Skip to main content

microcad_lang/builtin/
workpiece.rs

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