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
13 changes: 13 additions & 0 deletions .github/workflows/CI.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,11 @@ jobs:
- os: ubuntu-latest
version: '1.11'
arch: x64
prefix: xvfb-run -a
- os: ubuntu-latest
version: '1.12'
arch: x64
prefix: xvfb-run -a
- os: macOS-latest
version: '1.12'
arch: aarch64
Expand All @@ -56,9 +61,17 @@ jobs:
version: ${{ matrix.version }}
arch: ${{ matrix.arch }}

- name: Install display dependencies for GLMakie
if: runner.os == 'Linux'
run: |
sudo apt-get update
sudo apt-get install -y xvfb libgl1-mesa-dri

- uses: julia-actions/cache@v2
- uses: julia-actions/julia-buildpkg@v1
- uses: julia-actions/julia-runtest@v1
with:
prefix: ${{ matrix.prefix }}

- name: Upload PNG plots as artifact
if: always()
Expand Down
6 changes: 3 additions & 3 deletions data/2plate_kite/aero_geometry.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@ wing_sections:
headers: [airfoil_id, LE_x, LE_y, LE_z, TE_x, TE_y, TE_z]
data:
# Right section: points 2 (LE) and 3 (TE)
- [1, -0.5, 1.0, 2.0, 0.5, 1.0, 2.3]
- [1, -0.5, 1.0, 2.0, 0.5, 1.0, 2.2]

# Center section: points 4 (LE) and 5 (TE)
- [1, -0.5, 0.0, 2.5, 0.5, 0.0, 2.8]
- [1, -0.5, 0.0, 2.5, 0.5, 0.0, 2.7]

# Left section: points 6 (LE) and 7 (TE)
- [1, -0.5, -1.0, 2.0, 0.5, -1.0, 2.3]
- [1, -0.5, -1.0, 2.0, 0.5, -1.0, 2.2]

wing_airfoils:
alpha_range: [-180, 180, 1] # deg
Expand Down
3 changes: 2 additions & 1 deletion data/2plate_kite/settings.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
system:
log_file: "data/2plate" # filename without extension [replay only]
# use / as path delimiter, even on Windows
# use / as path delimiter, even on Windows
sample_freq: 20 # sample frequency in Hz
g_earth: 9.81

initial:
Expand Down
3 changes: 2 additions & 1 deletion examples/coupled_2plate_kite.jl
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ update_aero_yaml_from_struc_yaml!(struc_yaml, aero_yaml)
set = Settings("system.yaml")
set.g_earth = 0.0
vsm_set = VortexStepMethod.VSMSettings(
joinpath(get_data_path(), "vsm_settings.yaml"))
joinpath(get_data_path(), "vsm_settings.yaml");
data_prefix=false)

sys = load_sys_struct_from_yaml(struc_yaml;
system_name=MODEL_NAME, set, vsm_set)
Expand Down
3 changes: 2 additions & 1 deletion examples/coupled_2plate_kite_linear_vsm.jl
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ update_aero_yaml_from_struc_yaml!(struc_yaml, aero_yaml)

set = Settings("system.yaml")
vsm_set = VortexStepMethod.VSMSettings(
joinpath(get_data_path(), "vsm_settings.yaml"))
joinpath(get_data_path(), "vsm_settings.yaml");
data_prefix=false)

sys = load_sys_struct_from_yaml(struc_yaml;
system_name=MODEL_NAME, set, vsm_set)
Expand Down
3 changes: 2 additions & 1 deletion examples/coupled_linearize.jl
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ update_aero_yaml_from_struc_yaml!(struc_yaml, aero_yaml)

set = Settings("system.yaml")
vsm_set = VortexStepMethod.VSMSettings(
joinpath(get_data_path(), "vsm_settings.yaml"))
joinpath(get_data_path(), "vsm_settings.yaml");
data_prefix=false)

