geop_ops_pattern/
linear.rs1use 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#[derive(Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize)]
42pub struct LinearPattern;
43
44#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
45pub struct LinearPatternArgs {
46 #[serde(flatten)]
49 pub seeds: Seeds,
50 pub first: Direction,
51 #[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 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 fn new_args(_: &Context) -> LinearPatternArgs {
98 LinearPatternArgs {
99 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 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}