Skip to content

Commit 9029679

Browse files
committed
fix: include prebend and sweep in the rotor polar lever
The rotor polar moment took the section lever from the z span alone, so a swept or prebent blade was undercounted: the mass integration follows the full 3-D reference-axis arc, but the lever ignored the in-plane tangential sweep and the prebend projection. Resolve each section onto the rotor axes instead: in-plane radial r = hub_r + z*cos(cone) - x*sin(cone), tangential t = y, axial a = z*sin(cone) + x*cos(cone). The polar lever is then the full in-plane distance from the shaft axis r**2 + t**2, and the axial term carries the prebend projection too. A straight blade (x = y = 0) is unchanged.
1 parent 1d89bf1 commit 9029679

2 files changed

Lines changed: 44 additions & 11 deletions

File tree

src/pybmodes/io/windio.py

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -843,9 +843,11 @@ def _blade_span_mass_inertia(
843843
(prebend / sweep) are optional and default to zero (a straight span).
844844
845845
Returns the single-blade mass, the polar second moment about the rotor
846-
axis ``∫ (dm/ds) · r(s)² ds`` (radial ``r = hub_r + z·cone_cos``), the
847-
axial second moment ``∫ (dm/ds) · a(s)² ds`` (axial offset
848-
``a = z·cone_sin``), and the axial first moment ``∫ (dm/ds) · a(s) ds``.
846+
axis ``∫ (dm/ds) · (r² + t²) ds`` (in-plane radial
847+
``r = hub_r + z·cone_cos − x·cone_sin`` and tangential ``t = y`` so
848+
prebend / sweep are included), the axial second moment
849+
``∫ (dm/ds) · a(s)² ds`` (axial offset ``a = z·cone_sin + x·cone_cos``),
850+
and the axial first moment ``∫ (dm/ds) · a(s) ds``.
849851
The caller builds the rotor's polar inertia from the second radial
850852
moment, the coned transverse (diametral) inertia from the radial and
851853
axial second moments, and places the rotor body at its coned centre of
@@ -922,14 +924,21 @@ def _blade_span_mass_inertia(
922924
seg = np.linalg.norm(np.diff(xyz, axis=0), axis=1)
923925
s = np.concatenate([[0.0], np.cumsum(seg)])
924926
mass = _trapezoid(mpl, s)
925-
# The spanwise position resolves into a radial distance from the rotor
926-
# axis (hub radius + span*cos(cone)) and, for a coned rotor, an axial
927-
# offset along the shaft (span*sin(cone)). The radial part gives the
928-
# polar second moment; the axial part adds to the transverse moment.
929-
span = coords[2]
930-
radial = hub_r + span * cone_cos
931-
axial = span * cone_sin
932-
polar_second_moment = _second_moment(mpl, radial, s)
927+
# Resolve each section onto the rotor axes (r = in-plane radial from the
928+
# shaft, t = in-plane tangential, a = along the shaft). The reference axis
929+
# gives z spanwise, x prebend (flapwise, out of the rotor plane) and y
930+
# sweep (edgewise, in the rotor plane). Coning rotates the span/prebend
931+
# (radial/axial) plane by the cone angle about the edgewise axis; sweep is
932+
# unaffected. The polar lever is the in-plane distance from the shaft axis
933+
# ``r² + t²`` (so sweep and prebend are not dropped, issue #130); the
934+
# axial offset feeds the coned transverse term.
935+
x_off, y_off, span = coords[0], coords[1], coords[2]
936+
radial = hub_r + span * cone_cos - x_off * cone_sin
937+
tangential = y_off
938+
axial = span * cone_sin + x_off * cone_cos
939+
polar_second_moment = _second_moment(mpl, radial, s) + _second_moment(
940+
mpl, tangential, s
941+
)
933942
axial_second_moment = _second_moment(mpl, axial, s)
934943
axial_first_moment = _first_moment(mpl, axial, s)
935944
return mass, polar_second_moment, axial_second_moment, axial_first_moment

tests/test_windio_rna.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,30 @@ def _mk(cone: float):
200200
assert coned.mass == pytest.approx(flat.mass)
201201

202202

203+
def test_rna_rotor_inertia_includes_sweep(tmp_path: pathlib.Path) -> None:
204+
"""Sweep (reference_axis.y) sets an in-plane tangential distance from the
205+
shaft axis, so it adds ``y²`` to the rotor polar lever. A constant
206+
tangential offset leaves the span arc length (and thus the blade mass)
207+
unchanged but raises the rotor inertia; a lever built from the z span
208+
alone would drop it (Codex review on #130)."""
209+
pytest.importorskip("yaml")
210+
from pybmodes.io.windio import read_windio_rna
211+
212+
def _mk(y_off: float):
213+
o = _base_ontology()
214+
ra = o["components"]["blade"]["elastic_properties_mb"]["six_x_six"][
215+
"reference_axis"
216+
]
217+
ra["y"] = {"grid": [0.0, 1.0], "values": [y_off, y_off]}
218+
return read_windio_rna(_write(o, tmp_path, f"sw_{y_off}.yaml"))
219+
220+
straight = _mk(0.0)
221+
swept = _mk(10.0)
222+
assert swept.mass == pytest.approx(straight.mass) # constant offset, same arc
223+
assert swept.ixx > straight.ixx
224+
assert swept.izz > straight.izz
225+
226+
203227
def test_rna_coned_cm_shift_with_uptilt(tmp_path: pathlib.Path) -> None:
204228
"""A coned rotor's CM sits off the hub apex along the (tilted) shaft, so
205229
with uptilt the tower-top vertical CM shifts by m_blades·offset·sin(uptilt)

0 commit comments

Comments
 (0)