sys = load_sys_struct_from_yaml(struc_yaml;
system_name="2plate_kite", set, vsm_set)
Expand Down
3 changes: 2 additions & 1 deletion examples/coupled_realtime_visualization.jl
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ update_aero_yaml_from_struc_yaml!(struc_yaml, aero_yaml)
set = Settings("system.yaml")
set.profile_law = 3
vsm_set = VortexStepMethod.VSMSettings(
joinpath(get_data_path(), "vsm_settings.yaml"))
joinpath(get_data_path(), "vsm_settings.yaml");
data_prefix=false)
sys = load_sys_struct_from_yaml(struc_yaml;
system_name="2plate_kite", set, vsm_set)
sam = SymbolicAWEModel(set, sys)
Expand Down
3 changes: 2 additions & 1 deletion examples/coupled_simple_lin_model.jl
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ update_aero_yaml_from_struc_yaml!(struc_yaml, aero_yaml)

set = Settings("system.yaml")
vsm_set = VortexStepMethod.VSMSettings(
joinpath(get_data_path(), "vsm_settings.yaml"))
joinpath(get_data_path(), "vsm_settings.yaml");
data_prefix=false)

sys = load_sys_struct_from_yaml(struc_yaml;
system_name="2plate_kite", set, vsm_set)
Expand Down
17 changes: 15 additions & 2 deletions examples/sam_tutorial.jl
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ end

# --- STEP 3: Add a pulley ---

push!(points, Point(22, [0, 0, set.l_tether + 5], DYNAMIC))
push!(points, Point(22, [0, 0, set.l_tether + 5], DYNAMIC; extra_mass=0.1))
push!(points, Point(23, [1, 0, set.l_tether + 5], STATIC))
push!(segments, Segment(21, 21, 22,
seg_stiffness, seg_damping, seg_diameter))
Expand All @@ -94,13 +94,26 @@ end

# --- STEP 4: Add a kite ---

vsm_wing = VortexStepMethod.Wing(set; prn=false)
vsm_set = VortexStepMethod.VSMSettings(
joinpath(get_data_path(), "vsm_settings.yaml");
data_prefix=false)
vsm_wing = VortexStepMethod.Wing(set, vsm_set; prn=false)
vsm_aero = BodyAerodynamics([vsm_wing])
vsm_solver = Solver(vsm_aero;
solver_type=NONLIN, atol=2e-8, rtol=2e-8)
wings = [SymbolicAWEModels.Wing(1, vsm_aero, vsm_wing,
vsm_solver, [], I(3), [0.5, 0, set.l_tether + 6])]

# WING-type points: 3 LE/TE pairs matching the 3 aero sections
wing_z = set.l_tether + 6
for (i, y) in enumerate([-1.0, 0.0, 1.0])
n = length(points)
push!(points, Point(n + 1, [-0.5, y, wing_z],
WING; wing=1, transform=1, extra_mass=0.1))
push!(points, Point(n + 2, [0.5, y, wing_z],
WING; wing=1, transform=1, extra_mass=0.1))
end

