geop_ops_model/parameters/
program.rs1use std::collections::BTreeMap;
5
6use geop_core_math::geop_error::{GeopError, GeopResult};
7use geop_core_program::{
8 numeric::{ParameterValue, read_parameter_values},
9 operation_registry::OperationRegistry,
10 part::Part,
11 program::{Program, ProgramStep},
12 runner::Runner,
13};
14use serde::Serialize;
15
16use crate::parameters::{
17 definition::{NumberParameter, Parameter, ParameterKind, Parameters},
18 steps::{
19 DefineParameters, DefineParametersArgs, LOOKUP, LookupTable, LookupTableArgs, PARAMETERS,
20 PROPERTIES, PartPropertiesArgs,
21 },
22};
23
24pub fn parameters_of(program: &Program) -> Parameters {
28 let args = |kind: &str| program.steps.iter().find(|s| s.operation == kind);
29 let tables = (program.steps.iter())
30 .filter(|s| s.operation == LOOKUP)
31 .filter_map(|step| serde_json::from_value::<LookupTableArgs>(step.args.clone()).ok())
32 .map(|table| Parameter {
33 name: table.input,
34 kind: ParameterKind::Table {
35 columns: table.columns,
36 rows: table.rows,
37 selected: table.selected,
38 },
39 });
40 let numbers = (args(PARAMETERS))
41 .and_then(|step| serde_json::from_value::<DefineParametersArgs>(step.args.clone()).ok())
42 .unwrap_or_default()
43 .values
44 .into_iter()
45 .map(|p| Parameter {
46 name: p.name,
47 kind: ParameterKind::Number {
48 expression: p.expression,
49 min: p.min,
50 max: p.max,
51 },
52 });
53 let properties = (args(PROPERTIES))
54 .and_then(|step| serde_json::from_value::<PartPropertiesArgs>(step.args.clone()).ok())
55 .unwrap_or_default();
56 Parameters {
57 color: properties.color,
58 material: properties.material,
59 title: properties.title,
60 designation: properties.designation,
61 values: numbers.chain(tables).collect(),
62 }
63}
64
65pub fn set_parameters(program: &mut Program, parameters: Parameters) {
72 let face_colors = (program
73 .steps
74 .iter()
75 .find(|step| step.operation == PROPERTIES))
76 .and_then(|step| serde_json::from_value::<PartPropertiesArgs>(step.args.clone()).ok())
77 .map(|properties| properties.face_colors)
78 .unwrap_or_default();
79 let properties = PartPropertiesArgs {
80 title: parameters.title,
81 designation: parameters.designation,
82 color: parameters.color,
83 material: parameters.material,
84 face_colors,
85 };
86 let mut numbers = Vec::new();
87 let mut tables = Vec::new();
88 for p in parameters.values {
89 match p.kind {
90 ParameterKind::Number {
91 expression,
92 min,
93 max,
94 } => numbers.push(NumberParameter {
95 name: p.name,
96 expression,
97 min,
98 max,
99 }),
100 ParameterKind::Table {
101 columns,
102 rows,
103 selected,
104 } => tables.push(LookupTableArgs {
105 input: p.name,
106 columns,
107 rows,
108 selected,
109 }),
110 }
111 }
112 let values = DefineParametersArgs { values: numbers };
113 let empty = properties == PartPropertiesArgs::default();
114 set_step(program, PROPERTIES, (!empty).then_some(&properties));
115 set_step(
116 program,
117 PARAMETERS,
118 (!values.values.is_empty()).then_some(&values),
119 );
120 program.steps.retain(|step| step.operation != LOOKUP);
121 for (k, table) in tables.iter().enumerate() {
122 program.steps.insert(k, lookup_step(table));
123 }
124}
125
126#[allow(
128 clippy::expect_used,
129 reason = "the arguments of a lookup step are plain data"
130)]
131fn lookup_step(table: &LookupTableArgs) -> ProgramStep {
132 ProgramStep {
133 id: format!("table_{}", table.input),
134 operation: LOOKUP.to_string(),
135 args: serde_json::to_value(table).expect("a step's arguments are plain data"),
136 active: None,
137 }
138}
139
140#[allow(
143 clippy::expect_used,
144 reason = "the arguments of a parameters or properties step are plain data"
145)]
146fn set_step<A: Serialize>(program: &mut Program, kind: &str, args: Option<&A>) {
147 let steps = &mut program.steps;
148 let at = steps.iter().position(|s| s.operation == kind);
149 let Some(args) = args else {
150 if let Some(at) = at {
151 steps.remove(at);
152 }
153 return;
154 };
155 let args = serde_json::to_value(args).expect("a step's arguments are plain data");
156 match at {
157 Some(at) => steps[at].args = args,
158 None => {
159 let taken = |id: &str| steps.iter().any(|s| s.id == id);
160 let id = (std::iter::once(kind.to_string()))
161 .chain((1..).map(|n| format!("{kind}{n}")))
162 .find(|id| !taken(id))
163 .unwrap_or_else(|| kind.to_string());
164 let step = ProgramStep {
165 id,
166 operation: kind.to_string(),
167 args,
168 active: None,
169 };
170 steps.insert(0, step);
171 }
172 }
173}
174
175pub fn parameter_numbers(program: &Program) -> GeopResult<BTreeMap<String, f64>> {
181 let steps = (program.steps.iter())
182 .filter(|step| step.operation == PARAMETERS || step.operation == LOOKUP)
183 .cloned()
184 .collect();
185 let registry = (OperationRegistry::default().with(DefineParameters))
186 .and_then(|registry| registry.with(LookupTable))?;
187 let runner = Runner::new(registry);
188 let mut part = Part::default();
189 let defining = Program::from_steps(steps);
190 let output = runner.run(&mut part, &defining, None);
191 if let Some((step, e)) = output.step_errors.into_iter().next() {
192 return Err(e.with_context(format!("step {step:?}")));
193 }
194 let reader = part.current();
195 let values = read_parameter_values(&reader)?;
196 for p in ¶meters_of(program).values {
197 if !values.0.iter().any(|(name, _)| *name == p.name) {
198 return Err(GeopError::new(format!(
199 "the parameter {:?} comes to nothing",
200 p.name
201 )));
202 }
203 }
204 Ok((values.0.iter())
205 .filter_map(|(name, value)| match value {
206 ParameterValue::Number(n) => Some((name.clone(), *n)),
207 ParameterValue::Text(_) => None,
208 })
209 .collect())
210}