Skip to content

Commit bdfede8

Browse files
committed
Working billowing
1 parent 62de74d commit bdfede8

2 files changed

Lines changed: 74 additions & 109 deletions

File tree

data/TUDELFT_V3_KITE/vsm_settings_coarse.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ wings:
5050
spanwise_direction: [0.0, 1.0, 0.0] # Unit vector defining wingspan direction
5151
remove_nan: true # Remove NaN values from polar data
5252
use_prior_polar: true # Use previously computed polars if available
53-
billowing_angle: 5 # [deg] Half-angle of circular arc billowing
53+
billowing_percentage: 5 # [%] Percentage of trailing edge length reduction due to billowing
5454

5555
# Numerical method settings and convergence criteria
5656
solver_settings:

src/wing_geometry.jl

Lines changed: 73 additions & 108 deletions
Original file line numberDiff line numberDiff line change
@@ -815,10 +815,7 @@ function refine!(wing::AbstractWing; recompute_mapping=true, sort_sections=true)
815815
reuse_aero_data
816816
)
817817
elseif wing.spanwise_distribution == BILLOWING
818-
refine_mesh_with_billowing!(
819-
wing, LE, TE, aero_model, aero_data;
820-
reuse_aero_data
821-
)
818+
refine_mesh_with_billowing!(wing; reuse_aero_data)
822819
else
823820
throw(ArgumentError("Unsupported spanwise panel distribution: $(wing.spanwise_distribution)"))
824821
end
@@ -1118,14 +1115,18 @@ function refine_mesh_for_linear_cosine_distribution!(
11181115
end
11191116

11201117
"""
1121-
refine_mesh_by_splitting_provided_sections!(wing::AbstractWing)
1118+
refine_mesh_by_splitting_provided_sections!(wing; reuse_aero_data, billowing_angle)
11221119
11231120
Refine mesh by splitting provided sections into desired number of panels.
11241121
1125-
Returns:
1126-
Vector{Section}: Refined sections
1122+
When `billowing_angle > 0`, applies circular arc TE displacement to intermediate
1123+
sections within each rib pair (simulating fabric billowing between ribs).
11271124
"""
1128-
function refine_mesh_by_splitting_provided_sections!(wing::AbstractWing; reuse_aero_data::Bool=false)
1125+
function refine_mesh_by_splitting_provided_sections!(
1126+
wing::AbstractWing;
1127+
reuse_aero_data::Bool=false,
1128+
billowing_angle::Float64=0.0
1129+
)
11291130
n_sections_provided = length(wing.unrefined_sections)
11301131
n_panels_provided = n_sections_provided - 1
11311132
n_panels_desired = wing.n_panels
@@ -1197,6 +1198,7 @@ function refine_mesh_by_splitting_provided_sections!(wing::AbstractWing; reuse_a
11971198
]
11981199

11991200
# Generate sections for this pair
1201+
start_idx = idx
12001202
idx = refine_mesh_for_linear_cosine_distribution!(
12011203
wing,
12021204
idx,
@@ -1209,6 +1211,61 @@ function refine_mesh_by_splitting_provided_sections!(wing::AbstractWing; reuse_a
12091211
endpoints=false,
12101212
reuse_aero_data
12111213
)
1214+
1215+
# Apply billowing arc to the just-created sections
1216+
if billowing_angle > 0 && idx > start_idx
1217+
LE_1 = LE[left_section_index]
1218+
TE_1 = TE[left_section_index]
1219+
LE_2 = LE[left_section_index + 1]
1220+
TE_2 = TE[left_section_index + 1]
1221+
1222+
chord_1 = TE_1 - LE_1
1223+
chord_2 = TE_2 - LE_2
1224+
x_hat = normalize((chord_1 + chord_2) / 2)
1225+
y_hat = normalize(LE_1 - LE_2)
1226+
z_hat = cross(x_hat, y_hat)
1227+
z_norm = norm(z_hat)
1228+
if z_norm > 1e-10
1229+
z_hat = z_hat / z_norm
1230+
1231+
TE_mid = (TE_1 + TE_2) / 2
1232+
d = abs(dot(TE_1 - TE_2, y_hat))
1233+
span_len = norm(LE_1 - LE_2)
1234+
chord_len_1 = norm(chord_1)
1235+
chord_len_2 = norm(chord_2)
1236+
1237+
if d > 1e-12
1238+
R = (d / 2) / sin(billowing_angle)
1239+
h = (d / 2) / tan(billowing_angle)
1240+
1241+
for si in start_idx:(idx - 1)
1242+
sec = wing.refined_sections[si]
1243+
t = dot(sec.LE_point - LE_2,
1244+
y_hat) / span_len
1245+
1246+
arc_y = -R * sin(
1247+
billowing_angle * (1 - 2t))
1248+
arc_z = -h + R * cos(
1249+
billowing_angle * (1 - 2t))
1250+
arc_TE = TE_mid +
1251+
arc_y * y_hat + arc_z * z_hat
1252+
1253+
chord_dir = arc_TE - sec.LE_point
1254+
chord_dir_len = norm(chord_dir)
1255+
if chord_dir_len > 1e-12
1256+
target_chord = (
1257+
t * chord_len_1 +
1258+
(1 - t) * chord_len_2)
1259+
sec.TE_point .= (
1260+
sec.LE_point +
1261+
(target_chord /
1262+
chord_dir_len) *
1263+
chord_dir)
1264+
end
1265+
end
1266+
end
1267+
end
1268+
end
12121269
end
12131270
end
12141271

@@ -1277,115 +1334,23 @@ function billowing_angle_from_percentage(percentage::Real)
12771334
end
12781335

12791336
"""
1280-
refine_mesh_with_billowing!(wing, LE, TE, aero_model, aero_data; reuse_aero_data)
1337+
refine_mesh_with_billowing!(wing; reuse_aero_data)
12811338
1282-
Refine wing mesh using linear spacing, then apply circular arc billowing to TE positions.
1339+
Refine wing mesh using SPLIT_PROVIDED spacing with circular arc billowing.
12831340
12841341
Between each pair of unrefined (rib) sections, the trailing edge follows a circular arc
12851342
that bulges in the direction perpendicular to the chord and span (simulating fabric
12861343
billowing between ribs on a ram-air kite). The leading edge stays linearly interpolated.
12871344
12881345
The arc half-angle is `wing.billowing_angle` (0 = straight line, π/2 = semicircle).
1289-
"""
1290-
function refine_mesh_with_billowing!(wing, LE, TE, aero_model, aero_data;
1291-
reuse_aero_data::Bool=false)
1292-
n_sections = wing.n_panels + 1
12931346
1294-
# Step 1: Do standard LINEAR refinement first
1295-
refine_mesh_for_linear_cosine_distribution!(
1296-
wing, 1, LINEAR, n_sections, LE, TE, aero_model, aero_data;
1297-
reuse_aero_data
1347+
Delegates to [`refine_mesh_by_splitting_provided_sections!`](@ref).
1348+
"""
1349+
function refine_mesh_with_billowing!(wing; reuse_aero_data::Bool=false)
1350+
refine_mesh_by_splitting_provided_sections!(
1351+
wing; reuse_aero_data,
1352+
billowing_angle=wing.billowing_angle
12981353
)
1299-
1300-
angle = wing.billowing_angle
1301-
if angle 0.0
1302-
return nothing # No billowing needed
1303-
end
1304-
1305-
n_unrefined = length(wing.unrefined_sections)
1306-
1307-
# Collect unrefined LE positions for rib detection
1308-
unrefined_LEs = [s.LE_point for s in wing.unrefined_sections]
1309-
1310-
# Iterate over each pair of adjacent unrefined sections (ribs)
1311-
for rib_idx in 1:(n_unrefined - 1)
1312-
LE_1 = wing.unrefined_sections[rib_idx].LE_point
1313-
TE_1 = wing.unrefined_sections[rib_idx].TE_point
1314-
LE_2 = wing.unrefined_sections[rib_idx + 1].LE_point
1315-
TE_2 = wing.unrefined_sections[rib_idx + 1].TE_point
1316-
1317-
# Build local coordinate frame
1318-
chord_1 = TE_1 - LE_1
1319-
chord_2 = TE_2 - LE_2
1320-
x_hat = (chord_1 + chord_2) / 2
1321-
x_hat = x_hat / max(norm(x_hat), 1e-12)
1322-
1323-
y_vec = LE_1 - LE_2 # spanwise: from section 2 toward section 1
1324-
y_hat = y_vec / max(norm(y_vec), 1e-12)
1325-
1326-
z_hat = cross(x_hat, y_hat)
1327-
z_hat = z_hat / max(norm(z_hat), 1e-12)
1328-
1329-
# Project unrefined TEs into the local yz plane
1330-
TE_mid = (TE_1 + TE_2) / 2
1331-
y1 = dot(TE_1 - TE_mid, y_hat)
1332-
y2 = dot(TE_2 - TE_mid, y_hat)
1333-
d = abs(y1 - y2) # chord length of the arc in yz plane
1334-
1335-
if d < 1e-12
1336-
continue # degenerate: both TEs at same spanwise position
1337-
end
1338-
1339-
# Circular arc parameters
1340-
R = (d / 2) / sin(angle)
1341-
h = (d / 2) / tan(angle) # center offset from midpoint along z
1342-
1343-
# Spanwise extent of this rib pair (for checking membership)
1344-
span_len = dot(LE_1 - LE_2, y_hat)
1345-
1346-
# Rib chord lengths for interpolation
1347-
chord_len_1 = norm(chord_1)
1348-
chord_len_2 = norm(chord_2)
1349-
1350-
# Displace TE of refined sections that fall between this rib pair
1351-
for sec in wing.refined_sections
1352-
# Skip sections at any unrefined rib position
1353-
is_rib = any(norm(sec.LE_point - ule) < 0.01 for ule in unrefined_LEs)
1354-
if is_rib
1355-
continue
1356-
end
1357-
1358-
# Project refined section's LE onto the spanwise direction
1359-
frac_vec = sec.LE_point - LE_2
1360-
t = dot(frac_vec, y_hat) / span_len
1361-
1362-
# Skip sections outside this rib pair
1363-
if t < 0.01 || t > 0.99
1364-
continue
1365-
end
1366-
1367-
# Arc parametrization: t=1 -> TE_1, t=0 -> TE_2
1368-
# In local coords centered at TE midpoint:
1369-
# y(t) = -R * sin(angle * (1 - 2t))
1370-
# z(t) = -h + R * cos(angle * (1 - 2t))
1371-
arc_y = -R * sin(angle * (1 - 2t))
1372-
arc_z = -h + R * cos(angle * (1 - 2t))
1373-
1374-
# Transform arc position back to global coords
1375-
arc_TE = TE_mid + arc_y * y_hat + arc_z * z_hat
1376-
1377-
# Preserve chord length: use the arc TE to define the chord
1378-
# direction, but enforce the interpolated chord length
1379-
chord_dir = arc_TE - sec.LE_point
1380-
chord_dir_len = norm(chord_dir)
1381-
if chord_dir_len > 1e-12
1382-
target_chord = t * chord_len_1 + (1 - t) * chord_len_2
1383-
sec.TE_point .= sec.LE_point + (target_chord / chord_dir_len) * chord_dir
1384-
end
1385-
end
1386-
end
1387-
1388-
return nothing
13891354
end
13901355

13911356
"""

0 commit comments

Comments
 (0)