Skip to main content

geop_ops_assembly/
add_part.rs

1//! [`AddPart`]: place the part another program file builds in the part.
2
3use geop_core_editor::{
4    edit_form::{Edit, EditForm},
5    editable::{Context, OperationGroup, OperationInfo, OperationTier},
6    editing::CanvasEvent,
7    part_operation,
8    part_operation::PartOperation,
9    selectable::Selectable,
10};
11use geop_core_math::primitives::FrameAxis;
12use geop_core_program::{
13    element::ElementReference,
14    part::StepContext,
15    placed::PlacedArgs,
16    values::{Settings, pose_state},
17};
18use std::collections::BTreeMap;
19
20use geop_core_editor::view::{Listed, Listing, listing};
21use geop_core_math::{
22    geop_error::{GeopError, GeopResult, WithContext},
23    with_context,
24};
25use geop_ops_model::Namer;
26use geop_ops_model::datums::{ORIGIN, datums};
27use geop_ops_model::parameters::{Placement, instances};
28use geop_ops_model::selection::Selected;
29use serde::{Deserialize, Serialize};
30
31use crate::{
32    Mate,
33    add_part_derived::mirror_step,
34    editor::{self, PartEditing, PartSession},
35    mechanism::{define, resolve},
36    placed::define_placed,
37};
38use geop_ops_model::mate_entity::MateEntity;
39
40/// Places the part the program in another file builds, named by the
41/// step's id: its elements are then the part's, behind that name (see
42/// [`MateEntity`]).
43///
44/// The part goes where the program's state `<id>.pose` puts it, and the
45/// step adds its mates. It solves nothing: where every placed part is, so
46/// that every mate of the program holds, is the program's state, which the
47/// editor solves for (see [`crate::mechanism`]). So every step sees each
48/// placed part where it ends up, however late the mates that put it there
49/// are.
50///
51/// Mates that cannot hold do not fail the step — the parts are left as near
52/// to holding them as they get, as a sketch whose constraints conflict is —
53/// and the step's editor says which.
54///
55/// The parts placed in the part placed are this program's to move too:
56/// their poses are its state, behind the step's id, which the part placed
57/// is run with — starting where its own program has them — and their mates
58/// come up with its constraints, to hold them here as they held them there
59/// (see [`geop_core_program::instance`]).
60///
61/// A [`Mate::fixed_here`] among its mates, given no entity, holds the part this
62/// step places.
63#[derive(Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize)]
64pub struct AddPart;
65
66#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
67pub struct AddPartArgs {
68    /// The program file whose part is placed, relative to this program's
69    /// own file; empty until one is chosen.
70    #[serde(default)]
71    pub file: String,
72    /// The mates this step adds, by an id of their own: `m1`, `m2`, ...
73    /// Each is named `add_part(step,id)` in the part.
74    #[serde(default)]
75    pub mates: BTreeMap<String, Mate>,
76    /// The values the part's own parameters (see
77    /// [`geop_ops_model::parameters::Parameters`]) are given here instead of its
78    /// own, by name: a number, a table's row, the colour — the part as this
79    /// program places it.
80    #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
81    pub parameters: Settings,
82    /// The part mirrored, in the plane of its own origin normal to this
83    /// axis — `x` for its `yz` plane — or not: a mirrored component, the
84    /// other hand of the part, placed as a part derived from it (its
85    /// program, then [`crate::MirrorPart`]).
86    #[serde(default, skip_serializing_if = "Option::is_none")]
87    pub mirror: Option<FrameAxis>,
88}
89
90impl AddPartArgs {
91    /// The step that also holds the part it places fixed.
92    pub fn fixed(mut self) -> Self {
93        self.set_fixed(true);
94        self
95    }
96
97    /// Holds the part this step places fixed, or — not `fixed` — frees it
98    /// of every fixed mate.
99    pub fn set_fixed(&mut self, fixed: bool) {
100        if fixed {
101            self.mates.insert("fixed".into(), Mate::fixed_here());
102        } else {
103            self.mates.retain(|_, mate| !mate.is_fixed());
104        }
105    }
106}
107
108/// What a mate is listed as in the part's structure (see [`Listed::kind`]).
109const MATE: &str = "mate";
110
111part_operation!(AddPart(AddPartArgs));
112
113impl PartOperation for AddPart {
114    const KIND: &'static str = "add_part";
115    const INFO: OperationInfo = OperationInfo::new(
116        "Part",
117        "Place the part another program file builds, and mate it to what is already there.",
118        OperationGroup::Assembly,
119        OperationTier::Big,
120    )
121    .picks_built()
122    .shortcut("I")
123    .icon(crate::icons::ADD_PART);
124    type Args = AddPartArgs;
125    type Session = PartSession;
126
127    /// No file yet; the first part placed is fixed, the ones after it are
128    /// not — something has to stay put for the others to mate to — until
129    /// a joint or a constraint is chosen for it, which then holds it (see
130    /// [`crate::editor`]).
131    fn new_args(context: &Context) -> AddPartArgs {
132        let args = AddPartArgs::default();
133        match instances(&context.before).is_ok_and(|placed| placed.is_empty()) {
134            true => args.fixed(),
135            false => args,
136        }
137    }
138
139    /// Pulls released at the event before are let go of.
140    fn begin_event(session: &mut PartSession) {
141        session.let_go();
142    }
143
144    /// Places the part, then defines each mate: resolved, and as
145    /// constraints on the state; and lets a solve move the part, as far as
146    /// its mates let it. A mate that cannot be resolved fails the step,
147    /// after the others are defined. The placed part's own mates come up
148    /// with its constraints (see [`geop_lib_constraints::Constraints::of`]).
149    fn run(ctx: &mut StepContext, args: &AddPartArgs) -> GeopResult<()> {
150        let step = ctx.step();
151        let context = with_context!("add_part({step}, file={:?})", args.file);
152        if args.file.is_empty() {
153            return Err(GeopError::new("choose a file to place")).with_context(context);
154        }
155        let placement = Placement::State(pose_state(step));
156        let placed = PlacedArgs {
157            file: args.file.clone(),
158            inputs: args.parameters.clone(),
159            then: (args.mirror.map(mirror_step).transpose())
160                .with_context(context)?
161                .into_iter()
162                .collect(),
163        };
164        define_placed(ctx, step, &placed, placement).with_context(context)?;
165        let namer = Namer::new("add_part", step)?;
166        let own_frame = MateEntity::of(
167            step,
168            Selected::Datum(ElementReference::new(datums(), ORIGIN)),
169        );
170        let mut failed = Ok(());
171        // What the structure lists: every mate the step defines; what the
172        // state is constrained by: each, by its name and what it is called.
173        let mut listed = Vec::new();
174        let mut defined = Vec::new();
175        for (id, mate) in &args.mates {
176            let mut mate = mate.clone();
177            if mate.is_fixed() && mate.entities.is_empty() {
178                mate.entities.push(own_frame.clone());
179            }
180            if !mate.is_complete() {
181                continue;
182            }
183            let name = namer.name(&[id]);
184            listed.push(Listed {
185                kind: MATE.into(),
186                name: name.clone(),
187                elements: mate.entities.iter().map(Selectable::picked).collect(),
188            });
189            let resolved = ctx.produce(&crate::mate(&name), &(name.clone(), mate), resolve);
190            if resolved.is_ok() {
191                defined.push((name, format!("{step} {id}")));
192            }
193            failed = failed.and(resolved);
194        }
195        let constrained = define(ctx, Some(&pose_state(step)), &defined);
196        let listing = ctx.produce(&listing(step), &Listing(listed), |_, l| Ok(l.clone()));
197        failed.and(constrained).and(listing).with_context(context)
198    }
199
200    /// The file, where it goes, and its mates: see [`crate::editor`].
201    fn form<'a>(
202        context: &'a Context<'a>,
203        args: &AddPartArgs,
204        editing: &PartEditing,
205    ) -> EditForm<'a, AddPartArgs, PartSession> {
206        editor::form(context, args, &editing.session)
207    }
208
209    /// The part dragged; Delete removes the mate selected, Escape stops
210    /// editing it: see [`crate::editor`].
211    fn canvas(context: &Context, edit: Edit<'_, AddPartArgs, PartSession>, event: &CanvasEvent) {
212        editor::canvas(context, edit, event)
213    }
214}
215
216#[cfg(test)]
217mod configurations;
218#[cfg(test)]
219mod in_context;
220#[cfg(test)]
221mod mirrored;
222#[cfg(test)]
223mod nested;
224#[cfg(test)]
225mod tests;