Skip to main content

microcad_lang/model/
mod.rs

1// Copyright © 2025-2026 The µcad authors <info@microcad.xyz>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4//! Model tree module
5
6pub mod attribute;
7pub mod builder;
8pub mod creator;
9pub mod element;
10mod inner;
11pub mod iter;
12pub mod models;
13pub mod operation;
14pub mod output_type;
15pub mod properties;
16pub mod workpiece;
17
18pub use attribute::*;
19pub use builder::*;
20pub use creator::*;
21pub use element::*;
22pub use inner::*;
23pub use iter::*;
24pub use models::*;
25pub use operation::*;
26pub use output_type::*;
27pub use properties::*;
28pub use workpiece::*;
29
30use derive_more::{Deref, DerefMut};
31use microcad_core::{
32    BooleanOp, Integer,
33    hash::{ComputedHash, HashId},
34};
35use microcad_lang_base::{
36    Identifier, RcMut, SrcRef, SrcReferrer, TreeDisplay, TreeState, WriteToFile,
37};
38
39use crate::{lower::ir::WorkbenchKind, value::Value};
40
41/// A reference counted, mutable [`Model`].
42#[derive(Clone, Deref, DerefMut)]
43pub struct Model(RcMut<ModelInner>);
44
45impl Model {
46    /// Create new model from inner.
47    pub fn new(inner: RcMut<ModelInner>) -> Self {
48        Self(inner)
49    }
50
51    /// Return `true`, if model has no children.
52    pub fn is_empty(&self) -> bool {
53        self.borrow().is_empty()
54    }
55
56    /// Return `true`, if model wont produce any output
57    pub fn has_no_output(&self) -> bool {
58        let self_ = self.borrow();
59        match self_.element.value {
60            Element::BuiltinWorkpiece(_) | Element::InputPlaceholder => false,
61            _ => self_.is_empty(),
62        }
63    }
64
65    /// Make a deep copy if this model.
66    pub fn make_deep_copy(&self) -> Self {
67        let copy = Self(RcMut::new(self.0.borrow().clone_content()));
68        for child in self.borrow().children.iter() {
69            copy.append(child.make_deep_copy());
70        }
71        copy
72    }
73
74    /// Return address of this model.
75    pub fn addr(&self) -> usize {
76        self.0.as_ptr().addr()
77    }
78
79    /// Append a single model as child.
80    ///
81    /// Also tries to set the output type if it has not been determined yet.
82    pub fn append(&self, model: Model) -> Model {
83        model.borrow_mut().parent = Some(self.clone());
84
85        let mut self_ = self.0.borrow_mut();
86        self_.children.push(model.clone());
87
88        model
89    }
90
91    /// Append multiple models as children.
92    ///
93    /// Return self.
94    pub fn append_children(&self, models: Models) -> Self {
95        for model in models.iter() {
96            self.append(model.clone());
97        }
98        self.clone()
99    }
100
101    /// Short cut to generate boolean operator as binary operation with two models.
102    pub fn boolean_op(self, op: BooleanOp, other: Model) -> Model {
103        assert!(self != other, "lhs and rhs must be distinct.");
104        Models::from(vec![self.clone(), other]).boolean_op(op)
105    }
106
107    /// Multiply a model n times.
108    pub fn multiply(&self, n: Integer) -> Vec<Model> {
109        (0..n).map(|_| self.make_deep_copy()).collect()
110    }
111
112    /// Replace each input placeholder with copies of `input_model`.
113    pub fn replace_input_placeholders(&self, input_model: &Model) -> Self {
114        self.descendants().for_each(|model| {
115            let mut model_ = model.borrow_mut();
116            if model_.id.is_none() && matches!(model_.element.value, Element::InputPlaceholder) {
117                let input_model_ = input_model.borrow_mut();
118                *model_ = input_model_.clone_content();
119                model_.parent = Some(self.clone());
120                model_.children = input_model_.children.clone();
121            }
122        });
123        self.clone()
124    }
125
126    /// Deduce output type from children and set it and return it.
127    pub fn deduce_output_type(&self) -> OutputType {
128        let self_ = self.borrow();
129        let mut output_type = self_.element.output_type();
130        if output_type == OutputType::NotDetermined {
131            let children = &self_.children;
132            output_type = children.deduce_output_type();
133        }
134
135        output_type
136    }
137
138    /// Get render output type. Expects a render output.
139    pub fn render_output_type(&self) -> OutputType {
140        let self_ = self.borrow();
141        self_
142            .output
143            .as_ref()
144            .map(|output| output.output_type)
145            .unwrap_or(OutputType::InvalidMixed)
146    }
147
148    /// Return inner group if this model only contains a group as single child.
149    ///
150    /// This function is used when we evaluate operations like `subtract() {}` or `hull() {}`.
151    /// When evaluating these operations, we want to iterate over the group's children.
152    pub fn into_group(&self) -> Option<Model> {
153        self.borrow()
154            .children
155            .single_model()
156            .filter(|model| matches!(model.borrow().element.value, Element::Group))
157    }
158
159    /// Set the id of a model. This happens if the model was created by an assignment.
160    ///
161    /// For example, the assignment statement `a = Circle(4mm)` will result in a model with id `a`.
162    pub fn set_id(&self, id: Identifier) {
163        self.borrow_mut().id = Some(id);
164    }
165}
166
167/// Iterator methods.
168impl Model {
169    /// Returns an iterator of models to this model and its unnamed descendants, in tree order.
170    ///
171    /// Includes the current model.
172    pub fn descendants(&self) -> Descendants {
173        Descendants::new(self.clone())
174    }
175
176    /// An iterator that descends to multiplicity nodes.
177    pub fn multiplicity_descendants(&self) -> MultiplicityDescendants {
178        MultiplicityDescendants::new(self.clone())
179    }
180
181    /// Returns an iterator of models that belong to the same source file as this one
182    pub fn source_file_descendants(&self) -> SourceFileDescendants {
183        SourceFileDescendants::new(self.clone())
184    }
185
186    /// Parents iterator.
187    pub fn parents(&self) -> Parents {
188        Parents::new(self.clone())
189    }
190
191    /// Ancestors iterator.
192    pub fn ancestors(&self) -> Ancestors {
193        Ancestors::new(self.clone())
194    }
195
196    /// Get a property from this model.
197    pub fn get_property(&self, id: &Identifier) -> Option<Value> {
198        self.borrow().element.get_property(id).cloned()
199    }
200
201    /// Set a property in this model.
202    pub fn set_property(&mut self, id: Identifier, value: Value) -> Option<Value> {
203        self.borrow_mut().element.set_property(id, value)
204    }
205
206    /// Add a new property to the model.
207    pub fn add_property(&self, id: Identifier, value: Value) {
208        self.borrow_mut()
209            .element
210            .add_properties([(id, value)].into_iter().collect())
211    }
212}
213
214impl AttributesAccess for Model {
215    fn get_attributes_by_id(&self, id: &Identifier) -> Vec<Attribute> {
216        self.borrow().attributes.get_attributes_by_id(id)
217    }
218}
219
220impl PartialEq for Model {
221    fn eq(&self, other: &Self) -> bool {
222        self.addr() == other.addr()
223    }
224}
225
226impl SrcReferrer for Model {
227    fn src_ref(&self) -> SrcRef {
228        self.borrow().src_ref()
229    }
230}
231
232impl std::fmt::Display for Model {
233    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
234        write!(
235            f,
236            "{id}{element}{is_root} ->",
237            id = match &self.borrow().id {
238                Some(id) => format!("{id}: "),
239                None => String::new(),
240            },
241            element = *self.borrow().element,
242            is_root = if self.parents().next().is_some() {
243                ""
244            } else {
245                " (root)"
246            }
247        )
248    }
249}
250
251impl std::fmt::Debug for Model {
252    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
253        write!(
254            f,
255            "{}",
256            microcad_lang_base::shorten(
257                &format!(
258                    "{id}{element}{is_root} ->",
259                    id = match &self.borrow().id {
260                        Some(id) => format!("{id:?}: "),
261                        None => String::new(),
262                    },
263                    element = *self.borrow().element,
264                    is_root = if self.parents().next().is_some() {
265                        ""
266                    } else {
267                        " (root)"
268                    }
269                ),
270                140
271            )
272        )
273    }
274}
275
276impl TreeDisplay for Model {
277    fn tree_print(
278        &self,
279        f: &mut std::fmt::Formatter,
280        mut tree_state: TreeState,
281    ) -> std::fmt::Result {
282        let signature = if tree_state.debug {
283            format!("{self:?}")
284        } else {
285            self.to_string()
286        };
287        let self_ = self.borrow();
288        if let Some(output) = &self_.output {
289            writeln!(f, "{:tree_state$}{signature} {output}", "",)?;
290        } else {
291            writeln!(f, "{:tree_state$}{signature}", "",)?;
292        }
293        tree_state.indent();
294        if let Some(props) = self_.get_properties() {
295            props.tree_print(f, tree_state)?;
296        }
297        self_.attributes.tree_print(f, tree_state)?;
298        self_.children.tree_print(f, tree_state)
299    }
300}
301
302impl WriteToFile for Model {}
303
304impl std::hash::Hash for Model {
305    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
306        let self_ = self.borrow();
307        self_.element().hash(state);
308        self_.children().for_each(|child| child.hash(state));
309    }
310}
311
312impl ComputedHash for Model {
313    fn computed_hash(&self) -> HashId {
314        let self_ = self.borrow();
315        self_.output().computed_hash()
316    }
317}