//! The command registry. Ids are SolveCraft's own dotted ids (`solid.extrude`, //! `sketch.rectangle.two_point`); the toolbar placement (`tab`, `panel`) follows the familiar //! Design workspace layout. Ids used before the rename still resolve (`legacy_ids`). /// The params-doc note of every command that adds sketch geometry (see `sketch::created`). macro_rules! created_doc { () => { ". Returns created: {points, curves, constraints, dimensions}: the ids of what it added (stable and never reused: usable right away in sketch.dimension, sketch.constraint.*, sketch.delete…)" }; } mod browser; mod clipboard; mod component; mod component_clone; mod component_clone_assembly; mod construct; mod delete; mod edit; mod face; mod features; mod features_more; pub use features::auto_operation; mod config; mod file; mod inspect; pub(crate) mod joints; mod measure_sel; mod mesh; pub(crate) mod motion; mod parts; mod section; pub(crate) use measure_sel::measure_items; #[cfg(test)] mod kernel_heal_tests; mod parameters; mod plastic; mod sheet; mod sketch; pub(crate) use sketch::author_path_for_rebind; pub(crate) use sketch::created as sketch_created; mod sketch_3d; mod sketch_canvas; mod sketch_constraints; mod sketch_create; mod sketch_freeform; mod sketch_import; mod sketch_inspect; mod sketch_modify; mod sketch_project; mod surface; mod surface_analysis; use serde::Serialize; use serde_json::Value; use crate::{Result, Session}; pub type Run = fn(&mut Session, &Value) -> Result; pub type Enabled = fn(&Session) -> std::result::Result<(), String>; pub struct CommandSpec { pub id: &'static str, pub label: &'static str, /// Toolbar tab (`SOLID`, `SKETCH`…); empty = not on the toolbar. pub tab: &'static str, /// Toolbar panel (`CREATE`, `MODIFY`…). pub panel: &'static str, /// Icon name for the UI (drawn in code). pub icon: &'static str, pub shortcut: Option<&'static str>, /// One-line parameter documentation. pub params: &'static str, pub enabled: Enabled, pub run: Run, pub undoable: bool, } impl CommandSpec { pub const fn new(id: &'static str, label: &'static str, run: Run) -> Self { CommandSpec { id, label, tab: "", panel: "", icon: "", shortcut: None, params: "", enabled: always, run, undoable: true } } pub const fn at(mut self, tab: &'static str, panel: &'static str) -> Self { self.tab = tab; self.panel = panel; self } pub const fn icon(mut self, i: &'static str) -> Self { self.icon = i; self } pub const fn key(mut self, k: &'static str) -> Self { self.shortcut = Some(k); self } pub const fn params(mut self, p: &'static str) -> Self { self.params = p; self } pub const fn enabled(mut self, e: Enabled) -> Self { self.enabled = e; self } pub const fn noundo(mut self) -> Self { self.undoable = false; self } pub fn info(&self, s: &Session) -> CommandInfo { let e = (self.enabled)(s); CommandInfo { id: self.id, label: self.label, tab: self.tab, panel: self.panel, icon: self.icon, shortcut: self.shortcut, params: self.params, enabled: e.is_ok(), disabled_reason: e.err(), } } } #[derive(Clone, Debug, Serialize)] pub struct CommandInfo { pub id: &'static str, pub label: &'static str, pub tab: &'static str, pub panel: &'static str, pub icon: &'static str, pub shortcut: Option<&'static str>, pub params: &'static str, pub enabled: bool, pub disabled_reason: Option, } pub fn always(_: &Session) -> std::result::Result<(), String> { Ok(()) } pub fn in_sketch(s: &Session) -> std::result::Result<(), String> { if s.active_sketch.is_some() { Ok(()) } else { Err("no sketch is being edited (Create Sketch or Edit Sketch first)".into()) } } /// Every command, in toolbar order. pub fn command_specs() -> Vec<&'static CommandSpec> { let mut v: Vec<&'static CommandSpec> = Vec::new(); v.extend(sketch::COMMANDS.iter()); v.extend(sketch_create::COMMANDS.iter()); v.extend(sketch_constraints::COMMANDS.iter()); v.extend(sketch_import::COMMANDS.iter()); v.extend(sketch_inspect::COMMANDS.iter()); v.extend(sketch_canvas::COMMANDS.iter()); v.extend(sketch_3d::COMMANDS.iter()); v.extend(surface_analysis::COMMANDS.iter()); v.extend(surface::COMMANDS.iter()); v.extend(sketch_modify::COMMANDS.iter()); v.extend(sketch_project::COMMANDS.iter()); v.extend(features::COMMANDS.iter()); v.extend(features_more::COMMANDS.iter()); v.extend(mesh::COMMANDS.iter()); v.extend(construct::COMMANDS.iter()); v.extend(sheet::COMMANDS.iter()); v.extend(plastic::COMMANDS.iter()); v.extend(component::COMMANDS.iter()); v.extend(joints::COMMANDS.iter()); v.extend(motion::COMMANDS.iter()); v.extend(config::COMMANDS.iter()); v.extend(parts::COMMANDS.iter()); v.extend(browser::COMMANDS.iter()); v.extend(delete::COMMANDS.iter()); v.extend(edit::COMMANDS.iter()); v.extend(clipboard::COMMANDS.iter()); v.extend(parameters::COMMANDS.iter()); v.extend(file::COMMANDS.iter()); v.extend(inspect::COMMANDS.iter()); v.extend(section::COMMANDS.iter()); v } pub fn find_command(id: &str) -> Option<&'static CommandSpec> { let all = command_specs(); // Ids from before the rename still find their command. let id = crate::legacy_ids::current_id(id); all.iter().find(|c| c.id == id).or_else(|| all.iter().find(|c| c.id.eq_ignore_ascii_case(id))).copied() }