geop_ops_extrude_revolve/
sweep.rs1use geop_core_editor::{
5 edit_form::{EditForm, FormState},
6 editable::{Context, OperationGroup, OperationInfo, OperationTier},
7 editing::{Choice, Number, Unit},
8 part_operation,
9 part_operation::PartOperation,
10};
11use geop_core_math::geop_error::{GeopError, GeopResult, WithContext};
12use geop_core_program::{element::ElementReference, part::StepContext, reader::Reader};
13use geop_ops_model::sketch::{PlacedSketch, SketchRef, shape_profiles};
14use geop_ops_model::sketch3d::PlacedSketch3d;
15use geop_ops_model::{ModelOperation, NamedModel, Namer, build_model, read_model};
16use serde::{Deserialize, Serialize};
17
18use geop_lib_sweep::{
19 Control, Orientation, Rebuild, SweepLoop, profile_centre, starting_near, sweep_along,
20};
21
22use crate::sweeping::{Output, PathRef};
23
24#[derive(Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize)]
59pub struct Sweep;
60
61#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
62pub struct SweepArgs {
63 pub profile: SketchRef,
65 pub path: Option<PathRef>,
68 #[serde(default)]
70 pub orientation: Orientation,
71 #[serde(default, skip_serializing_if = "is_zero")]
74 pub twist: f64,
75 #[serde(default = "one", skip_serializing_if = "is_one")]
77 pub end_scale: f64,
78 #[serde(default, skip_serializing_if = "Vec::is_empty")]
81 pub rails: Vec<PathRef>,
82 #[serde(default)]
84 pub output: Output,
85}
86
87fn is_zero(x: &f64) -> bool {
88 *x == 0.0
89}
90
91fn one() -> f64 {
92 1.0
93}
94
95fn is_one(x: &f64) -> bool {
96 *x == 1.0
97}
98
99const ORIENTATIONS: [(Orientation, &str, &str); 2] = [
101 (Orientation::FollowPath, "follow_path", "follow path"),
102 (
103 Orientation::FixedNormal,
104 "fixed_normal",
105 "keep normal fixed",
106 ),
107];
108
109part_operation!(Sweep(SweepArgs));
110
111impl PartOperation for Sweep {
112 const KIND: &'static str = "sweep";
113 const INFO: OperationInfo = OperationInfo::new(
114 "Sweep",
115 "Sweep a sketch's area along the curves of another sketch into a solid, or its curves into faces.",
116 OperationGroup::Solid,
117 OperationTier::Small,
118 )
119 .shortcut("W")
120 .icon(crate::icons::SWEEP);
121 type Args = SweepArgs;
122 type Session = ();
123
124 fn new_args(context: &Context) -> SweepArgs {
127 let before = &context.before;
128 let mut sketches = before.targets_of::<PlacedSketch>();
129 let path = match before.targets_of::<PlacedSketch3d>().pop() {
132 Some(sketch) => Some(PathRef::Sketch3d(ElementReference::whole(sketch))),
133 None => (sketches.pop()).map(|sketch| PathRef::Sketch(ElementReference::whole(sketch))),
134 };
135 let profile = sketches
136 .pop()
137 .unwrap_or_else(|| geop_ops_model::sketch::sketch(""));
138 SweepArgs {
139 profile: SketchRef::whole(profile),
140 path,
141 orientation: Orientation::FollowPath,
142 twist: 0.0,
143 end_scale: 1.0,
144 rails: Vec::new(),
145 output: Output::new_for(&read_model(before).unwrap_or_default()),
146 }
147 }
148
149 fn run(ctx: &mut StepContext, args: &SweepArgs) -> GeopResult<()> {
150 let _step = ctx.step();
151 build_model::<Self>(ctx, args)
152 }
153 fn form<'a>(
156 context: &'a Context<'a>,
157 args: &SweepArgs,
158 _: &FormState<()>,
159 ) -> EditForm<'a, SweepArgs> {
160 let before = &context.before;
161 let mut f = EditForm::<SweepArgs>::default();
162 f.pick_one(
163 "profile",
164 "profile",
165 before,
166 Some(&args.profile),
167 |edit, profile| edit.args.profile = profile,
168 )
169 .pick_one("path", "path", before, args.path.as_ref(), |edit, path| {
170 edit.args.path = Some(path)
171 })
172 .pick(
173 "rails",
174 "guide rails",
175 before,
176 &args.rails,
177 true,
178 |edit, rails| edit.args.rails = rails,
179 );
180 f.form.dialog.optional("rails");
181 let orientation =
182 (ORIENTATIONS.iter().find(|o| o.0 == args.orientation)).map_or("", |o| o.1);
183 let orientations = ORIENTATIONS
184 .iter()
185 .map(|&(_, value, label)| Choice::new(value, label));
186 let label = "orientation";
187 f.select(
188 label,
189 label,
190 orientation,
191 orientations.collect(),
192 false,
193 |edit, choice| {
194 if let Some(&(orientation, ..)) = ORIENTATIONS.iter().find(|o| o.1 == choice) {
195 edit.args.orientation = orientation;
196 }
197 },
198 );
199 if args.rails.is_empty() {
200 let twist = Number::new("twist", args.twist, Unit::Angle);
201 f.number("twist", twist, |edit, v| edit.args.twist = v);
202 let end_scale =
203 Number::new("end scale", args.end_scale, Unit::Fraction).range(0.0, 4.0);
204 f.number("end_scale", end_scale, |edit, v| edit.args.end_scale = v);
205 }
206 let output = |args: &mut SweepArgs, output| args.output = output;
207 (args.output).fields(&mut f, before, false, output);
208 f
209 }
210}
211
212impl ModelOperation for Sweep {
213 fn build(
214 model: &mut NamedModel,
215 reader: &Reader,
216 step: &str,
217 args: &SweepArgs,
218 ) -> GeopResult<()> {
219 let namer = Namer::new("sweep", step)?;
220 let path = (args.path.as_ref())
221 .ok_or_else(|| GeopError::new("sweep: pick the path to sweep along"))?;
222 if path.is_sketch(&args.profile) {
223 return Err(GeopError::new(
224 "sweep: the profile and the path are two different sketches",
225 ));
226 }
227 let placed = reader
228 .retrieve_element(&args.profile)
229 .concerning("profile")?;
230 let sketch = &placed.sketch;
231 let geometry = sketch.enclose()?;
232 let shape = args.output.shape(sketch)?;
233 let loops: Vec<_> = (shape_profiles(&placed.name, sketch, &geometry, shape)?.into_iter())
234 .map(SweepLoop::plain)
235 .collect();
236 let plane = &placed.plane;
237 let chain = path.resolve(reader)?;
238 let chain = starting_near(chain, &profile_centre(plane, &loops)?)?;
239 let mut rails = Vec::new();
240 for rail in &args.rails {
241 if rail.is_sketch(&args.profile) || rail == path {
242 return Err(GeopError::new(format!(
243 "sweep: the rail {rail:?} is the profile or the path: a rail is a curve of its own"
244 )));
245 }
246 rails.push(rail.resolve(reader)?);
247 }
248 let control = Control {
249 orientation: args.orientation,
250 twist: args.twist.to_radians(),
251 end_scale: args.end_scale,
252 rails,
253 };
254
255 (args.output).build_whole(model, &namer, step, |model, name| {
256 sweep_along(
257 model,
258 &namer,
259 name,
260 &chain,
261 plane,
262 &loops,
263 &control,
264 Some(Rebuild {
265 room: geop_lib_shell::room,
266 rebuilt: geop_lib_shell::rebuilt,
267 }),
268 )
269 })
270 }
271}