Пример #1
0
    def get_inside_dim(self, cpacs_path):
        """Get user input from the CPACS file

        The function 'get_inside_dim' extracts from the CPACS file the required
        aircraft inside dimension, the code will use the default value when they are
        missing.

        Args:
            cpacs_path (str): Path to CPACS file

        """

        tixi = open_tixi(cpacs_path)

        # Get inside dimension from the CPACS file if exit
        self.seat_width = get_value_or_default(tixi, GEOM_XPATH + "/seatWidth",
                                               0.525)
        self.seat_length = get_value_or_default(tixi,
                                                GEOM_XPATH + "/seatLength",
                                                self.seat_length)
        self.aisle_width = get_value_or_default(tixi,
                                                GEOM_XPATH + "/aisleWidth",
                                                0.42)
        self.fuse_thick = get_value_or_default(tixi, GEOM_XPATH + "/fuseThick",
                                               6.63)
        self.toilet_length = get_value_or_default(tixi,
                                                  GEOM_XPATH + "/toiletLength",
                                                  self.toilet_length)

        tixi.save(cpacs_path)
Пример #2
0
def aircraft_name(tixi_or_cpacs):
    """The function get the name of the aircraft from the cpacs file or add a
        default one if non-existant.

    Args:
        cpacs_path (str): Path to the CPACS file

    Returns:
        name (str): Name of the aircraft.
    """

    # TODO: MODIFY this funtion, temporary it could accept a cpacs path or tixi handle
    # check xpath
    # *modify corresponding test

    if isinstance(tixi_or_cpacs, str):

        tixi = open_tixi(tixi_or_cpacs)

        aircraft_name_xpath = "/cpacs/header/name"
        name = get_value_or_default(tixi, aircraft_name_xpath, "Aircraft")

        tixi.save(tixi_or_cpacs)

    else:

        aircraft_name_xpath = "/cpacs/header/name"
        name = get_value_or_default(tixi_or_cpacs, aircraft_name_xpath,
                                    "Aircraft")

    name = name.replace(" ", "_")
    log.info("The name of the aircraft is : " + name)

    return name
Пример #3
0
def run_mesh_deformation(tixi, wkdir):
    """Function to run all the configuration files with SU2_DEF.

    Function 'run_mesh_deformation' will check in all config file directory and run
    SU2_DEF for each config file in order.

    Args:
        tixi (handles): TIXI Handle
        wkdir (str): Path to the working directory

    """

    log.info("All mesh deformation will be preformed.")

    mesh_dir = os.path.join(wkdir, "MESH")
    if not os.path.exists(mesh_dir):
        raise OSError("The MESH directory : " + mesh_dir + "does not exit!")
    os.chdir(mesh_dir)

    su2_def_mesh_list = []

    ted_dir_list = [dir for dir in os.listdir(mesh_dir) if "_TED_" in dir]

    # Get number of proc to use
    nb_proc = get_value_or_default(tixi, SU2_XPATH + "/settings/nbProc", 1)

    # Iterate in all TED directory
    for dir in sorted(ted_dir_list):
        ted_dir = os.path.join(mesh_dir, dir)
        os.chdir(ted_dir)

        cfg_file_list = [file for file in os.listdir(ted_dir) if "Config" in file]

        # Run all the config file in the directory with SU2_DEF, in alphabetical
        # order to respect the order of execution (DEF,ROT_,ROT_sym)
        for cfg_file in sorted(cfg_file_list):

            if os.path.isfile(cfg_file):
                run_soft("SU2_DEF", cfg_file, ted_dir, nb_proc)
            else:
                raise ValueError("Not correct configuration file to run!")

        tmp_su2_mesh_list = [file for file in os.listdir(ted_dir) if ".su2" in file]

        # Copy in the completely deform mesh in the MESH directory
        for su2_mesh in tmp_su2_mesh_list:
            if not su2_mesh.startswith("_"):
                shutil.copyfile(su2_mesh, os.path.join("..", su2_mesh))
                log.info(su2_mesh + " mesh has been copied in the MESH dir.")
                su2_def_mesh_list.append(su2_mesh)

            # Remove all SU2 mesh from the config folder (to save space)
            os.remove(su2_mesh)
            log.info(su2_mesh + " mesh has been deleted from the temp mesh.")

    # Add the list of available SU2 deformed mesh in the CPACS file
    su2_def_mesh_xpath = SU2_XPATH + "/availableDeformedMesh"
    add_string_vector(tixi, su2_def_mesh_xpath, su2_def_mesh_list)
Пример #4
0
def check_aeromap(tixi):
    """Check if aeromap is not used to train the model.

    To avoid re-writting results on the aeromap that was used to train the
    model, the uid of the training aeromap is compared to the one that is
    given by the user to be computed. Stops the program if they match.

    Args:
        tixi (Tixi handle): Handle of the current CPACS.

    Returns:
        None.

    """

    am_uid_use = get_value_or_default(tixi, SMUSE_XPATH + "/aeroMapUID", "")
    am_uid_train = get_value_or_default(tixi, SMTRAIN_XPATH + "/aeroMapUID", "")

    if am_uid_train == am_uid_use:
        sys.exit("Same aeromap that was used to create the model")
Пример #5
0
def get_normal_param(tixi, entry, outputs):
    """Add a variable to the optimisation dictionnary.

    It is checked if the variable has a user-specified initial value, else it
    will assign a default value or the variable will be excluded from the
    problem.

    Args:
        tixi (Tixi3 handle): Handle of the current CPACS file.
        entry (object): Current parameter object.

    """

    value = "-"
    xpath = entry.xpath
    def_val = entry.default_value

    if not def_val:
        if entry.var_type in [float, int]:
            def_val = 0.0
        else:
            def_val = "-"

    if entry.var_name not in BANNED_ENTRIES:
        value = get_value_or_default(tixi, xpath, def_val)
        if entry.var_type == int:
            value = int(value)

    if not is_digit(value):
        log.info("Not a digital value")
        value = "-"
    elif entry.var_type == bool:
        log.info("Boolean, not implemented yet")
        value = "-"

    # Ignores values that are not int or float
    if value != "-":
        value = str(value)
        tixi.updateTextElement(xpath, value)

        var["init"].append(value)
        var["xpath"].append(xpath)
        var["Name"].append(entry.var_name)

        add_type(entry, outputs, objective, var)
        add_bounds(value, var)
        log.info("Value : {}".format(value))
        log.info("Added to variable file")
Пример #6
0
def main(cpacs_path, cpacs_out_path):

    log.info("----- Start of " + os.path.basename(__file__) + " -----")

    # Load the model
    cpacs = CPACS(cpacs_path)
    Model = load_surrogate(cpacs.tixi)

    check_aeromap(cpacs.tixi)

    if get_value_or_default(cpacs.tixi, SMUSE_XPATH + "/AeroMapOnly", False):
        aeromap_calculation(Model.sm, cpacs)
    else:
        predict_output(Model, cpacs)

    cpacs.save_cpacs(cpacs_out_path, overwrite=True)

    log.info("----- End of " + os.path.basename(__file__) + " -----")
Пример #7
0
def main(cpacs_path, cpacs_out_path):

    log.info("----- Start of " + os.path.basename(__file__) + " -----")

    # Get number of proc to use
    tixi = open_tixi(cpacs_path)
    nb_proc = get_value_or_default(tixi, SU2_XPATH + "/settings/nbProc", 1)

    # Get results directory
    results_dir = get_results_directory("SU2Run")

    # Temporary CPACS to be stored after "generate_su2_cfd_config"
    cpacs_tmp_cfg = os.path.join(os.path.dirname(cpacs_out_path),
                                 "ConfigTMP.xml")

    # Execute SU2 functions
    generate_su2_cfd_config(cpacs_path, cpacs_tmp_cfg, results_dir)
    run_SU2_multi(results_dir, nb_proc)
    get_su2_results(cpacs_tmp_cfg, cpacs_out_path, results_dir)

    log.info("----- End of " + os.path.basename(__file__) + " -----")
Пример #8
0
def add_entries(tixi, module_list):
    """Add the entries of all the modules.

    Search all the entries that can be used as problem parameters and fills the
    variable dictionary with the valid entries.

    Args:
        tixi (Tixi3 handler): Tixi handle of the CPACS file.

    """

    use_am = get_value_or_default(tixi, SMUSE_XPATH + "/AeroMapOnly", False)
    if "SMUse" in module_list and use_am:
        get_aero_param(tixi)
    else:
        for mod_name, specs in get_all_module_specs().items():
            if specs and mod_name in module_list:
                if mod_name == "SMUse":
                    get_sm_vars(tixi)
                else:
                    get_module_vars(tixi, specs)
Пример #9
0
def load_surrogate(tixi):
    """Load a surrogate model object from file

    Using the pickle module, a surrogate model object is retrieved from a file
    provided by the user.

    Args:
        tixi (Tixi handle): Handle of the current CPACS.

    Returns:
        sm (object): The surrogate model.

    """

    file = get_value_or_default(tixi, SMUSE_XPATH + "/modelFile", "")

    log.info("Trying to open file" + file)
    with open(file, "rb") as f:
        Model = pickle.load(f)

    return Model
Пример #10
0
def generate_su2_cfd_config(cpacs_path, cpacs_out_path, wkdir):
    """Function to create SU2 confif file.

    Function 'generate_su2_cfd_config' reads data in the CPACS file and generate
    configuration files for one or multible flight conditions (alt,mach,aoa,aos)

    Source:
        * SU2 config template: https://github.com/su2code/SU2/blob/master/config_template.cfg

    Args:
        cpacs_path (str): Path to CPACS file
        cpacs_out_path (str):Path to CPACS output file
        wkdir (str): Path to the working directory

    """

    # Get value from CPACS
    cpacs = CPACS(cpacs_path)

    # Get SU2 mesh path
    su2_mesh_path = get_value(cpacs.tixi, SU2MESH_XPATH)

    # Get SU2 settings
    max_iter_xpath = SU2_XPATH + "/settings/maxIter"
    max_iter = get_value_or_default(cpacs.tixi, max_iter_xpath, 200)
    cfl_nb_xpath = SU2_XPATH + "/settings/cflNumber"
    cfl_nb = get_value_or_default(cpacs.tixi, cfl_nb_xpath, 1.0)
    mg_level_xpath = SU2_XPATH + "/settings/multigridLevel"
    mg_level = get_value_or_default(cpacs.tixi, mg_level_xpath, 3)

    # Mesh Marker
    bc_wall_xpath = SU2_XPATH + "/boundaryConditions/wall"
    bc_farfield_xpath = SU2_XPATH + "/boundaryConditions/farfield"
    bc_wall_list, engine_bc_list = get_mesh_marker(su2_mesh_path)

    create_branch(cpacs.tixi, bc_wall_xpath)
    bc_wall_str = ";".join(bc_wall_list)
    cpacs.tixi.updateTextElement(bc_wall_xpath, bc_wall_str)

    create_branch(cpacs.tixi, bc_farfield_xpath)
    bc_farfiled_str = ";".join(engine_bc_list)
    cpacs.tixi.updateTextElement(bc_farfield_xpath, bc_farfiled_str)

    # Fixed CL parameters
    fixed_cl_xpath = SU2_XPATH + "/fixedCL"
    fixed_cl = get_value_or_default(cpacs.tixi, fixed_cl_xpath, "NO")
    target_cl_xpath = SU2_XPATH + "/targetCL"
    target_cl = get_value_or_default(cpacs.tixi, target_cl_xpath, 1.0)

    if fixed_cl == "NO":
        active_aeroMap_xpath = SU2_XPATH + "/aeroMapUID"
        aeromap_uid = get_value(cpacs.tixi, active_aeroMap_xpath)

        log.info(
            f'Configuration file for "{aeromap_uid}" calculation will be created.'
        )

        active_aeromap = cpacs.get_aeromap_by_uid(aeromap_uid)

        # Get parameters of the aeroMap (altitude, machNumber, angleOfAttack, angleOfSideslip)
        alt_list = active_aeromap.get("altitude").tolist()
        mach_list = active_aeromap.get("machNumber").tolist()
        aoa_list = active_aeromap.get("angleOfAttack").tolist()
        aos_list = active_aeromap.get("angleOfSideslip").tolist()

        param_count = len(alt_list)

    else:  # if fixed_cl == 'YES':
        log.info(
            "Configuration file for fixed CL calculation will be created.")

        # Parameters fixed CL calulation
        param_count = 1

        # Create a new aeroMap
        fix_cl_aeromap = cpacs.create_aeromap("aeroMap_fixedCL_SU2")
        fix_cl_aeromap.description = "AeroMap created for SU2 fixed CL value of: " + str(
            target_cl)

        # Get cruise mach and altitude
        cruise_mach_xpath = RANGE_XPATH + "/cruiseMach"
        mach = get_value_or_default(cpacs.tixi, cruise_mach_xpath, 0.78)
        cruise_alt_xpath = RANGE_XPATH + "/cruiseAltitude"
        alt = get_value_or_default(cpacs.tixi, cruise_alt_xpath, 12000)

        # Add new parameters to the aeroMap and save it
        fix_cl_aeromap.add_row(alt=alt, mach=mach, aos=0.0, aoa=0.0)
        fix_cl_aeromap.save()

        # Parameter lists
        alt_list = [alt]
        mach_list = [mach]
        aoa_list = [0.0]
        aos_list = [0.0]

    # Get and modify the default configuration file
    cfg = ConfigFile(DEFAULT_CONFIG_PATH)

    # General parmeters
    cfg["REF_LENGTH"] = cpacs.aircraft.ref_lenght
    cfg["REF_AREA"] = cpacs.aircraft.ref_area
    cfg["REF_ORIGIN_MOMENT_X"] = cpacs.aircraft.ref_point_x
    cfg["REF_ORIGIN_MOMENT_Y"] = cpacs.aircraft.ref_point_y
    cfg["REF_ORIGIN_MOMENT_Z"] = cpacs.aircraft.ref_point_z

    # Settings
    cfg["INNER_ITER"] = int(max_iter)
    cfg["CFL_NUMBER"] = cfl_nb
    cfg["MGLEVEL"] = int(mg_level)

    # Fixed CL mode (AOA will not be taken into account)
    cfg["FIXED_CL_MODE"] = fixed_cl
    cfg["TARGET_CL"] = target_cl
    cfg["DCL_DALPHA"] = "0.1"
    cfg["UPDATE_AOA_ITER_LIMIT"] = "50"
    cfg["ITER_DCL_DALPHA"] = "80"
    # TODO: correct value for the 3 previous parameters ??

    # Mesh Marker
    bc_wall_str = "(" + ",".join(bc_wall_list) + ")"
    cfg["MARKER_EULER"] = bc_wall_str
    cfg["MARKER_FAR"] = " (Farfield, " + ",".join(engine_bc_list) + ")"
    cfg["MARKER_SYM"] = " (0)"  # TODO: maybe make that a variable?
    cfg["MARKER_PLOTTING"] = bc_wall_str
    cfg["MARKER_MONITORING"] = bc_wall_str
    cfg["MARKER_MOVING"] = "( NONE )"  # TODO: when do we need to define MARKER_MOVING?
    cfg["DV_MARKER"] = bc_wall_str

    # Parameters which will vary for the different cases (alt,mach,aoa,aos)
    for case_nb in range(param_count):

        cfg["MESH_FILENAME"] = su2_mesh_path

        alt = alt_list[case_nb]
        mach = mach_list[case_nb]
        aoa = aoa_list[case_nb]
        aos = aos_list[case_nb]

        Atm = Atmosphere(alt)

        cfg["MACH_NUMBER"] = mach
        cfg["AOA"] = aoa
        cfg["SIDESLIP_ANGLE"] = aos
        cfg["FREESTREAM_PRESSURE"] = Atm.pressure[0]
        cfg["FREESTREAM_TEMPERATURE"] = Atm.temperature[0]
        cfg["ROTATION_RATE"] = "0.0 0.0 0.0"

        config_file_name = "ConfigCFD.cfg"

        case_dir_name = "".join([
            "Case",
            str(case_nb).zfill(2),
            "_alt",
            str(alt),
            "_mach",
            str(round(mach, 2)),
            "_aoa",
            str(round(aoa, 1)),
            "_aos",
            str(round(aos, 1)),
        ])

        case_dir_path = os.path.join(wkdir, case_dir_name)
        if not os.path.isdir(case_dir_path):
            os.mkdir(case_dir_path)

        config_output_path = os.path.join(wkdir, case_dir_name,
                                          config_file_name)
        cfg.write_file(config_output_path, overwrite=True)

        # Damping derivatives
        damping_der_xpath = SU2_XPATH + "/options/clalculateDampingDerivatives"
        damping_der = get_value_or_default(cpacs.tixi, damping_der_xpath,
                                           False)

        if damping_der:

            rotation_rate_xpath = SU2_XPATH + "/options/rotationRate"
            rotation_rate = get_value_or_default(cpacs.tixi,
                                                 rotation_rate_xpath, 1.0)

            cfg["GRID_MOVEMENT"] = "ROTATING_FRAME"

            cfg["ROTATION_RATE"] = str(rotation_rate) + " 0.0 0.0"
            os.mkdir(os.path.join(wkdir, case_dir_name + "_dp"))
            config_output_path = os.path.join(wkdir, case_dir_name + "_dp",
                                              config_file_name)
            cfg.write_file(config_output_path, overwrite=True)

            cfg["ROTATION_RATE"] = "0.0 " + str(rotation_rate) + " 0.0"
            os.mkdir(os.path.join(wkdir, case_dir_name + "_dq"))
            config_output_path = os.path.join(wkdir, case_dir_name + "_dq",
                                              config_file_name)
            cfg.write_file(config_output_path, overwrite=True)

            cfg["ROTATION_RATE"] = "0.0 0.0 " + str(rotation_rate)
            os.mkdir(os.path.join(wkdir, case_dir_name + "_dr"))
            config_output_path = os.path.join(wkdir, case_dir_name + "_dr",
                                              config_file_name)
            cfg.write_file(config_output_path, overwrite=True)
            log.info("Damping derivatives cases directory has been created.")

        # Control surfaces deflections
        control_surf_xpath = SU2_XPATH + "/options/clalculateCotrolSurfacesDeflections"
        control_surf = get_value_or_default(cpacs.tixi, control_surf_xpath,
                                            False)

        if control_surf:

            # Get deformed mesh list
            su2_def_mesh_xpath = SU2_XPATH + "/availableDeformedMesh"
            if cpacs.tixi.checkElement(su2_def_mesh_xpath):
                su2_def_mesh_list = get_string_vector(cpacs.tixi,
                                                      su2_def_mesh_xpath)
            else:
                log.warning("No SU2 deformed mesh has been found!")
                su2_def_mesh_list = []

            for su2_def_mesh in su2_def_mesh_list:

                mesh_path = os.path.join(wkdir, "MESH", su2_def_mesh)

                config_dir_path = os.path.join(
                    wkdir, case_dir_name + "_" + su2_def_mesh.split(".")[0])
                os.mkdir(config_dir_path)
                cfg["MESH_FILENAME"] = mesh_path

                config_file_name = "ConfigCFD.cfg"
                config_output_path = os.path.join(wkdir, config_dir_path,
                                                  config_file_name)
                cfg.write_file(config_output_path, overwrite=True)

    # TODO: change that, but if it is save in tooloutput it will be erease by results...
    cpacs.save_cpacs(cpacs_out_path, overwrite=True)
