Skip to main content

geop_ops_pattern/
linear.rs

1//! [`LinearPattern`]: copies of bodies in a row, or in a grid of two rows.
2
3use geop_core_editor::{
4    edit_form::{EditForm, FormState},
5    editable::{Context, OperationGroup, OperationInfo, OperationTier},
6    part_operation,
7    part_operation::PartOperation,
8};
9use geop_core_math::{
10    geop_error::GeopResult,
11    primitives::{FrameAxis, Motion},
12};
13use geop_core_program::{part::StepContext, reader::Reader};
14use geop_ops_model::spacing::{Direction, Frames};
15use geop_ops_model::{ModelOperation, NamedModel};
16use serde::{Deserialize, Serialize};
17
18use crate::common::{Naming, Pattern, Seeds, build_pattern, placed_by, run_pattern};
19
20/// Copies the bodies `bodies` — solids, and sheets by one of their faces —
21/// or the features `features` in a row along a direction, or in a grid
22/// along two: `count` instances along each, the bodies or features
23/// themselves the first, each `Spacing::Step` from the last or spread
24/// evenly over `Spacing::Extent`.
25///
26/// A feature — a cut, a boss, a hole — is done again at each instance:
27/// its tools copied and combined with the solid it lies on as it combined
28/// them (see [`crate::features`]), its cosmetic threads copied with it.
29///
30/// The copies are kept as new bodies, or combined as [`geop_ops_model::Combine`] says:
31/// joined to, cut from or intersected with the target, the bodies
32/// themselves too unless they are the target — so a tool built as a new
33/// body, patterned and cut from a plate, drills a row of holes. Combined,
34/// the result is named `linear_pattern(P)` for the operation `P`.
35///
36/// The copy of each entity named `X` in the instance `i` along the first
37/// direction is named `linear_pattern(P,i,X)` — `i` counting from `1`, the
38/// bodies themselves being `0` — and in the instance `i`, `j` of a grid
39/// `linear_pattern(P,i.j,X)`: "copy 3's top face" is
40/// `linear_pattern(P,3,extrude(E,end))` however the bodies change.
41#[derive(Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize)]
42pub struct LinearPattern;
43
44#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
45pub struct LinearPatternArgs {
46    /// The bodies, or the features, to copy, and how to combine copies of
47    /// bodies.
48    #[serde(flatten)]
49    pub seeds: Seeds,
50    pub first: Direction,
51    /// A second direction, making a grid.
52    #[serde(default, skip_serializing_if = "Option::is_none")]
53    pub second: Option<Direction>,
54}
55
56impl Pattern for LinearPatternArgs {
57    const NAMING: Naming = Naming {
58        kind: "linear_pattern",
59        labelled: true,
60    };
61
62    fn seeds(&self) -> &Seeds {
63        &self.seeds
64    }
65
66    /// `0`, or in a grid `0.0`.
67    fn seed_label(&self) -> &'static str {
68        if self.second.is_some() { "0.0" } else { "0" }
69    }
70
71    fn placements(&self, reader: &Reader) -> GeopResult<Vec<(String, Motion)>> {
72        let frames = Frames::Linear {
73            first: self.first.clone(),
74            second: self.second.clone(),
75        };
76        placed_by(&frames, reader, &self.seeds)
77    }
78}
79
80part_operation!(LinearPattern(LinearPatternArgs));
81
82impl PartOperation for LinearPattern {
83    const KIND: &'static str = "linear_pattern";
84    const INFO: OperationInfo = OperationInfo::new(
85        "Linear pattern",
86        "Copy bodies in a row along a direction, or in a grid along two.",
87        OperationGroup::Bodies,
88        OperationTier::Big,
89    )
90    .shortcut("P")
91    .icon(geop_core_editor::view::icons::LINEAR_PATTERN);
92    type Args = LinearPatternArgs;
93    type Session = ();
94
95    /// Three times a unit apart along the origin's `x` axis, kept as new
96    /// bodies.
97    fn new_args(_: &Context) -> LinearPatternArgs {
98        LinearPatternArgs {
99            // Nothing picked: what is patterned is the user's choice, a body
100            // or a feature, never guessed.
101            seeds: Seeds::default(),
102            first: Direction::along(FrameAxis::X),
103            second: None,
104        }
105    }
106
107    fn run(ctx: &mut StepContext, args: &LinearPatternArgs) -> GeopResult<()> {
108        let _step = ctx.step();
109        run_pattern::<Self>(ctx, args)
110    }
111
112    /// The bodies or the features, picked; each direction — the second
113    /// turned on or off — with its count and spacing, both dragged by
114    /// handles; and how to combine copies of bodies.
115    fn form<'a>(
116        context: &'a Context<'a>,
117        args: &LinearPatternArgs,
118        _: &FormState<()>,
119    ) -> EditForm<'a, LinearPatternArgs> {
120        let before = &context.before;
121        let mut f = EditForm::default();
122        let at = args.seeds.center(before);
123        let set_seeds = |args: &mut LinearPatternArgs, seeds| args.seeds = seeds;
124        (args.seeds).fields(&mut f, before, set_seeds, |f| {
125            args.first
126                .fields(f, before, at, "", |args, first| args.first = first);
127            f.checkbox(
128                "grid",
129                "second direction",
130                args.second.is_some(),
131                |edit, on| {
132                    edit.args.second = on.then(|| Direction::along(FrameAxis::Y));
133                },
134            );
135            if let Some(second) = &args.second {
136                second.fields(f, before, at, "2", |args, second| {
137                    args.second = Some(second)
138                });
139            }
140        });
141        f
142    }
143}
144
145impl ModelOperation for LinearPattern {
146    fn build(
147        model: &mut NamedModel,
148        reader: &Reader,
149        step: &str,
150        args: &LinearPatternArgs,
151    ) -> GeopResult<()> {
152        build_pattern(model, reader, step, args)
153    }
154}