sys = SystemStructure("wing", set;
points, segments, tethers, winches, pulleys,
wings, transforms)
Expand Down
10 changes: 8 additions & 2 deletions examples/static_load_2plate_kite.jl
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ kite structure and simulate a few steps under those loads.
"""

using GLMakie
using SymbolicAWEModels, KiteUtils
using SymbolicAWEModels, VortexStepMethod, KiteUtils
using LinearAlgebra
using SymbolicAWEModels: Point

Expand All @@ -30,10 +30,16 @@ set_data_path(joinpath(pkg_root, "data", MODEL_NAME))

struc_yaml = joinpath(get_data_path(),
"quat_struc_geometry.yaml")
aero_yaml = joinpath(get_data_path(), "aero_geometry.yaml")
update_aero_yaml_from_struc_yaml!(struc_yaml, aero_yaml)

set = Settings("system.yaml")
vsm_set = VSMSettings(joinpath(get_data_path(),
"vsm_settings.yaml"); data_prefix=false)

sys = load_sys_struct_from_yaml(struc_yaml;
system_name=MODEL_NAME, set, aero_mode=AERO_NONE)
system_name=MODEL_NAME, set, vsm_set,
aero_mode=AERO_NONE)

function apply_loads!(points, F)
for (i, point) in enumerate(points)
Expand Down
99 changes: 97 additions & 2 deletions ext/SymbolicAWEModelsMakieExt.jl
Original file line number Diff line number Diff line change
Expand Up @@ -3026,9 +3026,12 @@ function Makie.plot(syss::Vector{<:SystemStructure};
push!(segment_colors_list, color)
obs = build_geometry_observables(sys, triggers[i])

# Plot with observable data
# Plot with observable data (use vector color to match
# setup_segment_hover_events! which assigns Vector{RGBA})
seg_colors = fill(to_color(color), length(sys.segments))
seg_plot = linesegments!(scene, obs[:segment_points];
color=color, linewidth=2, transparency=true)
color=seg_colors, linewidth=2,
transparency=true)
push!(all_plots, seg_plot)
push!(segment_plots, seg_plot)

Expand Down Expand Up @@ -3210,6 +3213,98 @@ function SymbolicAWEModels.record(lg::SysLog, sys::SystemStructure, filename::St
return scene
end

"""
record(logs::Vector{<:SysLog}, syss::Vector{<:SystemStructure},
filename::String; framerate=30, colors=Makie.wong_colors(),
vector_scale=0.2, kwargs...)

Record a multi-system SysLog animation to a video or GIF file.
The output format is determined by the file extension
(`.mp4`, `.gif`, `.mkv`, `.webm`).

# Arguments
- `logs::Vector{<:SysLog}`: Simulation logs (one per system)
- `syss::Vector{<:SystemStructure}`: System structures
(must match logs length)
- `filename::String`: Output filename
(e.g., `"sim.mp4"`, `"sim.gif"`)

# Keyword Arguments
- `framerate::Int=30`: Framerate (frames per second)
- `colors`: Color palette for distinguishing systems
(default: wong_colors())
- `vector_scale::Real=0.2`: Scale factor for wing arrows
- All other keyword arguments are passed through to `plot`

# Returns
- The Scene object used for recording