Пример #11
0
    def get_user_inputs(self, tixi):
        """Take user inputs from the GUI."""

        # Problem setup
        objectives = get_value_or_default(tixi, OPTIM_XPATH + "/objective",
                                          "cl")
        self.objective = split(";|,", objectives)
        self.minmax = get_value_or_default(tixi, OPTIM_XPATH + "/minmax",
                                           "max")

        # Global parameters
        self.driver = get_value_or_default(tixi,
                                           OPTIM_XPATH + "/parameters/driver",
                                           "COBYLA")
        self.max_iter = int(
            get_value_or_default(tixi, OPTIM_XPATH + "/iterationNB", 200))
        self.tol = float(
            get_value_or_default(tixi, OPTIM_XPATH + "/tolerance", 1e-3))
        self.save_iter = int(
            get_value_or_default(tixi, OPTIM_XPATH + "/saving/perIter", 1))

        # Specific DoE parameters
        self.doedriver = get_value_or_default(
            tixi, OPTIM_XPATH + "/parameters/DoE/driver", "Uniform")
        self.samplesnb = int(
            get_value_or_default(tixi,
                                 OPTIM_XPATH + "/parameters/DoE/sampleNB", 3))

        # User specified configuration file path
        self.user_config = str(
            get_value_or_default(tixi, OPTIM_XPATH + "/Config/filepath", "-"))

        fix_cl = get_value_or_default(
            tixi, "/cpacs/toolspecific/CEASIOMpy/aerodynamics/su2/fixedCL",
            "no")
        if fix_cl == "YES":
            tixi.updateTextElement(OPTIM_XPATH + "/aeroMapUID",
                                   "aeroMap_fixedCL_SU2")
            self.aeromap_uid = "aeroMap_fixedCL_SU2"
        else:
            self.aeromap_uid = str(
                get_value_or_default(tixi, OPTIM_XPATH + "/aeroMapUID", "-"))

        self.use_aeromap = get_value_or_default(
            tixi, OPTIM_XPATH + "/Config/useAero", False)
Пример #12
0
def main(cpacs_in_path, cpacs_out_path):

    log.info("Running PyTornado...")

    # ===== Delete old working directories =====
    settings_from_CPACS = get_pytornado_settings_from_CPACS(cpacs_in_path)
    if settings_from_CPACS is not None:
        if settings_from_CPACS.get("deleteOldWKDIRs", False):
            wkdirs = glob(os.path.join(DIR_MODULE, "wkdir_*"))
            for wkdir in wkdirs:
                shutil.rmtree(wkdir, ignore_errors=True)

    # ===== Paths =====
    dir_pyt_wkdir = os.path.join(DIR_MODULE, "wkdir_temp")

    dir_pyt_aircraft = os.path.join(dir_pyt_wkdir, "aircraft")
    dir_pyt_settings = os.path.join(dir_pyt_wkdir, "settings")
    dir_pyt_results = os.path.join(dir_pyt_wkdir, "_results")
    file_pyt_aircraft = os.path.join(dir_pyt_aircraft, "ToolInput.xml")
    file_pyt_settings = os.path.join(dir_pyt_settings, "cpacs_run.json")

    # ===== Make directories =====
    Path(dir_pyt_wkdir).mkdir(parents=True, exist_ok=True)
    Path(dir_pyt_aircraft).mkdir(parents=True, exist_ok=True)
    Path(dir_pyt_settings).mkdir(parents=True, exist_ok=True)
    Path(dir_pyt_results).mkdir(parents=True, exist_ok=True)

    # ===== Setup =====
    shutil.copy(src=cpacs_in_path, dst=file_pyt_aircraft)
    mi.check_cpacs_input_requirements(cpacs_in_path)

    # ===== Get PyTornado settings =====
    cpacs_settings = get_pytornado_settings(cpacs_in_path)
    with open(file_pyt_settings, "w") as fp:
        dump_pretty_json(cpacs_settings, fp)

    # ===== PyTornado analysis =====
    pytornado = import_pytornado("pytornado.stdfun.run")
    # pytornado.standard_run(args=pytornado.StdRunArgs(run=file_pyt_settings, verbose=True))
    results = pytornado.standard_run(
        args=pytornado.StdRunArgs(run=file_pyt_settings, verbose=True))

    # ===== Extract load =====
    tixi = open_tixi(cpacs_in_path)
    extract_loads_xpath = "/cpacs/toolspecific/pytornado/save_results/extractLoads"
    extract_loads = get_value_or_default(tixi, extract_loads_xpath, False)

    if extract_loads:
        _get_load_fields(results, dir_pyt_results)

    # ===== Clean up =====
    shutil.copy(src=file_pyt_aircraft, dst=cpacs_out_path)

    # ===== Copy files in the wkflow results directory =====
    # TODO: use dirs_exist_ok=True option when  python >=3.8 and remove "tmp"
    dst_pyt_wkdir = Path(get_results_directory("PyTornado"), "tmp")
    if os.path.isdir(dst_pyt_wkdir):
        shutil.rmtree(dst_pyt_wkdir)
    shutil.copytree(src=dir_pyt_wkdir, dst=dst_pyt_wkdir)
    shutil.rmtree(dir_pyt_wkdir, ignore_errors=True)

    log.info("PyTornado analysis completed")
Пример #13
0
def plot_aero_coef(cpacs_path, cpacs_out_path):
    """Plot Aero coefficients from the chosen aeroMap in the CPACS file

    Function 'plot_aero_coef' can plot one or several aeromap from the CPACS
    file according to some user option, these option will be shown in the the
    SettingGUI or default values will be used.

    Args:
        cpacs_path (str): Path to CPACS file
        cpacs_out_path (str):Path to CPACS output file
    """

    # Open TIXI handle
    cpacs = CPACS(cpacs_path)

    # Get aeroMap list to plot
    aeromap_to_plot_xpath = PLOT_XPATH + "/aeroMapToPlot"
    aeromap_uid_list = []

    # Option to select aeromap manualy
    manual_selct = get_value_or_default(cpacs.tixi, PLOT_XPATH + "/manualSelection", False)
    if manual_selct:
        aeromap_uid_list = open_select_aeromap_gui(cpacs)
        create_branch(cpacs.tixi, aeromap_to_plot_xpath)
        add_string_vector(cpacs.tixi, aeromap_to_plot_xpath, aeromap_uid_list)

    else:
        try:
            aeromap_uid_list = get_string_vector(cpacs.tixi, aeromap_to_plot_xpath)
        except ValueError:
            # If aeroMapToPlot is not define, select manualy anyway
            aeromap_uid_list = open_select_aeromap_gui(cpacs)
            create_branch(cpacs.tixi, aeromap_to_plot_xpath)
            add_string_vector(cpacs.tixi, aeromap_to_plot_xpath, aeromap_uid_list)

    # Create DataFrame from aeromap(s)
    aeromap_df_list = []
    for aeromap_uid in aeromap_uid_list:
        aeromap_df = cpacs.get_aeromap_by_uid(aeromap_uid).df
        aeromap_df["uid"] = aeromap_uid
        aeromap_df_list.append(aeromap_df)

    aeromap = pd.concat(aeromap_df_list, ignore_index=True)

    if len(aeromap_uid_list) > 1:
        uid_crit = None
    else:
        uid_crit = aeromap_uid_list[0]

    # Default options
    title = cpacs.ac_name
    criterion = pd.Series([True] * len(aeromap.index))
    groupby_list = ["uid", "machNumber", "altitude", "angleOfSideslip"]

    # Get criterion from CPACS
    crit_xpath = PLOT_XPATH + "/criterion"
    alt_crit = get_value_or_default(cpacs.tixi, crit_xpath + "/alt", "None")
    mach_crit = get_value_or_default(cpacs.tixi, crit_xpath + "/mach", "None")
    aos_crit = get_value_or_default(cpacs.tixi, crit_xpath + "/aos", "None")

    cpacs.save_cpacs(cpacs_out_path, overwrite=True)

    # Modify criterion and title according to user option
    if len(aeromap["altitude"].unique()) == 1:
        title += " - Alt = " + str(aeromap["altitude"].loc[0])
        groupby_list.remove("altitude")
    elif alt_crit not in NONE_LIST:
        criterion = criterion & (aeromap.altitude == alt_crit)
        title += " - Alt = " + str(alt_crit)
        groupby_list.remove("altitude")

    if len(aeromap["machNumber"].unique()) == 1:
        title += " - Mach = " + str(aeromap["machNumber"].loc[0])
        groupby_list.remove("machNumber")
    elif mach_crit not in NONE_LIST:
        criterion = criterion & (aeromap.machNumber == mach_crit)
        title += " - Mach = " + str(mach_crit)
        groupby_list.remove("machNumber")

    if len(aeromap["angleOfSideslip"].unique()) == 1:
        title += " - AoS = " + str(aeromap["angleOfSideslip"].loc[0])
        groupby_list.remove("angleOfSideslip")
    elif aos_crit not in NONE_LIST:
        criterion = criterion & (aeromap.angleOfSideslip == aos_crit)
        title += " - AoS = " + str(aos_crit)
        groupby_list.remove("angleOfSideslip")

    if uid_crit is not None and len(groupby_list) > 1:
        criterion = criterion & (aeromap.uid == uid_crit)
        title += " - " + uid_crit
        groupby_list.remove("uid")

    # Plot settings
    fig, axs = plt.subplots(2, 3)
    fig.suptitle(title, fontsize=14)
    fig.set_figheight(8)
    fig.set_figwidth(15)
    fig.subplots_adjust(left=0.06)
    axs[0, 1].axhline(y=0.0, color="k", linestyle="-")  # Line at Cm=0

    # Plot aerodynamic coerfficients
    for value, grp in aeromap.loc[criterion].groupby(groupby_list):

        legend = write_legend(groupby_list, value)

        axs[0, 0].plot(grp["angleOfAttack"], grp["cl"], "x-", label=legend)
        axs[1, 0].plot(grp["angleOfAttack"], grp["cd"], "x-")
        axs[0, 1].plot(grp["angleOfAttack"], grp["cms"], "x-")
        axs[1, 1].plot(grp["angleOfAttack"], grp["cl"] / grp["cd"], "x-")
        axs[0, 2].plot(grp["cd"], grp["cl"], "x-")
        axs[1, 2].plot(grp["cl"], grp["cl"] / grp["cd"], "x-")

    # Set subplot options
    subplot_options(axs[0, 0], "CL", "AoA")
    subplot_options(axs[1, 0], "CD", "AoA")
    subplot_options(axs[0, 1], "Cm", "AoA")
    subplot_options(axs[1, 1], "CL/CD", "AoA")
    subplot_options(axs[0, 2], "CL", "CD")
    subplot_options(axs[1, 2], "CL/CD", "CL")

    fig.legend(loc="upper right")
    plt.show()
