1use geop_core_editor::{
8 edit_form::{EditForm, FormState},
9 editable::{Context, OperationGroup, OperationInfo, OperationTier},
10 editing::Choice,
11 part_operation,
12 part_operation::PartOperation,
13};
14use geop_core_math::{
15 geop_error::{GeopError, GeopResult, WithContext},
16 primitives::{FrameAxis, Motion},
17 scalars::Scalar,
18 vector::Vector3,
19 with_context,
20};
21use geop_core_program::{
22 numeric::{ParameterValue, ParameterValues, parameter_values},
23 part::StepContext,
24 placed::{PlacedArgs, build_derived},
25 program::ProgramStep,
26 reader::Reader,
27 values::Settings,
28 workspace::is_program,
29};
30use geop_core_topology::Body;
31use geop_ops_model::{
32 ModelOperation, NamedModel, Namer, build_model, parameters::file_choices, read_model,
33};
34use serde::{Deserialize, Serialize};
35
36pub(crate) fn plane_name(normal: FrameAxis) -> &'static str {
39 match normal {
40 FrameAxis::X => "yz",
41 FrameAxis::Y => "zx",
42 FrameAxis::Z => "xy",
43 }
44}
45
46fn bodies(model: &NamedModel) -> Vec<Body> {
49 let topology = model.topology();
50 let solids = topology.solids.keys().map(|&solid| Body::Solid(solid));
51 let sheets = (topology.shells.iter())
52 .filter(|(_, shell)| shell.solid.is_none())
53 .map(|(&shell, _)| Body::Sheet(shell));
54 solids.chain(sheets).collect()
55}
56
57#[derive(Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize)]
64pub struct MirrorPart;
65
66#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
67pub struct MirrorPartArgs {
68 pub normal: FrameAxis,
71}
72
73pub const MIRRORED: &str = "mirrored";
75
76part_operation!(MirrorPart(MirrorPartArgs));
77
78impl PartOperation for MirrorPart {
79 const KIND: &'static str = "mirror_part";
80 const INFO: OperationInfo = OperationInfo::new(
81 "Mirror part",
82 "Mirror the whole part in place in a plane of its origin: the other hand of it.",
83 OperationGroup::Bodies,
84 OperationTier::Menu,
85 )
86 .icon(crate::icons::MIRROR_PART);
87 type Args = MirrorPartArgs;
88 type Session = ();
89
90 fn new_args(_: &Context) -> MirrorPartArgs {
92 MirrorPartArgs {
93 normal: FrameAxis::X,
94 }
95 }
96
97 fn run(ctx: &mut StepContext, args: &MirrorPartArgs) -> GeopResult<()> {
99 build_model::<Self>(ctx, args)?;
100 ctx.produce(¶meter_values(), &args.normal, |reader, normal| {
101 let mut values: ParameterValues = reader.previous()?;
102 values.0.push((
103 MIRRORED.to_string(),
104 ParameterValue::Text(plane_name(*normal).to_string()),
105 ));
106 Ok(values)
107 })
108 }
109
110 fn form<'a>(
112 _: &'a Context<'a>,
113 args: &MirrorPartArgs,
114 _: &FormState<()>,
115 ) -> EditForm<'a, MirrorPartArgs> {
116 let mut f = EditForm::<MirrorPartArgs>::default();
117 f.select(
118 "normal",
119 "Mirrored in",
120 plane_name(args.normal),
121 plane_choices(),
122 false,
123 |edit, plane| {
124 if let Some(normal) = FrameAxis::ALL.into_iter().find(|n| plane_name(*n) == plane) {
125 edit.args.normal = normal;
126 }
127 },
128 );
129 f
130 }
131}
132
133pub(crate) fn plane_choices() -> Vec<Choice> {
135 (FrameAxis::ALL.into_iter())
136 .map(|normal| {
137 let name = plane_name(normal);
138 Choice::new(name, format!("its {name} plane"))
139 })
140 .collect()
141}
142
143impl ModelOperation for MirrorPart {
144 fn build(
145 model: &mut NamedModel,
146 _: &Reader,
147 step: &str,
148 args: &MirrorPartArgs,
149 ) -> GeopResult<()> {
150 let ctx = with_context!("mirror_part({step}, {args:?})");
151 let mirror = mirror_in(args.normal).with_context(ctx)?;
152 for body in bodies(model) {
153 model.transform_body(body, &mirror).with_context(ctx)?;
154 }
155 Ok(())
156 }
157}
158
159fn mirror_in(normal: FrameAxis) -> GeopResult<Motion> {
161 let mut n = Vector3::zero();
162 n[normal as usize] = Scalar::ONE;
163 Motion::mirror(&Vector3::zero(), &n)
164}
165
166pub(crate) fn mirror_step(normal: FrameAxis) -> GeopResult<ProgramStep> {
170 ProgramStep::new::<MirrorPart>("mirrored", &MirrorPartArgs { normal })
171}
172
173#[derive(Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize)]
180pub struct DerivedPart;
181
182#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
183pub struct DerivedPartArgs {
184 #[serde(default)]
187 pub file: String,
188 #[serde(default, skip_serializing_if = "Settings::is_empty")]
190 pub parameters: Settings,
191}
192
193part_operation!(DerivedPart(DerivedPartArgs));
194
195impl PartOperation for DerivedPart {
196 const KIND: &'static str = "derived_part";
197 const INFO: OperationInfo = OperationInfo::new(
198 "Derived part",
199 "Bring the bodies another program file builds into the part, following it as it changes.",
200 OperationGroup::Bodies,
201 OperationTier::Menu,
202 )
203 .icon(crate::icons::DERIVED_PART);
204 type Args = DerivedPartArgs;
205 type Session = ();
206
207 fn new_args(_: &Context) -> DerivedPartArgs {
208 DerivedPartArgs::default()
209 }
210
211 fn run(ctx: &mut StepContext, args: &DerivedPartArgs) -> GeopResult<()> {
212 if args.file.is_empty() {
213 return Err(GeopError::new("choose a file to derive the part from"));
214 }
215 build_model::<Self>(ctx, args)
216 }
217
218 fn form<'a>(
220 context: &'a Context<'a>,
221 args: &DerivedPartArgs,
222 _: &FormState<()>,
223 ) -> EditForm<'a, DerivedPartArgs> {
224 let mut f = EditForm::<DerivedPartArgs>::default();
225 let files = file_choices(context.runner, is_program);
226 f.file(
227 "file",
228 "file",
229 args.file.clone(),
230 files,
231 &[],
232 |edit, file| {
233 edit.args.file = file.to_string();
234 },
235 );
236 f
237 }
238}
239
240impl ModelOperation for DerivedPart {
241 fn build(
242 model: &mut NamedModel,
243 reader: &Reader,
244 step: &str,
245 args: &DerivedPartArgs,
246 ) -> GeopResult<()> {
247 let ctx = with_context!("derived_part({step}, file={:?})", args.file);
248 let placed = PlacedArgs {
249 file: args.file.clone(),
250 inputs: args.parameters.clone(),
251 then: Vec::new(),
252 };
253 let derived = build_derived(reader, &placed).with_context(ctx)?;
254 if let Some((failed, e)) = derived.part.failures().first() {
255 let e = e.clone();
256 return Err(e.with_context(format!("step `{failed}` of {:?}", args.file)))
257 .with_context(ctx);
258 }
259 let source = read_model(&derived.part.current()).with_context(ctx)?;
260 let namer = Namer::new("derived", step).with_context(ctx)?;
261 for body in bodies(&source) {
262 let (spec, names) = source.body_record(body).with_context(ctx)?;
263 let names = names.renamed(|name| namer.name(&[name]));
264 model.build_body(spec, names).with_context(ctx)?;
265 }
266 Ok(())
267 }
268}