Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@
`test/solver/solver_test_wing.yaml` wing at 26.6° it stopped 3.6% below `LOOP`'s
peak circulation and reported `FAILURE`, and now lands on the same distribution
with a fixed-point residual at machine precision.
- `plot_slices_3d` in audit mode no longer crashes when a generated deflection
`.dat` holds no finite coordinates (an all-`NaN` contour from a deflection the
2D solver converged at no angle): it skips that overlay and warns, naming the
file and whether it was blank or missing.

## VortexStepMethod v5.0.0 2026-09-07

Expand Down
26 changes: 16 additions & 10 deletions ext/VortexStepMethodMakieExt.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1613,7 +1613,7 @@ function generated_slices(out_dir, delta, fit_pts)
n = length(rows)
deg = round(float(delta); digits=1)
tag = "_d" * (deg == round(deg) ? string(Int(deg)) : string(deg)) * ".dat"
missing_deltas = String[]
skipped_deltas = String[]
slices = map(1:n) do i
id = rows[i][1]
tangent = normalize(les[min(i + 1, n)] .- les[max(i - 1, 1)])
Expand All @@ -1626,21 +1626,26 @@ function generated_slices(out_dir, delta, fit_pts)
def3d = nothing
if !iszero(delta)
dpath = joinpath(out_dir, "airfoils", "$(id)$(tag)")
if isfile(dpath)
xd, yd = AirfoilAero.read_dat_coordinates(dpath)
found = isfile(dpath)
xd, yd = found ? AirfoilAero.read_dat_coordinates(dpath) :
(Float64[], Float64[])
if isempty(xd)
push!(skipped_deltas, basename(dpath) *
(found ? ": no finite coordinates" : ": no such file"))
else
def3d = map_airfoil_3d(les[i], tes[i], tangent, xd, yd)
d2 = (; d2..., def=Point2f.(xd, yd), def_kulfan=fit_pts(xd, yd))
else
push!(missing_deltas, basename(dpath))
end
end
(; centroid=Point3f((les[i] .+ tes[i]) ./ 2), label_y=les[i][2],
cloud3d=map_airfoil_3d(les[i], tes[i], tangent, xr, yr),
wrap3d=map_airfoil_3d(les[i], tes[i], tangent, xw, yw), def3d, d2)
end
isempty(missing_deltas) ||
@warn "No generated .dat for delta=$(delta)° ($(join(missing_deltas, ", ")));" *
" generated deflections are named airfoils/<i>_d<degrees>.dat."
isempty(skipped_deltas) ||
@warn "Skipping the delta=$(delta)° overlay for" *
" $(join(skipped_deltas, ", ")); generated deflections are named" *
" airfoils/<i>_d<degrees>.dat, and a blank one is a deflection the 2D" *
" solver converged at no angle."
return slices, reduce(hcat, les), reduce(hcat, tes)
end

Expand Down Expand Up @@ -1673,8 +1678,9 @@ function ObjAdapter.plot_slices_3d(path::String; n_slices::Int=10, rotation=I,
wrap_method=AirfoilAero.ShrinkWrap(), delta=0.0, crease_frac=0.75,
obj_path=nothing, is_show::Bool=true)
kulfan_pts(k) = Point2f.(AirfoilAero.kulfan_to_coordinates(k; n_points=150)...)
fit_pts(x, y) = kulfan_pts(AirfoilAero.fit_kulfan_parameters(
x, y, AirfoilAero.LeastSquaresFit()))
fit_pts(x, y) = isempty(x) ? Point2f[] :
kulfan_pts(AirfoilAero.fit_kulfan_parameters(
x, y, AirfoilAero.LeastSquaresFit()))
mesh_path = isdir(path) ? obj_path : path
vertices = faces = nothing
if mesh_path !== nothing
Expand Down
28 changes: 28 additions & 0 deletions test/plotting/test_plotting.jl
Original file line number Diff line number Diff line change
Expand Up @@ -503,4 +503,32 @@ end
ax_lw = Axis3(fig_lw[1, 1])
@test_nowarn Makie.plot!(ax_lw, plain_body; border_linewidth=3.0)
end

@testset "Audit slices (Makie)" begin
# An all-NaN deflected .dat reads as empty; here a header-only file stands in.
gen_dir, _ = ram_air_matrix_dir(; n_sections=4,
alpha_range=deg2rad.(-1:1.0:1), delta_range=deg2rad.(-1:1.0:1))
obj = joinpath(dirname(@__DIR__), "..", "data", "ram_air_kite",
"ram_air_kite_body.obj")
audit_dir = joinpath(mktempdir(), "audit")
cp(gen_dir, audit_dir)
rows = YAML.load_file(joinpath(audit_dir, "geometry.yaml"))["wing_sections"]["data"]
d1 = joinpath(audit_dir, "airfoils", "$(rows[1][1])_d1.dat")
@test isfile(d1)
open(d1, "w") do io
println(io, "deflection")
end

slices, _, _ = @test_logs((:warn, r"no finite coordinates"), match_mode=:any,
makie_ext.generated_slices(audit_dir, 1.0, (x, y) -> Point2f[]))
skipped = first(slices)
@test skipped.def3d === nothing
@test isempty(skipped.d2.def)
@test isempty(skipped.d2.def_kulfan)
@test any(s -> s.def3d !== nothing, slices)

fig = VortexStepMethod.ObjAdapter.plot_slices_3d(
audit_dir; delta=1.0, obj_path=obj, is_show=false)
@test fig isa Figure
end
nothing
Loading