This IPython notebook contains examples for generating and rendering the AirCONICS parametric transonic airliner example using the interactive WebServer from PythonOCC-contrib. Parts are generated under their respective headings and rendered collectively in the final cells.
For examples using the pythonocc-core Qt viewer, refer to the airconics examples/core directory
In [ ]:
from airconics import LiftingSurface, Engine, Fuselage
import airconics.AirCONICStools as act
from airconics.Addons.WebServer.TornadoWeb import TornadoWebRenderer
from IPython.display import display
In [ ]:
Propulsion = 1
EngineDia = 2.9
FuselageScaling = [55.902, 55.902, 55.902]
WingScaleFactor = 44.56
WingChordFactor = 1.0
Topology = 1
EngineSpanStation = 0.31
EngineCtrBelowLE = 0.3558
EngineCtrFwdOfLE = 0.9837
Scarf_deg = 3
# Derived Parameters
FuselageHeight = FuselageScaling[2]*0.105
FuselageLength = FuselageScaling[0]
FuselageWidth = FuselageScaling[1]*0.106
WingApex = [0.1748*FuselageLength,0,-0.0523*FuselageHeight]
# Fin:
FinChordFact = 1.01
FinScaleFact = WingScaleFactor/2.032
# TailPlane
TPChordFact = 1.01
TPScaleFact = WingScaleFactor * 0.388
# Engine:
NacelleLength = 1.95*EngineDia
Formulation of lifting surfaces in occ_airconics (and AirCONICS) follows the suggestions in Sobester [1] in which geometry--attached curvilinear functionals are used instead of parameters for shape definition. That is, $G(\textbf{f}, \textbf{X})$, where
and
$$\textbf{X}_i = \left[x_1^i, x_2^i,...\right], \forall i = 1,...m$$as opposed to the conventional $G(\bf{X})$ formulation where the shape $G$ changes in response to changes in design parameters $\textbf{X}$. The functions $f_i$ are defined by:
$Sweep (\epsilon)$
$Chord (\epsilon)$
$Rotation (\epsilon)$
$Twist (\epsilon)$
$Airfoil (\epsilon)$
where $\epsilon$ represents the spanwise coordinate ranging from 0 at the root of the wing to 1 at the tip. Output of the airfoil function uses the airconics.primitives.Airfoil class here, which fits a NURBS curve to airfoil coordinates.
The following code demonstrates construction of a wing using built in examples for a transonic airliner wing and tailplane (below).
In [ ]:
# Import all example functional definitions for the Common Research Model (CRM) Wing:
from airconics.examples.wing_example_transonic_airliner import *
# Position of the apex of the wing
P = WingApex
# Class definition
NSeg = 11
ChordFactor = 1
ScaleFactor = 50
# Generate (surface building is done during construction of the class)
Wing = LiftingSurface(P, mySweepAngleFunctionAirliner,
myDihedralFunctionAirliner,
myTwistFunctionAirliner,
myChordFunctionAirliner,
myAirfoilFunctionAirliner,
SegmentNo=NSeg,
ScaleFactor=WingScaleFactor,
ChordFactor=WingChordFactor)
RootChord = Wing.RootChord
# Display
renderer = TornadoWebRenderer()
Wing.Display(renderer)
display(renderer)
In [ ]:
from OCC.gp import gp_Ax1, gp_Pnt, gp_Dir
from airconics.examples.tailplane_example_transonic_airliner import *
# Position of the apex of the fin
P = [36.98-0.49-0.02, 0.0, 2.395-0.141]
SegmentNo = 10
Fin = liftingsurface.LiftingSurface(P, mySweepAngleFunctionFin,
myDihedralFunctionFin,
myTwistFunctionFin,
myChordFunctionFin,
myAirfoilFunctionFin,
SegmentNo=SegmentNo,
ChordFactor=FinChordFact,
ScaleFactor=FinScaleFact)
# Create the rotation axis centered at the apex point in the x direction
RotAxis = gp_Ax1(gp_Pnt(*P), gp_Dir(1, 0, 0))
Fin.RotateComponents(RotAxis, 90)
# Position of the apex of the tailplane
P = [43, 0.000, 1.633+0.02]
SegmentNo = 100
ChordFactor = 1.01
ScaleFactor = 17.3
TP = liftingsurface.LiftingSurface(P, mySweepAngleFunctionTP,
myDihedralFunctionTP,
myTwistFunctionTP,
myChordFunctionTP,
myAirfoilFunctionTP,
SegmentNo=SegmentNo,
ChordFactor=TPChordFact,
ScaleFactor=TPScaleFact)
# Display
renderer = TornadoWebRenderer()
Fin.Display(renderer)
TP.Display(renderer)
display(renderer)
Fuselage shapes are created following the parameterisation used in Sobester [2]. That is, the outer mould line (OML) is split into a Nose, Central and Tail section, the length of which is described on input to Fuselage class as a percentage of the total length. Rib curves are then formed by fitting a NURBS curve to the intersection points of sectional planar cuts and the guide curves of the extremeties of the OML e.g. Port, top and bottom curves. The OML is fitted in occ_airconics using the Open CASCADE ThruSections loft.
In [ ]:
NoseLengthRatio=0.182
TailLengthRatio=0.293
Fus = Fuselage(NoseLengthRatio, TailLengthRatio, Scaling=FuselageScaling,
NoseCoordinates=[0., 0., 0],
CylindricalMidSection=False,
Maxi_attempt=5)
# Display
renderer = TornadoWebRenderer()
Fus.Display(renderer)
display(renderer)
In [ ]:
# Export (can be commented out)
# act.export_STEPFile([Fus['OML']], 'fuselage.stp')
In [ ]:
# WingBodyFairing - A simple ellipsoid:
from airconics.base import AirconicsShape
WTBFZ = RootChord*0.009 #787: 0.2
WTBFheight = 1.8*0.1212*RootChord #787:2.7
WTBFwidth = 1.08*FuselageWidth
WTBFXCentre = WingApex[0] + RootChord/2.0 + RootChord*0.1297 # 787: 23.8
WTBFlength = 1.167*RootChord #787:26
WBF_shape = act.make_ellipsoid([WTBFXCentre, 0, WTBFZ], WTBFlength, WTBFwidth, WTBFheight)
WBF = AirconicsShape(components={'WBF': WBF_shape})
First, obtain the wing section and chord at which the engine will be fitted, then fit then engine. The default inputs to the Engine class produce a turbofan engine with Nacelle similar to that of the RR Trent 1000 / GEnx and its pylon (Currently a flat plate only).
In [ ]:
EngineSection, HChord = act.CutSect(Wing['Surface'], EngineSpanStation)
Chord = HChord.GetObject()
CEP = Chord.EndPoint()
Centreloc = [CEP.X()-EngineCtrFwdOfLE*NacelleLength,
CEP.Y(),
CEP.Z()-EngineCtrBelowLE*NacelleLength]
eng = Engine(HChord,
CentreLocation=Centreloc,
ScarfAngle=Scarf_deg,
HighlightRadius=EngineDia/2.0,
MeanNacelleLength=NacelleLength)
# Display
renderer = TornadoWebRenderer()
eng.Display(renderer)
display(renderer)
In [ ]:
# Trim the inboard section of the main wing:
CutCirc = act.make_circle3pt([0,WTBFwidth/4.,-45], [0,WTBFwidth/4.,45], [90,WTBFwidth/4.,0])
CutCircDisk = act.PlanarSurf(CutCirc)
Wing['Surface'] = act.TrimShapebyPlane(Wing['Surface'], CutCircDisk)
#Mirror the main wing and tailplane using class methods:
Wing2 = Wing.MirrorComponents(plane='xz')
TP2 = TP.MirrorComponents(plane='xz')
eng2 = eng.MirrorComponents(plane='xz')
In [ ]:
renderer = TornadoWebRenderer()
# display all entities:
# Fuselage and wing-body fairing
Fus.Display(renderer)
WBF.Display(renderer)
# #The Wings:
Wing.Display(renderer)
Wing2.Display(renderer)
#The Tailplane:
TP.Display(renderer)
TP2.Display(renderer)
#The Fin:
Fin.Display(renderer)
#The Engines:
eng.Display(renderer)
eng2.Display(renderer)
# Finally show the renderer
display(renderer)
This is a work in progress towards a topologically flexible model based on the tree-type definition described in Sobester [1]. Note the geometry is not currently defined by the tree however, the tree is simply stored as a result of adding components - this is for demonstration only, and the process is yet to be automated.
The $xz$ mirror plane is included in this representation, between central objects (Fuselage, Fin) and the mirrored objects (Tail Plane, Wing, Engine).
In [ ]:
from airconics import Topology
from IPython.display import Image
import pydot
In [ ]:
topo_renderer = TornadoWebRenderer()
topo = Topology()
# Note: no checks are done on the validity of the tree yet,
topo.AddPart(Fus, 'Fuselage', 3)
topo.AddPart(Fin, 'Fin', 0)
# Need to add a mirror plane here, arity zero
from OCC.gp import gp_Ax2, gp_Dir, gp_Pnt
xz_pln = gp_Ax2(gp_Pnt(0, 0, 0), gp_Dir(0, 1, 0))
topo.AddPart(xz_pln, 'Mirror', 0)
# These are the mirrored entities, with their arities
topo.AddPart(TP, 'Tail Plane', 0)
topo.AddPart(Wing, 'Wing', 1)
topo.AddPart(eng, 'Engine', 0)
# print the Topology (resembles a LISP tree)
print(topo)
# Create the graph with pydot
graph = pydot.graph_from_dot_data(topo.export_graphviz())
Image(graph.create_png())
In [ ]:
# This line will mirror geometry 'under' (added after) the mirror plane
topo.Build()
topo.Display(topo_renderer)
display(topo_renderer)
Let's try some further tests to the topology class representation using some other examples. For now, these are empty geometries, and inputs to the Fuselage, LiftingSurface and Engine classes are not yet included in the Topology tree.
In [ ]:
# Setup
# Create mock components, without generating any geometry
fus = Fuselage(construct_geometry=False)
engine = Engine(construct_geometry=False)
fin = LiftingSurface(construct_geometry=False)
mirror_pln = gp_Ax2()
wing = LiftingSurface(construct_geometry=False)
Vfin = LiftingSurface(construct_geometry=False)
# For now we must manually add parts and affinities
topo = Topology()
topo.AddPart(fus, 'Fuselage', 4)
topo.AddPart(engine, 'engine', 0)
topo.AddPart(fin, 'fin', 0)
topo.AddPart(mirror_pln, 'mirror_pln', 0)
topo.AddPart(wing, 'wing', 0)
topo.AddPart(Vfin, 'V-Fin', 0)
print(topo)
graph = pydot.graph_from_dot_data(topo.export_graphviz())
Image(graph.create_png())
In [ ]:
# Setup
# Create mock components, without generating any geometry
fus = Fuselage(construct_geometry=False)
mirror_pln = gp_Ax2()
engine = Engine(construct_geometry=False)
wing = LiftingSurface(construct_geometry=False)
tailplane = LiftingSurface(construct_geometry=False)
tail_fin = LiftingSurface(construct_geometry=False)
topo = Topology()
topo.AddPart(fus, 'Fuselage', 3)
topo.AddPart(mirror_pln, 'mirror', 0)
topo.AddPart(engine, 'powerplant', 0)
topo.AddPart(tailplane, 'Tailplane', 1)
topo.AddPart(tail_fin, "Tail fin", 0)
topo.AddPart(wing, "wing", 0)
print(topo)
graph = pydot.graph_from_dot_data(topo.export_graphviz())
Image(graph.create_png())
In [ ]:
# Setup
# Create mock components, without generating any geometry
fus = Fuselage(construct_geometry=False)
mirror_pln = gp_Ax2()
engine = Engine(construct_geometry=False)
wing_in = LiftingSurface(construct_geometry=False)
tailplane = LiftingSurface(construct_geometry=False)
pod = Fuselage(construct_geometry=False)
finup = LiftingSurface(construct_geometry=False)
findown = LiftingSurface(construct_geometry=False)
wing_out = LiftingSurface(construct_geometry=False)
topo = Topology()
topo.AddPart(fus, 'Fuselage', 3)
topo.AddPart(mirror_pln, 'mirror', 0)
topo.AddPart(engine, 'powerplant', 0)
topo.AddPart(wing, "wing", 0)
topo.AddPart(wing_in, "TP/inbbd wing", 1)
topo.AddPart(pod, 'Pod/tail boom', 3)
topo.AddPart(wing_out, "outbd wing", 0)
topo.AddPart(finup, "Fin (up)", 0)
topo.AddPart(findown, "Fin (down)", 0)
for node in topo._Tree:
print(node)
graph = pydot.graph_from_dot_data(topo.export_graphviz())
Image(graph.create_png())
[1] Sobester, A., “Four Suggestions for Better Parametric Geometries,” 10th AIAA Multidisciplinary Design Optimization Conference, AIAA SciTech, American Institute of Aeronautics and Astronautics, jan 2014.
[2] Sobester, A., “Self-Designing Parametric Geometries,” 56th AIAA/ASCE/AH- S/ASC Structures, Structural Dynamics, and Materials Conference, AIAA SciTech, American Institute of Aeronautics and Astronautics, jan 2015.
In [ ]: