microcad_lang/builtin/
workpiece.rs

1// Copyright © 2025 The µcad authors <info@ucad.xyz>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4//! Builtin function evaluation entity
5
6use custom_debug::Debug;
7use strum::Display;
8
9use crate::{
10    builtin::*, eval::*, model::*, render::*, resolve::*, src_ref::*, syntax::*, value::*,
11};
12
13/// The kind of the built-in workbench determines its output.
14#[derive(Debug, Clone, Display, PartialEq)]
15pub enum BuiltinWorkbenchKind {
16    /// A parametric 2D primitive.
17    Primitive2D,
18    /// A parametric 3D primitive.
19    Primitive3D,
20    /// An affine transformation.
21    Transform,
22    /// An operation on a model.
23    Operation,
24}
25
26/// The return value when calling a built-in workpiece.
27pub enum BuiltinWorkpieceOutput {
28    /// 2D geometry output.
29    Primitive2D(Box<dyn RenderWithContext<Geometry2DOutput>>),
30    /// 3D geometry output.
31    Primitive3D(Box<dyn RenderWithContext<Geometry3DOutput>>),
32    /// Transformation.
33    Transform(AffineTransform),
34    /// Operation.
35    Operation(Box<dyn Operation>),
36}
37
38/// Builtin sketch function type.
39pub type BuiltinWorkpieceFn = dyn Fn(&Tuple) -> RenderResult<BuiltinWorkpieceOutput>;
40
41/// The built-in workpiece.
42#[derive(Clone, Debug)]
43pub struct BuiltinWorkpiece {
44    /// Kind of the workpiece.
45    pub kind: BuiltinWorkbenchKind,
46    /// Output type.
47    pub output_type: OutputType,
48    /// Creator symbol.
49    pub creator: Hashed<Creator>,
50    /// The function that will be called when the workpiece is rendered.
51    #[debug(skip)]
52    pub f: &'static BuiltinWorkpieceFn,
53}
54
55impl BuiltinWorkpiece {
56    /// Call the workpiece with its arguments.
57    pub fn call(&self) -> RenderResult<BuiltinWorkpieceOutput> {
58        (self.f)(&self.creator.arguments)
59    }
60}
61
62impl std::fmt::Display for BuiltinWorkpiece {
63    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
64        write!(
65            f,
66            "{kind} {creator}",
67            kind = self.kind,
68            creator = *self.creator,
69        )
70    }
71}
72
73impl ComputedHash for BuiltinWorkpiece {
74    fn computed_hash(&self) -> crate::render::HashId {
75        self.creator.computed_hash()
76    }
77}
78
79/// Builtin part definition
80pub trait BuiltinWorkbenchDefinition {
81    /// Get id of the builtin part
82    fn id() -> &'static str;
83
84    /// The kind of the built-in workbench.
85    fn kind() -> BuiltinWorkbenchKind;
86
87    /// A help string as markdown.
88    fn help() -> Option<&'static str> {
89        None
90    }
91
92    /// The expected output type.
93    fn output_type() -> OutputType {
94        OutputType::NotDetermined
95    }
96
97    /// The function that generates an output from the workpiece.
98    fn workpiece_function() -> &'static BuiltinWorkpieceFn;
99
100    /// Construct the workpiece from an argument tuple.
101    fn workpiece(creator: Creator) -> BuiltinWorkpiece {
102        BuiltinWorkpiece {
103            kind: Self::kind(),
104            output_type: Self::output_type(),
105            creator: Hashed::new(creator),
106            f: Self::workpiece_function(),
107        }
108    }
109
110    /// Create model from workpiece and creator.
111    fn model(creator: Creator) -> Model {
112        let workpiece = Self::workpiece(creator);
113        let model = ModelBuilder::new(Element::BuiltinWorkpiece(workpiece), SrcRef(None)).build();
114
115        // Add a @input placeholder if we have a built-in operation or transform.
116        // This assures that multiplicity for built-ins is working correctly.
117        if Self::kind() == BuiltinWorkbenchKind::Operation
118            || Self::kind() == BuiltinWorkbenchKind::Transform
119        {
120            model.append(ModelBuilder::new(Element::InputPlaceholder, SrcRef(None)).build());
121        }
122        model
123    }
124
125    /// Workbench function
126    fn function() -> &'static BuiltinFn {
127        &|params, args, context| {
128            log::trace!(
129                "Built-in workbench {call} {id:?}({args})",
130                call = crate::mark!(CALL),
131                id = Self::id()
132            );
133            Ok(Value::Model(
134                ArgumentMatch::find_multi_match(args, params)?
135                    .iter()
136                    .map(|tuple| Self::model(Creator::new(context.current_symbol(), tuple.clone())))
137                    .collect::<Models>()
138                    .to_multiplicity(SrcRef(None)),
139            ))
140        }
141    }
142
143    /// Workbench function
144    fn doc() -> Option<DocBlock> {
145        Self::help().map(DocBlock::new_builtin)
146    }
147
148    /// Part initialization parameters
149    fn parameters() -> ParameterValueList {
150        ParameterValueList::default()
151    }
152
153    /// Create builtin symbol
154    fn symbol() -> Symbol {
155        Symbol::new_builtin(Builtin {
156            id: Identifier::no_ref(Self::id()),
157            parameters: Self::parameters(),
158            kind: BuiltinKind::Workbench(Self::kind()),
159            f: Self::function(),
160            doc: Self::doc(),
161        })
162    }
163}