def parse_xml_tree(self, root): # parse node if root.tag == "render_file": self.render_filename = root.attrib["filename"] if root.tag == "mass_property": self.mass = float(root.attrib["mass"]) self.inertia_tensor = self.convert_str_to_matrix( root.attrib["inertia_tensor"], 3, 3) if root.tag == "rotor": pos = self.convert_str_to_vector(root.attrib["position"], 3) dir = self.convert_str_to_vector(root.attrib["direction"], 3) clockwise = (root.attrib["clockwise"] == "1") torque_coef = float(root.attrib["torque_coef"]) rotor = Rotor(position_body=pos, direction_body=dir, clockwise=clockwise, torque_coef=torque_coef) self.rotors.append(rotor) if root.tag == "wing": area = float(root.attrib["area"]) dir = self.convert_str_to_vector(root.attrib["direction"], 3) angle0 = math.radians(float(root.attrib["angle0"])) wing = Wing(area=area, direction=dir, angle0=angle0) self.wing = wing # search sub-tree for child in root: self.parse_xml_tree(child)
def __init__(self): self.wing = Wing() # main geometry self.designGoals = DesignGoals() self.landingGear = LandingGear() self.vlm = VLMparameters() self.mass = AircraftMass() # total mass list self.drag = None # total drag list self.propulsion = Propulsion() # table lookup database self.aeroResults = None self._dragAero09 = True # if True then aero09 is used, otherwise Mason+Korn equation
def setup(self): self.wing = Wing() self.engine = Engine() self.fuselage = Fuselage() self.components = [self.wing, self.engine, self.fuselage] Wpay = Variable("W_{pay}", 700, "lbf", "payload") Wzfw = Variable("W_{zfw}", "lbf", "zero fuel weight") constraints = [Wzfw >= sum(summing_vars(self.components, "W")) + Wpay] return self.components, constraints
class FlyingWing(object): def __init__(self): self.wing = Wing() # main geometry self.designGoals = DesignGoals() self.landingGear = LandingGear() self.vlm = VLMparameters() self.mass = AircraftMass() # total mass list self.drag = None # total drag list self.propulsion = Propulsion() # table lookup database self.aeroResults = None self._dragAero09 = True # if True then aero09 is used, otherwise Mason+Korn equation def load_xls(self, name, xlsPath=None): """ Loads aircraft data from *.xls datasheet and calculates necessary parameters: - full geometry data set - weight and cg - parasite drag curve: CD0 vs Mach - engine thrust table if it does not exist for selected engine Parameters ---------- name : string name of aircraft in database xlsPath : path, optional database path """ self.name = str(name) if xlsPath == None: xlsPath = path.db.aircraftFW keyword = "SECTION: " db = ReadDatabase(xlsPath, name, keyword) idx = db.find_header(keyword + "DESIGN QUANTITIES") self.type = db.read_row(idx + 1, 1) self.designGoals.fuelMass = db.read_row(-1, 1) self.designGoals.avionicsMass = db.read_row(-1, 1) self.designGoals.grossMass = db.read_row(-1, 1) self.designGoals.cruiseMach = db.read_row(-1, 1) self.designGoals.cruiseAltitude = db.read_row(-1, 1) self.designGoals.loadFactor = db.read_row(-1, 1) self.designGoals.loadFactorLanding = db.read_row(-1, 1) self.designGoals.staticThrust = db.read_row(-1, 1) self.designGoals.numberOfOccupants = db.read_row(-1, 1) idx = db.find_header(keyword + "FUSELAGE") self.fusWidth = db.read_row(idx + 1, 1, False) idx = db.find_header(keyword + "MAIN WING") self.wing.segSpans = db.read_row(idx + 1, 1, True) self.wing.chords = db.read_row(-1, 1, True) self.wing.airfoilNames = db.read_row(-1, 1, True) self.wing.secOffset = db.read_row(-1, 1, True) self.wing.segDihedral = db.read_row(-1, 1, True) self.wing.secTwist = db.read_row(-1, 1, True) self.wing.incidence = db.read_row(-1, 1, False) elevonLocation = db.read_row(-1, 1, True) flapLocation = db.read_row(-1, 1, True) self.wing.set_elevon(elevonLocation) self.wing.set_flap(flapLocation) self.wing.flap.type = db.read_row(-1, 1, False) self.wing.material = db.read_row(-1, 1, False) self.wing.fuelTankCGratio = db.read_row(-1, 1, True) idx = db.find_header(keyword + "PROPULSION") engineName = db.read_row(idx + 1, 1, False) self.propulsion.load(engineName, True) self.propulsion.sfcModel = None self.propulsion.numberOfEngines = int(db.read_row(-1, 1, False)) self.propulsion.CGx = db.read_row(-1, 1, True) self.propulsion.CGy = db.read_row(-1, 1, True) self.propulsion.CGz = db.read_row(-1, 1, True) self.propulsion.engine.deignAltitude = self.designGoals.cruiseAltitude self.propulsion.engine.designMach = self.designGoals.cruiseMach self.propulsion.engine.designThrust = self.designGoals.staticThrust / self.propulsion.numberOfEngines idx = db.find_header(keyword + "LANDING GEAR") self.landingGear.groundContactX = db.read_row(idx + 1, 1, True) self.landingGear.groundContactY = db.read_row(-1, 1, True) self.landingGear.groundContactZ = db.read_row(-1, 1, True) self.landingGear.tireWidth = db.read_row(-1, 1, True) self.landingGear.tireDiameter = db.read_row(-1, 1, True) self.landingGear.strutLength = db.read_row(-1, 1, True) # self.landingGear._set_right_mlg() idx = db.find_header(keyword + "VLM PARAM") self.vlm.panelsChordwise = db.read_row(idx + 1, 1, False) self.vlm.panelsSpanwise = db.read_row(-1, 1, False) self.vlm.distribution = db.read_row(-1, 1, False) sec = db.read_section("PAYLOAD", 0) for line in sec: name = str(line[0]) mass = float(line[1]) CG = np.array([float(val) for val in line[2:5]]) MOI = np.array([float(val) for val in line[5:8]]) self.mass.payload.add_item(name, mass, CG, MOI) self.mass.update_total() self._process_data(True) # fuelCG = self.wing.locate_on_wing(self.wing.fuelTankCGratio[0],self.wing.fuelTankCGratio[1]) # fuelCG[1] = 0.0 # self.mass.set_fuel_mass(self.designGoals.fuelMass,fuelCG) self.designGoals._process_data() self._update_parasite_drag() self._update_mass() def _process_data(self, updateAirfoils=False): self.wing._process_data(updateAirfoils) self.designGoals.set_flight_conditions(self.wing.MAC) self.designGoals._process_data() self.propulsion._process_data() def save_xls(self): """ TBD. Saves configuration to *.xls datasheet """ pass def display(self, showAxes=False): """ Displays current aircraft configuration using mayavi. Notes ----- Method may produce an error depending on PC configuration. To fix the error follow these steps: 1. In tools->Preferences->Console->External modules set value of ETS_TOOLKIT to "wx" 2. In C:/Python27/Lib/site-packages/tvtk/pyface/light_manager.py, CameraLight class, _color_changed method, change: >>> self.source.color = val to >>> self.color = val """ flying_wing_display(self, showAxes) def display_2d(self, linetype="k-"): """ Displays 2D planform shape of the wing. Parameters ---------- linetype : string, optional matplotlib linetype format """ plt.figure() plt.plot(self.wing.x2d, self.wing.y2d, linetype) plt.axis("equal") plt.show() def _update_parasite_drag(self): alt = self.designGoals.cruiseAltitude if self._dragAero09: M, CD, Mdd, CDdd = get_parasite_drag_fw(self, alt) self._M = np.hstack([M[0] - 0.2, M[0] - 0.1, M]) + 0.1 self._CD = np.hstack([CD[0], CD[0], CD]) self.Mdd = Mdd self.CDdd = CDdd else: M, CD = get_transonic_drag_wing(self.wing, Ka=0.90) self._M = M self._CD = CD self._dragCurve = Akima1DInterpolator(self._M, self._CD) def plot_drag(self): m = np.linspace(self._M[0], self._M[-1], 100) cd = np.array([self.get_drag(_m) for _m in m]) plt.figure() plt.hold(True) # plt.plot(self._M, self._CD,'bs-') plt.plot(m, cd, "r-") plt.xlabel("Mach") plt.ylabel("Drag coefficient") plt.grid(True) plt.show() def _update_mass(self): self.mass = get_flying_wing_mass(self) # # def get_parasite_drag(self,velocity=None,altitude=None): # """ # Returns friction and form drag coefficient at given velocity and altitude. # If velocity and altitude are not specified then values from design targets # are used # # Parameters # ---------- # # velocity : float, m/sec # altitude : float, m # """ # self._update_parasite_drag() # return self.drag.get_total_drag() def update_aero_trim( self, velocity, altitude, CmTrim=0.0, loadFactor=1.0, mass=None, cg=None, inertia=None, CD0=None ): """ Updates results of aerodynamic analysis at trim condition using AVL solver. Stores the result of analysis in self.aeroResults. Parameters ---------- velocity : float, m/sec true airspeed of the aircraft. If velocity<5 then it is treated as Mach number, otherwise airspeed in m/sec. altitude : float, m density altitude at which analysis is performed CmTrim : float Required moment coefficient. By default CmTrim=0 - no pitching moment. loadFactor : float load factor can be used for gust analysis: analysis is performed with mass= mass*loadFactor mass : float, kg aircraft mass. If value is not defined then total aircraft mass will be calculated. cg : array, m center of gravity in format array([x,y,z]). If value is not specified then value will be calculated inertia : array, kg*m2 aircraft moment of inertia in format array([Ixx, Iyy, Izz]). Required for dynamic stability calculation. CD0 : float parasite drag coefficient. If value is not specified then value will be calculated. """ aero = Aerodynamics(self) fc = FlightConditionsAVL(self, velocity, altitude, CmTrim, loadFactor, mass, cg, inertia, CD0) self.aeroResults = aero.run_trim(fc) def get_aero_trim(self, velocity, altitude, CmTrim=0.0, loadFactor=1.0, mass=None, cg=None, inertia=None, CD0=None): """ Same as update_aero_trim, but returns results of analysis. """ self.update_aero_trim( velocity, altitude, CmTrim=0.0, loadFactor=1.0, mass=None, cg=None, inertia=None, CD0=None ) return self.aeroResults def get_aero_single_point( self, velocity, altitude, alpha=0.0, beta=0.0, elevator=0.0, mass=None, cg=None, inertia=None, CD0=None ): """ Performs aerodynamic analysis using AVL at given aircraft configuration and flight condtions. Parameters ---------- velocity : float, m/sec true airspeed of the aircraft. If velocity<5 then it is treated as Mach number, otherwise airspeed in m/sec. altitude : float, m density altitude at which analysis is performed alpha : float, deg aircraft angle of attack beta : float, deg aircraft sideslip angle elevator : float, deg elevator deflection. positive direction is down. mass : float, kg aircraft mass. If value is not defined then total aircraft mass will be calculated. cg : array, m center of gravity in format array([x,y,z]). If value is not specified then value will be calculated inertia : array, kg*m2 aircraft moment of inertia in format array([Ixx, Iyy, Izz]). Required for dynamic stability calculation. CD0 : float parasite drag coefficient. If value is not specified then value will be calculated. """ aero = Aerodynamics(self) fc = FlightConditionsAVL(self, velocity, altitude, 0, 1, mass, cg, inertia, CD0) alpha = float(alpha) beta = float(beta) elevator = float(elevator) self.aeroResults = aero.run_single_point(fc, alpha, beta, elevator) return self.aeroResults def get_cg(self, update=True): """ Returns center of gravity coordinates in format array([x,y,z]). Parameters ---------- update : bool runs weight and balance analysis first. Use True if configuration has been changed since last function call. """ if update: self._update_mass() return self.mass.total.CG def get_mass(self, update=True): """ Returns total mass (empty+payload) of current aircraft configuration. Parameters ---------- update : bool runs weight and balance analysis first. Use True if configuration has been changed since last function call. """ if update: self._update_mass() return self.mass.total.totalMass def get_mass_empty(self, update=True): """ Returns empty mass (airframe+propulsion) of current aircraft configuration. Parameters ---------- update : bool runs weight and balance analysis first. Use True if configuration has been changed since last function call. """ if update: self._update_mass() return self.mass.empty.totalMass def set_drag_method(self, option=1): """ Switches drag analysis methods. Parameters ---------- option : int if 1 - Aero09, else Friction+Korn """ if option == 1: self._dragAero09 = True else: self._dragAero09 = False self._update_parasite_drag() def get_drag(self, velocity=None, altitude=None): """ Returns parasite drag value of current aircraft configuration. Calculation is performed using in-house Aero09 code. Parameters ---------- velocity : float, m/sec true airspeed of the aircraft. If velocity<5 then it is treated as Mach number, otherwise airspeed in m/sec. altitude : float, m density altitude at which analysis is performed """ if velocity == None: velocity = self.designGoals.cruiseSpeed if altitude == None: altitude = self.designGoals.cruiseAltitude fc = FlightConditions(velocity, altitude) cd = self._dragCurve(fc.Mach) return cd def get_inertia(self): """ Returns moment of inertia. Now this analysis is not available, so function returns [1;1;1]. """ return np.zeros(3) # FIXME: should be replaced by real calculation def get_sfc(self, velocity, altitude, thrustRequired): """ Returns thrust specific fuel consumption (TSFC). Parameters ---------- velocity : float, m/sec true airspeed of the aircraft. If velocity<5 then it is treated as Mach number, otherwise airspeed in m/sec. altitude : float, m density altitude at which analysis is performed thrustRequired : float, N required thrust """ fc = FlightConditions(velocity, altitude, 0.0, self.wing.MAC) sfc = self.propulsion.get_sfc(fc.Mach, altitude, thrustRequired) return sfc
def parse_wing(self, node): wing = Wing(self.dirname) wing.units = self.units wing.airfoil_resample = self.airfoil_resample wing.circle_points=self.circle_points wing.name = node.getString('name') airfoil_root = node.getString('airfoil_root') airfoil_tip = node.getString('airfoil_tip') if airfoil_tip == "": airfoil_tip = None wing.load_airfoils( airfoil_root, airfoil_tip ) wing.span = myfloat(node, 'span') station_list = map( float, str(node.getString('stations')).split()) wing.set_stations( station_list ) wing.twist = myfloat(node, 'twist') sweep_curve = self.make_curve( node.getString('sweep_curve') ) if ( len(sweep_curve) >= 2 ): wing.set_sweep_curve( sweep_curve ) else: wing.set_sweep_angle( myfloat(node, 'sweep') ) chord_curve = self.make_curve( node.getString('chord_curve') ) if ( len(chord_curve) >= 2 ): wing.set_taper_curve( chord_curve ) else: chord_root = myfloat(node, 'chord_root') chord_tip = myfloat(node, 'chord_tip') wing.set_chord( chord_root, chord_tip ) wing.dihedral = myfloat(node, 'dihedral') wing.link_name = node.getString('wing_link') # parse flaps first so we can use this info to partition the # trailing edge and possibly other structures too for i in range(node.getLen('flap')): self.parse_flap(wing, node.getChild('flap[%d]' % i)) for i in range(node.getLen('leading_edge')): self.parse_leading_edge(wing, node.getChild('leading_edge[%d]' % i)) for i in range(node.getLen('trailing_edge')): self.parse_trailing_edge(wing, node.getChild('trailing_edge[%d]' % i)) for i in range(node.getLen('spar')): self.parse_spar(wing, node.getChild('spar[%d]' % i)) for i in range(node.getLen('stringer')): self.parse_stringer(wing, node.getChild('stringer[%d]' % i)) for i in range(node.getLen('sheet')): self.parse_sheet(wing, node.getChild('sheet[%d]' % i)) for i in range(node.getLen('simple_hole')): self.parse_simple_hole(wing, node.getChild('simple_hole[%d]' % i)) for i in range(node.getLen('shaped_hole')): self.parse_shaped_hole(wing, node.getChild('shaped_hole[%d]' % i)) for i in range(node.getLen('build_tab')): self.parse_build_tab(wing, node.getChild('build_tab[%d]' % i)) wing.build() wing.layout_parts_sheets( self.sheet_w, self.sheet_h, units=self.units, speed=self.nest_speed) #wing.layout_parts_templates( 8.5, 11 ) wing.layout_plans( self.plans_w, self.plans_h, units=self.units ) return wing
from wing_utils import dbg, setCtx, show setCtx(globals()) chord = 50 span = 200 incidenceAngle = 2 dihedral = 5 sweep = 0 tk = 0.26 # 0.25 prusa-slicer didn't print 1/4 of the wing shortCut = False wing = Wing.makeWing( airfoilSeq=naca5305, chord=chord, span=span, incidenceAngle=incidenceAngle, dihedral=dihedral, sweep=sweep, shellThickness=tk, shortCut=shortCut, ) # Flip the wing so it LE is on the plate. I did this because # the last print the cabin warpped by 8degrees. wing = wing.rotate((0, 0, 0), (1, 0, 0), 180).translate((0, 0, chord)) # show(wing, name="wing") # TODO: We fudge cabinLength so we are slightly past the trailing edge. # If we don't the trailing edge sticks ever so slightly past the cabin, why? cabinLength: float = chord * 1.001 cabinDiameter: float = 4.0 cabinYOffset: float = -1.0 cabinZOffset: float = 0
class FlyingWing(object): def __init__(self): self.wing = Wing() #main geometry self.designGoals = DesignGoals() self.landingGear = LandingGear() self.vlm = VLMparameters() self.mass = AircraftMass() #total mass list self.drag = None #total drag list self.propulsion = Propulsion() #table lookup database self.aeroResults = None self._dragAero09 = False # if True then aero09 is used, otherwise Mason+Korn equation def load_xls(self, name, xlsPath=None): """ Loads aircraft data from *.xls datasheet and calculates necessary parameters: - full geometry data set - weight and cg - parasite drag curve: CD0 vs Mach - engine thrust table if it does not exist for selected engine Parameters ---------- name : string name of aircraft in database xlsPath : path, optional database path """ self.name = str(name) if xlsPath==None: xlsPath = path.db.aircraftFW keyword = 'SECTION: ' db = ReadDatabase(xlsPath, name, keyword) idx = db.find_header(keyword+'DESIGN QUANTITIES') self.type = db.read_row(idx+1,1) self.designGoals.fuelMass = db.read_row(-1,1) self.designGoals.avionicsMass = db.read_row(-1,1) self.designGoals.grossMass = db.read_row(-1,1) self.designGoals.cruiseMach = db.read_row(-1,1) self.designGoals.cruiseAltitude = db.read_row(-1,1) self.designGoals.loadFactor = db.read_row(-1,1) self.designGoals.loadFactorLanding = db.read_row(-1,1) #self.designGoals.staticThrust = db.read_row(-1,1) #self.designGoals.numberOfOccupants = db.read_row(-1,1) missionProfile = db.read_row(-1,1) self.mission = mission_profile.load(missionProfile) idx = db.find_header(keyword+'FUSELAGE') self.fusWidth = db.read_row(idx+1,1,False) idx = db.find_header(keyword+'MAIN WING') self.wing.segSpans = db.read_row(idx+1,1,True) self.wing.chords = db.read_row(-1,1,True) self.wing.airfoilNames = db.read_row(-1,1,True) self.wing.secOffset = db.read_row(-1,1,True) self.wing.segDihedral = db.read_row(-1,1,True) self.wing.secTwist = db.read_row(-1,1,True) self.wing.incidence = db.read_row(-1,1,False) elevonLocation = db.read_row(-1,1,True) flapLocation = db.read_row(-1,1,True) self.wing.set_elevon(elevonLocation) self.wing.set_flap(flapLocation) self.wing.flap.type = db.read_row(-1,1,False) #FIXME: CLmax3D is temporal, later calcluation of CLmax should be added self.CLmax3D = db.read_row(-1,1,True) self.wing.material = db.read_row(-1,1,False) self.wing.fuelTankCGratio = db.read_row(-1,1,True) idx = db.find_header(keyword+'PROPULSION') engineName = db.read_row(idx+1,1,False) #FIXME: table/direct analysis should be controlled explicitly self.propulsion.load(engineName,False) self.propulsion.sfcModel = None self.propulsion.numberOfEngines = int(db.read_row(-1,1,False)) self.propulsion.CGx = db.read_row(-1,1,True) self.propulsion.CGy = db.read_row(-1,1,True) self.propulsion.CGz = db.read_row(-1,1,True) self.propulsion.engine.deignAltitude = self.designGoals.cruiseAltitude self.propulsion.engine.designMach = self.designGoals.cruiseMach self.propulsion.engine.designThrust = self.designGoals.staticThrust / self.propulsion.numberOfEngines idx = db.find_header(keyword+'LANDING GEAR') self.landingGear.groundContactX = db.read_row(idx+1,1,True) self.landingGear.groundContactY = db.read_row(-1,1,True) self.landingGear.groundContactZ = db.read_row(-1,1,True) self.landingGear.tireWidth = db.read_row(-1,1,True) self.landingGear.tireDiameter = db.read_row(-1,1,True) self.landingGear.strutLength = db.read_row(-1,1,True) #self.landingGear._set_right_mlg() idx = db.find_header(keyword+'VLM PARAM') self.vlm.panelsChordwise = db.read_row(idx+1,1,False) self.vlm.panelsSpanwise = db.read_row(-1,1,False) self.vlm.distribution = db.read_row(-1,1,False) sec = db.read_section('PAYLOAD',0) for line in sec: name = str(line[0]) mass = float(line[1]) CG = np.array([float(val) for val in line[2:5]]) MOI = np.array([float(val) for val in line[5:8]]) self.mass.payload.add_item(name,mass,CG,MOI) self.mass.update_total() self._process_data(True) # fuelCG = self.wing.locate_on_wing(self.wing.fuelTankCGratio[0],self.wing.fuelTankCGratio[1]) # fuelCG[1] = 0.0 # self.mass.set_fuel_mass(self.designGoals.fuelMass,fuelCG) #self.designGoals._process_data() self._update_parasite_drag() self._update_mass() def _process_data(self,updateAirfoils=False): self.wing._process_data(updateAirfoils) self.designGoals.set_flight_conditions(self.wing.MAC) self.designGoals._process_data() self.propulsion._process_data() def save_xls(self): """ TBD. Saves configuration to *.xls datasheet """ pass def display(self,showAxes=False): """ Displays current aircraft configuration using mayavi. Notes ----- Method may produce an error depending on PC configuration. To fix the error follow these steps: 1. In tools->Preferences->Console->External modules set value of ETS_TOOLKIT to "wx" 2. In C:/Python27/Lib/site-packages/tvtk/pyface/light_manager.py, CameraLight class, _color_changed method, change: >>> self.source.color = val to >>> self.color = val """ flying_wing_display(self,showAxes) def display_2d(self,linetype='k-'): """ Displays 2D planform shape of the wing. Parameters ---------- linetype : string, optional matplotlib linetype format """ plt.figure() plt.plot(self.wing.x2d, self.wing.y2d,linetype) plt.axis('equal') plt.show() def _update_parasite_drag(self): alt = self.designGoals.cruiseAltitude if self._dragAero09: M, CD, Mdd, CDdd = get_parasite_drag_fw(self,alt) self._M = np.hstack([M[0]-.2,M[0]-.1,M])+.1 self._CD = np.hstack([CD[0],CD[0],CD]) self.Mdd = Mdd self.CDdd = CDdd else: M, CD = get_transonic_drag_wing(self.wing,Ka=0.90) self._M = M self._CD = CD self._dragCurve = Akima1DInterpolator(self._M,self._CD) def plot_drag(self): m = np.linspace(self._M[0],self._M[-1],100) cd = np.array([self.get_drag(_m) for _m in m]) plt.figure() plt.hold(True) #plt.plot(self._M, self._CD,'bs-') plt.plot(m,cd,'r-') plt.xlabel('Mach') plt.ylabel('Drag coefficient') plt.grid(True) plt.show() def _update_mass(self): self.mass = get_flying_wing_mass(self) def update_aero_trim(self,velocity,altitude,CmTrim=0.0,loadFactor=1.0, mass=None,cg=None,inertia=None,CD0=None): """ Updates results of aerodynamic analysis at trim condition using AVL solver. Stores the result of analysis in self.aeroResults. Parameters ---------- velocity : float, m/sec true airspeed of the aircraft. If velocity<5 then it is treated as Mach number, otherwise airspeed in m/sec. altitude : float, m density altitude at which analysis is performed CmTrim : float Required moment coefficient. By default CmTrim=0 - no pitching moment. loadFactor : float load factor can be used for gust analysis: analysis is performed with mass= mass*loadFactor mass : float, kg aircraft mass. If value is not defined then total aircraft mass will be calculated. cg : array, m center of gravity in format array([x,y,z]). If value is not specified then value will be calculated inertia : array, kg*m2 aircraft moment of inertia in format array([Ixx, Iyy, Izz]). Required for dynamic stability calculation. CD0 : float parasite drag coefficient. If value is not specified then value will be calculated. """ aero = Aerodynamics(self) fc = FlightConditionsAVL(self,velocity,altitude,CmTrim,loadFactor,mass, cg,inertia,CD0) self.aeroResults = aero.run_trim(fc) def get_aero_trim(self,velocity,altitude,CmTrim=0.0,loadFactor=1.0, mass=None,cg=None,inertia=None,CD0=None): """ Same as update_aero_trim, but returns results of analysis. """ self.update_aero_trim(velocity,altitude,CmTrim=0.0,loadFactor=1.0, mass=None,cg=None,inertia=None,CD0=None) return self.aeroResults def get_aero_single_point(self,velocity=None,altitude=None,alpha=0.0,beta=0.0, elevator=0.0,mass=None,cg=None,inertia=None,CD0=None): """ Performs aerodynamic analysis using AVL at given aircraft configuration and flight condtions. Parameters ---------- velocity : float, m/sec true airspeed of the aircraft. If velocity<5 then it is treated as Mach number, otherwise airspeed in m/sec. altitude : float, m density altitude at which analysis is performed alpha : float, deg aircraft angle of attack beta : float, deg aircraft sideslip angle elevator : float, deg elevator deflection. positive direction is down. mass : float, kg aircraft mass. If value is not defined then total aircraft mass will be calculated. cg : array, m center of gravity in format array([x,y,z]). If value is not specified then value will be calculated inertia : array, kg*m2 aircraft moment of inertia in format array([Ixx, Iyy, Izz]). Required for dynamic stability calculation. CD0 : float parasite drag coefficient. If value is not specified then value will be calculated. """ if velocity==None: velocity = self.designGoals.cruiseSpeed if altitude==None: altitude = self.designGoals.cruiseAltitude aero = Aerodynamics(self) fc = FlightConditionsAVL(self,velocity,altitude,0,1,mass,cg,inertia,CD0) alpha = float(alpha) beta = float(beta) elevator = float(elevator) self.aeroResults = aero.run_single_point(fc,alpha,beta,elevator) return self.aeroResults def get_cg(self,update=True): """ Returns center of gravity coordinates in format array([x,y,z]). Parameters ---------- update : bool runs weight and balance analysis first. Use True if configuration has been changed since last function call. """ if update: self._update_mass() return self.mass.total.CG def get_mass(self,update=True): """ Returns total mass (empty+payload) of current aircraft configuration. Parameters ---------- update : bool runs weight and balance analysis first. Use True if configuration has been changed since last function call. """ if update: self._update_mass() return self.mass.total.totalMass def get_mass_empty(self,update=True): """ Returns empty mass (airframe+propulsion) of current aircraft configuration. Parameters ---------- update : bool runs weight and balance analysis first. Use True if configuration has been changed since last function call. """ if update: self._update_mass() return self.mass.empty.totalMass def get_mass_fuel(self,update=True): """ Returns empty mass (airframe+propulsion) of current aircraft configuration. Parameters ---------- update : bool runs weight and balance analysis first. Use True if configuration has been changed since last function call. """ if update: self._update_mass() return self.mass.fuel.mass # def get_mass_fuel(self,update=True): # """ # Returns fuel mass at given state. # # Parameters # ---------- # # update : bool # runs weight and balance analysis first. Use True if configuration has been changed since # last function call. # """ # if update: # self._update_mass() # return self.mass.fuel.mass def set_drag_method(self,option=1): """ Switches drag analysis methods. Parameters ---------- option : int if 1 - Aero09, else Friction+Korn """ if option==1: self._dragAero09 = True else: self._dragAero09 = False self._update_parasite_drag() def get_drag(self,velocity=None,altitude=None): """ Returns parasite drag value of current aircraft configuration. Calculation is performed using in-house Aero09 code. Parameters ---------- velocity : float, m/sec true airspeed of the aircraft. If velocity<5 then it is treated as Mach number, otherwise airspeed in m/sec. altitude : float, m density altitude at which analysis is performed """ if velocity==None: velocity = self.designGoals.cruiseSpeed if altitude==None: altitude = self.designGoals.cruiseAltitude fc = FlightConditions(velocity,altitude) cd = self._dragCurve(fc.Mach) return cd*1.10 def get_inertia(self): """ Returns moment of inertia. Now this analysis is not available, so function returns [1;1;1]. """ return np.zeros(3) #FIXME: should be replaced by real calculation def get_sfc(self,velocity,altitude,thrustRequired=None): """ Returns thrust specific fuel consumption (TSFC). Parameters ---------- velocity : float, m/sec true airspeed of the aircraft. If velocity<5 then it is treated as Mach number, otherwise airspeed in m/sec. altitude : float, m density altitude at which analysis is performed thrustRequired : float, N required thrust """ if thrustRequired==None: thrustRequired = self.propulsion.get_thrust_available(altitude) fc = FlightConditions(velocity,altitude,0.0,self.wing.MAC) sfc = self.propulsion.get_sfc(fc.Mach,altitude,thrustRequired) return sfc def get_fuel_flow(self,velocity,altitude,thrustRequired=None): atm = FlightConditions(velocity,altitude) if thrustRequired==None: thrustRequired = self.propulsion.get_thrust_available(altitude) ff = self.propulsion.get_fuel_flow(atm.Mach,altitude,thrustRequired) return ff def get_mission_fuel(self,mission=None,iterMax=20,tol=1.0,display=False): if not self.mission.calculated: wfuel,mis = mission2.get_mission_fuel(self,mission, iterMax, tol, display) self.designGoals.fuelMass = wfuel self._update_mass() self.mission = mis self.mission.calculated = True return wfuel else: return self.designGoals.fuelMass def set_fuel_fraction(self,fuelMassFraction=1.0): fuelMassFraction = min([1.0,fuelMassFraction]) fuelMassFraction = max([0.0,fuelMassFraction]) self.mass.fuel.set_fuel_fraction(fuelMassFraction) self.mass.update_total() def get_CLmax3D(self,deflection, altitude=0.0, velocity=0.1): """ Calculation of 3D CLmax coefficient based on Gudmunsson's GA design book p441. 2D section analysis is performed here using Xfoil and javafoil for plain flap only. The result need to be validated. Parameters ---------- deflection : float, deg plain flap deflection angles altitude : float, m field altitude where analysis is performed. Typically should be SL velocity : float, m/sec or Mach airspeed close to takeoff/landing speed """ fc = FlightConditions(velocity,altitude) h = altitude V = velocity isFlapped = self.wing.isFlapped CLmaxFl = list() alphaFl = list() CL = np.zeros(len(isFlapped)) for i,val in enumerate(isFlapped): if val==1: Cf = 1-self.wing.flap.location[i] _c, _a = self.wing.airfoils[i].get_clmax(V,h,Cf,deflection) CLmaxFl.append(_c) alphaFl.append(_a) CL[i] = _c CLmaxFl = np.mean(CLmaxFl) alphaFl = np.mean(alphaFl) #--- for i,val in enumerate(isFlapped): if val==0: pol = self.wing.airfoils[i].get_xfoil_polar(fc.Mach,fc.Re,[-20,20,0.5]) CL[i] = pol._alphaCl(alphaFl) #--- Sref = self.wing.area Si = self.wing.segAreas sum1 = np.sum( isFlapped*CL*Si ) sum2 = np.sum( (1.0-isFlapped)*CL*Si ) CLmax = 0.9/Sref* (sum1 + sum2) return 2.0*CLmax
def OnWing(self, e): o = Wing() wings.append(o) cad.AddUndoably(o, None, None)
def __init__(self): CL = Variable('C_L', '-', 'Lift coefficient') CLmax = Variable('C_{L_{max}}', '-', 'Max lift coefficient') CD = Variable('C_D', '-', 'Drag coefficient') D = Variable('D', 'N', 'Total aircraft drag (cruise)') Dfuse = Variable('D_{fuse}', 'N', 'Fuselage drag') Dht = Variable('D_{ht}', 'N', 'Horizontal tail drag') Dvt = Variable('D_{vt}', 'N', 'Vertical tail drag') Dwing = Variable('D_{wing}', 'N', 'Wing drag') LD = Variable('\\frac{L}{D}', '-', 'Lift/drag ratio') Lh = Variable('L_h', 'N', 'Horizontal tail downforce') Lw = Variable('L_w', 'N', 'Wing lift') M = Variable('M', '-', 'Cruise Mach number') R = Variable('Range', 'nautical_miles', 'Range') Sw = Variable('S_w', 'm**2', 'Wing reference area') Te = Variable('T_e', 'N', 'Engine thrust at takeoff') TSFC = Variable('c_T', 'lb/lbf/hr', 'Thrust specific fuel consumption') V = Variable('V_{\\infty}', 'm/s', 'Cruise velocity') VTO = Variable('V_{TO}', 'm/s', 'Takeoff speed') W = Variable('W', 'N', 'Total aircraft weight') Weng = Variable('W_{eng}', 'N', 'Engine weight') Wfuel = Variable('W_{fuel}', 'N', 'Fuel weight') Wfuse = Variable('W_{fuse}', 'N', 'Fuselage weight') Wht = Variable('W_{ht}', 'N', 'Horizontal tail weight') Wlg = Variable('W_{lg}', 'N', 'Landing gear weight') Wpay = Variable('W_{pay}', 'N', 'Payload weight') Wvt = Variable('W_{vt}', 'N', 'Vertical tail weight') Wwing = Variable('W_{wing}', 'N', 'Wing weight') Wzf = Variable('W_{zf}', 'N', 'Zero fuel weight') a = Variable('a', 'm/s', 'Speed of sound (35,000 ft)') g = Variable('g', 9.81, 'm/s^2', 'Gravitational acceleration') lr = Variable('l_r', 5000, 'ft', 'Runway length') rho = Variable('\\rho', 'kg/m^3', 'Air density (cruise)') rho0 = Variable('\\rho_0', 'kg/m^3', 'Air density (sea level)') xCG = Variable('x_{CG}', 'm', 'x-location of CG') xCGeng = Variable('x_{CG_{eng}}', 'm', 'x-location of engine CG') xCGfu = Variable('x_{CG_{fu}}', 'm', 'x-location of fuselage CG') xCGht = Variable('x_{CG_{ht}}', 'm', 'x-location of htail CG') xCGlg = Variable('x_{CG_{lg}}', 'm', 'x-location of landing gear CG') xCGvt = Variable('x_{CG_{vt}}', 'm', 'x-location of vtail CG') xCGwing = Variable('x_{CG_{wing}}', 'm', 'x-location of wing CG') xTO = Variable('x_{TO}', 'm', 'Takeoff distance') xi = Variable('\\xi', '-', 'Takeoff parameter') xw = Variable('x_w', 'm', 'x-location of wing aerodynamic center') y = Variable('y', '-', 'Takeoff parameter') z_bre = Variable('z_{bre}', '-', 'Breguet parameter') with SignomialsEnabled(): objective = Wfuel # High level constraints hlc = [ # Drag and weight buildup D >= Dvt + Dfuse + Dwing + Dht, Wzf >= Wvt + Wfuse + Wlg + Wwing + Wht + Weng + Wpay, W >= Wzf + Wfuel, # Range equation for a jet V == M*a, D == 0.5*rho*V**2*Sw*CD, W == 0.5*rho*V**2*Sw*CL, LD == CL/CD, Lw >= W, # TODO: add Lh R <= z_bre*LD*V/(TSFC*g), Wfuel/Wzf >= te_exp_minus1(z_bre, nterm=3), # CG relationships TCS([xCG*W >= Wvt*xCGvt + Wfuse*xCGfu + Wlg*xCGlg + Wwing*xCGwing + Wht*xCGht + Weng*xCGeng + Wfuel*xCGwing + Wpay*xCGfu], reltol=1E-2, raiseerror=False), xw == xCGwing, xCGeng == xCGwing, #Takeoff relationships xi >= 0.5*rho0*VTO**2*Sw*CD/Te, 4*g*xTO*Te/(W*VTO**2) >= 1 + y, 1 >= 0.0464*xi**2.73/y**2.88 + 1.044*xi**0.296/y**0.049, VTO == 1.2*(2*W/(rho0*Sw*CLmax))**0.5, xTO <= lr, ] # Subsystem models vt = VerticalTail.coupled737() fu = Fuselage.coupled737() lg = LandingGear.coupled737() ht = HorizontalTail.coupled737() wi = Wing.coupled737() wb = WingBox() substitutions = { 'C_{L_{max}}': 2.5, 'M': 0.78, 'Range': 3000, 'c_T': 0.3, 'W_{eng}': 10000, 'a': 297, } lc = LinkedConstraintSet([hlc, vt, fu, lg, ht, wi], exclude=[vk.name for vk in wb.varkeys if not vk.value]) Model.__init__(self, objective, lc, substitutions)