Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions docs/reference/classes/aero_surfaces/TubeFins.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
TubeFins Class
==============

.. autoclass:: rocketpy.TubeFins
:members:
3 changes: 2 additions & 1 deletion docs/reference/classes/aero_surfaces/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,12 @@ AeroSurface Classes
TrapezoidalFins
EllipticalFins
FreeFormFins
TubeFins
Fin
TrapezoidalFin
EllipticalFin
FreeFormFin
RailButtons
AirBrakes
GenericSurface
LinearGenericSurface
LinearGenericSurface
87 changes: 87 additions & 0 deletions docs/technical/aerodynamics/tube_fins.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
Tube Fin Aerodynamics
=====================

RocketPy models a tube-fin set as a symmetric ring of uncanted ring airfoils.
The implementation follows the preliminary tube-fin model in OpenRocket's
`TubeFinSetCalc <https://github.com/openrocket/openrocket/blob/unstable/core/src/main/java/info/openrocket/core/aerodynamics/barrowman/TubeFinSetCalc.java>`_.
The normal-force derivative is based on Ribner's analysis of a ring airfoil in
nonaxial flow [1]_.

Geometry
--------

Let :math:`n` be the number of tubes, :math:`R` the rocket-body radius,
:math:`r_i` the tube inner radius, :math:`r_o` the tube outer radius, and
:math:`L` the tube length. The tubes are distributed evenly around the rocket.
The current implementation requires each tube to touch its two neighbors:

.. math::

r_o = R \frac{\sin(\pi / n)}{1 - \sin(\pi / n)}.

This constraint also places each tube against the rocket body. Configurations
with fewer than three tubes, gaps between tubes, or overlapping tubes are
rejected.

Normal Force
------------

The ring-airfoil aspect ratio and its modified form are

.. math::

AR = \frac{2 r_i}{L}, \qquad AR' = \frac{2 AR}{\pi}.

For a rocket reference area :math:`A_{ref} = \pi R^2`, the normal-force
coefficient derivative of the complete tube set is

.. math::

C_{N_\alpha} =
\frac{n}{A_{ref}}
2 \left(\frac{AR'}{1 + AR'}\right) \pi^2 r_i L.

RocketPy applies this derivative symmetrically for positive and negative
angles of attack and caps the magnitude at 20 degrees:

.. math::

C_N(\alpha) = C_{N_\alpha}
\operatorname{clip}\left(\alpha, -20^\circ, 20^\circ\right).

Center of Pressure
------------------

For Mach numbers up to 0.5, the center of pressure is placed at the quarter
chord:

.. math::

x_{CP} = \frac{L}{4}.

The position is measured from the tube leading edge. OpenRocket moves this
position with Mach number above Mach 0.5; RocketPy does not yet implement that
correction. Simulations that exceed Mach 0.5 should use aerodynamic data from a
higher-fidelity source instead of this fixed-CP model.

Model Limits
------------

The tube-fin surface contributes normal force and the corresponding pitch and
yaw moments about its center of pressure. It does not calculate:

- friction or pressure drag from the tubes;
- roll forcing or damping;
- side-force or yaw behavior for asymmetric tube layouts;
- tube cant; or
- aerodynamic corrections for separated or overlapping tubes.

Represent tube-fin drag in the rocket's power-on and power-off drag curves.
Use :class:`rocketpy.GenericSurface` when measured, wind-tunnel, or CFD
coefficients are available outside the limits above.

References
----------

.. [1] Ribner, H. S. "The Ring Airfoil in Nonaxial Flow." *Journal of the
Aeronautical Sciences*, 14(9), 529--530, 1947.
3 changes: 2 additions & 1 deletion docs/technical/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,10 @@ in their code.
Equations of Motion v1 <equations_of_motion_v1.rst>
Elliptical Fins <aerodynamics/elliptical_fins.rst>
Individual Fin <aerodynamics/individual_fins.rst>
Tube Fins <aerodynamics/tube_fins.rst>
Roll Moment <aerodynamics/roll_equations.rst>
Sensitivity Analysis <sensitivity.rst>
References <references.rst>

This section is still a work in progress, however, and not everything is documented yet.
If you have any questions, please contact the maintainers of RocketPy.
If you have any questions, please contact the maintainers of RocketPy.
41 changes: 35 additions & 6 deletions docs/user/aerodynamics/surfaces.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,11 @@ Aerodynamic Surfaces
This page provides an overview of the aerodynamic surfaces available in
RocketPy and explains how they connect to the rocket's simulation.

RocketPy models the aerodynamic forces and moments generated by three types
of surfaces: **nose cones**, **fins**, and **tails**. Each surface is
defined by its geometric parameters and, optionally, by an airfoil profile.
The aerodynamic coefficients are computed internally using the Barrowman
method and are used during the flight simulation to evaluate the rocket's
stability and control.
RocketPy models the aerodynamic forces and moments generated by **nose
cones**, **planar fins**, **tube fins**, and **tails**. Each surface is defined
by its geometric parameters. Planar fins can also use a measured airfoil lift
curve. The resulting aerodynamic coefficients are used during the flight
simulation to evaluate the rocket's stability and control.

.. seealso::

Expand Down Expand Up @@ -112,6 +111,10 @@ RocketPy distinguishes between two levels of fin definition:
separately. Individual fins are useful for canards, asymmetric
configurations, or when you need fine-grained control.

- **Tube-fin sets** (:class:`rocketpy.TubeFins`): This class defines a
symmetric ring of cylindrical fins. Its normal-force derivative follows
Ribner's ring-airfoil model rather than the planar-fin Barrowman equations.

.. seealso::

