Guided Examples
This section describes examples of how to use VSPGeom to import different types of geometry. It assumes familiarity with basic usage.
DegenGeom Files
OpenVSP geometry
We start by creating a geometry in OpenVSP. Let's use the default wing geometry and write out a CSV DegenGeom file using the tab Analysis > DegenGeom.
Import to Julia
We shall now use VSPGeom to import the DegenGeom file into Julia. We make use of the readDegenGeom
function to read the file. We would also like to know the output that we obtained from readDegenGeom
.
using VSPGeom
comp = readDegenGeom("wing.csv")
println(typeof(comp))
println(size(comp))
Vector{VSPComponent}
(2,)
The various geometry components in the DegenGeom file will now be available to us in the variable comp
which appears to be a Vector of VSPComponent
objects. This specific vector has two elements in it. Before we start using this object, let's inspect it using the dump
function provided by Julia.
julia> dump(VSPComponent)
VSPComponent <: Any name::String type::String GeomID::String SurfNdx::Int64 MainSurfNdx::Int64 SymCopyNdx::Int64 surface_node::DataFrames.DataFrame surface_face::DataFrames.DataFrame plate::DataFrames.DataFrame stick_node::DataFrames.DataFrame stick_face::DataFrames.DataFrame point::DataFrames.DataFrame
Besides several variables like type
, name
, GeomID
etc. that describe the geometry, we notice a collection of DataFrame
objects. These are the degenGeom representations of the geometry in the form of DataFrame
objects. Let's inspect the PLATE
type of representation for the first component using the describe
function provided by the DataFrames
package.
julia> VSPGeom.DataFrames.describe(comp[1].plate)
14×7 DataFrame Row │ variable mean min median max nmissing ⋯ │ Symbol Union… Union… Union… Union… Int64 ⋯ ─────┼────────────────────────────────────────────────────────────────────────── 1 │ x 3.83639 0.0 4.16566 6.19615 0 ⋯ 2 │ y 4.5 0.0 4.5 9.0 0 3 │ z 0.0 0.0 0.0 0.0 0 4 │ zCamber 1.83628e-16 0.0 7.58942e-17 9.4438e-16 0 5 │ t 0.126253 0.0 0.0987493 0.399672 0 ⋯ 6 │ nCamberx 2.22461e-14 -3.69162e-14 0.0 4.0181e-13 0 7 │ nCambery 0.0 0.0 0.0 0.0 0 8 │ nCamberz 0.882353 0.0 1.0 1.0 0 9 │ u 1.5 1.0 1.5 2.0 0 ⋯ 10 │ wTop 3.0 2.0 3.0 4.0 0 11 │ wBot 1.0 0.0 1.0 2.0 0 12 │ xxCamber 102 13 │ xyCamber 102 ⋯ 14 │ xzCamber 102 1 column omitted
The PLATE
degenGeom representation for component 1 has the variables x
, y
, z
, zCamber
and so on inside it. These correspond to the right half of the OpenVSP Wing geometry. Component 2 represents the left half of the wing that lies in the negative Y-plane indicated by the negative values for y
in its dataframe or table. We can also display a summary of only the y
values for each of the components using the cols
option.
julia> println(VSPGeom.DataFrames.describe(comp[1].plate, cols="y"))
1×7 DataFrame Row │ variable mean min median max nmissing eltype │ Symbol Float64 Float64 Float64 Float64 Int64 DataType ─────┼────────────────────────────────────────────────────────────────── 1 │ y 4.5 0.0 4.5 9.0 0 Float64
julia> println(VSPGeom.DataFrames.describe(comp[2].plate, cols="y"))
1×7 DataFrame Row │ variable mean min median max nmissing eltype │ Symbol Float64 Float64 Float64 Float64 Int64 DataType ─────┼────────────────────────────────────────────────────────────────── 1 │ y -4.5 -9.0 -4.5 0.0 0 Float64
DegenGeom variables: Accessing and restructuring as mesh
Variables in the DegenGeom may now be accessed like fields in a struct as shown below. Let's create a surface plot of the camber surface for the left and right halves of the wing geometry. We shall use the function degenGeomSize
to obtain the mesh size and restructure the coordinate variables into a surface mesh.
x1 = comp[1].plate.x + comp[1].plate.zCamber .* comp[1].plate.nCamberx;
y1 = comp[1].plate.y + comp[1].plate.zCamber .* comp[1].plate.nCambery;
z1 = comp[1].plate.z + comp[1].plate.zCamber .* comp[1].plate.nCamberz;
x2 = comp[2].plate.x + comp[2].plate.zCamber .* comp[2].plate.nCamberx;
y2 = comp[2].plate.y + comp[2].plate.zCamber .* comp[2].plate.nCambery;
z2 = comp[2].plate.z + comp[2].plate.zCamber .* comp[2].plate.nCamberz;
# Reshape right wing to a mesh
nx, ny = degenGeomSize(comp[1].plate)
xr = reshape(x1, (nx, ny))
yr = reshape(y1, (nx, ny))
zr = reshape(z1, (nx, ny))
# Reshape left wing to a mesh
nx, ny = degenGeomSize(comp[2].plate)
xl = reshape(x2, (nx, ny))
yl = reshape(y2, (nx, ny))
zl = reshape(z2, (nx, ny))
using Plots
surface(xl, yl, zl, zlims=(-4, 4),
color=:blue, label="Left wing", colorbar=false)
surface!(xr, yr, zr,
color=:red, label="Right wing", colorbar=false,
camera=(20, 30), aspect_ratio=1, proj_type=:persp)
STL Files
OpenVSP geometry
In addition to the DegenGeom file format, OpenVSP has the capability to generate unstructured triangular element meshes of geometry and write out ASCII STL mesh files. These files may contain a single solid or multiple named solids. Opting for the "Tagged Multi Solid File (non standard)" option during mesh export enables the ability to manipulate each component geometry individually.
Import to Julia
We shall use the readSTL
function in VSPGeom to import the geometry from the STL file.
using VSPGeom
geom = readSTL("aircraft.stl")
println(typeof(geom))
println(size(geom))
Vector{TriMesh}
(3,)
Similar to the readDegenGeom
function, readSTL
also returns an array of TriMesh
geometry objects.
Accessing STL mesh variables
The vertices of each cell are stored using the connectivity information in the TriMesh
object. A unique list of vertices are populated in TriMesh.points
. TriMesh.cells
stores the vertices of each cell using indices. Each index refers to the corresponding point in TriMesh.points
. The indices start from 0
as prescribed in the STL standard. The vertices and normals of cells in the TriMesh
object may be accessed as shown below.
ncells = geom[1].ncells
println("No. of cells = $ncells")
# An arbitrary cell
n = 5
println("Normal of cell $n:")
println(geom[1].normals[n])
vtxs = getVertices(geom[1], n)
println("3 vertices of cell $n:")
println(vtxs[1])
println(vtxs[2])
println(vtxs[3])
No. of cells = 10788
Normal of cell 5:
[-0.55963106524, 0.58701913843, -0.58499709567]
3 vertices of cell 5:
[0.0, 0.0, 0.0]
[0.072867418691, 0.069467702964, 0.0]
[0.072516092201, -1.48961804e-17, -0.069371725476]
STL Connectivity information
When an STL file is read, VSPGeom
also populates the connectivity information for each of the cells. This information is stored in TriMesh.cells
as integer indices. For example, the vertex indices for, say the 14th cell, can be displayed as shown below.
geom[1].cells[14]
3-element Vector{Int64}:
37
38
39
By default, the indices start from 1, which is conventional for arrays in Julia. However, this may be switched to zero-based numbering using the setZeroBased!
function as shown below. This provision is useful if the connectivity indices require to be written to a format that uses zero-numbering like the VTK format.
setZeroBased!(geom[1]; value=true)
geom[1].cells[14]
3-element Vector{Int64}:
36
37
38
The getVertices
function will return the right vertices no matter which convention is used for the index numbering.
Writing out a VTK geometry file
VSPGeom provides the convenience function getVTKElements
that enables easy writing out of VTK files using the WriteVTK.jl
package. An example using this function is provided below.
points, cells = getVTKElements(geom[1])
VSPGeom.WriteVTK.vtk_grid("fuselage", points, cells) do vtk
# This creates a sample scalar field dataset with random numbers
vtk["myfield"] = rand(geom[1].ncells)
end
1-element Vector{String}:
"fuselage.vtu"
A screenshot of this VTK file visualized in Paraview is shown below.