Пример #14
0
def convert_cpacs_to_sumo(cpacs_path, cpacs_out_path):
    """Function to convert a CPACS file geometry into a SUMO file geometry.

    Function 'convert_cpacs_to_sumo' open an input cpacs file with TIXI handle
    and via two main loop, one for fuselage(s), one for wing(s) it convert
    every element (as much as possible) in the SUMO (.smx) format, which is
    also an xml file. Due to some differences between both format, some CPACS
    definition could lead to issues. The output sumo file is saved in the
    folder /ToolOutput

    Source:
        * CPACS documentation: https://www.cpacs.de/pages/documentation.html

    Args:
        cpacs_path (str): Path to the CPACS file

    Returns:
        sumo_output_path (str): Path to the SUMO file

    """

    EMPTY_SMX = MODULE_DIR + "/files/sumo_empty.smx"

    tixi = open_tixi(cpacs_path)
    sumo = open_tixi(EMPTY_SMX)

    # Fuslage(s) ---------------------------------------------------------------

    if tixi.checkElement(FUSELAGES_XPATH):
        fus_cnt = tixi.getNamedChildrenCount(FUSELAGES_XPATH, "fuselage")
        log.info(str(fus_cnt) + " fuselage has been found.")
    else:
        fus_cnt = 0
        log.warning("No fuselage has been found in this CPACS file!")

    for i_fus in range(fus_cnt):
        fus_xpath = FUSELAGES_XPATH + "/fuselage[" + str(i_fus + 1) + "]"
        fus_uid = tixi.getTextAttribute(fus_xpath, "uID")
        fus_transf = Transformation()
        fus_transf.get_cpacs_transf(tixi, fus_xpath)

        # Create new body (SUMO)
        sumo.createElementAtIndex("/Assembly", "BodySkeleton", i_fus + 1)
        body_xpath = "/Assembly/BodySkeleton[" + str(i_fus + 1) + "]"

        sumo.addTextAttribute(body_xpath, "akimatg", "false")
        sumo.addTextAttribute(body_xpath, "name", fus_uid)

        body_tansf = Transformation()
        body_tansf.translation = fus_transf.translation

        # Convert angles
        body_tansf.rotation = euler2fix(fus_transf.rotation)

        # Add body rotation
        body_rot_str = (str(math.radians(body_tansf.rotation.x)) + " " +
                        str(math.radians(body_tansf.rotation.y)) + " " +
                        str(math.radians(body_tansf.rotation.z)))
        sumo.addTextAttribute(body_xpath, "rotation", body_rot_str)

        # Add body origin
        body_ori_str = (str(body_tansf.translation.x) + " " +
                        str(body_tansf.translation.y) + " " +
                        str(body_tansf.translation.z))
        sumo.addTextAttribute(body_xpath, "origin", body_ori_str)

        # Positionings
        if tixi.checkElement(fus_xpath + "/positionings"):
            pos_cnt = tixi.getNamedChildrenCount(fus_xpath + "/positionings",
                                                 "positioning")
            log.info(str(fus_cnt) + ' "Positionning" has been found : ')

            pos_x_list = []
            pos_y_list = []
            pos_z_list = []
            from_sec_list = []
            to_sec_list = []

            for i_pos in range(pos_cnt):
                pos_xpath = fus_xpath + "/positionings/positioning[" + str(
                    i_pos + 1) + "]"

                length = tixi.getDoubleElement(pos_xpath + "/length")
                sweep_deg = tixi.getDoubleElement(pos_xpath + "/sweepAngle")
                sweep = math.radians(sweep_deg)
                dihedral_deg = tixi.getDoubleElement(pos_xpath +
                                                     "/dihedralAngle")
                dihedral = math.radians(dihedral_deg)

                # Get the corresponding translation of each positionning
                pos_x_list.append(length * math.sin(sweep))
                pos_y_list.append(length * math.cos(dihedral) *
                                  math.cos(sweep))
                pos_z_list.append(length * math.sin(dihedral) *
                                  math.cos(sweep))

                # Get which section are connected by the positionning
                if tixi.checkElement(pos_xpath + "/fromSectionUID"):
                    from_sec = tixi.getTextElement(pos_xpath +
                                                   "/fromSectionUID")
                else:
                    from_sec = ""
                from_sec_list.append(from_sec)

                if tixi.checkElement(pos_xpath + "/toSectionUID"):
                    to_sec = tixi.getTextElement(pos_xpath + "/toSectionUID")
                else:
                    to_sec = ""
                to_sec_list.append(to_sec)

            # Re-loop though the positionning to re-order them
            for j_pos in range(pos_cnt):
                if from_sec_list[j_pos] == "":
                    prev_pos_x = 0
                    prev_pos_y = 0
                    prev_pos_z = 0

                elif from_sec_list[j_pos] == to_sec_list[j_pos - 1]:
                    prev_pos_x = pos_x_list[j_pos - 1]
                    prev_pos_y = pos_y_list[j_pos - 1]
                    prev_pos_z = pos_z_list[j_pos - 1]

                else:
                    index_prev = to_sec_list.index(from_sec_list[j_pos])
                    prev_pos_x = pos_x_list[index_prev]
                    prev_pos_y = pos_y_list[index_prev]
                    prev_pos_z = pos_z_list[index_prev]

                pos_x_list[j_pos] += prev_pos_x
                pos_y_list[j_pos] += prev_pos_y
                pos_z_list[j_pos] += prev_pos_z

        else:
            log.warning('No "positionings" have been found!')
            pos_cnt = 0

        # Sections
        sec_cnt = tixi.getNamedChildrenCount(fus_xpath + "/sections",
                                             "section")
        log.info("    -" + str(sec_cnt) + " fuselage sections have been found")

        if pos_cnt == 0:
            pos_x_list = [0.0] * sec_cnt
            pos_y_list = [0.0] * sec_cnt
            pos_z_list = [0.0] * sec_cnt

        for i_sec in range(sec_cnt):
            sec_xpath = fus_xpath + "/sections/section[" + str(i_sec + 1) + "]"
            sec_uid = tixi.getTextAttribute(sec_xpath, "uID")

            sec_transf = Transformation()
            sec_transf.get_cpacs_transf(tixi, sec_xpath)

            if sec_transf.rotation.x or sec_transf.rotation.y or sec_transf.rotation.z:

                log.warning('Sections "' + sec_uid + '" is rotated, it is \
                            not possible to take that into acount in SUMO !')

            # Elements
            elem_cnt = tixi.getNamedChildrenCount(sec_xpath + "/elements",
                                                  "element")

            if elem_cnt > 1:
                log.warning("Sections " + sec_uid + "  contains multiple \
                             element, it could be an issue for the conversion \
                             to SUMO!")

            for i_elem in range(elem_cnt):
                elem_xpath = sec_xpath + "/elements/element[" + str(i_elem +
                                                                    1) + "]"
                elem_uid = tixi.getTextAttribute(elem_xpath, "uID")

                elem_transf = Transformation()
                elem_transf.get_cpacs_transf(tixi, elem_xpath)

                if elem_transf.rotation.x or elem_transf.rotation.y or elem_transf.rotation.z:
                    log.warning('Element "' + elem_uid + '" is rotated, it \
                                 is not possible to take that into acount in \
                                 SUMO !')

                # Fuselage profiles
                prof_uid = tixi.getTextElement(elem_xpath + "/profileUID")
                prof_vect_x, prof_vect_y, prof_vect_z = get_profile_coord(
                    tixi, prof_uid)

                prof_size_y = (max(prof_vect_y) - min(prof_vect_y)) / 2
                prof_size_z = (max(prof_vect_z) - min(prof_vect_z)) / 2

                prof_vect_y[:] = [y / prof_size_y for y in prof_vect_y]
                prof_vect_z[:] = [z / prof_size_z for z in prof_vect_z]

                prof_min_y = min(prof_vect_y)
                prof_min_z = min(prof_vect_z)

                prof_vect_y[:] = [y - 1 - prof_min_y for y in prof_vect_y]
                prof_vect_z[:] = [z - 1 - prof_min_z for z in prof_vect_z]

                # Could be a problem if they are less positionings than secions
                # TODO: solve that!
                pos_y_list[i_sec] += (
                    (1 + prof_min_y) * prof_size_y) * elem_transf.scaling.y
                pos_z_list[i_sec] += (
                    (1 + prof_min_z) * prof_size_z) * elem_transf.scaling.z

                # #To Plot a particular section
                # if i_sec==5:
                #     plt.plot(prof_vect_z, prof_vect_y,'x')
                #     plt.xlabel('y')
                #     plt.ylabel('z')
                #     plt.grid(True)
                #     plt.show

                # Put value in SUMO format
                body_frm_center_x = (elem_transf.translation.x +
                                     sec_transf.translation.x +
                                     pos_x_list[i_sec]) * fus_transf.scaling.x
                body_frm_center_y = (
                    elem_transf.translation.y * sec_transf.scaling.y +
                    sec_transf.translation.y +
                    pos_y_list[i_sec]) * fus_transf.scaling.y
                body_frm_center_z = (
                    elem_transf.translation.z * sec_transf.scaling.z +
                    sec_transf.translation.z +
                    pos_z_list[i_sec]) * fus_transf.scaling.z

                body_frm_height = (prof_size_z * 2 * elem_transf.scaling.z *
                                   sec_transf.scaling.z * fus_transf.scaling.z)

                if body_frm_height < 0.01:
                    body_frm_height = 0.01
                body_frm_width = (prof_size_y * 2 * elem_transf.scaling.y *
                                  sec_transf.scaling.y * fus_transf.scaling.y)
                if body_frm_width < 0.01:
                    body_frm_width = 0.01

                # Convert the profile points in the SMX format
                prof_str = ""
                teta_list = []
                teta_half = []
                prof_vect_y_half = []
                prof_vect_z_half = []
                check_max = 0
                check_min = 0

                # Use polar angle to keep point in the correct order
                for i, item in enumerate(prof_vect_y):
                    teta_list.append(math.atan2(prof_vect_z[i],
                                                prof_vect_y[i]))

                for t, teta in enumerate(teta_list):
                    HALF_PI = math.pi / 2
                    EPSILON = 0.04

                    if abs(teta) <= HALF_PI - EPSILON:
                        teta_half.append(teta)
                        prof_vect_y_half.append(prof_vect_y[t])
                        prof_vect_z_half.append(prof_vect_z[t])
                    elif abs(teta) < HALF_PI + EPSILON:
                        # Check if not the last element of the list
                        if not t == len(teta_list) - 1:
                            next_val = prof_vect_z[t + 1]
                            # Check if it is better to keep next point
                            if not abs(next_val) > abs(prof_vect_z[t]):
                                if prof_vect_z[t] > 0 and not check_max:
                                    teta_half.append(teta)
                                    # Force y=0, to get symmetrical profile
                                    prof_vect_y_half.append(0)
                                    prof_vect_z_half.append(prof_vect_z[t])
                                    check_max = 1
                                elif prof_vect_z[t] < 0 and not check_min:
                                    teta_half.append(teta)
                                    # Force y=0, to get symmetrical profile
                                    prof_vect_y_half.append(0)
                                    prof_vect_z_half.append(prof_vect_z[t])
                                    check_min = 1

                # Sort points by teta value, to fit the SUMO profile format
                teta_half, prof_vect_z_half, prof_vect_y_half = (
                    list(t) for t in zip(*sorted(
                        zip(teta_half, prof_vect_z_half, prof_vect_y_half))))

                # Write profile as a string and add y=0 point at the begining
                # and at the end to ensure symmmetry
                if not check_min:
                    prof_str += str(0) + " " + str(prof_vect_z_half[0]) + " "
                for i, item in enumerate(prof_vect_z_half):
                    prof_str += (str(round(prof_vect_y_half[i], 4)) + " " +
                                 str(round(prof_vect_z_half[i], 4)) + " ")
                if not check_max:
                    prof_str += str(0) + " " + str(prof_vect_z_half[i]) + " "

                # Write the SUMO file
                sumo.addTextElementAtIndex(body_xpath, "BodyFrame", prof_str,
                                           i_sec + 1)
                frame_xpath = body_xpath + "/BodyFrame[" + str(i_sec + 1) + "]"

                body_center_str = (str(body_frm_center_x) + " " +
                                   str(body_frm_center_y) + " " +
                                   str(body_frm_center_z))

                sumo.addTextAttribute(frame_xpath, "center", body_center_str)
                sumo.addTextAttribute(frame_xpath, "height",
                                      str(body_frm_height))
                sumo.addTextAttribute(frame_xpath, "width",
                                      str(body_frm_width))
                sumo.addTextAttribute(frame_xpath, "name", sec_uid)

        # Fuselage symmetry (mirror copy)
        if tixi.checkAttribute(fus_xpath, "symmetry"):
            if tixi.getTextAttribute(fus_xpath, "symmetry") == "x-z-plane":
                sumo_mirror_copy(sumo, body_xpath, fus_uid, False)

    # To remove the default BodySkeleton
    if fus_cnt == 0:
        sumo.removeElement("/Assembly/BodySkeleton")
    else:
        sumo.removeElement("/Assembly/BodySkeleton[" + str(fus_cnt + 1) + "]")

    # Wing(s) ------------------------------------------------------------------

    if tixi.checkElement(WINGS_XPATH):
        wing_cnt = tixi.getNamedChildrenCount(WINGS_XPATH, "wing")
        log.info(str(wing_cnt) + " wings has been found.")
    else:
        wing_cnt = 0
        log.warning("No wings has been found in this CPACS file!")

    for i_wing in range(wing_cnt):
        wing_xpath = WINGS_XPATH + "/wing[" + str(i_wing + 1) + "]"
        wing_uid = tixi.getTextAttribute(wing_xpath, "uID")
        wing_transf = Transformation()
        wing_transf.get_cpacs_transf(tixi, wing_xpath)

        # Create new wing (SUMO)
        sumo.createElementAtIndex("/Assembly", "WingSkeleton", i_wing + 1)
        wg_sk_xpath = "/Assembly/WingSkeleton[" + str(i_wing + 1) + "]"

        sumo.addTextAttribute(wg_sk_xpath, "akimatg", "false")
        sumo.addTextAttribute(wg_sk_xpath, "name", wing_uid)

        # Create a class for the transformation of the WingSkeleton
        wg_sk_tansf = Transformation()

        # Convert WingSkeleton rotation and add it to SUMO
        wg_sk_tansf.rotation = euler2fix(wing_transf.rotation)
        wg_sk_rot_str = (str(math.radians(wg_sk_tansf.rotation.x)) + " " +
                         str(math.radians(wg_sk_tansf.rotation.y)) + " " +
                         str(math.radians(wg_sk_tansf.rotation.z)))
        sumo.addTextAttribute(wg_sk_xpath, "rotation", wg_sk_rot_str)

        # Add WingSkeleton origin
        wg_sk_tansf.translation = wing_transf.translation
        wg_sk_ori_str = (str(wg_sk_tansf.translation.x) + " " +
                         str(wg_sk_tansf.translation.y) + " " +
                         str(wg_sk_tansf.translation.z))
        sumo.addTextAttribute(wg_sk_xpath, "origin", wg_sk_ori_str)

        if tixi.checkAttribute(wing_xpath, "symmetry"):
            if tixi.getTextAttribute(wing_xpath, "symmetry") == "x-z-plane":
                sumo.addTextAttribute(wg_sk_xpath, "flags",
                                      "autosym,detectwinglet")
            else:
                sumo.addTextAttribute(wg_sk_xpath, "flags", "detectwinglet")

        # Positionings
        if tixi.checkElement(wing_xpath + "/positionings"):
            pos_cnt = tixi.getNamedChildrenCount(wing_xpath + "/positionings",
                                                 "positioning")
            log.info(str(wing_cnt) + ' "positionning" has been found : ')

            pos_x_list = []
            pos_y_list = []
            pos_z_list = []
            from_sec_list = []
            to_sec_list = []

            for i_pos in range(pos_cnt):
                pos_xpath = wing_xpath + "/positionings/positioning[" + str(
                    i_pos + 1) + "]"

                length = tixi.getDoubleElement(pos_xpath + "/length")
                sweep_deg = tixi.getDoubleElement(pos_xpath + "/sweepAngle")
                sweep = math.radians(sweep_deg)
                dihedral_deg = tixi.getDoubleElement(pos_xpath +
                                                     "/dihedralAngle")
                dihedral = math.radians(dihedral_deg)

                # Get the corresponding translation of each positionning
                pos_x_list.append(length * math.sin(sweep))
                pos_y_list.append(length * math.cos(dihedral) *
                                  math.cos(sweep))
                pos_z_list.append(length * math.sin(dihedral) *
                                  math.cos(sweep))

                # Get which section are connected by the positionning
                if tixi.checkElement(pos_xpath + "/fromSectionUID"):
                    from_sec = tixi.getTextElement(pos_xpath +
                                                   "/fromSectionUID")
                else:
                    from_sec = ""
                from_sec_list.append(from_sec)

                if tixi.checkElement(pos_xpath + "/toSectionUID"):
                    to_sec = tixi.getTextElement(pos_xpath + "/toSectionUID")
                else:
                    to_sec = ""
                to_sec_list.append(to_sec)

            # Re-loop though the positionning to re-order them
            for j_pos in range(pos_cnt):
                if from_sec_list[j_pos] == "":
                    prev_pos_x = 0
                    prev_pos_y = 0
                    prev_pos_z = 0
                elif from_sec_list[j_pos] == to_sec_list[j_pos - 1]:
                    prev_pos_x = pos_x_list[j_pos - 1]
                    prev_pos_y = pos_y_list[j_pos - 1]
                    prev_pos_z = pos_z_list[j_pos - 1]
                else:
                    index_prev = to_sec_list.index(from_sec_list[j_pos])
                    prev_pos_x = pos_x_list[index_prev]
                    prev_pos_y = pos_y_list[index_prev]
                    prev_pos_z = pos_z_list[index_prev]

                pos_x_list[j_pos] += prev_pos_x
                pos_y_list[j_pos] += prev_pos_y
                pos_z_list[j_pos] += prev_pos_z

        else:
            log.warning('No "positionings" have been found!')
            pos_cnt = 0

        # Sections
        sec_cnt = tixi.getNamedChildrenCount(wing_xpath + "/sections",
                                             "section")
        log.info("    -" + str(sec_cnt) + " wing sections have been found")
        wing_sec_index = 1

        if pos_cnt == 0:
            pos_x_list = [0.0] * sec_cnt
            pos_y_list = [0.0] * sec_cnt
            pos_z_list = [0.0] * sec_cnt

        for i_sec in reversed(range(sec_cnt)):
            sec_xpath = wing_xpath + "/sections/section[" + str(i_sec +
                                                                1) + "]"
            sec_uid = tixi.getTextAttribute(sec_xpath, "uID")
            sec_transf = Transformation()
            sec_transf.get_cpacs_transf(tixi, sec_xpath)

            # Elements
            elem_cnt = tixi.getNamedChildrenCount(sec_xpath + "/elements",
                                                  "element")

            if elem_cnt > 1:
                log.warning("Sections " + sec_uid + "  contains multiple \
                             element, it could be an issue for the conversion \
                             to SUMO!")

            for i_elem in range(elem_cnt):
                elem_xpath = sec_xpath + "/elements/element[" + str(i_elem +
                                                                    1) + "]"
                elem_uid = tixi.getTextAttribute(elem_xpath, "uID")
                elem_transf = Transformation()
                elem_transf.get_cpacs_transf(tixi, elem_xpath)

                # Get wing profile (airfoil)
                prof_uid = tixi.getTextElement(elem_xpath + "/airfoilUID")
                prof_vect_x, prof_vect_y, prof_vect_z = get_profile_coord(
                    tixi, prof_uid)

                # Apply scaling
                for i, item in enumerate(prof_vect_x):
                    prof_vect_x[i] = (item * elem_transf.scaling.x *
                                      sec_transf.scaling.x *
                                      wing_transf.scaling.x)
                for i, item in enumerate(prof_vect_y):
                    prof_vect_y[i] = (item * elem_transf.scaling.y *
                                      sec_transf.scaling.y *
                                      wing_transf.scaling.y)
                for i, item in enumerate(prof_vect_z):
                    prof_vect_z[i] = (item * elem_transf.scaling.z *
                                      sec_transf.scaling.z *
                                      wing_transf.scaling.z)

                # Plot setions (for tests)
                # if (i_sec>8 and i_sec<=10):
                #     plt.plot(prof_vect_x, prof_vect_z,'x')
                #     plt.xlabel('x')
                #     plt.ylabel('z')
                #     plt.grid(True)
                #     plt.show()

                prof_size_x = max(prof_vect_x) - min(prof_vect_x)
                prof_size_y = max(prof_vect_y) - min(prof_vect_y)
                prof_size_z = max(prof_vect_z) - min(prof_vect_z)

                if prof_size_y == 0:
                    prof_vect_x[:] = [x / prof_size_x for x in prof_vect_x]
                    prof_vect_z[:] = [z / prof_size_x for z in prof_vect_z]
                    # Is it correct to divide by prof_size_x ????

                    wg_sec_chord = prof_size_x
                else:
                    log.error("An airfoil profile is not define correctly")

                # SUMO variable for WingSection
                wg_sec_center_x = (elem_transf.translation.x +
                                   sec_transf.translation.x +
                                   pos_x_list[i_sec]) * wing_transf.scaling.x
                wg_sec_center_y = (
                    elem_transf.translation.y * sec_transf.scaling.y +
                    sec_transf.translation.y +
                    pos_y_list[i_sec]) * wing_transf.scaling.y
                wg_sec_center_z = (
                    elem_transf.translation.z * sec_transf.scaling.z +
                    sec_transf.translation.z +
                    pos_z_list[i_sec]) * wing_transf.scaling.z

                # Add roation from element and sections
                # Adding the two angles: Maybe not work in every case!!!
                add_rotation = SimpleNamespace()
                add_rotation.x = elem_transf.rotation.x + sec_transf.rotation.x
                add_rotation.y = elem_transf.rotation.y + sec_transf.rotation.y
                add_rotation.z = elem_transf.rotation.z + sec_transf.rotation.z

                # Get Section rotation for SUMO
                wg_sec_rot = euler2fix(add_rotation)
                wg_sec_dihed = math.radians(wg_sec_rot.x)
                wg_sec_twist = math.radians(wg_sec_rot.y)
                wg_sec_yaw = math.radians(wg_sec_rot.z)

                # Convert point list into string
                prof_str = ""

                # Airfoil points order : shoud be from TE (1 0) to LE (0 0)
                # then TE(1 0), but not reverse way.

                # to avoid double zero, not accepted by SUMO
                for i, item in enumerate(prof_vect_x):
                    # if not (prof_vect_x[i] == prof_vect_x[i-1] or \
                    #         round(prof_vect_z[i],4) == round(prof_vect_z[i-1],4)):
                    if round(prof_vect_z[i], 4) != round(
                            prof_vect_z[i - 1], 4):
                        prof_str += (str(round(prof_vect_x[i], 4)) + " " +
                                     str(round(prof_vect_z[i], 4)) + " ")

                sumo.addTextElementAtIndex(wg_sk_xpath, "WingSection",
                                           prof_str, wing_sec_index)
                wg_sec_xpath = wg_sk_xpath + "/WingSection[" + str(
                    wing_sec_index) + "]"
                sumo.addTextAttribute(wg_sec_xpath, "airfoil", prof_uid)
                sumo.addTextAttribute(wg_sec_xpath, "name", sec_uid)
                wg_sec_center_str = (str(wg_sec_center_x) + " " +
                                     str(wg_sec_center_y) + " " +
                                     str(wg_sec_center_z))
                sumo.addTextAttribute(wg_sec_xpath, "center",
                                      wg_sec_center_str)
                sumo.addTextAttribute(wg_sec_xpath, "chord", str(wg_sec_chord))
                sumo.addTextAttribute(wg_sec_xpath, "dihedral",
                                      str(wg_sec_dihed))
                sumo.addTextAttribute(wg_sec_xpath, "twist", str(wg_sec_twist))
                sumo.addTextAttribute(wg_sec_xpath, "yaw", str(wg_sec_yaw))
                sumo.addTextAttribute(wg_sec_xpath, "napprox", "-1")
                sumo.addTextAttribute(wg_sec_xpath, "reversed", "false")
                sumo.addTextAttribute(wg_sec_xpath, "vbreak", "false")

                wing_sec_index += 1

        # Add Wing caps
        add_wing_cap(sumo, wg_sk_xpath)

    # Engyine pylon(s) ---------------------------------------------------------

    inc_pylon_xpath = "/cpacs/toolspecific/CEASIOMpy/engine/includePylon"
    include_pylon = get_value_or_default(tixi, inc_pylon_xpath, False)

    if tixi.checkElement(PYLONS_XPATH) and include_pylon:
        pylon_cnt = tixi.getNamedChildrenCount(PYLONS_XPATH, "enginePylon")
        log.info(str(pylon_cnt) + " pylons has been found.")
    else:
        pylon_cnt = 0
        log.warning("No pylon has been found in this CPACS file!")

    for i_pylon in range(pylon_cnt):
        pylon_xpath = PYLONS_XPATH + "/enginePylon[" + str(i_pylon + 1) + "]"
        pylon_uid = tixi.getTextAttribute(pylon_xpath, "uID")
        pylon_transf = Transformation()
        pylon_transf.get_cpacs_transf(tixi, pylon_xpath)

        # Create new wing (SUMO) Pylons will be modeled as a wings
        sumo.createElementAtIndex("/Assembly", "WingSkeleton", i_pylon + 1)
        wg_sk_xpath = "/Assembly/WingSkeleton[" + str(i_pylon + 1) + "]"

        sumo.addTextAttribute(wg_sk_xpath, "akimatg", "false")
        sumo.addTextAttribute(wg_sk_xpath, "name", pylon_uid)

        # Create a class for the transformation of the WingSkeleton
        wg_sk_tansf = Transformation()

        # Convert WingSkeleton rotation and add it to SUMO
        wg_sk_tansf.rotation = euler2fix(pylon_transf.rotation)

        wg_sk_rot_str = sumo_str_format(
            math.radians(wg_sk_tansf.rotation.x),
            math.radians(wg_sk_tansf.rotation.y),
            math.radians(wg_sk_tansf.rotation.z),
        )
        sumo.addTextAttribute(wg_sk_xpath, "rotation", wg_sk_rot_str)

        # Add WingSkeleton origin
        wg_sk_tansf.translation = pylon_transf.translation

        sumo.addTextAttribute(
            wg_sk_xpath,
            "origin",
            sumo_str_format(wg_sk_tansf.translation.x,
                            wg_sk_tansf.translation.y,
                            wg_sk_tansf.translation.z),
        )
        sumo.addTextAttribute(wg_sk_xpath, "flags", "detectwinglet")

        # Positionings
        if tixi.checkElement(pylon_xpath + "/positionings"):
            pos_cnt = tixi.getNamedChildrenCount(pylon_xpath + "/positionings",
                                                 "positioning")
            log.info(str(pylon_cnt) + ' "positionning" has been found : ')

            pos_x_list = []
            pos_y_list = []
            pos_z_list = []
            from_sec_list = []
            to_sec_list = []

            for i_pos in range(pos_cnt):
                pos_xpath = pylon_xpath + "/positionings/positioning[" + str(
                    i_pos + 1) + "]"

                length = tixi.getDoubleElement(pos_xpath + "/length")
                sweep_deg = tixi.getDoubleElement(pos_xpath + "/sweepAngle")
                sweep = math.radians(sweep_deg)
                dihedral_deg = tixi.getDoubleElement(pos_xpath +
                                                     "/dihedralAngle")
                dihedral = math.radians(dihedral_deg)

                # Get the corresponding translation of each positionning
                pos_x_list.append(length * math.sin(sweep))
                pos_y_list.append(length * math.cos(dihedral) *
                                  math.cos(sweep))
                pos_z_list.append(length * math.sin(dihedral) *
                                  math.cos(sweep))

                # Get which section are connected by the positionning
                if tixi.checkElement(pos_xpath + "/fromSectionUID"):
                    from_sec = tixi.getTextElement(pos_xpath +
                                                   "/fromSectionUID")
                else:
                    from_sec = ""
                from_sec_list.append(from_sec)

                if tixi.checkElement(pos_xpath + "/toSectionUID"):
                    to_sec = tixi.getTextElement(pos_xpath + "/toSectionUID")
                else:
                    to_sec = ""
                to_sec_list.append(to_sec)

            # Re-loop though the positionning to re-order them
            for j_pos in range(pos_cnt):
                if from_sec_list[j_pos] == "":
                    prev_pos_x = 0
                    prev_pos_y = 0
                    prev_pos_z = 0
                elif from_sec_list[j_pos] == to_sec_list[j_pos - 1]:
                    prev_pos_x = pos_x_list[j_pos - 1]
                    prev_pos_y = pos_y_list[j_pos - 1]
                    prev_pos_z = pos_z_list[j_pos - 1]
                else:
                    index_prev = to_sec_list.index(from_sec_list[j_pos])
                    prev_pos_x = pos_x_list[index_prev]
                    prev_pos_y = pos_y_list[index_prev]
                    prev_pos_z = pos_z_list[index_prev]

                pos_x_list[j_pos] += prev_pos_x
                pos_y_list[j_pos] += prev_pos_y
                pos_z_list[j_pos] += prev_pos_z

        else:
            log.warning('No "positionings" have been found!')
            pos_cnt = 0

        # Sections
        sec_cnt = tixi.getNamedChildrenCount(pylon_xpath + "/sections",
                                             "section")
        log.info("    -" + str(sec_cnt) + " wing sections have been found")
        wing_sec_index = 1

        if pos_cnt == 0:
            pos_x_list = [0.0] * sec_cnt
            pos_y_list = [0.0] * sec_cnt
            pos_z_list = [0.0] * sec_cnt

        check_reversed_wing = []

        for i_sec in range(sec_cnt):
            # for i_sec in reversed(range(sec_cnt)):
            sec_xpath = pylon_xpath + "/sections/section[" + str(i_sec +
                                                                 1) + "]"
            sec_uid = tixi.getTextAttribute(sec_xpath, "uID")
            sec_transf = Transformation()
            sec_transf.get_cpacs_transf(tixi, sec_xpath)

            # Elements
            elem_cnt = tixi.getNamedChildrenCount(sec_xpath + "/elements",
                                                  "element")

            if elem_cnt > 1:
                log.warning("Sections " + sec_uid + "  contains multiple \
                             element, it could be an issue for the conversion \
                             to SUMO!")

            for i_elem in range(elem_cnt):
                elem_xpath = sec_xpath + "/elements/element[" + str(i_elem +
                                                                    1) + "]"
                elem_uid = tixi.getTextAttribute(elem_xpath, "uID")
                elem_transf = Transformation()
                elem_transf.get_cpacs_transf(tixi, elem_xpath)

                # Get pylon profile (airfoil)
                prof_uid = tixi.getTextElement(elem_xpath + "/airfoilUID")
                prof_vect_x, prof_vect_y, prof_vect_z = get_profile_coord(
                    tixi, prof_uid)

                # Apply scaling
                for i, item in enumerate(prof_vect_x):
                    prof_vect_x[i] = (item * elem_transf.scaling.x *
                                      sec_transf.scaling.x *
                                      pylon_transf.scaling.x)
                for i, item in enumerate(prof_vect_y):
                    prof_vect_y[i] = (item * elem_transf.scaling.y *
                                      sec_transf.scaling.y *
                                      pylon_transf.scaling.y)
                for i, item in enumerate(prof_vect_z):
                    prof_vect_z[i] = (item * elem_transf.scaling.z *
                                      sec_transf.scaling.z *
                                      pylon_transf.scaling.z)

                prof_size_x = max(prof_vect_x) - min(prof_vect_x)
                prof_size_y = max(prof_vect_y) - min(prof_vect_y)
                prof_size_z = max(prof_vect_z) - min(prof_vect_z)

                if prof_size_y == 0:
                    prof_vect_x[:] = [x / prof_size_x for x in prof_vect_x]
                    prof_vect_z[:] = [z / prof_size_x for z in prof_vect_z]
                    # Is it correct to divide by prof_size_x ????

                    wg_sec_chord = prof_size_x
                else:
                    log.error("An airfoil profile is not define correctly")

                # SUMO variable for WingSection
                wg_sec_center_x = (elem_transf.translation.x +
                                   sec_transf.translation.x +
                                   pos_x_list[i_sec]) * pylon_transf.scaling.x
                wg_sec_center_y = (
                    elem_transf.translation.y * sec_transf.scaling.y +
                    sec_transf.translation.y +
                    pos_y_list[i_sec]) * pylon_transf.scaling.y
                wg_sec_center_z = (
                    elem_transf.translation.z * sec_transf.scaling.z +
                    sec_transf.translation.z +
                    pos_z_list[i_sec]) * pylon_transf.scaling.z

                check_reversed_wing.append(wg_sec_center_y)

                # Add roation from element and sections
                # Adding the two angles: Maybe not work in every case!!!
                add_rotation = SimpleNamespace()
                add_rotation.x = elem_transf.rotation.x + sec_transf.rotation.x
                add_rotation.y = elem_transf.rotation.y + sec_transf.rotation.y
                add_rotation.z = elem_transf.rotation.z + sec_transf.rotation.z

                # Get Section rotation for SUMO
                wg_sec_rot = euler2fix(add_rotation)
                wg_sec_dihed = math.radians(wg_sec_rot.x)
                wg_sec_twist = math.radians(wg_sec_rot.y)
                wg_sec_yaw = math.radians(wg_sec_rot.z)

                # Convert point list into string
                prof_str = ""

                # Airfoil points order : should be from TE (1 0) to LE (0 0)
                # then TE(1 0), but not reverse way.

                # to avoid double zero, not accepted by SUMO
                for i, item in enumerate(prof_vect_x):
                    # if not (prof_vect_x[i] == prof_vect_x[i-1] or \
                    #         round(prof_vect_z[i],4) == round(prof_vect_z[i-1],4)):
                    if round(prof_vect_z[i], 4) != round(
                            prof_vect_z[i - 1], 4):
                        prof_str += (str(round(prof_vect_x[i], 4)) + " " +
                                     str(round(prof_vect_z[i], 4)) + " ")

                sumo.addTextElementAtIndex(wg_sk_xpath, "WingSection",
                                           prof_str, wing_sec_index)
                wg_sec_xpath = wg_sk_xpath + "/WingSection[" + str(
                    wing_sec_index) + "]"
                sumo.addTextAttribute(wg_sec_xpath, "airfoil", prof_uid)
                sumo.addTextAttribute(wg_sec_xpath, "name", sec_uid)
                sumo.addTextAttribute(
                    wg_sec_xpath,
                    "center",
                    sumo_str_format(wg_sec_center_x, wg_sec_center_y,
                                    wg_sec_center_z),
                )
                sumo.addTextAttribute(wg_sec_xpath, "chord", str(wg_sec_chord))
                sumo.addTextAttribute(wg_sec_xpath, "dihedral",
                                      str(wg_sec_dihed))
                sumo.addTextAttribute(wg_sec_xpath, "twist", str(wg_sec_twist))
                sumo.addTextAttribute(wg_sec_xpath, "yaw", str(wg_sec_yaw))
                sumo.addTextAttribute(wg_sec_xpath, "napprox", "-1")
                sumo.addTextAttribute(wg_sec_xpath, "reversed", "false")
                sumo.addTextAttribute(wg_sec_xpath, "vbreak", "false")

                wing_sec_index += 1

        # Check if the wing section order must be inverted with reversed attribute
        if check_reversed_wing[0] < check_reversed_wing[1]:
            log.info("Wing section order will be reversed.")
            for i_sec in range(sec_cnt):
                wg_sec_xpath = wg_sk_xpath + "/WingSection[" + str(i_sec +
                                                                   1) + "]"
                sumo.removeAttribute(wg_sec_xpath, "reversed")
                sumo.addTextAttribute(wg_sec_xpath, "reversed", "true")

        # If symmetry, create a mirror copy of the Pylon
        if tixi.checkAttribute(pylon_xpath, "symmetry"):
            if tixi.getTextAttribute(pylon_xpath, "symmetry") == "x-z-plane":
                sumo_mirror_copy(sumo, wg_sk_xpath, pylon_uid, True)

        add_wing_cap(sumo, wg_sk_xpath)

    # Engine(s) ----------------------------------------------------------------

    inc_engine_xpath = "/cpacs/toolspecific/CEASIOMpy/engine/includeEngine"
    include_engine = get_value_or_default(tixi, inc_engine_xpath, False)

    if tixi.checkElement(ENGINES_XPATH) and include_engine:
        engine_cnt = tixi.getNamedChildrenCount(ENGINES_XPATH, "engine")
        log.info(str(engine_cnt) + " engines has been found.")
    else:
        engine_cnt = 0
        log.warning("No engine has been found in this CPACS file!")

    for i_engine in range(engine_cnt):
        engine_xpath = ENGINES_XPATH + "/engine[" + str(i_engine + 1) + "]"

        engine = Engine(tixi, engine_xpath)

        # Nacelle (sumo)
        xengtransl = engine.transf.translation.x
        yengtransl = engine.transf.translation.y
        zengtransl = engine.transf.translation.z

        engineparts = [
            engine.nacelle.fancowl, engine.nacelle.corecowl,
            engine.nacelle.centercowl
        ]

        for engpart in engineparts:

            if not engpart.isengpart:
                log.info("This engine part is not define.")
                continue

            if engpart.iscone:

                xcontours = engpart.pointlist.xlist
                ycontours = engpart.pointlist.ylist

                xengtransl += engpart.xoffset

                ysectransl = 0
                zsectransl = 0

            else:

                xlist = engpart.section.pointlist.xlist
                ylist = engpart.section.pointlist.ylist

                xscaling = engpart.section.transf.scaling.x
                zscaling = engpart.section.transf.scaling.z

                # Why scaling z for point in y??? CPACS mystery...
                xlist = [i * xscaling for i in xlist]
                ylist = [i * zscaling for i in ylist]

                # Nacelle parts contour points
                # In CPACS nacelles are define as the revolution of section, in SUMO they have to
                # be define as a body composed of section + a lip at the inlet

                # Find upper part of the profile
                xminidx = xlist.index(min(xlist))

                yavg1 = sum(ylist[0:xminidx]) / (xminidx)
                yavg2 = sum(ylist[xminidx:-1]) / (len(ylist) - xminidx)

                if yavg1 > yavg2:
                    xcontours = xlist[0:xminidx]
                    ycontours = ylist[0:xminidx]
                else:
                    xcontours = xlist[xminidx:-1]
                    ycontours = ylist[xminidx:-1]

                ysectransl = engpart.section.transf.translation.y
                zsectransl = engpart.section.transf.translation.z

            # # Plot
            # fig, ax = plt.subplots()
            # ax.plot(xlist, ylist,'x')
            # ax.plot(xcontours, ycontours,'or')
            # ax.set(xlabel='x', ylabel='y',title='Engine profile')
            # ax.grid()
            # plt.show()

            sumo.createElementAtIndex("/Assembly", "BodySkeleton", i_fus + 1)
            body_xpath = "/Assembly/BodySkeleton[" + str(i_fus + 1) + "]"

            sumo.addTextAttribute(body_xpath, "akimatg", "false")
            sumo.addTextAttribute(body_xpath, "name", engpart.uid)

            # Add body rotation and origin
            sumo.addTextAttribute(body_xpath, "rotation",
                                  sumo_str_format(0, 0, 0))
            sumo.addTextAttribute(
                body_xpath,
                "origin",
                sumo_str_format(xengtransl + ysectransl, yengtransl,
                                zengtransl),
            )

            # Add section
            for i_sec in range(len(xcontours)):

                namesec = "section_" + str(i_sec + 1)
                # Only circle profiles
                prof_str = " 0 -1 0.7071 -0.7071 1 0 0.7071 0.7071 0 1"
                sumo.addTextElementAtIndex(body_xpath, "BodyFrame", prof_str,
                                           i_sec + 1)
                frame_xpath = body_xpath + "/BodyFrame[" + str(i_sec + 1) + "]"

                diam = (ycontours[i_sec] + zsectransl) * 2
                if diam < 0.01:
                    diam = 0.01

                sumo.addTextAttribute(frame_xpath, "center",
                                      sumo_str_format(xcontours[i_sec], 0, 0))
                sumo.addTextAttribute(frame_xpath, "height", str(diam))
                sumo.addTextAttribute(frame_xpath, "width", str(diam))
                sumo.addTextAttribute(frame_xpath, "name", namesec)

            # Nacelle/engine options
            sumo_add_nacelle_lip(sumo, body_xpath)

            if not engpart.iscone:
                sumo_add_engine_bc(sumo, "Engine", engpart.uid)

            if engine.sym:

                sumo.createElementAtIndex("/Assembly", "BodySkeleton",
                                          i_fus + 1)
                body_xpath = "/Assembly/BodySkeleton[" + str(i_fus + 1) + "]"

                sumo.addTextAttribute(body_xpath, "akimatg", "false")
                sumo.addTextAttribute(body_xpath, "name", engpart.uid + "_sym")

                # Add body rotation and origin
                sumo.addTextAttribute(body_xpath, "rotation",
                                      sumo_str_format(0, 0, 0))
                sumo.addTextAttribute(
                    body_xpath,
                    "origin",
                    sumo_str_format(xengtransl + ysectransl, -yengtransl,
                                    zengtransl),
                )

                # Add section
                for i_sec in range(len(xcontours)):

                    namesec = "section_" + str(i_sec + 1)
                    # Only circle profiles
                    prof_str = " 0 -1 0.7071 -0.7071 1 0 0.7071 0.7071 0 1"
                    sumo.addTextElementAtIndex(body_xpath, "BodyFrame",
                                               prof_str, i_sec + 1)
                    frame_xpath = body_xpath + "/BodyFrame[" + str(i_sec +
                                                                   1) + "]"

                    diam = (ycontours[i_sec] + zsectransl) * 2
                    if diam < 0.01:
                        diam = 0.01

                    sumo.addTextAttribute(
                        frame_xpath, "center",
                        sumo_str_format(xcontours[i_sec], 0, 0))
                    sumo.addTextAttribute(frame_xpath, "height", str(diam))
                    sumo.addTextAttribute(frame_xpath, "width", str(diam))
                    sumo.addTextAttribute(frame_xpath, "name", namesec)

                # Nacelle/Enine options
                sumo_add_nacelle_lip(sumo, body_xpath)

                if not engpart.iscone:
                    sumo_add_engine_bc(sumo, "Engine_sym",
                                       engpart.uid + "_sym")

    # Get results directory
    results_dir = get_results_directory("CPACS2SUMO")
    sumo_file_path = Path(results_dir, "ToolOutput.smx")

    create_branch(tixi, SUMOFILE_XPATH)
    tixi.updateTextElement(SUMOFILE_XPATH, str(sumo_file_path))

    # Save CPACS and SMX file
    tixi.save(cpacs_out_path)
    sumo.save(str(sumo_file_path))
