microcad_lang/model/
models.rs

1// Copyright © 2025 The µcad authors <info@ucad.xyz>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4//! Model tree module
5
6use crate::{model::*, src_ref::*};
7use derive_more::{Deref, DerefMut};
8use microcad_core::BooleanOp;
9
10/// Model multiplicities.
11#[derive(Debug, Default, Clone, PartialEq, Deref, DerefMut)]
12pub struct Models(Vec<Model>);
13
14impl Models {
15    /// Returns the first model if there is exactly one model in the list.
16    pub fn single_model(&self) -> Option<Model> {
17        match self.0.len() {
18            1 => self.0.first().cloned(),
19            _ => None,
20        }
21    }
22
23    /// Convert the models into a multiplicity node.
24    pub fn to_multiplicity(&self, src_ref: SrcRef) -> Model {
25        match self.single_model() {
26            Some(model) => model,
27            None => ModelBuilder::new(Element::Multiplicity, src_ref)
28                .add_children(self.clone())
29                .expect("No error")
30                .build(),
31        }
32    }
33
34    /// Check if all models contain an operation element.
35    pub fn is_operation(&self) -> bool {
36        self.iter().all(Model::is_operation)
37    }
38
39    /// A union operation model for this collection.
40    pub fn union(&self) -> Model {
41        self.boolean_op(microcad_core::BooleanOp::Union)
42    }
43
44    /// Return an boolean operation model for this collection.
45    pub fn boolean_op(&self, op: BooleanOp) -> Model {
46        match self.single_model() {
47            Some(model) => model,
48            None => ModelBuilder::new(Element::BuiltinWorkpiece(op.into()), SrcRef(None))
49                .add_children(
50                    [ModelBuilder::new(Element::Group, SrcRef(None))
51                        .add_children(self.clone())
52                        .expect("No error")
53                        .build()]
54                    .into_iter()
55                    .collect(),
56                )
57                .expect("No error")
58                .build(),
59        }
60    }
61
62    /// Merge two lists of [`Models`] into one by concatenation.
63    /// TODO: Use iterators!
64    pub fn merge(lhs: Models, rhs: Models) -> Self {
65        lhs.iter().chain(rhs.iter()).cloned().collect()
66    }
67
68    /// Filter the models by source file.
69    pub fn filter_by_source_hash(&self, source_hash: u64) -> Models {
70        self.iter()
71            .filter(|model| source_hash == model.source_hash())
72            .cloned()
73            .collect()
74    }
75
76    /// Deduce output type from models.
77    pub fn deduce_output_type(&self) -> OutputType {
78        self.iter().map(|model| model.deduce_output_type()).fold(
79            OutputType::NotDetermined,
80            // TODO: weird naming
81            |output_type, model_output_type| output_type.merge(&model_output_type),
82        )
83    }
84}
85
86impl From<Vec<Model>> for Models {
87    fn from(value: Vec<Model>) -> Self {
88        Self(value)
89    }
90}
91
92impl From<Option<Model>> for Models {
93    fn from(value: Option<Model>) -> Self {
94        Self(value.into_iter().collect())
95    }
96}
97
98impl std::fmt::Display for Models {
99    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
100        self.iter().try_for_each(|model| model.fmt(f))
101    }
102}
103
104impl FromIterator<Model> for Models {
105    fn from_iter<T: IntoIterator<Item = Model>>(iter: T) -> Self {
106        Self(iter.into_iter().collect())
107    }
108}
109
110impl TreeDisplay for Models {
111    fn tree_print(&self, f: &mut std::fmt::Formatter, depth: TreeState) -> std::fmt::Result {
112        self.iter().try_for_each(|child| child.tree_print(f, depth))
113    }
114}