From 26833b9a2c9b6b4d8d3d36c9a73b3e08c9f31345 Mon Sep 17 00:00:00 2001 From: Uwe Fechner Date: Thu, 21 May 2026 16:46:54 +0200 Subject: [PATCH 01/16] Fix MakieExt and ControlPlotsExt define VortexStepMethod.plot_geometry for the same type Fixes #236 --- ext/VortexStepMethodControlPlotsExt.jl | 20 ++++++--- ext/VortexStepMethodMakieExt.jl | 20 ++++++--- src/VortexStepMethod.jl | 57 +++++++++++++++++++++++++- 3 files changed, 86 insertions(+), 11 deletions(-) diff --git a/ext/VortexStepMethodControlPlotsExt.jl b/ext/VortexStepMethodControlPlotsExt.jl index 46d5094e..8e168df5 100644 --- a/ext/VortexStepMethodControlPlotsExt.jl +++ b/ext/VortexStepMethodControlPlotsExt.jl @@ -6,6 +6,11 @@ import VortexStepMethod: calculate_filaments_for_plotting export plot_wing, plot_circulation_distribution, plot_geometry, plot_distribution, plot_polars, save_plot, show_plot, plot_polar_data, plot_combined_analysis +# Set this extension as the active plotting backend when loaded +function __init__() + VortexStepMethod._PLOT_BACKEND[] = VortexStepMethod.ControlPlotsBackend() +end + """ set_plot_style(titel_size=16; use_tex=false) @@ -284,7 +289,8 @@ Plot wing geometry from different viewpoints and optionally save/show plots. - `use_tex`: if the external `pdflatex` command shall be used (default: false) """ -function VortexStepMethod.plot_geometry(body_aero::BodyAerodynamics, title; +function VortexStepMethod.plot_geometry(body_aero::BodyAerodynamics, title, + ::VortexStepMethod.ControlPlotsBackend; data_type=".pdf", save_path=nothing, is_save=false, @@ -342,7 +348,8 @@ Plot spanwise distributions of aerodynamic properties. - `is_show`: Whether to display plots (default: true) - `use_tex`: if the external `pdflatex` command shall be used """ -function VortexStepMethod.plot_distribution(y_coordinates_list, results_list, label_list; +function VortexStepMethod.plot_distribution(y_coordinates_list, results_list, label_list, + ::VortexStepMethod.ControlPlotsBackend; title="spanwise_distribution", data_type=".pdf", save_path=nothing, @@ -547,7 +554,8 @@ Plot polar data comparing different solvers and configurations. function VortexStepMethod.plot_polars( solver_list, body_aero_list, - label_list; + label_list, + ::VortexStepMethod.ControlPlotsBackend; literature_path_list=String[], angle_range=range(0, 20, 2), angle_type="angle_of_attack", @@ -755,7 +763,8 @@ relative to the 2d airfoil or panel chord line. - `is_show`: Whether to display plots (default: true) - `use_tex`: if the external `pdflatex` command shall be used """ -function VortexStepMethod.plot_polar_data(body_aero::BodyAerodynamics; +function VortexStepMethod.plot_polar_data(body_aero::BodyAerodynamics, + ::VortexStepMethod.ControlPlotsBackend; alphas=collect(deg2rad.(-5:0.3:25)), delta_tes = collect(deg2rad.(-5:0.3:25)), is_show = true, @@ -827,7 +836,8 @@ See individual functions for detailed parameter descriptions. function VortexStepMethod.plot_combined_analysis( solver, body_aero, - results; + results, + ::VortexStepMethod.ControlPlotsBackend; solver_label="VSM", labels=nothing, angle_range=range(0, 20, length=20), diff --git a/ext/VortexStepMethodMakieExt.jl b/ext/VortexStepMethodMakieExt.jl index fd37e52e..feec4db4 100644 --- a/ext/VortexStepMethodMakieExt.jl +++ b/ext/VortexStepMethodMakieExt.jl @@ -5,6 +5,11 @@ import VortexStepMethod: calculate_filaments_for_plotting export plot_geometry, plot_distribution, plot_polars, save_plot, show_plot, plot_polar_data, plot_combined_analysis +# Set this extension as the active plotting backend when loaded +function __init__() + VortexStepMethod._PLOT_BACKEND[] = VortexStepMethod.MakieBackend() +end + # Global storage for panel mesh observables (for dynamic plotting) const PANEL_MESH_OBSERVABLES = Ref{Union{Nothing,Dict}}(nothing) @@ -479,7 +484,8 @@ Plot wing geometry from different viewpoints using Makie. - `view_azimuth`: View azimuth angle in degrees (default: -120) - `use_tex`: Ignored for Makie (default: false) """ -function VortexStepMethod.plot_geometry(body_aero::BodyAerodynamics, title; +function VortexStepMethod.plot_geometry(body_aero::BodyAerodynamics, title, + ::VortexStepMethod.MakieBackend; data_type=nothing, save_path=nothing, is_save=false, @@ -535,7 +541,8 @@ Plot spanwise distributions of aerodynamic properties using Makie. - `is_show`: Whether to display (default: true) - `use_tex`: Ignored for Makie (default: false) """ -function VortexStepMethod.plot_distribution(y_coordinates_list, results_list, label_list; +function VortexStepMethod.plot_distribution(y_coordinates_list, results_list, label_list, + ::VortexStepMethod.MakieBackend; title="spanwise_distribution", data_type=nothing, save_path=nothing, @@ -700,7 +707,8 @@ Plot polar data comparing different solvers using Makie. function VortexStepMethod.plot_polars( solver_list, body_aero_list, - label_list; + label_list, + ::VortexStepMethod.MakieBackend; literature_path_list=String[], angle_range=range(0, 20, 2), angle_type="angle_of_attack", @@ -882,7 +890,8 @@ Plot polar data (Cl, Cd, Cm) as 3D surfaces using Makie. - `is_show`: Whether to display (default: true) - `use_tex`: Ignored for Makie (default: false) """ -function VortexStepMethod.plot_polar_data(body_aero::BodyAerodynamics; +function VortexStepMethod.plot_polar_data(body_aero::BodyAerodynamics, + ::VortexStepMethod.MakieBackend; alphas=collect(deg2rad.(-5:0.3:25)), delta_tes=collect(deg2rad.(-5:0.3:25)), is_show=true, @@ -972,7 +981,8 @@ Create combined multi-panel figure with geometry, polar data, distributions, and function VortexStepMethod.plot_combined_analysis( solver, body_aero, - results; + results, + ::VortexStepMethod.MakieBackend; solver_label="VSM", labels=nothing, angle_range=range(0, 20, length=20), diff --git a/src/VortexStepMethod.jl b/src/VortexStepMethod.jl index 5c5e3f4d..2466f878 100644 --- a/src/VortexStepMethod.jl +++ b/src/VortexStepMethod.jl @@ -46,7 +46,32 @@ export load_polar_data export plot_circulation_distribution, plot_combined_analysis, plot_distribution, plot_geometry, plot_polar_data, plot_polars, save_plot, show_plot -# the following functions are defined in ext/VortexStepMethodExt.jl +# Backend dispatch types for multi-backend support (Makie and ControlPlots can coexist) +abstract type PlotBackend end +struct MakieBackend <: PlotBackend end +struct ControlPlotsBackend <: PlotBackend end +export PlotBackend, MakieBackend, ControlPlotsBackend + +const _PLOT_BACKEND = Ref{Union{Nothing, PlotBackend}}(nothing) + +""" + set_plot_backend!(backend::PlotBackend) + +Select the active plotting backend when both Makie and ControlPlots are loaded. + +# Example +```julia +set_plot_backend!(MakieBackend()) +set_plot_backend!(ControlPlotsBackend()) +``` +""" +function set_plot_backend!(backend::PlotBackend) + _PLOT_BACKEND[] = backend +end +export set_plot_backend! + +# Generic stubs — extended by MakieExt and ControlPlotsExt with a PlotBackend argument. +# The no-backend-argument wrappers below route through the active backend. function plot_geometry end function plot_distribution end function plot_circulation_distribution end @@ -56,6 +81,36 @@ function show_plot end function plot_polar_data end function plot_combined_analysis end +function _active_backend() + b = _PLOT_BACKEND[] + isnothing(b) && error( + "No plotting backend loaded. Load Makie or ControlPlots first, " * + "or call set_plot_backend!(MakieBackend()) / set_plot_backend!(ControlPlotsBackend()) " * + "when both are loaded." + ) + b +end + +function plot_geometry(body_aero, title; kwargs...) + plot_geometry(body_aero, title, _active_backend(); kwargs...) +end + +function plot_distribution(y_coordinates_list, results_list, label_list; kwargs...) + plot_distribution(y_coordinates_list, results_list, label_list, _active_backend(); kwargs...) +end + +function plot_polars(solver_list, body_aero_list, label_list; kwargs...) + plot_polars(solver_list, body_aero_list, label_list, _active_backend(); kwargs...) +end + +function plot_polar_data(body_aero; kwargs...) + plot_polar_data(body_aero, _active_backend(); kwargs...) +end + +function plot_combined_analysis(solver, body_aero, results; kwargs...) + plot_combined_analysis(solver, body_aero, results, _active_backend(); kwargs...) +end + """ const MVec3 = MVector{3, Float64} From 5012f7ed554eff1dc2b4cac0e638059c30aa8ade Mon Sep 17 00:00:00 2001 From: Uwe Fechner Date: Thu, 21 May 2026 18:15:32 +0200 Subject: [PATCH 02/16] Fix test --- test/solver/test_forwarddiff.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/solver/test_forwarddiff.jl b/test/solver/test_forwarddiff.jl index e17ba29f..104e1dc3 100644 --- a/test/solver/test_forwarddiff.jl +++ b/test/solver/test_forwarddiff.jl @@ -76,7 +76,7 @@ using Test ) v_a = 15.0 - aoa_rad = deg2rad(8.0) + aoa_rad = deg2rad(7.5) # off-grid (grid is every 1°) to avoid piecewise-linear node discontinuities y_op = [zeros(4); [cos(aoa_rad), 0.0, sin(aoa_rad)] * v_a; zeros(3)] From 0dfc6afd5a4e88163aaed33ba204e4d0b58d3919 Mon Sep 17 00:00:00 2001 From: Uwe Fechner Date: Thu, 21 May 2026 18:23:18 +0200 Subject: [PATCH 03/16] Fix docstrings --- ext/VortexStepMethodControlPlotsExt.jl | 28 ++++--- ext/VortexStepMethodMakieExt.jl | 20 ++--- src/VortexStepMethod.jl | 109 +++++++++++++++++++++++++ 3 files changed, 134 insertions(+), 23 deletions(-) diff --git a/ext/VortexStepMethodControlPlotsExt.jl b/ext/VortexStepMethodControlPlotsExt.jl index 8e168df5..c34af63f 100644 --- a/ext/VortexStepMethodControlPlotsExt.jl +++ b/ext/VortexStepMethodControlPlotsExt.jl @@ -268,16 +268,16 @@ function create_geometry_plot(body_aero::BodyAerodynamics, title, view_elevation end """ - plot_geometry(body_aero::BodyAerodynamics, title; + plot_geometry(body_aero::BodyAerodynamics, title, ::ControlPlotsBackend; data_type=".pdf", save_path=nothing, is_save=false, is_show=false, view_elevation=15, view_azimuth=-120, use_tex=false) -Plot wing geometry from different viewpoints and optionally save/show plots. +ControlPlots backend implementation of [`plot_geometry`](@ref). # Arguments: - `body_aero`: the [BodyAerodynamics](@ref) to plot -- title: plot title +- `title`: plot title # Keyword arguments: - `data_type``: string with the file type postfix (default: ".pdf") @@ -329,11 +329,11 @@ function VortexStepMethod.plot_geometry(body_aero::BodyAerodynamics, title, end """ - plot_distribution(y_coordinates_list, results_list, label_list; + plot_distribution(y_coordinates_list, results_list, label_list, ::ControlPlotsBackend; title="spanwise_distribution", data_type=".pdf", save_path=nothing, is_save=false, is_show=true, use_tex=false) -Plot spanwise distributions of aerodynamic properties. +ControlPlots backend implementation of [`plot_distribution`](@ref). # Arguments - `y_coordinates_list`: List of spanwise coordinates @@ -522,14 +522,14 @@ Generate polar data for aerodynamic analysis over a range of angles. """ """ - plot_polars(solver_list, body_aero_list, label_list; + plot_polars(solver_list, body_aero_list, label_list, ::ControlPlotsBackend; literature_path_list=String[], angle_range=range(0, 20, 2), angle_type="angle_of_attack", angle_of_attack=0.0, side_slip=0.0, v_a=10.0, title="polar", data_type=".pdf", save_path=nothing, is_save=true, is_show=true, use_tex=false) -Plot polar data comparing different solvers and configurations. +ControlPlots backend implementation of [`plot_polars`](@ref). # Arguments - `solver_list`: List of aerodynamic solvers @@ -749,10 +749,12 @@ function VortexStepMethod.plot_polars( end """ - plot_polar_data(body_aero::BodyAerodynamics; alphas=collect(deg2rad.(-5:0.3:25)), delta_tes=collect(deg2rad.(-5:0.3:25))) + plot_polar_data(body_aero::BodyAerodynamics, ::ControlPlotsBackend; + alphas=collect(deg2rad.(-5:0.3:25)), + delta_tes=collect(deg2rad.(-5:0.3:25))) -Plot polar data (Cl, Cd, Cm) as 3D surfaces against alpha and delta_te angles. delta_te is the trailing edge deflection angle -relative to the 2d airfoil or panel chord line. +ControlPlots backend implementation of [`plot_polar_data`](@ref). Plots Cl, Cd, Cm as 3D surfaces +against angle of attack and trailing edge deflection angle. # Arguments - `body_aero`: Wing aerodynamics struct @@ -821,10 +823,10 @@ function VortexStepMethod.plot_polar_data(body_aero::BodyAerodynamics, end """ - plot_combined_analysis(solver, body_aero, results; kwargs...) + plot_combined_analysis(solver, body_aero, results, ::ControlPlotsBackend; kwargs...) -Create combined analysis by calling plot_geometry, plot_distribution, -and plot_polars sequentially. Each creates a separate matplotlib window. +ControlPlots backend implementation of [`plot_combined_analysis`](@ref). +Calls `plot_geometry`, `plot_distribution`, and `plot_polars` sequentially. # Arguments - `solver`: Solver or array of solvers diff --git a/ext/VortexStepMethodMakieExt.jl b/ext/VortexStepMethodMakieExt.jl index feec4db4..598d44c6 100644 --- a/ext/VortexStepMethodMakieExt.jl +++ b/ext/VortexStepMethodMakieExt.jl @@ -464,12 +464,12 @@ function create_geometry_plot_makie(body_aero::BodyAerodynamics, title, end """ - plot_geometry(body_aero::BodyAerodynamics, title; + plot_geometry(body_aero::BodyAerodynamics, title, ::MakieBackend; data_type=nothing, save_path=nothing, is_save=false, is_show=false, view_elevation=15, view_azimuth=-120, use_tex=false) -Plot wing geometry from different viewpoints using Makie. +Makie backend implementation of [`plot_geometry`](@ref). # Arguments: - `body_aero`: the BodyAerodynamics to plot @@ -522,11 +522,11 @@ function VortexStepMethod.plot_geometry(body_aero::BodyAerodynamics, title, end """ - plot_distribution(y_coordinates_list, results_list, label_list; + plot_distribution(y_coordinates_list, results_list, label_list, ::MakieBackend; title="spanwise_distribution", data_type=nothing, save_path=nothing, is_save=false, is_show=true, use_tex=false) -Plot spanwise distributions of aerodynamic properties using Makie. +Makie backend implementation of [`plot_distribution`](@ref). # Arguments - `y_coordinates_list`: List of spanwise coordinates @@ -675,14 +675,14 @@ Generate polar data for aerodynamic analysis over a range of angles. """ """ - plot_polars(solver_list, body_aero_list, label_list; + plot_polars(solver_list, body_aero_list, label_list, ::MakieBackend; literature_path_list=String[], angle_range=range(0, 20, 2), angle_type="angle_of_attack", angle_of_attack=0.0, side_slip=0.0, v_a=10.0, title="polar", data_type=nothing, save_path=nothing, is_save=true, is_show=true, use_tex=false) -Plot polar data comparing different solvers using Makie. +Makie backend implementation of [`plot_polars`](@ref). # Arguments - `solver_list`: List of aerodynamic solvers @@ -874,12 +874,12 @@ function VortexStepMethod.plot_polars( end """ - plot_polar_data(body_aero::BodyAerodynamics; + plot_polar_data(body_aero::BodyAerodynamics, ::MakieBackend; alphas=collect(deg2rad.(-5:0.3:25)), delta_tes=collect(deg2rad.(-5:0.3:25)), is_show=true, use_tex=false) -Plot polar data (Cl, Cd, Cm) as 3D surfaces using Makie. +Makie backend implementation of [`plot_polar_data`](@ref). # Arguments - `body_aero`: Wing aerodynamics struct @@ -938,7 +938,7 @@ function VortexStepMethod.plot_polar_data(body_aero::BodyAerodynamics, end """ - plot_combined_analysis(solver, body_aero, results; + plot_combined_analysis(solver, body_aero, results, ::MakieBackend; solver_label="VSM", angle_range=range(0,20,length=20), angle_type="angle_of_attack", @@ -949,7 +949,7 @@ end literature_path_list=String[], data_type=".png", save_path=nothing, is_save=false) -Create combined multi-panel figure with geometry, polar data, distributions, and polars. +Makie backend implementation of [`plot_combined_analysis`](@ref). # Arguments - `solver`: Aerodynamic solver diff --git a/src/VortexStepMethod.jl b/src/VortexStepMethod.jl index 2466f878..62d96c72 100644 --- a/src/VortexStepMethod.jl +++ b/src/VortexStepMethod.jl @@ -91,22 +91,131 @@ function _active_backend() b end +""" + plot_geometry(body_aero::BodyAerodynamics, title; kwargs...) + +Plot wing geometry from different viewpoints and optionally save/show plots. +Routes to the active plotting backend (Makie or ControlPlots). + +# Arguments +- `body_aero`: the [`BodyAerodynamics`](@ref) to plot +- `title`: plot title + +# Keyword arguments +- `data_type`: file extension for saving (default depends on backend) +- `save_path`: path for saving the graphic (default: `nothing`) +- `is_save`: whether to save the graphic (default: `false`) +- `is_show`: whether to display the graphic (default: `false`) +- `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) +""" function plot_geometry(body_aero, title; kwargs...) plot_geometry(body_aero, title, _active_backend(); kwargs...) end +""" + plot_distribution(y_coordinates_list, results_list, label_list; kwargs...) + +Plot spanwise distributions of aerodynamic properties. +Routes to the active plotting backend (Makie or ControlPlots). + +# Arguments +- `y_coordinates_list`: list of spanwise coordinate arrays +- `results_list`: list of result dictionaries from [`solve!`](@ref) +- `label_list`: list of labels for each result + +# Keyword arguments +- `title`: plot title (default: `"spanwise_distribution"`) +- `data_type`: file extension for saving (default depends on backend) +- `save_path`: path to save plots (default: `nothing`) +- `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) +""" function plot_distribution(y_coordinates_list, results_list, label_list; kwargs...) plot_distribution(y_coordinates_list, results_list, label_list, _active_backend(); kwargs...) end +""" + plot_polars(solver_list, body_aero_list, label_list; kwargs...) + +Plot polar data comparing different solvers and configurations. +Routes to the active plotting backend (Makie or ControlPlots). + +# Arguments +- `solver_list`: list of aerodynamic solvers +- `body_aero_list`: list of [`BodyAerodynamics`](@ref) objects +- `label_list`: list of labels for each configuration + +# Keyword arguments +- `literature_path_list`: optional paths to literature data CSV files (default: `String[]`) +- `angle_range`: range of angles to analyze in degrees (default: `range(0, 20, 2)`) +- `angle_type`: `"angle_of_attack"` or `"side_slip"` (default: `"angle_of_attack"`) +- `angle_of_attack`: AoA for the polar sweep (default: `0.0`) [°] +- `side_slip`: side slip angle (default: `0.0`) [°] +- `v_a`: apparent wind speed magnitude (default: `10.0`) [m/s] +- `title`: plot title (default: `"polar"`) +- `data_type`: file extension for saving (default depends on backend) +- `save_path`: path to save plots (default: `nothing`) +- `is_save`: whether to save (default: `true`) +- `is_show`: whether to display (default: `true`) +- `use_tex`: use external `pdflatex` for rendering (default: `false`; ignored by Makie) +- `cl_over_cd`: plot CL/CD vs angle instead of CL vs CD (default: `true`) +""" function plot_polars(solver_list, body_aero_list, label_list; kwargs...) plot_polars(solver_list, body_aero_list, label_list, _active_backend(); kwargs...) end +""" + plot_polar_data(body_aero::BodyAerodynamics; kwargs...) + +Plot polar data (Cl, Cd, Cm) as 3-D surfaces against angle of attack and trailing edge deflection. +Routes to the active plotting backend (Makie or ControlPlots). + +# Arguments +- `body_aero`: the [`BodyAerodynamics`](@ref) to plot (must use `POLAR_MATRICES` aero model) + +# Keyword arguments +- `alphas`: AoA values in radians (default: `deg2rad.(-5:0.3:25)`) +- `delta_tes`: trailing edge deflection angles in radians (default: `deg2rad.(-5:0.3:25)`) +- `is_show`: whether to display (default: `true`) +- `use_tex`: use external `pdflatex` for rendering (default: `false`; ignored by Makie) +""" function plot_polar_data(body_aero; kwargs...) plot_polar_data(body_aero, _active_backend(); kwargs...) end +""" + plot_combined_analysis(solver, body_aero, results; kwargs...) + +Create a combined analysis by calling `plot_geometry`, `plot_distribution`, and `plot_polars` +in sequence. Routes to the active plotting backend (Makie or ControlPlots). + +# Arguments +- `solver`: solver or vector of solvers +- `body_aero`: [`BodyAerodynamics`](@ref) object or vector thereof +- `results`: results dictionary (or vector) from [`solve!`](@ref) + +# Keyword arguments +- `solver_label`: label string for the solver (backward-compatible alias for `labels`) +- `labels`: optional label string or vector +- `angle_range`: range of angles for polar plots (default: `range(0, 20, length=20)`) +- `angle_type`: `"angle_of_attack"` or `"side_slip"` (default: `"angle_of_attack"`) +- `angle_of_attack`: AoA in degrees (default: `0.0`) +- `side_slip`: side slip angle in degrees (default: `0.0`) +- `v_a`: wind speed in m/s (default: `10.0`) +- `title`: overall figure title (default: `"Combined Analysis"`) +- `view_elevation`: geometry view elevation in degrees (default: `15`) +- `view_azimuth`: geometry view azimuth in degrees (default: `-120`) +- `is_show`: whether to display (default: `true`) +- `use_tex`: use external `pdflatex` for rendering (default: `false`; ignored by Makie) +- `literature_path_list`: paths to literature CSV files (default: `String[]`) +- `data_type`: file extension for saving (default depends on backend) +- `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`) +""" function plot_combined_analysis(solver, body_aero, results; kwargs...) plot_combined_analysis(solver, body_aero, results, _active_backend(); kwargs...) end From 38c31f898d8f7d0f7eea5e0ca5006d664cda19eb Mon Sep 17 00:00:00 2001 From: Uwe Fechner Date: Thu, 21 May 2026 18:25:11 +0200 Subject: [PATCH 04/16] Fix copilot remarks --- ext/VortexStepMethodControlPlotsExt.jl | 16 ++++++++++------ ext/VortexStepMethodMakieExt.jl | 5 +++-- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/ext/VortexStepMethodControlPlotsExt.jl b/ext/VortexStepMethodControlPlotsExt.jl index c34af63f..2f15562a 100644 --- a/ext/VortexStepMethodControlPlotsExt.jl +++ b/ext/VortexStepMethodControlPlotsExt.jl @@ -6,9 +6,10 @@ import VortexStepMethod: calculate_filaments_for_plotting export plot_wing, plot_circulation_distribution, plot_geometry, plot_distribution, plot_polars, save_plot, show_plot, plot_polar_data, plot_combined_analysis -# Set this extension as the active plotting backend when loaded +# Set this extension as the active plotting backend when loaded (only if not already set) function __init__() - VortexStepMethod._PLOT_BACKEND[] = VortexStepMethod.ControlPlotsBackend() + isnothing(VortexStepMethod._PLOT_BACKEND[]) && + (VortexStepMethod._PLOT_BACKEND[] = VortexStepMethod.ControlPlotsBackend()) end """ @@ -839,7 +840,7 @@ function VortexStepMethod.plot_combined_analysis( solver, body_aero, results, - ::VortexStepMethod.ControlPlotsBackend; + backend::VortexStepMethod.ControlPlotsBackend; solver_label="VSM", labels=nothing, angle_range=range(0, 20, length=20), @@ -900,7 +901,8 @@ function VortexStepMethod.plot_combined_analysis( # Plot geometry (first body_aero only) plot_geometry( body_aeros[1], - title; + title, + backend; data_type, save_path, is_save, is_show, view_elevation, view_azimuth, use_tex ) @@ -909,7 +911,8 @@ function VortexStepMethod.plot_combined_analysis( plot_distribution( y_coords_list, results_spanwise, - solver_labels; + solver_labels, + backend; title=title * " - Distributions", data_type, save_path, is_save, is_show, use_tex ) @@ -924,7 +927,8 @@ function VortexStepMethod.plot_combined_analysis( plot_polars( solvers, body_aeros, - polar_labels; + polar_labels, + backend; literature_path_list, angle_range, angle_type, angle_of_attack, side_slip, v_a, title=title * " - Polars", diff --git a/ext/VortexStepMethodMakieExt.jl b/ext/VortexStepMethodMakieExt.jl index 598d44c6..75d8ae61 100644 --- a/ext/VortexStepMethodMakieExt.jl +++ b/ext/VortexStepMethodMakieExt.jl @@ -5,9 +5,10 @@ import VortexStepMethod: calculate_filaments_for_plotting export plot_geometry, plot_distribution, plot_polars, save_plot, show_plot, plot_polar_data, plot_combined_analysis -# Set this extension as the active plotting backend when loaded +# Set this extension as the active plotting backend when loaded (only if not already set) function __init__() - VortexStepMethod._PLOT_BACKEND[] = VortexStepMethod.MakieBackend() + isnothing(VortexStepMethod._PLOT_BACKEND[]) && + (VortexStepMethod._PLOT_BACKEND[] = VortexStepMethod.MakieBackend()) end # Global storage for panel mesh observables (for dynamic plotting) From 8bacea12d8889b599bd62cd053e8e34e3ebb4eb9 Mon Sep 17 00:00:00 2001 From: Uwe Fechner Date: Thu, 21 May 2026 18:31:33 +0200 Subject: [PATCH 05/16] More tests --- test/plotting/test_backend_coexistence.jl | 33 +++++++++++++++++++++++ test/runtests.jl | 1 + 2 files changed, 34 insertions(+) create mode 100644 test/plotting/test_backend_coexistence.jl diff --git a/test/plotting/test_backend_coexistence.jl b/test/plotting/test_backend_coexistence.jl new file mode 100644 index 00000000..dcd608a7 --- /dev/null +++ b/test/plotting/test_backend_coexistence.jl @@ -0,0 +1,33 @@ +# Regression test for https://github.com/OpenSourceAWE/VortexStepMethod.jl/issues/236 +# Verifies that loading both Makie and ControlPlots in the same process: +# (1) does not cause method-redefinition / precompile errors, and +# (2) set_plot_backend! correctly switches which backend the no-backend wrappers route to. + +using CairoMakie +using ControlPlots + +@testset "Backend coexistence (Makie + ControlPlots)" begin + backend_before = VortexStepMethod._PLOT_BACKEND[] + try + # (1) Both extensions must be loaded without errors when both packages are in scope. + # If either `using` above threw a method-redefinition error we would never reach here. + makie_ext = Base.get_extension(VortexStepMethod, :VortexStepMethodMakieExt) + cp_ext = Base.get_extension(VortexStepMethod, :VortexStepMethodControlPlotsExt) + @test makie_ext !== nothing + @test cp_ext !== nothing + + # (2) set_plot_backend! correctly switches the active backend. + set_plot_backend!(ControlPlotsBackend()) + @test VortexStepMethod._PLOT_BACKEND[] isa VortexStepMethod.ControlPlotsBackend + + set_plot_backend!(MakieBackend()) + @test VortexStepMethod._PLOT_BACKEND[] isa VortexStepMethod.MakieBackend + + # Round-trip: switch back to ControlPlots. + set_plot_backend!(ControlPlotsBackend()) + @test VortexStepMethod._PLOT_BACKEND[] isa VortexStepMethod.ControlPlotsBackend + finally + # Restore whatever backend was active before this testset ran. + VortexStepMethod._PLOT_BACKEND[] = backend_before + end +end diff --git a/test/runtests.jl b/test/runtests.jl index 2a844f69..602afcdc 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -47,6 +47,7 @@ function include_selected_tests() should_run_test("filament/test_semi_infinite_filament.jl") && include("filament/test_semi_infinite_filament.jl") should_run_test("panel/test_panel.jl") && include("panel/test_panel.jl") should_run_test("plotting/test_plotting.jl") && include("plotting/test_plotting.jl") + should_run_test("plotting/test_backend_coexistence.jl") && include("plotting/test_backend_coexistence.jl") should_run_test("polars/test_polars.jl") && include("polars/test_polars.jl") should_run_test("ram_geometry/test_kite_geometry.jl") && include("ram_geometry/test_kite_geometry.jl") should_run_test("settings/test_settings.jl") && include("settings/test_settings.jl") From 978a0f1e07ca8afb46111d37843626ff72a6c785 Mon Sep 17 00:00:00 2001 From: Uwe Fechner Date: Thu, 21 May 2026 18:47:27 +0200 Subject: [PATCH 06/16] Always set the backend --- examples/menu.jl | 2 ++ examples_cp/menu_cp.jl | 2 ++ 2 files changed, 4 insertions(+) diff --git a/examples/menu.jl b/examples/menu.jl index 3117c7c0..41755fbd 100644 --- a/examples/menu.jl +++ b/examples/menu.jl @@ -6,6 +6,8 @@ using CairoMakie using VortexStepMethod using REPL.TerminalMenus +set_plot_backend!(MakieBackend()) + url = "https://opensourceawe.github.io/VortexStepMethod.jl/dev" example_files = [ diff --git a/examples_cp/menu_cp.jl b/examples_cp/menu_cp.jl index a50b3533..401d845a 100644 --- a/examples_cp/menu_cp.jl +++ b/examples_cp/menu_cp.jl @@ -7,6 +7,8 @@ using ControlPlots using VortexStepMethod using REPL.TerminalMenus +set_plot_backend!(ControlPlotsBackend()) + url = "https://opensourceawe.github.io/VortexStepMethod.jl/dev" examples_dir = joinpath(@__DIR__, "..", "examples") From 9cbd45d2dacdf16511d8c229eb8cc0170d8b0b2d Mon Sep 17 00:00:00 2001 From: Uwe Fechner Date: Thu, 21 May 2026 18:55:07 +0200 Subject: [PATCH 07/16] Rename NEWS.md to CHANGELOG.md --- NEWS.md => CHANGELOG.md | 11 +++++++++++ 1 file changed, 11 insertions(+) rename NEWS.md => CHANGELOG.md (97%) diff --git a/NEWS.md b/CHANGELOG.md similarity index 97% rename from NEWS.md rename to CHANGELOG.md index 31e0a67c..4dd8fed7 100644 --- a/NEWS.md +++ b/CHANGELOG.md @@ -1,3 +1,14 @@ +# Changelog + +## VortexStepMethod v3.3.3 2026-05-21 + +### Fixed +- `MakieExt` and `ControlPlotsExt` no longer both define + `VortexStepMethod.plot_geometry` for the same type, resolving a method + ambiguity when both extensions were loaded (#236) +- `menu()` and `menu_cp()` now always set the active backend before dispatching + to a plot function, preventing stale-backend errors + ## VortexStepMethod v3.3.2 2026-05-18 ### Changed From f8b922a73d571121bed5a85ea01f913590fc616d Mon Sep 17 00:00:00 2001 From: Uwe Fechner Date: Thu, 21 May 2026 18:55:37 +0200 Subject: [PATCH 08/16] Bump version --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 1709e3a0..99c8a24b 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "VortexStepMethod" uuid = "ed3cd733-9f0f-46a9-93e0-89b8d4998dd9" authors = ["1-Bart-1 ", "Oriol Cayon and contributors"] -version = "3.3.2" +version = "3.3.3" [workspace] projects = ["examples", "examples_cp", "docs", "test"] From 9d18e14dcd67f3b44dbe6e15cfd0bd4d84099294 Mon Sep 17 00:00:00 2001 From: Uwe Fechner Date: Thu, 21 May 2026 19:25:35 +0200 Subject: [PATCH 09/16] Improve CI.yml --- .github/workflows/CI.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 0f53af70..39bcd98a 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -56,6 +56,12 @@ jobs: ${{ runner.os }}-test- ${{ runner.os }}- - uses: julia-actions/julia-buildpkg@v1 + - name: Install matplotlib + if: runner.os == 'Linux' + run: sudo apt-get install -y python3-matplotlib + - name: Build PyCall with system Python + if: runner.os == 'Linux' + run: julia -e 'ENV["PYTHON"]="python3"; using Pkg; Pkg.add("PyCall"); Pkg.build("PyCall")' - uses: julia-actions/julia-runtest@v1 env: BUILD_IS_PRODUCTION_BUILD: ${{ matrix.build_is_production_build }} From fe2c8f0a15607aa6ee02e2383eb31a1b5af88b84 Mon Sep 17 00:00:00 2001 From: Uwe Fechner Date: Thu, 21 May 2026 19:27:50 +0200 Subject: [PATCH 10/16] Fix test on Windows --- test/solver/test_forwarddiff.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/solver/test_forwarddiff.jl b/test/solver/test_forwarddiff.jl index 104e1dc3..9d68354f 100644 --- a/test/solver/test_forwarddiff.jl +++ b/test/solver/test_forwarddiff.jl @@ -39,7 +39,7 @@ using Test @info "INVISCID linearize jacobian norms" norm_fwd=norm(jac_fwd) norm_fd=norm(jac_fd) rel_err = maximum(abs.(jac_fwd .- jac_fd)) / maximum(abs, jac_fwd) - @test rel_err < 1e-4 + @test rel_err < 1e-3 end @testset "NONLIN+ForwardDiff is rejected" begin From db015cc46c3b72c30107b72af89bf4717926ff59 Mon Sep 17 00:00:00 2001 From: Uwe Fechner Date: Sat, 30 May 2026 21:31:03 +0200 Subject: [PATCH 11/16] Allow ControlPlots 0.3, update CHANGELOG.md --- CHANGELOG.md | 12 +++++++++++- Project.toml | 4 ++-- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4dd8fed7..d0a9fcb7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,16 @@ # Changelog -## VortexStepMethod v3.3.3 2026-05-21 +## VortexStepMethod v3.3.4 2026-05-30 + +### Added +- `PlotBackend`, `MakieBackend`, `ControlPlotsBackend`, and + `set_plot_backend!` so applications can explicitly choose which plotting + extension the backend-agnostic plotting API should use + +### Changed +- backend-agnostic plotting wrappers now route through the active plotting + backend, and each plotting extension initializes itself as the default only + when no backend has been selected yet ### Fixed - `MakieExt` and `ControlPlotsExt` no longer both define diff --git a/Project.toml b/Project.toml index 99c8a24b..95af8a15 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "VortexStepMethod" uuid = "ed3cd733-9f0f-46a9-93e0-89b8d4998dd9" authors = ["1-Bart-1 ", "Oriol Cayon and contributors"] -version = "3.3.3" +version = "3.3.4" [workspace] projects = ["examples", "examples_cp", "docs", "test"] @@ -43,7 +43,7 @@ VortexStepMethodMakieExt = "Makie" [compat] Colors = "0.13" -ControlPlots = "0.2.5" +ControlPlots = "0.2.5, 0.3" DefaultApplication = "1" DelimitedFiles = "1" DifferentiationInterface = "0.7.4" From d3f7ebaa110b0bede7926132c0dda9d005974d74 Mon Sep 17 00:00:00 2001 From: Uwe Fechner Date: Sat, 30 May 2026 21:49:31 +0200 Subject: [PATCH 12/16] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d0a9fcb7..1ab2f777 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ - backend-agnostic plotting wrappers now route through the active plotting backend, and each plotting extension initializes itself as the default only when no backend has been selected yet +- relaxed `ControlPlots` compatibility to include both `0.2.5` and `0.3` ### Fixed - `MakieExt` and `ControlPlotsExt` no longer both define From f720e5761bf53ae7e23905faf5c7b67bc7b050af Mon Sep 17 00:00:00 2001 From: Uwe Fechner Date: Sat, 30 May 2026 22:01:41 +0200 Subject: [PATCH 13/16] Allow ControlPlots 0.3 --- .gitignore | 1 + test/Project.toml | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 6c871484..edfc74d3 100644 --- a/.gitignore +++ b/.gitignore @@ -37,3 +37,4 @@ data/ram_air_kite/ram_air_kite_foil_cd_polar.csv data/ram_air_kite/ram_air_kite_foil_cm_polar.csv output/ output_cairo/ +examples_cp/.CondaPkg/ diff --git a/test/Project.toml b/test/Project.toml index 229362d1..8ca3f410 100644 --- a/test/Project.toml +++ b/test/Project.toml @@ -29,7 +29,7 @@ Aqua = "0.8" BenchmarkTools = "1" CSV = "0.10" CairoMakie = "0" -ControlPlots = "0.2.5" +ControlPlots = "0.2.5, 0.3" DataFrames = "1.7" Documenter = "1.8" Interpolations = "0.15, 0.16" From 289c4b16d9a4aeaefc09ad29a6d91bc111c1886f Mon Sep 17 00:00:00 2001 From: Uwe Fechner Date: Sat, 30 May 2026 22:05:39 +0200 Subject: [PATCH 14/16] Add files to .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index edfc74d3..b5872a75 100644 --- a/.gitignore +++ b/.gitignore @@ -38,3 +38,4 @@ data/ram_air_kite/ram_air_kite_foil_cm_polar.csv output/ output_cairo/ examples_cp/.CondaPkg/ +LocalPreferences.toml From 1251a175e39a1dc982d67e4859b8e9f80fe60e22 Mon Sep 17 00:00:00 2001 From: Uwe Fechner Date: Sat, 30 May 2026 22:09:19 +0200 Subject: [PATCH 15/16] Add install_controlplots --- bin/install_controlplots | 335 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 335 insertions(+) create mode 100755 bin/install_controlplots diff --git a/bin/install_controlplots b/bin/install_controlplots new file mode 100755 index 00000000..ea1ded43 --- /dev/null +++ b/bin/install_controlplots @@ -0,0 +1,335 @@ +#!/bin/bash -eu +# SPDX-FileCopyrightText: 2025 Uwe Fechner +# SPDX-License-Identifier: MIT +# +# Install and configure matplotlib for ControlPlots.jl (via PythonCall). +# +# Two backends are supported: +# 1) System Python – uses the matplotlib package installed by Ubuntu/Debian (apt). +# Fastest option; shares the system Python install. +# 2) CondaPkg – installs matplotlib into a pixi-managed Conda environment. +# Self-contained; does not require root / sudo. +# +# Both options configure the Qt (qtagg) backend for interactive plot windows. + +print_usage() { + echo "Usage:" + echo " ./bin/install_controlplots [--system | --conda] [-y | --yes] [-h | --help]" + echo "" + echo "Options:" + echo " --system Use the system Python and matplotlib (Ubuntu/Debian apt)" + echo " --conda Use CondaPkg (pixi) to install matplotlib" + echo " -y, --yes Non-interactive; accept defaults" + echo " -h, --help Show this help message" +} + +_backend="" # "system" or "conda" +_yes=false + +while [[ $# -gt 0 ]]; do + case $1 in + -h|--help) + print_usage + exit 0 + ;; + --system) + _backend="system" + shift + ;; + --conda) + _backend="conda" + shift + ;; + -y|--yes) + _yes=true + shift + ;; + *) + echo "Unknown option: $1" + print_usage + exit 1 + ;; + esac +done + +# Always run from the repository root (resolve from this script location). +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)" +cd "${REPO_ROOT}" + +CONTROLPLOTS_PROJECT="examples_cp" + +# ── Interactive backend selection ───────────────────────────────────────────── + +if [[ -z "$_backend" ]]; then + echo "Which matplotlib backend do you want to use for ControlPlots?" + echo "" + echo " 1) System Python (uses Ubuntu/Debian python3-matplotlib via apt)" + echo " - Requires sudo" + echo " - Shares the system-wide Python installation" + echo "" + echo " 2) CondaPkg (installs matplotlib into a pixi-managed Conda environment)" + echo " - No sudo required" + echo " - Self-contained; downloads ~200 MB on first use" + echo "" + if [[ "$_yes" == true ]]; then + _choice="2" + echo "Using default: 2 (CondaPkg)" + else + read -rp "Enter 1 or 2 [default: 2]: " _choice + fi + case "${_choice:-2}" in + 1) _backend="system" ;; + 2) _backend="conda" ;; + *) + echo "Invalid choice. Please enter 1 or 2." + exit 1 + ;; + esac +fi + +echo "" +echo "Selected backend: $_backend" +echo "" + +# ── Helper: install matplotlib via CondaPkg ─────────────────────────────────── + +_install_matplotlib_condapkg() { + echo "Installing matplotlib and pyqt into CondaPkg (pixi) environment..." + julia --project="${CONTROLPLOTS_PROJECT}" -e ' +using Pkg +# Ensure CondaPkg is available +if Base.find_package("CondaPkg") === nothing + Pkg.add("CondaPkg") +end +using CondaPkg +CondaPkg.add("matplotlib") +CondaPkg.add("pyqt") +CondaPkg.resolve() +println("matplotlib and pyqt installed in CondaPkg environment.") +' +} + +_verify_controlplots() { + echo "Verifying ControlPlots can be loaded..." + local _julia_prefix="" + if [[ "$(uname -s)" == "Linux" ]]; then + _julia_prefix="env -u LD_PRELOAD -u LD_LIBRARY_PATH" + fi + if $_julia_prefix julia --project="${CONTROLPLOTS_PROJECT}" -e ' +using ControlPlots +println("ControlPlots loaded successfully.") +'; then + echo "" + echo "✓ ControlPlots is working." + else + echo "" + echo "Warning: ControlPlots could not be loaded. Check the error output above." + echo "You may need to run this script again with the other backend option." + exit 1 + fi +} + +# ── Helper: write MPLBACKEND=qtagg to LocalPreferences.toml ────────────────── + +_set_mplbackend_qtagg() { + local _prefs_file="${CONTROLPLOTS_PROJECT}/LocalPreferences.toml" + # Remove any existing [ENV] section managed by this script. + if [[ -f "$_prefs_file" ]]; then + _tmp=$(mktemp) + awk ' + /^\[ENV\]/ { in_env=1; next } + in_env && /^\[/ { in_env=0 } + !in_env { print } + ' "$_prefs_file" > "$_tmp" + mv "$_tmp" "$_prefs_file" + fi + { + echo "" + echo "[ENV]" + echo "MPLBACKEND = \"qtagg\"" + } >> "$_prefs_file" + echo "Set MPLBACKEND=qtagg in $_prefs_file." +} + +# ── System Python backend ───────────────────────────────────────────────────── + +if [[ "$_backend" == "system" ]]; then + # Detect package manager and install python3-matplotlib if missing. + if [[ "$(uname -s)" == "Linux" ]]; then + if grep -qiE "ubuntu|debian" /etc/os-release 2>/dev/null; then + if ! dpkg -s python3-matplotlib &>/dev/null 2>&1 || ! dpkg -s python3-pyqt5 &>/dev/null 2>&1; then + echo "Installing python3-matplotlib and python3-pyqt5 via apt..." + sudo apt install -y python3-matplotlib python3-pyqt5 + else + echo "python3-matplotlib and python3-pyqt5 are already installed." + fi + elif grep -qi "fedora" /etc/os-release 2>/dev/null; then + if ! rpm -q python3-matplotlib &>/dev/null 2>&1 || ! rpm -q python3-pyqt5 &>/dev/null 2>&1; then + echo "Installing python3-matplotlib and python3-qt5 via dnf..." + sudo dnf install -y python3-matplotlib python3-pyqt5 + else + echo "python3-matplotlib and python3-pyqt5 are already installed." + fi + else + echo "Warning: Could not detect Ubuntu/Debian or Fedora." + echo "Please ensure python3-matplotlib is installed manually before continuing." + if [[ "$_yes" == false ]]; then + read -rp "Continue anyway? (y/n) [default: y]: " _cont + case "${_cont:-y}" in + n|N) echo "Aborted."; exit 1 ;; + esac + fi + fi + elif [[ "$(uname -s)" =~ ^(MINGW|MSYS|CYGWIN) ]]; then + # Windows (Git Bash): use pip to install matplotlib and PyQt5. + _pip="" + for _pip_candidate in pip3 pip; do + if command -v "$_pip_candidate" &>/dev/null; then + _pip="$_pip_candidate" + break + fi + done + if [[ -n "$_pip" ]]; then + if ! "$_pip" show matplotlib &>/dev/null 2>&1 || ! "$_pip" show PyQt5 &>/dev/null 2>&1; then + echo "Installing matplotlib and PyQt5 via pip..." + "$_pip" install --user matplotlib PyQt5 + else + echo "matplotlib and PyQt5 are already installed." + fi + else + echo "Warning: pip not found. Please install matplotlib and PyQt5 manually:" + echo " pip install matplotlib PyQt5" + if [[ "$_yes" == false ]]; then + read -rp "Continue anyway? (y/n) [default: y]: " _cont + case "${_cont:-y}" in + n|N) echo "Aborted."; exit 1 ;; + esac + fi + fi + else + echo "Warning: System Python backend is intended for Ubuntu/Debian/Fedora Linux or Windows." + echo "On this OS ($(uname -s)) you may need to install matplotlib manually." + if [[ "$_yes" == false ]]; then + read -rp "Continue anyway? (y/n) [default: y]: " _cont + case "${_cont:-y}" in + n|N) echo "Aborted."; exit 1 ;; + esac + fi + fi + + # Locate the system python3 executable. + _syspython="" + if [[ "$(uname -s)" =~ ^(MINGW|MSYS|CYGWIN) ]]; then + # On Windows (Git Bash) 'python3' may not exist; also try 'python'. + for _candidate in python3 python; do + if command -v "$_candidate" &>/dev/null; then + _syspython=$(command -v "$_candidate") + # Verify it runs (guards against the Windows Store stub). + if "$_syspython" --version &>/dev/null 2>&1; then + break + fi + _syspython="" + fi + done + else + for _candidate in python3 /usr/bin/python3; do + if command -v "$_candidate" &>/dev/null; then + _syspython=$(command -v "$_candidate") + break + fi + done + fi + if [[ -z "$_syspython" ]]; then + echo "Error: python3 not found on PATH. Please install python3." + exit 1 + fi + echo "Found system Python: $_syspython" + + # Remove the CondaPkg-managed environment so that PythonCall won't keep + # using a stale conda Python when switching to system Python. + if [[ -d "${CONTROLPLOTS_PROJECT}/.CondaPkg" ]]; then + echo "Removing ${CONTROLPLOTS_PROJECT}/.CondaPkg (switching from CondaPkg to system Python)..." + rm -rf "${CONTROLPLOTS_PROJECT}/.CondaPkg" + fi + # Unset JULIA_PYTHONCALL_EXE so the LocalPreferences.toml exe setting takes + # effect. + unset JULIA_PYTHONCALL_EXE + # Prevent CondaPkg from re-installing a Conda env in the current process. + export JULIA_CONDAPKG_BACKEND="Null" + export JULIA_PYTHONCALL_EXE="$_syspython" + + # Helper: update a LocalPreferences.toml file with the system Python settings. + _write_system_python_prefs() { + local _pf="$1" + if [[ -f "$_pf" ]]; then + _tmp=$(mktemp) + awk ' + /^\[PythonCall\]/ { in_sec=1; next } + /^\[PyCall\]/ { in_sec=1; next } + /^\[CondaPkg\]/ { in_sec=1; next } + in_sec && /^\[/ { in_sec=0 } + !in_sec { print } + ' "$_pf" > "$_tmp" + mv "$_tmp" "$_pf" + fi + { + echo "" + echo "[PythonCall]" + echo "exe = \"$_syspython\"" + echo "" + echo "[CondaPkg]" + echo "backend = \"Null\"" + } >> "$_pf" + echo "Written to $_pf." + } + + echo "" + echo "Saving python_exe=$_syspython to LocalPreferences.toml files..." + # Write to both the root project and examples_cp project, because + # `julia --project=examples_cp` reads examples_cp/LocalPreferences.toml, + # not the root one. + _write_system_python_prefs "LocalPreferences.toml" + _write_system_python_prefs "${CONTROLPLOTS_PROJECT}/LocalPreferences.toml" + + _set_mplbackend_qtagg + _verify_controlplots + + echo "" + echo "Done. ControlPlots will use the system Python (PythonCall) matplotlib with the Qt (qtagg) backend." + +# ── CondaPkg backend ────────────────────────────────────────────────────────── + +elif [[ "$_backend" == "conda" ]]; then + # Remove [PythonCall] exe, [CondaPkg] backend override, and legacy [PyCall] + # so PythonCall falls back to the CondaPkg-managed Python. + # Must be done in both the root and examples_cp LocalPreferences.toml. + _remove_python_prefs() { + local _pf="$1" + [[ -f "$_pf" ]] || return 0 + _tmp=$(mktemp) + awk ' + /^\[PythonCall\]/ { in_sec=1; next } + /^\[PyCall\]/ { in_sec=1; next } + /^\[CondaPkg\]/ { in_sec=1; next } + in_sec && /^\[/ { in_sec=0 } + !in_sec { print } + ' "$_pf" > "$_tmp" + if ! diff -q "$_pf" "$_tmp" &>/dev/null; then + mv "$_tmp" "$_pf" + echo "Removed [PythonCall]/[CondaPkg] sections from $_pf (switching to CondaPkg)." + else + rm -f "$_tmp" + fi + } + _remove_python_prefs "LocalPreferences.toml" + _remove_python_prefs "${CONTROLPLOTS_PROJECT}/LocalPreferences.toml" + + _install_matplotlib_condapkg + + _set_mplbackend_qtagg + _verify_controlplots + + echo "" + echo "Done. ControlPlots will use the CondaPkg-managed matplotlib with the Qt (qtagg) backend." +fi From 11fc477b828179af731718cb7369c2c79ccde89f Mon Sep 17 00:00:00 2001 From: Uwe Fechner Date: Sat, 30 May 2026 22:15:04 +0200 Subject: [PATCH 16/16] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3ba23ceb..ce0a34a3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,7 +11,7 @@ - backend-agnostic plotting wrappers now route through the active plotting backend, and each plotting extension initializes itself as the default only when no backend has been selected yet -- relaxed `ControlPlots` compatibility to include both `0.2.5` and `0.3 +- relaxed `ControlPlots` compatibility to include both `0.2.5` and `0.3` ## VortexStepMethod v3.3.3 2026-05-21