Пример #15
0
def add_skin_friction(cpacs_path, cpacs_out_path):
    """Function to add the skin frictions drag coefficient to aerodynamic coefficients

    Function 'add_skin_friction' add the skin friction drag 'cd0' to  the
    SU2 and pyTornado aeroMap, if their UID is not given, it will add skin
    friction to all aeroMap. For each aeroMap it creates a new aeroMap where
    the skin friction drag coefficient is added with the correct projections.

    Args:
        cpacs_path (str):  Path to CPACS file
        cpacs_out_path (str): Path to CPACS output file
    """

    # Load a CPACS file
    cpacs = CPACS(cpacs_path)

    analyses_xpath = "/cpacs/toolspecific/CEASIOMpy/geometry/analysis"

    # Required input data from CPACS
    wetted_area = get_value(cpacs.tixi, analyses_xpath + "/wettedArea")

    # Wing area/span, default values will be calculated if no value found in the CPACS file
    wing_area_xpath = analyses_xpath + "/wingArea"
    wing_area = get_value_or_default(cpacs.tixi, wing_area_xpath,
                                     cpacs.aircraft.wing_area)
    wing_span_xpath = analyses_xpath + "/wingSpan"
    wing_span = get_value_or_default(cpacs.tixi, wing_span_xpath,
                                     cpacs.aircraft.wing_span)

    # Get aeroMapToCalculate
    aeroMap_to_calculate_xpath = SF_XPATH + "/aeroMapToCalculate"
    if cpacs.tixi.checkElement(aeroMap_to_calculate_xpath):
        aeromap_uid_list = get_string_vector(cpacs.tixi,
                                             aeroMap_to_calculate_xpath)
    else:
        aeromap_uid_list = []

    # If no aeroMap in aeroMapToCalculate, get all existing aeroMap
    if len(aeromap_uid_list) == 0:
        aeromap_uid_list = cpacs.get_aeromap_uid_list()

        if not aeromap_uid_list:
            raise ValueError(
                "No aeroMap has been found in this CPACS file, skin friction cannot be added!"
            )

    # Get unique aeroMap list
    aeromap_uid_list = list(set(aeromap_uid_list))
    new_aeromap_uid_list = []

    # Add skin friction to all listed aeroMap
    for aeromap_uid in aeromap_uid_list:

        log.info("adding skin friction coefficients to: " + aeromap_uid)

        aeromap = cpacs.get_aeromap_by_uid(aeromap_uid)

        # Create new aeromap object to store coef with added skin friction
        aeromap_sf = cpacs.duplicate_aeromap(aeromap_uid,
                                             aeromap_uid + "_SkinFriction")
        aeromap_sf.description = (
            aeromap_sf.description +
            " Skin friction has been add to this AeroMap.")

        # Add skin friction to all force coefficient (with projections)
        aeromap_sf.df["cd"] = aeromap.df.apply(
            lambda row: row["cd"] + estimate_skin_friction_coef(
                wetted_area, wing_area, wing_span, row["machNumber"], row[
                    "altitude"]) * math.cos(math.radians(row["angleOfAttack"]))
            * math.cos(math.radians(row["angleOfSideslip"])),
            axis=1,
        )

        aeromap_sf.df["cl"] = aeromap.df.apply(
            lambda row: row["cl"] + estimate_skin_friction_coef(
                wetted_area, wing_area, wing_span, row[
                    "machNumber"], row["altitude"]) * math.sin(
                        math.radians(row["angleOfAttack"])),
            axis=1,
        )

        aeromap_sf.df["cs"] = aeromap.df.apply(
            lambda row: row["cs"] + estimate_skin_friction_coef(
                wetted_area, wing_area, wing_span, row["machNumber"], row[
                    "altitude"]) * math.sin(
                        math.radians(row["angleOfSideslip"])),
            axis=1,
        )

        # TODO: Should we change something in moment coef?
        # e.i. if a force is not apply at aero center...?

        aeromap_sf.save()

    # Get aeroMap list to plot
    plot_xpath = "/cpacs/toolspecific/CEASIOMpy/aerodynamics/plotAeroCoefficient"
    aeromap_to_plot_xpath = plot_xpath + "/aeroMapToPlot"

    if cpacs.tixi.checkElement(aeromap_to_plot_xpath):
        aeromap_uid_list = get_string_vector(cpacs.tixi, aeromap_to_plot_xpath)
        new_aeromap_to_plot = aeromap_uid_list + new_aeromap_uid_list
        new_aeromap_to_plot = list(set(new_aeromap_to_plot))
        add_string_vector(cpacs.tixi, aeromap_to_plot_xpath,
                          new_aeromap_to_plot)
    else:
        create_branch(cpacs.tixi, aeromap_to_plot_xpath)
        add_string_vector(cpacs.tixi, aeromap_to_plot_xpath,
                          new_aeromap_uid_list)

    log.info('AeroMap "' + aeromap_uid + '" has been added to the CPACS file')

    cpacs.save_cpacs(cpacs_out_path, overwrite=True)