For the mathematical model of individual fins, including the moment
Expand Down Expand Up @@ -178,6 +181,31 @@ Parameters:
- ``coordinates``: A list of ``(x, y)`` tuples defining the fin shape
in the fin coordinate frame.

Tube Fins
~~~~~~~~~

Tube fins are defined by the number and length of the tubes, their inner and
outer radii, and the rocket-body radius at the mounting position. Add them to a
rocket with :meth:`rocketpy.Rocket.add_tube_fins` or create a
:class:`rocketpy.TubeFins` object and pass it to
:meth:`rocketpy.Rocket.add_surfaces`.

The current implementation is a subsonic normal-force model with these
limits:

- The center of pressure is fixed at one quarter of the tube length, measured
from the leading edge. Use the model only through Mach 0.5.
- Lift is capped at an absolute angle of attack of 20 degrees.
- At least three uncanted tubes must be distributed evenly around the body.
Every tube must touch the body and both adjacent tubes. Separated and
overlapping tube layouts are rejected.
- The model does not calculate tube-fin friction drag, pressure drag, roll,
side force, or yaw. Include tube-fin drag in the rocket's power-on and
power-off drag curves.

For the equations and geometry constraint, see
:doc:`Tube Fin Aerodynamics </technical/aerodynamics/tube_fins>`.

Common Fin Set Parameters
-------------------------

Expand Down Expand Up @@ -260,6 +288,7 @@ Fins can be added to a rocket using the ``Rocket`` class methods:
- :meth:`rocketpy.Rocket.add_trapezoidal_fins`
- :meth:`rocketpy.Rocket.add_elliptical_fins`
- :meth:`rocketpy.Rocket.add_free_form_fins`
- :meth:`rocketpy.Rocket.add_tube_fins`
- :meth:`rocketpy.Rocket.add_surfaces` (for individual fins)

Tail
Expand Down
1 change: 1 addition & 0 deletions rocketpy/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
Tail,
TrapezoidalFin,
TrapezoidalFins,
TubeFins,
)
from .sensitivity import SensitivityModel
from .sensors import Accelerometer, Barometer, GnssReceiver, Gyroscope
Expand Down
35 changes: 35 additions & 0 deletions rocketpy/plots/aero_surface_plots.py
Original file line number Diff line number Diff line change
Expand Up @@ -842,6 +842,41 @@ def draw(self, *, filename=None):
show_or_save_plot(filename)


class _TubeFinsPlots(_AeroSurfacePlots):
"""Class that contains all tube-fin plots."""

def draw(self, *, filename=None):
"""Draw a side-view envelope of the tube-fin set."""
axial, radial = self.aero_surface.shape_vec
_, ax = plt.subplots()

ax.plot(axial, radial, color="#A60628", label="Tube-fin envelope")
ax.plot(axial, -radial, color="#A60628")
ax.plot(
[0, self.aero_surface.length],
[0, 0],
color="#7A68A6",
linestyle="--",
label="Rocket centerline",
)

cp_point = (self.aero_surface.cpz, 0)
ax.scatter(*cp_point, label="Center of Pressure", color="red", zorder=10)
ax.scatter(*cp_point, facecolors="none", edgecolors="red", s=300, zorder=10)

limit = self.aero_surface.rocket_radius + 2 * self.aero_surface.outer_radius
ax.set_xlim(-0.02 * self.aero_surface.length, 1.02 * self.aero_surface.length)
ax.set_ylim(-1.05 * limit, 1.05 * limit)
ax.set_aspect("equal")
ax.set_xlabel("Length (m)")
ax.set_ylabel("Radius (m)")
ax.set_title("Tube Fin Set Side-View Envelope")
ax.grid(True, linestyle="--", linewidth=0.5)
ax.legend(bbox_to_anchor=(1.05, 1.0), loc="upper left")
plt.tight_layout()
show_or_save_plot(filename)


class _TailPlots(_AeroSurfacePlots):
"""Class that contains all tail plots."""

Expand Down
15 changes: 15 additions & 0 deletions rocketpy/prints/aero_surface_prints.py
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,21 @@ class _FreeFormFinPrints(_FinPrints):
"""Class that contains all free form fins prints."""


class _TubeFinsPrints(_AeroSurfacePrints):
"""Class that contains all tube-fin prints."""

def geometry(self):
"""Print the geometric information of the tube-fin set."""
print("Geometric information of the tube-fin set:")
print("------------------------------------------")
print(f"Number of tubes: {self.aero_surface.n}")
print(f"Tube length: {self.aero_surface.length:.3f} m")
print(f"Inner tube radius: {self.aero_surface.inner_radius:.3f} m")
print(f"Outer tube radius: {self.aero_surface.outer_radius:.3f} m")
print(f"Reference rocket radius: {self.aero_surface.rocket_radius:.3f} m")
print(f"Ring-airfoil aspect ratio: {self.aero_surface.aspect_ratio:.3f}\n")


class _TailPrints(_AeroSurfacePrints):
"""Class that contains all tail prints."""

Expand Down
1 change: 1 addition & 0 deletions rocketpy/rocket/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
Tail,
TrapezoidalFin,
TrapezoidalFins,
TubeFins,
)
from rocketpy.rocket.components import Components
from rocketpy.rocket.parachute import Parachute
Expand Down
1 change: 1 addition & 0 deletions rocketpy/rocket/aero_surface/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,4 @@
from rocketpy.rocket.aero_surface.nose_cone import NoseCone
from rocketpy.rocket.aero_surface.rail_buttons import RailButtons
from rocketpy.rocket.aero_surface.tail import Tail
from rocketpy.rocket.aero_surface.tube_fins import TubeFins
Loading
Loading