turbo_moc.geometry.blade module#
turbo_moc.geometry.blade – stator blade parametrization built on top of a MOC nozzle wall contour (the suction side).
Ported from opt_framework/test_2/01_geometry_parametrization/1.1_stator_axial.py, refactored into turbo_moc.design_nozzle()’s “plain args in, flat JSON-safe dict out” pattern: parametrize_stator_blade(…) takes the nozzle wall contour (x, y in meters, matching design_nozzle()’s wall_final schema) plus blade parameters (previously hardcoded module globals in the original script), and returns a flat dict (all lengths in mm, matching the original script’s working units) – no solver/UI objects. The geometry helpers below are a line-for-line port of the original script’s module-level functions.
export_blade_step(…) is kept separate/optional since it depends on cadquery, which the rest of turbo_moc does not otherwise require.
- turbo_moc.geometry.blade.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.blade.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.blade.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.blade.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.blade.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.blade.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.blade.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.