Пример #16
0
def generate_mesh_def_config(tixi, wkdir, ted_uid, wing_uid, sym_dir, defl_list):
    """Function to create config file for a TED.

    Function 'generate_mesh_def_config' will create SU2 configuration files to
    create SU2 deformed mesh for a specific Trailing Edge Device (TED) at several
    deflection angle (from defl_list)

    Args:
        tixi (handle): TIXI handle
        wkdir (str): Path to the working directory
        ted_uid (str): uID of the TED
        wing_uid (str): uID of the coresponding wing
        sym_dir (str): Direction of the axis of symmetry ('x','y','z' or '')
        defl_list (str): List of deflection angles to generate

    """

    tigl = open_tigl(tixi)
    ac_name = aircraft_name(tixi)
    DEFAULT_CONFIG_PATH = MODULE_DIR + "/files/DefaultConfig_v7.cfg"
    cfg = ConfigFile(DEFAULT_CONFIG_PATH)
    config_dir_name = ac_name + "_TED_" + ted_uid
    # TODO: add check or remove if already exist?
    os.mkdir(os.path.join(wkdir, "MESH", config_dir_name))

    # Get TED and hinge line definition
    ted_corner = get_ted_corner(tixi, tigl, ted_uid)
    ted_corner_list, ted_corner_sym_list = get_ffd_box(ted_corner, sym_dir)
    ted_hinge = get_ted_hinge(tixi, tigl, ted_uid)
    hinge_list, hinge_sym_list = get_hinge_lists(ted_hinge, sym_dir)

    # General parmeters
    ref_len = get_value(tixi, REF_XPATH + "/length")
    ref_area = get_value(tixi, REF_XPATH + "/area")
    ref_ori_moment_x = get_value_or_default(tixi, REF_XPATH + "/point/x", 0.0)
    ref_ori_moment_y = get_value_or_default(tixi, REF_XPATH + "/point/y", 0.0)
    ref_ori_moment_z = get_value_or_default(tixi, REF_XPATH + "/point/z", 0.0)

    cfg["REF_LENGTH"] = ref_len
    cfg["REF_AREA"] = ref_area
    cfg["REF_ORIGIN_MOMENT_X"] = ref_ori_moment_x
    cfg["REF_ORIGIN_MOMENT_Y"] = ref_ori_moment_y
    cfg["REF_ORIGIN_MOMENT_Z"] = ref_ori_moment_z
    cfg["GRID_MOVEMENT"] = "NONE"
    cfg["ROTATION_RATE"] = "0.0 0.0 0.0"

    # TODO: is it the best way or should be pass as arg?
    mesh_dir = os.path.join(wkdir, "MESH")
    su2_mesh_path = os.path.join(mesh_dir, ac_name + "_baseline.su2")
    cfg["MESH_FILENAME"] = "../" + ac_name + "_baseline.su2"

    # Mesh Marker
    bc_wall_list, engine_bc_list = get_mesh_marker(su2_mesh_path)

    bc_wall_str = "(" + ",".join(bc_wall_list) + ")"
    cfg["MARKER_EULER"] = bc_wall_str
    cfg["MARKER_FAR"] = " (Farfield)"
    cfg["MARKER_SYM"] = " (0)"
    cfg["MARKER_PLOTTING"] = bc_wall_str
    cfg["MARKER_MONITORING"] = bc_wall_str
    cfg["MARKER_MOVING"] = "( NONE )"
    cfg["DV_MARKER"] = bc_wall_str

    # FFD BOX definition
    cfg["DV_KIND"] = "FFD_SETTING"
    cfg["DV_MARKER"] = "( " + wing_uid + ")"
    cfg["FFD_CONTINUITY"] = "2ND_DERIVATIVE"
    cfg["FFD_DEFINITION"] = "( " + ted_uid + ", " + ",".join(ted_corner_list) + ")"
    cfg["FFD_DEGREE"] = "( 6, 10, 3 )"  # TODO: how to chose/calculate these value?
    if sym_dir:
        cfg["FFD_DEFINITION"] += "; (" + ted_uid + "_sym, " + ",".join(ted_corner_sym_list) + ")"
        cfg["FFD_DEGREE"] += ";( 6, 10, 3 )"  # TODO: how to chose/calculate these value?
    cfg["MESH_OUT_FILENAME"] = "_mesh_ffd_box.su2"

    # Write Config definition for FFD box
    config_file_name = "ConfigDEF.cfg"
    config_path = os.path.join(wkdir, "MESH", config_dir_name, config_file_name)
    cfg.write_file(config_path, overwrite=True)
    log.info(config_path + " have has been written.")

    # FFD BOX rotation
    for defl in defl_list:

        cfg["DV_KIND"] = "FFD_ROTATION"
        cfg["DV_MARKER"] = "( " + wing_uid + ")"
        cfg["DV_PARAM"] = "( " + ted_uid + ", " + ",".join(hinge_list) + ")"
        cfg["DV_VALUE"] = str(defl / 1000)  # SU2 use 1/1000 degree...

        cfg["MESH_FILENAME"] = "_mesh_ffd_box.su2"
        defl_mesh_name = ac_name + "_TED_" + ted_uid + "_defl" + str(defl) + ".su2"
        if sym_dir:
            defl_mesh_name = "_" + defl_mesh_name
        cfg["MESH_OUT_FILENAME"] = defl_mesh_name

        # Write Config rotation for FFD box
        config_file_name = "ConfigROT_defl" + str(defl) + ".cfg"
        config_path = os.path.join(wkdir, "MESH", config_dir_name, config_file_name)
        cfg.write_file(config_path, overwrite=True)
        log.info(config_path + " have has been written.")

        if sym_dir:
            # TODO: add a condition for anti symmetric deflection (e.g. ailerons)
            cfg["DV_MARKER"] = "( " + wing_uid + ")"
            cfg["DV_PARAM"] = "( " + ted_uid + "_sym, " + ",".join(hinge_sym_list) + ")"
            cfg["DV_VALUE"] = str(defl / 1000)  # SU2 use 1/1000 degree...

            cfg["MESH_FILENAME"] = defl_mesh_name
            defl_mesh_sym_name = ac_name + "_TED_" + ted_uid + "_defl" + str(defl) + "_sym.su2"
            cfg["MESH_OUT_FILENAME"] = defl_mesh_sym_name

            config_file_name = "ConfigROT_sym_defl" + str(defl) + ".cfg"
            config_path = os.path.join(wkdir, "MESH", config_dir_name, config_file_name)
            cfg.write_file(config_path, overwrite=True)
            log.info(config_path + " have has been written.")
