1use geop_core_editor::part_operation::PartOperation;
11use geop_core_math::{
12 geop_error::{GeopError, GeopResult, WithContext},
13 with_context,
14};
15use geop_core_program::{
16 element::{ElementReference, HasElements, Kind, whole},
17 part::StepContext,
18 reader::Reader,
19 target::{Target, TargetReference},
20};
21use geop_ops_model::sketch::{PlacedSketch, sketch};
22use geop_ops_model::{
23 ModelOperation, NamedModel, Namer, Records, SolidNotes, build_model, solid_notes,
24};
25
26use crate::{edge_flange::EdgeRef, flat_pattern::FlatPatternData, sheet::Sheet};
27
28#[derive(Clone, Debug, PartialEq)]
30pub enum SheetBody {
31 Bent(Sheet),
33 Flat(FlatPatternData),
35}
36
37impl SheetBody {
38 pub fn bent(&self) -> Option<&Sheet> {
40 match self {
41 SheetBody::Bent(sheet) => Some(sheet),
42 SheetBody::Flat(_) => None,
43 }
44 }
45
46 pub fn thickness(&self) -> f64 {
48 match self {
49 SheetBody::Bent(sheet) => sheet.rules.thickness,
50 SheetBody::Flat(flat) => flat.thickness,
51 }
52 }
53}
54
55pub type Sheets = Records<SheetBody>;
58
59pub fn sheets() -> TargetReference<Sheets> {
61 TargetReference::new("sheets")
62}
63
64impl Kind for SheetBody {
66 const NAME: &'static str = "sheet_body";
67}
68
69impl HasElements<SheetBody> for Sheets {
70 type Element<'a> = &'a SheetBody;
71 const SINGLETON: Option<&'static str> = Some("sheets");
72
73 fn element(&self, name: &str) -> GeopResult<&SheetBody> {
74 self.get(name).ok_or_else(|| {
75 GeopError::new(format!(
76 "{name:?} is no sheet-metal body: none was recorded for it"
77 ))
78 })
79 }
80}
81
82pub fn sheet_body<'a>(reader: &Reader<'a>, solid: &str) -> Option<&'a SheetBody> {
85 reader.defines(&sheets()).then_some(())?;
86 reader
87 .retrieve_element(&ElementReference::new(sheets(), solid))
88 .ok()
89}
90
91pub(crate) type WithEdge = (String, Sheet, (usize, usize, bool));
94
95pub(crate) fn with_edge(
100 model: &NamedModel,
101 reader: &Reader,
102 edge: Option<&EdgeRef>,
103 what: &str,
104) -> GeopResult<WithEdge> {
105 let edge = edge
106 .ok_or_else(|| GeopError::new(format!("no edge is picked: {what} is bent from one")))?
107 .name();
108 (model.solid_names().into_iter())
109 .find_map(|solid| {
110 let sheet = sheet_body(reader, &solid)?.bent()?;
111 let at = sheet.edge_named(edge)?;
112 Some((solid, sheet.clone(), at))
113 })
114 .ok_or_else(|| {
115 GeopError::new(format!(
116 "{edge:?} is no edge of a sheet-metal body's flat face on its outline: {what} is bent from one of those"
117 ))
118 })
119}
120
121#[derive(Clone, Debug, PartialEq)]
123pub enum Making {
124 Bent(Sheet),
126 Flat(Box<Unfolded>),
128}
129
130#[derive(Clone, Debug, PartialEq)]
133pub struct Unfolded {
134 pub sheet: Sheet,
135 pub bend_lines: PlacedSketch,
136 pub data: FlatPatternData,
137}
138
139#[derive(Clone, Debug, PartialEq)]
142pub struct Made {
143 pub consumes: Option<String>,
144 pub solid: String,
145 pub making: Making,
146}
147
148pub fn made(step: &str) -> TargetReference<Made> {
150 TargetReference::new(format!("sheet_metal:{step}"))
151}
152
153impl Target for Made {}
155
156impl Kind for Made {
158 const NAME: &'static str = "sheet_metal";
159}
160
161impl HasElements<Made> for Made {
162 type Element<'a> = &'a Made;
163
164 fn element(&self, name: &str) -> GeopResult<&Made> {
165 whole(self, name)
166 }
167}
168
169fn made_by<'a>(reader: &Reader<'a>, step: &str) -> GeopResult<&'a Made> {
171 reader.retrieve_element(&ElementReference::whole(made(step)))
172}
173
174pub(crate) trait SheetStep: PartOperation + ModelOperation {
178 fn plan(reader: &Reader, step: &str, args: &Self::Args) -> GeopResult<Made>;
180}
181
182fn planned<O: SheetStep>(reader: &Reader, (step, args): &(String, O::Args)) -> GeopResult<Made> {
184 O::plan(reader, step, args)
185}
186
187#[allow(clippy::ptr_arg)]
191fn record(reader: &Reader, step: &String) -> GeopResult<Sheets> {
192 let mut sheets: Sheets = reader.previous()?;
193 let made = made_by(reader, step)?;
194 if let Some(consumed) = &made.consumes {
195 sheets.remove(consumed);
196 }
197 let body = match &made.making {
198 Making::Bent(sheet) => SheetBody::Bent(sheet.clone()),
199 Making::Flat(unfolded) => SheetBody::Flat(unfolded.data.clone()),
200 };
201 sheets.insert(made.solid.clone(), body);
202 Ok(sheets)
203}
204
205#[allow(clippy::ptr_arg)]
208fn note(reader: &Reader, step: &String) -> GeopResult<SolidNotes> {
209 let mut notes: SolidNotes = reader.previous()?;
210 let made = made_by(reader, step)?;
211 if let Some(consumed) = &made.consumes {
212 notes.remove(consumed);
213 }
214 let thickness = match &made.making {
215 Making::Bent(sheet) => sheet.rules.thickness,
216 Making::Flat(unfolded) => unfolded.data.thickness,
217 };
218 notes.note_mut(made.solid.clone()).thickness = Some(thickness);
219 Ok(notes)
220}
221
222pub(crate) fn run<O: SheetStep>(ctx: &mut StepContext, args: &O::Args) -> GeopResult<()> {
225 let step = ctx.step();
226 ctx.produce(&made(step), &(step.to_string(), args.clone()), planned::<O>)?;
227 ctx.produce(&sheets(), &step.to_string(), record)?;
228 ctx.produce(&solid_notes(), &step.to_string(), note)?;
229 build_model::<O>(ctx, args)
230}
231
232#[allow(clippy::ptr_arg)]
234fn bend_lines(reader: &Reader, step: &String) -> GeopResult<PlacedSketch> {
235 match &made_by(reader, step)?.making {
236 Making::Flat(unfolded) => Ok(unfolded.bend_lines.clone()),
237 Making::Bent(_) => Err(GeopError::new(format!(
238 "step {step:?} made no flat pattern to draw the bend lines of"
239 ))),
240 }
241}
242
243pub(crate) fn add_bend_lines(ctx: &mut StepContext, name: &str) -> GeopResult<()> {
246 let step = ctx.step();
247 ctx.produce(&sketch(name), &step.to_string(), bend_lines)
248}
249
250pub(crate) fn build(model: &mut NamedModel, reader: &Reader, step: &str) -> GeopResult<()> {
253 let ctx = with_context!("building the sheet metal of step {step}");
254 let made = made_by(reader, step).with_context(ctx)?;
255 match &made.making {
256 Making::Bent(sheet) => sheet
257 .build(model, made.consumes.as_deref(), &made.solid)
258 .with_context(ctx),
259 Making::Flat(unfolded) => {
260 let namer = Namer::new("flat_pattern", step)?;
261 crate::flat_pattern::build(model, &unfolded.sheet, made.consumes.as_deref(), &namer)
262 .with_context(ctx)
263 }
264 }
265}