Skip to content

Commit

Permalink
Merge pull request #52 from Czarified/fuselage
Browse files Browse the repository at this point in the history
Fuselage work on MinorFrames
  • Loading branch information
Czarified authored May 17, 2024
2 parents 794a299 + 792d150 commit 85b66fe
Show file tree
Hide file tree
Showing 3 changed files with 162 additions and 10 deletions.
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "hyperstruct"
version = "0.0.3"
version = "0.0.4"
description = "Hyperstruct"
authors = ["Benjamin Crews <[email protected]>"]
license = "MIT"
Expand Down
7 changes: 7 additions & 0 deletions src/hyperstruct/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
"""Hyperstruct."""

from dataclasses import dataclass
from importlib.metadata import version


__version__ = version("hyperstruct")


@dataclass
Expand Down Expand Up @@ -54,6 +58,9 @@ class Component:
weights estimation.
"""

material: Material
"""material the cover is made of."""

def synthesis(self) -> None:
"""The sizing method.
Expand Down
163 changes: 154 additions & 9 deletions src/hyperstruct/fuselage.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
import numpy as np

from hyperstruct import Component
from hyperstruct import Material


@dataclass
Expand All @@ -27,9 +26,6 @@ class Cover(Component):
through a systematic check for the different criteria.
"""

material: Material
"""material the cover is made of."""

milled: bool
"""if panel is to be milled for different field vs land thicknesses."""

Expand Down Expand Up @@ -130,6 +126,12 @@ def field_thickness_postbuckled(self, alpha: float = 45) -> float:
Postbuckled strength assumes sizing covers with diagonal tension. The
diagonal tension angle is unknown because intermediate frame and
stringer sizing is not known. An initial estimate of 45 degrees is used.
Args:
alpha: Diagonal tension angle (assumed 45 degrees)
Returns:
A float of field thickness.
"""
F_scr = (
self.k_s
Expand Down Expand Up @@ -195,7 +197,11 @@ def thickness_pressure(self, F_allow: Any = None) -> Tuple[float, float]:
from zero to peak pressure 20,000 times during the vehicle's useful
life, with a stress concentration factor of 4.0.
Returns: (t_l, t_c)
Args:
F_allow: Allowable stress (25% yield, if not provided)
Returns:
A tuple of (Land thickness, Field thickness).
"""
b = min(self.D, self.L)
if not F_allow:
Expand Down Expand Up @@ -255,22 +261,32 @@ def panel_flutter(self, mach: float, altitude: float) -> float:
To find the final required thickness, this method must be looped
over for all Mach and altitudes corresponding to the flight
envelope conditions of the aircraft.
Args:
mach: Mach Number
altitude: Altitude (in thousands of feet)
Returns:
A float of Field Thickness.
"""
# Dynamic pressures based on standard day atmosphere.
# Dynamic Pressure, q, in [psf]
# Altitudes must be measured in [ft]
if altitude <= 20000:
if altitude <= 20:
q = mach**2 * (1479.757 - 52.187 * altitude + 0.619868 * altitude**2)
elif 20000 < altitude <= 70000:
elif 20 < altitude <= 70:
q = mach**2 * (
1465.175
- 50.76695 * altitude
+ 0.6434412 * altitude**2
- 0.002907194 * altitude**3
)
elif altitude > 70000:
elif altitude > 70:
q = mach**2 * (199.659 / altitude) ** 4

# Dynamic pressure is in [psf] convert to [psi]
q = q / 144

# Calculate the Mach Number Effect
# The Mach Number Effect, FM, is a 3 piece function from SWEEP.
# This function is more conservative than the AFRL baseline curve,
Expand Down Expand Up @@ -320,7 +336,8 @@ def acoustic_fatigue(self) -> Tuple[float, float]:
decibel level is then increased by 30, which represents jet noise instead
of a purely random spectrum.
Returns: (t_l, t_c)
Returns:
A tuple of Land thickness, Field thickness (t_l, t_c).
"""
# Random distribution acoustic level, that provides a material fatigue
# life of 10^9 cycles.
Expand All @@ -343,3 +360,131 @@ def acoustic_fatigue(self) -> Tuple[float, float]:
f_c = 1.0794 + 0.000143 * x_c - 0.076475 * (1 / x_c) - 0.29969 * np.log(x_c)

return (float(f_l * t_l), float(f_c * t_c))


@dataclass
class MinorFrame(Component):
"""Fuselage Minor Frame Component.
Minor Frames form part of the basic structural grid work that resists
vehicle shear and bending loads. As opposed to Major Frames, Minor
Frames do not redistribute concentrated loads. Minor Frames are sized
for general shell stability, acoustic fatigue, and forced crippling
due to postbuckled cover (skin) design.
Minor Frames have an assumed construction, shown here? The geometry
variables, frame depth (c), and cap flange width (b), are user input
parameters. Thickness is the only parameter determined by sizing.
For simplicity, the flanges are assumed to have a thickness twice
that of the web.
"""

c: float
"""frame depth."""

b: float
"""cap flange width."""

t_r: float = 0.0
"""cap flange thickness."""

@property
def t_w(self) -> float:
"""Web thickness."""
return self.t_r / 2

@property
def area(self) -> float:
"""Cross-sectional area."""
return 4 * self.b * self.t_r + self.c * self.t_w

@property
def i_xx(self) -> float:
"""Second moment of area (bending moment of inertia).
Assumes the simplified case that t_r = 2*t_w.
"""
return self.t_r * (
self.b * self.c**2 + 2 * self.b**3 / 3 - self.b**2 * self.c + self.c**3 / 24
)

@property
def rho(self) -> float:
"""Radius of gyration."""
return float(
(
(
self.b * self.c**2
+ 2 * self.b**3 / 3
- self.b**2 * self.c
+ self.c**3 / 24
)
/ (4 * self.b + self.c / 2)
)
** 0.5
)

def general_stability(self, L: float, D: float, M: float) -> float:
"""Thickness to avoid general instability.
The thickness that provides frame stiffness sufficient to prevent
general instability failure is solved via the Shanley equation.
Args:
L: Frame Spacing
D: Fuselage Diameter
M: Bending moment at the cut
Returns:
A float of Flange thickness.
"""
c_f = 1 / 16000
numerator = c_f * M * D**2
denominator = (
self.material.E_c
* L
* (self.b * self.c**2 + 2 * self.b**3 * self.c / 3 + self.c**3 / 24)
)

return float(numerator / denominator)

def acoustic_fatigue(self, b: float) -> float:
"""Thickness requirements based on acoustic fatigue.
Assumptions are:
1.) The overall pressure level from jet engine noise is approximately
30db higher than the random spectrum pressure level.
2.) The accuracy of noise intensity prediction is on the order of +/-3db.
3.) Beam theory is sufficient for modelign the shell elements.
4.) The design to repetitive pressure intensity from jet engine noise
can be correlated tot he material endurance limit. Polished specimen
s-n data, K_t=1, is a sufficiently representative strength index.
5.) The method is based on a limited amount of testing.
6.) The method does not consider panel aspect ratio, and is, therefore,
conservative when aspect ratios approach 1.
The sound pressure level used is based on a material property. This value
provides the fatigue life of 10^9 cycles for the material. The overall
decibel level is then increased by 30, which represents jet noise instead
of a purely random spectrum.
Args:
b: Support spacing (frame spacing)
Returns:
A float of Flange thickness.
"""
# Random distribution acoustic level, that provides a material fatigue
# life of 10^9 cycles.
db_r = self.material.db_r
db_oa = db_r + 30
P = 2.9e-9 * 10 ** (db_oa / 20)
# Note: K_c is directly hardcoded to 7.025, per Fig. 20 of ADA002867
t_r = 7.025 * b**0.5 * P**2 / self.material.F_en

return float(t_r)

def forced_crippling(self) -> float:
"""Thickness from force crippling."""
return 0.0

0 comments on commit 85b66fe

Please sign in to comment.