Пример #17
0
def get_su2_results(cpacs_path, cpacs_out_path, wkdir):
    """Function to write SU2 results in a CPACS file.

    Function 'get_su2_results' get available results from the latest SU2
    calculation and put it at the correct place in the CPACS file.

    '/cpacs/vehicles/aircraft/model/analyses/aeroPerformance/aerpMap[n]/aeroPerformanceMap'

    Args:
        cpacs_path (str): Path to input CPACS file
        cpacs_out_path (str): Path to output CPACS file
        wkdir (str): Path to the working directory

    """

    cpacs = CPACS(cpacs_path)

    if not os.path.exists(wkdir):
        raise OSError("The working directory : " + wkdir + "does not exit!")

    dir_list = os.listdir(wkdir)

    # Get and save Wetted area
    wetted_area = get_wetted_area(wkdir)
    create_branch(cpacs.tixi, WETTED_AREA_XPATH)
    cpacs.tixi.updateDoubleElement(WETTED_AREA_XPATH, wetted_area, "%g")

    # Check if loads shoud be extracted
    check_extract_loads_xpath = SU2_XPATH + "/results/extractLoads"
    check_extract_loads = get_value_or_default(cpacs.tixi,
                                               check_extract_loads_xpath,
                                               False)

    # Get fixed_cl option
    fixed_cl_xpath = SU2_XPATH + "/fixedCL"
    fixed_cl = get_value_or_default(cpacs.tixi, fixed_cl_xpath, "NO")

    # Get aeroMap uid
    if fixed_cl == "YES":
        aeromap_uid = "aeroMap_fixedCL_SU2"
    elif fixed_cl == "NO":
        su2_aeromap_xpath = SU2_XPATH + "/aeroMapUID"
        aeromap_uid = get_value(cpacs.tixi, su2_aeromap_xpath)
    else:
        raise ValueError(
            "The value for fixed_cl is not valid! Should be YES or NO")

    aeromap = cpacs.get_aeromap_by_uid(aeromap_uid)

    alt_list = aeromap.get("altitude").tolist()
    mach_list = aeromap.get("machNumber").tolist()
    aoa_list = aeromap.get("angleOfAttack").tolist()
    aos_list = aeromap.get("angleOfSideslip").tolist()

    case_dir_list = [dir for dir in dir_list if "Case" in dir]

    for config_dir in sorted(case_dir_list):

        case_nb = int(config_dir.split("_")[0].split("Case")[1])

        aoa = aoa_list[case_nb]
        aos = aos_list[case_nb]
        mach = mach_list[case_nb]
        alt = alt_list[case_nb]

        config_dir_path = os.path.join(wkdir, config_dir)

        if os.path.isdir(config_dir_path):

            force_file_path = os.path.join(config_dir_path,
                                           "forces_breakdown.dat")

            if not os.path.isfile(force_file_path):
                raise OSError("No result force file have been found!")

            if fixed_cl == "YES":
                cl_cd, aoa = get_efficiency_and_aoa(force_file_path)

                # Replace aoa with the with the value from fixed cl calculation
                aeromap.df.loc[0, ["angleOfAttack"]] = aoa

                # Save cl/cd found during the fixed CL calculation
                # TODO: maybe save cl/cd somewhere else
                lDRatio_xpath = "/cpacs/toolspecific/CEASIOMpy/ranges/lDRatio"
                create_branch(cpacs.tixi, lDRatio_xpath)
                cpacs.tixi.updateDoubleElement(lDRatio_xpath, cl_cd, "%g")

            # Read result file
            with open(force_file_path) as f:
                for line in f.readlines():
                    if "Total CL:" in line:
                        cl = float(line.split(":")[1].split("|")[0])
                    if "Total CD:" in line:
                        cd = float(line.split(":")[1].split("|")[0])
                    if "Total CSF:" in line:
                        cs = float(line.split(":")[1].split("|")[0])
                    # TODO: Check which axis name corespond to waht: cml, cmd, cms
                    if "Total CMx:" in line:
                        cmd = float(line.split(":")[1].split("|")[0])
                    if "Total CMy:" in line:
                        cms = float(line.split(":")[1].split("|")[0])
                    if "Total CMz:" in line:
                        cml = float(line.split(":")[1].split("|")[0])
                    if "Free-stream velocity" in line and "m/s" in line:
                        velocity = float(line.split(" ")[7])

            # Damping derivatives
            rotation_rate_xpath = SU2_XPATH + "/options/rotationRate"
            rotation_rate = get_value_or_default(cpacs.tixi,
                                                 rotation_rate_xpath, -1.0)
            ref_len = cpacs.aircraft.ref_lenght
            adim_rot_rate = rotation_rate * ref_len / velocity

            coefs = {
                "cl": cl,
                "cd": cd,
                "cs": cs,
                "cmd": cmd,
                "cms": cms,
                "cml": cml
            }

            if "_dp" in config_dir:
                for coef in COEFS:
                    coef_baseline = aeromap.get(coef,
                                                alt=alt,
                                                mach=mach,
                                                aoa=aoa,
                                                aos=aos)
                    dcoef = (coefs[coef] - coef_baseline) / adim_rot_rate
                    aeromap.add_damping_derivatives(
                        alt=alt,
                        mach=mach,
                        aos=aos,
                        aoa=aoa,
                        coef=coef,
                        axis="dp",
                        value=dcoef,
                        rate=rotation_rate,
                    )

            elif "_dq" in config_dir:
                for coef in COEFS:
                    coef_baseline = aeromap.get(coef,
                                                alt=alt,
                                                mach=mach,
                                                aoa=aoa,
                                                aos=aos)
                    dcoef = (coefs[coef] - coef_baseline) / adim_rot_rate
                    aeromap.add_damping_derivatives(
                        alt=alt,
                        mach=mach,
                        aos=aos,
                        aoa=aoa,
                        coef=coef,
                        axis="dq",
                        value=dcoef,
                        rate=rotation_rate,
                    )

            elif "_dr" in config_dir:
                for coef in COEFS:
                    coef_baseline = aeromap.get(coef,
                                                alt=alt,
                                                mach=mach,
                                                aoa=aoa,
                                                aos=aos)
                    dcoef = (coefs[coef] - coef_baseline) / adim_rot_rate
                    aeromap.add_damping_derivatives(
                        alt=alt,
                        mach=mach,
                        aos=aos,
                        aoa=aoa,
                        coef=coef,
                        axis="dr",
                        value=dcoef,
                        rate=rotation_rate,
                    )

            elif "_TED_" in config_dir:

                # TODO: convert when it is possible to save TED in cpacspy
                raise NotImplementedError("TED not implemented yet")

                # config_dir_split = config_dir.split('_')
                # ted_idx = config_dir_split.index('TED')
                # ted_uid = config_dir_split[ted_idx+1]
                # defl_angle = float(config_dir.split('_defl')[1])

                # try:
                #     print(Coef.IncrMap.dcl)
                # except AttributeError:
                #     Coef.IncrMap = a.p.m.f.IncrementMap(ted_uid)

                # dcl = (cl-Coef.cl[-1])
                # dcd = (cd-Coef.cd[-1])
                # dcs = (cs-Coef.cs[-1])
                # dcml = (cml-Coef.cml[-1])
                # dcmd = (cmd-Coef.cmd[-1])
                # dcms = (cms-Coef.cms[-1])

                # control_parameter = -1

                # Coef.IncrMap.add_cs_coef(dcl,dcd,dcs,dcml,dcmd,dcms,ted_uid,control_parameter)

            else:  # Baseline coefficients, (no damping derivative or control surfaces case)
                aeromap.add_coefficients(
                    alt=alt,
                    mach=mach,
                    aos=aos,
                    aoa=aoa,
                    cd=cd,
                    cl=cl,
                    cs=cs,
                    cml=cml,
                    cmd=cmd,
                    cms=cms,
                )

            if check_extract_loads:
                results_files_dir = os.path.join(wkdir, config_dir)
                extract_loads(results_files_dir)

    # Save object Coef in the CPACS file
    aeromap.save()

    # Save the CPACS file
    cpacs.save_cpacs(cpacs_out_path, overwrite=True)
