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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
### Features

- Add `v7 list-reusable-models` command. [#146](https://github.com/nablaflow/cli/pull/146)
- Add support for reusable models in `v7 batch`.

# 1.1.0 - 2026-01-28

Expand Down
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,16 @@ dir

An example is available under [examples/aerocloud/v7/batch](examples/aerocloud/v7/batch).

> [!TIP]
> You can skip uploading files and use a reusable model by just setting its ID in each simulation's `params.json`:
>
> ```json
> {
> "model_id": "b7203095-f9fd-4270-ac6d-03c46b02932b",
> ...
> }
> ```

With the folder ready, run the following command to enter the Batch mode:

```bash
Expand Down
10 changes: 9 additions & 1 deletion build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use std::{
path::Path,
};
use syn::{
ItemEnum,
ItemEnum, ItemStruct,
visit_mut::{self, VisitMut},
};

Expand Down Expand Up @@ -47,4 +47,12 @@ impl VisitMut for AddClapValueEnum {

visit_mut::visit_item_enum_mut(self, node);
}

fn visit_item_struct_mut(&mut self, node: &mut ItemStruct) {
if node.ident == "Quaternion" {
node.attrs.push(syn::parse_quote! { #[derive(PartialEq)] });
}

visit_mut::visit_item_struct_mut(self, node);
}
}
5 changes: 4 additions & 1 deletion src/aerocloud/extra_types.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::aerocloud::types::{
BoundaryLayerTreatment, FileUnit, Fluid, FluidSpeed, GroundOffset,
BoundaryLayerTreatment, FileUnit, Fluid, FluidSpeed, GroundOffset, Id,
Quaternion, SimulationQuality, UpdatePartV7Params, YawAngle, YawAngles,
};
use color_eyre::eyre;
Expand Down Expand Up @@ -36,6 +36,9 @@ pub struct CreateSimulationV7ParamsFromJson {

#[serde(default)]
pub boundary_layer_treatment: Option<BoundaryLayerTreatment>,

#[serde(default)]
pub model_id: Option<Id>,
}

impl CreateSimulationV7ParamsFromJson {
Expand Down
109 changes: 89 additions & 20 deletions src/commands/aerocloud/v7/batch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ const SLEEP_FOR_FEEDBACK: Duration = Duration::from_millis(100);

pub async fn run(client: &Client, root_dir: Option<&Path>) -> eyre::Result<()> {
let sims = if let Some(root_dir) = root_dir {
let sims = SimulationParams::many_from_root_dir(root_dir).await?;
let sims = SimulationParams::many_from_root_dir(client, root_dir).await?;

if sims.is_empty() {
tracing::error!("no simulations found in `{}`", root_dir.display());
Expand All @@ -89,16 +89,20 @@ pub async fn run(client: &Client, root_dir: Option<&Path>) -> eyre::Result<()> {
result
}

pub fn refresh_sims_in_background(root_dir: &Path, tx: mpsc::Sender<Event>) {
pub fn refresh_sims_in_background(
client: Client,
root_dir: &Path,
tx: mpsc::Sender<Event>,
) {
let root_dir = root_dir.to_owned();

tokio::spawn(async move {
// NOTE: sleep so that reloading popup is shown and user has visual feedback on the
// operation.
time::sleep(SLEEP_FOR_FEEDBACK).await;

let sims = SimulationParams::many_from_root_dir(&root_dir).await?;
tx.send(Event::SimsReloaded(sims)).await?;
let res = SimulationParams::many_from_root_dir(&client, &root_dir).await;
tx.send(Event::SimsReloaded(res)).await?;

Ok::<(), eyre::Report>(())
});
Expand Down Expand Up @@ -126,6 +130,7 @@ enum ActiveState {
},
ConfirmSubmit,
ReloadingSims,
ReloadingSimsFailed(String),
Submitting {
cancellation_token: CancellationToken,
bytes_count: ByteSize,
Expand Down Expand Up @@ -157,7 +162,7 @@ pub enum Event {
ProjectsUpdated(eyre::Result<Vec<ProjectV7>>),
ProjectSelected(Box<ProjectV7>),
FileUploaded(ByteSize),
SimsReloaded(Vec<SimulationParams>),
SimsReloaded(eyre::Result<Vec<SimulationParams>>),
SimSubmitted {
internal_id: Uuid,
res: eyre::Result<Box<SimulationV7>, eyre::Report>,
Expand Down Expand Up @@ -327,7 +332,11 @@ impl Batch {
}
(KeyCode::Char('r'), _) => {
if let Some(root_dir) = self.root_dir.as_ref() {
refresh_sims_in_background(root_dir, tx.clone());
refresh_sims_in_background(
self.client.clone(),
root_dir,
tx.clone(),
);
}

next_state = Some(ActiveState::ReloadingSims);
Expand Down Expand Up @@ -374,7 +383,11 @@ impl Batch {
}
(KeyCode::Char('r'), _) => {
if let Some(root_dir) = self.root_dir.as_ref() {
refresh_sims_in_background(root_dir, tx.clone());
refresh_sims_in_background(
self.client.clone(),
root_dir,
tx.clone(),
);
}

next_state = Some(ActiveState::ReloadingSims);
Expand Down Expand Up @@ -469,14 +482,14 @@ impl Batch {
_ => {}
}
}
(ActiveState::ReloadingSims, Event::KeyPressed(key_event)) => {
if let KeyCode::Char('q') = key_event.code {
next_state = Some(ActiveState::ViewingList);
}
(ActiveState::ReloadingSims, Event::KeyPressed(key_event))
if key_event.code == KeyCode::Char('q') =>
{
next_state = Some(ActiveState::ViewingList);
}
(
ActiveState::ReloadingSims,
Event::SimsReloaded(mut simulations),
Event::SimsReloaded(Ok(mut simulations)),
) => {
// Copy over selection status.
for new_sim in &mut simulations {
Expand All @@ -490,13 +503,24 @@ impl Batch {
self.simulations = simulations;
next_state = Some(ActiveState::ViewingList);
}
(ActiveState::ReloadingSims, Event::SimsReloaded(Err(err))) => {
next_state = Some(ActiveState::ReloadingSimsFailed(
human_err_report(&err),
));
}
(
ActiveState::ReloadingSimsFailed(..),
Event::KeyPressed(key_event),
) if key_event.code == KeyCode::Char('q') => {
next_state = Some(ActiveState::ViewingList);
}
(
ActiveState::Submitting {
cancellation_token, ..
},
Event::KeyPressed(key_event),
) => {
if let KeyCode::Char('q') = key_event.code {
if key_event.code == KeyCode::Char('q') {
cancellation_token.cancel();

// TODO: should we ask for confirmation?
Expand Down Expand Up @@ -618,13 +642,16 @@ impl Batch {

match state {
ActiveState::ReloadingSims => {
Batch::render_reloading_sims_popup(area, buf);
Self::render_reloading_sims_popup(area, buf);
}
ActiveState::ReloadingSimsFailed(error) => {
Self::render_reloading_sims_failed_popup(area, buf, error);
}
ActiveState::ConfirmExit { .. } => {
Batch::render_exit_popup(area, buf);
Self::render_exit_popup(area, buf);
}
ActiveState::ConfirmSubmit => {
Batch::render_submit_confirmation_popup(simulations, area, buf);
Self::render_submit_confirmation_popup(simulations, area, buf);
}
ActiveState::Submitting {
bytes_count,
Expand All @@ -633,10 +660,10 @@ impl Batch {
sims_progress,
..
} => {
assert!(*bytes_count > ByteSize::default());
assert!(*bytes_count >= ByteSize::default());
assert!(*sims_count > 0);

Batch::render_submitting(
Self::render_submitting(
*bytes_count,
*bytes_progress,
*sims_count,
Expand Down Expand Up @@ -767,6 +794,44 @@ impl Batch {
Widget::render(&paragraph, area, buf);
}

fn render_reloading_sims_failed_popup(
area: Rect,
buf: &mut Buffer,
error: &str,
) {
let lines = {
let mut l = vec![Line::default()];

for line in error.lines() {
l.push(Line::from(line));
}

l.push(Line::default());

l
};

let area = center(
area,
Constraint::Percentage(55),
Constraint::Length(u16::try_from(lines.len()).unwrap_or(5) + 2), // top and bottom border + content
);

let block = Block::bordered()
.title(
Line::from(Span::styled(" Failed to reload config ", STYLE_BOLD))
.centered(),
)
.title_bottom(Line::raw(" (q) close and continue ").centered())
.border_set(border::THICK)
.style(STYLE_ERROR);

let paragraph = Paragraph::new(lines).block(block);

Widget::render(&Clear, area, buf);
Widget::render(&paragraph, area, buf);
}

fn render_submit_confirmation_popup(
simulations: &[SimulationParams],
area: Rect,
Expand Down Expand Up @@ -911,7 +976,11 @@ impl Batch {
.padding(Padding::vertical(1))
.title(Line::from("Uploading files").centered()),
)
.ratio(bytes_progress.0 as f64 / bytes_count.0 as f64)
.ratio(if bytes_count.0 == 0 {
1.0
} else {
bytes_progress.0 as f64 / bytes_count.0 as f64
})
.label(Span::styled(
format!("{bytes_progress}/{bytes_count}"),
Style::new().bold(),
Expand Down Expand Up @@ -1022,7 +1091,7 @@ impl From<&SimulationParams> for ListItem<'_> {
}
}

if p.files.is_empty() {
if p.model_params.is_empty() {
spans.push(Span::styled("(no files) ", STYLE_ERROR));
}

Expand Down
Loading