def toolspecific_update(fus_nb, awg, mw, out, cpacs_out_path): """ The function that update the cpacs file after the Weight_unc_main program. Args: fus_nb (integer): Number of fuselage [-]. awg (class): AircraftWingGeometry class. mw (class): Masses and Weights class. out (class): Output class. cpacs_out_path (str): Path of the output file. """ tixi = open_tixi(cpacs_out_path) # Path creation create_branch(tixi, CREW_XPATH + "/cabinCrewMembers", False) create_branch(tixi, PASS_XPATH, False) # Path update if not tixi.checkElement(CREW_XPATH + "/cabinCrewMembers/cabinCrewMemberNb"): tixi.createElement(CREW_XPATH + "/cabinCrewMembers", "cabinCrewMemberNb") tixi.updateDoubleElement( CREW_XPATH + "/cabinCrewMembers/cabinCrewMemberNb", out.cabin_crew_nb, "%g") if not tixi.checkElement(PASS_XPATH + "/passNb"): tixi.createElement(PASS_XPATH, "passNb") tixi.updateIntegerElement(PASS_XPATH + "/passNb", out.pass_nb, "%i") if not tixi.checkElement(PASS_XPATH + "/mass"): tixi.createElement(PASS_XPATH, "mass") tixi.updateDoubleElement(PASS_XPATH + "/mass", mw.mass_pass, "%g") if not tixi.checkElement(PASS_XPATH + "/toiletNb"): tixi.createElement(PASS_XPATH, "toiletNb") tixi.updateIntegerElement(PASS_XPATH + "/toiletNb", int(out.toilet_nb), "%i") if not tixi.checkElement(PASS_XPATH + "/fuelMassMaxpass"): tixi.createElement(PASS_XPATH, "fuelMassMaxpass") FMP_PATH = PASS_XPATH + "/fuelMassMaxpass" if not tixi.checkElement(FMP_PATH + "/description"): tixi.createElement(FMP_PATH, "description") tixi.updateTextElement( FMP_PATH + "/description", "Maximum amount of " + "fuel with maximum payload [kg]") if not tixi.checkElement(FMP_PATH + "/mass"): tixi.createElement(FMP_PATH, "mass") tixi.updateDoubleElement(FMP_PATH + "/mass", mw.mass_fuel_maxpass, "%g") tixi.save(cpacs_out_path) return ()
def _save_quit(self): # Iterate over all existing tabs for tab in self.tab_list: # Iterate in Variable dictionary of each tab for key, var in tab.var_dict.items(): # Get the XPath from the GUI setting dictionary and crate a branch name = tab.gui_dict[key][0] xpath = tab.gui_dict[key][4] create_branch(self.cpacs.tixi, xpath) if name == "__AEROMAP_CHECHBOX": aeromap_uid_list_str = "" for aeromap_uid, aeromap_bool in tab.aeromap_var_dict.items(): if aeromap_bool.get(): aeromap_uid_list_str += aeromap_uid + ";" if aeromap_uid_list_str == "": messagebox.showerror( "ValueError", 'In the Tab "' + tab.module_name + '", no value has been selected for "' + name + '" ', ) raise TypeError("No value has been selected for " + name + " !") self.cpacs.tixi.updateTextElement(xpath, aeromap_uid_list_str) # '__AEROMAP_SELECTION' and list Type value will be saved as any other variable else: if str(var.get()) == "": # Not working when it expect an 'int' or a 'float' messagebox.showerror( "ValueError", 'In the Tab "' + tab.module_name + '", no value has been entered for "' + name + '" ', ) raise TypeError("No value has been entered for " + name + " !") try: self.cpacs.tixi.updateTextElement(xpath, str(var.get())) except: messagebox.showerror( "TypeError", 'In the Tab "' + tab.module_name + '", the value "' + name + '" has not the correct type!', ) raise TypeError(name + " has not the correct type!") self.cpacs.save_cpacs(self.cpacs_out_path, overwrite=True) self.quit()
def get_user_fuel(fus_nb, ui, cpacs_in): """ Function to extract from the xml file the required input data, the code will use the default value when they are missing. INPUT (int) fus_nb --Arg.: Number of fuselage. (class) ui --Arg.: UserInputs class. ##======= Classes are defined in the InputClasses folder =======## (char) cpacs_in --Arg.: Relative location of the xml file in the ToolInput folder (cpacs option) or relative location of the temp. xml file in the ToolOutput folder (input option). OUTPUT (class) ui --Out.: UserInputs class. (file) cpacs_in --Out.: Updated cpasc file """ log.info("Starting data extraction from CPACS file") # Path creation ========================================================== tixi = open_tixi(cpacs_in) FUEL_PATH = "/cpacs/toolspecific/CEASIOMpy/fuels" create_branch(tixi, FUEL_PATH, False) if fus_nb: for i in range(0, fus_nb): if fus_nb > 1: F = "fuelOnCabin" + str(i + 1) else: F = "fuelOnCabin" if not tixi.checkElement(FUEL_PATH + "/" + F): tixi.createElement(FUEL_PATH, F) tixi.updateDoubleElement(FUEL_PATH + "/" + F, ui.F_FUEL[i], "%g") else: ui.F_FUEL[i] = tixi.getDoubleElement(FUEL_PATH + "/" + F) else: if not tixi.checkElement(FUEL_PATH + "/fuelOnCabin"): tixi.createElement(FUEL_PATH, "fuelOnCabin") tixi.updateDoubleElement(FUEL_PATH + "/fuelOnCabin", ui.FUEL_ON_CABIN, "%g") # Modif Aidan: this code seems useless # else: # temp = tixi.updateDoubleElement(FUEL_PATH + '/fuelOnCabin',\ # ui.FUEL_ON_CABIN, '%g') # if temp != ui.FUEL_ON_CABIN and temp > 0: # ui.FUEL_ON_CABIN = temp log.info("Data from CPACS file succesfully extracted") # # Saving and closing the cpacs file -------------------------------------- tixi.save(cpacs_in) return ui
def get_user_fuel(fus_nb, ui, cpacs_in): """Function to extract fuel data from a CPACS file Function 'get_user_fuel' extracts fuel data from the CPACS file, the code will use the default value when they are missing. Args: fus_nb (int): Number of fuselage. ui (class): UserInputs class cpacs_in (str): Path to the CPACS file Returns: ui (class): Modified UserInputs class """ log.info("Starting data extraction from CPACS file") tixi = open_tixi(cpacs_in) create_branch(tixi, FUEL_XPATH, False) if fus_nb: for i in range(0, fus_nb): if fus_nb > 1: F = "fuelOnCabin" + str(i + 1) else: F = "fuelOnCabin" if not tixi.checkElement(FUEL_XPATH + "/" + F): tixi.createElement(FUEL_XPATH, F) tixi.updateDoubleElement(FUEL_XPATH + "/" + F, ui.F_FUEL[i], "%g") else: ui.F_FUEL[i] = tixi.getDoubleElement(FUEL_XPATH + "/" + F) else: if not tixi.checkElement(FUEL_XPATH + "/fuelOnCabin"): tixi.createElement(FUEL_XPATH, "fuelOnCabin") tixi.updateDoubleElement(FUEL_XPATH + "/fuelOnCabin", ui.FUEL_ON_CABIN, "%g") else: temp = tixi.updateDoubleElement(FUEL_XPATH + "/fuelOnCabin", ui.FUEL_ON_CABIN, "%g") if temp != ui.FUEL_ON_CABIN and temp > 0: ui.FUEL_ON_CABIN = temp log.info("Data from CPACS file succesfully extracted") tixi.save(cpacs_in) return ui
def cpacs_engine_update(ui, ed, mw, cpacs_out_path): """ The function that update the cpacs file after the Weight_unc_main program. Args: mw (class): MassesWeihts class. ui (class): UserInputs class. ed (class): EngineData class. cpacs_out_path (str): Path of the CPACS output file. """ tixi = open_tixi(cpacs_out_path) EN_XPATH = "/cpacs/vehicles/engines" if tixi.checkElement(EN_XPATH): tixi.removeElement(EN_XPATH) for e in range(0, ed.NE): EN_XPATH = "/cpacs/vehicles/engines/engine" + str(e + 1) create_branch(tixi, EN_XPATH, True) EN_UID = "EngineuID_" + str(e + 1) add_uid(tixi, EN_XPATH, EN_UID) tixi.createElement(EN_XPATH, "name") if not ed.EN_NAME[e]: EN_NAME = "Engine_" + str(e + 1) tixi.updateTextElement(EN_XPATH + "/name", EN_NAME) else: tixi.updateTextElement(EN_XPATH + "/name", ed.EN_NAME[e]) ENA_XPATH = EN_XPATH + "/analysis/mass" create_branch(tixi, ENA_XPATH, False) add_uid(tixi, EN_XPATH, EN_UID + "_mass") tixi.createElement(ENA_XPATH, "mass") tixi.updateDoubleElement(ENA_XPATH + "/mass", ed.en_mass, "%g") ENT_XPATH = EN_XPATH + "/analysis" tixi.createElement(ENT_XPATH, "thrust00") tixi.updateDoubleElement(ENT_XPATH + "/thrust00", ed.max_thrust, "%g") tixi.save(cpacs_out_path) return ()
def create_default_toolspecific(): """Create a default XML /toolspecific based on all __spec__ xpath and default values. Two CPACS file are created and saved in /utils/doc/ """ CPACS_PATH = "./doc/empty_cpacs.xml" tixi_in = open_tixi(CPACS_PATH) tixi_out = open_tixi(CPACS_PATH) for mod_name, specs in get_all_module_specs().items(): if specs is not None: # Inputs for entry in specs.cpacs_inout.inputs: xpath = entry.xpath if xpath.endswith("/"): xpath = xpath[:-1] value_name = xpath.split("/")[-1] xpath_parent = xpath[:-(len(value_name) + 1)] if not tixi_in.checkElement(xpath): create_branch(tixi_in, xpath_parent) if entry.default_value is not None: value = str(entry.default_value) else: value = "No default value" tixi_in.addTextElement(xpath_parent, value_name, value) # Outputs for entry in specs.cpacs_inout.outputs: xpath = entry.xpath create_branch(tixi_out, xpath) TOOLSPECIFIC_INPUT_PATH = "./doc/input_toolspecifics.xml" TOOLSPECIFIC_OUTPUT_PATH = "./doc/output_toolspecifics.xml" tixi_in.save(TOOLSPECIFIC_INPUT_PATH) tixi_out.save(TOOLSPECIFIC_OUTPUT_PATH)
def write_inouts(v, inout, tixi): """Write inputs or outputs to cpacs. Write the specified input or the predicted output of the model to the CPACS file. Args: v (DataFrame): Contains the inout, locations. inout (np.array): Values of the inout. Returns: None. """ v.fillna("-", inplace=True) for i, name in enumerate(v.index): if v.loc[name, "setcmd"] != "-": exec("{} = {}".format(name, inout[0][i])) eval(v.loc[name, "setcmd"]) elif v.loc[name, "getcmd"] != "-": xpath = v.loc[name, "getcmd"] create_branch(tixi, xpath) tixi.updateDoubleElement(xpath, inout[0][i], "%g")
def cpacs_update(mass_pass, out, mw, out_xml): """ The function updates the cpacs file after the range analysis. INPUT (float) mass_pass --Arg.: Passenger mass, countig also the extra mass. (class) out --Arg.: RangeOutput class. ##======== Class is defined in the InputClasses folder =======## (class) mw --Arg.: MassesWeights class. ##======= Class is defined in the InputClasses folder =======## (char) out_xml --Arg.: Path of the output file. OUTPUT (file) cpacs.xml --Out.: Updated cpacs file. """ tixi = open_tixi(out_xml) # PATH CHECKS ========================================================== CEASIOM_PATH = "/cpacs/toolspecific/CEASIOMpy" # Ranges RANGE_PATH = CEASIOM_PATH + "/ranges" R_DES_MAXP_PATH = RANGE_PATH + "/rangeMaxP/rangeDescription" R_DES_MAXF_PATH = RANGE_PATH + "/rangeMaxF/rangeDescription" R_DES_MAXIMUM_PATH = RANGE_PATH + "/rangeMaximum/rangeDescription" create_branch(tixi, R_DES_MAXP_PATH + "/range", False) create_branch(tixi, R_DES_MAXP_PATH + "/payload", False) create_branch(tixi, R_DES_MAXF_PATH + "/range", False) create_branch(tixi, R_DES_MAXF_PATH + "/payload", False) create_branch(tixi, R_DES_MAXIMUM_PATH + "/range", False) create_branch(tixi, R_DES_MAXIMUM_PATH + "/payload", False) # Fuel consumption FCONS_PATH = "/cpacs/toolspecific/CEASIOMpy/fuelConsumption" FDES_PATH = FCONS_PATH + "/description" FTO_PATH = FCONS_PATH + "/fuelForTakeOff" FC_PATH = FCONS_PATH + "/fuelForClimb" FCR_PATH = FCONS_PATH + "/fuelForCruise" FL_PATH = FCONS_PATH + "/fuelForLoiter" FLD_PATH = FCONS_PATH + "/fuelForLanding" FAL_PATH = FCONS_PATH + "/fuelRemained" create_branch(tixi, FDES_PATH, False) create_branch(tixi, FTO_PATH, False) create_branch(tixi, FC_PATH, False) create_branch(tixi, FCR_PATH, False) create_branch(tixi, FL_PATH, False) create_branch(tixi, FLD_PATH, False) create_branch(tixi, FAL_PATH, False) # RANGES =============================================================== # Max payload max range ------------------------------------------------ add_uid(tixi, R_DES_MAXP_PATH, "Maximum_range_[km]" + "_with_maximum_payload_[kg]") tixi.updateDoubleElement(R_DES_MAXP_PATH + "/range", out.ranges[1], "%g") tixi.updateDoubleElement(R_DES_MAXP_PATH + "/payload", out.payloads[1], "%g") # Max fuel range with some payload ------------------------------------- add_uid(tixi, R_DES_MAXF_PATH, "Range_[km]_with_" + "maximum_fuel_and_some_payload_[kg]") tixi.updateDoubleElement(R_DES_MAXF_PATH + "/range", out.ranges[2], "%g") tixi.updateDoubleElement(R_DES_MAXF_PATH + "/payload", out.payloads[2], "%g") # Maximum range, no payload and max fuel ------------------------------- add_uid(tixi, R_DES_MAXIMUM_PATH, "Maximum_range_[km]_with_" + "max_fuel_and_no_payload_[kg]") tixi.updateDoubleElement(R_DES_MAXIMUM_PATH + "/range", out.ranges[3], "%g") tixi.updateDoubleElement(R_DES_MAXIMUM_PATH + "/payload", out.payloads[3], "%g") # FUEL CONSUMPTION ===================================================== add_uid( tixi, FDES_PATH, "Fuel required for each flight phase " + "[kg], with maximum payload.") tixi.updateDoubleElement(FTO_PATH, mw.mf_for_to, "%g") tixi.updateDoubleElement(FC_PATH, mw.mf_for_climb, "%g") tixi.updateDoubleElement(FCR_PATH, mw.mf_for_cruise, "%g") tixi.updateDoubleElement(FL_PATH, mw.mf_for_loiter, "%g") tixi.updateDoubleElement(FLD_PATH, mw.mf_for_landing, "%g") tixi.updateDoubleElement(FAL_PATH, mw.mf_after_land, "%g") # Saving and closing the new cpacs file inside the ToolOutput folder --- tixi.save(out_xml) return out_xml
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)
def get_engine_inputs(ui, ed, cpacs_in): """ Function to extract from the xml file the required input data, the code will use the default value when they are missing. INPUT (class) ui --Arg.: UserInputs class. (class) ed --Arg.: EngineData class. ##======= Class ares defined in the InputClasses folder =======## (char) cpacs_in --Arg.: Relative location of the xml file in the ToolInput folder (cpacs option) or relative location of the temp. xml file in the ToolOutput folder (input option). OUTPUT (class) ed --Out.: EngineData class. (file) cpacs_in --Out.: Updated cpasc file """ log.info("Starting engine data extraction from CPACS file") tixi = open_tixi(cpacs_in) create_branch(tixi, PROP_XPATH, False) # Propulsion ============================================================= if not tixi.checkElement(PROP_XPATH + "/turboprop"): create_branch(tixi, PROP_XPATH, False) tixi.createElement(PROP_XPATH, "turboprop") if ed.TURBOPROP: tixi.updateTextElement(PROP_XPATH + "/turboprop", "True") else: tixi.updateTextElement(PROP_XPATH + "/turboprop", "False") else: temp = tixi.getTextElement(PROP_XPATH + "/turboprop") if temp == "False": ed.TURBOPROP = False else: ed.TURBOPROP = True if not tixi.checkElement(PROP_XPATH + "/auxiliaryPowerUnit"): tixi.createElement(PROP_XPATH, "auxiliaryPowerUnit") if ed.APU: tixi.updateTextElement(PROP_XPATH + "/auxiliaryPowerUnit", "True") else: tixi.updateTextElement(PROP_XPATH + "/auxiliaryPowerUnit", "False") else: temp = tixi.getTextElement(PROP_XPATH + "/auxiliaryPowerUnit") if temp == "False": ed.APU = False else: ed.APU = True if not tixi.checkElement(PROP_XPATH + "/engineNumber"): tixi.createElement(PROP_XPATH, "engineNumber") tixi.updateIntegerElement(PROP_XPATH + "/engineNumber", ed.NE, "%i") else: ed.NE = tixi.getIntegerElement(PROP_XPATH + "/engineNumber") # Engines (TODO: check this _XPATH) mp = [] tp = [] EN_XPATH = "/cpacs/vehicles/engines" if tixi.checkElement(EN_XPATH): for e in range(0, ed.NE - 1): if ed.NE > 1: EN_XPATH += "/engine" + str(e + 1) else: EN_XPATH += "/engine" if not tixi.checkElement(EN_XPATH): raise Exception("Engine definition inclomplete, missing" + " one or more engines in the cpacs file") if not tixi.checkElement(EN_XPATH + "/name"): ed.EN_NAME.append("Engine_" + str(e + 1)) tixi.createElement(EN_XPATH, "name") tixi.updateTextElement(EN_XPATH + "/name", ed.EN_NAME[e]) else: if e > len(ed.EN_NAME): ed.EN_NAME.append(tixi.getTextElement(EN_XPATH + "/name")) ENA_XPATH = EN_XPATH + "/analysis/mass" if tixi.checkElement(ENA_XPATH + "/mass"): ed.en_mass = tixi.getDoubleElement(ENA_XPATH + "/mass") mp.append(ed.en_mass) if e > 0 and ed.en_mass != mp[e - 1]: raise Exception("The engines have different masses, this" + " can lead to an unbalanced aircraft") elif ed.en_mass: tixi.createElement(ENA_XPATH, "mass") tixi.updateDoubleElement(ENA_XPATH + "/mass", ed.en_mass, "%g") else: raise Exception("Engine definition inclomplete, missing" + " engine mass in the cpacs file") if tixi.checkElement(EN_XPATH + "/analysis/thrust00"): ed.max_thrust = tixi.getDoubleElement(EN_XPATH + "/analysis/thrust00") tp.append(ed.max_thrust) if e > 0 and ed.max_thrust != tp[e - 1]: raise Exception("The engines have different thrust, this") # + ' can lead to an unbalanced flight') elif ed.max_thrust: tixi.createElement(EN_XPATH, "/analysisthrust00") tixi.updateDoubleElement(EN_XPATH + "/analysis/thrust00", ed.max_thrust, "%g") else: raise Exception("Engine definition inclomplete, missing" + " engine thrust in the cpacs file") log.info("Data from CPACS file succesfully extracted") tixi.save(cpacs_in) return ed
def get_data(ui, bi, mw, ed, cpacs_in): """ The function extracts from the xml file the required input data, the code will use the default value when they are missing. INPUT (class) ui --Arg.: UserInputs class. (class) bi --Arg.: BalanceInputs class. (class) mw --Arg.: MassesWeight class. (class) ed --Arg.: EngineData class. ##======= Classes are defined in the InputClasses folder =======## (char) cpacs_in --Arg.: Relative location of the xml file in the ToolInput folder (cpacs option) or relative location of the temp. xml file in the ToolOutput folder (input option). OUTPUT (class) mw --Out.: MassesWeight class updated. (class) ed --Out.: EngineData class updated. (file) cpacs_in --Out.: Updated cpasc file. """ log.info("CPACS file path check") # path definition ======================================================== # Opening CPACS file tixi = open_tixi(cpacs_in) TSPEC_PATH = "/cpacs/toolspecific/CEASIOMpy" GEOM_PATH = TSPEC_PATH + "/geometry" FMP_PATH = TSPEC_PATH + "/weight/passengers/fuelMassMaxpass/mass" PROP_PATH = TSPEC_PATH + "/propulsion" MASS_PATH = "/cpacs/vehicles/aircraft/model/analyses/massBreakdown" MTOM_PATH = MASS_PATH + "/designMasses/mTOM/mass" F_PATH = MASS_PATH + "/fuel/massDescription/mass" OEM_PATH = MASS_PATH + "/mOEM/massDescription/mass" PAY_PATH = MASS_PATH + "/payload/massDescription/mass" EN_PATH = "/cpacs/vehicles/engines/engine1/analysis/mass/mass" BC_PATH = TSPEC_PATH + "/balance/userBalance" create_branch(tixi, BC_PATH, False) # Compulsory path checks ================================================= if not tixi.checkElement(TSPEC_PATH): raise Exception("Missing required toolspecific path. Run " + "Weight_unc_main.py," + " in the 4Weight_unc_module folder.") elif not tixi.checkElement(MASS_PATH): raise Exception("Missing required massBreakdown path. Run " + "Weight_unc_main.py," + " in the 4Weight_unc_module folder.") elif not tixi.checkElement(MTOM_PATH): raise Exception("Missing required mTOM/mass path. Run " + "Weight_unc_main.py," + " in the 4Weight_unc_module folder.") elif not tixi.checkElement(FMP_PATH): raise Exception("Missing required fuelMassMaxpass/mass path. Run " + "Weight_unc_main.py," + " in the 4Weight_unc_module folder.") elif not tixi.checkElement(OEM_PATH): raise Exception("Missing required mOEM/massDescription/mass " + "path. Run Weight_unc_main.py," + " in the 4Weight_unc_module folder.") elif not tixi.checkElement(PAY_PATH): raise Exception("Missing required payload/massDescription/mass " + "path. Run Weight_unc_main.py," + " in the 4Weight_unc_module folder.") elif not tixi.checkElement(F_PATH): raise Exception("Missing required /fuel/massDescription/mass " + "path. Run Weight_unc_main.py," + " in the 4Weight_unc_module folder.") elif not tixi.checkElement(EN_PATH): raise Exception("Missing required /cpacs/vehicles/engines/engine1" + "/analysis/mass path. Run Weight_unc_main.py," + " in the 4Weight_unc_module folder.") else: log.info("All path correctly defined in the toolinput.xml file, " + "beginning data extracction.") # Gathering data ========================================================= # Geometry Data if not tixi.checkElement(GEOM_PATH + "/floorsNb"): tixi.createElement(GEOM_PATH, "floorsNb") tixi.updateDoubleElement(GEOM_PATH + "/floorsNb", ui.FLOORS_NB, "%g") else: temp = tixi.getDoubleElement(GEOM_PATH + "/floorsNb") if temp != ui.FLOORS_NB and temp > 0: ui.FLOORS_NB = temp if not tixi.checkElement(GEOM_PATH + "/cabinHeight"): tixi.createElement(GEOM_PATH, "cabinHeight") tixi.updateDoubleElement(GEOM_PATH + "/cabinHeight", ui.H_LIM_CABIN, "%g") else: temp = tixi.getDoubleElement(GEOM_PATH + "/cabinHeight") if temp != ui.H_LIM_CABIN and temp > 0: ui.H_LIM_CABIN = temp # User Case Balance if not tixi.checkElement(BC_PATH + "/userCase"): tixi.createElement(BC_PATH, "userCase") if bi.USER_CASE: tixi.updateTextElement(BC_PATH + "/userCase", "True") else: tixi.updateTextElement(BC_PATH + "/userCase", "False") else: temp = tixi.getTextElement(BC_PATH + "/userCase") if temp == "False": bi.USER_CASE = False else: bi.USER_CASE = True if bi.USER_CASE: if tixi.checkElement(BC_PATH + "/fuelPercentage"): bi.F_PERC = tixi.getDoubleElement(BC_PATH + "/fuelPercentage") elif bi.F_PERC: tixi.createElement(BC_PATH, "fuelPercentage") tixi.updateDoubleElement(BC_PATH + "/fuelPercentage", bi.F_PERC, "%g") else: raise Exception("User balance option defined" + " True but no fuel percentage data in the" + " CPACS file or in th BalanceInput class.") if tixi.checkElement(BC_PATH + "/payloadPercentage"): bi.P_PERC = tixi.getDoubleElement(BC_PATH + "/payloadPercentage") elif bi.P_PERC: tixi.createElement(BC_PATH, "payloadPercentage") tixi.updateDoubleElement(BC_PATH + "/payloadPercentage", bi.P_PERC, "%g") else: raise Exception("User balance option defined" + " True but no payload percentage data in" + " the CPACS file or in th BalanceInput class.") # Engines Data ed.en_mass = tixi.getDoubleElement(EN_PATH) if not tixi.checkElement(PROP_PATH + "/wingMountedEngine"): create_branch(tixi, PROP_PATH, False) tixi.createElement(PROP_PATH, "wingMountedEngine") if ed.WING_MOUNTED: tixi.updateTextElement(PROP_PATH + "/wingMountedEngine", "True") else: tixi.updateTextElement(PROP_PATH + "/wingMountedEngine", "False") else: temp = tixi.getTextElement(PROP_PATH + "/wingMountedEngine") if temp == "False": ed.WING_MOUNTED = False else: ed.WING_MOUNTED = True if not tixi.checkElement(PROP_PATH + "/userEnginePlacement"): tixi.createElement(PROP_PATH, "userEnginePlacement") if bi.USER_EN_PLACEMENT: tixi.updateTextElement(PROP_PATH + "/userEnginePlacement", "True") else: tixi.updateTextElement(PROP_PATH + "/userEnginePlacement", "False") else: temp = tixi.getTextElement(PROP_PATH + "/userEnginePlacement") if temp == "False": bi.USER_EN_PLACEMENT = False else: bi.USER_EN_PLACEMENT = True if not tixi.checkElement(PROP_PATH + "/engineNumber"): create_branch(tixi, PROP_PATH, False) tixi.createElement(PROP_PATH, "engineNumber") tixi.updateIntegerElement(PROP_PATH + "/engineNumber", ed.NE, "%i") else: ed.NE = tixi.getIntegerElement(PROP_PATH + "/engineNumber") # User Engine Placement tp = [] ed.EN_NAME = [] if tixi.checkElement(EN_PATH): for e in range(0, ed.NE): EN_PATH = "/cpacs/vehicles/engines" if ed.NE > 1: EN_PATH += "/engine" + str(e + 1) else: EN_PATH += "/engine" if not tixi.checkElement(EN_PATH): raise Exception("Engine definition inclomplete, missing" + " one or more engines in the cpacs file") if not tixi.checkElement(EN_PATH + "/name"): ed.EN_NAME.append("Engine_" + str(e + 1)) else: ed.EN_NAME.append(tixi.getTextElement(EN_PATH + "/name")) ENA_PATH = EN_PATH + "/analysis/mass" if tixi.checkElement(ENA_PATH): ed.en_mass = tixi.getDoubleElement(ENA_PATH + "/mass") tp.append(ed.en_mass) if e > 0 and ed.en_mass != tp[e - 1]: log.warning("The engines have different masses," + "this can lead to an unbalanced aircraft") elif ed.en_mass: tixi.createElement(ENA_PATH, "mass") tixi.updateDoubleElement(ENA_PATH + "/mass", ed.en_mass, "%g") else: raise Exception("Engine definition inclomplete, missing" + " engine mass in the cpacs file") s = np.shape(ed.EN_PLACEMENT) warn = False if not ed.NE: raise Exception("No engine defined for the aircraft") elif s[0] < ed.NE or s[1] < 3 or np.any(ed.EN_PLACEMENT) == False: warn = True else: log.info("EngineData class defined correctly.") s = ed.EN_PLACEMENT if bi.USER_EN_PLACEMENT: ed.EN_PLACEMENT = [] for e in range(1, ed.NE + 1): if ed.NE > 1: ENLOC_PATH = "/cpacs/vehicles/engines/engine" + str( e) + "/analysis/mass/location" else: ENLOC_PATH = "/cpacs/vehicles/engines/engine/analysis/mass/location" if not tixi.checkElement(ENLOC_PATH) and warn: raise Exception("User engine Placement option defined" + " True but no engine placement data in the" + " CPACS file.") if not tixi.checkElement(ENLOC_PATH) and not warn: create_branch(tixi, ENLOC_PATH, False) tixi.createElement(ENLOC_PATH, "x") tixi.createElement(ENLOC_PATH, "y") tixi.createElement(ENLOC_PATH, "z") tixi.updateDoubleElement(ENLOC_PATH + "/x", s[e - 1][0], "%g") tixi.updateDoubleElement(ENLOC_PATH + "/y", s[e - 1][1], "%g") tixi.updateDoubleElement(ENLOC_PATH + "/z", s[e - 1][2], "%g") ed.EN_PLACEMENT.append([s[e - 1][0], s[e - 1][1], s[e - 1][2]]) else: x = tixi.getDoubleElement(ENLOC_PATH + "/x") y = tixi.getDoubleElement(ENLOC_PATH + "/y") z = tixi.getDoubleElement(ENLOC_PATH + "/z") ed.EN_PLACEMENT.append([x, y, z]) ed.EN_PLACEMENT = np.array(ed.EN_PLACEMENT) # REQUIRED TOOLSPECIFIC DATA ============================================ # Fuel mw.mass_fuel_maxpass = tixi.getDoubleElement(FMP_PATH) # REQUIRED MASSBREAKDOWN DATA =========================================== mw.maximum_take_off_mass = tixi.getDoubleElement(MTOM_PATH) mw.operating_empty_mass = tixi.getDoubleElement(OEM_PATH) mw.mass_payload = tixi.getDoubleElement(PAY_PATH) mw.mass_fuel_tot = tixi.getDoubleElement(F_PATH) log.info("Data from CPACS file succesfully extracted") # # Saving and closing the cpacs file ====================================== tixi.save(cpacs_in) return (mw, ed)
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)
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)
def get_data(mw, ri, cpacs_in): """ The function extracts from the xml file the required input data, the code will use the default value when they are missing. INPUT (class) mw --Arg.: MassesWeight class updated (class) ri --Arg.: RangeInput class updated ##======= Classes are defined in the Input_classes folder =======## (char) opt --Arg.: Cpacs or input option (char) cpacs_in --Arg.: Relative location of the xml file in the ToolInput folder (cpacs option) or relative location of the temp. xml file in the ToolOutput folder (input option). OUTPUT (class) mw --Out.: MassesWeight class updated. (class) ri --Out.: RangeInput class updated. (file) cpacs_in --Out.: Updated cpasc file. """ log.info("CPACS file path check") # path definition ======================================================== # Opening CPACS file tixi = open_tixi(cpacs_in) TSPEC_PATH = "/cpacs/toolspecific/CEASIOMpy" W_PATH = TSPEC_PATH + "/weight" C_PATH = W_PATH + "/crew" P_PATH = C_PATH + "/pilots" CC_PATH = C_PATH + "/cabinCrewMembers" PASS_PATH = W_PATH + "/passengers" FMP_PATH = PASS_PATH + "/fuelMassMaxpass/mass" PROP_PATH = TSPEC_PATH + "/propulsion" RANGE_PATH = TSPEC_PATH + "/ranges" MASS_PATH = "/cpacs/vehicles/aircraft/model/analyses/massBreakdown" DM_PATH = MASS_PATH + "/designMasses" MTOM_PATH = DM_PATH + "/mTOM/mass" F_PATH = MASS_PATH + "/fuel/massDescription/mass" OEM_PATH = MASS_PATH + "/mOEM/massDescription/mass" PAY_PATH = MASS_PATH + "/payload/massDescription/mass" F1_PATH = "/cpacs/vehicles/fuels/fuel" F2_PATH = TSPEC_PATH + "/fuels" TSFC_PATH = PROP_PATH + "/tSFC" create_branch(tixi, TSFC_PATH, False) create_branch(tixi, RANGE_PATH, False) create_branch(tixi, P_PATH, False) create_branch(tixi, F1_PATH, False) create_branch(tixi, F2_PATH, False) add_uid(tixi, F1_PATH, "kerosene") # Compulsory path checks ================================================= if not tixi.checkElement(TSPEC_PATH): raise Exception("Missing required toolspecific path.") elif not tixi.checkElement(CC_PATH + "/cabinCrewMemberNb"): raise Exception("Missing required cabinCrewMemberNb path.") elif not tixi.checkElement(MASS_PATH): raise Exception("Missing required massBreakdown path.") elif not tixi.checkElement(DM_PATH): raise Exception("Missing required designMasses path.") elif not tixi.checkElement(MTOM_PATH): raise Exception("Missing required mTOM/mass path.") elif not tixi.checkElement(F_PATH): raise Exception("Missing required fuel/massDescription/mass path.") elif not tixi.checkElement(FMP_PATH): raise Exception("Missing required fuelMassMaxpass/mass path.") elif not tixi.checkElement(OEM_PATH): raise Exception("Missing required mOEM/massDescription/mass path.") elif not tixi.checkElement(PAY_PATH): raise Exception("Missing required payload/massDescription/mass path.") else: log.info("All path correctly defined in the toolinput.xml file, " + "beginning data extracction.") # Gathering data ========================================================= # TOOLSPECIFIC ---------------------------------------------------------- if not tixi.checkElement(RANGE_PATH + "/lDRatio"): tixi.createElement(RANGE_PATH, "lDRatio") tixi.updateDoubleElement(RANGE_PATH + "/lDRatio", ri.LD, "%g") else: temp = tixi.getIntegerElement(RANGE_PATH + "/lDRatio") if temp != ri.LD and temp > 0: ri.LD = temp if not tixi.checkElement(RANGE_PATH + "/cruiseSpeed"): tixi.createElement(RANGE_PATH, "cruiseSpeed") tixi.updateDoubleElement(RANGE_PATH + "/cruiseSpeed", ri.CRUISE_SPEED, "%g") else: temp = tixi.getIntegerElement(RANGE_PATH + "/cruiseSpeed") if temp != ri.CRUISE_SPEED and temp > 0: ri.CRUISE_SPEED = temp if not tixi.checkElement(RANGE_PATH + "/loiterTime"): tixi.createElement(RANGE_PATH, "loiterTime") tixi.updateDoubleElement(RANGE_PATH + "/loiterTime", ri.LOITER_TIME, "%g") else: temp = tixi.getIntegerElement(RANGE_PATH + "/loiterTime") if temp != ri.LOITER_TIME and temp > 0: ri.LOITER_TIME = temp if not tixi.checkElement(TSPEC_PATH + "/geometry/winglet"): tixi.createElement(TSPEC_PATH + "/geometry", "winglet") tixi.updateIntegerElement(TSPEC_PATH + "/geometry/winglet", ri.WINGLET, "%i") else: temp = tixi.getIntegerElement(TSPEC_PATH + "/geometry/winglet") if temp != ri.WINGLET: ri.WINGLET = temp if not tixi.checkElement(P_PATH + "/pilotNb"): tixi.createElement(P_PATH, "pilotNb") tixi.updateIntegerElement(P_PATH + "/pilotNb", ri.pilot_nb, "%i") else: temp = tixi.getIntegerElement(P_PATH + "/pilotNb") if temp != ri.pilot_nb and temp > 0: ri.pilot_nb = temp # Pilots user input data if not tixi.checkElement(P_PATH + "/pilotMass"): tixi.createElement(P_PATH, "pilotMass") tixi.updateDoubleElement(P_PATH + "/pilotMass", ri.MASS_PILOT, "%g") else: temp = tixi.getDoubleElement(P_PATH + "/pilotMass") if temp != ri.MASS_PILOT and temp > 0: ri.MASS_PILOT = temp # Cabin crew user input data if not tixi.checkElement(CC_PATH + "/cabinCrewMemberMass"): tixi.createElement(CC_PATH, "cabinCrewMemberMass") tixi.updateDoubleElement(CC_PATH + "/cabinCrewMemberMass", ri.MASS_CABIN_CREW, "%g") else: temp = tixi.getDoubleElement(CC_PATH + "/cabinCrewMemberMass") if temp != ri.MASS_CABIN_CREW and temp > 0: ri.MASS_CABIN_CREW = temp # Passengers input if not tixi.checkElement(PASS_PATH + "/passMass"): tixi.createElement(PASS_PATH, "passMass") tixi.updateDoubleElement(PASS_PATH + "/passMass", ri.MASS_PASS, "%g") else: temp = tixi.getDoubleElement(PASS_PATH + "/passMass") if temp != ri.MASS_PASS and temp > 0: ri.MASS_PASS = temp # Propulsion and Fuel if not tixi.checkElement(PROP_PATH + "/turboprop"): create_branch(tixi, PROP_PATH, False) tixi.createElement(PROP_PATH, "turboprop") if ri.TURBOPROP: tixi.updateTextElement(PROP_PATH + "/turboprop", "True") else: tixi.updateTextElement(PROP_PATH + "/turboprop", "False") else: temp = tixi.getTextElement(PROP_PATH + "/turboprop") if temp == "False": ri.TURBOPROP = False else: ri.TURBOPROP = True if not tixi.checkElement(F2_PATH + "/resFuelPerc"): tixi.createElement(F2_PATH, "resFuelPerc") tixi.updateDoubleElement(F2_PATH + "/resFuelPerc", ri.RES_FUEL_PERC, "%g") else: temp = tixi.getDoubleElement(F2_PATH + "/resFuelPerc") if temp != ri.RES_FUEL_PERC and temp > 0: ri.RES_FUEL_PERC = temp if not tixi.checkElement(TSFC_PATH + "/tsfcCruise"): tixi.createElement(TSFC_PATH, "tsfcCruise") tixi.updateDoubleElement(TSFC_PATH + "/tsfcCruise", ri.TSFC_CRUISE, "%g") else: temp = tixi.getDoubleElement(TSFC_PATH + "/tsfcCruise") if temp != ri.TSFC_CRUISE and temp > 0: ri.TSFC_CRUISE = temp if not tixi.checkElement(TSFC_PATH + "/tsfcLoiter"): tixi.createElement(TSFC_PATH, "tsfcLoiter") tixi.updateDoubleElement(TSFC_PATH + "/tsfcLoiter", ri.TSFC_LOITER, "%g") else: temp = tixi.getDoubleElement(TSFC_PATH + "/tsfcLoiter") if temp != ri.TSFC_LOITER and temp > 0: ri.TSFC_LOITER = temp # REQUIRED DATA ========================================================= # Cabin Crew ri.cabin_crew_nb = tixi.getIntegerElement(CC_PATH + "/cabinCrewMemberNb") # Fuel mw.mass_fuel_maxpass = tixi.getDoubleElement(FMP_PATH) # REQUIRED MASSBREAKDOWN DATA =========================================== mw.maximum_take_off_mass = tixi.getDoubleElement(MTOM_PATH) mw.operating_empty_mass = tixi.getDoubleElement(OEM_PATH) mw.mass_payload = tixi.getDoubleElement(PAY_PATH) mw.mass_fuel_max = tixi.getDoubleElement(F_PATH) log.info("Data from CPACS file succesfully extracted") # Saving and closing the cpacs file ====================================== tixi.save(cpacs_in) return (mw, ri)
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))
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)
def cpacs_update(mw, out, cpacs_path, cpacs_out_path): """ The function updates the cpacs file after the Weight analysis. Args: mw (class) : MassesWeights class out (class) : WeightOutput class cpacs_path (str) : Path to the CPACS file cpacs_out_path (str) : Path to the output CPACS file """ tixi = open_tixi( cpacs_out_path ) # (because it has been modifed somewre else, TODO: change that) # Path update if not tixi.checkElement(CREW_XPATH + "/cabinCrewMembers/cabinCrewMemberNb"): create_branch(tixi, CREW_XPATH + "/cabinCrewMembers/cabinCrewMemberNb") tixi.updateDoubleElement( CREW_XPATH + "/cabinCrewMembers/cabinCrewMemberNb", out.cabin_crew_nb, "%g" ) if not tixi.checkElement(PASS_XPATH + "/passNb"): tixi.createElement(PASS_XPATH, "passNb") tixi.updateIntegerElement(PASS_XPATH + "/passNb", out.pass_nb, "%i") if not tixi.checkElement(PASS_XPATH + "/rowNb"): tixi.createElement(PASS_XPATH, "rowNb") tixi.updateIntegerElement(PASS_XPATH + "/rowNb", out.row_nb, "%i") if not tixi.checkElement(PASS_XPATH + "/aisleNb"): tixi.createElement(PASS_XPATH, "aisleNb") tixi.updateIntegerElement(PASS_XPATH + "/aisleNb", out.aisle_nb, "%i") if not tixi.checkElement(PASS_XPATH + "/toiletNb"): tixi.createElement(PASS_XPATH, "toiletNb") tixi.updateIntegerElement(PASS_XPATH + "/toiletNb", out.toilet_nb, "%i") if not tixi.checkElement(PASS_XPATH + "/abreastNb"): tixi.createElement(PASS_XPATH, "abreastNb") tixi.updateIntegerElement(PASS_XPATH + "/abreastNb", out.abreast_nb, "%i") if not tixi.checkElement(PASS_XPATH + "/fuelMassMaxpass"): tixi.createElement(PASS_XPATH, "fuelMassMaxpass") FMP_XPATH = PASS_XPATH + "/fuelMassMaxpass" if not tixi.checkElement(FMP_XPATH + "/description"): tixi.createElement(FMP_XPATH, "description") tixi.updateTextElement( FMP_XPATH + "/description", "Maximum amount of " + "fuel with maximum payload [kg]" ) if not tixi.checkElement(FMP_XPATH + "/mass"): tixi.createElement(FMP_XPATH, "mass") tixi.updateDoubleElement(FMP_XPATH + "/mass", mw.mass_fuel_maxpass, "%g") # CPACS MASS BREAKDOWN UPDATE # Path creation if tixi.checkElement(MASSBREAKDOWN_XPATH): tixi.removeElement(MASSBREAKDOWN_XPATH) MD_XPATH = MASSBREAKDOWN_XPATH + "/designMasses" MTOM_XPATH = MD_XPATH + "/mTOM" MZFM_XPATH = MD_XPATH + "/mZFM" MF_XPATH = MASSBREAKDOWN_XPATH + "/fuel/massDescription" OEM_XPATH = MASSBREAKDOWN_XPATH + "/mOEM/massDescription" PAY_XPATH = MASSBREAKDOWN_XPATH + "/payload/massDescription" MC_XPATH = MASSBREAKDOWN_XPATH + "/payload/mCargo" OIM_XPATH = MASSBREAKDOWN_XPATH + "/mOEM/mOperatorItems/mCrewMembers/massDescription" create_branch(tixi, MTOM_XPATH + "/mass", False) create_branch(tixi, MZFM_XPATH + "/mass", False) create_branch(tixi, MF_XPATH + "/mass", False) create_branch(tixi, OEM_XPATH + "/mass", False) create_branch(tixi, PAY_XPATH + "/mass", False) create_branch(tixi, MC_XPATH, False) create_branch(tixi, OIM_XPATH + "/mass", False) # DESIGN MASSES add_uid(tixi, MTOM_XPATH, "MTOM") tixi.createElement(MTOM_XPATH, "name") tixi.updateTextElement(MTOM_XPATH + "/name", "Maximum take-off mass") tixi.createElement(MTOM_XPATH, "description") tixi.updateTextElement( MTOM_XPATH + "/description", "Maximum " + "take off mass [kg], CoG coordinate [m] and " + "moment of inertia.", ) tixi.updateDoubleElement(MTOM_XPATH + "/mass", mw.maximum_take_off_mass, "%g") # MZFM add_uid(tixi, MZFM_XPATH, "MZFM") tixi.createElement(MZFM_XPATH, "name") tixi.updateTextElement(MZFM_XPATH + "/name", "Maximum zero fuel mass") tixi.createElement(MZFM_XPATH, "description") tixi.updateTextElement( MZFM_XPATH + "/description", "Maximum " + "zero fuel mass [kg] and corresponding CoG " + "coordinate [m], moment of inertia.", ) tixi.updateDoubleElement(MZFM_XPATH + "/mass", mw.zero_fuel_mass, "%g") # FUEL MASS add_uid(tixi, MF_XPATH, "MFM") tixi.createElement(MF_XPATH, "name") tixi.updateTextElement(MF_XPATH + "/name", "Max fuel mass") tixi.createElement(MF_XPATH, "description") tixi.updateTextElement(MF_XPATH + "/description", "Maximum fuel mass [kg]") tixi.updateDoubleElement(MF_XPATH + "/mass", mw.mass_fuel_max, "%g") # OEM add_uid(tixi, OEM_XPATH, "OEM") tixi.createElement(OEM_XPATH, "name") tixi.updateTextElement(OEM_XPATH + "/name", "Operating empty mass") tixi.createElement(OEM_XPATH, "description") tixi.updateTextElement( OEM_XPATH + "/description", "Operating empty" + " mass [kg] and related inertia [kgm^2]." ) tixi.updateDoubleElement(OEM_XPATH + "/mass", mw.operating_empty_mass, "%g") tixi.updateDoubleElement(OIM_XPATH + "/mass", mw.mass_crew, "%g") add_uid(tixi, OIM_XPATH, "massCrew") # PAYLOAD MASS AND FUEL WITH MAX PAYLOAD add_uid(tixi, PAY_XPATH, "MPM") tixi.createElement(PAY_XPATH, "name") tixi.updateTextElement(PAY_XPATH + "/name", "Max payload mass") tixi.createElement(PAY_XPATH, "description") tixi.updateTextElement(PAY_XPATH + "/description", "Maximum " + "payload mass [kg].") tixi.updateDoubleElement(PAY_XPATH + "/mass", mw.mass_payload, "%g") if mw.mass_cargo: tixi.createElement(MC_XPATH, "massCargo") tixi.updateDoubleElement(MC_XPATH + "/massCargo", mw.mass_cargo, "%g") tixi.save(cpacs_out_path)
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)
def cpacs_mbd_update(out, mw, bi, ms_zpm, out_xml): """ The function updates the cpacs file after the Weight_and_Balance program. INPUT (float) mass_pass --Arg.: Passenger mass, countig also the extra mass. (class) out --Arg.: BalanceOutput class. (class) mw --Arg.: MassesWeights class. (class) bi --Arg.: BalanceInput class. ##======= Classes are defined in the InputClasses folder =======## (cahr) out_xml --Arg.: Path of the output file. OUTPUT (file) cpacs.xml --Out.: Updated cpacs file. """ tixi = open_tixi(out_xml) # CREATING PATH ========================================================== MB_PATH = "/cpacs/vehicles/aircraft/" + "model/analyses/massBreakdown" MD_PATH = MB_PATH + "/designMasses" MTOM_PATH = MD_PATH + "/mTOM" MZFM_PATH = MD_PATH + "/mZFM" OEM_PATH = MB_PATH + "/mOEM/massDescription" J_PATH = OEM_PATH + "/massInertia/J" CG_PATH = OEM_PATH + "/location/" create_branch(tixi, MTOM_PATH + "/location/x", False) create_branch(tixi, MTOM_PATH + "/location/y", False) create_branch(tixi, MTOM_PATH + "/location/z", False) create_branch(tixi, MTOM_PATH + "/massInertia/Jxx", False) create_branch(tixi, MTOM_PATH + "/massInertia/Jyy", False) create_branch(tixi, MTOM_PATH + "/massInertia/Jzz", False) create_branch(tixi, MTOM_PATH + "/massInertia/Jxy", False) create_branch(tixi, MTOM_PATH + "/massInertia/Jyz", False) create_branch(tixi, MTOM_PATH + "/massInertia/Jxz", False) create_branch(tixi, MZFM_PATH + "/location/x", False) create_branch(tixi, MZFM_PATH + "/location/y", False) create_branch(tixi, MZFM_PATH + "/location/z", False) create_branch(tixi, MZFM_PATH + "/massInertia/Jxx", False) create_branch(tixi, MZFM_PATH + "/massInertia/Jyy", False) create_branch(tixi, MZFM_PATH + "/massInertia/Jzz", False) create_branch(tixi, MZFM_PATH + "/massInertia/Jxy", False) create_branch(tixi, MZFM_PATH + "/massInertia/Jyz", False) create_branch(tixi, MZFM_PATH + "/massInertia/Jxz", False) create_branch(tixi, OEM_PATH + "/location/x", False) create_branch(tixi, OEM_PATH + "/location/y", False) create_branch(tixi, OEM_PATH + "/location/z", False) create_branch(tixi, OEM_PATH + "/massInertia/Jxx", False) create_branch(tixi, OEM_PATH + "/massInertia/Jyy", False) create_branch(tixi, OEM_PATH + "/massInertia/Jzz", False) create_branch(tixi, OEM_PATH + "/massInertia/Jxy", False) create_branch(tixi, OEM_PATH + "/massInertia/Jyz", False) create_branch(tixi, OEM_PATH + "/massInertia/Jxz", False) # DESIGN MASSES ========================================================== # MTOM ------------------------------------------------------------------- tixi.uIDSetToXPath(MTOM_PATH + "/location", "MTOMloc") tixi.updateDoubleElement(MTOM_PATH + "/location" + "/x", out.center_of_gravity[0], "%g") tixi.updateDoubleElement(MTOM_PATH + "/location" + "/y", out.center_of_gravity[1], "%g") tixi.updateDoubleElement(MTOM_PATH + "/location" + "/z", out.center_of_gravity[2], "%g") tixi.updateDoubleElement(MTOM_PATH + "/massInertia" + "/Jxx", out.Ixx_lump, "%g") tixi.updateDoubleElement(MTOM_PATH + "/massInertia" + "/Jyy", out.Iyy_lump, "%g") tixi.updateDoubleElement(MTOM_PATH + "/massInertia" + "/Jzz", out.Izz_lump, "%g") tixi.updateDoubleElement(MTOM_PATH + "/massInertia" + "/Jxy", out.Ixy_lump, "%g") tixi.updateDoubleElement(MTOM_PATH + "/massInertia" + "/Jyz", out.Iyz_lump, "%g") tixi.updateDoubleElement(MTOM_PATH + "/massInertia" + "/Jxz", out.Ixz_lump, "%g") # MZFM ------------------------------------------------------------------- add_uid(tixi, MZFM_PATH + "/location", "MZFMloc") # updating path tixi.updateDoubleElement(MZFM_PATH + "/location" + "/x", out.cg_zpm[0], "%g") tixi.updateDoubleElement(MZFM_PATH + "/location" + "/y", out.cg_zpm[1], "%g") tixi.updateDoubleElement(MZFM_PATH + "/location" + "/z", out.cg_zpm[2], "%g") tixi.updateDoubleElement(MZFM_PATH + "/massInertia" + "/Jxx", out.Ixx_lump_zfm, "%g") tixi.updateDoubleElement(MZFM_PATH + "/massInertia" + "/Jyy", out.Iyy_lump_zfm, "%g") tixi.updateDoubleElement(MZFM_PATH + "/massInertia" + "/Jzz", out.Izz_lump_zfm, "%g") tixi.updateDoubleElement(MZFM_PATH + "/massInertia" + "/Jxy", out.Ixy_lump_zfm, "%g") tixi.updateDoubleElement(MZFM_PATH + "/massInertia" + "/Jyz", out.Iyz_lump_zfm, "%g") tixi.updateDoubleElement(MZFM_PATH + "/massInertia" + "/Jxz", out.Ixz_lump_zfm, "%g") # OEM ==================================================================== add_uid(tixi, OEM_PATH + "/location", "OEMloc") tixi.updateDoubleElement((CG_PATH + "x"), out.cg_oem[0], "%g") tixi.updateDoubleElement((CG_PATH + "y"), out.cg_oem[1], "%g") tixi.updateDoubleElement((CG_PATH + "z"), out.cg_oem[2], "%g") tixi.updateDoubleElement((J_PATH + "xx"), out.Ixx_lump_oem, "%g") tixi.updateDoubleElement((J_PATH + "yy"), out.Iyy_lump_oem, "%g") tixi.updateDoubleElement((J_PATH + "zz"), out.Izz_lump_oem, "%g") tixi.updateDoubleElement((J_PATH + "xy"), out.Ixy_lump_oem, "%g") tixi.updateDoubleElement((J_PATH + "yz"), out.Iyz_lump_oem, "%g") tixi.updateDoubleElement((J_PATH + "xz"), out.Ixz_lump_oem, "%g") # ZPM INERTIA ============================================================ B_PATH = "/cpacs/toolspecific/CEASIOMpy/balance" ZPM_PATH = B_PATH + "/mZPM" create_branch(tixi, ZPM_PATH + "/name", False) tixi.updateTextElement(ZPM_PATH + "/name", "Maximum no payload mass") create_branch(tixi, ZPM_PATH + "/description", False) tixi.updateTextElement( ZPM_PATH + "/description", "Maximum " + "no payload mass [kg], CoG coordinate [m] and " + "moment of inertia.", ) create_branch(tixi, ZPM_PATH + "/mass", False) tixi.updateDoubleElement(ZPM_PATH + "/mass", ms_zpm, "%g") create_branch(tixi, ZPM_PATH + "/location/x", False) create_branch(tixi, ZPM_PATH + "/location/y", False) create_branch(tixi, ZPM_PATH + "/location/z", False) create_branch(tixi, ZPM_PATH + "/massInertia/Jxx", False) create_branch(tixi, ZPM_PATH + "/massInertia/Jyy", False) create_branch(tixi, ZPM_PATH + "/massInertia/Jzz", False) create_branch(tixi, ZPM_PATH + "/massInertia/Jxy", False) create_branch(tixi, ZPM_PATH + "/massInertia/Jyz", False) create_branch(tixi, ZPM_PATH + "/massInertia/Jxz", False) LOC_PATH = ZPM_PATH + "/location" MOI_PATH = ZPM_PATH + "/massInertia" add_uid(tixi, ZPM_PATH, "MZPM") add_uid(tixi, LOC_PATH, "MZPMloc") tixi.updateDoubleElement((LOC_PATH + "/x"), out.cg_zpm[0], "%g") tixi.updateDoubleElement((LOC_PATH + "/y"), out.cg_zpm[1], "%g") tixi.updateDoubleElement((LOC_PATH + "/z"), out.cg_zpm[2], "%g") tixi.updateDoubleElement((MOI_PATH + "/Jxx"), out.Ixx_lump_zpm, "%g") tixi.updateDoubleElement((MOI_PATH + "/Jyy"), out.Iyy_lump_zpm, "%g") tixi.updateDoubleElement((MOI_PATH + "/Jzz"), out.Izz_lump_zpm, "%g") tixi.updateDoubleElement((MOI_PATH + "/Jxy"), out.Ixy_lump_zpm, "%g") tixi.updateDoubleElement((MOI_PATH + "/Jyz"), out.Iyz_lump_zpm, "%g") tixi.updateDoubleElement((MOI_PATH + "/Jxz"), out.Ixz_lump_zpm, "%g") # USER CASE ============================================================== if bi.USER_CASE: UC_PATH = "/cpacs/toolspecific/CEASIOMpy/balance/userBalance" LOC_PATH = UC_PATH + "/location" MOI_PATH = UC_PATH + "/massInertia" create_branch(tixi, LOC_PATH + "/x", False) create_branch(tixi, LOC_PATH + "/y", False) create_branch(tixi, LOC_PATH + "/z", False) create_branch(tixi, MOI_PATH + "/Jxx", False) create_branch(tixi, MOI_PATH + "/Jyy", False) create_branch(tixi, MOI_PATH + "/Jzz", False) create_branch(tixi, MOI_PATH + "/Jxy", False) create_branch(tixi, MOI_PATH + "/Jyz", False) create_branch(tixi, MOI_PATH + "/Jxz", False) add_uid(tixi, LOC_PATH, "USERCase") tixi.updateDoubleElement((LOC_PATH + "/x"), out.cg_user[0], "%g") tixi.updateDoubleElement((LOC_PATH + "/y"), out.cg_user[1], "%g") tixi.updateDoubleElement((LOC_PATH + "/z"), out.cg_user[2], "%g") tixi.updateDoubleElement((MOI_PATH + "/Jxx"), out.Ixx_lump_user, "%g") tixi.updateDoubleElement((MOI_PATH + "/Jyy"), out.Iyy_lump_user, "%g") tixi.updateDoubleElement((MOI_PATH + "/Jzz"), out.Izz_lump_user, "%g") tixi.updateDoubleElement((MOI_PATH + "/Jxy"), out.Ixy_lump_user, "%g") tixi.updateDoubleElement((MOI_PATH + "/Jyz"), out.Iyz_lump_user, "%g") tixi.updateDoubleElement((MOI_PATH + "/Jxz"), out.Ixz_lump_user, "%g") # Saving and closing the new cpacs file inside the ToolOutput folder ----- tixi.save(out_xml) return ()
def get_data(mw, bi, cpacs_in): """ The function extracts from the XML file the required input data, the code will use the default value when they are missing. INPUT (class) mw --Arg.: MassesWeight class. (class) bi --Arg.: BalanceInput class. ##======= Classes are defined in the InputClasses folder =======## (char) cpacs_in --Arg.: Relative location of the xml file in the ToolInput folder (cpacs option) or relative location of the temp. xml file in the ToolOutput folder (input option). OUTPUT (class) mw --Out.: MassesWeight class updated. (class) bi --Out.: RangeInput class updated. (file) cpacs_in --Out.: Updated cpasc file. """ log.info("CPACS file path check") # path definition ======================================================== # Opening CPACS file tixi = open_tixi(cpacs_in) TSPEC_PATH = "/cpacs/toolspecific/CEASIOMpy" FMP_PATH = TSPEC_PATH + "/weight/passengers/fuelMassMaxpass/mass" PROP_PATH = TSPEC_PATH + "/propulsion" BC_PATH = TSPEC_PATH + "/balance/userBalance" create_branch(tixi, BC_PATH, False) MASS_PATH = "/cpacs/vehicles/aircraft/model/analyses/massBreakdown" MTOM_PATH = MASS_PATH + "/designMasses/mTOM/mass" F_PATH = MASS_PATH + "/fuel/massDescription/mass" OEM_PATH = MASS_PATH + "/mOEM/massDescription/mass" PAY_PATH = MASS_PATH + "/payload/massDescription/mass" # Compulsory path checks ================================================= if not tixi.checkElement(TSPEC_PATH): raise Exception("Missing required toolspecific path. Run " + "Weight_main.py, in the 1Weight_module folder.") elif not tixi.checkElement(MASS_PATH): raise Exception("Missing required massBreakdown path. Run " + "Weight_main.py, in the 1Weight_module folder.") elif not tixi.checkElement(MTOM_PATH): raise Exception("Missing required mTOM/mass path. Run " + "Weight_main.py, in the 1Weight_module folder.") elif not tixi.checkElement(FMP_PATH): raise Exception("Missing required fuelMassMaxpass/mass path. Run " + "Weight_main.py, in the 1Weight_module folder.") elif not tixi.checkElement(OEM_PATH): raise Exception("Missing required mOEM/massDescription/mass " + "path. Run " + "Weight_main.py, in the 1Weight_module folder.") elif not tixi.checkElement(PAY_PATH): raise Exception("Missing required payload/massDescription/mass " + "path. Run " + "Weight_main.py, in the 1Weight_module folder.") elif not tixi.checkElement(F_PATH): raise Exception("Missing required /fuel/massDescription/mass " + "path. Run " + "Weight_main.py, in the 1Weight_module folder.") else: log.info("All path correctly defined in the toolinput.xml file, " + "beginning data extracction.") # Gathering data ========================================================= # TOOLSPECIFIC ---------------------------------------------------------- if not tixi.checkElement(PROP_PATH + "/wingMountedEngine"): create_branch(tixi, PROP_PATH, False) tixi.createElement(PROP_PATH, "wingMountedEngine") if bi.WING_MOUNTED: tixi.updateTextElement(PROP_PATH + "/wingMountedEngine", "True") else: tixi.updateTextElement(PROP_PATH + "/wingMountedEngine", "False") else: temp = tixi.getTextElement(PROP_PATH + "/wingMountedEngine") if temp == "False": bi.WING_MOUNTED = False else: bi.WING_MOUNTED = True # User Case Balance if not tixi.checkElement(BC_PATH + "/userCase"): tixi.createElement(BC_PATH, "userCase") if bi.USER_CASE: tixi.updateTextElement(BC_PATH + "/userCase", "True") print("1") else: tixi.updateTextElement(BC_PATH + "/userCase", "False") print("2") else: temp = tixi.getTextElement(BC_PATH + "/userCase") if temp == "False": bi.USER_CASE = False else: bi.USER_CASE = True if bi.USER_CASE: if tixi.checkElement(BC_PATH + "/fuelPercentage"): bi.F_PERC = tixi.getDoubleElement(BC_PATH + "/fuelPercentage") elif bi.F_PERC: tixi.createElement(BC_PATH, "fuelPercentage") tixi.updateDoubleElement(BC_PATH + "/fuelPercentage", bi.F_PERC, "%g") else: raise Exception("User balance option defined" + " True but no fuel percentage data in the" + " CPACS file or in th BalanceInput class.") if tixi.checkElement(BC_PATH + "/payloadPercentage"): bi.P_PERC = tixi.getDoubleElement(BC_PATH + "/payloadPercentage") elif bi.P_PERC: tixi.createElement(BC_PATH, "payloadPercentage") tixi.updateDoubleElement(BC_PATH + "/payloadPercentage", bi.P_PERC, "%g") else: raise Exception("User balance option defined" + " True but no payload percentage data in" + " the CPACS file or in th BalanceInput class.") # REQUIRED TOOLSPECIFIC DATA ============================================ # Fuel mw.mass_fuel_maxpass = tixi.getDoubleElement(FMP_PATH) # REQUIRED MASSBREAKDOWN DATA =========================================== mw.maximum_take_off_mass = tixi.getDoubleElement(MTOM_PATH) mw.operating_empty_mass = tixi.getDoubleElement(OEM_PATH) mw.mass_payload = tixi.getDoubleElement(PAY_PATH) mw.mass_fuel_max = tixi.getDoubleElement(F_PATH) log.info("Data from CPACS file succesfully extracted") # Saving and closing the cpacs file ====================================== tixi.save(cpacs_in) return (mw, bi)
def cpacs_weight_update(out, mw, ui, cpacs_out_path): """ The function that update the cpacs file after the Weight_unc_main program. Args: out (class): Output class. mw (class): Mass and weight class. ui (class): UserInputs class. cpacs_out_path (str): Path of the output file. """ tixi = open_tixi(cpacs_out_path) # Path definition MB_PATH = "/cpacs/vehicles/aircraft/model/analyses/massBreakdown" if tixi.checkElement(MB_PATH): tixi.removeElement(MB_PATH) MD_PATH = MB_PATH + "/designMasses" MTOM_PATH = MD_PATH + "/mTOM" MZFM_PATH = MD_PATH + "/mZFM" MF_PATH = MB_PATH + "/fuel/massDescription" OEM_PATH = MB_PATH + "/mOEM/massDescription" PAY_PATH = MB_PATH + "/payload/massDescription" MC_PATH = MB_PATH + "/payload/mCargo" EM_PATH = MB_PATH + "/mOEM/mEM" OIM_PATH = MB_PATH + "/mOEM/mOperatorItems/mCrewMembers/massDescription" MSYS_PATH = EM_PATH + "/mSystems/massDescription/mass" MSTR_PATH = EM_PATH + "/mStructure/massDescription/mass" MEN_PATH = EM_PATH + "/mPowerUnits/massDescription/mass" create_branch(tixi, MTOM_PATH + "/mass", False) create_branch(tixi, MZFM_PATH + "/mass", False) create_branch(tixi, MF_PATH + "/mass", False) create_branch(tixi, OEM_PATH + "/mass", False) create_branch(tixi, PAY_PATH + "/mass", False) create_branch(tixi, MC_PATH, False) create_branch(tixi, OIM_PATH + "/mass", False) create_branch(tixi, EM_PATH, False) create_branch(tixi, MSYS_PATH, False) create_branch(tixi, MSTR_PATH, False) create_branch(tixi, MEN_PATH, False) # DESIGN MASSES add_uid(tixi, MTOM_PATH, "MTOM") tixi.createElement(MTOM_PATH, "name") tixi.updateTextElement(MTOM_PATH + "/name", "Maximum take-off mass") tixi.createElement(MTOM_PATH, "description") tixi.updateTextElement( MTOM_PATH + "/description", "Maximum " + "take off mass [kg], CoG coordinate [m] and " + "moment of inertia.", ) tixi.updateDoubleElement(MTOM_PATH + "/mass", mw.maximum_take_off_mass, "%g") # MZFM add_uid(tixi, MZFM_PATH, "MZFM") tixi.createElement(MZFM_PATH, "name") tixi.updateTextElement(MZFM_PATH + "/name", "Maximum zero fuel mass") tixi.createElement(MZFM_PATH, "description") tixi.updateTextElement( MZFM_PATH + "/description", "Maximum " + "zero fuel mass [kg] and corresponding CoG " + "coordinate [m], moment of inertia.", ) tixi.updateDoubleElement(MZFM_PATH + "/mass", mw.zero_fuel_mass, "%g") # FUEL MASS add_uid(tixi, MF_PATH, "MFM") tixi.createElement(MF_PATH, "name") tixi.updateTextElement(MF_PATH + "/name", "Max fuel mass") tixi.createElement(MF_PATH, "description") tixi.updateTextElement(MF_PATH + "/description", "Maximum fuel mass [kg].") tixi.updateDoubleElement(MF_PATH + "/mass", mw.mass_fuel_max, "%g") # OEM add_uid(tixi, OEM_PATH, "OEM") tixi.createElement(OEM_PATH, "name") tixi.updateTextElement(OEM_PATH + "/name", "Operating empty mass") tixi.createElement(OEM_PATH, "description") tixi.updateTextElement( OEM_PATH + "/description", "Operating empty" + " mass [kg] and related inertia [kgm^2].") tixi.updateDoubleElement(OEM_PATH + "/mass", mw.operating_empty_mass, "%g") tixi.updateDoubleElement(OIM_PATH + "/mass", mw.mass_crew, "%g") add_uid(tixi, OIM_PATH, "massCrew") tixi.updateDoubleElement(MSYS_PATH, mw.mass_systems, "%g") add_uid(tixi, EM_PATH + "/mSystems/massDescription", "mSys") tixi.updateDoubleElement(MSTR_PATH, mw.mass_structure, "%g") add_uid(tixi, EM_PATH + "/mStructure/massDescription", "mStrt") tixi.updateDoubleElement(MEN_PATH, mw.mass_engines, "%g") add_uid(tixi, EM_PATH + "/mPowerUnits/massDescription", "mEng") # PAYLOAD MASS AND FUEL CARGO MASS ======================================= add_uid(tixi, PAY_PATH, "MPM") tixi.createElement(PAY_PATH, "name") tixi.updateTextElement(PAY_PATH + "/name", "Max payload mass") tixi.createElement(PAY_PATH, "description") tixi.updateTextElement(PAY_PATH + "/description", "Maximum payload mass [kg].") tixi.updateDoubleElement(PAY_PATH + "/mass", mw.mass_payload, "%g") tixi.createElement(MC_PATH, "massCargo") tixi.updateDoubleElement(MC_PATH + "/massCargo", ui.MASS_CARGO, "%g") tixi.save(cpacs_out_path) return ()
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()
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)