Пример #18
0
def get_cl(cpacs_path, cpacs_out_path):
    """Function to calculate CL required as a function of the parameter found
    in the CPACS file.

    Function 'get_cl' find input value in the CPACS file, calculate the
    required CL (with function calculate_cl) and  save the CL value in the
    CPACS file.

    Args:
        cpacs_path (str):  Path to CPACS file
        cpacs_out_path (str): Path to CPACS output file
    """
    cpacs = CPACS(cpacs_path)
    tixi = cpacs.tixi

    # XPath definition
    model_xpath = "/cpacs/vehicles/aircraft/model"
    ref_area_xpath = model_xpath + "/reference/area"

    mass_type_xpath = "/cpacs/toolspecific/CEASIOMpy/aerodynamics/clCalculation/massType"
    custom_mass_xpath = "/cpacs/toolspecific/CEASIOMpy/aerodynamics/clCalculation/customMass"
    percent_fuel_mass_xpath = (
        "/cpacs/toolspecific/CEASIOMpy/aerodynamics/clCalculation/percentFuelMass"
    )

    cruise_alt_xpath = CLCALC_XPATH + "/cruiseAltitude"
    cruise_mach_xpath = CLCALC_XPATH + "/cruiseMach"
    load_fact_xpath = CLCALC_XPATH + "/loadFactor"

    # Required input data from CPACS
    ref_area = get_value(tixi, ref_area_xpath)
    log.info(f"Aircraft reference area is {ref_area} [m^2]")

    # Mass
    mass = None
    mass_type = get_value_or_default(tixi, mass_type_xpath, "mTOM")

    if mass_type == "Custom":
        mass = get_value(tixi, custom_mass_xpath)

    elif mass_type == "% fuel mass":
        percent_fuel_mass = get_value(tixi, percent_fuel_mass_xpath)
        mtom = get_value(
            tixi,
            model_xpath + "/analyses/massBreakdown/designMasses/mTOM/mass")
        mzfm = get_value(
            tixi,
            model_xpath + "/analyses/massBreakdown/designMasses/mZFM/mass")
        if mzfm > mtom:
            raise ValueError("mZFM is bigger than mTOM!")
        mass = (mtom - mzfm) * percent_fuel_mass / 100 + mzfm

    else:
        mass_xpath = model_xpath + f"/analyses/massBreakdown/designMasses/{mass_type}/mass"
        mass = get_value(tixi, mass_xpath)

    # mtom = get_value(tixi,mtom_xpath)
    if mass:
        log.info(f"Aircraft mass use for this analysis is {mass} [kg]")
    else:
        raise ValueError("The chosen aircraft mass has not been found!")

    # Required input data that could be replace by a default value if missing
    cruise_alt = get_value_or_default(tixi, cruise_alt_xpath, 12000.0)
    cruise_mach = get_value_or_default(tixi, cruise_mach_xpath, 0.78)
    load_fact = get_value_or_default(tixi, load_fact_xpath, 1.05)

    # CL calculation
    target_cl = calculate_cl(ref_area, cruise_alt, cruise_mach, mass,
                             load_fact)

    # Save TargetCL and fixedCL option
    create_branch(tixi, SU2_XPATH)
    create_branch(tixi, SU2_XPATH + "/targetCL")
    create_branch(tixi, SU2_XPATH + "/fixedCL")
    tixi.updateDoubleElement(SU2_XPATH + "/targetCL", target_cl, "%g")
    tixi.updateTextElement(SU2_XPATH + "/fixedCL", "YES")
    log.info("Target CL has been saved in the CPACS file")

    cpacs.save_cpacs(cpacs_out_path, overwrite=True)
Пример #19
0
    def __init__(self, tabs, cpacs, module_name):
        """Tab class

        Note:
            A tab will only be created if the module actually has
            any settings which are to be shown

        Args:
            tabs (object): Tab object
            cpacs (object): CPACS object
            module_name (str): String of the module name for which a tab is to be created
        """

        self.var_dict = {}
        self.group_dict = {}

        self.module_name = module_name
        self.tabs = tabs
        self.cpacs = cpacs
        self.tab = tk.Frame(tabs, borderwidth=1)
        tabs.add(self.tab, text=module_name)

        # Get GUI dict from specs
        specs = mi.get_specs_for_module(module_name)

        self.gui_dict = specs.cpacs_inout.get_gui_dict()

        # canvas has replaced self.tab in the following lines
        space_label = tk.Label(self.tab, text=" ")
        space_label.grid(column=0, row=0)

        row_pos = 1

        for (
            key,
            (name, def_value, dtype, unit, xpath, description, group),
        ) in self.gui_dict.items():
            # Create a LabelFrame for new groupe
            if group:
                if group not in self.group_dict:
                    self.labelframe = tk.LabelFrame(self.tab, text=group)
                    self.labelframe.grid(
                        column=0, row=row_pos, columnspan=3, sticky=tk.W, padx=15, pady=5
                    )
                    self.group_dict[group] = self.labelframe
                parent = self.group_dict[group]
            else:  # if not a group, use tab as parent
                parent = self.tab

            # Name label for variable
            if name not in ["__AEROMAP_SELECTION", "__AEROMAP_CHECHBOX"]:
                self.name_label = tk.Label(parent, text=name)
                self.name_label.grid(column=0, row=row_pos, sticky=tk.W, padx=5, pady=5)

            # Type and Value
            if dtype is bool:
                self.var_dict[key] = tk.BooleanVar()
                value = get_value_or_default(self.cpacs.tixi, xpath, def_value)
                self.var_dict[key].set(value)
                bool_entry = tk.Checkbutton(parent, text="", variable=self.var_dict[key])
                bool_entry.grid(column=1, row=row_pos, padx=5, pady=5)

            elif dtype is int:
                value = get_value_or_default(self.cpacs.tixi, xpath, def_value)
                self.var_dict[key] = tk.IntVar()
                self.var_dict[key].set(int(value))
                value_entry = tk.Entry(parent, bd=2, width=8, textvariable=self.var_dict[key])
                value_entry.grid(column=1, row=row_pos, padx=5, pady=5)

            elif dtype is float:
                value = get_value_or_default(self.cpacs.tixi, xpath, def_value)
                self.var_dict[key] = tk.DoubleVar()
                self.var_dict[key].set(value)
                value_entry = tk.Entry(parent, bd=2, width=8, textvariable=self.var_dict[key])
                value_entry.grid(column=1, row=row_pos, padx=5, pady=5)

            elif dtype == "pathtype":

                value = get_value_or_default(self.cpacs.tixi, xpath, def_value)
                self.var_dict[key] = tk.StringVar()
                self.var_dict[key].set(value)
                value_entry = tk.Entry(parent, textvariable=self.var_dict[key])
                value_entry.grid(column=1, row=row_pos, padx=5, pady=5)

                self.key = key
                self.browse_button = tk.Button(parent, text="Browse", command=self._browse_file)
                self.browse_button.grid(column=2, row=row_pos, padx=5, pady=5)

            elif dtype is list:
                if name == "__AEROMAP_SELECTION":

                    # Get the list of all AeroMaps
                    self.aeromap_uid_list = self.cpacs.get_aeromap_uid_list()

                    # Try to get the pre-selected AeroMap from the xpath
                    try:
                        selected_aeromap = get_value(self.cpacs.tixi, xpath)
                        selected_aeromap_index = self.aeromap_uid_list.index(selected_aeromap)
                    except:
                        selected_aeromap = ""
                        selected_aeromap_index = 0

                    self.labelframe = tk.LabelFrame(parent, text="Choose an AeroMap")
                    self.labelframe.grid(
                        column=0, row=row_pos, columnspan=3, sticky=tk.W, padx=15, pady=5
                    )

                    # The Combobox is directly use as the varaible
                    self.var_dict[key] = ttk.Combobox(
                        self.labelframe, values=self.aeromap_uid_list
                    )
                    self.var_dict[key].current(selected_aeromap_index)
                    self.var_dict[key].grid(column=1, row=row_pos, padx=15, pady=5)

                elif name == "__AEROMAP_CHECHBOX":

                    # Just to find back the name when data are saved
                    self.var_dict[key] = None
                    # __AEROMAP_CHECHBOX is a bit different, data are saved in their own dictionary
                    self.aeromap_var_dict = {}

                    # Get the list of all AeroMaps
                    self.aeromap_uid_list = self.cpacs.get_aeromap_uid_list()
                    self.labelframe = tk.LabelFrame(parent, text="Selecte AeroMap(s)")
                    self.labelframe.grid(
                        column=0, row=row_pos, columnspan=3, sticky=tk.W, padx=15, pady=5
                    )

                    # Try to get pre-selected AeroMaps from the xpath
                    try:
                        selected_aeromap = get_string_vector(self.cpacs.tixi, xpath)
                    except ValueError:
                        selected_aeromap = ""

                    # Create one checkbox for each AeroMap
                    for aeromap in self.aeromap_uid_list:
                        self.aeromap_var_dict[aeromap] = tk.BooleanVar()

                        # if aeromap in selected_aeromap:
                        # For now, set all to True
                        self.aeromap_var_dict[aeromap].set(True)

                        aeromap_entry = tk.Checkbutton(
                            self.labelframe, text=aeromap, variable=self.aeromap_var_dict[aeromap]
                        )
                        aeromap_entry.pack(padx=5, pady=3, anchor=tk.W)  # side=tk.TOP)

                else:  # Other kind of list (not aeroMap)

                    # 'def_value' will be the list of possibilies in this case

                    # Try to get the pre-selected AeroMap from the xpath
                    try:  # TODO Should be retested...
                        selected_value = get_value(self.cpacs.tixi, xpath)
                        selected_value_index = def_value.index(selected_value)
                    except:
                        selected_value = ""
                        selected_value_index = 0

                    # The Combobox is directly use as the varaible
                    self.var_dict[key] = ttk.Combobox(parent, width=12, values=def_value)
                    self.var_dict[key].current(selected_value_index)
                    self.var_dict[key].grid(column=1, row=row_pos, padx=5, pady=5)

            else:
                value = get_value_or_default(self.cpacs.tixi, xpath, def_value)
                self.var_dict[key] = tk.StringVar()
                self.var_dict[key].set(value)
                value_entry = tk.Entry(parent, textvariable=self.var_dict[key])
                value_entry.grid(column=1, row=row_pos, padx=5, pady=5)

            # Units
            if unit and unit != "1":
                unit_label = tk.Label(parent, text=pretty_unit(unit))
                unit_label.grid(column=2, row=row_pos)

            row_pos += 1
