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