aerosandbox.geometry ==================== .. py:module:: aerosandbox.geometry Submodules ---------- .. toctree:: :maxdepth: 1 /autoapi/aerosandbox/geometry/airfoil/index /autoapi/aerosandbox/geometry/airplane/index /autoapi/aerosandbox/geometry/common/index /autoapi/aerosandbox/geometry/fuselage/index /autoapi/aerosandbox/geometry/mesh_utilities/index /autoapi/aerosandbox/geometry/nosecone_shapes/index /autoapi/aerosandbox/geometry/openvsp_io/index /autoapi/aerosandbox/geometry/polygon/index /autoapi/aerosandbox/geometry/propulsor/index /autoapi/aerosandbox/geometry/wing/index Classes ------- .. autoapisummary:: aerosandbox.geometry.Airfoil aerosandbox.geometry.KulfanAirfoil aerosandbox.geometry.Wing aerosandbox.geometry.WingXSec aerosandbox.geometry.ControlSurface aerosandbox.geometry.Fuselage aerosandbox.geometry.FuselageXSec aerosandbox.geometry.Airplane aerosandbox.geometry.Propulsor Functions --------- .. autoapisummary:: aerosandbox.geometry.is_casadi_type aerosandbox.geometry.reflect_over_XZ_plane Package Contents ---------------- .. py:function:: is_casadi_type(object, recursive = True) Returns a boolean of whether an object is a CasADi data type or not. If the recursive flag is True, iterates recursively, returning True if any subelement (at any depth) is a CasADi type. :param object: The object to evaluate. :param recursive: If the object is a list or tuple, recursively iterate through every subelement. If any of the :param subelements: :type subelements: at any depth Returns: A boolean if the object is (or contains, if recursive=True) a CasADi data type. .. py:function:: reflect_over_XZ_plane(input_vector) Takes in a vector or an array and flips the y-coordinates. :param input_vector: A vector or list of vectors to flip. :return: Vector with flipped sign on y-coordinate. .. py:class:: Airfoil(name = 'Untitled', coordinates = None, **deprecated_keyword_arguments) Bases: :py:obj:`aerosandbox.geometry.polygon.Polygon` An airfoil. See constructor docstring for usage details. .. py:attribute:: name :value: 'Untitled' .. py:attribute:: coordinates :value: None .. py:method:: __repr__() .. py:method:: to_kulfan_airfoil(n_weights_per_side = 8, N1 = 0.5, N2 = 1.0, normalize_coordinates = True, use_leading_edge_modification = True) .. py:method:: generate_polars(alphas=np.linspace(-13, 13, 27), Res=np.geomspace(1000.0, 100000000.0, 12), cache_filename = None, xfoil_kwargs = None, unstructured_interpolated_model_kwargs = None, include_compressibility_effects = True, transonic_buffet_lift_knockdown = 0.3, make_symmetric_polars = False) Generates airfoil polar surrogate models (CL, CD, CM functions) from XFoil data and assigns them in-place to this Airfoil's polar functions. In other words, when this function is run, the following functions will be added (or overwritten) to the instance: * Airfoil.CL_function(alpha, Re, mach) * Airfoil.CD_function(alpha, Re, mach) * Airfoil.CM_function(alpha, Re, mach) Where alpha is in degrees. Warning: In-place operation! Modifies this Airfoil object by setting Airfoil.CL_function, etc. to the new polars. :param alphas: The range of alphas to sample from XFoil at. Given in degrees. :param Res: The range of Reynolds numbers to sample from XFoil at. Dimensionless. :param cache_filename: A path-like filename (ideally a "*.json" file) that can be used to cache the XFoil results, making it much faster to regenerate the results. * If the file does not exist, XFoil will be run, and a cache file will be created. * If the file does exist, XFoil will not be run, and the cache file will be read instead. :param xfoil_kwargs: Keyword arguments to pass into the AeroSandbox XFoil module. See the aerosandbox.XFoil constructor for options. :param unstructured_interpolated_model_kwargs: Keyword arguments to pass into the UnstructuredInterpolatedModels that contain the polars themselves. See the aerosandbox.UnstructuredInterpolatedModel constructor for options. :param include_compressibility_effects: Includes compressibility effects in the polars, such as wave drag, mach tuck, CL effects across normal shocks. Note that accuracy here is dubious in the transonic regime and above - you should really specify your own CL/CD/CM models Returns: None (in-place), adds the following functions to the instance: * Airfoil.CL_function(alpha, Re, mach) * Airfoil.CD_function(alpha, Re, mach) * Airfoil.CM_function(alpha, Re, mach) .. py:method:: get_aero_from_neuralfoil(alpha, Re, mach = 0.0, n_crit = 9.0, xtr_upper = 1.0, xtr_lower = 1.0, model_size = 'large', control_surfaces = None, include_360_deg_effects = True) .. py:method:: plot_polars(alphas = np.linspace(-20, 20, 500), Res = 10**np.arange(3, 9), mach = 0.0, show = True, Re_colors=None) .. py:method:: local_camber(x_over_c = np.linspace(0, 1, 101)) Returns the local camber of the airfoil at a given point or points. :param x_over_c: The x/c locations to calculate the camber at [1D array, more generally, an iterable of floats] :returns: Local camber of the airfoil (y/c) [1D array]. .. py:method:: local_thickness(x_over_c = np.linspace(0, 1, 101)) Returns the local thickness of the airfoil at a given point or points. :param x_over_c: The x/c locations to calculate the thickness at [1D array, more generally, an iterable of floats] :returns: Local thickness of the airfoil (y/c) [1D array]. .. py:method:: max_camber(x_over_c_sample = np.linspace(0, 1, 101)) Returns the maximum camber of the airfoil. :param x_over_c_sample: Where should the airfoil be sampled to determine the max camber? Returns: The maximum thickness, as a fraction of chord. .. py:method:: max_thickness(x_over_c_sample = np.linspace(0, 1, 101)) Returns the maximum thickness of the airfoil. :param x_over_c_sample: Where should the airfoil be sampled to determine the max thickness? Returns: The maximum thickness, as a fraction of chord. .. py:method:: draw(draw_mcl=False, draw_markers=True, backend='matplotlib', show=True) Draw the airfoil object. :param draw_mcl: Should we draw the mean camber line (MCL)? [boolean] :param backend: Which backend should we use? "plotly" or "matplotlib" :param show: Should we show the plot? [boolean] Returns: None .. py:method:: LE_index() Returns the index of the leading edge point in the airfoil coordinates. .. py:method:: lower_coordinates() Returns an Nx2 ndarray of [x, y] coordinates that describe the lower surface of the airfoil. Order is from the leading edge to the trailing edge. Includes the leading edge point; be careful about duplicates if using this method in conjunction with Airfoil.upper_coordinates(). .. py:method:: upper_coordinates() Returns an Nx2 ndarray of [x, y] coordinates that describe the upper surface of the airfoil. Order is from the trailing edge to the leading edge. Includes the leading edge point; be careful about duplicates if using this method in conjunction with Airfoil.lower_coordinates(). .. py:method:: LE_radius(softness = 1e-06) .. py:method:: TE_thickness() Returns the thickness of the trailing edge of the airfoil. .. py:method:: TE_angle() Returns the trailing edge angle of the airfoil, in degrees. .. py:method:: repanel(n_points_per_side = 100, spacing_function_per_side=np.cosspace) Returns a repaneled copy of the airfoil with cosine-spaced coordinates on the upper and lower surfaces. :param n_points_per_side: Number of points per side (upper and lower) of the airfoil [int] Notes: The number of points defining the final airfoil will be `n_points_per_side * 2 - 1`, since one point (the leading edge point) is shared by both the upper and lower surfaces. :param spacing_function_per_side: Determines how to space the points on each side of the airfoil. Can be `np.linspace` or `np.cosspace`, or any other function of the call signature `f(a, b, n)` that returns a spaced array of `n` points between `a` and `b`. [function] Returns: A copy of the airfoil with the new coordinates. .. py:method:: normalize(return_dict = False) Returns a copy of the Airfoil with a new set of `coordinates`, such that: - The leading edge (LE) is at (0, 0) - The trailing edge (TE) is at (1, 0) - The chord length is equal to 1 The trailing-edge (TE) point is defined as the midpoint of the line segment connecting the first and last coordinate points (upper and lower surface TE points, respectively). The TE point is not necessarily one of the original points in the airfoil coordinates (`Airfoil.coordinates`); in general, it will not be one of the points if the TE thickness is nonzero. The leading-edge (LE) point is defined as the coordinate point with the largest Euclidian distance from the trailing edge. (In other words, if you were to center a circle on the trailing edge and progressively grow it, what's the last coordinate point that it would intersect?) The LE point is always one of the original points in the airfoil coordinates. The chord is defined as the Euclidian distance between the LE and TE points. Coordinate modifications to achieve the constraints described above (LE @ origin, TE at (1, 0), and chord of 1) are done by means of a translation and rotation. :param return_dict: Determines the output type of the function. - If `False` (default), returns a copy of the Airfoil with the new coordinates. - If `True`, returns a dictionary with keys: - "airfoil": a copy of the Airfoil with the new coordinates - "x_translation": the amount by which the airfoil's LE was translated in the x-direction - "y_translation": the amount by which the airfoil's LE was translated in the y-direction - "scale_factor": the amount by which the airfoil was scaled (if >1, the airfoil had to get bigger) - "rotation_angle": the angle (in degrees) by which the airfoil was rotated about the LE. Sign convention is that positive angles rotate the airfoil counter-clockwise. All of thes values represent the "required change", e.g.: - "x_translation" is the amount by which the airfoil's LE had to be translated in the x-direction to get it to the origin. - "rotation_angle" is the angle (in degrees) by which the airfoil had to be rotated (CCW). Returns: Depending on the value of `return_dict`, either: - A copy of the airfoil with the new coordinates (default), or - A dictionary with keys "airfoil", "x_translation", "y_translation", "scale_factor", and "rotation_angle". documentation for `return_tuple` for more information. .. py:method:: add_control_surface(deflection = 0.0, hinge_point_x = 0.75, modify_coordinates = True, modify_polars = True) Returns a version of the airfoil with a trailing-edge control surface added at a given point. Implicitly repanels the airfoil as part of this operation. :param deflection: Deflection angle [degrees]. Downwards-positive. :param hinge_point_x: Chordwise location of the hinge, as a fraction of chord (x/c) [float] Returns: an Airfoil object with the new control deflection. .. py:method:: set_TE_thickness(thickness = 0.0) Creates a modified copy of the Airfoil that has a specified trailing-edge thickness. Note that the trailing-edge thickness is given nondimensionally (e.g., as a fraction of chord). :param thickness: The target trailing-edge thickness, given nondimensionally (e.g., as a fraction of chord). Returns: The modified airfoil. .. py:method:: scale(scale_x = 1.0, scale_y = 1.0) Scales an Airfoil about the origin. :param scale_x: Amount to scale in the x-direction. :param scale_y: Amount to scale in the y-direction. Scaling by a negative y-value will result in coordinates being re-ordered such that the order of the coordinates is still correct (i.e., starts from the upper-surface trailing edge, continues along the upper surface to the nose, then continues along the lower surface to the trailing edge). Returns: A copy of the Airfoil with appropriate scaling applied. .. py:method:: translate(translate_x = 0.0, translate_y = 0.0) Translates an Airfoil by a given amount. :param translate_x: Amount to translate in the x-direction :param translate_y: Amount to translate in the y-direction Returns: The translated Airfoil. .. py:method:: rotate(angle, x_center = 0.0, y_center = 0.0) Rotates the airfoil clockwise by the specified amount, in radians. Rotates about the point (x_center, y_center), which is (0, 0) by default. :param angle: Angle to rotate, counterclockwise, in radians. :param x_center: The x-coordinate of the center of rotation. :param y_center: The y-coordinate of the center of rotation. Returns: The rotated Airfoil. .. py:method:: blend_with_another_airfoil(airfoil, blend_fraction = 0.5, n_points_per_side = 100) Blends this airfoil with another airfoil. Merges both the coordinates and the aerodynamic functions. :param airfoil: The other airfoil to blend with. :param blend_fraction: The fraction of the other airfoil to use when blending. Defaults to 0.5 (50%). * A blend fraction of 0 will return an identical airfoil to this one (self). * A blend fraction of 1 will return an identical airfoil to the other one (`airfoil` parameter). :param n_points_per_side: The number of points per side to use when blending the coordinates of the two airfoils. Returns: A new airfoil that is a blend of this airfoil and another one. .. py:method:: write_dat(filepath = None, include_name = True) Writes a .dat file corresponding to this airfoil to a filepath. :param filepath: filepath (including the filename and .dat extension) [string] If None, this function returns the .dat file as a string. :param include_name: Should the name be included in the .dat file? (In a standard *.dat file, it usually is.) Returns: None .. py:class:: KulfanAirfoil(name = 'Untitled', lower_weights = None, upper_weights = None, leading_edge_weight = 0.0, TE_thickness = 0.0, N1 = 0.5, N2 = 1.0) Bases: :py:obj:`aerosandbox.geometry.airfoil.airfoil.Airfoil` An airfoil. See constructor docstring for usage details. .. py:attribute:: name :value: 'Untitled' .. py:attribute:: lower_weights :value: None .. py:attribute:: upper_weights :value: None .. py:attribute:: leading_edge_weight :value: 0.0 .. py:attribute:: TE_thickness :value: 0.0 Returns the thickness of the trailing edge of the airfoil. .. py:attribute:: N1 :value: 0.5 .. py:attribute:: N2 :value: 1.0 .. py:method:: __repr__() .. py:property:: kulfan_parameters .. py:property:: coordinates :type: aerosandbox.numpy.ndarray .. py:method:: to_airfoil(n_coordinates_per_side=200, spacing_function_per_side=np.cosspace) .. py:method:: repanel(n_points_per_side = 100, spacing_function_per_side=np.cosspace) Returns a repaneled copy of the airfoil with cosine-spaced coordinates on the upper and lower surfaces. :param n_points_per_side: Number of points per side (upper and lower) of the airfoil [int] Notes: The number of points defining the final airfoil will be `n_points_per_side * 2 - 1`, since one point (the leading edge point) is shared by both the upper and lower surfaces. :param spacing_function_per_side: Determines how to space the points on each side of the airfoil. Can be `np.linspace` or `np.cosspace`, or any other function of the call signature `f(a, b, n)` that returns a spaced array of `n` points between `a` and `b`. [function] Returns: A copy of the airfoil with the new coordinates. .. py:method:: normalize(return_dict = False) Returns a copy of the Airfoil with a new set of `coordinates`, such that: - The leading edge (LE) is at (0, 0) - The trailing edge (TE) is at (1, 0) - The chord length is equal to 1 The trailing-edge (TE) point is defined as the midpoint of the line segment connecting the first and last coordinate points (upper and lower surface TE points, respectively). The TE point is not necessarily one of the original points in the airfoil coordinates (`Airfoil.coordinates`); in general, it will not be one of the points if the TE thickness is nonzero. The leading-edge (LE) point is defined as the coordinate point with the largest Euclidian distance from the trailing edge. (In other words, if you were to center a circle on the trailing edge and progressively grow it, what's the last coordinate point that it would intersect?) The LE point is always one of the original points in the airfoil coordinates. The chord is defined as the Euclidian distance between the LE and TE points. Coordinate modifications to achieve the constraints described above (LE @ origin, TE at (1, 0), and chord of 1) are done by means of a translation and rotation. :param return_dict: Determines the output type of the function. - If `False` (default), returns a copy of the Airfoil with the new coordinates. - If `True`, returns a dictionary with keys: - "airfoil": a copy of the Airfoil with the new coordinates - "x_translation": the amount by which the airfoil's LE was translated in the x-direction - "y_translation": the amount by which the airfoil's LE was translated in the y-direction - "scale_factor": the amount by which the airfoil was scaled (if >1, the airfoil had to get bigger) - "rotation_angle": the angle (in degrees) by which the airfoil was rotated about the LE. Sign convention is that positive angles rotate the airfoil counter-clockwise. All of thes values represent the "required change", e.g.: - "x_translation" is the amount by which the airfoil's LE had to be translated in the x-direction to get it to the origin. - "rotation_angle" is the angle (in degrees) by which the airfoil had to be rotated (CCW). Returns: Depending on the value of `return_dict`, either: - A copy of the airfoil with the new coordinates (default), or - A dictionary with keys "airfoil", "x_translation", "y_translation", "scale_factor", and "rotation_angle". documentation for `return_tuple` for more information. .. py:method:: draw(*args, draw_markers=False, **kwargs) Draw the airfoil object. :param draw_mcl: Should we draw the mean camber line (MCL)? [boolean] :param backend: Which backend should we use? "plotly" or "matplotlib" :param show: Should we show the plot? [boolean] Returns: None .. py:method:: get_aero_from_neuralfoil(alpha, Re, mach = 0.0, n_crit = 9.0, xtr_upper = 1.0, xtr_lower = 1.0, model_size = 'large', control_surfaces = None, include_360_deg_effects = True) .. py:method:: upper_coordinates(x_over_c = np.linspace(1, 0, 101)) Returns an Nx2 ndarray of [x, y] coordinates that describe the upper surface of the airfoil. Order is from the trailing edge to the leading edge. Includes the leading edge point; be careful about duplicates if using this method in conjunction with Airfoil.lower_coordinates(). .. py:method:: lower_coordinates(x_over_c = np.linspace(0, 1, 101)) Returns an Nx2 ndarray of [x, y] coordinates that describe the lower surface of the airfoil. Order is from the leading edge to the trailing edge. Includes the leading edge point; be careful about duplicates if using this method in conjunction with Airfoil.upper_coordinates(). .. py:method:: local_camber(x_over_c = np.linspace(0, 1, 101)) Returns the local camber of the airfoil at a given point or points. :param x_over_c: The x/c locations to calculate the camber at [1D array, more generally, an iterable of floats] :returns: Local camber of the airfoil (y/c) [1D array]. .. py:method:: local_thickness(x_over_c = np.linspace(0, 1, 101)) Returns the local thickness of the airfoil at a given point or points. :param x_over_c: The x/c locations to calculate the thickness at [1D array, more generally, an iterable of floats] :returns: Local thickness of the airfoil (y/c) [1D array]. .. py:method:: LE_radius(relative_softness = 0.03) .. py:method:: TE_angle() Returns the trailing edge angle of the airfoil, in degrees. .. py:method:: area() Returns the area of the polygon. .. py:method:: set_TE_thickness(thickness = 0.0) Creates a modified copy of the KulfanAirfoil that has a specified trailing-edge thickness. Note that the trailing-edge thickness is given nondimensionally (e.g., as a fraction of chord). :param thickness: The target trailing-edge thickness, given nondimensionally (e.g., as a fraction of chord). Returns: The modified KulfanAirfoil. .. py:method:: scale(scale_x = 1.0, scale_y = 1.0) Scales a KulfanAirfoil about the origin. :param scale_x: Amount to scale in the x-direction. Note: not supported by KulfanAirfoil due to inherent limitations of parameterization; only given here so that argument symmetry to Airfoil.scale() is retained. Raises a ValueError if modified, along with instructions to use `Airfoil` if needed. :param scale_y: Amount to scale in the y-direction. Scaling by a negative y-value will result in `lower_weights` and `upper_weights` being flipped as appropriate. Returns: A copy of the KulfanAirfoil with appropriate scaling applied. .. py:method:: blend_with_another_airfoil(airfoil, blend_fraction = 0.5) Blends this airfoil with another airfoil. Merges both the coordinates and the aerodynamic functions. :param airfoil: The other airfoil to blend with. :param blend_fraction: The fraction of the other airfoil to use when blending. Defaults to 0.5 (50%). * A blend fraction of 0 will return an identical airfoil to this one (self). * A blend fraction of 1 will return an identical airfoil to the other one (`airfoil` parameter). :param n_points_per_side: The number of points per side to use when blending the coordinates of the two airfoils. Returns: A new airfoil that is a blend of this airfoil and another one. .. py:class:: Wing(name = None, xsecs = None, symmetric = False, color = None, analysis_specific_options = None, **kwargs) Bases: :py:obj:`aerosandbox.common.AeroSandboxObject` Definition for a Wing. Anatomy of a Wing: A wing consists chiefly of a collection of cross-sections, or "xsecs". A cross-section is a 2D "slice" of a wing. These can be accessed with `Wing.xsecs`, which gives a list of xsecs in the Wing. Each xsec is a WingXSec object, a class that is defined separately. You may also see references to wing "sections", which are different than cross-sections (xsecs)! Sections are the portions of the wing that are in between xsecs. In other words, a wing with N cross-sections (xsecs, WingXSec objects) will always have N-1 sections. Sections are never explicitly defined, since you can get all needed information by lofting from the adjacent cross-sections. For example, section 0 (the first one) is a loft between cross-sections 0 and 1. Wings are lofted linearly between cross-sections. If the wing is symmetric across the XZ plane, just define the right half and supply `symmetric=True` in the constructor. If the wing is not symmetric across the XZ plane (e.g., a single vertical stabilizer), just define the wing. .. py:attribute:: name :value: None .. py:attribute:: xsecs :value: None .. py:attribute:: symmetric :value: False .. py:attribute:: color :value: None .. py:attribute:: analysis_specific_options :value: None .. py:method:: __repr__() .. py:method:: translate(xyz) Translates the entire Wing by a certain amount. :param xyz: Returns: The new wing object. .. py:method:: span(type = 'yz', include_centerline_distance=False, _sectional = False) Computes the span, with options for various ways of measuring this (see `type` argument). If the wing is symmetric, both left/right sides are included in order to obtain the full span. In the case where the root cross-section is not coincident with the center plane (e.g., XZ plane), this function's behavior depends on the `include_centerline_distance` argument. :param type: One of the following options, as a string: * "xyz": First, computes the quarter-chord point of each WingXSec. Then, connects these with straight lines. Then, adds up the lengths of these lines. * "xy" or "top": Same as "xyz", except it projects each line segment onto the XY plane before adding up the lengths. * "yz" (default) or "front": Same as "xyz", except it projects each line segment onto the YZ plane (i.e., front view) before adding up the lengths. * "xz" or "side": Same as "xyz", except it projects each line segment onto the XZ plane before adding up the lengths. Rarely needed. * "x": Same as "xyz", except it only counts the x-components of each line segment when adding up the lengths. * "y": Same as "xyz", except it only counts the y-components of each line segment when adding up the lengths. * "z": Same as "xyz", except it only counts the z-components of each line segment when adding up the lengths. :param include_centerline_distance: A boolean flag that tells the function what to do if a wing's root is not :param coincident with the centerline plane: * If True, we first figure out which WingXSec has its quarter-chord point closest to the centerline plane (i.e., XZ plane). Then, we compute the distance from that quarter-chord point directly to the centerline plane (along Y). We then add that distance to the span calculation. In other words, the fictitious span connecting the left and right root cross-sections is included. * If False, this distance is ignored. In other words, the fictitious span connecting the left and right root cross-sections is not included. This is the default behavior. Note: For computation, either the root WingXSec (i.e., index=0) or the tip WingXSec (i.e., index=-1) is used, whichever is closer to the centerline plane. This will almost-always be the root WingXSec, but some weird edge cases (e.g., a half-wing defined on the left-hand-side of the airplane, rather than the conventional right-hand side) will result in the tip WingXSec being used. :type coincident with the centerline plane: i.e., XZ plane :param _sectional: A boolean. If False, returns the total span. If True, returns a list of spans for each of the `n-1` lofted sections (between the `n` wing cross-sections in wing.xsec). .. py:method:: area(type = 'planform', include_centerline_distance=False, _sectional = False) Computes the wing area, with options for various ways of measuring this (see `type` argument): If the wing is symmetric, both left/right sides are included in order to obtain the full area. In the case where the root cross-section is not coincident with the center plane (e.g., XZ plane), this function's behavior depends on the `include_centerline_distance` argument. :param type: One of the following options, as a string: * "planform" (default): First, lofts a quadrilateral mean camber surface between each WingXSec. Then, computes the area of each of these sectional surfaces. Then, sums up all the areas and returns it. When airplane designers refer to "wing area" (in the absence of any other qualifiers), this is typically what they mean. * "wetted": Computes the actual surface area of the wing that is in contact with the air. Will typically be a little more than double the "planform" area above; intuitively, this is because it adds both the "top" and "bottom" surface areas. Accounts for airfoil thickness/shape effects. * "xy" or "projected" or "top": Same as "planform", but each sectional surface is projected onto the XY plane (i.e., top-down view) before computing the areas. Note that if you try to use this method with a vertically-oriented wing, like most vertical stabilizers, you will get an area near zero. * "xz" or "side": Same as "planform", but each sectional surface is projected onto the XZ plane before computing the areas. :param include_centerline_distance: A boolean flag that tells the function what to do if a wing's root chord is :param not coincident with the centerline plane: * If True, we first figure out which WingXSec is closest to the centerline plane (i.e., XZ plane). Then, we imagine that this WingXSec is extruded along the Y axis to the centerline plane (assuming a straight extrusion to produce a rectangular mid-camber surface). In doing so, we use the wing geometric chord as the extrusion width. We then add the area of this fictitious surface to the area calculation. * If False, this function will simply ignore this fictitious wing area. This is the default behavior. :type not coincident with the centerline plane: i.e., XZ plane :param _sectional: A boolean. If False, returns the total area. If True, returns a list of areas for each of the `n-1` lofted sections (between the `n` wing cross-sections in wing.xsec). .. py:method:: aspect_ratio(type = 'geometric') Computes the aspect ratio of the wing, with options for various ways of measuring this. * geometric: geometric aspect ratio, computed in the typical fashion (b^2 / S). * effective: Differs from the geometric aspect ratio only in the case of symmetric wings whose root cross-section is not on the centerline. In these cases, it includes the span and area of the fictitious wing center when computing aspect ratio. :param type: One of the above options, as a string. .. py:method:: is_entirely_symmetric() .. py:method:: mean_geometric_chord() Returns the mean geometric chord of the wing (S/b). :return: .. py:method:: mean_aerodynamic_chord() Computes the length of the mean aerodynamic chord of the wing. Uses the generalized methodology described here: https://core.ac.uk/download/pdf/79175663.pdf Returns: The length of the mean aerodynamic chord. .. py:method:: mean_twist_angle() Returns the mean twist angle (in degrees) of the wing, weighted by area. :return: mean twist angle (in degrees) .. py:method:: mean_sweep_angle(x_nondim=0.25) Returns the mean sweep angle (in degrees) of the wing, relative to the x-axis. Positive sweep is backwards, negative sweep is forward. This is purely measured from root to tip, with no consideration for the sweep of the individual cross-sections in between. :param x_nondim: The nondimensional x-coordinate of the cross-section to use for sweep angle computation. * If you provide 0, it will use the leading edge of the cross-section. * If you provide 0.25, it will use the quarter-chord point of the cross-section. * If you provide 1, it will use the trailing edge of the cross-section. :returns: The mean sweep angle, in degrees. .. py:method:: mean_dihedral_angle(x_nondim=0.25) Returns the mean dihedral angle (in degrees) of the wing, relative to the XY plane. Positive dihedral is bending up, negative dihedral is bending down. This is purely measured from root to tip, with no consideration for the dihedral of the individual cross-sections in between. :param x_nondim: The nondimensional x-coordinate of the cross-section to use for sweep angle computation. * If you provide 0, it will use the leading edge of the cross-section. * If you provide 0.25, it will use the quarter-chord point of the cross-section. * If you provide 1, it will use the trailing edge of the cross-section. :returns: The mean dihedral angle, in degrees .. py:method:: aerodynamic_center(chord_fraction = 0.25, _sectional=False) Computes the location of the aerodynamic center of the wing. Uses the generalized methodology described here: https://core.ac.uk/downloattttd/pdf/79175663.pdf Args: chord_fraction: The position of the aerodynamic center along the MAC, as a fraction of MAC length. Typically, this value (denoted `h_0` in the literature) is 0.25 for a subsonic wing. However, wing-fuselage interactions can cause a forward shift to a value more like 0.1 or less. Citing Cook, Michael V., "Flight Dynamics Principles", 3rd Ed., Sect. 3.5.3 "Controls-fixed static stability". PDF: https://www.sciencedirect.com/science/article/pii/B9780080982427000031 Returns: The (x, y, z) coordinates of the aerodynamic center of the wing. .. py:method:: taper_ratio() Gives the taper ratio of the Wing. Strictly speaking, only valid for trapezoidal wings. :returns: Taper ratio of the Wing. .. py:method:: volume(_sectional = False) Computes the volume of the Wing. :param _sectional: A boolean. If False, returns the total volume. If True, returns a list of volumes for each of :param the `n-1` lofted sections: :type the `n-1` lofted sections: between the `n` wing cross-sections in wing.xsec :returns: The computed volume. .. py:method:: get_control_surface_names() Gets the names of all control surfaces on this wing. :returns: A list of control surface names. .. py:method:: set_control_surface_deflections(control_surface_mappings) Sets the deflection of all control surfaces on this wing, based on the provided mapping. :param control_surface_mappings: A dictionary mapping control surface names to their deflection angles, in degrees. Note: control surface names are set in the asb.ControlSurface constructor. :returns: None. (in-place) .. py:method:: control_surface_area(by_name = None, type = 'planform') Computes the total area of all control surfaces on this wing, optionally filtered by their name. Control surfaces are defined on a section-by-section basis, and are defined in the WingXSec constructor using its `control_surfaces` argument. Note: If redundant control surfaces are defined (e.g., elevons, as defined by separate ailerons + elevator), the area will be duplicated. If the wing is symmetric, control surfaces on both left/right sides are included in order to obtain the full area. :param by_name: If not None, only control surfaces with this name will be included in the area calculation. Note: control surface names are set in the asb.ControlSurface constructor. :param type: One of the following options, as a string: * "planform" (default): First, lofts a quadrilateral mean camber surface between each WingXSec. Then, computes the area of each of these sectional surfaces. Then, computes what fraction of this area is control surface. Then, sums up all the areas and returns it. When airplane designers refer to "control surface area" (in the absence of any other qualifiers), this is typically what they mean. * "wetted": Computes the actual surface area of the control surface that is in contact with the air. Will typically be a little more than double the "planform" area above; intuitively, this is because it adds both the "top" and "bottom" surface areas. Accounts for airfoil thickness/shape effects. * "xy" or "projected" or "top": Same as "planform", but each sectional surface is projected onto the XY plane (i.e., top-down view) before computing the areas. Note that if you try to use this method with a vertically-oriented wing, like most vertical stabilizers, you will get an area near zero. * "xz" or "side": Same as "planform", but each sectional surface is projected onto the XZ plane before computing the areas. .. py:method:: mesh_body(method='quad', chordwise_resolution = 36, chordwise_spacing_function_per_side = np.cosspace, mesh_surface = True, mesh_tips = True, mesh_trailing_edge = True, mesh_symmetric = True) Meshes the outer mold line surface of the wing. Uses the `(points, faces)` standard mesh format. For reference on this format, see the documentation in `aerosandbox.geometry.mesh_utilities`. Order of faces: * On the right wing (or, if `Wing.symmetric` is `False`, just the wing itself): * If `mesh_surface` is `True`: * First face is nearest the top-side trailing edge of the wing root. * Proceeds chordwise, along the upper surface of the wing from back to front. Upon reaching the leading edge, continues along the lower surface of the wing from front to back. * Then, repeats this process for the next spanwise slice of the wing, and so on. * If `mesh_trailing_edge` is `True`: * Continues by meshing the trailing edge of the wing. Meshes the inboard trailing edge first, then proceeds spanwise to the outboard trailing edge. * If `mesh_tips` is `True`: * Continues by meshing the wing tips. Meshes the inboard tip first, then meshes the outboard tip. * Within each tip, meshes from the :param method: One of the following options, as a string: * "tri": Triangular mesh. * "quad": Quadrilateral mesh. :param chordwise_resolution: Number of points to use per wing chord, per wing section. :param chordwise_spacing_function_per_side: A function that determines how to space points in the chordwise :param direction along the top and bottom surfaces. Common values would be `np.linspace` or `np.cosspace`: :param : :param but it can be any function with the call signature `f: :type but it can be any function with the call signature `f: a, b, n :param between `a` and `b`. [function]: :param mesh_surface: If True, includes the actual wing surface in the mesh. :param mesh_tips: If True, includes the wing tips (both on the inboard-most section and on the outboard-most :param section) in the mesh.: :param mesh_trailing_edge: If True, includes the wing trailing edge in the mesh, if the trailing-edge thickness :param is nonzero.: :param mesh_symmetric: Has no effect if the wing is not symmetric. If the wing is symmetric this determines whether :param the generated mesh is also symmetric: :type the generated mesh is also symmetric: right side :param or if if only one side of the wing: :type or if if only one side of the wing: right side Returns: Standard unstructured mesh format: A tuple of `points` and `faces`, where: * `points` is a `n x 3` array of points, where `n` is the number of points in the mesh. * `faces` is a `m x 3` array of faces if `method` is "tri", or a `m x 4` array of faces if `method` is "quad". * Each row of `faces` is a list of indices into `points`, which specifies a face. .. py:method:: mesh_thin_surface(method='tri', chordwise_resolution = 36, chordwise_spacing_function = np.cosspace, add_camber = True) Meshes the mean camber line of the wing as a thin-sheet body. Uses the `(points, faces)` standard mesh format. For reference on this format, see the documentation in `aerosandbox.geometry.mesh_utilities`. Order of faces: * On the right wing (or, if `Wing.symmetric` is `False`, just the wing itself): * First face is the face nearest the leading edge of the wing root. * Proceeds along a chordwise strip to the trailing edge. * Then, goes to the subsequent spanwise location and does another chordwise strip, et cetera until we get to the wing tip. * On the left wing (applicable only if `Wing.symmetric` is `True`): * Same order: Starts at the root leading edge, goes in chordwise strips. Order of vertices within each face: * On the right wing (or, if `Wing.symmetric` is `False`, just the wing itself): * Front-left * Back-left * Back-right * Front-right * On the left wing (applicable only if `Wing.symmetric` is `True`): * Front-left * Back-left * Back-right * Front-right :param method: A string, which determines whether to mesh the fuselage as a series of quadrilaterals or triangles. * "quad" meshes the fuselage as a series of quadrilaterals. * "tri" meshes the fuselage as a series of triangles. :param chordwise_resolution: Determines the number of chordwise panels to use in the meshing. [int] :param chordwise_spacing_function: Determines how to space the chordwise panels. Can be `np.linspace` or :param `np.cosspace`: :type `np.cosspace`: a, b, n :param or any other function of the call signature `f: :type or any other function of the call signature `f: a, b, n :param `n` points between `a` and `b`. [function]: :param add_camber: Controls whether to mesh the thin surface with camber (i.e., mean camber line), or to just :param mesh the flat planform. [bool]: Returns: Standard unstructured mesh format: A tuple of `points` and `faces`, where: * `points` is a `n x 3` array of points, where `n` is the number of points in the mesh. * `faces` is a `m x 3` array of faces if `method` is "tri", or a `m x 4` array of faces if `method` is "quad". * Each row of `faces` is a list of indices into `points`, which specifies a face. .. py:method:: mesh_line(x_nondim = 0.25, z_nondim = 0, add_camber = True) Meshes a line that goes through each of the WingXSec objects in this wing. :param x_nondim: The nondimensional (chord-normalized) x-coordinate that the line should go through. Can either :param be a single value used at all cross-sections: :param or can be an iterable of values to be used at the: :param respective cross-sections.: :param z_nondim: The nondimensional (chord-normalized) y-coordinate that the line should go through. Here, :param y-coordinate means the "vertical" component: :type y-coordinate means the "vertical" component: think standard 2D airfoil axes :param value used at all cross-sections: :param or can be an iterable of values to be used at the respective cross: :param sections.: :param add_camber: Controls whether the camber of each cross-section's airfoil should be added to the line or :param not. Essentially modifies `z_nondim` to be `z_nondim + camber`.: Returns: A list of points, where each point is a 3-element array of the form `[x, y, z]`. Goes from the root to the tip. Ignores any wing symmetry (e.g., only gives one side). .. py:method:: draw(*args, **kwargs) An alias to the more general Airplane.draw() method. See there for documentation. :param \*args: Arguments to pass through to Airplane.draw() :param \*\*kwargs: Keyword arguments to pass through to Airplane.draw() Returns: Same return as Airplane.draw() .. py:method:: draw_wireframe(*args, **kwargs) An alias to the more general Airplane.draw_wireframe() method. See there for documentation. :param \*args: Arguments to pass through to Airplane.draw_wireframe() :param \*\*kwargs: Keyword arguments to pass through to Airplane.draw_wireframe() Returns: Same return as Airplane.draw_wireframe() .. py:method:: draw_three_view(*args, **kwargs) An alias to the more general Airplane.draw_three_view() method. See there for documentation. :param \*args: Arguments to pass through to Airplane.draw_three_view() :param \*\*kwargs: Keyword arguments to pass through to Airplane.draw_three_view() Returns: Same return as Airplane.draw_three_view() .. py:method:: subdivide_sections(ratio, spacing_function = np.linspace) Generates a new Wing that subdivides the existing sections of this Wing into several smaller ones. Splits each section into N=`ratio` smaller sub-sections by inserting new cross-sections (xsecs) as needed. This can allow for finer aerodynamic resolution of sectional properties in certain analyses. :param ratio: The number of new sections to split each old section into. :param spacing_function: A function that takes in three arguments: the start, end, and number of points to generate. The default is `np.linspace`, which generates a linearly-spaced array of points. Other options include `np.cosspace`, which generates a cosine-spaced array of points. Returns: A new Wing object with subdivided sections. .. py:method:: _compute_xyz_le_of_WingXSec(index) .. py:method:: _compute_xyz_te_of_WingXSec(index) .. py:method:: _compute_xyz_of_WingXSec(index, x_nondim, z_nondim) .. py:method:: _compute_frame_of_WingXSec(index) Computes the local reference frame associated with a particular cross-section (XSec) of this wing. :param index: Which cross-section (as indexed in Wing.xsecs) should we get the frame of? :returns: A tuple of (xg_local, yg_local, zg_local), where each entry refers to the respective (normalized) axis of the local reference frame of the WingXSec. Given in geometry axes. .. py:method:: _compute_frame_of_section(index) Computes the local reference frame associated with a particular section. (Note that sections and cross sections are different! cross-sections, or xsecs, are the vertices, and sections are the parts in between. In other words, a wing with N cross-sections (xsecs) will always have N-1 sections. :param index: Which section should we get the frame of? If given `i`, this retrieves the frame of the section :param between xsecs `i` and `i+1`.: :returns: A tuple of (xg_local, yg_local, zg_local), where each entry refers to the respective (normalized) axis of the local reference frame of the section. Given in geometry axes. .. py:class:: WingXSec(xyz_le = None, chord = 1.0, twist = 0.0, airfoil = None, control_surfaces = None, analysis_specific_options = None, **deprecated_kwargs) Bases: :py:obj:`aerosandbox.common.AeroSandboxObject` Definition for a wing cross-section ("X-section"). .. py:attribute:: xyz_le .. py:attribute:: chord :value: 1.0 .. py:attribute:: twist :value: 0.0 .. py:attribute:: airfoil :value: None .. py:attribute:: control_surfaces :value: None .. py:attribute:: analysis_specific_options :value: None .. py:method:: __repr__() .. py:method:: translate(xyz) Returns a copy of this WingXSec that has been translated by `xyz`. :param xyz: The amount to translate the WingXSec. Given as a 3-element NumPy vector. Returns: A new WingXSec object. .. py:method:: xsec_area() Computes the WingXSec's cross-sectional (xsec) area. Returns: The (dimensional) cross-sectional area of the WingXSec. .. py:class:: ControlSurface(name = 'Untitled', symmetric = True, deflection = 0.0, hinge_point = 0.75, trailing_edge = True, analysis_specific_options = None) Bases: :py:obj:`aerosandbox.common.AeroSandboxObject` Definition for a control surface, which is attached to a particular WingXSec via WingXSec's `control_surfaces=[]` parameter. .. py:attribute:: name :value: 'Untitled' .. py:attribute:: symmetric :value: True .. py:attribute:: deflection :value: 0.0 .. py:attribute:: hinge_point :value: 0.75 .. py:attribute:: trailing_edge :value: True .. py:attribute:: analysis_specific_options :value: None .. py:method:: __repr__() .. py:class:: Fuselage(name = 'Untitled', xsecs = None, color = None, analysis_specific_options = None, **kwargs) Bases: :py:obj:`aerosandbox.AeroSandboxObject` Definition for a Fuselage or other slender body (pod, fuel tank, etc.). Anatomy of a Fuselage: A fuselage consists chiefly of a collection of cross-sections, or "xsecs". A cross-section is a 2D "slice" of a fuselage. These can be accessed with `Fuselage.xsecs`, which gives a list of xsecs in the Fuselage. Each xsec is a FuselageXSec object, a class that is defined separately. You may also see references to fuselage "sections", which are different from cross-sections (xsecs)! Sections are the portions of the fuselage that are in between xsecs. In other words, a fuselage with N cross-sections (xsecs, FuselageXSec objects) will always have N-1 sections. Sections are never explicitly defined, since you can get all needed information by lofting from the adjacent cross-sections. For example, section 0 (the first one) is a loft between cross-sections 0 and 1. Fuselages are lofted linearly between cross-sections. .. py:attribute:: name :value: 'Untitled' .. py:attribute:: xsecs :value: None .. py:attribute:: color :value: None .. py:attribute:: analysis_specific_options :value: None .. py:method:: __repr__() .. py:method:: add_loft(kind, to_xsec, from_xsec = None, n_points = 5, spacing = np.cosspace) :abstractmethod: .. py:method:: translate(xyz) Translates the entire Fuselage by a certain amount. :param xyz: Returns: self .. py:method:: area_wetted() Returns the wetted area of the fuselage. :return: .. py:method:: area_projected(type = 'XY') Returns the area of the fuselage as projected onto one of the principal planes. :param type: A string, which determines which principal plane to use for projection. One of: * "XY", in which case the projected area is onto the XY plane (i.e., top-down) * "XZ", in which case the projected area is onto the XZ plane (i.e., side-view) Returns: The projected area. .. py:method:: area_base() Returns the area of the base (i.e. "trailing edge") of the fuselage. Useful for certain types of drag calculation. Returns: .. py:method:: fineness_ratio(assumed_shape='cylinder') Approximates the fineness ratio using the volume and length. The fineness ratio of a fuselage is defined as: FR = length / max_diameter :param assumed_shape: A string, which determines the assumed shape of the fuselage for the approximation. One of: * "cylinder", in which case the fuselage is assumed to have a cylindrical shape. * "sears-haack", in which case the fuselage is assumed to have Sears-Haack fuselage shape. Returns: An approximate value of the fuselage's fineness ratio. .. py:method:: length() Returns the total front-to-back length of the fuselage. Measured as the difference between the x-coordinates of the leading and trailing cross-sections. :return: .. py:method:: volume(_sectional = False) Computes the volume of the Fuselage. :param _sectional: A boolean. If False, returns the total volume. If True, returns a list of volumes for each of :param the `n-1` lofted sections: :type the `n-1` lofted sections: between the `n` fuselage cross-sections in fuselage.xsec :returns: The computed volume. .. py:method:: x_centroid_projected(type = 'XY') Returns the x_g coordinate of the centroid of the planform area. :param type: A string, which determines which principal plane to use for projection. One of: * "XY", in which case the projected area is onto the XY plane (i.e., top-down) * "XZ", in which case the projected area is onto the XZ plane (i.e., side-view) Returns: The x_g coordinate of the centroid. .. py:method:: mesh_body(method='quad', tangential_resolution = 36) Meshes the fuselage as a solid (thickened) body. Uses the `(points, faces)` standard mesh format. For reference on this format, see the documentation in `aerosandbox.geometry.mesh_utilities`. :param method: A string, which determines whether to mesh the fuselage as a series of quadrilaterals or triangles. * "quad" meshes the fuselage as a series of quadrilaterals. * "tri" meshes the fuselage as a series of triangles. :param tangential_resolution: An integer, which determines the number of points to use to mesh each cross-section. Returns: Standard unstructured mesh format: A tuple of`points` and `faces`, where: * `points` is a `n x 3` array of points, where `n` is the number of points in the mesh. * `faces` is a `m x 3` array of faces if `method` is "tri", or a `m x 4` array of faces if `method` is "quad". * Each row of `faces` is a list of indices into `points`, which specifies a face. .. py:method:: mesh_line(y_nondim = 0.0, z_nondim = 0.0) Returns points along a line that goes through each of the FuselageXSec objects in this Fuselage. :param y_nondim: The nondimensional (width-normalized) y-coordinate that the line should go through. Can either :param be a single value used at all cross-sections: :param or can be an iterable of values to be used at the: :param respective cross-sections.: :param z_nondim: The nondimensional (height-normalized) z-coordinate that the line should go through. Can either :param be a single value used at all cross-sections: :param or can be an iterable of values to be used at the: :param respective cross-sections.: Returns: A list of points, where each point is a 3-element array of the form `[x, y, z]`. Goes from the nose to the tail. .. py:method:: draw(*args, **kwargs) An alias to the more general Airplane.draw() method. See there for documentation. :param \*args: Arguments to pass through to Airplane.draw() :param \*\*kwargs: Keyword arguments to pass through to Airplane.draw() Returns: Same return as Airplane.draw() .. py:method:: draw_wireframe(*args, **kwargs) An alias to the more general Airplane.draw_wireframe() method. See there for documentation. :param \*args: Arguments to pass through to Airplane.draw_wireframe() :param \*\*kwargs: Keyword arguments to pass through to Airplane.draw_wireframe() Returns: Same return as Airplane.draw_wireframe() .. py:method:: draw_three_view(*args, **kwargs) An alias to the more general Airplane.draw_three_view() method. See there for documentation. :param \*args: Arguments to pass through to Airplane.draw_three_view() :param \*\*kwargs: Keyword arguments to pass through to Airplane.draw_three_view() Returns: Same return as Airplane.draw_three_view() .. py:method:: subdivide_sections(ratio, spacing_function = np.linspace) Generates a new Fuselage that subdivides the existing sections of this Fuselage into several smaller ones. Splits each section into N=`ratio` smaller subsections by inserting new cross-sections (xsecs) as needed. This can allow for finer aerodynamic resolution of sectional properties in certain analyses. :param ratio: The number of new sections to split each old section into. :param spacing_function: A function that takes in three arguments: the start, end, and number of points to generate. The default is `np.linspace`, which generates a linearly-spaced array of points. Other options include `np.cosspace`, which generates a cosine-spaced array of points. Returns: A new Fuselage object with subdivided sections. .. py:method:: _compute_frame_of_FuselageXSec(index) Computes the local frame of a FuselageXSec, given the index of the FuselageXSec in the Fuselage.xsecs list. :param index: The index of the FuselageXSec in the Fuselage.xsecs list. Returns: A tuple: xg_local: The x-axis of the local coordinate frame, in aircraft geometry axes. yg_local: The y-axis of the local coordinate frame, in aircraft geometry axes. zg_local: The z-axis of the local coordinate frame, in aircraft geometry axes. .. py:class:: FuselageXSec(xyz_c = None, xyz_normal = None, radius = None, width = None, height = None, shape = 2.0, analysis_specific_options = None) Bases: :py:obj:`aerosandbox.AeroSandboxObject` Definition for a fuselage cross-section ("X-section"). .. py:attribute:: xyz_c .. py:attribute:: xyz_normal .. py:attribute:: shape :value: 2.0 .. py:attribute:: analysis_specific_options :value: None .. py:method:: __repr__() .. py:method:: xsec_area() Computes the FuselageXSec's cross-sectional (xsec) area. The computation method is a closed-form approximation for the area of a superellipse. The exact equation for the area of a superellipse with shape parameter `s` is: area = width * height * (gamma(1 + 1/n))^2 / gamma(1 + 2/n) where gamma() is the gamma function. The gamma function is (relatively) computationally expensive to evaluate and differentiate, so we replace this area calculation with a closed-form approximation (with essentially no loss in accuracy): area = width * height / (s^-1.8717618013591173 + 1) This approximation has the following properties: * It is numerically exact for the case of s = 1 (a diamond) * It is numerically exact for the case of s = 2 (a circle) * It is correct in the asymptotic limit where s -> infinity (a square) * In the range of sensible s values (1 < s < infinity), its error is less than 0.6%. * It always produces a positive area for any physically-meaningful value of s (s > 0). In the range of s values where s is physically-meaningful but not in a sensible range (0 < s < 1), this equation will over-predict area. The value of the constant seen in this expression (1.872...) is given by log(4/pi - 1) / log(2), and it is chosen as such so that the expression is exactly correct in the s=2 (circle) case. Returns: .. py:method:: xsec_perimeter() Computes the FuselageXSec's perimeter. ("Circumference" in the case of a circular cross-section.) The computation method is a closed-form approximation for the perimeter of a superellipse. The exact equation for the perimeter of a superellipse is quite long and is not repeated here for brevity; a Google search will bring it up. More importantly, this exact equation can only be represented as an infinite sum - not particularly useful for fast computation. We replace this exact equation with the following closed-form approximation obtained from symbolic regression: Imagine a superellipse centered on the origin of a 2D plane. Now, imagine that the superellipse is stretched such that the first quadrant (e.g., x>0, y>0) goes from (1, 0) to (0, h). Assume it has shape parameter s (where, as a reminder, s=1 is a diamond, s=2 is a circle, s=Inf is a square). Then, the perimeter of that single quadrant is: h + (((((s-0.88487077) * h + 0.2588574 / h) ^ exp(s / -0.90069205)) + h) + 0.09919785) ^ (-1.4812293 / s) See `AeroSandbox/studies/SuperellipseProperties` for details about how this was obtained. We can extrapolate from here to the general case of a superellipse, as shown in the code below. This approximation has the following properties: * For the s=1 case (diamond), the error is +0.2%. * For the s=2 case (circle), the error is -0.1%. * In the s -> infinity limit (square), the error is +0.1%. Returns: .. py:method:: compute_frame() Computes the local coordinate frame of the FuselageXSec, in aircraft geometry axes. xg_local is aligned with the FuselageXSec's normal vector. zg_local is roughly aligned with the z-axis of the aircraft geometry axes, but projected onto the FuselageXSec's plane. yg_local is the cross product of zg_local and xg_local. Returns: A tuple: xg_local: The x-axis of the local coordinate frame, in aircraft geometry axes. yg_local: The y-axis of the local coordinate frame, in aircraft geometry axes. zg_local: The z-axis of the local coordinate frame, in aircraft geometry axes. .. py:method:: get_3D_coordinates(theta = None) Samples points from the perimeter of this FuselageXSec. :param theta: Coordinate in the tangential-ish direction to sample points at. Given in the 2D FuselageXSec :param coordinate system: * y_2D points along the (global) y_g * z_2D points along the (global) z_g In other words, a value of: * theta=0 -> samples points from the right side of the FuselageXSec * theta=pi/2 -> samples points from the top of the FuselageXSec * theta=pi -> samples points from the left side of the FuselageXSec * theta=3pi/2 -> samples points from the bottom of the FuselageXSec :param where: * y_2D points along the (global) y_g * z_2D points along the (global) z_g In other words, a value of: * theta=0 -> samples points from the right side of the FuselageXSec * theta=pi/2 -> samples points from the top of the FuselageXSec * theta=pi -> samples points from the left side of the FuselageXSec * theta=3pi/2 -> samples points from the bottom of the FuselageXSec Returns: Points sampled from the perimeter of the FuselageXSec, as a [x, y, z] tuple. If theta is a float, then each of x, y, and z will be floats. If theta is an array, then x, y, and z will also be arrays of the same size. .. py:method:: equivalent_radius(preserve='area') Computes an equivalent radius for non-circular cross-sections. This may be necessary when doing analysis that uses axisymmetric assumptions. Can either hold area or perimeter fixed, depending on whether cross-sectional area or wetted area is more important. :param preserve: One of: * "area": holds the cross-sectional area constant * "perimeter": holds the cross-sectional perimeter (i.e., the wetted area of the Fuselage) constant Returns: An equivalent radius value. .. py:method:: translate(xyz) Returns a copy of this FuselageXSec that has been translated by `xyz`. :param xyz: The amount to translate the FuselageXSec. Given as a 3-element NumPy vector. Returns: A copy of this FuselageXSec, translated by `xyz`. .. py:class:: Airplane(name = None, xyz_ref = None, wings = None, fuselages = None, propulsors = None, s_ref = None, c_ref = None, b_ref = None, analysis_specific_options = None) Bases: :py:obj:`aerosandbox.AeroSandboxObject` Definition for an airplane. Anatomy of an Airplane: An Airplane consists chiefly of a collection of wings and fuselages. These can be accessed with `Airplane.wings` and `Airplane.fuselages`, which gives a list of those respective components. Each wing is a Wing object, and each fuselage is a Fuselage object. .. py:attribute:: name :value: None .. py:attribute:: xyz_ref .. py:attribute:: wings :value: None .. py:attribute:: fuselages :value: None .. py:attribute:: propulsors :value: None .. py:attribute:: analysis_specific_options :value: None .. py:method:: __repr__() .. py:method:: mesh_body(method='quad', thin_wings=False, stack_meshes=True) Returns a surface mesh of the Airplane, in (points, faces) format. For reference on this format, see the documentation in `aerosandbox.geometry.mesh_utilities`. :param method: :param thin_wings: Controls whether wings should be meshed as thin surfaces, rather than full 3D bodies. :param stack_meshes: Controls whether the meshes should be merged into a single mesh or not. * If True, returns a (points, faces) tuple in standard mesh format. * If False, returns a list of (points, faces) tuples in standard mesh format. Returns: .. py:method:: draw(backend = 'pyvista', thin_wings = False, ax=None, use_preset_view_angle = None, set_background_pane_color = None, set_background_pane_alpha = None, set_lims = True, set_equal = True, set_axis_visibility = None, show = True, show_kwargs = None) Produces an interactive 3D visualization of the airplane. :param backend: The visualization backend to use. Options are: * "matplotlib" for a Matplotlib backend * "pyvista" for a PyVista backend * "plotly" for a Plot.ly backend * "trimesh" for a trimesh backend :param thin_wings: A boolean that determines whether to draw the full airplane (i.e. thickened, 3D bodies), or to use a :param thin-surface representation for any Wing objects.: :param show: A boolean that determines whether to display the object after plotting it. If False, the object is :param returned but not displayed. If True: :param the object is displayed and returned.: Returns: The plotted object, in its associated backend format. Also displays the object if `show` is True. .. py:method:: draw_wireframe(ax=None, color='k', thin_linewidth=0.2, thick_linewidth=0.5, fuselage_longeron_theta=None, use_preset_view_angle = None, set_background_pane_color = None, set_background_pane_alpha = None, set_lims = True, set_equal = True, set_axis_visibility = None, show = True) Draws a wireframe of the airplane on a Matplotlib 3D axis. :param ax: The axis to draw on. Must be a 3D axis. If None, creates a new axis. :param color: The color of the wireframe. :param thin_linewidth: The linewidth of the thin lines. .. py:method:: draw_three_view(axs=None, style = 'shaded', show = True) Draws a standard 4-panel three-view diagram of the airplane using Matplotlib backend. Creates a new figure. :param axs: A 2D numpy array of Matplotlib axes objects, with shape at least (2, 2). Each subplot must be 3D. If :param None: :param creates a new figure with the required axes.: :param style: Determines what drawing style to use for the three-view. A string, one of: * "shaded" * "wireframe" :param show: A Boolean of whether to show the figure after creating it, or to hold it so that the user can :param modify the figure further before showing.: Returns: A 2D NumPy array of Matplotlib axes objects, with shape (2, 2). The axes are arranged as follows: * axs[0, 0]: Top view * axs[0, 1]: Front view * axs[1, 0]: Side view * axs[1, 1]: Isometric view .. py:method:: is_entirely_symmetric() Returns a boolean describing whether the airplane is geometrically entirely symmetric across the XZ-plane. :return: [boolean] .. py:method:: aerodynamic_center(chord_fraction = 0.25) Computes the approximate location of the aerodynamic center of the wing. Uses the generalized methodology described here: https://core.ac.uk/download/pdf/79175663.pdf :param chord_fraction: The position of the aerodynamic center along the MAC, as a fraction of MAC length. :param Typically: :type Typically: denoted `h_0` in the literature :param this value: :type this value: denoted `h_0` in the literature :param wing-fuselage interactions can cause a forward shift to a value more like 0.1 or less. Citing Cook: :param : :param Michael V.: :param "Flight Dynamics Principles": :param 3rd Ed.: :param Sect. 3.5.3 "Controls-fixed static stability". PDF: :param https: //www.sciencedirect.com/science/article/pii/B9780080982427000031 Returns: The (x, y, z) coordinates of the aerodynamic center of the airplane. .. py:method:: with_control_deflections(control_surface_deflection_mappings) Returns a copy of the airplane with the specified control surface deflections applied. :param control_surface_deflection_mappings: A dictionary mapping control surface names to deflections. * Keys: Control surface names. * Values: Deflections, in degrees. Downwards-positive, following typical convention. Returns: A copy of the airplane with the specified control surface deflections applied. .. py:method:: generate_cadquery_geometry(minimum_airfoil_TE_thickness = 0.001, fuselage_tol = 0.0001) Uses the CADQuery library (OpenCASCADE backend) to generate a 3D CAD model of the airplane. :param minimum_airfoil_TE_thickness: The minimum thickness of the trailing edge of the airfoils, as a fraction :param of each airfoil's chord. This will be enforced by thickening the trailing edge of the airfoils if: :param necessary. This is useful for avoiding numerical issues in CAD software that can arise from extremely: :param thin: :type thin: i.e., <1e-6 meters :param tol: The geometric tolerance (meters) to use when generating the CAD geometry. This is passed directly to the CADQuery Returns: A CADQuery Workplane object containing the CAD geometry of the airplane. .. py:method:: export_cadquery_geometry(filename, minimum_airfoil_TE_thickness = 0.001) Exports the airplane geometry to a STEP file. :param filename: The filename to export to. Should include the ".step" extension. :param minimum_airfoil_TE_thickness: The minimum thickness of the trailing edge of the airfoils, as a fraction :param of each airfoil's chord. This will be enforced by thickening the trailing edge of the airfoils if: :param necessary. This is useful for avoiding numerical issues in CAD software that can arise from extremely: :param thin: :type thin: i.e., <1e-6 meters Returns: None, but exports the airplane geometry to a STEP file. .. py:method:: export_AVL(filename, include_fuselages = True) .. py:method:: export_XFLR(*args, **kwargs) .. py:method:: export_XFLR5_xml(filename, mass_props = None, include_fuselages = False, mainwing = None, elevator = None, fin = None) Exports the airplane geometry to an XFLR5 `.xml` file. To import the `.xml` file into XFLR5, go to File -> Import -> Import from XML. :param filename: The filename to export to. Should include the ".xml" extension. :param mass_props: The MassProperties object to use when exporting the airplane. If not specified, will default to a 1 kg point mass at the origin. - Note: XFLR5 does not natively support user-defined inertia tensors, so we have to synthesize an equivalent set of point masses to represent the inertia tensor. :param include_fuselages: Whether to include fuselages in the export. :param mainwing: The main wing of the airplane. If not specified, will default to the first wing in the airplane. :param elevator: The elevator of the airplane. If not specified, will default to the second wing in the airplane. :param fin: The fin of the airplane. If not specified, will default to the third wing in the airplane. Returns: None, but exports the airplane geometry to an XFLR5 `.xml` file. To import the `.xml` file into XFLR5, go to File -> Import -> Import from XML. .. py:method:: export_OpenVSP_vspscript(filename) Exports the airplane geometry to a `*.vspscript` file compatible with OpenVSP. To import the `.vspscript` file into OpenVSP: Open OpenVSP, then File -> Run Script -> Select the `.vspscript` file. :param filename: The filename to export to, given as a string or Path. Should include the ".vspscript" extension. Returns: A string of the file contents, and also saves the file to the specified filename .. py:class:: Propulsor(name = 'Untitled', xyz_c = None, xyz_normal = None, radius = 1.0, length = 0.0, color = None, analysis_specific_options = None) Bases: :py:obj:`aerosandbox.AeroSandboxObject` Definition for a Propulsor, which could be a propeller, a rotor, or a jet engine. Assumes a disk- or cylinder-shaped propulsor. .. py:attribute:: name :value: 'Untitled' .. py:attribute:: xyz_c .. py:attribute:: xyz_normal .. py:attribute:: radius :value: 1.0 .. py:attribute:: length :value: 0.0 .. py:attribute:: color :value: None .. py:attribute:: analysis_specific_options :value: None .. py:method:: __repr__() .. py:method:: xsec_area() Returns the cross-sectional area of the propulsor, in m^2. .. py:method:: xsec_perimeter() Returns the cross-sectional perimeter of the propulsor, in m. .. py:method:: volume() Returns the volume of the propulsor, in m^3. .. py:method:: compute_frame() Computes the local coordinate frame of the propulsor, in aircraft geometry axes. xg_local is aligned with the propulsor's normal vector. zg_local is roughly aligned with the z-axis of the aircraft geometry axes, but projected onto the propulsor's plane. yg_local is the cross product of zg_local and xg_local. Returns: A tuple: xg_local: The x-axis of the local coordinate frame, in aircraft geometry axes. yg_local: The y-axis of the local coordinate frame, in aircraft geometry axes. zg_local: The z-axis of the local coordinate frame, in aircraft geometry axes. .. py:method:: get_disk_3D_coordinates(theta = None, l_over_length = None) .. py:method:: translate(xyz) Returns a copy of this propulsor that has been translated by `xyz`. :param xyz: The amount to translate the propulsor, in meters. Given in aircraft geometry axes, as with everything else. Returns: A copy of this propulsor, translated by `xyz`.