forked from 1b-agent/fieldcraft-upstream
Rename the project to FieldCraft
Crates fieldcraft-*, binaries fieldcraft and fieldcraft-cli, the .fieldcraft file (format tag fieldcraft/1, manifest fieldcraft.json), FIELDCRAFT_* environment variables, the MCP server name, egui widget ids (fc_*), the app icon document FieldCraft.icon and its generated family, docs and licences. No compatibility shim: no files of the earlier name exist. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm
This commit is contained in:
1 parent
9bc4e1b182
commit
d5bd74570f
268 files changed
+1666
-1666
No files matched your search
@@ -61,7 +61,7 @@ fn button_style(t: &Tokens) -> IconButtonStyle {
|
||||
}
|
||||
}
|
||||
|
||||
/// ModelCraft's mark: the application icon's bracket (`assets/app-icon/icon-small.svg`) drawn in
|
||||
/// FieldCraft's mark: the application icon's bracket (`assets/app-icon/icon-small.svg`) drawn in
|
||||
/// code from the SVG's own coordinates, without its tile, each face in the middle of its gradient.
|
||||
fn mark(p: &egui::Painter, c: egui::Pos2) {
|
||||
// The bracket spans y 141–883 in the SVG; draw it 22 points tall around its centre.
|
||||
@@ -142,7 +142,7 @@ pub fn app_bar(app: &mut ModelApp, ui: &mut egui::Ui) {
|
||||
let t = Tokens::get(app.ui.dark);
|
||||
let style = button_style(&t);
|
||||
let mut anchors: Vec<(Quick, Rect)> = Vec::new();
|
||||
egui::Panel::top("mc_appbar").exact_size(HEIGHT).show_separator_line(false).frame(egui::Frame::NONE.fill(t.app_bar)).show(ui, |ui| {
|
||||
egui::Panel::top("fc_appbar").exact_size(HEIGHT).show_separator_line(false).frame(egui::Frame::NONE.fill(t.app_bar)).show(ui, |ui| {
|
||||
let r = ui.max_rect();
|
||||
app.place("app_bar", r);
|
||||
if app.custom_titlebar || app.integrated_titlebar {
|
||||
@@ -154,7 +154,7 @@ pub fn app_bar(app: &mut ModelApp, ui: &mut egui::Ui) {
|
||||
let lay = bar_layout(r, app.integrated_titlebar && !fullscreen(ui.ctx()), captions, version_w);
|
||||
mark(ui.painter(), lay.mark);
|
||||
if let Some(at) = lay.name {
|
||||
ui.painter().text(at, Align2::LEFT_CENTER, "ModelCraft", FontId::proportional(14.0), t.app_bar_text);
|
||||
ui.painter().text(at, Align2::LEFT_CENTER, "FieldCraft", FontId::proportional(14.0), t.app_bar_text);
|
||||
}
|
||||
let (undo, redo) = app.session.undo_depth();
|
||||
let busy = app.busy().is_some();
|
||||
@@ -185,7 +185,7 @@ pub fn app_bar(app: &mut ModelApp, ui: &mut egui::Ui) {
|
||||
}
|
||||
// Solve: the one prominent action, a filled button with its label.
|
||||
let solve = lay.solve;
|
||||
let resp = ui.interact(solve, ui.id().with("mc_solve"), Sense::click());
|
||||
let resp = ui.interact(solve, ui.id().with("fc_solve"), Sense::click());
|
||||
let can = !app.session.model.steps().is_empty() && !busy;
|
||||
let fill = if resp.hovered() && can {
|
||||
t.accent.gamma_multiply(1.15)
|
||||
@@ -230,7 +230,7 @@ pub fn app_bar(app: &mut ModelApp, ui: &mut egui::Ui) {
|
||||
if tab_w > 40.0 {
|
||||
let tab = Rect::from_min_size(pos2(tab_x, r.top() + 5.0), vec2(tab_w, r.height() - 5.0));
|
||||
let path = app.session.path.clone();
|
||||
craft_ui::tabs::Tab::new(egui::Id::new("mc_document_tab"), &label, true).show_at(ui, tab, |ui, resp| {
|
||||
craft_ui::tabs::Tab::new(egui::Id::new("fc_document_tab"), &label, true).show_at(ui, tab, |ui, resp| {
|
||||
ui.painter().rect_filled(tab, CornerRadius { nw: 5, ne: 5, sw: 0, se: 0 }, t.toolbar);
|
||||
let clip = tab.shrink2(vec2(10.0, 0.0));
|
||||
ui.painter().with_clip_rect(clip).text(
|
||||
@@ -297,7 +297,7 @@ fn quick_button(
|
||||
selected: bool,
|
||||
) -> bool {
|
||||
let resp = ui
|
||||
.scope_builder(egui::UiBuilder::new().max_rect(rect).id_salt(("mc_quick", key)), |ui| {
|
||||
.scope_builder(egui::UiBuilder::new().max_rect(rect).id_salt(("fc_quick", key)), |ui| {
|
||||
ui.add_enabled_ui(enabled, |ui| {
|
||||
IconButton::new(tip, rect.size(), style).selected(selected).show(ui, |_, _, _| {}).on_hover_text(tip).on_disabled_hover_text(tip)
|
||||
})
|
||||
@@ -326,7 +326,7 @@ fn help_menu(app: &mut ModelApp, ui: &mut egui::Ui) {
|
||||
ui.label("Left drag orbits (or pans or zooms: the navigation bar), right or middle drag pans, the wheel zooms, F or a double click fits.");
|
||||
ui.label(format!("S or {} searches every command; the command line at the bottom runs `id {{json}}`.", shortcut("K")));
|
||||
ui.separator();
|
||||
ui.label(egui::RichText::new(format!("ModelCraft {}", crate::VERSION)).color(t.text_dim));
|
||||
ui.label(egui::RichText::new(format!("FieldCraft {}", crate::VERSION)).color(t.text_dim));
|
||||
if let Some(n) = open {
|
||||
app.ui.menu = None;
|
||||
app.open_sample(n);
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
//! ATTRIBUTION.md.
|
||||
|
||||
use egui::{Color32, FontId, Pos2, Rect, RichText, Sense, Stroke, Vec2, pos2, vec2};
|
||||
use modelcraft_model::Model;
|
||||
use fieldcraft_model::Model;
|
||||
use serde::Serialize;
|
||||
use serde_json::{Value, json};
|
||||
|
||||
@@ -267,7 +267,7 @@ enum Act {
|
||||
pub fn browser(app: &mut ModelApp, ui: &mut egui::Ui) {
|
||||
let t = Tokens::get(app.ui.dark);
|
||||
let mut acts: Vec<Act> = Vec::new();
|
||||
egui::Panel::left("mc_browser")
|
||||
egui::Panel::left("fc_browser")
|
||||
.exact_size(WIDTH)
|
||||
.resizable(false)
|
||||
.show_separator_line(true)
|
||||
@@ -276,7 +276,7 @@ pub fn browser(app: &mut ModelApp, ui: &mut egui::Ui) {
|
||||
app.place("browser", ui.max_rect());
|
||||
ui.label(RichText::new("BROWSER").font(crate::theme::label_font(ui.ctx(), 10.5)).color(t.text_dim));
|
||||
ui.add_space(4.0);
|
||||
egui::ScrollArea::vertical().id_salt("mc_browser_scroll").auto_shrink([false, false]).show(ui, |ui| {
|
||||
egui::ScrollArea::vertical().id_salt("fc_browser_scroll").auto_shrink([false, false]).show(ui, |ui| {
|
||||
ui.spacing_mut().item_spacing.y = 0.0;
|
||||
rows(app, ui, &mut acts);
|
||||
});
|
||||
@@ -313,7 +313,7 @@ fn rows(app: &mut ModelApp, ui: &mut egui::Ui, acts: &mut Vec<Act>) {
|
||||
draw_row(
|
||||
app,
|
||||
ui,
|
||||
egui::Id::new("mc_row_root"),
|
||||
egui::Id::new("fc_row_root"),
|
||||
&Row { icon: Some("ui.views"), label: &name, note: dirty.then(|| "unsaved".into()), ..Default::default() },
|
||||
);
|
||||
// Geometry.
|
||||
@@ -323,7 +323,7 @@ fn rows(app: &mut ModelApp, ui: &mut egui::Ui, acts: &mut Vec<Act>) {
|
||||
let r = draw_row(
|
||||
app,
|
||||
ui,
|
||||
egui::Id::new("mc_row_geometry"),
|
||||
egui::Id::new("fc_row_geometry"),
|
||||
&Row {
|
||||
depth: 1,
|
||||
fold: Some(open(app, "geometry")),
|
||||
@@ -345,14 +345,14 @@ fn rows(app: &mut ModelApp, ui: &mut egui::Ui, acts: &mut Vec<Act>) {
|
||||
draw_row(
|
||||
app,
|
||||
ui,
|
||||
egui::Id::new("mc_row_design"),
|
||||
egui::Id::new("fc_row_design"),
|
||||
&Row { depth: 2, icon: Some("geometry.open"), label: &label, note: Some(format!("{bodies} bodies")), ..Default::default() },
|
||||
);
|
||||
for (i, b) in d.bodies.iter().enumerate() {
|
||||
draw_row(
|
||||
app,
|
||||
ui,
|
||||
egui::Id::new(("mc_row_body", i)),
|
||||
egui::Id::new(("fc_row_body", i)),
|
||||
&Row { depth: 3, icon: Some("geometry.bodies"), label: &b.name, ..Default::default() },
|
||||
);
|
||||
}
|
||||
@@ -361,7 +361,7 @@ fn rows(app: &mut ModelApp, ui: &mut egui::Ui, acts: &mut Vec<Act>) {
|
||||
draw_row(
|
||||
app,
|
||||
ui,
|
||||
egui::Id::new(("mc_row_surface", i)),
|
||||
egui::Id::new(("fc_row_surface", i)),
|
||||
&Row { depth: 2, icon: Some("geometry.import.mesh"), label: s, ..Default::default() },
|
||||
);
|
||||
}
|
||||
@@ -369,7 +369,7 @@ fn rows(app: &mut ModelApp, ui: &mut egui::Ui, acts: &mut Vec<Act>) {
|
||||
draw_row(
|
||||
app,
|
||||
ui,
|
||||
egui::Id::new("mc_row_no_geometry"),
|
||||
egui::Id::new("fc_row_no_geometry"),
|
||||
&Row { depth: 2, label: "No geometry: Open Design", dim: true, ..Default::default() },
|
||||
);
|
||||
}
|
||||
@@ -379,7 +379,7 @@ fn rows(app: &mut ModelApp, ui: &mut egui::Ui, acts: &mut Vec<Act>) {
|
||||
let r = draw_row(
|
||||
app,
|
||||
ui,
|
||||
egui::Id::new("mc_row_mesh"),
|
||||
egui::Id::new("fc_row_mesh"),
|
||||
&Row {
|
||||
depth: 1,
|
||||
fold: Some(open(app, "mesh")),
|
||||
@@ -399,7 +399,7 @@ fn rows(app: &mut ModelApp, ui: &mut egui::Ui, acts: &mut Vec<Act>) {
|
||||
let r = draw_row(
|
||||
app,
|
||||
ui,
|
||||
egui::Id::new("mc_row_nodes"),
|
||||
egui::Id::new("fc_row_nodes"),
|
||||
&Row {
|
||||
depth: 2,
|
||||
eye: Some(app.ui.show_nodes),
|
||||
@@ -420,7 +420,7 @@ fn rows(app: &mut ModelApp, ui: &mut egui::Ui, acts: &mut Vec<Act>) {
|
||||
draw_row(
|
||||
app,
|
||||
ui,
|
||||
egui::Id::new(("mc_row_kind", kind.name())),
|
||||
egui::Id::new(("fc_row_kind", kind.name())),
|
||||
&Row { depth: 2, swatch: Some(t.element(kind)), label: kind.name(), note: Some(format!("{n}")), ..Default::default() },
|
||||
);
|
||||
}
|
||||
@@ -441,7 +441,7 @@ fn rows(app: &mut ModelApp, ui: &mut egui::Ui, acts: &mut Vec<Act>) {
|
||||
let r = draw_row(
|
||||
app,
|
||||
ui,
|
||||
egui::Id::new(("mc_row_group", key)),
|
||||
egui::Id::new(("fc_row_group", key)),
|
||||
&Row {
|
||||
depth: 1,
|
||||
fold: Some(open(app, key)),
|
||||
@@ -461,7 +461,7 @@ fn rows(app: &mut ModelApp, ui: &mut egui::Ui, acts: &mut Vec<Act>) {
|
||||
let r = draw_row(
|
||||
app,
|
||||
ui,
|
||||
egui::Id::new(("mc_row_def", kind.stem(), &name)),
|
||||
egui::Id::new(("fc_row_def", kind.stem(), &name)),
|
||||
&Row { depth: 2, icon: Some(kind.icon()), label: &name, selected, ..Default::default() },
|
||||
);
|
||||
if r.double {
|
||||
@@ -478,7 +478,7 @@ fn rows(app: &mut ModelApp, ui: &mut egui::Ui, acts: &mut Vec<Act>) {
|
||||
let r = draw_row(
|
||||
app,
|
||||
ui,
|
||||
egui::Id::new("mc_row_results"),
|
||||
egui::Id::new("fc_row_results"),
|
||||
&Row {
|
||||
depth: 1,
|
||||
fold: Some(open(app, "results")),
|
||||
@@ -499,7 +499,7 @@ fn rows(app: &mut ModelApp, ui: &mut egui::Ui, acts: &mut Vec<Act>) {
|
||||
let r = draw_row(
|
||||
app,
|
||||
ui,
|
||||
egui::Id::new(("mc_row_result", &step)),
|
||||
egui::Id::new(("fc_row_result", &step)),
|
||||
&Row { depth: 2, icon: Some("results.deformed"), label: &label, selected, dim: !current, ..Default::default() },
|
||||
);
|
||||
if r.clicked {
|
||||
@@ -511,14 +511,14 @@ fn rows(app: &mut ModelApp, ui: &mut egui::Ui, acts: &mut Vec<Act>) {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use modelcraft_engine::Session;
|
||||
use fieldcraft_engine::Session;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn definitions_read_back_as_their_parameters() {
|
||||
let mut app = ModelApp::new(Session::default());
|
||||
app.run_script(&modelcraft_engine::sample::cantilever()).unwrap();
|
||||
app.run_script(&fieldcraft_engine::sample::cantilever()).unwrap();
|
||||
let m = &app.session.model;
|
||||
assert_eq!(Def::Step.names(m), ["Load", "Modes"]);
|
||||
let steel = definition(m, Def::Material, "Steel").unwrap();
|
||||
@@ -537,9 +537,9 @@ mod tests {
|
||||
Def::FluidDomain,
|
||||
] {
|
||||
for verb in ["create", "edit", "delete"] {
|
||||
assert!(modelcraft_engine::find_command(&kind.command(verb)).is_some(), "{kind:?} {verb}");
|
||||
assert!(fieldcraft_engine::find_command(&kind.command(verb)).is_some(), "{kind:?} {verb}");
|
||||
}
|
||||
assert!(modelcraft_engine::catalogue::find(kind.icon()).is_some());
|
||||
assert!(fieldcraft_engine::catalogue::find(kind.icon()).is_some());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -292,7 +292,7 @@ impl Canvas {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use modelcraft_engine::Session;
|
||||
use fieldcraft_engine::Session;
|
||||
|
||||
use super::*;
|
||||
|
||||
@@ -331,7 +331,7 @@ mod tests {
|
||||
#[test]
|
||||
fn the_whole_window_is_captured_with_panels_and_viewport() {
|
||||
let mut app = ModelApp::new(Session::default());
|
||||
app.run_script(&modelcraft_engine::sample::script()).unwrap();
|
||||
app.run_script(&fieldcraft_engine::sample::script()).unwrap();
|
||||
let image = app.capture(size(900.0, 600.0)).unwrap();
|
||||
assert_eq!(image.size, [900, 600]);
|
||||
let t = Tokens::dark();
|
||||
|
||||
@@ -24,13 +24,13 @@ pub fn panel(app: &mut ModelApp, ui: &mut egui::Ui) {
|
||||
return;
|
||||
}
|
||||
let t = Tokens::get(app.ui.dark);
|
||||
egui::Panel::right("mc_context")
|
||||
egui::Panel::right("fc_context")
|
||||
.exact_size(WIDTH)
|
||||
.resizable(false)
|
||||
.frame(egui::Frame::NONE.fill(t.dialog_bg).inner_margin(egui::Margin::symmetric(8, 8)))
|
||||
.show(ui, |ui| {
|
||||
app.place("context", ui.max_rect());
|
||||
egui::ScrollArea::vertical().id_salt("mc_context_scroll").auto_shrink([false, false]).show(ui, |ui| {
|
||||
egui::ScrollArea::vertical().id_salt("fc_context_scroll").auto_shrink([false, false]).show(ui, |ui| {
|
||||
ui.set_width(WIDTH - 16.0);
|
||||
if app.dialog.is_some() {
|
||||
form(app, ui, &t);
|
||||
@@ -61,7 +61,7 @@ pub fn panel(app: &mut ModelApp, ui: &mut egui::Ui) {
|
||||
/// The open command's parameters as JSON, with the command's parameter documentation; OK runs it.
|
||||
fn form(app: &mut ModelApp, ui: &mut egui::Ui, t: &Tokens) {
|
||||
let Some(d) = app.dialog.as_mut() else { return };
|
||||
let Some(spec) = modelcraft_engine::find_command(d.command) else {
|
||||
let Some(spec) = fieldcraft_engine::find_command(d.command) else {
|
||||
app.dialog = None;
|
||||
return;
|
||||
};
|
||||
@@ -134,7 +134,7 @@ fn properties(app: &mut ModelApp, ui: &mut egui::Ui, t: &Tokens, kind: Def, name
|
||||
});
|
||||
});
|
||||
ui.add_space(4.0);
|
||||
egui::Grid::new("mc_properties").num_columns(2).spacing(vec2(10.0, 3.0)).show(ui, |ui| {
|
||||
egui::Grid::new("fc_properties").num_columns(2).spacing(vec2(10.0, 3.0)).show(ui, |ui| {
|
||||
for (k, text) in rows.iter() {
|
||||
ui.label(RichText::new(k).color(t.text_dim));
|
||||
ui.add(egui::Label::new(text).wrap());
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
use std::sync::mpsc::Sender;
|
||||
|
||||
use modelcraft_render::StandardView;
|
||||
use fieldcraft_render::StandardView;
|
||||
use serde_json::{Value, json};
|
||||
|
||||
use crate::capture::CaptureSize;
|
||||
@@ -79,9 +79,9 @@ fn render_dim(p: &Value, key: &str, default: usize) -> Result<usize, String> {
|
||||
None | Some(Value::Null) => Ok(default),
|
||||
Some(v) => v
|
||||
.as_f64()
|
||||
.filter(|x| x.fract() == 0.0 && (1.0..=modelcraft_render::MAX_DIM as f64).contains(x))
|
||||
.filter(|x| x.fract() == 0.0 && (1.0..=fieldcraft_render::MAX_DIM as f64).contains(x))
|
||||
.map(|x| x as usize)
|
||||
.ok_or_else(|| format!("`{key}` must be a whole number of pixels in 1..={}", modelcraft_render::MAX_DIM)),
|
||||
.ok_or_else(|| format!("`{key}` must be a whole number of pixels in 1..={}", fieldcraft_render::MAX_DIM)),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -102,7 +102,7 @@ pub fn handle(app: &mut ModelApp, ctx: &egui::Context, req: &ControlRequest) ->
|
||||
wrap(app.run_script(steps).map(|()| json!({"journal": app.session.journal.len()})))
|
||||
}
|
||||
"engine.commands" => {
|
||||
ok(Value::Array(modelcraft_engine::commands().iter().map(|c| json!({"id": c.id, "label": c.label, "params": c.params})).collect()))
|
||||
ok(Value::Array(fieldcraft_engine::commands().iter().map(|c| json!({"id": c.id, "label": c.label, "params": c.params})).collect()))
|
||||
}
|
||||
"ui.inspect" => ok(json!({
|
||||
"ui": app.ui,
|
||||
@@ -303,7 +303,7 @@ pub fn handle(app: &mut ModelApp, ctx: &egui::Context, req: &ControlRequest) ->
|
||||
.take(1000)
|
||||
.map(|v| {
|
||||
let c = |i: usize| v.get(i).and_then(Value::as_f64).unwrap_or(f64::NAN);
|
||||
crate::viewport::to_screen(app, modelcraft_render::Vec3::new(c(0), c(1), c(2))).map_or(Value::Null, |q| json!([q.x, q.y]))
|
||||
crate::viewport::to_screen(app, fieldcraft_render::Vec3::new(c(0), c(1), c(2))).map_or(Value::Null, |q| json!([q.x, q.y]))
|
||||
})
|
||||
.collect();
|
||||
ok(json!(pts))
|
||||
@@ -384,12 +384,12 @@ pub fn capture_to(app: &mut ModelApp, size: CaptureSize, path: Option<&str>) ->
|
||||
}
|
||||
}
|
||||
|
||||
/// Save a screenshot as PNG: at `path`, or `modelcraft-shot.png` in the temporary folder.
|
||||
/// Save a screenshot as PNG: at `path`, or `fieldcraft-shot.png` in the temporary folder.
|
||||
pub fn save_screenshot(image: &egui::ColorImage, path: Option<&str>) -> Value {
|
||||
let [w, h] = image.size;
|
||||
let rgba: Vec<u8> = image.pixels.iter().flat_map(|c| c.to_array()).collect();
|
||||
let Some(png) = modelcraft_render::png(w, h, &rgba) else { return json!({"ok": false, "error": "could not encode the screenshot"}) };
|
||||
let path = path.map(str::to_string).unwrap_or_else(|| std::env::temp_dir().join("modelcraft-shot.png").to_string_lossy().to_string());
|
||||
let Some(png) = fieldcraft_render::png(w, h, &rgba) else { return json!({"ok": false, "error": "could not encode the screenshot"}) };
|
||||
let path = path.map(str::to_string).unwrap_or_else(|| std::env::temp_dir().join("fieldcraft-shot.png").to_string_lossy().to_string());
|
||||
match std::fs::write(&path, &png) {
|
||||
Ok(()) => json!({"ok": true, "result": {"path": path, "width": w, "height": h}}),
|
||||
Err(e) => json!({"ok": false, "error": format!("{path}: {e}")}),
|
||||
@@ -398,7 +398,7 @@ pub fn save_screenshot(image: &egui::ColorImage, path: Option<&str>) -> Value {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use modelcraft_engine::Session;
|
||||
use fieldcraft_engine::Session;
|
||||
|
||||
use super::*;
|
||||
|
||||
@@ -413,12 +413,12 @@ mod tests {
|
||||
#[test]
|
||||
fn engine_view_and_settings_methods() {
|
||||
let mut app = ModelApp::new(Session::default());
|
||||
let r = call(&mut app, "engine.script", json!({"commands": modelcraft_engine::sample::script()}));
|
||||
let r = call(&mut app, "engine.script", json!({"commands": fieldcraft_engine::sample::script()}));
|
||||
assert_eq!(r["ok"], true, "{r}");
|
||||
let r = call(&mut app, "engine.execute", json!({"command": "model.info"}));
|
||||
assert_eq!(r["result"]["elements"]["hex8"], 64);
|
||||
assert_eq!(call(&mut app, "engine.execute", json!({"command": "no.such"}))["ok"], false);
|
||||
assert!(call(&mut app, "engine.commands", json!({}))["result"].as_array().is_some_and(|a| a.len() == modelcraft_engine::commands().len()));
|
||||
assert!(call(&mut app, "engine.commands", json!({}))["result"].as_array().is_some_and(|a| a.len() == fieldcraft_engine::commands().len()));
|
||||
let r = call(&mut app, "ui.set", json!({"style": "wireframe", "show_nodes": true}));
|
||||
assert_eq!(r["result"]["style"], "wireframe");
|
||||
assert_eq!(app.ui.style, crate::Style::Wireframe);
|
||||
@@ -445,8 +445,8 @@ mod tests {
|
||||
#[test]
|
||||
fn ui_render_writes_a_png_of_the_model() {
|
||||
let mut app = ModelApp::new(Session::default());
|
||||
app.run_script(&modelcraft_engine::sample::script()).unwrap();
|
||||
let path = std::env::temp_dir().join(format!("modelcraft-render-{}.png", std::process::id()));
|
||||
app.run_script(&fieldcraft_engine::sample::script()).unwrap();
|
||||
let path = std::env::temp_dir().join(format!("fieldcraft-render-{}.png", std::process::id()));
|
||||
let r = call(&mut app, "ui.render", json!({"path": path.to_string_lossy(), "width": 120, "height": 80}));
|
||||
assert_eq!(r["ok"], true, "{r}");
|
||||
let bytes = std::fs::read(&path).unwrap();
|
||||
@@ -458,8 +458,8 @@ mod tests {
|
||||
#[test]
|
||||
fn ui_render_refuses_hostile_sizes() {
|
||||
let mut app = ModelApp::new(Session::default());
|
||||
app.run_script(&modelcraft_engine::sample::script()).unwrap();
|
||||
let path = std::env::temp_dir().join(format!("modelcraft-render-hostile-{}.png", std::process::id()));
|
||||
app.run_script(&fieldcraft_engine::sample::script()).unwrap();
|
||||
let path = std::env::temp_dir().join(format!("fieldcraft-render-hostile-{}.png", std::process::id()));
|
||||
let path = path.to_string_lossy();
|
||||
for (w, h) in [
|
||||
(json!(100_000), json!(100_000)),
|
||||
@@ -526,7 +526,7 @@ mod tests {
|
||||
let r = call(&mut app, "ui.toolbar", json!({}));
|
||||
assert_eq!(r["result"]["tab"], "mesh", "{r}");
|
||||
let entries = r["result"]["entries"].as_array().unwrap();
|
||||
assert_eq!(entries.len(), modelcraft_engine::catalogue::CATALOGUE.len());
|
||||
assert_eq!(entries.len(), fieldcraft_engine::catalogue::CATALOGUE.len());
|
||||
let soon = entries.iter().find(|e| e["id"] == "mesh.order.quadratic").unwrap();
|
||||
assert_eq!((soon["coming_soon"].as_str(), soon["command"].is_null()), (Some("M2"), true));
|
||||
assert_eq!(call(&mut app, "ui.set", json!({"tab": "nope"}))["ok"], false);
|
||||
@@ -542,7 +542,7 @@ mod tests {
|
||||
#[test]
|
||||
fn ui_capture_saves_the_whole_window() {
|
||||
let mut app = ModelApp::new(Session::default());
|
||||
let path = std::env::temp_dir().join(format!("modelcraft-capture-{}.png", std::process::id()));
|
||||
let path = std::env::temp_dir().join(format!("fieldcraft-capture-{}.png", std::process::id()));
|
||||
let r = call(&mut app, "ui.capture", json!({"path": path.to_string_lossy(), "width": 640, "height": 400, "scale": 1}));
|
||||
assert_eq!(
|
||||
(r["ok"].as_bool(), r["result"]["width"].as_u64(), r["result"]["offscreen"].as_bool()),
|
||||
@@ -559,7 +559,7 @@ mod tests {
|
||||
#[test]
|
||||
fn screenshots_are_saved_as_png() {
|
||||
let image = egui::ColorImage::filled([4, 3], egui::Color32::RED);
|
||||
let path = std::env::temp_dir().join(format!("modelcraft-shot-{}.png", std::process::id()));
|
||||
let path = std::env::temp_dir().join(format!("fieldcraft-shot-{}.png", std::process::id()));
|
||||
let r = save_screenshot(&image, Some(&path.to_string_lossy()));
|
||||
assert_eq!(r["ok"], true, "{r}");
|
||||
assert!(std::fs::read(&path).unwrap().starts_with(&[0x89, b'P', b'N', b'G']));
|
||||
|
||||
+49
-49
@@ -16,7 +16,7 @@
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::mpsc::{Receiver, TryRecvError};
|
||||
|
||||
use modelcraft_engine::Session;
|
||||
use fieldcraft_engine::Session;
|
||||
use serde_json::{Value, json};
|
||||
|
||||
use crate::ModelApp;
|
||||
@@ -44,18 +44,18 @@ pub struct Services {
|
||||
pub recent_path: Option<PathBuf>,
|
||||
}
|
||||
|
||||
const MODEL: Filter = Filter { name: "ModelCraft model", extensions: &["modelcraft"] };
|
||||
const MODEL: Filter = Filter { name: "FieldCraft model", extensions: &["fieldcraft"] };
|
||||
const DESIGN: Filter = Filter { name: "SolidCraft design", extensions: &["solidcraft"] };
|
||||
const MESH: Filter = Filter { name: "Face-tagged mesh (3MF)", extensions: &["3mf", "3MF"] };
|
||||
const ANY: Filter = Filter { name: "Models, designs and tagged meshes", extensions: &["modelcraft", "solidcraft", "3mf", "3MF"] };
|
||||
const ANY: Filter = Filter { name: "Models, designs and tagged meshes", extensions: &["fieldcraft", "solidcraft", "3mf", "3MF"] };
|
||||
|
||||
/// The Open picker's filters: everything ModelCraft opens, then each type.
|
||||
/// The Open picker's filters: everything FieldCraft opens, then each type.
|
||||
pub const OPEN_FILTERS: &[Filter] = &[ANY, MODEL, DESIGN, MESH];
|
||||
|
||||
/// What opening a file does with it, from its extension.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum Kind {
|
||||
/// A `.modelcraft` model replaces the session (`file.open`).
|
||||
/// A `.fieldcraft` model replaces the session (`file.open`).
|
||||
Model,
|
||||
/// A `.solidcraft` design becomes the geometry (`geometry.open`).
|
||||
Design,
|
||||
@@ -64,11 +64,11 @@ pub enum Kind {
|
||||
}
|
||||
|
||||
impl Kind {
|
||||
/// The kind of a file by its extension (any case); `None` for a file ModelCraft does not open.
|
||||
/// The kind of a file by its extension (any case); `None` for a file FieldCraft does not open.
|
||||
pub fn of(path: &str) -> Option<Kind> {
|
||||
let ext = Path::new(path).extension()?.to_str()?.to_ascii_lowercase();
|
||||
match ext.as_str() {
|
||||
"modelcraft" => Some(Kind::Model),
|
||||
"fieldcraft" => Some(Kind::Model),
|
||||
"solidcraft" => Some(Kind::Design),
|
||||
"3mf" => Some(Kind::Mesh),
|
||||
_ => None,
|
||||
@@ -162,9 +162,9 @@ impl Recent {
|
||||
|
||||
/// A command running in the background on a copy of the session: a file opening, a solve.
|
||||
pub struct Loading {
|
||||
/// What the window shows while it runs ("Opening beam.modelcraft").
|
||||
/// What the window shows while it runs ("Opening beam.fieldcraft").
|
||||
pub title: String,
|
||||
/// The status when it succeeded ("Opened beam.modelcraft").
|
||||
/// The status when it succeeded ("Opened beam.fieldcraft").
|
||||
done: String,
|
||||
/// The file it opens, for the recent list.
|
||||
recent: Option<String>,
|
||||
@@ -177,7 +177,7 @@ pub struct Loading {
|
||||
/// What waits for an answer about unsaved changes.
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum Replace {
|
||||
/// A `.modelcraft` file.
|
||||
/// A `.fieldcraft` file.
|
||||
File(String),
|
||||
/// A sample (`file.sample`).
|
||||
Sample(&'static str),
|
||||
@@ -187,7 +187,7 @@ impl Replace {
|
||||
fn name(&self) -> String {
|
||||
match self {
|
||||
Replace::File(p) => file_name(p),
|
||||
Replace::Sample(n) => modelcraft_engine::sample::find(n).map_or_else(|| (*n).to_string(), |s| format!("the {} sample", s.label)),
|
||||
Replace::Sample(n) => fieldcraft_engine::sample::find(n).map_or_else(|| (*n).to_string(), |s| format!("the {} sample", s.label)),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -239,7 +239,7 @@ fn off_thread(name: String, work: impl FnOnce() + Send + 'static) {
|
||||
/// Run `command` on `session` off the window thread.
|
||||
fn spawn(session: Session, command: &'static str, params: Value) -> Receiver<(Box<Session>, Result<Value, String>)> {
|
||||
let (tx, rx) = std::sync::mpsc::channel();
|
||||
off_thread(format!("modelcraft {command}"), move || {
|
||||
off_thread(format!("fieldcraft {command}"), move || {
|
||||
let mut s = session;
|
||||
let r = s.execute(command, ¶ms).map_err(|e| e.to_string());
|
||||
let _ = tx.send((Box::new(s), r));
|
||||
@@ -253,7 +253,7 @@ impl ModelApp {
|
||||
model_name(&self.session)
|
||||
}
|
||||
|
||||
/// What is running in the background ("Opening beam.modelcraft", "Solving"), if anything.
|
||||
/// What is running in the background ("Opening beam.fieldcraft", "Solving"), if anything.
|
||||
pub fn busy(&self) -> Option<&str> {
|
||||
self.loading.as_ref().map(|l| l.title.as_str())
|
||||
}
|
||||
@@ -302,7 +302,7 @@ impl ModelApp {
|
||||
/// replacing unsaved changes asks first.
|
||||
pub fn open_path(&mut self, path: &str) {
|
||||
let Some(kind) = Kind::of(path) else {
|
||||
self.set_status(format!("{path}: ModelCraft opens .modelcraft models, .solidcraft designs and .3mf tagged meshes"), true);
|
||||
self.set_status(format!("{path}: FieldCraft opens .fieldcraft models, .solidcraft designs and .3mf tagged meshes"), true);
|
||||
return;
|
||||
};
|
||||
if kind == Kind::Model && self.has_unsaved_work() {
|
||||
@@ -326,7 +326,7 @@ impl ModelApp {
|
||||
/// Help › Samples (and the empty viewport): open a sample model, asking first when that
|
||||
/// would discard unsaved changes.
|
||||
pub fn open_sample(&mut self, name: &str) {
|
||||
let Some(sample) = modelcraft_engine::sample::find(name) else {
|
||||
let Some(sample) = fieldcraft_engine::sample::find(name) else {
|
||||
self.set_status(format!("no sample `{name}`"), true);
|
||||
return;
|
||||
};
|
||||
@@ -338,7 +338,7 @@ impl ModelApp {
|
||||
}
|
||||
|
||||
fn load_sample(&mut self, name: &str) {
|
||||
let Some(sample) = modelcraft_engine::sample::find(name) else { return };
|
||||
let Some(sample) = fieldcraft_engine::sample::find(name) else { return };
|
||||
if self.run("file.sample", json!({"name": sample.name})).is_ok() {
|
||||
self.model_changed();
|
||||
self.fit_view();
|
||||
@@ -392,7 +392,7 @@ impl ModelApp {
|
||||
Some((session, Ok(reply))) => {
|
||||
// The session it replaces may be large: it is freed off the window thread.
|
||||
let old = std::mem::replace(&mut self.session, *session);
|
||||
off_thread("modelcraft drop".into(), move || drop(old));
|
||||
off_thread("fieldcraft drop".into(), move || drop(old));
|
||||
self.model_changed();
|
||||
if l.fit {
|
||||
self.fit_view();
|
||||
@@ -452,8 +452,8 @@ impl ModelApp {
|
||||
self.open_form("file.save_as", json!({}));
|
||||
return false;
|
||||
};
|
||||
let Some(path) = pick(&format!("{}.modelcraft", self.model_name()), &[MODEL]) else { return false };
|
||||
let path = with_extension(path, "modelcraft");
|
||||
let Some(path) = pick(&format!("{}.fieldcraft", self.model_name()), &[MODEL]) else { return false };
|
||||
let path = with_extension(path, "fieldcraft");
|
||||
let saved = self.run("file.save_as", json!({"path": path})).is_ok();
|
||||
self.saved_to_recent(saved)
|
||||
}
|
||||
@@ -461,7 +461,7 @@ impl ModelApp {
|
||||
/// Write Deck: pick where to write the model as a `format` deck. Without a picker the
|
||||
/// command's form opens with the format filled in.
|
||||
pub fn write_deck(&mut self, format: &str) {
|
||||
let Some(f) = modelcraft_decks::format(format) else {
|
||||
let Some(f) = fieldcraft_decks::format(format) else {
|
||||
self.set_status(format!("no {format} deck writer yet"), true);
|
||||
return;
|
||||
};
|
||||
@@ -564,10 +564,10 @@ pub fn show(app: &mut ModelApp, ctx: &egui::Context) {
|
||||
if hovering || app.loading.is_some() {
|
||||
let text = match app.loading.as_ref() {
|
||||
Some(l) => format!("{}…", l.title),
|
||||
None => "Drop to open: .modelcraft models, .solidcraft designs, .3mf tagged meshes".into(),
|
||||
None => "Drop to open: .fieldcraft models, .solidcraft designs, .3mf tagged meshes".into(),
|
||||
};
|
||||
let mut cancel = false;
|
||||
egui::Area::new(egui::Id::new("mc_loading")).order(egui::Order::Foreground).anchor(egui::Align2::CENTER_CENTER, egui::vec2(0.0, 0.0)).show(
|
||||
egui::Area::new(egui::Id::new("fc_loading")).order(egui::Order::Foreground).anchor(egui::Align2::CENTER_CENTER, egui::vec2(0.0, 0.0)).show(
|
||||
ctx,
|
||||
|ui| {
|
||||
egui::Frame::popup(ui.style()).fill(t.overlay).inner_margin(egui::Margin::symmetric(16, 12)).show(ui, |ui| {
|
||||
@@ -593,7 +593,7 @@ pub fn show(app: &mut ModelApp, ctx: &egui::Context) {
|
||||
match app.prompt.clone() {
|
||||
Some(Prompt::Unsaved(next)) => {
|
||||
let mut answer = None;
|
||||
crate::frame::window(ctx, "Unsaved changes", crate::frame::Width::Normal).id(egui::Id::new("mc_unsaved")).show(ctx, |ui| {
|
||||
crate::frame::window(ctx, "Unsaved changes", crate::frame::Width::Normal).id(egui::Id::new("fc_unsaved")).show(ctx, |ui| {
|
||||
ui.label(format!("Save the changes to {} before opening {}?", app.model_name(), next.name()));
|
||||
ui.label(egui::RichText::new("Without saving, they are lost.").color(t.text_dim));
|
||||
ui.add_space(6.0);
|
||||
@@ -619,11 +619,11 @@ pub fn show(app: &mut ModelApp, ctx: &egui::Context) {
|
||||
Some(Prompt::Recover) => {
|
||||
let entries = app.recover_list(ctx);
|
||||
let (mut recover, mut discard, mut close) = (None, None, false);
|
||||
crate::frame::window(ctx, "Recover", crate::frame::Width::Wide).id(egui::Id::new("mc_recover")).show(ctx, |ui| {
|
||||
crate::frame::window(ctx, "Recover", crate::frame::Width::Wide).id(egui::Id::new("fc_recover")).show(ctx, |ui| {
|
||||
if entries.is_empty() {
|
||||
ui.label(egui::RichText::new("No autosaved models to recover.").color(t.text_dim));
|
||||
}
|
||||
egui::Grid::new("mc_recover_grid").num_columns(3).spacing(egui::vec2(10.0, 4.0)).show(ui, |ui| {
|
||||
egui::Grid::new("fc_recover_grid").num_columns(3).spacing(egui::vec2(10.0, 4.0)).show(ui, |ui| {
|
||||
for e in &entries {
|
||||
let id = e.get("id").and_then(Value::as_str).unwrap_or_default().to_string();
|
||||
let name = e.get("path").and_then(Value::as_str).map_or_else(|| "Untitled".to_string(), file_name);
|
||||
@@ -648,15 +648,15 @@ pub fn show(app: &mut ModelApp, ctx: &egui::Context) {
|
||||
app.recover(&id);
|
||||
} else if let Some(id) = discard {
|
||||
let _ = app.run("file.recovery.discard", json!({"id": id}));
|
||||
ctx.data_mut(|d| d.remove::<Vec<Value>>(egui::Id::new("mc_recover_entries")));
|
||||
ctx.data_mut(|d| d.remove::<Vec<Value>>(egui::Id::new("fc_recover_entries")));
|
||||
} else if close || ctx.input(|i| i.key_pressed(egui::Key::Escape)) {
|
||||
app.prompt = None;
|
||||
ctx.data_mut(|d| d.remove::<Vec<Value>>(egui::Id::new("mc_recover_entries")));
|
||||
ctx.data_mut(|d| d.remove::<Vec<Value>>(egui::Id::new("fc_recover_entries")));
|
||||
}
|
||||
}
|
||||
Some(Prompt::Samples) => {
|
||||
let (mut open, mut close) = (None, false);
|
||||
crate::frame::window(ctx, "Samples", crate::frame::Width::Normal).id(egui::Id::new("mc_samples")).show(ctx, |ui| {
|
||||
crate::frame::window(ctx, "Samples", crate::frame::Width::Normal).id(egui::Id::new("fc_samples")).show(ctx, |ui| {
|
||||
sample_rows(ui, &t, &mut open);
|
||||
ui.add_space(6.0);
|
||||
close = ui.button("Close").clicked();
|
||||
@@ -675,7 +675,7 @@ pub fn show(app: &mut ModelApp, ctx: &egui::Context) {
|
||||
/// One row per sample: its name, what it shows and an Open button; `open` gets the one clicked.
|
||||
pub fn sample_rows(ui: &mut egui::Ui, t: &crate::theme::Tokens, open: &mut Option<&'static str>) {
|
||||
let width = ui.available_width().clamp(280.0, 420.0);
|
||||
for sample in modelcraft_engine::sample::SAMPLES {
|
||||
for sample in fieldcraft_engine::sample::SAMPLES {
|
||||
ui.horizontal(|ui| {
|
||||
ui.vertical(|ui| {
|
||||
ui.set_width(width - 70.0);
|
||||
@@ -693,7 +693,7 @@ pub fn sample_rows(ui: &mut egui::Ui, t: &crate::theme::Tokens, open: &mut Optio
|
||||
impl ModelApp {
|
||||
/// The recover window's entries, read from disk once while it is open.
|
||||
fn recover_list(&mut self, ctx: &egui::Context) -> Vec<Value> {
|
||||
let key = egui::Id::new("mc_recover_entries");
|
||||
let key = egui::Id::new("fc_recover_entries");
|
||||
if let Some(v) = ctx.data(|d| d.get_temp::<Vec<Value>>(key)) {
|
||||
return v;
|
||||
}
|
||||
@@ -739,7 +739,7 @@ pub fn file_menu(app: &mut ModelApp, ui: &mut egui::Ui) {
|
||||
}
|
||||
ui.separator();
|
||||
crate::frame::menu_heading(ui, &t, "WRITE DECK");
|
||||
for c in modelcraft_engine::catalogue::CATALOGUE.iter().filter(|c| c.panel == "Decks") {
|
||||
for c in fieldcraft_engine::catalogue::CATALOGUE.iter().filter(|c| c.panel == "Decks") {
|
||||
let format = c.preset().get("format").and_then(Value::as_str).map(str::to_string);
|
||||
let soon = c.milestone().map(|m| format!("soon · {m}"));
|
||||
let enabled = format.is_some() && soon.is_none();
|
||||
@@ -771,7 +771,7 @@ mod tests {
|
||||
use super::*;
|
||||
|
||||
fn temp(name: &str) -> PathBuf {
|
||||
let dir = std::env::temp_dir().join(format!("modelcraft-files-{}-{name}", std::process::id()));
|
||||
let dir = std::env::temp_dir().join(format!("fieldcraft-files-{}-{name}", std::process::id()));
|
||||
let _ = std::fs::remove_dir_all(&dir);
|
||||
std::fs::create_dir_all(&dir).unwrap();
|
||||
dir
|
||||
@@ -787,26 +787,26 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn file_types_dispatch_to_their_command() {
|
||||
assert_eq!(Kind::of("a/b.modelcraft"), Some(Kind::Model));
|
||||
assert_eq!(Kind::of("a/b.fieldcraft"), Some(Kind::Model));
|
||||
assert_eq!(Kind::of("B.SOLIDCRAFT"), Some(Kind::Design));
|
||||
assert_eq!(Kind::of("part.3MF").map(Kind::command), Some("geometry.import.mesh"));
|
||||
assert_eq!(Kind::of("part.step"), None);
|
||||
assert_eq!(Kind::of("noext"), None);
|
||||
assert_eq!(with_extension("a/b".into(), "modelcraft"), "a/b.modelcraft");
|
||||
assert_eq!(with_extension("a/b.MODELCRAFT".into(), "modelcraft"), "a/b.MODELCRAFT");
|
||||
assert_eq!(with_extension("a/b".into(), "fieldcraft"), "a/b.fieldcraft");
|
||||
assert_eq!(with_extension("a/b.FIELDCRAFT".into(), "fieldcraft"), "a/b.FIELDCRAFT");
|
||||
}
|
||||
|
||||
/// Open (picker) → background open → the model, the recent list; Save As (picker) and Save.
|
||||
#[test]
|
||||
fn the_pickers_open_and_save_models() {
|
||||
let dir = temp("models");
|
||||
let saved = dir.join("beam.modelcraft").to_string_lossy().to_string();
|
||||
let saved = dir.join("beam.fieldcraft").to_string_lossy().to_string();
|
||||
let mut first = ModelApp::new(Session::default());
|
||||
first.run_script(&modelcraft_engine::sample::script()).unwrap();
|
||||
first.run_script(&fieldcraft_engine::sample::script()).unwrap();
|
||||
// Save As through the save picker: the extension is added, the file becomes the model's.
|
||||
let bare = dir.join("beam").to_string_lossy().to_string();
|
||||
first.services.pick_save = Some(Box::new(move |name: &str, f: &[Filter]| {
|
||||
assert_eq!((name, f.first().map(|f| f.extensions)), ("Untitled.modelcraft", Some(&["modelcraft"][..])));
|
||||
assert_eq!((name, f.first().map(|f| f.extensions)), ("Untitled.fieldcraft", Some(&["fieldcraft"][..])));
|
||||
Some(bare.clone())
|
||||
}));
|
||||
first.start("file.save_as").unwrap();
|
||||
@@ -821,12 +821,12 @@ mod tests {
|
||||
app.start("file.open").unwrap();
|
||||
assert!(app.dialog.is_none(), "a picker, not the form");
|
||||
assert_eq!(offered.borrow()[0][0], MODEL, "Open Model offers models first");
|
||||
assert_eq!(app.busy(), Some("Opening beam.modelcraft"));
|
||||
assert_eq!(app.busy(), Some("Opening beam.fieldcraft"));
|
||||
app.wait_loaded();
|
||||
assert_eq!(app.session.model.elements().len(), first.session.model.elements().len());
|
||||
assert_eq!(app.session.path.as_deref(), Some(saved.as_str()));
|
||||
assert_eq!(app.recent.paths, vec![saved.clone()]);
|
||||
assert_eq!(app.status.as_ref().map(|s| (s.text.as_str(), s.error)), Some(("Opened beam.modelcraft", false)));
|
||||
assert_eq!(app.status.as_ref().map(|s| (s.text.as_str(), s.error)), Some(("Opened beam.fieldcraft", false)));
|
||||
// Save goes to the model's own file without asking.
|
||||
app.services.pick_save = Some(Box::new(|_: &str, _: &[Filter]| -> Option<String> { panic!("Save asked for a file") }));
|
||||
app.run("mesh.node.add", serde_json::json!({"label": 99999, "xyz": [0, 0, 0]})).unwrap();
|
||||
@@ -861,7 +861,7 @@ mod tests {
|
||||
#[test]
|
||||
fn opening_a_model_over_unsaved_changes_asks_first() {
|
||||
let dir = temp("unsaved");
|
||||
let path = dir.join("other.modelcraft").to_string_lossy().to_string();
|
||||
let path = dir.join("other.fieldcraft").to_string_lossy().to_string();
|
||||
let mut other = ModelApp::new(Session::default());
|
||||
other.run("mesh.node.add", serde_json::json!({"label": 5, "xyz": [0, 0, 0]})).unwrap();
|
||||
other.run("file.save_as", serde_json::json!({"path": path})).unwrap();
|
||||
@@ -884,7 +884,7 @@ mod tests {
|
||||
let dir = temp("decks");
|
||||
let target = dir.join("cantilever").to_string_lossy().to_string();
|
||||
let mut app = ModelApp::new(Session::default());
|
||||
app.run_script(&modelcraft_engine::sample::cantilever()).unwrap();
|
||||
app.run_script(&fieldcraft_engine::sample::cantilever()).unwrap();
|
||||
let t = target.clone();
|
||||
app.services.pick_save = Some(Box::new(move |name: &str, f: &[Filter]| {
|
||||
assert_eq!((name, f.first().map(|f| f.extensions)), ("Untitled.inp", Some(&["inp"][..])));
|
||||
@@ -904,17 +904,17 @@ mod tests {
|
||||
let mut r = Recent::load(Some(file.clone()));
|
||||
assert!(r.paths.is_empty());
|
||||
for i in 0..12 {
|
||||
r.add(&format!("/m/{i}.modelcraft"));
|
||||
r.add(&format!("/m/{i}.fieldcraft"));
|
||||
}
|
||||
r.add("/m/3.modelcraft");
|
||||
r.add("/m/3.fieldcraft");
|
||||
let again = Recent::load(Some(file.clone()));
|
||||
assert_eq!(again.paths.len(), MAX_RECENT);
|
||||
assert_eq!(again.paths.first().map(String::as_str), Some("/m/3.modelcraft"));
|
||||
assert_eq!(again.paths.iter().filter(|p| *p == "/m/3.modelcraft").count(), 1);
|
||||
assert_eq!(again.paths.first().map(String::as_str), Some("/m/3.fieldcraft"));
|
||||
assert_eq!(again.paths.iter().filter(|p| *p == "/m/3.fieldcraft").count(), 1);
|
||||
std::fs::write(&file, b"{not json").unwrap();
|
||||
assert!(Recent::load(Some(file.clone())).paths.is_empty());
|
||||
std::fs::write(&file, br#"["/x/a.modelcraft", "/x/b.exe", ""]"#).unwrap();
|
||||
assert_eq!(Recent::load(Some(file)).paths, vec!["/x/a.modelcraft".to_string()]);
|
||||
std::fs::write(&file, br#"["/x/a.fieldcraft", "/x/b.exe", ""]"#).unwrap();
|
||||
assert_eq!(Recent::load(Some(file)).paths, vec!["/x/a.fieldcraft".to_string()]);
|
||||
let _ = std::fs::remove_dir_all(&dir);
|
||||
}
|
||||
|
||||
@@ -934,7 +934,7 @@ mod tests {
|
||||
app.wait_loaded();
|
||||
assert_eq!(app.status.as_ref().map(|s| s.text.as_str()), Some("Solved 2 steps"));
|
||||
app.logic(&egui::Context::default());
|
||||
assert_eq!(app.ui.tab, modelcraft_engine::catalogue::Tab::Results);
|
||||
assert_eq!(app.ui.tab, fieldcraft_engine::catalogue::Tab::Results);
|
||||
assert!(app.plot().is_some());
|
||||
// Over unsaved changes, opening a sample asks first.
|
||||
app.run("mesh.node.add", serde_json::json!({"label": 900000, "xyz": [0, 0, 0]})).unwrap();
|
||||
|
||||
@@ -93,7 +93,7 @@ fn popup<R>(
|
||||
if !open {
|
||||
return None;
|
||||
}
|
||||
let id = egui::Id::new(("mc_menu", key));
|
||||
let id = egui::Id::new(("fc_menu", key));
|
||||
let shown =
|
||||
egui::Popup::new(id, ui.ctx().clone(), anchor, ui.layer_id()).kind(egui::PopupKind::Menu).open_bool(&mut open).close_behavior(close).show(
|
||||
|ui| {
|
||||
|
||||
+12
-12
@@ -16,7 +16,7 @@ use std::ops::Range;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use egui_wgpu::wgpu;
|
||||
use modelcraft_render::{Rgb, Scene};
|
||||
use fieldcraft_render::{Rgb, Scene};
|
||||
|
||||
/// Triangle vertex: position (3 × f32), normal (3 × f32), sRGB colour (4 × u8).
|
||||
pub const TRI_SIZE: usize = 28;
|
||||
@@ -141,7 +141,7 @@ fn vs_tri(@location(0) p: vec3<f32>, @location(1) n: vec3<f32>, @location(2) c:
|
||||
}
|
||||
|
||||
/// Two-sided lighting: a key light above the eye, a fill from the eye and a small highlight
|
||||
/// (modelcraft_render::shade computes the same on the CPU).
|
||||
/// (fieldcraft_render::shade computes the same on the CPU).
|
||||
@fragment
|
||||
fn fs_tri(i: TOut) -> @location(0) vec4<f32> {
|
||||
let v = normalize(u.back.xyz);
|
||||
@@ -235,9 +235,9 @@ pub struct GpuTarget {
|
||||
impl Resources {
|
||||
fn new(device: &wgpu::Device, t: GpuTarget) -> Self {
|
||||
let module =
|
||||
device.create_shader_module(wgpu::ShaderModuleDescriptor { label: Some("mc_viewport"), source: wgpu::ShaderSource::Wgsl(SHADER.into()) });
|
||||
device.create_shader_module(wgpu::ShaderModuleDescriptor { label: Some("fc_viewport"), source: wgpu::ShaderSource::Wgsl(SHADER.into()) });
|
||||
let bgl = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
|
||||
label: Some("mc_viewport"),
|
||||
label: Some("fc_viewport"),
|
||||
entries: &[wgpu::BindGroupLayoutEntry {
|
||||
binding: 0,
|
||||
visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
|
||||
@@ -246,18 +246,18 @@ impl Resources {
|
||||
}],
|
||||
});
|
||||
let layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
||||
label: Some("mc_viewport"),
|
||||
label: Some("fc_viewport"),
|
||||
bind_group_layouts: &[Some(&bgl)],
|
||||
immediate_size: 0,
|
||||
});
|
||||
let uniform = device.create_buffer(&wgpu::BufferDescriptor {
|
||||
label: Some("mc_viewport_u"),
|
||||
label: Some("fc_viewport_u"),
|
||||
size: UNIFORM_SIZE,
|
||||
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
|
||||
mapped_at_creation: false,
|
||||
});
|
||||
let bind = device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||
label: Some("mc_viewport"),
|
||||
label: Some("fc_viewport"),
|
||||
layout: &bgl,
|
||||
entries: &[wgpu::BindGroupEntry { binding: 0, resource: uniform.as_entire_binding() }],
|
||||
});
|
||||
@@ -294,9 +294,9 @@ impl Resources {
|
||||
})
|
||||
};
|
||||
Resources {
|
||||
tri: pipeline("mc_tris", "vs_tri", "fs_tri", tri_layout(), depth(wgpu::CompareFunction::Less, true), None),
|
||||
tri: pipeline("fc_tris", "vs_tri", "fs_tri", tri_layout(), depth(wgpu::CompareFunction::Less, true), None),
|
||||
line: pipeline(
|
||||
"mc_lines",
|
||||
"fc_lines",
|
||||
"vs_line",
|
||||
"fs_line",
|
||||
line_layout(),
|
||||
@@ -321,8 +321,8 @@ impl Resources {
|
||||
self.tris.clear();
|
||||
self.lines.clear();
|
||||
let max = device.limits().max_buffer_size;
|
||||
self.tris = upload(device, queue, "mc_tris", &sc.tris, TRI_SIZE, 3, max);
|
||||
self.lines = upload(device, queue, "mc_lines", &sc.lines, LINE_SIZE, 1, max);
|
||||
self.tris = upload(device, queue, "fc_tris", &sc.tris, TRI_SIZE, 3, max);
|
||||
self.lines = upload(device, queue, "fc_lines", &sc.lines, LINE_SIZE, 1, max);
|
||||
self.key = Some(key);
|
||||
}
|
||||
}
|
||||
@@ -433,7 +433,7 @@ fn uniform_bytes(cb: &ViewportCallback, linear: bool) -> Vec<u8> {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use egui_wgpu::wgpu::naga;
|
||||
use modelcraft_render::{SceneLine, ScenePoint, SceneTri, Vec3};
|
||||
use fieldcraft_render::{SceneLine, ScenePoint, SceneTri, Vec3};
|
||||
|
||||
use super::*;
|
||||
|
||||
|
||||
@@ -132,7 +132,7 @@ pub fn paint_tinted(app: &mut crate::ModelApp, ui: &egui::Ui, id: &'static str,
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use modelcraft_engine::catalogue::CATALOGUE;
|
||||
use fieldcraft_engine::catalogue::CATALOGUE;
|
||||
|
||||
#[test]
|
||||
fn every_catalogue_entry_has_an_icon_and_every_icon_an_entry() {
|
||||
|
||||
+27
-27
@@ -1,6 +1,6 @@
|
||||
//! The ModelCraft egui front end.
|
||||
//! The FieldCraft egui front end.
|
||||
//!
|
||||
//! A thin shell over [`modelcraft_engine::Session`]: panels read engine state and act through
|
||||
//! A thin shell over [`fieldcraft_engine::Session`]: panels read engine state and act through
|
||||
//! commands ([`ModelApp::run`]); nothing here owns model data. The window follows the Craft
|
||||
//! family's layout (SolidCraft, VectorCraft):
|
||||
//!
|
||||
@@ -45,9 +45,9 @@ use std::hash::{Hash, Hasher};
|
||||
use std::sync::Arc;
|
||||
use std::sync::mpsc::{Receiver, Sender};
|
||||
|
||||
use modelcraft_engine::Session;
|
||||
use modelcraft_engine::catalogue::{self, Tab, Workspace};
|
||||
use modelcraft_render::{Aabb3, Camera, DisplayMesh, Scene, StandardView};
|
||||
use fieldcraft_engine::Session;
|
||||
use fieldcraft_engine::catalogue::{self, Tab, Workspace};
|
||||
use fieldcraft_render::{Aabb3, Camera, DisplayMesh, Scene, StandardView};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::{Value, json};
|
||||
|
||||
@@ -185,9 +185,9 @@ struct PlotKey {
|
||||
/// The result's identity: every solve stores a new one.
|
||||
result: usize,
|
||||
current: bool,
|
||||
view: modelcraft_engine::results::ResultsView,
|
||||
view: fieldcraft_engine::results::ResultsView,
|
||||
/// The mode and field frame drawn (an animated explicit step changes frames).
|
||||
pick: modelcraft_engine::results::Pick,
|
||||
pick: fieldcraft_engine::results::Pick,
|
||||
frame: Option<u64>,
|
||||
}
|
||||
|
||||
@@ -227,7 +227,7 @@ pub struct ModelApp {
|
||||
/// Whether results were shown last frame (the Results tab comes up when they appear).
|
||||
results_shown: bool,
|
||||
/// Tessellated design bodies, kept while the same design is open.
|
||||
bodies: (Option<Arc<modelcraft_geometry::Design>>, Vec<modelcraft_geometry::SurfaceMesh>),
|
||||
bodies: (Option<Arc<fieldcraft_geometry::Design>>, Vec<fieldcraft_geometry::SurfaceMesh>),
|
||||
/// The frame-independent part of the results plot (scale, contour) and what it was built for.
|
||||
prepared: (Option<PlotKey>, results::Prepared),
|
||||
/// The built scene and its key.
|
||||
@@ -274,7 +274,7 @@ pub struct ModelApp {
|
||||
/// The timeline's items and the journal state they were built for.
|
||||
timeline: Option<((u64, usize, (usize, usize)), std::sync::Arc<Vec<timeline::Item>>)>,
|
||||
/// The browser's element counts and the model revision they were counted at.
|
||||
element_counts: Option<(u64, Vec<(modelcraft_model::ElementKind, usize)>)>,
|
||||
element_counts: Option<(u64, Vec<(fieldcraft_model::ElementKind, usize)>)>,
|
||||
/// The selected definition's property rows and what they were read for.
|
||||
properties: Option<((u64, browser::Def, String), std::sync::Arc<Vec<(String, String)>>)>,
|
||||
/// The search's query and its results.
|
||||
@@ -415,7 +415,7 @@ impl ModelApp {
|
||||
},
|
||||
None => (id, json!({})),
|
||||
};
|
||||
let spec = modelcraft_engine::find_command(command).ok_or_else(|| format!("unknown command `{command}`"))?;
|
||||
let spec = fieldcraft_engine::find_command(command).ok_or_else(|| format!("unknown command `{command}`"))?;
|
||||
if self.file_action(spec.id, &preset, interactive) {
|
||||
self.dialog = None;
|
||||
return Ok(());
|
||||
@@ -432,7 +432,7 @@ impl ModelApp {
|
||||
|
||||
/// Open a command's parameter form with `params` filled in.
|
||||
pub fn open_form(&mut self, command: &str, params: Value) {
|
||||
if let Some(spec) = modelcraft_engine::find_command(command) {
|
||||
if let Some(spec) = fieldcraft_engine::find_command(command) {
|
||||
self.dialog = Some(ParamsDialog { command: spec.id, text: params.to_string(), error: None });
|
||||
}
|
||||
}
|
||||
@@ -472,7 +472,7 @@ impl ModelApp {
|
||||
let triple = |v: [f64; 3]| format!("{}, {}, {}", results::number(v[0]), results::number(v[1]), results::number(v[2]));
|
||||
let mut lines = vec![format!("Node {node}")];
|
||||
if let Some(u) = vector("displacement") {
|
||||
lines.push(format!("U {} |U| {}", triple(u), results::number(modelcraft_engine::results::magnitude(u))));
|
||||
lines.push(format!("U {} |U| {}", triple(u), results::number(fieldcraft_engine::results::magnitude(u))));
|
||||
}
|
||||
if let Some(rf) = vector("reaction").filter(|r| r.iter().any(|c| *c != 0.0)) {
|
||||
lines.push(format!("RF {}", triple(rf)));
|
||||
@@ -481,13 +481,13 @@ impl ModelApp {
|
||||
lines.push(format!("Von Mises {}", results::number(m)));
|
||||
}
|
||||
if let Some(v) = vector("velocity") {
|
||||
lines.push(format!("V {} |V| {}", triple(v), results::number(modelcraft_engine::results::magnitude(v))));
|
||||
lines.push(format!("V {} |V| {}", triple(v), results::number(fieldcraft_engine::results::magnitude(v))));
|
||||
}
|
||||
if let Some(shape) = vector("shape") {
|
||||
lines.push(format!("Mode {} shape {}", pick.mode, triple(shape)));
|
||||
}
|
||||
let contour = self.plot().and_then(|p| p.contour.as_ref().map(|c| (c.field, c.at(p.result.nodes(), node))));
|
||||
if let Some((field, v)) = contour.filter(|(f, _)| *f != modelcraft_engine::results::Field::VonMises) {
|
||||
if let Some((field, v)) = contour.filter(|(f, _)| *f != fieldcraft_engine::results::Field::VonMises) {
|
||||
lines.push(format!("{} {}", field.label(), results::number(v)));
|
||||
}
|
||||
self.set_status(lines.join(" "), false);
|
||||
@@ -615,10 +615,10 @@ impl ModelApp {
|
||||
}
|
||||
|
||||
/// A CPU render of the viewport's scene with the current camera, as PNG bytes.
|
||||
pub fn render_png(&mut self, w: usize, h: usize) -> Result<Vec<u8>, modelcraft_render::RenderError> {
|
||||
pub fn render_png(&mut self, w: usize, h: usize) -> Result<Vec<u8>, fieldcraft_render::RenderError> {
|
||||
let cam = self.cam;
|
||||
let (_, scene) = self.scene();
|
||||
modelcraft_render::render_png(scene, &cam, w, h)
|
||||
fieldcraft_render::render_png(scene, &cam, w, h)
|
||||
}
|
||||
|
||||
/// Frame the whole model.
|
||||
@@ -850,12 +850,12 @@ const PERSPECTIVE_FOV: f64 = 0.6;
|
||||
|
||||
/// A command's name as people see it (its label).
|
||||
pub fn command_label(id: &str) -> &str {
|
||||
modelcraft_engine::find_command(id).map_or(id, |c| c.label)
|
||||
fieldcraft_engine::find_command(id).map_or(id, |c| c.label)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use modelcraft_render::Vec3;
|
||||
use fieldcraft_render::Vec3;
|
||||
|
||||
use super::*;
|
||||
|
||||
@@ -910,7 +910,7 @@ mod tests {
|
||||
#[test]
|
||||
fn the_sample_fills_the_view_and_renders() {
|
||||
let mut app = ModelApp::new(Session::default());
|
||||
app.run_script(&modelcraft_engine::sample::script()).unwrap();
|
||||
app.run_script(&fieldcraft_engine::sample::script()).unwrap();
|
||||
let d = app.display().clone();
|
||||
assert!(!d.tris.is_empty() && !d.edges.is_empty());
|
||||
// Fitted: the model's centre is the camera target.
|
||||
@@ -921,10 +921,10 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn the_open_design_and_imported_surfaces_are_drawn() {
|
||||
let dir = std::env::temp_dir().join(format!("modelcraft-ui-design-{}", std::process::id()));
|
||||
let dir = std::env::temp_dir().join(format!("fieldcraft-ui-design-{}", std::process::id()));
|
||||
std::fs::create_dir_all(&dir).unwrap();
|
||||
let design = dir.join("plate.solidcraft");
|
||||
let bytes = modelcraft_geometry::sample::plate_design(&modelcraft_geometry::sample::Plate::default()).unwrap();
|
||||
let bytes = fieldcraft_geometry::sample::plate_design(&fieldcraft_geometry::sample::Plate::default()).unwrap();
|
||||
std::fs::write(&design, bytes).unwrap();
|
||||
let mut app = ModelApp::new(Session::default());
|
||||
app.run("geometry.open", json!({"path": design.to_string_lossy()})).unwrap();
|
||||
@@ -952,17 +952,17 @@ mod tests {
|
||||
#[test]
|
||||
fn solving_shows_the_result_on_the_results_tab() {
|
||||
let mut app = ModelApp::new(Session::default());
|
||||
app.run_script(&modelcraft_engine::sample::cantilever()).unwrap();
|
||||
app.run_script(&fieldcraft_engine::sample::cantilever()).unwrap();
|
||||
app.start("solve.static").unwrap();
|
||||
app.run_dialog().unwrap();
|
||||
let view = app.session.results_view.clone().unwrap();
|
||||
assert_eq!((view.step.as_str(), view.contour, view.deformed), ("Load", Some(modelcraft_engine::results::Field::VonMises), true));
|
||||
assert_eq!((view.step.as_str(), view.contour, view.deformed), ("Load", Some(fieldcraft_engine::results::Field::VonMises), true));
|
||||
app.logic(&egui::Context::default());
|
||||
assert_eq!(app.ui.tab, Tab::Results);
|
||||
app.run("solve.frequency", json!({})).unwrap();
|
||||
app.run("results.deformed", json!({"step": "Modes"})).unwrap();
|
||||
let view = app.session.results_view.clone().unwrap();
|
||||
assert_eq!((view.step.as_str(), view.mode, view.contour), ("Modes", 1, Some(modelcraft_engine::results::Field::Displacement)));
|
||||
assert_eq!((view.step.as_str(), view.mode, view.contour), ("Modes", 1, Some(fieldcraft_engine::results::Field::Displacement)));
|
||||
// The plot draws contour bands: more triangles than the model's faces.
|
||||
let faces = app.display().tris.len();
|
||||
assert!(app.scene().1.tris.len() > faces);
|
||||
@@ -984,7 +984,7 @@ mod tests {
|
||||
let (w, h) = (400, 200);
|
||||
let cam = app.cam;
|
||||
let radius = app.scene().1.radius;
|
||||
let canvas = modelcraft_render::render(app.scene().1, &cam, w, h).unwrap();
|
||||
let canvas = fieldcraft_render::render(app.scene().1, &cam, w, h).unwrap();
|
||||
let pixel = |p: Vec3| {
|
||||
let (x, y, _) = cam.to_screen(p, w as f64, h as f64, radius).unwrap();
|
||||
let i = (y as usize * w + x as usize) * 4;
|
||||
@@ -1027,13 +1027,13 @@ mod tests {
|
||||
#[test]
|
||||
fn explicit_frames_are_drawn_and_animated() {
|
||||
let mut app = ModelApp::new(Session::default());
|
||||
app.run_script(&modelcraft_engine::sample::impact()).unwrap();
|
||||
app.run_script(&fieldcraft_engine::sample::impact()).unwrap();
|
||||
app.run("solve.explicit", json!({})).unwrap();
|
||||
// The last frame at true scale: the bar has moved back off the wall's side (x < 0 is
|
||||
// the wall, so nothing passes it) and carries stress contours.
|
||||
let last = app.plot().unwrap();
|
||||
assert_eq!(last.frame().map(|(k, _, n)| (k, n)), Some((10, 11)));
|
||||
assert!(last.contour.as_ref().is_some_and(|c| c.field == modelcraft_engine::results::Field::VonMises));
|
||||
assert!(last.contour.as_ref().is_some_and(|c| c.field == fieldcraft_engine::results::Field::VonMises));
|
||||
assert!(last.display.bounds.min.x > -1e-6, "{:?}", last.display.bounds);
|
||||
// Frame 0 is the undeformed bar at rest on its initial velocity.
|
||||
app.run("results.frame", json!({"frame": 0})).unwrap();
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
//! `crates/ui-egui/src/palette.rs` (MIT OR Apache-2.0), see ATTRIBUTION.md.
|
||||
|
||||
use egui::{Align2, FontId, Rect, RichText, Sense, Vec2, pos2, vec2};
|
||||
use modelcraft_engine::catalogue::{self, CATALOGUE};
|
||||
use fieldcraft_engine::catalogue::{self, CATALOGUE};
|
||||
use serde_json::Value;
|
||||
|
||||
use crate::ModelApp;
|
||||
@@ -80,7 +80,7 @@ pub fn submit(app: &mut ModelApp, line: &str) {
|
||||
Some(i) => (line.get(..i).unwrap_or(line), line.get(i..).unwrap_or("").trim()),
|
||||
None => (line, ""),
|
||||
};
|
||||
if modelcraft_engine::find_command(id).is_some() || catalogue::find(id).is_some() {
|
||||
if fieldcraft_engine::find_command(id).is_some() || catalogue::find(id).is_some() {
|
||||
if rest.is_empty() {
|
||||
let _ = app.start(id);
|
||||
} else {
|
||||
@@ -104,7 +104,7 @@ pub fn submit(app: &mut ModelApp, line: &str) {
|
||||
/// The command line, the status and the model's size, above the timeline.
|
||||
pub fn command_line(app: &mut ModelApp, ui: &mut egui::Ui) {
|
||||
let t = Tokens::get(app.ui.dark);
|
||||
egui::Panel::bottom("mc_command_line")
|
||||
egui::Panel::bottom("fc_command_line")
|
||||
.exact_size(HEIGHT)
|
||||
.frame(egui::Frame::NONE.fill(t.panel).inner_margin(egui::Margin::symmetric(8, 3)))
|
||||
.show(ui, |ui| {
|
||||
@@ -113,7 +113,7 @@ pub fn command_line(app: &mut ModelApp, ui: &mut egui::Ui) {
|
||||
ui.label(RichText::new("›").strong().color(t.accent));
|
||||
let r = ui.add(
|
||||
egui::TextEdit::singleline(&mut app.command_line)
|
||||
.id(egui::Id::new("mc_command_line_text"))
|
||||
.id(egui::Id::new("fc_command_line_text"))
|
||||
.hint_text(r#"Type a command, e.g. mesh.node.add {"label": 1, "xyz": [0, 0, 0]} — or press S to search"#)
|
||||
.desired_width(440.0),
|
||||
);
|
||||
@@ -149,7 +149,7 @@ pub fn popup(app: &mut ModelApp, ctx: &egui::Context) {
|
||||
let mut open = true;
|
||||
let center = ctx.content_rect().center();
|
||||
let r = crate::frame::window(ctx, "Search commands", crate::frame::Width::Normal)
|
||||
.id(egui::Id::new("mc_search"))
|
||||
.id(egui::Id::new("fc_search"))
|
||||
.pivot(Align2::CENTER_TOP)
|
||||
.fixed_pos(pos2(center.x, 140.0))
|
||||
.fixed_size(vec2(440.0, 380.0))
|
||||
@@ -157,7 +157,7 @@ pub fn popup(app: &mut ModelApp, ctx: &egui::Context) {
|
||||
.show(ctx, |ui| {
|
||||
let r = ui.add(
|
||||
egui::TextEdit::singleline(&mut app.palette_text)
|
||||
.id(egui::Id::new("mc_search_text"))
|
||||
.id(egui::Id::new("fc_search_text"))
|
||||
.hint_text("command name, id or what it does")
|
||||
.desired_width(f32::INFINITY),
|
||||
);
|
||||
@@ -221,7 +221,7 @@ pub fn popup(app: &mut ModelApp, ctx: &egui::Context) {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use modelcraft_engine::Session;
|
||||
use fieldcraft_engine::Session;
|
||||
|
||||
use super::*;
|
||||
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
//! The results display in the viewport: what the engine's [`ResultsView`] asks for, drawn on the
|
||||
//! model's display data. The deformed shape moves every element by its nodes' displacements
|
||||
//! (or a mode shape) times the scale; contours split the faces into flat-coloured bands
|
||||
//! ([`modelcraft_render::contour`]); animation steps the deformation through a fixed number of
|
||||
//! ([`fieldcraft_render::contour`]); animation steps the deformation through a fixed number of
|
||||
//! frames per cycle (an explicit step's animation runs through its field frames, contours
|
||||
//! following). A plot of a stale result (the model changed since the solve) is drawn the
|
||||
//! same way and marked.
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use modelcraft_engine::results::{Field, Kind, Pick, ResultsView, displacements, field_values, magnitude, range};
|
||||
use modelcraft_engine::{Session, StepResult};
|
||||
use modelcraft_render::contour::{Bands, band_color, split_triangle};
|
||||
use modelcraft_render::{DisplayMesh, Scene, SceneTri, Vec3};
|
||||
use fieldcraft_engine::results::{Field, Kind, Pick, ResultsView, displacements, field_values, magnitude, range};
|
||||
use fieldcraft_engine::{Session, StepResult};
|
||||
use fieldcraft_render::contour::{Bands, band_color, split_triangle};
|
||||
use fieldcraft_render::{DisplayMesh, Scene, SceneTri, Vec3};
|
||||
|
||||
use crate::Style;
|
||||
use crate::theme::{Tokens, rgb};
|
||||
@@ -227,7 +227,7 @@ pub(crate) mod tests {
|
||||
/// The sample cantilever, solved (static step `Load`, frequency step `Modes`).
|
||||
pub(crate) fn solved_cantilever() -> Session {
|
||||
let mut s = Session::default();
|
||||
s.run_script(&modelcraft_engine::sample::cantilever()).unwrap();
|
||||
s.run_script(&fieldcraft_engine::sample::cantilever()).unwrap();
|
||||
s.execute("solve.static", &json!({})).unwrap();
|
||||
s.execute("solve.frequency", &json!({})).unwrap();
|
||||
s
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
//! panel (right), which run the `results.*` commands.
|
||||
|
||||
use egui::{Align2, Color32, FontId, Rect, RichText, Stroke, pos2, vec2};
|
||||
use modelcraft_engine::results::{Field, Kind};
|
||||
use modelcraft_explicit::{Energies, ExplicitResult};
|
||||
use fieldcraft_engine::results::{Field, Kind};
|
||||
use fieldcraft_explicit::{Energies, ExplicitResult};
|
||||
use serde_json::{Value, json};
|
||||
|
||||
use crate::ModelApp;
|
||||
@@ -76,7 +76,7 @@ fn legend(plot: &Plot, painter: &egui::Painter, rect: Rect, t: &Tokens) {
|
||||
for k in (0..l.bands.count).rev() {
|
||||
let row = (l.bands.count - 1 - k) as f32;
|
||||
let r = Rect::from_min_size(pos2(origin.x + 10.0, y + row * SWATCH.y), SWATCH);
|
||||
let c = modelcraft_render::contour::band_color(&stops, k, l.bands.count);
|
||||
let c = fieldcraft_render::contour::band_color(&stops, k, l.bands.count);
|
||||
painter.rect_filled(r, 0.0, Color32::from_rgb(c.0, c.1, c.2));
|
||||
if k + 1 == l.bands.count {
|
||||
painter.text(pos2(r.right() + 6.0, r.top()), Align2::LEFT_CENTER, number(l.bands.level(k + 1)), font.clone(), t.overlay_text);
|
||||
@@ -111,9 +111,9 @@ fn probe(app: &ModelApp, plot: &Plot, painter: &egui::Painter, t: &Tokens) {
|
||||
pub fn controls(app: &mut ModelApp, plot: &Plot, ui: &mut egui::Ui, t: &Tokens) {
|
||||
let mut run: Vec<(&'static str, Value)> = Vec::new();
|
||||
let steps: Vec<(String, bool)> = app.session.result_steps().map(|(n, c)| (n.to_string(), c)).collect();
|
||||
egui::Grid::new("mc_results_grid").num_columns(2).spacing(vec2(8.0, 4.0)).show(ui, |ui| {
|
||||
egui::Grid::new("fc_results_grid").num_columns(2).spacing(vec2(8.0, 4.0)).show(ui, |ui| {
|
||||
ui.label("Step");
|
||||
egui::ComboBox::from_id_salt("mc_results_step").selected_text(&plot.view.step).width(COMBO_WIDTH).truncate().show_ui(ui, |ui| {
|
||||
egui::ComboBox::from_id_salt("fc_results_step").selected_text(&plot.view.step).width(COMBO_WIDTH).truncate().show_ui(ui, |ui| {
|
||||
for (name, current) in &steps {
|
||||
let label = if *current { name.clone() } else { format!("{name} (stale)") };
|
||||
if ui.selectable_label(*name == plot.view.step, label).clicked() && *name != plot.view.step {
|
||||
@@ -124,7 +124,7 @@ pub fn controls(app: &mut ModelApp, plot: &Plot, ui: &mut egui::Ui, t: &Tokens)
|
||||
ui.end_row();
|
||||
ui.label("Contours");
|
||||
let shown = plot.view.contour.map_or("None", Field::label);
|
||||
egui::ComboBox::from_id_salt("mc_results_field").selected_text(shown).width(COMBO_WIDTH).truncate().show_ui(ui, |ui| {
|
||||
egui::ComboBox::from_id_salt("fc_results_field").selected_text(shown).width(COMBO_WIDTH).truncate().show_ui(ui, |ui| {
|
||||
if ui.selectable_label(plot.view.contour.is_none(), "None").clicked() {
|
||||
run.push(("results.contour", json!({"field": "none"})));
|
||||
}
|
||||
@@ -165,7 +165,7 @@ pub fn controls(app: &mut ModelApp, plot: &Plot, ui: &mut egui::Ui, t: &Tokens)
|
||||
} else if let Kind::Frequency(f) = plot.result.kind() {
|
||||
ui.separator();
|
||||
ui.label(RichText::new("Modes").color(t.text_dim));
|
||||
egui::ScrollArea::vertical().id_salt("mc_results_modes").max_height(150.0).show(ui, |ui| {
|
||||
egui::ScrollArea::vertical().id_salt("fc_results_modes").max_height(150.0).show(ui, |ui| {
|
||||
for (i, m) in f.modes.iter().enumerate() {
|
||||
let mode = i + 1;
|
||||
if ui.selectable_label(plot.view.mode == mode, format!("{mode} {} Hz", number(m.frequency))).clicked() {
|
||||
@@ -236,7 +236,7 @@ fn explicit(ui: &mut egui::Ui, e: &ExplicitResult, plot: &Plot, t: &Tokens, run:
|
||||
painter.line_segment([pos2(x, rect.top()), pos2(x, rect.bottom())], Stroke::new(1.0, t.text_dim));
|
||||
let i = h.time.iter().enumerate().min_by(|a, b| (a.1 - now).abs().total_cmp(&(b.1 - now).abs())).map_or(0, |(i, _)| i);
|
||||
let Some(at_now) = h.energies.get(i) else { return };
|
||||
egui::Grid::new("mc_results_energies").num_columns(2).spacing(vec2(8.0, 2.0)).show(ui, |ui| {
|
||||
egui::Grid::new("fc_results_energies").num_columns(2).spacing(vec2(8.0, 2.0)).show(ui, |ui| {
|
||||
for (name, color, f) in &series {
|
||||
ui.label(RichText::new(*name).color(*color));
|
||||
ui.label(number(f(at_now)));
|
||||
@@ -257,7 +257,7 @@ fn reactions_table(ui: &mut egui::Ui, r: &Value, t: &Tokens) {
|
||||
ui.label(f(2));
|
||||
ui.end_row();
|
||||
};
|
||||
egui::Grid::new("mc_results_reactions").num_columns(4).spacing(vec2(6.0, 2.0)).show(ui, |ui| {
|
||||
egui::Grid::new("fc_results_reactions").num_columns(4).spacing(vec2(6.0, 2.0)).show(ui, |ui| {
|
||||
for h in ["Set", "Fx", "Fy", "Fz"] {
|
||||
ui.label(RichText::new(h).small().color(t.text_dim));
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
//! the model has no element faces; once it is meshed, as boundaries only, so the mesh is not
|
||||
//! hidden behind coincident faces.
|
||||
|
||||
use modelcraft_render::{Aabb3, Camera, DisplayMesh, Scene, SceneLine, ScenePoint, SceneTri};
|
||||
use fieldcraft_render::{Aabb3, Camera, DisplayMesh, Scene, SceneLine, ScenePoint, SceneTri};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::theme::{Tokens, rgb};
|
||||
@@ -99,7 +99,7 @@ pub fn radius(b: &Aabb3, cam: &Camera) -> f64 {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use modelcraft_model::{ElementKind, Model};
|
||||
use fieldcraft_model::{ElementKind, Model};
|
||||
|
||||
use super::*;
|
||||
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
//! ATTRIBUTION.md. Widget states are coloured with craft-ui's `WidgetPalette`.
|
||||
|
||||
use egui::{Color32, Stroke};
|
||||
use modelcraft_model::ElementKind;
|
||||
use modelcraft_render::Rgb;
|
||||
use fieldcraft_model::ElementKind;
|
||||
use fieldcraft_render::Rgb;
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub struct Tokens {
|
||||
|
||||
@@ -123,7 +123,7 @@ fn transport_glyph(p: &egui::Painter, c: egui::Pos2, what: &str, col: Color32) {
|
||||
pub fn timeline(app: &mut ModelApp, ui: &mut egui::Ui) {
|
||||
let t = Tokens::get(app.ui.dark);
|
||||
let mut roll: Option<usize> = None;
|
||||
egui::Panel::bottom("mc_timeline").exact_size(HEIGHT).show_separator_line(false).frame(egui::Frame::NONE.fill(t.timeline)).show(ui, |ui| {
|
||||
egui::Panel::bottom("fc_timeline").exact_size(HEIGHT).show_separator_line(false).frame(egui::Frame::NONE.fill(t.timeline)).show(ui, |ui| {
|
||||
let r = ui.max_rect();
|
||||
app.place("timeline", r);
|
||||
let p = ui.painter().clone();
|
||||
@@ -140,7 +140,7 @@ pub fn timeline(app: &mut ModelApp, ui: &mut egui::Ui) {
|
||||
] {
|
||||
let br = Rect::from_center_size(pos2(x + 11.0, r.center().y), Vec2::splat(22.0));
|
||||
let enabled = target != done && target >= oldest && target <= done + redo;
|
||||
let resp = ui.interact(br, ui.id().with(("mc_transport", what)), if enabled { Sense::click() } else { Sense::hover() });
|
||||
let resp = ui.interact(br, ui.id().with(("fc_transport", what)), if enabled { Sense::click() } else { Sense::hover() });
|
||||
if resp.hovered() && enabled {
|
||||
p.rect_filled(br, 3.0, t.hover);
|
||||
}
|
||||
@@ -165,7 +165,7 @@ pub fn timeline(app: &mut ModelApp, ui: &mut egui::Ui) {
|
||||
return;
|
||||
}
|
||||
ui.scope_builder(UiBuilder::new().max_rect(strip), |ui| {
|
||||
egui::ScrollArea::horizontal().id_salt("mc_timeline_scroll").stick_to_right(true).auto_shrink([false, false]).show(ui, |ui| {
|
||||
egui::ScrollArea::horizontal().id_salt("fc_timeline_scroll").stick_to_right(true).auto_shrink([false, false]).show(ui, |ui| {
|
||||
let marker_at = list.iter().position(|i| i.start >= done).unwrap_or(list.len());
|
||||
let width = list.len() as f32 * STRIDE + 12.0;
|
||||
let (area, _) = ui.allocate_exact_size(vec2(width, r.height()), Sense::hover());
|
||||
@@ -185,10 +185,10 @@ pub fn timeline(app: &mut ModelApp, ui: &mut egui::Ui) {
|
||||
}
|
||||
let undone = item.start >= done;
|
||||
let reachable = item.end >= oldest;
|
||||
let resp = ui.interact(br, ui.id().with(("mc_journal", k)), if reachable { Sense::click() } else { Sense::hover() });
|
||||
let resp = ui.interact(br, ui.id().with(("fc_journal", k)), if reachable { Sense::click() } else { Sense::hover() });
|
||||
let bg = if resp.hovered() && reachable { t.hover } else { t.timeline_item };
|
||||
p.rect(br, 3.0, bg, Stroke::new(1.0, t.border), egui::StrokeKind::Inside);
|
||||
let icon = modelcraft_engine::catalogue::entry_of(&item.command).map_or("ui.views", |c| c.id);
|
||||
let icon = fieldcraft_engine::catalogue::entry_of(&item.command).map_or("ui.views", |c| c.id);
|
||||
let tint = if undone || !reachable { Color32::from_white_alpha(110) } else { Color32::WHITE };
|
||||
icons::paint_tinted(app, ui, icon, br.shrink(4.0), Look::of(!undone), tint);
|
||||
if item.count() > 1 {
|
||||
@@ -222,7 +222,7 @@ pub fn timeline(app: &mut ModelApp, ui: &mut egui::Ui) {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use modelcraft_engine::Session;
|
||||
use fieldcraft_engine::Session;
|
||||
|
||||
use super::*;
|
||||
|
||||
|
||||
@@ -170,7 +170,7 @@ pub fn resize_zones(ui: &mut Ui) {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use egui::{Event, Pos2, RawInput, ViewportId};
|
||||
use modelcraft_engine::Session;
|
||||
use fieldcraft_engine::Session;
|
||||
|
||||
use super::*;
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
//! The ribbon: the workspace switcher, the workspace's tabs, and the current tab's panels, all
|
||||
//! from the capability catalogue (`modelcraft_engine::catalogue`).
|
||||
//! from the capability catalogue (`fieldcraft_engine::catalogue`).
|
||||
//!
|
||||
//! Each panel shows its ready entries as icon buttons (up to [`MAX_BUTTONS`]; a panel with none
|
||||
//! ready shows its first entry, greyed with its milestone) over its label with a caret. The label
|
||||
@@ -13,7 +13,7 @@
|
||||
//! (MIT OR Apache-2.0), see ATTRIBUTION.md; the tabs are craft-ui `Tab`s.
|
||||
|
||||
use egui::{Align2, Color32, FontId, Rect, Sense, Stroke, Vec2, pos2, vec2};
|
||||
use modelcraft_engine::catalogue::{self, Capability, Tab, Workspace};
|
||||
use fieldcraft_engine::catalogue::{self, Capability, Tab, Workspace};
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::ModelApp;
|
||||
@@ -115,7 +115,7 @@ fn badge(p: &egui::Painter, t: &Tokens, corner: egui::Pos2, m: &str) {
|
||||
pub fn toolbar(app: &mut ModelApp, ui: &mut egui::Ui) {
|
||||
let t = Tokens::get(app.ui.dark);
|
||||
let mut start: Option<&'static str> = None;
|
||||
egui::Panel::top("mc_toolbar").exact_size(HEIGHT).show_separator_line(false).frame(egui::Frame::NONE.fill(t.toolbar)).show(ui, |ui| {
|
||||
egui::Panel::top("fc_toolbar").exact_size(HEIGHT).show_separator_line(false).frame(egui::Frame::NONE.fill(t.toolbar)).show(ui, |ui| {
|
||||
let r = ui.max_rect();
|
||||
app.place("ribbon", r);
|
||||
app.toolbar_rects.clear();
|
||||
@@ -129,7 +129,7 @@ pub fn toolbar(app: &mut ModelApp, ui: &mut egui::Ui) {
|
||||
let w = 12.0 + painter.layout_no_wrap(label.clone(), label_font(ui.ctx(), 12.0), t.text).size().x.max(28.0);
|
||||
let tr = Rect::from_min_size(pos2(x, r.top() + 2.0), vec2(w, 22.0));
|
||||
let active = app.ui.tab == tab;
|
||||
let resp = craft_ui::tabs::Tab::new(egui::Id::new(("mc_tab", panel_key(tab, ""))), &label, active)
|
||||
let resp = craft_ui::tabs::Tab::new(egui::Id::new(("fc_tab", panel_key(tab, ""))), &label, active)
|
||||
.focus_stroke(Stroke::new(1.0, t.accent))
|
||||
.show_at(ui, tr, |ui, resp| {
|
||||
let p = ui.painter();
|
||||
@@ -179,7 +179,7 @@ pub fn toolbar(app: &mut ModelApp, ui: &mut egui::Ui) {
|
||||
if collapsed {
|
||||
// One button: the panel's first button's icon over a caret; it opens the drop-down.
|
||||
let br = Rect::from_center_size(pos2(px + width / 2.0, top + 21.0), Vec2::splat(BUTTON));
|
||||
let resp = ui.interact(br, ui.id().with(("mc_collapsed", &key)), Sense::click());
|
||||
let resp = ui.interact(br, ui.id().with(("fc_collapsed", &key)), Sense::click());
|
||||
if resp.hovered() {
|
||||
painter.rect_filled(br, 4.0, t.hover);
|
||||
}
|
||||
@@ -232,7 +232,7 @@ pub fn toolbar(app: &mut ModelApp, ui: &mut egui::Ui) {
|
||||
} else {
|
||||
Rect::from_center_size(pos2(px + width / 2.0, top + 51.0), vec2(width, lh))
|
||||
};
|
||||
let lresp = ui.interact(lr, ui.id().with(("mc_panel", &key)), Sense::click());
|
||||
let lresp = ui.interact(lr, ui.id().with(("fc_panel", &key)), Sense::click());
|
||||
let open = app.ui.menu.as_deref() == Some(key.as_str());
|
||||
if lresp.hovered() || open {
|
||||
painter.rect_filled(lr, 3.0, t.hover);
|
||||
@@ -253,7 +253,7 @@ pub fn toolbar(app: &mut ModelApp, ui: &mut egui::Ui) {
|
||||
}
|
||||
if !overflow.is_empty() {
|
||||
let mr = Rect::from_min_size(pos2(px.min(r.right() - MORE_WIDTH), top + 4.0), vec2(MORE_WIDTH - 2.0, 52.0));
|
||||
let resp = ui.interact(mr, ui.id().with("mc_more"), Sense::click());
|
||||
let resp = ui.interact(mr, ui.id().with("fc_more"), Sense::click());
|
||||
if resp.hovered() || app.ui.menu.as_deref() == Some("more") {
|
||||
painter.rect_filled(mr, 4.0, t.hover);
|
||||
}
|
||||
@@ -281,7 +281,7 @@ pub fn toolbar(app: &mut ModelApp, ui: &mut egui::Ui) {
|
||||
/// it opens the list of workspaces.
|
||||
fn workspace_switcher(app: &mut ModelApp, ui: &mut egui::Ui, t: &Tokens, r: Rect) {
|
||||
let ws = Rect::from_min_max(pos2(r.left() + 6.0, r.top() + 4.0), pos2(r.left() + 104.0, r.bottom() - 5.0));
|
||||
let resp = ui.interact(ws, ui.id().with("mc_workspace"), Sense::click());
|
||||
let resp = ui.interact(ws, ui.id().with("fc_workspace"), Sense::click());
|
||||
let open = app.ui.menu.as_deref() == Some("workspace");
|
||||
ui.painter().rect(ws, 4.0, if resp.hovered() || open { t.hover } else { t.field }, Stroke::new(1.0, t.border), egui::StrokeKind::Inside);
|
||||
let w = app.ui.workspace;
|
||||
@@ -320,7 +320,7 @@ fn workspace_switcher(app: &mut ModelApp, ui: &mut egui::Ui, t: &Tokens, r: Rect
|
||||
/// One entry's button; returns the id to start when it was clicked.
|
||||
fn button(app: &mut ModelApp, ui: &mut egui::Ui, t: &Tokens, c: &'static Capability, br: Rect) -> Option<&'static str> {
|
||||
let enabled = c.command().is_some();
|
||||
let resp = ui.interact(br, ui.id().with(("mc_button", c.id)), if enabled { Sense::click() } else { Sense::hover() });
|
||||
let resp = ui.interact(br, ui.id().with(("fc_button", c.id)), if enabled { Sense::click() } else { Sense::hover() });
|
||||
app.toolbar_rects.push((c.id, br, Fit::Full));
|
||||
let running = app.dialog.as_ref().is_some_and(|d| c.command() == Some(d.command));
|
||||
if running {
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
//! follow SolidCraft's `viewport.rs` (MIT OR Apache-2.0), see ATTRIBUTION.md.
|
||||
|
||||
use egui::{Color32, Pos2, Rect, Sense, Stroke, Vec2, pos2, vec2};
|
||||
use modelcraft_render::{StandardView, Vec3};
|
||||
use fieldcraft_render::{StandardView, Vec3};
|
||||
|
||||
use crate::gpu::{GpuScene, ViewportCallback};
|
||||
use crate::icons::{self, Look};
|
||||
@@ -144,14 +144,14 @@ fn paint_gpu(app: &mut ModelApp, ui: &egui::Ui, painter: &egui::Painter, rect: R
|
||||
fn paint_cpu(app: &mut ModelApp, ui: &egui::Ui, painter: &egui::Painter, rect: Rect) {
|
||||
let ppp = ui.ctx().pixels_per_point();
|
||||
// A window larger than the renderer's limit is drawn smaller and scaled up.
|
||||
let (w, h) = modelcraft_render::fit_size((rect.width() * ppp) as usize, (rect.height() * ppp) as usize);
|
||||
let (w, h) = fieldcraft_render::fit_size((rect.width() * ppp) as usize, (rect.height() * ppp) as usize);
|
||||
let size = [w, h];
|
||||
let cam = app.cam;
|
||||
let key = app.scene().0;
|
||||
let fresh = app.viewport.cpu.as_ref().is_some_and(|(_, k, c, s)| *k == key && *c == cam && *s == size);
|
||||
// A scene the renderer refused is reported once and not retried until it changes.
|
||||
if !fresh && app.viewport.cpu_failed != Some(key) {
|
||||
let c = match modelcraft_render::render(&app.scene.1, &cam, w, h) {
|
||||
let c = match fieldcraft_render::render(&app.scene.1, &cam, w, h) {
|
||||
Ok(c) => c,
|
||||
Err(e) => {
|
||||
app.viewport.cpu = None;
|
||||
@@ -167,7 +167,7 @@ fn paint_cpu(app: &mut ModelApp, ui: &egui::Ui, painter: &egui::Painter, rect: R
|
||||
tex.set(image, egui::TextureOptions::LINEAR);
|
||||
tex
|
||||
}
|
||||
None => ui.ctx().load_texture("mc_viewport_cpu", image, egui::TextureOptions::LINEAR),
|
||||
None => ui.ctx().load_texture("fc_viewport_cpu", image, egui::TextureOptions::LINEAR),
|
||||
};
|
||||
app.viewport.cpu = Some((tex, key, cam, size));
|
||||
}
|
||||
@@ -184,7 +184,7 @@ const AXIS_RADIUS: f32 = 11.0;
|
||||
fn view_along(d: Vec3) -> StandardView {
|
||||
let mut best = (StandardView::Front, f64::NEG_INFINITY);
|
||||
for v in StandardView::ALL.into_iter().filter(|v| *v != StandardView::Iso) {
|
||||
let mut c = modelcraft_render::Camera::default();
|
||||
let mut c = fieldcraft_render::Camera::default();
|
||||
c.set_view(v);
|
||||
let score = c.back().dot(d);
|
||||
if score > best.1 {
|
||||
@@ -231,7 +231,7 @@ fn axis_view(app: &mut ModelApp, ui: &mut egui::Ui, rect: Rect, t: &Tokens) {
|
||||
painter.circle_stroke(*at, AXIS_RADIUS, Stroke::new(if hot { 2.0 } else { 1.0 }, if hot { t.accent } else { ink }));
|
||||
painter.text(*at, egui::Align2::CENTER_CENTER, *name, egui::FontId::proportional(11.0), ink);
|
||||
}
|
||||
let resp = ui.interact(area, ui.id().with("mc_axis_view"), Sense::click());
|
||||
let resp = ui.interact(area, ui.id().with("fc_axis_view"), Sense::click());
|
||||
if let Some(i) = hovered
|
||||
&& let Some((name, d, ..)) = tips.get(i)
|
||||
{
|
||||
@@ -242,7 +242,7 @@ fn axis_view(app: &mut ModelApp, ui: &mut egui::Ui, rect: Rect, t: &Tokens) {
|
||||
}
|
||||
}
|
||||
let home = Rect::from_center_size(pos2(c.x - 44.0, c.y - 44.0), Vec2::splat(18.0));
|
||||
let hr = ui.interact(home, ui.id().with("mc_axis_home"), Sense::click());
|
||||
let hr = ui.interact(home, ui.id().with("fc_axis_home"), Sense::click());
|
||||
if hr.hovered() {
|
||||
painter.rect_filled(home.expand(2.0), 3.0, t.hover);
|
||||
}
|
||||
@@ -276,7 +276,7 @@ fn nav_bar(app: &mut ModelApp, ui: &mut egui::Ui, rect: Rect, t: &Tokens) {
|
||||
let mut style_button = None;
|
||||
for (i, (key, icon, tip)) in NAV.iter().enumerate() {
|
||||
let br = Rect::from_min_size(pos2(bar.left() + 5.0 + i as f32 * 30.0, bar.top() + 2.0), Vec2::splat(26.0));
|
||||
let resp = ui.interact(br, ui.id().with(("mc_nav", *key)), Sense::click());
|
||||
let resp = ui.interact(br, ui.id().with(("fc_nav", *key)), Sense::click());
|
||||
let active = match *key {
|
||||
"orbit" => app.nav == NavMode::Orbit,
|
||||
"pan" => app.nav == NavMode::Pan,
|
||||
|
||||
Reference in new issue
Block a user