Пример #20
0
def get_user_inputs(ed, ui, adui, cpacs_in):
    """Function to extract from the xml file the required input data,
        the code will use the default value when they are missing.

    Function 'get_user_inputs' ...

    Args:
        ed (int): EngineData class.
        ui (class): UserInputs class
        adui (str): AdvancedInputs class.
        cpacs_in (str): Path to the CPACS file

    Returns:
        ed (int): Updated ngineData class.
        ui (class): Updated UserInputs class
        adui (str): Updated AdvancedInputs class.

    """

    log.info("Starting data extraction from CPACS file")

    tixi = open_tixi(cpacs_in)

    create_branch(tixi, FUEL_XPATH, False)
    create_branch(tixi, GEOM_XPATH, False)
    create_branch(tixi, RANGE_XPATH, False)
    create_branch(tixi, PILOTS_XPATH, False)
    create_branch(tixi, CAB_CREW_XPATH, False)
    create_branch(tixi, PASS_XPATH, False)
    create_branch(tixi, ML_XPATH, False)
    create_branch(tixi, PROP_XPATH, False)

    # cpacs/vehicles
    MC_XPATH = MASSBREAKDOWN_XPATH + "/payload/mCargo/massDescription"

    create_branch(tixi, MC_XPATH, False)
    create_branch(tixi, F_XPATH, False)
    add_uid(tixi, F_XPATH, "kerosene")

    # Gathering data =========================================================
    # Geometry ===============================================================
    if not tixi.checkElement(GEOM_XPATH + "/description"):
        tixi.createElement(GEOM_XPATH, "description")
        tixi.updateTextElement(GEOM_XPATH + "/description",
                               "User " + "geometry input")

    ui.FLOORS_NB = get_value_or_default(tixi, GEOM_XPATH + "/floorsNb",
                                        ui.FLOORS_NB)
    adui.VRT_THICK = get_value_or_default(tixi, GEOM_XPATH + "/virtualThick",
                                          0.00014263)
    adui.VRT_STR_DENSITY = get_value_or_default(tixi,
                                                GEOM_XPATH + "/virtualDensity",
                                                2700.0)
    ui.H_LIM_CABIN = get_value_or_default(tixi, GEOM_XPATH + "/cabinHeight",
                                          2.3)

    # People =================================================================
    # Pilots user input data

    adui.PILOT_NB = get_value_or_default(tixi, PILOTS_XPATH + "/pilotNb", 2)
    adui.MASS_PILOT = get_value_or_default(tixi, PILOTS_XPATH + "/pilotMass",
                                           102.0)
    adui.MASS_CABIN_CREW = get_value_or_default(
        tixi, CAB_CREW_XPATH + "/cabinCrewMemberMass", 68.0)
    adui.MASS_PASS = get_value_or_default(tixi, PASS_XPATH + "/passMass",
                                          105.0)
    adui.PASS_BASE_DENSITY = get_value_or_default(tixi,
                                                  PASS_XPATH + "/passDensity",
                                                  1.66)
    adui.PASS_PER_TOILET = get_value_or_default(tixi,
                                                PASS_XPATH + "/passPerToilet",
                                                50)

    # what to to with this input
    if tixi.checkElement(PASS_XPATH + "/passNb"):
        temp = tixi.getIntegerElement(PASS_XPATH + "/passNb")
        if temp != ui.MAX_PASS and temp > 0:
            ui.MAX_PASS = temp

    # Fuel ===================================================================
    adui.FUEL_DENSITY = get_value_or_default(tixi, F_XPATH + "/density", 800)
    adui.RES_FUEL_PERC = get_value_or_default(tixi, F_XPATH + "/resFuelPerc",
                                              0.06)

    # Weight =================================================================
    # Mass limits data
    if not tixi.checkElement(ML_XPATH + "/description"):
        tixi.createElement(ML_XPATH, "description")
        tixi.updateTextElement(
            ML_XPATH + "/description",
            "Desired max fuel " + "volume [m^3] and payload mass [kg]")

    ui.MAX_PAYLOAD = get_value_or_default(tixi, ML_XPATH + "/maxPayload", 0.0)
    ui.MAX_FUEL_VOL = get_value_or_default(tixi, ML_XPATH + "/maxFuelVol", 0.0)
    ui.MASS_CARGO = get_value_or_default(tixi, MC_XPATH + "/massCargo", 0.0)
    # If the cargo mass is defined in the UserInputs class will be added
    # in the CPACS file after the analysis.

    # Flight =================================================================

    ed.TSFC_CRUISE = get_value_or_default(tixi, PROP_XPATH + "/tSFC", 0.5)

    # TODO: These data should be taken from aeroMaps...
    if not tixi.checkElement(RANGE_XPATH + "/lDRatio"):
        tixi.createElement(RANGE_XPATH, "lDRatio")
        tixi.updateDoubleElement(RANGE_XPATH + "/lDRatio", ui.LD, "%g")
    else:
        temp = tixi.getIntegerElement(RANGE_XPATH + "/lDRatio")
        if temp != ui.LD and temp > 0:
            ui.LD = temp

    if not tixi.checkElement(RANGE_XPATH + "/cruiseSpeed"):
        tixi.createElement(RANGE_XPATH, "cruiseSpeed")
        tixi.updateDoubleElement(RANGE_XPATH + "/cruiseSpeed", ui.CRUISE_SPEED,
                                 "%g")
    else:
        temp = tixi.getIntegerElement(RANGE_XPATH + "/cruiseSpeed")
        if temp != ui.CRUISE_SPEED and temp > 0:
            ui.CRUISE_SPEED = temp

    # TODO: see how to enter input for Engines
    if not tixi.checkElement(PROP_XPATH + "/userEngineOption"):
        tixi.createElement(PROP_XPATH, "userEngineOption")
        if ui.USER_ENGINES:
            tixi.updateTextElement(PROP_XPATH + "/userEngineOption", "True")
        else:
            tixi.updateTextElement(PROP_XPATH + "/userEngineOption", "False")
    else:
        temp = tixi.getTextElement(PROP_XPATH + "/userEngineOption")
        if temp == "False":
            ui.USER_ENGINES = False
        else:
            ui.USER_ENGINES = True

    if not tixi.checkElement(PROP_XPATH + "/singleHydraulics"):
        tixi.createElement(PROP_XPATH, "singleHydraulics")
        if adui.SINGLE_HYDRAULICS:
            tixi.updateTextElement(PROP_XPATH + "/singleHydraulics", "True")
        else:
            tixi.updateTextElement(PROP_XPATH + "/singleHydraulics", "False")
    else:
        temp = tixi.getTextElement(PROP_XPATH + "/singleHydraulics")
        if temp == "False":
            adui.SINGLE_HYDRAULICS = False
        else:
            adui.SINGLE_HYDRAULICS = True

    log.info("Data from CPACS file succesfully extracted")

    tixi.save(cpacs_in)

    return (ed, ui, adui)
Пример #21
0
def create_SU2_mesh(cpacs_path, cpacs_out_path):
    """Function to create a simple SU2 mesh form an SUMO file (.smx)

    Function 'create_mesh' is used to generate an unstructured mesh with  SUMO
    (which integrage Tetgen for the volume mesh) using a SUMO (.smx) geometry
    file as input.
    Meshing option could be change manually (only in the script for now)

    Source :
        * sumo help, tetgen help (in the folder /doc)

    Args:
        cpacs_path (str): Path to the CPACS file
        cpacs_out_path (str): Path to the output CPACS file

    """

    tixi = open_tixi(cpacs_path)

    sumo_dir = get_results_directory("SUMOAutoMesh")
    su2_mesh_path = os.path.join(sumo_dir, "ToolOutput.su2")

    sumo_file_xpath = "/cpacs/toolspecific/CEASIOMpy/filesPath/sumoFilePath"
    sumo_file_path = get_value_or_default(tixi, sumo_file_xpath, "")
    if sumo_file_path == "":
        raise ValueError("No SUMO file to use to create a mesh")

    # Set mesh parameters
    log.info("Mesh parameter will be set")
    refine_level_xpath = "/cpacs/toolspecific/CEASIOMpy/mesh/sumoOptions/refinementLevel"
    refine_level = get_value_or_default(tixi, refine_level_xpath, 0.0)
    log.info("Refinement level is {}".format(refine_level))
    add_mesh_parameters(sumo_file_path, refine_level)

    # Check current Operating System
    current_os = platform.system()

    if current_os == "Darwin":
        log.info("Your OS is Mac\n\n")
        log.info(
            "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!")
        log.info("On MacOS the mesh has to be generated manually.")
        log.info("To create a SU2Mesh you have to :")
        log.info("Open the .smx geometry that you will find there:")
        log.info(sumo_file_path)
        log.info('Click on the button "Mesh"')
        log.info('Click on "Create Mesh"')
        log.info('Click on "Volume Mesh"')
        log.info('Click on "Run"')
        log.info('When the mesh generation is completed, click on "Close"')
        log.info('Go to the Menu "Mesh" -> "Save volume mesh..."')
        log.info('Chose "SU2 (*.su2)" as File Type"')
        log.info("Copy/Paste the following line as File Name")
        log.info(su2_mesh_path)
        log.info('Click on "Save"')
        log.info("You can now close SUMO, your workflow will continue.")
        log.info(
            "More information:"
            "https://ceasiompy.readthedocs.io/en/latest/user_guide/modules/SUMOAutoMesh/index.html"
        )
        log.info(
            "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n\n"
        )

        # For now, I did not find a way to run "sumo -batch" on Mac...
        # The command just open SUMO GUI, the mesh has to be generate and save manually
        with change_working_dir(sumo_dir):
            command = ["open", "/Applications/SUMO/dwfsumo.app/"]

        # /Applications/SUMO/dwfsumo.app/Contents/MacOS/dwfsumo
        # -batch output=su2 -tetgen-options=pq1.16VY ToolOutput.smx
        os.system(" ".join(command))
        input("Press ENTER to continue...")

    elif current_os == "Linux":
        log.info("Your OS is Linux")

        # Check if SUMO is installed
        soft_dict = get_install_path(["sumo"])

        # Run SUMO in batch
        output = "-output=su2"
        options = "-tetgen-options=pq1.16VY"  # See Tetgen help for more options
        # Command line to run: sumo -batch -output=su2 -tetgen-options=pq1.16VY ToolOutput.smx
        command = [
            soft_dict["sumo"], "-batch", output, options, sumo_file_path
        ]

        with change_working_dir(sumo_dir):
            os.system(" ".join(command))

    elif current_os == "Windows":
        log.info("Your OS is Windows")
        # TODO: develop this part

        log.warning("OS not supported yet by SUMOAutoMesh!")
        raise NotImplementedError("OS not supported yet!")

    else:
        raise OSError("OS not recognize!")

    # Copy the mesh in the MESH directory
    su2_mesh_name = aircraft_name(tixi) + "_baseline.su2"
    su2_mesh_new_path = os.path.join(sumo_dir, su2_mesh_name)
    shutil.copyfile(su2_mesh_path, su2_mesh_new_path)

    if os.path.isfile(su2_mesh_new_path):
        log.info("An SU2 Mesh has been correctly generated.")
        create_branch(tixi, SU2MESH_XPATH)
        tixi.updateTextElement(SU2MESH_XPATH, su2_mesh_new_path)
        os.remove(su2_mesh_path)

    else:
        raise ValueError("No SU2 Mesh file has been generated!")

    tixi.save(cpacs_out_path)
Пример #22
0
    def get_user_inputs(self, cpacs_path):
        """Get user input from the CPACS file

        The function 'get_user_inputs' extracts from the CPACS file the required
        input data, the code will use the default value when they are missing.

        Args:
            cpacs_path (str): Path to CPACS file

        """

        tixi = open_tixi(cpacs_path)

        description = "User geometry input"
        get_value_or_default(tixi, GEOM_XPATH + "/description", description)

        self.IS_DOUBLE_FLOOR = get_value_or_default(
            tixi, GEOM_XPATH + "/isDoubleFloor", 0)
        self.PILOT_NB = get_value_or_default(tixi, PILOTS_XPATH + "/pilotNb",
                                             2)
        self.MASS_PILOT = get_value_or_default(tixi,
                                               PILOTS_XPATH + "/pilotMass",
                                               102)
        self.MASS_CABIN_CREW = get_value_or_default(
            tixi, CAB_CREW_XPATH + "/cabinCrewMemberMass", 68)
        self.MASS_PASS = get_value_or_default(tixi, PASS_XPATH + "/passMass",
                                              105)
        self.PASS_PER_TOILET = get_value_or_default(
            tixi, PASS_XPATH + "/passPerToilet", 50)

        description = "Desired max fuel volume [m^3] and payload mass [kg]"
        get_value_or_default(tixi, ML_XPATH + "/description", description)

        self.MAX_PAYLOAD = get_value_or_default(tixi, ML_XPATH + "/maxPayload",
                                                0)
        self.MAX_FUEL_VOL = get_value_or_default(tixi,
                                                 ML_XPATH + "/maxFuelVol", 0)
        self.MASS_CARGO = get_value_or_default(
            tixi, MASSBREAKDOWN_XPATH + "/payload/mCargo/massDescription/mass",
            0.0)
        self.FUEL_DENSITY = get_value_or_default(tixi, F_XPATH + "/density",
                                                 800)
        add_uid(tixi, F_XPATH, "kerosene")

        self.TURBOPROP = get_value_or_default(tixi, PROP_XPATH + "/turboprop",
                                              False)
        self.RES_FUEL_PERC = get_value_or_default(tixi,
                                                  FUEL_XPATH + "/resFuelPerc",
                                                  0.06)

        tixi.save(cpacs_path)
Пример #23
0
def generate_config_deformed_mesh(cpacs_path, cpacs_out_path, skip_config=False, skip_su2=False):
    """Function to generate all deform meshes with SU2 from CPACS data

    Function 'generate_config_deformed_mesh' reads data in the CPACS file
    and generate all the corresponding directory and config file which allow to
    generate deformed meshes.

    Args:
        cpacs_path (str): Path to CPACS file
        cpacs_out_path (str):Path to CPACS output file
        skip_config (bool):
        skip_su2 (bool):

    """

    tixi = open_tixi(cpacs_path)

    wkdir = get_results_directory("SU2Run")

    # Get SU2 mesh path
    su2_mesh_xpath = "/cpacs/toolspecific/CEASIOMpy/filesPath/su2Mesh"
    su2_mesh_path = get_value(tixi, su2_mesh_xpath)

    if wkdir in su2_mesh_path:
        log.info("The Baseline SU2 mesh is already in the working directory.")
    else:
        mesh_dir = os.path.join(wkdir, "MESH")
        if not os.path.isdir(mesh_dir):
            os.mkdir(mesh_dir)
        ac_name = aircraft_name(tixi)
        su2_mesh_new_path = os.path.join(mesh_dir, ac_name + "_baseline.su2")
        shutil.copyfile(su2_mesh_path, su2_mesh_new_path)
        tixi.updateTextElement(su2_mesh_xpath, su2_mesh_new_path)

    if not skip_config:

        # Control surfaces deflections
        control_surf_xpath = SU2_XPATH + "/options/clalculateCotrolSurfacesDeflections"
        control_surf = get_value_or_default(tixi, control_surf_xpath, False)

        if not control_surf:
            log.warning(
                "The CPACS file indicate that Control surface deflection should not be calculated!"
            )
            # active_ted_list = []
        else:

            ted_df = get_ted_list(tixi)

            # TODO: option to calculate only TED selected in cpacs
            # if ...
            #     active_ted_xpath = SU2_XPATH + '/options/....'
            #     # check element
            #     active_ted_list = get_string_vector(tixi,active_ted_xpath)
            # else: calculate all TED adn all deflections from CPACS
            #     active_ted_list = ted_list

            for i, row in ted_df.iterrows():

                # Unwrap TED data from the dataframe
                ted_uid = row["ted_uid"]
                wing_uid = row["wing_uid"]
                sym_dir = row["sym_dir"]
                defl_list = row["defl_list"]

                generate_mesh_def_config(tixi, wkdir, ted_uid, wing_uid, sym_dir, defl_list)

    if not skip_su2:

        run_mesh_deformation(tixi, wkdir)

    tixi.save(cpacs_out_path)