1use geop_core_editor::{
4 edit_form::{EditForm, FormState},
5 editable::{Context, OperationGroup, OperationInfo, OperationTier},
6 editing::{Number, Unit},
7 part_operation,
8 part_operation::PartOperation,
9};
10use geop_core_math::{
11 geop_error::GeopResult,
12 primitives::{FrameAxis, Motion},
13};
14use geop_core_program::{numeric::NumericInput, part::StepContext, reader::Reader};
15use geop_ops_model::datums::origin_axis;
16use geop_ops_model::{ModelOperation, NamedModel};
17use serde::{Deserialize, Serialize};
18
19use crate::{
20 common::{Naming, Pattern, Seeds, build_pattern, placed_by, run_pattern},
21 references::TurnAxis,
22};
23use geop_ops_model::spacing::{Frames, Spacing, count_field};
24
25#[derive(Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize)]
40pub struct CircularPattern;
41
42#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
43pub struct CircularPatternArgs {
44 #[serde(flatten)]
47 pub seeds: Seeds,
48 pub axis: Option<TurnAxis>,
50 #[serde(default)]
52 pub reversed: bool,
53 pub count: NumericInput,
56 pub angle: Spacing,
58}
59
60impl Pattern for CircularPatternArgs {
61 const NAMING: Naming = Naming {
62 kind: "circular_pattern",
63 labelled: true,
64 };
65
66 fn seeds(&self) -> &Seeds {
67 &self.seeds
68 }
69
70 fn seed_label(&self) -> &'static str {
71 "0"
72 }
73
74 fn placements(&self, reader: &Reader) -> GeopResult<Vec<(String, Motion)>> {
75 let frames = Frames::Circular {
76 axis: self.axis.clone(),
77 reversed: self.reversed,
78 count: self.count.clone(),
79 angle: self.angle.clone(),
80 };
81 placed_by(&frames, reader, &self.seeds)
82 }
83}
84
85part_operation!(CircularPattern(CircularPatternArgs));
86
87impl PartOperation for CircularPattern {
88 const KIND: &'static str = "circular_pattern";
89 const INFO: OperationInfo = OperationInfo::new(
90 "Circular pattern",
91 "Copy bodies turned around an axis.",
92 OperationGroup::Bodies,
93 OperationTier::Small,
94 )
95 .shortcut("Shift+P")
96 .icon(geop_core_editor::view::icons::CIRCULAR_PATTERN);
97 type Args = CircularPatternArgs;
98 type Session = ();
99
100 fn new_args(_: &Context) -> CircularPatternArgs {
102 CircularPatternArgs {
103 seeds: Seeds::default(),
106 axis: Some(TurnAxis::Datum(origin_axis(FrameAxis::Z))),
107 reversed: false,
108 count: NumericInput::Plain(4.0),
109 angle: Spacing::extent(360.0),
110 }
111 }
112
113 fn run(ctx: &mut StepContext, args: &CircularPatternArgs) -> GeopResult<()> {
114 let _step = ctx.step();
115 run_pattern::<Self>(ctx, args)
116 }
117
118 fn form<'a>(
121 context: &'a Context<'a>,
122 args: &CircularPatternArgs,
123 _: &FormState<()>,
124 ) -> EditForm<'a, CircularPatternArgs> {
125 let before = &context.before;
126 let mut f = EditForm::default();
127 let set_seeds = |args: &mut CircularPatternArgs, seeds| args.seeds = seeds;
128 (args.seeds).fields(&mut f, before, set_seeds, |f| {
129 f.pick_one(
130 "axis",
131 "axis",
132 before,
133 args.axis.as_ref(),
134 |edit, picked| edit.args.axis = Some(picked),
135 )
136 .checkbox("reversed", "reverse direction", args.reversed, |edit, b| {
137 edit.args.reversed = b
138 });
139 count_field(f, "count", &args.count, before, None, |edit, count| {
140 edit.args.count = count
141 });
142 args.angle.fields(
143 f,
144 "angle",
145 ["angle", "total angle"],
146 |label, value| {
147 Number::numeric(value, label, before, Unit::Angle).range(-360.0, 360.0)
148 },
149 |args, angle| args.angle = angle,
150 );
151 });
152 f
153 }
154}
155
156impl ModelOperation for CircularPattern {
157 fn build(
158 model: &mut NamedModel,
159 reader: &Reader,
160 step: &str,
161 args: &CircularPatternArgs,
162 ) -> GeopResult<()> {
163 build_pattern(model, reader, step, args)
164 }
165}