1use 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#[derive(Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize)]
64pub struct AddPart;
65
66#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
67pub struct AddPartArgs {
68 #[serde(default)]
71 pub file: String,
72 #[serde(default)]
75 pub mates: BTreeMap<String, Mate>,
76 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
81 pub parameters: Settings,
82 #[serde(default, skip_serializing_if = "Option::is_none")]
87 pub mirror: Option<FrameAxis>,
88}
89
90impl AddPartArgs {
91 pub fn fixed(mut self) -> Self {
93 self.set_fixed(true);
94 self
95 }
96
97 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
108const 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 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 fn begin_event(session: &mut PartSession) {
141 session.let_go();
142 }
143
144 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 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 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 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;