1use std::{any::Any, collections::BTreeMap, rc::Rc};
7
8use geop_core_math::geop_error::{GeopError, GeopResult};
9use geop_core_program::{
10 operation::{ErasedOperation, Operation, args, saved},
11 operation_registry::OperationRegistry,
12 part::Part,
13 program::{Program, ProgramStep},
14 reader::Reader,
15 runner::Runner,
16};
17use serde::Serialize;
18
19use crate::editing::{Event, Form, hit::Scene};
20
21thread_local! {
22 static FORMS_BUILT: std::cell::Cell<usize> = const { std::cell::Cell::new(0) };
23}
24
25pub fn forms_built() -> usize {
30 FORMS_BUILT.get()
31}
32
33#[derive(Clone, Debug, PartialEq, Serialize)]
37pub struct OperationInfo {
38 pub label: &'static str,
39 pub doc: &'static str,
40 pub group: OperationGroup,
41 pub tier: OperationTier,
42 pub experimental: bool,
45 pub picks_built: bool,
49 #[serde(skip)]
55 pub picks_whole: bool,
56 pub reads: &'static [&'static str],
60 pub icon: Option<&'static str>,
65 pub shortcut: Option<&'static str>,
70 #[serde(skip)]
75 pub kept_on_escape: bool,
76}
77
78impl OperationInfo {
79 pub const fn new(
82 label: &'static str,
83 doc: &'static str,
84 group: OperationGroup,
85 tier: OperationTier,
86 ) -> Self {
87 Self {
88 label,
89 doc,
90 group,
91 tier,
92 experimental: false,
93 picks_built: false,
94 picks_whole: false,
95 reads: &[],
96 icon: None,
97 shortcut: None,
98 kept_on_escape: false,
99 }
100 }
101
102 pub const fn experimental(self) -> Self {
104 Self {
105 experimental: true,
106 ..self
107 }
108 }
109
110 pub const fn picks_built(self) -> Self {
112 Self {
113 picks_built: true,
114 ..self
115 }
116 }
117
118 pub const fn picks_whole(self) -> Self {
120 Self {
121 picks_whole: true,
122 ..self
123 }
124 }
125
126 pub const fn reads(self, reads: &'static [&'static str]) -> Self {
128 Self { reads, ..self }
129 }
130
131 pub const fn shortcut(self, shortcut: &'static str) -> Self {
134 Self {
135 shortcut: Some(shortcut),
136 ..self
137 }
138 }
139
140 pub const fn kept_on_escape(self) -> Self {
143 Self {
144 kept_on_escape: true,
145 ..self
146 }
147 }
148
149 pub const fn icon(self, icon: &'static str) -> Self {
151 Self {
152 icon: Some(icon),
153 ..self
154 }
155 }
156}
157
158#[derive(Clone, Debug, PartialEq, Serialize)]
161pub struct Listed {
162 pub kind: &'static str,
163 #[serde(flatten)]
164 pub info: &'static OperationInfo,
165}
166
167#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
172pub enum OperationGroup {
173 Sketch,
175 Solid,
178 Features,
180 #[serde(rename = "Pattern & bodies")]
182 Bodies,
183 Surface,
185 #[serde(rename = "Sheet metal")]
187 SheetMetal,
188 Part,
191 Assembly,
193 Output,
195}
196
197#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
200pub enum OperationTier {
201 Big,
203 Small,
205 Menu,
207}
208
209pub struct Context<'a> {
215 pub step: &'a str,
216 pub runner: &'a Runner,
217 pub before: Reader<'a>,
218 pub built: Option<Reader<'a>>,
219 pub whole: Reader<'a>,
223 pub scene: Option<&'a dyn Scene>,
226}
227
228pub trait Editable: Operation {
231 const INFO: OperationInfo;
232 type EditingState: Default + 'static;
235
236 fn new_args(context: &Context) -> Self::Args;
239
240 fn presentation(context: &Context, args: &Self::Args, editing: &Self::EditingState) -> Form;
244
245 fn process_user_event(
247 context: &Context,
248 args: &Self::Args,
249 editing: &Self::EditingState,
250 event: Event,
251 ) -> (Self::Args, Self::EditingState);
252}
253
254pub struct Edited {
256 pub args: serde_json::Value,
257 pub editing: Box<dyn Any>,
258}
259
260pub trait ErasedEditable: ErasedOperation {
263 fn info(&self) -> &'static OperationInfo;
264 fn new_args(&self, context: &Context) -> GeopResult<serde_json::Value>;
265 fn initial_editing(&self) -> Box<dyn Any>;
266 fn presentation(
267 &self,
268 context: &Context,
269 args: &serde_json::Value,
270 editing: &dyn Any,
271 ) -> GeopResult<Form>;
272 fn process_user_event(
273 &self,
274 context: &Context,
275 args: &serde_json::Value,
276 editing: &dyn Any,
277 event: Event,
278 ) -> GeopResult<Edited>;
279}
280
281fn editing<O: Editable>(editing: &dyn Any) -> GeopResult<&O::EditingState> {
282 editing.downcast_ref().ok_or_else(|| {
283 GeopError::new(format!(
284 "operation `{}` was given an editing state that is not `{}`",
285 O::KIND,
286 std::any::type_name::<O::EditingState>()
287 ))
288 })
289}
290
291impl<O: Editable> ErasedEditable for O {
292 fn info(&self) -> &'static OperationInfo {
293 &O::INFO
294 }
295
296 fn new_args(&self, context: &Context) -> GeopResult<serde_json::Value> {
297 saved::<O>(&<O as Editable>::new_args(context))
298 }
299
300 fn initial_editing(&self) -> Box<dyn Any> {
301 Box::new(O::EditingState::default())
302 }
303
304 fn presentation(
305 &self,
306 context: &Context,
307 args: &serde_json::Value,
308 editing: &dyn Any,
309 ) -> GeopResult<Form> {
310 Ok(<O as Editable>::presentation(
311 context,
312 &self::args::<O>(args)?,
313 self::editing::<O>(editing)?,
314 ))
315 }
316
317 fn process_user_event(
318 &self,
319 context: &Context,
320 args: &serde_json::Value,
321 editing: &dyn Any,
322 event: Event,
323 ) -> GeopResult<Edited> {
324 let (args, editing) = <O as Editable>::process_user_event(
325 context,
326 &self::args::<O>(args)?,
327 self::editing::<O>(editing)?,
328 event,
329 );
330 Ok(Edited {
331 args: saved::<O>(&args)?,
332 editing: Box::new(editing),
333 })
334 }
335}
336
337thread_local! {
338 static LISTED: std::cell::Cell<usize> = const { std::cell::Cell::new(0) };
339}
340
341pub fn forms_listed() -> usize {
345 LISTED.get()
346}
347
348#[derive(Default)]
353pub struct Editors {
354 by_kind: BTreeMap<&'static str, Rc<dyn ErasedEditable>>,
355 order: Vec<&'static str>,
356}
357
358#[derive(Clone)]
364pub struct Against<'a> {
365 pub part: &'a Part,
366 pub outside: Option<Reader<'a>>,
367}
368
369impl<'a> From<&'a Part> for Against<'a> {
370 fn from(part: &'a Part) -> Self {
371 Self {
372 part,
373 outside: None,
374 }
375 }
376}
377
378impl<'a> Against<'a> {
379 fn placed(&self, reader: Reader<'a>) -> Reader<'a> {
381 match &self.outside {
382 Some(outside) => reader.placed_in(outside.clone()),
383 None => reader,
384 }
385 }
386
387 pub fn current(&self) -> Reader<'a> {
389 self.placed(self.part.current())
390 }
391
392 pub fn after(&self, step: &str) -> Reader<'a> {
394 self.placed(self.part.after(step))
395 }
396
397 pub fn before(&self, program: &Program, step: &str) -> GeopResult<Reader<'a>> {
401 let part = self.part;
402 let position = (program.steps.iter().position(|s| s.id == step))
403 .ok_or_else(|| GeopError::new(format!("step `{step}` is not in the program")))?;
404 let ran = |id: &str| part.order().iter().any(|ran| ran == id);
405 if ran(step) {
406 return Ok(self.placed(part.before(step)));
407 }
408 let earlier = (program.steps.iter().take(position).rev()).find(|s| ran(&s.id));
409 Ok(self.placed(match (earlier, program.steps.first()) {
410 (Some(earlier), _) => part.after(&earlier.id),
411 (None, Some(first)) if ran(&first.id) || position == 0 => part.before(&first.id),
412 _ => part.current(),
413 }))
414 }
415}
416
417struct Step<'a> {
420 args: &'a serde_json::Value,
421 operation: &'a dyn ErasedEditable,
422 context: Context<'a>,
423}
424
425impl Editors {
426 pub fn with<O: Editable>(mut self, operation: O) -> GeopResult<Self> {
428 self.add(Rc::new(operation))?;
429 Ok(self)
430 }
431
432 pub(crate) fn add(&mut self, operation: Rc<dyn ErasedEditable>) -> GeopResult<()> {
435 let kind = operation.kind();
436 if self.by_kind.contains_key(kind) {
437 return Err(GeopError::new(format!(
438 "operation `{kind}` is registered twice"
439 )));
440 }
441 self.by_kind.insert(kind, operation);
442 self.order.push(kind);
443 Ok(())
444 }
445
446 pub fn operations(&self) -> GeopResult<OperationRegistry> {
448 let mut registry = OperationRegistry::default();
449 for operation in self.by_kind.values() {
450 let operation: Rc<dyn ErasedOperation> = operation.clone();
451 registry = registry.with_erased(operation)?;
452 }
453 Ok(registry)
454 }
455
456 pub fn infos(&self) -> Vec<Listed> {
458 (self.order.iter())
459 .filter_map(|kind| self.info(kind))
460 .collect()
461 }
462
463 pub fn info(&self, kind: &str) -> Option<Listed> {
465 (self.by_kind.get_key_value(kind)).map(|(&kind, operation)| Listed {
466 kind,
467 info: operation.info(),
468 })
469 }
470
471 fn get(&self, kind: &str) -> GeopResult<&dyn ErasedEditable> {
472 self.by_kind
473 .get(kind)
474 .map(Rc::as_ref)
475 .ok_or_else(|| GeopError::new(format!("unknown operation `{kind}`")))
476 }
477
478 fn step<'a>(
482 &'a self,
483 runner: &'a Runner,
484 part: Against<'a>,
485 program: &'a Program,
486 step: &'a str,
487 ) -> GeopResult<Step<'a>> {
488 let program_step = program.step(step)?;
489 let context = Context {
490 step,
491 runner,
492 before: part.before(program, step)?,
493 built: Some(part.after(step)),
494 whole: part.current(),
495 scene: None,
496 };
497 Ok(Step {
498 args: &program_step.args,
499 operation: self.get(&program_step.operation)?,
500 context,
501 })
502 }
503
504 pub fn new_step<'a>(
507 &self,
508 runner: &Runner,
509 part: impl Into<Against<'a>>,
510 program: &Program,
511 kind: &str,
512 id: &str,
513 before: Option<&str>,
514 ) -> GeopResult<ProgramStep> {
515 let operation = self.get(kind)?;
516 let part = part.into();
517 let before = match before {
518 Some(step) => part.before(program, step)?,
519 None => part.current(),
520 };
521 let context = Context {
522 step: id,
523 runner,
524 before,
525 built: None,
526 whole: part.current(),
527 scene: None,
528 };
529 Ok(ProgramStep {
530 id: id.to_string(),
531 operation: operation.kind().to_string(),
532 args: operation.new_args(&context)?,
533 active: None,
534 })
535 }
536
537 pub fn initial_editing(&self, program: &Program, step: &str) -> GeopResult<Box<dyn Any>> {
539 Ok(self.get(&program.step(step)?.operation)?.initial_editing())
540 }
541
542 pub fn presentation<'a>(
544 &self,
545 runner: &Runner,
546 part: impl Into<Against<'a>>,
547 program: &Program,
548 step: &str,
549 editing: &dyn Any,
550 ) -> GeopResult<Form> {
551 FORMS_BUILT.set(FORMS_BUILT.get() + 1);
552 let step = self.step(runner, part.into(), program, step)?;
553 (step.operation).presentation(&step.context, step.args, editing)
554 }
555
556 pub fn listed<'a>(
561 &self,
562 runner: &Runner,
563 part: impl Into<Against<'a>>,
564 program: &Program,
565 step: &str,
566 ) -> GeopResult<Form> {
567 LISTED.set(LISTED.get() + 1);
568 let mut step = self.step(runner, part.into(), program, step)?;
569 step.context.built = None;
570 let editing = step.operation.initial_editing();
571 (step.operation).presentation(&step.context, step.args, &*editing)
572 }
573
574 #[allow(clippy::too_many_arguments)]
577 pub fn process_user_event<'a>(
578 &self,
579 runner: &Runner,
580 part: impl Into<Against<'a>>,
581 program: &Program,
582 step: &str,
583 editing: &dyn Any,
584 event: Event,
585 scene: Option<&dyn Scene>,
586 ) -> GeopResult<Edited> {
587 let mut step = self.step(runner, part.into(), program, step)?;
588 step.context.scene = scene;
589 (step.operation).process_user_event(&step.context, step.args, editing, event)
590 }
591}