Skip to main content

geop_ops_assembly/
panel.rs

1//! The joints panel ([`JointsPanel`]): the values of an assembly a user
2//! sets — a joint's coordinates — how free its placed parts are, and which
3//! of its constraints conflict.
4
5use std::{
6    cell::RefCell,
7    collections::{BTreeMap, BTreeSet},
8    rc::Rc,
9};
10
11use geop_core_editor::{
12    editor::{Changed, EditorContext},
13    registry::Panel,
14};
15use geop_core_math::{
16    geop_error::{GeopError, GeopResult},
17    scalars::Scalar,
18};
19use geop_core_program::{program::Program, reader::Reader};
20use serde::{Deserialize, Serialize};
21
22use geop_ops_model::placed::placed_parts;
23
24use crate::{
25    settle::Settled,
26    solving::{self, Freedom, SettingsState, settings},
27};
28
29/// The joints panel: see the module docs.
30pub struct JointsPanel {
31    pub(crate) settled: Rc<Settled>,
32    /// How free the placed parts are, as the last edit left them (see
33    /// [`solving::freedom`]): asked over the whole part, so once an edit,
34    /// never for every move of the pointer.
35    freedom: RefCell<Option<Freedom>>,
36}
37
38impl JointsPanel {
39    pub(crate) fn new(settled: Rc<Settled>) -> Self {
40        Self {
41            settled,
42            freedom: RefCell::new(None),
43        }
44    }
45}
46
47/// What the joints panel shows.
48#[derive(Serialize)]
49pub struct JointsState {
50    /// The values of the part shown a user sets, grouped as its steps
51    /// declare them — a joint's coordinates — with where they are: what
52    /// [`JointsCommand::Set`] sets (see [`solving::settings`]).
53    pub joints: Vec<SettingsState>,
54    /// How free each of its placed parts is — if it has placed parts — as
55    /// the last edit left them (see [`solving::freedom`]).
56    pub freedom: Option<Freedom>,
57    /// A smallest set of its constraints that cannot all hold together, by
58    /// name: none where they all hold.
59    pub conflicting: Vec<String>,
60}
61
62/// What the joints panel is sent.
63#[derive(Serialize, Deserialize)]
64#[serde(tag = "command", rename_all = "snake_case")]
65pub enum JointsCommand {
66    /// Set the value `parameter` a step offers a user to set — a joint's
67    /// coordinate: an angle in degrees, or a distance — to `value`: the
68    /// parts move to it, everything else that is free giving way.
69    Set { parameter: String, value: f64 },
70}
71
72impl Panel for JointsPanel {
73    const KEY: &'static str = "joints";
74    type State = JointsState;
75    type Command = JointsCommand;
76
77    fn state(&self, _: &Program, part: &Reader) -> JointsState {
78        let placed = placed_parts(part).is_ok_and(|placed| !placed.is_empty());
79        JointsState {
80            joints: settings(part),
81            freedom: self.freedom.borrow().clone().filter(|_| placed),
82            conflicting: self.settled.conflicting.borrow().clone(),
83        }
84    }
85
86    /// How free each placed part is: `fixed`, or how many ways it moves.
87    fn details(&self, state: &JointsState) -> BTreeMap<String, String> {
88        let parts = state.freedom.iter().flat_map(|f| &f.parts);
89        (parts.map(|(part, &dof)| {
90            let said = if dof == 0 {
91                "fixed".into()
92            } else {
93                format!("{dof} DOF")
94            };
95            (part.clone(), said)
96        }))
97        .collect()
98    }
99
100    fn command(&self, ctx: &mut EditorContext, command: JointsCommand) -> GeopResult<Changed> {
101        let JointsCommand::Set { parameter, value } = command;
102        ctx.idle()?;
103        ctx.run_all();
104        let settable = (settings(&ctx.part()).into_iter())
105            .any(|s| s.values.iter().any(|v| v.state == parameter));
106        if !settable {
107            return Err(GeopError::new(format!(
108                "{parameter:?} is no value of this program a user sets"
109            )));
110        }
111        let set = BTreeMap::from([(parameter, Scalar::from_f64(value))]);
112        let mut rerun = ctx.rerun();
113        let state = rerun.program.state.clone();
114        let (moved, _) = solving::set(&mut rerun, &state, &set)?;
115        ctx.move_state(moved);
116        Ok(Changed::Program)
117    }
118
119    /// How free the placed parts of what is shown are, kept for the panel
120    /// to show.
121    fn after_edit(&self, ctx: &mut EditorContext) {
122        let placed = placed_parts(&ctx.part()).is_ok_and(|placed| !placed.is_empty());
123        let mut rerun = ctx.rerun();
124        let state = rerun.program.state.clone();
125        *self.freedom.borrow_mut() = placed
126            .then(|| solving::freedom(&mut rerun, &state, &BTreeSet::new()).ok())
127            .flatten();
128    }
129}