Skip to main content

geop_ops_parts/operation/
schema.rs

1//! A description of every operation and its arguments, for whatever edits
2//! programs: a UI builds the form for an operation from its
3//! [`OperationSchema`] rather than knowing the operation by hand, so a new
4//! operation shows up in every editor without touching any of them.
5//!
6//! Written by `#[derive(OperationArgs)]` / `#[derive(Operations)]` from
7//! each argument's `#[arg(...)]` and doc comment.
8
9use geop_core_part::DatumKind;
10use serde::Serialize;
11
12use super::entity::Role;
13
14/// What kind of value an argument holds — and so how an editor lets a user
15/// enter it.
16#[derive(Clone, Debug, PartialEq, Serialize)]
17#[serde(tag = "type", rename_all = "snake_case")]
18pub enum ArgKind {
19    /// A real number: a length, a distance. `min`/`max` bound what a slider
20    /// offers, not what is valid.
21    Number {
22        default: f64,
23        min: f64,
24        max: f64,
25    },
26    Bool {
27        default: bool,
28    },
29    /// The name of a solid of the part — picked in the viewport.
30    Solid,
31    /// The name of a face of the part — picked in the viewport.
32    Face,
33    /// The name of a sketch of the part.
34    Sketch,
35    /// The id of a line of the sketch named by the argument `sketch`.
36    SketchLine {
37        sketch: &'static str,
38    },
39    /// One of a fixed set of values.
40    Choice {
41        options: &'static [&'static str],
42        default: &'static str,
43    },
44    /// A plane to sketch on: a base plane, a planar face or a datum plane,
45    /// picked in the viewport (see [`crate::EntityRef`]).
46    Plane,
47    /// Entities to build on — vertices, edges, faces, datums, the origin,
48    /// world axes and base planes — picked in the viewport, in order (see
49    /// [`crate::EntityRef`]).
50    Selection,
51    /// How to build a datum from the entities of the argument `selection`:
52    /// one of `options`, each of which fits only some selections (see
53    /// [`crate::operation::inspect_selection`]).
54    Construction {
55        selection: &'static str,
56        options: &'static [ConstructionSchema],
57    },
58    /// Sketch geometry, drawn on the plane given by the argument `plane`.
59    Drawing {
60        plane: &'static str,
61    },
62    /// A new body, or a boolean with a target solid (see
63    /// [`crate::operation::Combine`]): a choice of mode, and the target
64    /// picked in the viewport. `sign` names the number argument whose sign
65    /// picks the mode until the user chooses one: join when it is
66    /// positive, cut when it is negative — an extrude up out of a face
67    /// adds material, one down into it removes some.
68    Combine {
69        sign: Option<&'static str>,
70    },
71}
72
73/// One argument of an operation.
74#[derive(Clone, Debug, PartialEq, Serialize)]
75pub struct ArgSchema {
76    /// The field name, as the argument serializes.
77    pub name: &'static str,
78    pub doc: &'static str,
79    pub kind: ArgKind,
80}
81
82/// One way to build a datum (see [`crate::Construction`]): what it builds,
83/// what it needs selected — one entity per input, in any order — and the
84/// values it takes besides.
85#[derive(Clone, Debug, PartialEq, Serialize)]
86pub struct ConstructionSchema {
87    /// How the construction is spelled: `offset`.
88    pub method: &'static str,
89    pub label: &'static str,
90    pub doc: &'static str,
91    pub result: DatumKind,
92    pub inputs: &'static [Role],
93    pub params: &'static [ArgSchema],
94}
95
96/// One operation: its kind (as a program step spells it), a short label,
97/// what it does, and its arguments.
98#[derive(Clone, Debug, PartialEq, Serialize)]
99pub struct OperationSchema {
100    pub kind: &'static str,
101    pub label: &'static str,
102    pub doc: &'static str,
103    pub args: Vec<ArgSchema>,
104}
105
106/// An operation's arguments, described field by field — implemented by
107/// `#[derive(OperationArgs)]`.
108pub trait OperationArgs {
109    fn schema() -> Vec<ArgSchema>;
110}
111
112#[cfg(test)]
113mod tests {
114    use super::*;
115    use crate::PartOperation;
116
117    /// Every operation is described, with its arguments in declaration
118    /// order and their doc comments.
119    #[test]
120    fn schemas_describe_every_operation() {
121        let schemas = PartOperation::schemas();
122        let kinds: Vec<&str> = schemas.iter().map(|s| s.kind).collect();
123        assert_eq!(
124            kinds,
125            ["add_sketch", "extrude", "revolve", "boolean", "add_datum"]
126        );
127        let extrude = &schemas[1];
128        assert_eq!(extrude.label, "Extrude");
129        let args: Vec<&str> = extrude.args.iter().map(|a| a.name).collect();
130        assert_eq!(args, ["sketch", "distance", "symmetric", "combine"]);
131        assert_eq!(
132            extrude.args[3].kind,
133            ArgKind::Combine {
134                sign: Some("distance")
135            }
136        );
137        assert_eq!(extrude.args[0].kind, ArgKind::Sketch);
138        assert!(extrude.args[1].doc.starts_with("How far"));
139        assert_eq!(schemas[0].label, "Sketch");
140        serde_json::to_string(&schemas).unwrap();
141    }
142
143    /// The kind a schema gives an operation is the tag it serializes under,
144    /// so an editor can build steps from schemas alone.
145    #[test]
146    fn schema_kinds_are_the_serialized_tags() {
147        let kinds: Vec<&str> = PartOperation::schemas().iter().map(|s| s.kind).collect();
148        for (_, program) in crate::examples::all() {
149            for step in &program.steps {
150                let json = serde_json::to_value(&step.operation).unwrap();
151                assert_eq!(json["operation"], step.operation.kind());
152                assert!(kinds.contains(&step.operation.kind()));
153            }
154        }
155    }
156}