turbo_moc.geometry package#
Blade geometry and coordinate mappings.
The canonical design pipeline lives in geometry_2d, geometry_3d, meridional_channel and linear_cascade. Shared spline/section fitting remains in blade and spline_tool. Historical public names below resolve to preserved deprecated implementations so existing clients and the comparison app run.
- class turbo_moc.geometry.BSplineTool(degree=3)[source]#
Bases:
objectMethods
backward(data_points, num_cp)Generates control points from data points using Least Squares.
backward_fixed(data_points, num_cp)forward(control_points, knots[, num_points])Generates the curve from control points (Forward Method).
reparameterize_equidistant(data_points[, ...])Re-samples data points so they are physically equidistant along the arc length.
- backward(data_points, num_cp)[source]#
Generates control points from data points using Least Squares. Uses a Uniform Knot Sequence for equispaced control variables.
- turbo_moc.geometry.apply_conformal_mapping(chordwise, pitchwise, x1, y1, r1, r2, c_axial, theta0=0.0)[source]#
Log-spiral conformal map from the unrolled (chordwise, pitchwise) frame to Cartesian (X, Y) on an annulus: r=r1 at chordwise=x1, r=r2 at chordwise=x1+c_axial, theta=theta0 at pitchwise=y1.
- turbo_moc.geometry.build_meridional_view(stator_axial_chord, stator_r_hub_in, stator_r_hub_out, stator_r_tip_in, stator_r_tip_out, rotor_axial_chord, rotor_r_hub_in, rotor_r_hub_out, rotor_r_tip_in, rotor_r_tip_out, gap)[source]#
Hub/tip line coordinates for a stator row (x in [0, stator_axial_chord]) followed by a rotor row (x in [stator_axial_chord + gap, … + rotor_axial_chord]) – for turbo_moc.plotly/mpl’s plot_meridional_view.
- Parameters:
- stator_axial_chord, rotor_axial_chordfloat
Each row’s own axial (flow-direction) extent – e.g. the stator blade’s own chordwise span (mm), the rotor’s own “chord” (CSTAR, already dimensionalized if it was designed with r_star).
- stator_r_hub_in/out, stator_r_tip_in/out, rotor_r_hub_in/out,
- rotor_r_tip_in/outfloat
Hub/tip radii at each row’s own inlet/outlet.
- gapfloat
Axial (row-to-row) spacing between the stator’s own trailing edge and the rotor’s own leading edge.
- turbo_moc.geometry.compute_throat_sonic_state(nozzle_result: dict) tuple[float, float][source]#
(rho_star, a_star) at the Phase 1 nozzle’s throat/sonic point (wall_final index 0), using the design’s own fluid_name + local (rho, T). DmassT_INPUTS, not PT_INPUTS: the throat state can sit arbitrarily close to the saturation curve (e.g. wet-steam/two-phase designs), where (p, T) alone is degenerate – CoolProp raises “Saturation pressure … is within 1e-4% of given p” because phase can’t be resolved from pressure and temperature at saturation. rho and T together are never degenerate this way.
- turbo_moc.geometry.conformal_length_scale(chord, x1, r1, r2, c_axial)[source]#
Exact local length-magnification factor of the log-spiral conformal map at a given chordwise coordinate.
w = X + iY, as a function of u = chordwise + i*pitchwise, is w = C * exp(a*u) with a = log(r2/r1)/c_axial and C a constant complex number – a holomorphic (complex-differentiable) function of u. For any holomorphic map, the local length-magnification factor is exactly
|dw/du| = |a| * |w| = |a| * r(the map’s own r-formula, at this chordwise coordinate; the absolute value matters here – this module’s own _reference_point_and_scale anchors x1 at the chord MAXIMUM, which makes c_axial = min(chord)-max(chord), hence a, negative by convention, even though the magnification itself is always positive). A length measured in the 2D cascade plane at this chordwise location is scaled by exactly this factor once mapped onto the annulus – not an approximation, and no numerical search needed.The conformal map preserves ANGLES (that’s what “conformal” means), not lengths – e.g. the 2D-plane throat_opening is not the actual radial-passage throat width; see mapped_throat_width.
- turbo_moc.geometry.consistent_radial_wrap(blade_data, r1, r2_target, source='stator')[source]#
Blade count and outer radius that keep the designed 2D passage.
Under the log-spiral map one 2D pitch p turns by
|a| p, a = ln(r2/r1) / c_axial, at every radius; the copies are laid out 2*pi/N apart. Both agree only forN = 2*pi / (|a| p)(implied_n_blades_radial, which is exactly this, independent of where it is evaluated). So N is the nearest integer to that count at (r1, r2_target), and r2 is then moved to the radius where it is exact:|ln(r2/r1)| = 2*pi |c_axial| / (N p), same side of r1 as r2_target
Returns n_blades, r2, and the target’s non-integer implied count.
- turbo_moc.geometry.export_annular_blade_step(blade_data, r_hub, r_shroud, face_path, solid_path=None, n_blades=1, flare='pitch_scale', n_span=13, theta0=0.0, source='stator', z_offset=0.0)[source]#
Export a TRUE annular 3D blade solid: the profile is wrapped circumferentially at every span station from r_hub to r_shroud (see turbo_moc.geometry.annular’s module docstring for the wrap math), then lofted through all n_span sections and capped at the hub/shroud ends (_close_loft_solid) into one watertight body – unlike
export_flared_blade_step()’s flat 2-wire loft between parallel planes, this actually curves around the machine axis. Always exports a SINGLE blade; n_blades only affects the pitch-scale flare physics (pitch = 2*pi*r/n_blades convention) and is not used to multiply the exported solid.Requires cadquery (not a core turbo_moc dependency – import kept local so the rest of turbo_moc.geometry works without it installed).
Unlike export_flared_blade_step’s hub_face (flat, so a genuine planar Face), the hub SECTION here is a non-planar 3D curve – it curves around the cylinder AND varies axially along the profile at the same time – so cq.Face.makeFromWires (planar faces only) can’t build a face from it. face_path therefore gets the hub WIRE itself (a valid STEP curve, still useful as a reference/sketch import), not a face.
- z_offsetfloat
Added to every axial (Z) coordinate – positions this solid in a shared global frame against an already-placed neighbor (e.g. a rotor STEP file), same purpose as turbo_moc.geometry. export_turbogrid_blade’s x_offset. No sign/orientation logic is applied here (unlike that function’s axial_sign) – a STEP solid has no inherent “inlet/outlet” semantics, so z_offset is a plain additive shift; figure out which end of THIS blade (its wrapped curve/trailing_edge z-range) needs to land where before picking a value.
- turbo_moc.geometry.export_annular_blade_stl(blade_data, r_hub, r_shroud, stl_path, n_blades=1, flare='pitch_scale', n_span=13, theta0=0.0, source='stator', z_offset=0.0)[source]#
Export the same watertight annular blade solid as export_annular_blade_step, but as an STL mesh – for CAD-quality (true solid, properly shaded) 3D preview in the app via dash-vtk, which reads triangulated meshes, not BREP/STEP. Requires cadquery.
- turbo_moc.geometry.export_blade_step(blade_data, face_path, solid_path=None, extrude_length=1.0)[source]#
Export the closed blade contour (from parametrize_stator_blade) to STEP: a 2D face and, optionally, an extruded 3D solid.
Requires cadquery (not a core turbo_moc dependency – import kept local so the rest of turbo_moc.geometry works without it installed).
- turbo_moc.geometry.export_flared_blade_step(blade_data, r_hub_in, r_hub_out, r_tip_in, r_tip_out, face_path, solid_path=None, source='stator')[source]#
Export a FLARED 3D blade solid: lofts between a hub-radius copy of the 2D blade profile (at Z=0) and a tip-radius copy (at Z=blade_height) whose pitchwise (tangential) extent is scaled by r_tip_mean/r_hub_mean – pitch = 2*pi*r/Z_blades at constant blade count, so this is the physically-correct scaling for “same blade count, wider pitch at larger radius”, not an arbitrary stretch.
See turbo_moc.geometry.meridional’s module docstring for why this needs no separate axial (chordwise) shift between hub and tip: flare is assumed to share the same axial stations for hub/tip (Anderson et al.’s own convention), so it shows up as radius change (the meridional view, build_meridional_view) and this pitch scaling here – the two share the SAME hub/tip radii inputs, not independently guessed ones.
- Parameters:
- blade_datadict
A stator blade dict (parametrize_stator_blade/_semi’s output, needs “blade_curve”/”trailing_edge”) or a rotor blade dict (design_rotor_vortex_blade’s output, needs “blade”) – pick which via source.
- r_hub_in, r_hub_out, r_tip_in, r_tip_outfloat
Hub/tip radii at this row’s own inlet/outlet (same length units as blade_data, typically mm) – blade_height and the pitch-scale factor are both derived from these, not separate inputs.
- sourcestr
“stator” or “rotor” – which blade_data shape to expect.
- Requires cadquery (not a core turbo_moc dependency – import kept local so
- the rest of turbo_moc.geometry works without it installed).
- turbo_moc.geometry.export_radial_blade_step(blade_data, r1, r2, depth, face_path, solid_path=None, source='stator', theta0=0.0, z_offset=0.0)[source]#
Export a radial-wrapped blade (see _build_radial_blade_solid) as STEP – face_path gets the bottom (z = z_offset) section wire, solid_path gets the watertight extruded solid. Requires cadquery (not a core dependency – import kept local).
- turbo_moc.geometry.export_radial_blade_stl(blade_data, r1, r2, depth, stl_path, source='stator', theta0=0.0, z_offset=0.0)[source]#
Export the same watertight radial blade solid as export_radial_blade_step, but as an STL mesh for CAD-quality 3D preview via dash-vtk. Requires cadquery.
- turbo_moc.geometry.export_turbogrid_blade(blade_data, r_hub, r_shroud, n_blades, output_dir, span_sections=11, hub_shroud_offset_fraction=0.01, inlet_extension_fraction=1.0, outlet_extension_fraction=2.0, x_offset=0.0, flare='pitch_scale', theta0=0.0, source='stator', axis='X', units='MM', te_type='CutOffEnd')[source]#
Export BladeGen.inf + hub.curve + shroud.curve + profile.curve for TurboGrid meshing – the wrapped-annulus counterpart to turbo_moc. geometry.export_annular_blade_step’s STEP solid (same wrap math, turbo_moc.geometry.wrap_blade_annular, same r_hub/r_shroud/flare conventions), but as the plain-text profile-points format TurboGrid itself consumes, with no cadquery dependency.
- Parameters:
- blade_datadict
Stator (parametrize_stator_blade/_semi’s output) or rotor (design_rotor_vortex_blade’s output, already scaled to mm) – pick which via source.
- r_hub, r_shroudfloat
Hub/shroud radii (mm).
- n_bladesint
Blade count – written as “Number of Blade Sets” and used for the pitch-scale flare physics (see wrap_blade_annular).
- output_dirPath
Directory the four files are written into (created if missing).
- span_sectionsint
Number of profile.curve span stations (>= 2).
- hub_shroud_offset_fractionfloat
Inset the first/last profile station this fraction of span in from r_hub/r_shroud – TurboGrid wants near-wall profiles, not sections sitting exactly on the hub/shroud wall.
- inlet_extension_fraction, outlet_extension_fractionfloat
How far hub.curve/shroud.curve extend upstream/downstream of the blade’s own LE/TE, as a fraction of its axial chord (asymmetric by default – more room downstream for wake development). Set the side facing an adjacent row to 0 – extensions belong at the machine’s true inlet/outlet, not padding an internal row interface (see shift_turbogrid_files’s docstring).
- x_offsetfloat
Added to every axial (X) coordinate after the fact – positions this row in a shared global frame against an already-exported neighbor (e.g. a turbodash rotor), equivalent to exporting at 0 then calling shift_turbogrid_files, just in one step.
- flare, theta0see wrap_blade_annular.
- source“stator” or “rotor”.
- axisAxis of Rotation written to BladeGen.inf (“X”, “Y”, or “Z”).
- unitsGeometry Units written to BladeGen.inf.
- te_typeBlade 0 TE treatment written to BladeGen.inf.
- Returns:
- dict
Paths written: {“inf”, “profile”, “hub”, “shroud”}.
- turbo_moc.geometry.extract_axial_passage_2d(blade_data, pitch, face_path, solid_path=None, extrude_length=1.0, source='stator', inlet_chords=1.0, outlet_chords=6.0, bend_margin_inlet=0.15, bend_margin_outlet=0.05)[source]#
One periodic passage domain (a pitch-wide domain, vertical near the inlet/outlet and following the blade-to-blade centerline in between, with the blade solid subtracted out as an internal hole) in the unrolled/axial cascade view, as a STEP face and, optionally, an extruded 3D solid. See module docstring for the domain’s shape and why the stator and rotor get different middle-section constructions.
- Parameters:
- blade_datadict
Stator (parametrize_stator_blade/_semi output – needs “blade_curve”/”trailing_edge”/”suction”/”metal_angle_in”/ “metal_angle_out”/”axial_chord_convergent”/ “axial_chord_divergent”) or rotor (design_rotor_vortex_blade output – needs “blade”/”suction”/”pressure”/”chord”) blade dict. Pick which via source.
- pitchfloat
Blade-to-blade pitch (mm), i.e. the periodic translation. For a stator this IS blade_data[“pitch”]; for a rotor that’s been scaled from its own nondimensional r* (see turbo_moc.app’s rotor_scale_mm), pass the SCALED pitch, and pre-scale blade_data’s own length-valued arrays/fields by the same factor (angles need no scaling).
- face_path, solid_pathstr
Output STEP file paths. solid_path is optional – omit to skip the 3D extrusion and only export the 2D face.
- extrude_lengthfloat
3D solid extrusion depth (mm), only used if solid_path is given.
- sourcestr
“stator” or “rotor” – which raw-surface axis convention to use: stator’s pitch runs along x (pitchwise), rotor’s along y (see turbo_moc/geometry/radial.py’s module docstring on the two blades’ opposite chordwise/pitchwise axis conventions).
- inlet_chords, outlet_chordsfloat
How far the inlet/outlet caps sit upstream of the leading edge / downstream of the trailing edge, each as a multiple of the blade’s own chord (stator:
axial_chord_convergent + axial_chord_divergent; rotor: “chord”) – e.g. the outlet default 6.0 places the outlet six chords downstream, far enough that the outlet boundary condition doesn’t interact with the blade’s own near-field. Decoupled from the stator’s own inlet_chords/ outlet_chords – pass whichever value applies to source. NOTE: for the rotor, max(c_vals) (blade’s own raw chordwise convention) is the OUTLET side, not the inlet (opposite of the stator’s), so internally outlet_chords is applied there and inlet_chords at min(c_vals) – this function handles that swap; callers just pass the physically-correct inlet/outlet values for the source.- bend_margin_inlet, bend_margin_outletfloat
Stator only: where the side walls switch between vertical and staggered, each as a fraction of the blade’s own chordwise extent measured inward from the nearer edge (leading edge for bend_margin_inlet, trailing edge for bend_margin_outlet). Clamped so the vertical sections never cross the LE/TE-angle- lines’ own intersection point. The rotor’s own transition points are the natural chordwise overlap of its own suction surface with the blade’s own true LE/TE (see module docstring) – these two params are unused for it.
- turbo_moc.geometry.extract_radial_passage_2d(blade_data, pitch, r1, r2, face_path, solid_path=None, extrude_length=1.0, source='stator', inlet_chords=1.0, outlet_chords=6.0, bend_margin_inlet=0.15, bend_margin_outlet=0.05, n_vertical=25, n_cap=15)[source]#
Conformally maps the SAME periodic passage domain as extract_axial_passage_2d (see its docstring/module docstring for the construction) onto an annular passage between radii r1/r2, using turbo_moc.geometry.radial’s log-spiral wrap – the same mapping already used to wrap blades onto an annulus for Phase 5. One passage, one blade hole (matching n_blades=1 in wrap_curve_radial terms).
IMPORTANT: r1/r2 land at the BLADE’s own leading/trailing edge – same convention as wrap_blade_radial/wrap_rotor_blade_radial – NOT at the passage domain’s own (extended) inlet/outlet caps. The mapping’s reference point/scale (see turbo_moc.geometry.radial’s _reference_point_and_scale) is derived from the blade’s own contour and then shared with the band’s outer boundary, so the band’s inlet/outlet caps (which sit beyond the blade’s LE/TE by
inlet_chords/outlet_chords) extrapolate naturally to radii outside [r1, r2] via the same log-spiral formula, rather than r1/r2 themselves being squeezed onto the (much wider) extended domain.- Parameters:
- blade_data, pitch, source, inlet_chords, outlet_chords,
- bend_margin_inlet, bend_margin_outletsee extract_axial_passage_2d.
- r1, r2float
Inner/outer radii (same length units as blade_data), reached at the blade’s own leading/trailing edge (not the extended domain caps – see above).
- face_path, solid_path, extrude_lengthsee extract_axial_passage_2d.
- n_vertical, n_capint
How densely to resample the domain’s straight vertical/cap segments before mapping (see _dense_boundary_points) – these become log-spiral arcs, not straight lines, once mapped.
- turbo_moc.geometry.implied_n_blades_radial(blade_data, r1, r2, source='stator')[source]#
The blade count that makes the copy-to-copy spacing (2*pi*r_eval/n_blades) match this blade’s own mapped pitch at the evaluation point (stator: its throat; rotor: its hub reference point, see _eval_reference) – i.e. the n_blades that actually tiles the annulus without overlap or gaps, AT THAT RADIUS (pitch still varies with span under this map, so this is a single representative value, same role as the axial wrap’s own pitch-consistency check, not an exact guarantee everywhere along the blade). A default N_blades chosen far from this will visibly overlap (too large) or leave gaps (too small) in the 3D preview.
- turbo_moc.geometry.mapped_pitch_width(blade_data, r1, r2, source='stator')[source]#
The 2D-plane pitch, rescaled by the exact local conformal length factor at the evaluation point’s chordwise location (the stator’s own throat, or the rotor’s hub reference point – see _eval_reference) – i.e. the actual circumferential footprint ONE mapped blade copy occupies there. Unlike the axial wrap, nothing here guarantees this matches 2*pi*r/n_blades for whatever n_blades you pick (see this module’s docstring and implied_n_blades_radial) – adjacent copies will overlap if n_blades is too large for this footprint.
- turbo_moc.geometry.mapped_throat_width(blade_data, r1, r2, source='stator')[source]#
The 2D-plane throat_opening, rescaled by the EXACT local conformal length factor at the throat’s own (chordwise, pitchwise) location – see conformal_length_scale. Uses the SAME reference (x1, c_axial) the actual wrap (wrap_blade_radial/wrap_rotor_blade_radial) derives from the main blade contour, so the result is consistent with the geometry that actually gets rendered/exported.
- blade_datadict
Stator: needs “blade_curve” ({“x”,”y”}) and “throat_point” ({“x”,”y”}, see parametrize_stator_blade/_semi) and “throat_opening”. Rotor: this tool’s rotor method has no throat_opening/throat_point – not supported here (its sizing is pitch-consistency only to begin with, see turbo_moc.geometry.sizing.size_rotor_from_pitch).
- turbo_moc.geometry.move_control_point_along_normal(blade_data, cp_index, distance, num_curve_points=None)[source]#
Nudge one B-spline control point of a parametrized blade by distance (mm, positive/negative) along its local normal – estimated as the 90-degree rotation of the discrete tangent through its neighboring control points – then re-evaluate the fitted curve from the updated control points.
True click-and-drag isn’t practical in a Plotly/Dash app without heavy custom clientside JS (Plotly’s editable config covers shapes/ annotations, not scatter markers), so this select-a-point-then-nudge workflow is the supported editing mode for turbo_moc.app’s Phase 2.
Moving cp_index 0 or -1 (the fixed leading/trailing anchor points from backward_fixed) will break their exact match to the raw suction/ pressure endpoints – allowed, but usually not what you want.
Returns a new blade_data dict (does not mutate the input): only “control_points” and “blade_curve” change, everything else (pitch, trailing_edge, suction/pressure raw sides, knots, …) is carried over.
- turbo_moc.geometry.parametrize_stator_blade(wall_x, wall_y, metal_angle_in=0.0, metal_angle_out=70.0, r_trailing=0.5, inlet_opening_ratio=1.8, n_cp=30, num_baseline_points=500, num_curve_points=500, mirror=False)[source]#
Build a closed stator blade profile from a MOC nozzle wall contour (used as the divergent-section suction side), following the approach of opt_framework’s 1.1_stator_axial.py.
- Parameters:
- wall_x, wall_yarray_like
Nozzle wall contour, in METERS, matching design_nozzle()’s
wall_final["x"]/["y"](throat at index 0, increasing x downstream). Converted to mm internally – all lengths in the returned dict are in mm, matching the original script’s units.- metal_angle_in, metal_angle_outfloat
Camberline metal angles [deg] at the convergent-section inlet and at the throat (matching the divergent-section wall angle).
- r_trailingfloat
Trailing-edge circle radius [mm].
- inlet_opening_ratiofloat
pitch / inlet_opening (throat pitch to passage-inlet-opening ratio), sets how far upstream the pressure-side Hermite spline reaches.
- n_cpint
Number of B-spline control points fitted to the closed contour.
- num_baseline_points, num_curve_pointsint
Resampling / evaluation resolution for the B-spline fit.
- mirrorbool
Flip the blade’s concavity (which side is suction/pressure) by reflecting the finished shape across the chordwise (y) axis – i.e. negating every “x” (pitchwise) coordinate in the returned dict, post-construction. NOT the same as negating metal_angle_out: that feeds a turning-angle profile AND a separate frame rotation, both driven by metal_angle_out, and negating it does not cancel cleanly – it produces a self-crossing, invalid shape (confirmed by direct comparison, not assumed). Reflecting the finished 2D points is the only method that’s actually been verified to give a clean mirror image.
- Returns:
- dict
Flat, JSON-safe dict:
suction/pressure(raw sides, mm),blade_curve(closed B-spline-fitted contour, mm),control_points(n_cp x 2, mm),trailing_edge(mm),pitch,throat_opening,inlet_opening(mm), and the input parameters echoed back.
- turbo_moc.geometry.parametrize_stator_blade_semi(wall_x, wall_y, metal_angle_in=0.0, metal_angle_out=70.0, r_trailing=0.5, inlet_opening_ratio=2.5, n_cp=30, num_baseline_points=500, num_curve_points=500, trailing_edge_num_points=15, mirror=False)[source]#
Build a closed stator blade profile from a MOC nozzle wall contour, following opt_framework’s stator_parametrization_semi_nozzle.py – a lighter-weight alternative to parametrize_stator_blade() that only needs the suction-side wall, not a mirrored copy of it:
Pressure side: a single circular arc between two points (near the leading and trailing edge), not a mirrored-wall-plus-Hermite-spline bridge – so pitch/inlet_opening/the arc’s own geometry set the passage shape directly, rather than the wall’s own divergence.
Control points: a plain (non-fixed-endpoint) least-squares B-spline fit (BSplineTool.backward), not backward_fixed – the fitted curve is not forced through the exact suction/pressure start/end points.
Same “plain args in, flat JSON-safe dict out” pattern, same units convention (wall_x/wall_y in METERS, everything in the returned dict in mm) as parametrize_stator_blade() – see its docstring for the parameter meanings shared between the two. inlet_opening_ratio defaults to 2.5 here (vs. 1.8 for the mirrored-wall variant) and trailing_edge_num_points to 15 (vs. 50), matching the two original scripts’ own defaults.
- turbo_moc.geometry.read_turbogrid_x_extent(hub_curve_path)[source]#
(x_min, x_max) of a hub.curve/shroud.curve file – format-agnostic (works on ANY BladeGen-format export, not just this module’s own, since the X column convention is shared; see this module’s docstring). Use this on a row’s hub.curve where the facing side’s extension fraction was exported as 0 to get that row’s TRUE blade/domain edge there, for computing the axial offset to hand to shift_turbogrid_files.
- turbo_moc.geometry.rescale_rotor_blade(rotor_data: dict, k: float) dict[source]#
Pure post-hoc multiplication of design_rotor_vortex_blade’s output by k – only blade.x/y, pitch, chord: the fields the 3D wrap/export pipeline (wrap_blade_annular’s source=”rotor” branch reads ONLY blade_data[“blade”]) and this module’s own results table actually use. The rotor’s other returned substructures (pressure/suction/mach_lines/ blade_lower/upper) are left out entirely – not needed downstream, and not meaningfully rescaled here. Doesn’t mutate the input.
- turbo_moc.geometry.rescale_stator_blade(blade_data: dict, k: float) dict[source]#
New blade dict (does not mutate input) with every length field (point arrays + scalar lengths + control_points) multiplied by k; angles/counts/booleans/strings/knots are carried over unchanged. Pure post-hoc multiplication of an already-solved reference shape – never re-runs parametrize_stator_blade at a different scale.
- turbo_moc.geometry.rotate_radial_points(x, y, angle_rad)[source]#
Rigid rotation by angle_rad about the origin – lays out the n_blades copies around the annulus after mapping.
- turbo_moc.geometry.shift_turbogrid_files(src_dir, dst_dir, x_offset)[source]#
Copy a BladeGen export (profile.curve + hub.curve + shroud.curve + BladeGen.inf – from THIS module or any other tool writing the same format, e.g. turbodash’s) into dst_dir with every X (axial) coordinate shifted by x_offset. BladeGen.inf has no coordinates, so it’s just copied unchanged.
This is the tool for lining up two independently-exported blade rows (each with its own local axial origin) so they share one global axial frame with no overlap and no gap at their interface – same principle turbodash’s own assembly exporter uses (one additive offset per row), just applied here as a standalone post-export step since the two rows come from different, unconnected design tools. Pick one row to anchor (x_offset=0, leave it as exported) and shift the OTHER by:
x_offset = (anchor_x_max_at_interface + gap) - this_row_x_min_at_interface
where both x_max/x_min are read (read_turbogrid_x_extent) from each row’s hub.curve AFTER re-exporting with that row’s facing-side extension fraction set to 0 – otherwise you’re offsetting by each row’s own inlet/outlet padding instead of its true blade/domain edge, and the rows will overlap or leave an unwanted gap at the interface.
Radius (hub/shroud) continuity at the interface is NOT handled here – shifting coordinates only repositions a row axially. If the two rows’ hub/shroud radii differ at the interface, that’s a design mismatch (fix it in whichever tool designed that row), not a file-shift fix.
- turbo_moc.geometry.size_rotor_from_pitch(rotor_data: dict, r_hub: float, r_shroud: float, n_blades: int) dict[source]#
k derived from pitch consistency alone – unlike the stator, no mass-flow equation is available at the 2D-cascade level for this tool’s rotor method (design_rotor_vortex_blade has no mdot semantics). r_hub/r_shroud are expected to come from the STATOR’s own sizing result (size_stator_mode_a/b): same annulus, axial stage, one through-flow passage – n_blades is the rotor’s own free choice, generally different from the stator’s N.
- turbo_moc.geometry.size_stator_mode_a(blade_data: dict, nozzle_result: dict, mdot: float, n_blades: int, r_hub: float) dict[source]#
Case A (‘cycle-level’): mdot, n_blades, r_hub given; k and H derived in closed form from pitch + mass-flow consistency. No iteration, no re-invocation of design_nozzle at a different scale.
- turbo_moc.geometry.size_stator_mode_b(blade_data: dict, nozzle_result: dict, r_hub: float, H: float, n_blades: int, mdot: float) dict[source]#
Case B (‘full meanline’): r_hub, H, n_blades, mdot ALL given (e.g. from a turbodash YAML sizing). Computes k two independent ways and reports their mismatch as a diagnostic – a large residual means the 2D blade SHAPE (metal_angle_out/solidity) needs redesigning, not just rescaling. scaled_blade is built from k_pitch: pitch consistency is the hard geometric constraint (N_blades copies of the blade must tile the annulus exactly), so it is treated as ground truth for the exported shape; the mass-flow side is the diagnostic that tells you whether that shape is also aerodynamically consistent.
- turbo_moc.geometry.wrap_blade_annular(blade_data, r_hub, r_shroud, n_blades=1, flare='pitch_scale', n_span=13, theta0=0.0, source='stator', n_preview=None)[source]#
Wrap a Phase-2-style stator blade (parametrize_stator_blade/_semi’s output) or a Phase-3-style rotor blade (design_rotor_vortex_blade’s output) onto a true annulus from r_hub to r_shroud: the blade curves circumferentially at every span station, not just a flat hub/tip loft.
- Parameters:
- blade_datadict
Stator: needs “blade_curve” and “trailing_edge” ({“x”, “y”} each, mm). Rotor: needs “blade” ({“x”, “y”}).
- r_hub, r_shroudfloat
Hub/shroud radii (same length units as blade_data, mm).
- n_bladesint
TRUE number of blades around the full annulus – sets the angular spacing (2*pi/n_blades) between copies. Changing this always changes how tightly packed the returned copies are, even if only a few of them are actually returned (see n_preview).
- flarestr
“pitch_scale” (pitch grows with radius, matching export_flared_blade_step’s convention) or “constant” (profile unchanged across span).
- n_spanint
Number of span stations from r_hub to r_shroud (>= 2).
- theta0float
Angular offset [rad] of the blade’s own reference (index-0) copy.
- sourcestr
“stator” or “rotor” – which blade_data shape to expect.
- n_previewint or None
Cap on how many of the n_blades copies to actually compute/return (e.g. for a responsive Mesh3d preview) – purely a rendering cap, NEVER changes d_theta/the physical spacing above. None (default) returns all n_blades copies.
- Returns:
- dict
“curve”: {“r”, “x”, “y”, “z”} each shape (n_drawn, n_span, n_pts) – the main blade contour (blade_curve for stator, blade for rotor). “trailing_edge”: same shape, or None for a rotor (its TE is part of the closed “blade” contour already). “r_hub”/”r_shroud”/”n_blades”/”n_span”/”flare”/”units” echoed back (“n_blades” is always the TRUE count, not n_drawn).
- turbo_moc.geometry.wrap_blade_radial(blade_data, r1, r2, n_blades, theta0=0.0)[source]#
Wrap a Phase-2-style stator blade (parametrize_stator_blade/_semi’s output dict) onto an annular passage between radii r1 and r2.
- Parameters:
- blade_datadict
Needs “blade_curve” and “trailing_edge” ({“x”, “y”} each, mm).
- r1, r2float
Inner/outer radii of the annular passage (same length units as blade_data, mm), reached at the blade’s own chordwise extremes.
- n_bladesint
Number of blade copies laid out around the full annulus.
- theta0float
Angular offset [rad] of the first (index-0) blade copy.
- Returns:
- dict
“blades”: list of n_blades {“x”, “y”} closed-contour dicts; “trailing_edges”: matching list for the trailing-edge arc; “r1”/”r2”/”n_blades”/”d_theta” echoed back; “units”.
- turbo_moc.geometry.wrap_curve_radial(x, y, r1, r2, n_blades, theta0=0.0, chordwise_axis='y')[source]#
Wrap ONE closed (or open) curve (x, y) onto an annular passage between radii r1/r2, laid out as n_blades copies spaced 2*pi/n_blades apart. chordwise_axis picks which of the curve’s own axes runs chordwise (the one that maps to radius) – “y” for a stator-convention curve, “x” for a rotor-convention one; see module docstring.
Returns a list of n_blades {“x”, “y”} dicts (the mapped curve, one per copy) plus the reference (x1, y1, c_axial, d_theta) used, for reuse when wrapping a second curve (e.g. a trailing edge) with the SAME reference frame as this one.
- turbo_moc.geometry.wrap_rotor_blade_radial(rotor_data, r1, r2, n_blades, theta0=0.0)[source]#
Wrap a Phase-3-style rotor blade (turbo_moc.rotor.design_rotor_vortex_blade’s output dict) onto an annular passage between radii r1 and r2.
- Parameters:
- rotor_datadict
Needs “blade” ({“x”, “y”}) – the closed LE/TE-rounded blade contour. Its trailing edge is already part of this closed curve (unlike the stator’s, which keeps a separate arc), so there is no separate trailing-edge overlay here. Its units don’t matter (it may well be nondimensional, r*): the map normalises the contour by its own chordwise extent, so a uniformly rescaled blade wraps to exactly the same result.
- r1, r2float
Radii [mm] the blade’s chordwise extremes map to; the result is in the same units.
- n_bladesint
Number of blade copies laid out around the full annulus.
- theta0float
Angular offset [rad] of the first (index-0) blade copy.
- Returns:
- dict
Same shape as wrap_blade_radial’s return, with “trailing_edges” as a list of empty {“x”: [], “y”: []} placeholders (nothing extra to draw – kept for a uniform shape with the stator’s result, so plot_radial_cascade works unchanged for either source).
Submodules#
- turbo_moc.geometry.annular module
- turbo_moc.geometry.blade module
- turbo_moc.geometry.flow_interfaces module
- turbo_moc.geometry.geometry_2d module
apply_domain_shift()assess_clearance()auto_center_flow_domain()blade_contour()blade_reference_camberline()build_geometry()cubic_blend()draw_domain()generate_flow_domain()metal_direction()plot_construction()plot_flow_domain()point_segment_distances()resample_curve()validate_domain_shift()validate_flow_domain_settings()xy()
- turbo_moc.geometry.geometry_3d module
- turbo_moc.geometry.linear_cascade module
- turbo_moc.geometry.meridional module
- turbo_moc.geometry.meridional_channel module
GeometryDefinitionMeridionalChannelMeridionalChannel.characteristic_lengthMeridionalChannel.is_linearMeridionalChannel.map_points()MeridionalChannel.meridional_points()MeridionalChannel.parameter_from_prime()MeridionalChannel.periodic_transform()MeridionalChannel.prime_chord()MeridionalChannel.prime_from_parameter()MeridionalChannel.rotation()MeridionalChannel.section_scale()
axial_geometry()build_section()corresponding_boundary()geometry_from_machine()meridional_line()radial_geometry()scale_blade()scale_flow_domain()span_stations()validate_machine()
- turbo_moc.geometry.moc module
- turbo_moc.geometry.passage module
- turbo_moc.geometry.radial module
- turbo_moc.geometry.rotor_geometry module
- turbo_moc.geometry.sizing module
- turbo_moc.geometry.spline_tool module
- turbo_moc.geometry.turbogrid module