microcad-lang 0.5.0

µcad language
Documentation
// Copyright © 2025-2026 The µcad authors <info@microcad.xyz>
// SPDX-License-Identifier: AGPL-3.0-or-later

//! Model tree module

pub mod attribute;
pub mod builder;
pub mod creator;
pub mod element;
mod inner;
pub mod iter;
pub mod models;
pub mod operation;
pub mod output_type;
pub mod properties;
pub mod workpiece;

pub use attribute::*;
pub use builder::*;
pub use creator::*;
pub use element::*;
pub use inner::*;
pub use iter::*;
pub use models::*;
pub use operation::*;
pub use output_type::*;
pub use properties::*;
pub use workpiece::*;

use derive_more::{Deref, DerefMut};
use microcad_core::{
    BooleanOp, Integer,
    hash::{ComputedHash, HashId},
};
use microcad_lang_base::{
    Identifier, RcMut, SrcRef, SrcReferrer, TreeDisplay, TreeState, WriteToFile,
};

use crate::{lower::ir::WorkbenchKind, value::Value};

/// A reference counted, mutable [`Model`].
#[derive(Clone, Deref, DerefMut)]
pub struct Model(RcMut<ModelInner>);

impl Model {
    /// Create new model from inner.
    pub fn new(inner: RcMut<ModelInner>) -> Self {
        Self(inner)
    }

    /// Return `true`, if model has no children.
    pub fn is_empty(&self) -> bool {
        self.borrow().is_empty()
    }

    /// Return `true`, if model wont produce any output
    pub fn has_no_output(&self) -> bool {
        let self_ = self.borrow();
        match self_.element.value {
            Element::BuiltinWorkpiece(_) | Element::InputPlaceholder => false,
            _ => self_.is_empty(),
        }
    }

    /// Make a deep copy if this model.
    pub fn make_deep_copy(&self) -> Self {
        let copy = Self(RcMut::new(self.0.borrow().clone_content()));
        for child in self.borrow().children.iter() {
            copy.append(child.make_deep_copy());
        }
        copy
    }

    /// Return address of this model.
    pub fn addr(&self) -> usize {
        self.0.as_ptr().addr()
    }

    /// Append a single model as child.
    ///
    /// Also tries to set the output type if it has not been determined yet.
    pub fn append(&self, model: Model) -> Model {
        model.borrow_mut().parent = Some(self.clone());

        let mut self_ = self.0.borrow_mut();
        self_.children.push(model.clone());

        model
    }

    /// Append multiple models as children.
    ///
    /// Return self.
    pub fn append_children(&self, models: Models) -> Self {
        for model in models.iter() {
            self.append(model.clone());
        }
        self.clone()
    }

    /// Short cut to generate boolean operator as binary operation with two models.
    pub fn boolean_op(self, op: BooleanOp, other: Model) -> Model {
        assert!(self != other, "lhs and rhs must be distinct.");
        Models::from(vec![self.clone(), other]).boolean_op(op)
    }

    /// Multiply a model n times.
    pub fn multiply(&self, n: Integer) -> Vec<Model> {
        (0..n).map(|_| self.make_deep_copy()).collect()
    }

    /// Replace each input placeholder with copies of `input_model`.
    pub fn replace_input_placeholders(&self, input_model: &Model) -> Self {
        self.descendants().for_each(|model| {
            let mut model_ = model.borrow_mut();
            if model_.id.is_none() && matches!(model_.element.value, Element::InputPlaceholder) {
                let input_model_ = input_model.borrow_mut();
                *model_ = input_model_.clone_content();
                model_.parent = Some(self.clone());
                model_.children = input_model_.children.clone();
            }
        });
        self.clone()
    }

    /// Deduce output type from children and set it and return it.
    pub fn deduce_output_type(&self) -> OutputType {
        let self_ = self.borrow();
        let mut output_type = self_.element.output_type();
        if output_type == OutputType::NotDetermined {
            let children = &self_.children;
            output_type = children.deduce_output_type();
        }

        output_type
    }

    /// Get render output type. Expects a render output.
    pub fn render_output_type(&self) -> OutputType {
        let self_ = self.borrow();
        self_
            .output
            .as_ref()
            .map(|output| output.output_type)
            .unwrap_or(OutputType::InvalidMixed)
    }

