@@ -732,32 +732,45 @@ def _finite_float(value: Any, what: str) -> float:
732732 return f
733733
734734
735- def _windio_rotor_angles (cone_value : Any , uptilt_value : Any ) -> tuple [float , float ]:
736- """Convert the WindIO precone and shaft tilt to radians, resolving the
737- rad/deg ambiguity once for the pair.
735+ def _windio_rotor_angles (
736+ cone_value : Any , uptilt_value : Any , units : str = "auto"
737+ ) -> tuple [float , float ]:
738+ """Convert the WindIO precone and shaft tilt to radians.
738739
739740 The WindIO v2 turbine schema annotates ``hub.cone_angle`` and drivetrain
740741 ``uptilt`` as degrees, but the IEA reference ontologies store radians
741- (IEA-22 ``cone_angle`` 0.0698 = 4 deg, ``uptilt`` 0.1047 = 6 deg). A file
742- uses one convention throughout, so decide it once from the *larger* of the
743- two magnitudes rather than per value: a degrees file reliably carries a
744- several-degree shaft tilt (magnitude well above any radians precone /
745- tilt, which stay under ~0.3 rad), whereas judging each value alone would
746- misread a small schema-conforming degrees angle such as 0.25 deg as
747- 0.25 rad. If the larger magnitude exceeds 0.5 the pair is degrees;
748- otherwise radians. Anything above 90 is non-physical either way and is
749- rejected (issue #130 review).
742+ (IEA-22 ``cone_angle`` 0.0698 = 4 deg, ``uptilt`` 0.1047 = 6 deg).
743+ ``units`` selects how to reconcile that:
744+
745+ - ``"rad"`` / ``"deg"`` — take the file at its word (no guessing).
746+ - ``"auto"`` (default) — a file uses one convention throughout, so decide
747+ it once from the *larger* of the two magnitudes: a degrees file reliably
748+ carries a several-degree shaft tilt whose magnitude dwarfs any radians
749+ precone / tilt (which stay under ~0.3 rad), so the larger angle
750+ disambiguates the pair even when the cone is a fraction of a degree.
751+ Larger magnitude over 0.5 means degrees, otherwise radians. The
752+ residual blind spot is a degrees file whose angles are *both* below
753+ ~0.5 deg (e.g. a zero-tilt turbine with a sub-degree precone); it reads
754+ as radians. Pass ``units="deg"`` for such a file (issue #130 review).
755+
756+ Anything above 90 (in the resolved unit) is non-physical and rejected.
750757 """
751758 cone = _finite_float (cone_value , "hub cone_angle" )
752759 uptilt = _finite_float (uptilt_value , "nacelle.drivetrain.uptilt" )
753- biggest = max (abs (cone ), abs (uptilt ))
754- if biggest > 90.0 :
760+ if units not in ("auto" , "rad" , "deg" ):
761+ raise ValueError (
762+ f"angle_units must be 'auto', 'rad' or 'deg'; got { units !r} ."
763+ )
764+ if units == "auto" :
765+ biggest = max (abs (cone ), abs (uptilt ))
766+ units = "deg" if biggest > 0.5 else "rad"
767+ if max (abs (cone ), abs (uptilt )) > 90.0 :
755768 raise ValueError (
756769 f"rotor precone / shaft tilt (cone_angle={ cone !r} , uptilt="
757- f"{ uptilt !r} ) is non-physical as either radians or degrees; "
758- f"expected values well under 90 deg."
770+ f"{ uptilt !r} ) is non-physical in { units } ; expected values well "
771+ f"under 90 deg."
759772 )
760- if biggest > 0.5 : # degrees: a real shaft tilt dwarfs any radians angle
773+ if units == "deg" :
761774 return float (np .radians (cone )), float (np .radians (uptilt ))
762775 return cone , uptilt
763776
@@ -980,6 +993,7 @@ def read_windio_rna(
980993 component_hub : str = "hub" ,
981994 component_nacelle : str = "nacelle" ,
982995 component_blade : str = "blade" ,
996+ angle_units : str = "auto" ,
983997) -> TipMassProps :
984998 """Lump the WindIO rotor-nacelle assembly into a tower-top ``TipMassProps``.
985999
@@ -1024,6 +1038,12 @@ def read_windio_rna(
10241038 goes beyond the ElastoDyn deck path's bare point-mass lumping, which the
10251039 WindIO ontology's per-station blade mass makes possible.
10261040
1041+ ``angle_units`` selects how ``cone_angle`` / ``uptilt`` are read. The
1042+ WindIO v2 schema annotates them as degrees, but the IEA reference files
1043+ store radians; ``"auto"`` (default) disambiguates by magnitude (see
1044+ :func:`_windio_rotor_angles`), while ``"rad"`` / ``"deg"`` take the file
1045+ at its word for the rare all-sub-degree case ``"auto"`` cannot resolve.
1046+
10271047 Requires the optional ``[windio]`` extra (PyYAML).
10281048 """
10291049 from pybmodes .io .bmi import TipMassProps
@@ -1112,7 +1132,9 @@ def _nac_geom(key: str) -> float:
11121132 )
11131133 if hub_r < 0.0 :
11141134 raise ValueError (f"hub diameter must be non-negative; got { hub_diam !r} ." )
1115- cone_ang , uptilt = _windio_rotor_angles (hub .get ("cone_angle" , 0.0 ), uptilt_raw )
1135+ cone_ang , uptilt = _windio_rotor_angles (
1136+ hub .get ("cone_angle" , 0.0 ), uptilt_raw , angle_units
1137+ )
11161138 cone_cos = float (np .cos (cone_ang ))
11171139 cone_sin = float (np .sin (cone_ang ))
11181140
0 commit comments