1use geop_core_editor::{
5 edit_form::{Edit, EditForm, FormState},
6 editable::{Context, OperationGroup, OperationInfo, OperationTier},
7 editing::{Choice, Tone},
8 part_operation,
9 part_operation::PartOperation,
10};
11use geop_core_math::{
12 geop_error::{GeopError, GeopResult, WithContext},
13 primitives::Pose,
14 with_context,
15};
16use geop_core_program::{
17 element::{ElementReference, HasElements, Kind, whole},
18 numeric::NumericInput,
19 part::StepContext,
20 placed::PlacedArgs,
21 reader::Reader,
22 target::{Target, TargetReference},
23};
24use geop_ops_model::Namer;
25use geop_ops_model::parameters::{Placement, instance, instances, located};
26use geop_ops_model::spacing::{Frames, Spacing};
27use serde::{Deserialize, Serialize};
28
29use crate::placed::define_placed;
30
31#[derive(Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize)]
46pub struct PartPattern;
47
48#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
49pub struct PartPatternArgs {
50 #[serde(default)]
52 pub part: String,
53 pub frames: Frames,
55}
56
57fn placed_names(before: &Reader) -> Vec<String> {
59 (instances(before).unwrap_or_default().into_iter())
60 .map(|(name, _)| name.to_string())
61 .collect()
62}
63
64#[derive(Clone, Debug, PartialEq)]
68struct Copies {
69 placed: PlacedArgs,
70 motions: Vec<(String, Pose)>,
71}
72
73impl Target for Copies {}
75
76impl Kind for Copies {
78 const NAME: &'static str = "copies";
79}
80
81impl HasElements<Copies> for Copies {
82 type Element<'a> = &'a Copies;
83
84 fn element(&self, name: &str) -> GeopResult<&Copies> {
85 whole(self, name)
86 }
87}
88
89fn copies(step: &str) -> TargetReference<Copies> {
91 TargetReference::new(format!("part_pattern:{step}"))
92}
93
94fn plan(reader: &Reader, args: &PartPatternArgs) -> GeopResult<Copies> {
99 if args.part.is_empty() {
100 return Err(GeopError::new("choose the placed part to pattern"));
101 }
102 let Ok(seed) = instance(reader, &args.part) else {
103 return Err(GeopError::new(format!(
104 "{:?} is no part placed so far: pattern one an earlier step placed",
105 args.part
106 )));
107 };
108 let anchor = || Ok(located(reader, &args.part)?.position());
109 let motions = args.frames.motions(reader, &anchor)?;
110 if motions.is_empty() {
111 return Err(GeopError::new(
112 "the pattern puts no copy anywhere but where the part is: it needs 2 or more instances",
113 ));
114 }
115 Ok(Copies {
116 placed: PlacedArgs {
117 file: seed.file.clone(),
118 inputs: seed.inputs.clone(),
119 then: seed.then.clone(),
120 },
121 motions,
122 })
123}
124
125part_operation!(PartPattern(PartPatternArgs));
126
127impl PartOperation for PartPattern {
128 const KIND: &'static str = "part_pattern";
129 const INFO: OperationInfo = OperationInfo::new(
130 "Part pattern",
131 "Place copies of a placed part in a row, round an axis, along a curve, at points or holes, from a table or filling a face.",
132 OperationGroup::Assembly,
133 OperationTier::Menu,
134 )
135 .experimental()
136 .icon(crate::icons::PART_PATTERN);
137 type Args = PartPatternArgs;
138 type Session = ();
139
140 fn new_args(context: &Context) -> PartPatternArgs {
142 PartPatternArgs {
143 part: placed_names(&context.before).pop().unwrap_or_default(),
144 frames: Frames::Circular {
145 axis: None,
146 reversed: false,
147 count: NumericInput::Plain(6.0),
148 angle: Spacing::extent(360.0),
149 },
150 }
151 }
152
153 fn run(ctx: &mut StepContext, args: &PartPatternArgs) -> GeopResult<()> {
160 let step = ctx.step();
161 let context = with_context!("part_pattern({step}, {args:?})");
162 ctx.produce(&copies(step), args, plan)
163 .with_context(context)?;
164 let Copies { placed, motions } = (ctx.reader())
165 .retrieve_element(&ElementReference::whole(copies(step)))
166 .with_context(context)?
167 .clone();
168 let namer = Namer::new("part_pattern", step)?;
169 for (label, motion) in motions {
170 let name = namer.name(&[&label]);
171 let placement = Placement::After {
172 of: args.part.clone(),
173 motion,
174 };
175 define_placed(ctx, &name, &placed, placement).with_context(context)?;
176 }
177 Ok(())
178 }
179
180 fn form<'a>(
182 context: &'a Context<'a>,
183 args: &PartPatternArgs,
184 _: &FormState<()>,
185 ) -> EditForm<'a, PartPatternArgs> {
186 let mut f = EditForm::<PartPatternArgs>::default();
187 let before = &context.before;
188 let options = std::iter::once(Choice::new("", "Choose a placed part…"))
189 .chain(
190 placed_names(before)
191 .into_iter()
192 .map(|name| Choice::new(name.clone(), name)),
193 )
194 .collect();
195 f.select(
196 "part",
197 "part",
198 args.part.clone(),
199 options,
200 true,
201 |Edit { args, .. }, part| args.part = part.to_string(),
202 );
203 let at = located(before, &args.part).ok().map(|pose| pose.position());
204 args.frames
205 .fields(&mut f, before, at, |args: &mut PartPatternArgs, frames| {
206 args.frames = frames
207 });
208 f.form.dialog.text(
209 "hint",
210 "Each copy is a placed part of its own, named part_pattern(step,i); it moves with the part.",
211 Tone::Hint,
212 );
213 f
214 }
215}