    /// Return inner group if this model only contains a group as single child.
    ///
    /// This function is used when we evaluate operations like `subtract() {}` or `hull() {}`.
    /// When evaluating these operations, we want to iterate over the group's children.
    pub fn into_group(&self) -> Option<Model> {
        self.borrow()
            .children
            .single_model()
            .filter(|model| matches!(model.borrow().element.value, Element::Group))
    }

    /// Set the id of a model. This happens if the model was created by an assignment.
    ///
    /// For example, the assignment statement `a = Circle(4mm)` will result in a model with id `a`.
    pub fn set_id(&self, id: Identifier) {
        self.borrow_mut().id = Some(id);
    }
}

/// Iterator methods.
impl Model {
    /// Returns an iterator of models to this model and its unnamed descendants, in tree order.
    ///
    /// Includes the current model.
    pub fn descendants(&self) -> Descendants {
        Descendants::new(self.clone())
    }

    /// An iterator that descends to multiplicity nodes.
    pub fn multiplicity_descendants(&self) -> MultiplicityDescendants {
        MultiplicityDescendants::new(self.clone())
    }

    /// Returns an iterator of models that belong to the same source file as this one
    pub fn source_file_descendants(&self) -> SourceFileDescendants {
        SourceFileDescendants::new(self.clone())
    }

    /// Parents iterator.
    pub fn parents(&self) -> Parents {
        Parents::new(self.clone())
    }

    /// Ancestors iterator.
    pub fn ancestors(&self) -> Ancestors {
        Ancestors::new(self.clone())
    }

    /// Get a property from this model.
    pub fn get_property(&self, id: &Identifier) -> Option<Value> {
        self.borrow().element.get_property(id).cloned()
    }

    /// Set a property in this model.
    pub fn set_property(&mut self, id: Identifier, value: Value) -> Option<Value> {
        self.borrow_mut().element.set_property(id, value)
    }

    /// Add a new property to the model.
    pub fn add_property(&self, id: Identifier, value: Value) {
        self.borrow_mut()
            .element
            .add_properties([(id, value)].into_iter().collect())
    }
}

impl AttributesAccess for Model {
    fn get_attributes_by_id(&self, id: &Identifier) -> Vec<Attribute> {
        self.borrow().attributes.get_attributes_by_id(id)
    }
}

impl PartialEq for Model {
    fn eq(&self, other: &Self) -> bool {
        self.addr() == other.addr()
    }
}

impl SrcReferrer for Model {
    fn src_ref(&self) -> SrcRef {
        self.borrow().src_ref()
    }
}

impl std::fmt::Display for Model {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(
            f,
            "{id}{element}{is_root} ->",
            id = match &self.borrow().id {
                Some(id) => format!("{id}: "),
                None => String::new(),
            },
            element = *self.borrow().element,
            is_root = if self.parents().next().is_some() {
                ""
            } else {
                " (root)"
            }
        )
    }
}

impl std::fmt::Debug for Model {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(
            f,
            "{}",
            microcad_lang_base::shorten(
                &format!(
                    "{id}{element}{is_root} ->",
                    id = match &self.borrow().id {
                        Some(id) => format!("{id:?}: "),
                        None => String::new(),
                    },
                    element = *self.borrow().element,
                    is_root = if self.parents().next().is_some() {
                        ""
                    } else {
                        " (root)"
                    }
                ),
                140
            )
        )
    }
}

impl TreeDisplay for Model {
    fn tree_print(
        &self,
        f: &mut std::fmt::Formatter,
        mut tree_state: TreeState,
    ) -> std::fmt::Result {
        let signature = if tree_state.debug {
            format!("{self:?}")
        } else {
            self.to_string()
        };
        let self_ = self.borrow();
        if let Some(output) = &self_.output {
            writeln!(f, "{:tree_state$}{signature} {output}", "",)?;
        } else {
            writeln!(f, "{:tree_state$}{signature}", "",)?;
        }
        tree_state.indent();
        if let Some(props) = self_.get_properties() {
            props.tree_print(f, tree_state)?;
        }
        self_.attributes.tree_print(f, tree_state)?;
        self_.children.tree_print(f, tree_state)
    }
}

impl WriteToFile for Model {}

impl std::hash::Hash for Model {
    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
        let self_ = self.borrow();
        self_.element().hash(state);
        self_.children().for_each(|child| child.hash(state));
    }
}

impl ComputedHash for Model {
    fn computed_hash(&self) -> HashId {
        let self_ = self.borrow();
        self_.output().computed_hash()
    }
}