# Example
```julia
record([log1, log2], [sys1, sys2], "output.mp4")
record([log1, log2], [sys1, sys2], "output.mp4";
framerate=60, colors=[:red, :blue])
```
"""
function SymbolicAWEModels.record(
logs::Vector{<:SysLog},
syss::Vector{<:SystemStructure},
filename::String;
framerate::Int=30,
colors=Makie.wong_colors(),
vector_scale::Real=0.2,
kwargs...)
length(logs) == length(syss) ||
error("logs and systems must have same length")
n_frames = minimum(length(lg.syslog) for lg in logs)
n_frames == 0 &&
error("Empty SysLog provided for recording")

println("Recording multi-system video to: $filename")
println("Framerate: $framerate fps")
println("Total frames: $n_frames")
println("Systems: $(length(syss))")

# Initialize all systems with first state
for (sys, lg) in zip(syss, logs)
update_from_sysstate!(sys, lg.syslog[1])
end

# Create plot with observables for dynamic updates
scene = plot(syss; colors, vector_scale,
use_observables=true, kwargs...)

# Record video by stepping through all frames
Makie.record(scene, filename, 1:n_frames;
framerate=framerate) do frame_num
# Update each system's state
for (sys, lg) in zip(syss, logs)
frame = min(frame_num, length(lg.syslog))
update_from_sysstate!(sys, lg.syslog[frame])
end
# Trigger all geometry observables
if !isnothing(PLOT_MULTI_GEOMETRY_OBS[])
for obs in PLOT_MULTI_GEOMETRY_OBS[]
obs[] = time()
end
end

# Yield to render thread for observable updates
sleep(0.001)

# Print progress every 10% or at the end
if frame_num % max(1, div(n_frames, 10)) == 0 ||
frame_num == n_frames
pct = round(100 * frame_num / n_frames, digits=1)
println(" Progress: $pct% " *
"($frame_num/$n_frames frames)")
end
end

println("Video saved successfully!")
return scene
end

"""
setup_replay_controls!(scene, n_frames, update_frame!, get_time, get_dt;
replay_speed=1.0, autoplay=false, loop=false)
Expand Down
4 changes: 4 additions & 0 deletions src/generate_system/accessors.jl
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,10 @@ end
sys::SystemStructure{VSMWing}, idx::Int64, component::Int)
get_brake(sys::SystemStructure{VSMWing}, idx::Int64) = sys.winches[idx].brake
@register_symbolic get_brake(sys::SystemStructure{VSMWing}, idx::Int64)
get_speed_controlled(sys::SystemStructure{VSMWing}, idx::Int64) =
sys.winches[idx].speed_controlled
@register_symbolic get_speed_controlled(
sys::SystemStructure{VSMWing}, idx::Int64)
get_fix_point_sphere(sys::SystemStructure{VSMWing}, idx::Int64) =
sys.points[idx].fix_sphere
@register_symbolic get_fix_point_sphere(sys::SystemStructure{VSMWing}, idx::Int64)
Expand Down
6 changes: 5 additions & 1 deletion src/generate_system/winch_eqs.jl
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ function winch_eqs!(eqs, defaults, winches, tethers, points, psys, pset;
winch_force(t)[eachindex(winches)]
winch_force_vec(t)[1:3, eachindex(winches)]
brake(t)[eachindex(winches)]
speed_controlled(t)[eachindex(winches)]
# Winch motor and friction dynamics
ω_motor(t)[eachindex(winches)]
tau_friction(t)[eachindex(winches)]
Expand Down Expand Up @@ -60,12 +61,15 @@ function winch_eqs!(eqs, defaults, winches, tethers, points, psys, pset;
eqs = [
eqs
brake[winch.idx] ~ get_brake(psys, winch.idx)
speed_controlled[winch.idx] ~
get_speed_controlled(psys, winch.idx)
D(tether_len[winch.idx]) ~
ifelse(brake[winch.idx] == true, 0,
tether_vel[winch.idx])
D(tether_vel[winch.idx]) ~
ifelse(brake[winch.idx] == true, 0,
tether_acc[winch.idx])
ifelse(speed_controlled[winch.idx] == true,
0, tether_acc[winch.idx]))

# Winch motor, gear, and friction dynamics
ω_motor[winch.idx] ~
Expand Down
3 changes: 3 additions & 0 deletions src/model_management.jl
Original file line number Diff line number Diff line change
Expand Up @@ -379,6 +379,9 @@ function init!(sam::SymbolicAWEModel;
tunable_params::Bool=false
)
prn && @info "Initializing $(sam.sys_struct.name) model..."
sam.sys_struct isa SystemStructure{VSMWing} || error(
"Equation generation requires SystemStructure{VSMWing}, " *
"got SystemStructure{$(eltype(sam.sys_struct.wings))}.")
time = @elapsed begin
if isnothing(solver)
solver = if sam.set.solver == "FBDF"
Expand Down
2 changes: 1 addition & 1 deletion src/simulate.jl
Original file line number Diff line number Diff line change
Expand Up @@ -301,7 +301,7 @@ function sim_reposition!(
# Update the transform with the new target pose
sys_struct.transforms[1].elevation = target_elevation
sys_struct.transforms[1].azimuth = target_azimuth
sys_struct.transforms[1].heading = target_heading - sys_struct.wings[1].heading
sys_struct.transforms[1].heading = target_heading

# Apply the transformation without changing velocities
SymbolicAWEModels.reposition!(sys_struct.transforms, sys_struct)
Expand Down
Loading
Loading