Skip to main content

geop_ops_assembly/
part_pattern.rs

1//! [`PartPattern`]: copies of a placed part wherever a list of frames puts
2//! them.
3
4use 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/// Places copies of the part placed by the step `part` — the same part,
32/// built once — wherever `frames` puts them (see [`Frames`]): in a row
33/// or a grid, round an axis, along a curve, at points — the part at the
34/// first — from a table of offsets, filling a face, or at the holes of a
35/// feature, of the part or of a part placed in it. Each copy is a placed
36/// part of its own, named `part_pattern(P,i)` for the step `P` and the
37/// copy labelled `i` — `1`, `2`, ..., or `i.j` in a grid or a fill —
38/// its entities behind that name like any placed part's:
39/// `part_pattern(bolts,2)/extrude(head,end)`.
40///
41/// A copy goes where the pattern puts it, relative to where the part is —
42/// no mate moves it, and when the part moves, its copies move with it.
43/// What the copies are laid out along, round or at is picked in the part
44/// or in a part placed in it, where it is as the copies are placed.
45#[derive(Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize)]
46pub struct PartPattern;
47
48#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
49pub struct PartPatternArgs {
50    /// The placed part copied, by the id of the step that placed it.
51    #[serde(default)]
52    pub part: String,
53    /// Where the copies go.
54    pub frames: Frames,
55}
56
57/// The placed parts the steps before have placed, by name, in order.
58fn 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/// The copies a part pattern places: the file and inputs of the part it
65/// copies — a copy places what the part was placed from — and each copy's
66/// label and the motion from the part to it.
67#[derive(Clone, Debug, PartialEq)]
68struct Copies {
69    placed: PlacedArgs,
70    motions: Vec<(String, Pose)>,
71}
72
73/// Each part pattern's own, made before its copies are placed.
74impl Target for Copies {}
75
76/// Read whole.
77impl 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
89/// The copies the part pattern `step` places.
90fn copies(step: &str) -> TargetReference<Copies> {
91    TargetReference::new(format!("part_pattern:{step}"))
92}
93
94/// The copies `args` asks for, of the part as the steps before left it:
95/// the part's file and inputs, and the motions to where the frames put
96/// them, as many as they come to — a face filled through where the part
97/// is.
98fn 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    /// The newest placed part, six round an axis still to pick.
141    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    /// Places the copies, each where the pattern puts it after the part:
154    /// the axis resolved in the part as the steps before left it.
155    ///
156    /// Which copies there are, and where, is a node of its own ([`plan`]),
157    /// made from what it reads of the part: the step declares a copy for
158    /// each motion it holds and computes nothing itself.
159    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    /// The part to copy, and where the copies go.
181    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}