diff --git a/CHANGELOG.md b/CHANGELOG.md index e8dddfd6..ce57a116 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,9 @@ ### Added +- `plot_geometry`, `plot_distribution`, `plot_combined_analysis`, `plot_section_polars`, + `plot_airfoil_fit` and `plot_airfoils` take `show_title=true`; with `false` the title + is not drawn, and still names the saved file and window. - The panel plots (`plot!(ax, panel)`, `plot!(ax, body)`, `plot(panel)`, `plot(body)`) take `border_color`, default `:black`, for the panel outlines. diff --git a/ext/VortexStepMethodMakieExt.jl b/ext/VortexStepMethodMakieExt.jl index 25b22660..3ca5b5ff 100644 --- a/ext/VortexStepMethodMakieExt.jl +++ b/ext/VortexStepMethodMakieExt.jl @@ -619,7 +619,7 @@ end """ create_geometry_plot_makie(body_aero::BodyAerodynamics, title, - view_elevation, view_azimuth; zoom=1.8) + view_elevation, view_azimuth; zoom=0.5, show_title=true) Create a 3D Makie plot of wing geometry including panels and filaments. @@ -630,10 +630,11 @@ Create a 3D Makie plot of wing geometry including panels and filaments. - `view_azimuth`: initial view azimuth angle [°] # Keyword arguments -- `zoom`: zoom factor (default: 1.8) +- `zoom`: zoom factor (default: 0.5) +- `show_title`: draw `title` above the axis (default: true) """ function create_geometry_plot_makie(body_aero::BodyAerodynamics, title, - view_elevation, view_azimuth; zoom=0.5) + view_elevation, view_azimuth; zoom=0.5, show_title=true) panels = body_aero.panels va_vec = getfield(body_aero, :va_vec) @@ -641,6 +642,7 @@ function create_geometry_plot_makie(body_aero::BodyAerodynamics, title, fig = Figure(size=(1400, 1400)) ax = Axis3(fig[1, 1]; title=title, + titlevisible=show_title, xlabel="x", ylabel="y", zlabel="z", aspect=:data, azimuth=deg2rad(view_azimuth), @@ -698,7 +700,8 @@ end plot_geometry(body_aero::BodyAerodynamics, title; data_type=nothing, save_path=nothing, is_save=false, is_show=false, - view_elevation=15, view_azimuth=-120, use_tex=false) + view_elevation=15, view_azimuth=-120, use_tex=false, + show_title=true) Makie implementation of [`plot_geometry`](@ref). @@ -714,6 +717,7 @@ Makie implementation of [`plot_geometry`](@ref). - `view_elevation`: View elevation angle in degrees (default: 15) - `view_azimuth`: View azimuth angle in degrees (default: -120) - `use_tex`: Ignored for Makie (default: false) +- `show_title`: Whether to draw the title; it names the files either way (default: true) """ function VortexStepMethod.plot_geometry(body_aero::BodyAerodynamics, title; data_type=nothing, @@ -722,27 +726,21 @@ function VortexStepMethod.plot_geometry(body_aero::BodyAerodynamics, title; is_show=false, view_elevation=15, view_azimuth=-120, - use_tex=false) + use_tex=false, + show_title=true) if is_save - # Angled view - fig = create_geometry_plot_makie(body_aero, "$(title)_angled_view", 15, -120) - save_plot(fig, save_path, "$(title)_angled_view", data_type=data_type) - - # Top view - fig = create_geometry_plot_makie(body_aero, "$(title)_top_view", 90, 0) - save_plot(fig, save_path, "$(title)_top_view", data_type=data_type) - - # Front view - fig = create_geometry_plot_makie(body_aero, "$(title)_front_view", 0, 0) - save_plot(fig, save_path, "$(title)_front_view", data_type=data_type) - - # Side view - fig = create_geometry_plot_makie(body_aero, "$(title)_side_view", 0, -90) - save_plot(fig, save_path, "$(title)_side_view", data_type=data_type) + views = (angled=(15, -120), top=(90, 0), front=(0, 0), side=(0, -90)) + for (view_name, (elevation, azimuth)) in pairs(views) + view_title = "$(title)_$(view_name)_view" + fig = create_geometry_plot_makie(body_aero, view_title, elevation, azimuth; + show_title) + save_plot(fig, save_path, view_title; data_type) + end end - fig = create_geometry_plot_makie(body_aero, title, view_elevation, view_azimuth) + fig = create_geometry_plot_makie(body_aero, title, view_elevation, view_azimuth; + show_title) is_show && show_plot(fig; name=title) @@ -761,7 +759,8 @@ span_axis(position, title, ylabel) = """ plot_distribution(y_coordinates_list, results_list, label_list; title="spanwise_distribution", data_type=nothing, - save_path=nothing, is_save=false, is_show=true, use_tex=false) + save_path=nothing, is_save=false, is_show=true, use_tex=false, + show_title=true) Makie implementation of [`plot_distribution`](@ref). @@ -777,6 +776,7 @@ Makie implementation of [`plot_distribution`](@ref). - `is_save`: Whether to save (default: false) - `is_show`: Whether to display (default: true) - `use_tex`: Ignored for Makie (default: false) +- `show_title`: Whether to draw the title; it names the file either way (default: true) """ function VortexStepMethod.plot_distribution(y_coordinates_list, results_list, label_list; title="spanwise_distribution", @@ -784,7 +784,8 @@ function VortexStepMethod.plot_distribution(y_coordinates_list, results_list, la save_path=nothing, is_save=false, is_show=true, - use_tex=false) + use_tex=false, + show_title=true) length(results_list) == length(label_list) || throw(ArgumentError( "Number of results ($(length(results_list))) must match labels ($(length(label_list)))" @@ -792,7 +793,7 @@ function VortexStepMethod.plot_distribution(y_coordinates_list, results_list, la # Create figure with 3x3 grid fig = Figure(size=(1600, 1000)) - Label(fig[0, :], title, fontsize=20) + show_title && Label(fig[0, :], title, fontsize=20) # Row 1: CL, CD, Gamma ax_cl = span_axis(fig[1, 1], "CL Distribution", "Lift Coefficient CL") @@ -1124,7 +1125,8 @@ end view_elevation=15, view_azimuth=-120, is_show=true, use_tex=false, literature_path_list=String[], - data_type=".png", save_path=nothing, is_save=false) + data_type=".png", save_path=nothing, is_save=false, + show_title=true) Makie implementation of [`plot_combined_analysis`](@ref). @@ -1154,6 +1156,7 @@ Makie implementation of [`plot_combined_analysis`](@ref). - `is_save`: Save plots to files (default: false) - `cl_over_cd`: Plot CL/CD vs angle instead of CL vs CD (default: true) - `angle_of_attack_for_spanwise_distribution`: AoA for spanwise plots (default: 5.0) +- `show_title`: Whether to draw the overall title (default: true) """ function VortexStepMethod.plot_combined_analysis( solver, @@ -1177,6 +1180,7 @@ function VortexStepMethod.plot_combined_analysis( is_save=false, angle_of_attack_for_spanwise_distribution=5.0, cl_over_cd=true, + show_title=true, ) # Normalize inputs to arrays for consistent handling solvers = solver isa Vector ? solver : [solver] @@ -1218,7 +1222,7 @@ function VortexStepMethod.plot_combined_analysis( # Fallback if screen detection fails Figure(size=(1800, 1200)) end - Label(fig[0, 1:2], title, fontsize=20, font=:bold) + show_title && Label(fig[0, 1:2], title, fontsize=20, font=:bold) # Use first body_aero for geometry and polar data display first_body = body_aeros[1] @@ -1489,7 +1493,7 @@ Implementation of [`plot_section_polars`](@ref); rendered through `MakieControlP function VortexStepMethod.plot_section_polars(body_aero::BodyAerodynamics; panels=eachindex(body_aero.panels), alphas=deg2rad.(-20:0.5:30), delta=nothing, is_show::Bool=true, is_save::Bool=false, save_path=nothing, - data_type::String=".png") + data_type::String=".png", show_title::Bool=true) panel_indices = vcat(panels) chosen_panels = body_aero.panels[panel_indices] @@ -1499,7 +1503,8 @@ function VortexStepMethod.plot_section_polars(body_aero::BodyAerodynamics; for (i, panel) in zip(panel_indices, chosen_panels)] plt = MakieControlPlots.plotx(rad2deg.(alphas), cl, cd, cm; - xlabel="α [deg]", ylabels=["cl", "cd", "cm"], title="Section polars", + xlabel="α [deg]", ylabels=["cl", "cd", "cm"], + title=show_title ? "Section polars" : "", labels=[labels], disp=(is_show || is_save)) if is_save && !isnothing(save_path) @@ -1737,7 +1742,8 @@ function ObjAdapter.plot_slices_3d(path::String; n_slices::Int=10, rotation=I, end """ - plot_airfoil_fit(x::Vector, y::Vector; title="Airfoil Fit", is_show=true) + plot_airfoil_fit(x::Vector, y::Vector; title="Airfoil Fit", is_show=true, + show_title=true) Plot a single airfoil with its Kulfan CST fit. @@ -1747,15 +1753,17 @@ Plot a single airfoil with its Kulfan CST fit. # Keyword Arguments - `title`: Plot title - `is_show`: Display figure +- `show_title`: Draw the title; it names the window either way # Returns - Makie Figure object and the fitted `KulfanParameters` """ function ObjAdapter.plot_airfoil_fit(x::Vector, y::Vector; title::String="Airfoil Fit", - is_show::Bool=true) + is_show::Bool=true, show_title::Bool=true) fig = Figure(size=(800, 400)) ax = Axis(fig[1, 1]; title=title, + titlevisible=show_title, xlabel="x/c", ylabel="y/c", aspect=DataAspect()) @@ -1793,7 +1801,7 @@ end """ plot_airfoils(geometry_file; overlay=nothing, symmetric=false, idxs=nothing, n_cols=3, is_show=true, is_save=false, - save_path=nothing, data_type=".png") + save_path=nothing, data_type=".png", show_title=true) Makie implementation of [`plot_airfoils`](@ref). @@ -1804,11 +1812,12 @@ becomes unreadable. Both modes show the raw `_raw.dat` slice points as dots with the fitted airfoil as a line. Pass `idxs` (e.g. `idxs=[1]`) to plot only those airfoils by position; otherwise `symmetric=true` shows just the first half of the sections (the wing is mirror-symmetric, so the other half is redundant). +`show_title=false` leaves the `"Airfoils: "` title off the figure. """ function ObjAdapter.plot_airfoils(geometry_file::String; overlay=nothing, symmetric::Bool=false, idxs=nothing, n_cols::Int=3, is_show::Bool=true, is_save::Bool=false, save_path=nothing, - data_type::String=".png") + data_type::String=".png", show_title::Bool=true) airfoils = ObjAdapter.airfoils_from_yaml(geometry_file) isempty(airfoils) && error("No airfoils with a dat_file found in $geometry_file") @@ -1826,7 +1835,8 @@ function ObjAdapter.plot_airfoils(geometry_file::String; ids = [af.id for af in airfoils] crange = (minimum(ids), maximum(ids)) fig = Figure(size=(900, 600)) - ax = Axis(fig[1, 1]; title, xlabel="x/c", ylabel="y/c", aspect=DataAspect()) + ax = Axis(fig[1, 1]; title, titlevisible=show_title, xlabel="x/c", ylabel="y/c", + aspect=DataAspect()) for af in airfoils isempty(af.x_raw) || scatter!(ax, af.x_raw, af.y_raw; color=af.id, colorrange=crange, colormap=:viridis, markersize=3) @@ -1837,7 +1847,7 @@ function ObjAdapter.plot_airfoils(geometry_file::String; ncol = min(n_cols, n) nrow = ceil(Int, n / ncol) fig = Figure(size=(380 * ncol, 320 * nrow)) - Label(fig[0, :], title, fontsize=16) + show_title && Label(fig[0, :], title, fontsize=16) for (i, af) in enumerate(airfoils) ax = Axis(fig[div(i - 1, ncol) + 1, mod1(i, ncol)]; title="Airfoil $(af.id)", xlabel="x/c", ylabel="y/c", diff --git a/src/VortexStepMethod.jl b/src/VortexStepMethod.jl index 6f473f1c..2cf1cd7c 100644 --- a/src/VortexStepMethod.jl +++ b/src/VortexStepMethod.jl @@ -77,6 +77,7 @@ Plot wing geometry from different viewpoints and optionally save/show plots. - `view_elevation`: initial view elevation angle in degrees (default: `15`) - `view_azimuth`: initial view azimuth angle in degrees (default: `-120`) - `use_tex`: use external `pdflatex` for rendering (default: `false`; ignored by Makie) +- `show_title`: draw the title; it names the saved files either way (default: `true`) """ function plot_geometry end @@ -97,6 +98,7 @@ Plot spanwise distributions of aerodynamic properties. - `is_save`: whether to save (default: `false`) - `is_show`: whether to display (default: `true`) - `use_tex`: use external `pdflatex` for rendering (default: `false`; ignored by Makie) +- `show_title`: draw the title; it names the saved file either way (default: `true`) """ function plot_distribution end @@ -172,6 +174,7 @@ in sequence. - `save_path`: directory to save files (default: `nothing`) - `is_save`: whether to save (default: `false`) - `cl_over_cd`: plot CL/CD vs angle (default: `true`) +- `show_title`: draw the overall title (default: `true`) """ function plot_combined_analysis end @@ -196,6 +199,7 @@ Rendered through `MakieControlPlots`; returns its plot object. - `is_save`: whether to save (default: `false`) - `save_path`: directory to save the figure (default: `nothing`) - `data_type`: file extension for saving (default: `".png"`) +- `show_title`: draw the title (default: `true`) """ function plot_section_polars end diff --git a/test/plotting/test_plotting.jl b/test/plotting/test_plotting.jl index 751e961f..5c4b2f58 100644 --- a/test/plotting/test_plotting.jl +++ b/test/plotting/test_plotting.jl @@ -25,6 +25,23 @@ using Test const makie_ext = Base.get_extension(VortexStepMethod, :VortexStepMethodMakieExt) +""" + drawn_titles(fig) -> Vector{String} + +Texts of the `Label`s and of the visible `Axis`/`Axis3` titles drawn on `fig`. +""" +function drawn_titles(fig) + titles = String[] + for block in fig.content + if block isa Label + push!(titles, string(block.text[])) + elseif block isa Union{Axis, Axis3} && block.titlevisible[] + push!(titles, string(block.title[])) + end + end + return titles +end + global ram_wing = ram_air_matrix_wing(; n_panels=20, n_sections=4, alpha_range=deg2rad.(-1:1.0:1), delta_range=deg2rad.(-1:1.0:1)) @@ -166,6 +183,37 @@ end ) @test fig isa Figure + @testset "show_title=false hides the title but still names the file" begin + fig = plot_geometry(body_aero, "Hidden geometry"; is_show=false) + @test "Hidden geometry" in drawn_titles(fig) + fig = plot_geometry(body_aero, "Hidden geometry"; data_type=".png", + save_path=save_dir, is_save=true, is_show=false, show_title=false) + @test isempty(drawn_titles(fig)) + @test isfile(joinpath(save_dir, "Hidden_geometry_top_view.png")) + + distribution_args = ([y_coordinates], [results_vsm], ["VSM"]) + fig = plot_distribution(distribution_args...; title="Hidden distribution", + is_show=false) + @test "Hidden distribution" in drawn_titles(fig) + fig = plot_distribution(distribution_args...; title="Hidden distribution", + data_type=".png", save_path=save_dir, is_save=true, is_show=false, + show_title=false) + @test "Hidden distribution" ∉ drawn_titles(fig) + @test "CL Distribution" in drawn_titles(fig) + @test isfile(joinpath(save_dir, "Hidden_distribution.png")) + + combined_kwargs = (angle_range=range(0, 10, 2), va, is_show=false) + fig = plot_combined_analysis(vsm_solver, body_aero, results_vsm; + title="Hidden combined", combined_kwargs...) + @test "Hidden combined" in drawn_titles(fig) + fig = plot_combined_analysis(vsm_solver, body_aero, results_vsm; + title="Hidden combined", data_type=".png", save_path=save_dir, + is_save=true, show_title=false, combined_kwargs...) + @test "Hidden combined" ∉ drawn_titles(fig) + @test "Wing Geometry" in drawn_titles(fig) + @test isfile(joinpath(save_dir, "Hidden_combined.png")) + end + @testset "round-off CS plots flat, a varying CS keeps its autoscale" begin fig = Figure() ax_noise = Axis(fig[1, 1]) @@ -625,6 +673,32 @@ end end end +@testset "show_title=false hides the airfoil and section polar titles" begin + body_aero, _ = create_body_aero_with_skin() + @test plot_section_polars(body_aero; is_show=false).title == "Section polars" + @test plot_section_polars(body_aero; is_show=false, show_title=false).title == "" + + _, geometry_file = ram_air_matrix_dir(; n_sections=4, + alpha_range=deg2rad.(-1:1.0:1), delta_range=deg2rad.(-1:1.0:1)) + obj_adapter = VortexStepMethod.ObjAdapter + airfoil = first(obj_adapter.airfoils_from_yaml(geometry_file)) + fig, _ = obj_adapter.plot_airfoil_fit(airfoil.x, airfoil.y; title="Hidden fit", + is_show=false) + @test "Hidden fit" in drawn_titles(fig) + fig, _ = obj_adapter.plot_airfoil_fit(airfoil.x, airfoil.y; title="Hidden fit", + is_show=false, show_title=false) + @test drawn_titles(fig) == ["Kulfan Parameters"] + + airfoils_title = "Airfoils: geometry.yaml" + for overlay in (false, true) + fig = obj_adapter.plot_airfoils(geometry_file; overlay, is_show=false) + @test airfoils_title in drawn_titles(fig) + fig = obj_adapter.plot_airfoils(geometry_file; overlay, is_show=false, + show_title=false) + @test airfoils_title ∉ drawn_titles(fig) + end +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,