In this article, we'll explore the power of programmatic generative design by creating an open-source web application for turbofan model generation using CadQuery, Streamlit, and SplineCloud. Generative modeling enables us to define not just geometric objects but families of geometries based on the predefined rules, making the design process to be more focused on formalizing the knowledge about building components instead of creating single instances, bringing the new level of automation to the engineering process.
Traditional parametric 3D modeling software relies on delivering interactive tools for constructing geometric models of components, leaving limited options for modifications - any change requires manual adjustments of the construction history, which usually leads to conflicts in construction history and hours spent on manual rework whenever a new change comes into play. In contrast, generative modeling defines geometry through variables, constraints, and design rules encoded in algorithms. This allows to automate the generation of the whole components using user inputs regarding main design parameters, avoiding the need for manual adjustments and micro control of the shapes. This becomes possible either by using application programming interfaces of the advanced CAD systems or by using specialized libraries with interfaces to CAD kernel, which allow writing the code for geometry construction logic.
For a turbofan, a programmable CAD approach allows us to:
CadQuery is a Python-based CAD scripting library used for intuitive and feature-rich modeling. Instead of relying on GUI-based CAD tools, we define our geometry using code.
To build the turbofan, we define multiple airfoil sections that shape the vanes. These sections vary in chord length, twist angle, and offset, creating an optimized aerodynamic structure. Here’s how we define an airfoil section:
class AirfoilSection:
def __init__(self, profile_curve, chord, offset, twist_angle):
self.profile_curve = profile_curve
self.chord = chord
self.offset = cq.Vector(0, 0, offset)
self.twist_axis = cq.Vector(0, 0, 1)
self.twist_angle = twist_angle
def build_sketch(self):
edge = cq.Edge(BRepBuilderAPI_MakeEdge(self.bspl).Edge())
return cq.Sketch().edge(edge).close().assemble()
Each section is then lofted to form a 3D vane:
@cache
def build_vane(self):
return cq.Workplane().placeSketch(
*(section.build_sketch().moved(section.location) for section in self.sections)
).loft()
By dynamically adjusting parameters such as twist angle and chord ratio, we can generate a wide range of turbofan designs fitted for different applications.
SplineCloud allows building complex spline interpolation curves, that become accessible over API for reuse in code. This makes it handy for aerodynamic modeling. Instead of manually defining splines interpolating airfoil profile points, we can use SplineCloud to approximate airfoil contour for the collections of different airfoils and reuse spline curves in code over SplineCloud API.
For example, given an airfoil profile curve stored as a parametric spline, we can easily reconstruct it in Python using SplineCloud:
import splinecloud_scipy as sc
spline = sc.load_spline(curve_id)
@cached_property
def spline(self):
return sc.load_spline(self.profile_curve) if isinstance(self.profile_curve, str) else self.profile_curve
This allows us to easily integrate aerodynamic shapes into our model, ensuring that the generated turbofan model follows the design inputs on the airfoil shape and the resulting geometry has a class-A smooth surface with no overfitted areas and breaks in regularity.
Once individual vanes are defined, we need to arrange them around the central hub. The following code ensures correct placement and spacing:
@cache
def build_turbofan(self):
vanes = [
self.build_vane().translate((0, 0, self.vane_offset)).rotate((0, 1, 0), i * 360 / self.vanes_count)
for i in range(self.vanes_count)
]
hub = cq.Workplane("XZ").circle(self.hub_diameter / 2).extrude(self.hub_height)
for vane in vanes:
hub = hub.union(vane)
return hub.faces("XZ").circle(self.center_hole_diameter / 2).cutThruAll()
This method allows the turbofan model to remain scalable - modifying the vane count or hub diameter automatically updates the model.
To make the process of building the generative model interactive and user-friendly, we integrate it with Streamlit, allowing real-time adjustments and visualization. This approach enables engineers and designers to experiment with different airfoil shapes, blade configurations and export 3D models in STL or STEP formats for further analysis or manufacturing.
The Streamlit-based UI enables users to define key turbofan parameters interactively. The interface to the generative model consists of:
The turbofan is constructed using three airfoil sections:
We define these sections and create the full turbofan geometry:
sections = [
AirfoilSection(_root_curve, root_chord_ratio, 0, root_twist),
AirfoilSection(_middle_curve, middle_chord_ratio, middle_offset_distance, middle_twist),
AirfoilSection(_tip_curve, tip_chord_ratio, middle_offset_distance + tip_offset_distance, tip_twist)
]
turbofan = Turbofan(
sections=sections,
vanes_count=vanes_count,
center_hole_diameter=center_hole_diameter,
hub_diameter=hub_diameter
).build_turbofan()
The build_turbofan() method generates the CAD model, ready for export or visualization.
The final turbofan model is displayed in a 3D viewer using PyVista, providing an interactive preview inside the Streamlit app:
mesh = pv.read(file_path_stl)
plotter = pv.Plotter(window_size=[500, 500])
plotter.add_mesh(mesh)
plotter.view_isometric()
plotter.set_background("#0e1117")
stpyvista.stv(plotter, key=f"turbofan_{datetime.now()}")
The application allows users to export the generated turbofan 3D model in either STL or STEP formats. A function is defined to handle the export process:
def generate_temp_file(model, file_format, tessellation):
"""Generates a temporary file in STL or STEP format."""
file_suffix = ".stl" if file_format.lower() == "stl" else ".step"
export_type = cq.exporters.ExportTypes.STL if file_format.lower() == "stl" else cq.exporters.ExportTypes.STEP
with tempfile.NamedTemporaryFile(delete=False, suffix=file_suffix) as tmpfile:
cq.exporters.export(model, tmpfile.name, exportType=export_type, tolerance=getattr(Tessellation, tessellation.upper()).value)
return tmpfile.name
Streamlit's download buttons allow users to save the exported files directly from the UI:
with open(file_path_stl, 'rb') as stl_file:
st.download_button(label="Download STL File", data=stl_file, file_name="turbofan.stl", mime="application/vnd.ms-pki.stl")
By combining CadQuery, Streamlit, and SplineCloud, we can create a web application for generating turbofan models based on high-level user inputs, like vane airfoils, vane twist, the number of vanes, and hub size.
The advantages of this generative design approach include:
Whether you're designing turbomachinery for aerospace or experimenting with fluid dynamics, the programmatic generative modeling approach provides a way to automate model generation, reducing the rework process and minimizing errors. The integration of SplineCloud further enhances the fidelity of complex aerodynamic surfaces, making it a must-have tool for accurate modeling using a programmable approach.
Want to try it yourself? Check out the full code and start exploring the capabilities of generative design today!
References:
Link to the NACA airfoils data repository on SplineCloud: https://splinecloud.com/repository/AlinaNeh/NACA_airfoil_database/
Link to the application repository on GitHub: https://github.com/scorpaena/turbofan
Link to CQ-UAV library: https://github.com/nomad-vagabond/cq-uav/tree/main/cquav/turbofan