Introduction
The µcad programming language is purely declarative so-called markup programming language, which means that a µcad program can be evaluated like a mathematical equation, resulting in a graphical output.
Purpose of this document
This document is meant as a explaining reference of all features of microcad. So chapter-wise this book will explain them around some example code. This example code is automatically tested and gives us a bit of a guarantee, that those features we explain here, work like we say.
Contribute
You might directly contribute your changes or new texts to this book via our git repository or if you are not so familiar with git, just leave an issue at codeberg.org so we can implement your ideas.
❤️ Donate
If you like this project, you can help us spend more time on it by donating:
Preface
In the 2010s, 3D printing suddenly became accessible to almost everyone — whether in maker spaces or even at home. People began building their own DIY printers and writing software to control them. Naturally, these developers needed a platform to design 3D models that could be printed.
Around that time, someone came up with an idea: a programming language made specifically for people who prefer working with a keyboard rather than a mouse or graphics tablet. That language is called OpenSCAD. It turned out to be a great success, inspiring countless impressive projects.
We loved it too and created many “thingies” with it. However, as experienced software engineers, we also had a few points of critique. While OpenSCAD is easy to learn and has a syntax reminiscent of C, we felt the language could be improved in several ways:
- more specialization for creating graphics,
- better support for modular programming,
- strict typing and unit handling,
- a syntax closer to Rust than to C,
- a solid library system,
- plugin support for other programming languages,
- and a more powerful visualization concept.
Over the past few years, as we became more familiar with Rust, we started microcad as a fun side project. In 2025, we were fortunate to receive funding to develop a visualization plugin.
After more than a year of work, we’ve developed and partially implemented several concepts. There’s still plenty to do and many more ideas to explore — but we believe we’ve now reached a point where it’s time to share our work with others.
Automatic Tested Code Examples
Within this document all code examples are automatically tested.
Test result banners
Each code example has a banner nearby which indicates the current testing status of the code. If you click one of these banners you will jump directly into a specific test report.
The following banners indicate that the code is tested ok:
| Banner | Meaning |
|---|---|
| Ok | |
| Ok (with warnings) | |
| Fails intentionally |
The following banners occur if a test is still marked as todo but is ok already. This can be corrected by changing the documentation.
| Banner | Meaning |
|---|---|
| Marked as todo but is ok | |
| Marked as todo but fails intentionally |
If you see one of the following banners, we did something wrong. Either the example may be wrong or the µcad interpreter might have a bug.
| Banner | Meaning |
|---|---|
| Fails with errors | |
| Fails with wrong errors or warnings | |
| Is ok but was meant to fail | |
| Fails early while parsing |
The following banners occur if tests are marked as todo and so are not running successful.
| Banner | Meaning |
|---|---|
| Work in progress | |
| Work in progress (should fail) |
Test list
See this section for a list of tests within this document.
Program Structure
A µcad program primary consists of the following core elements:
This section just explains the core elements but each may contain various statements.
Source Files
Source files are simply files which contain µcad code.
Such files might have the extension .µcad, .mcad or .ucad.
A source file can include the following types of statements which we will all discuss within this book:
| Statement | Purpose | Example |
|---|---|---|
| expression | calculate values | x * 5; |
| assignment | store values | y = x; |
| function | separate calculations | fn f() { } |
| workbench | build or transform 2D sketches and 3D parts | part P() { } |
| module | modularization of complex code | mod m { } |
| if | process conditions | if x > 1 { } |
| use | use elements from other modules | use m; |
| call | use functions and workbenches | f(); |
| comment | for documentation | // comment |
In its simplest form, a µcad program consists of a single file containing one or more of the above statements.
A source file can serve as both a module and a workbench.
You can use it to provide structure (for example, by organizing submodules) or
as a kind of workbench where you add statements to generate outputs—such as a
2D graphic or a 3D model.
The workbench section of the file is only evaluated if it is in the main file
(the one that microcad was called with).
The following examples illustrate this: a circle and a sphere are created, each with a radius of one centimeter.
// simply draw a circle
std::geo2d::Circle(radius = 1cm);
// simply draw a sphere
std::geo3d::Sphere(radius = 1cm);
Statements within a source file can represent either a 2D or a 3D model — but not both at the same time. Mixing 2D and 3D statements in the same file will result in an error:
std::geo2d::Circle(radius = 1cm);
std::geo3d::Sphere(radius = 1cm); // error: can't mix 2D and 3D
However, µcad programs can also be split across multiple files.
To include other files, the mod
statement is used…
Modules
A module is a way to organize and group code into logical units.
Modules help manage scope and control visibility (e.g., using pub to make
items public available).
In short:
- Modules define a namespace for your code.
- They can be nested to create hierarchies,
- declared with the
modkeyword (internal modules) - or in separate files (external modules).
- Control what is exposed to the outside world using
pub.
Example
The following example shows two nested internal modules carrying a public constant and a public function which then are used from outside the modules:
// define private module
mod my {
// define public module
pub mod math {
// define PI as a public constant value
pub const PI = 3.14159;
// define some public calculation function
pub fn abs(x: Scalar) -> Scalar {
if x < 0 { return -x; } else { return x; }
}
}
}
// use the function and the constant
x = my::math::abs(-1.0) * my::math::PI;
Internal Modules
Internal modules are modules which are defined with the mod keyword followed
by a name and a body as the following example shows:
mod my_module {
// statements
}
Module names are always written in
lower_snake_case.
The following statements can be listed in a module:
| Statement | Purpose | Example |
|---|---|---|
| const assignment | name constants | const y = x;pub y = x; |
| function | separate calculations | fn f() { } |
| workbench | build or transform 2D sketches and 3D parts | part P() { } |
| module | modularization of complex code | mod m { } |
| if | process conditions | if x > 1 { } |
| use | use elements from other modules | use m; |
| call | use functions and workbenches | f(); |
| comment | for documentation | // comment |
External Modules
External modules are external source files.
For example if you put a second file beside your main source code file, you can easily import this second file.
mod second;
second::f();
// file: second.µcad
pub fn f() {}
By using mod second, in the first source file, microcad searches for either a
file called second.µcad or second/mod.µcad and loads it into a module.
Use Statements
When including code from other modules or other files
fully qualified names of symbols (e.g. std:geo3d::cube) often produce much
boiler plate code when using them often.
The powerful use statement solves this problem and gives you an elegant method
to import code from other sources.
Internally every use statement builds one or more aliases, each with an identifier and a target symbol it points to.
The following example which uses two parts of geo3d shows the problem:
std::geo3d::Sphere(radius = 40mm);
std::geo3d::Cube(size = 40mm);
Use Statement
With use it first seems not shorter, but if we would use sphere and cube more often this would
shorten things a lot:
use std::geo2d::Circle;
use std::geo2d::Rect;
Circle(radius = 4mm);
Rect(size = 40mm);
You may also use whole the module if the names you are using already exist as a symbol:
use std::geo2d;
geo2d::Circle(radius = 40mm);
Use As Statement
Another way to be explicit when name conflicts exist is to use use as where you can
locally rename the target symbol:
use std::geo2d::Circle as Disk;
Disk(radius = 4mm);
Or you may use use as with a module:
use std::geo2d as geo;
geo::Circle(radius = 4mm);
Use All Statement
The shortest way to use many symbols from one module is to put an * at the end.
The following example aliases all symbols of std::geo3d into the current scope.
use std::geo3d::*;
Sphere(radius = 4mm);
Cube(size = 40mm);
Public Use Statement
This statement does not only make the target symbol visible on the current scope but in the symbol table where outside code might use it too.
Sphere and Cube will be made available for using them outside of module my in the following example:
mod my {
pub use std::geo2d::*;
}
my::Circle(radius = 4mm);
my::Rect(size = 40mm);
Functions
Functions provide a way to encapsulate frequently used code into sub-routines. These sub-routines can then be called to execute their code with a specific set of parameters.
// define function print_error with text as parameter of type String
fn print_error( text: String ) {
// code body
std::print("ERROR: {text}");
}
print_error("first");
print_error("second");
Functions may be declared within source files, modules or workbenches.
Declaration
A function declaration starts with the keyword fn, followed by an
identifier, a parameter list, and a function body.
Functions can also return a value as result:
fn pow( x: Scalar, n: Integer ) {
if n > 1 {
return x * pow(x, n-1); // return recursive product
} else if n == 1 {
return x; // return x
}
}
std::debug::assert_eq([ pow(8.0,2), 64.0 ]);
Returning a value twice is not allowed.
fn pow( x: Scalar, n: Integer ) {
if n > 1 {
return x * pow(x, n-1); // return recursive product
}
return x;
}
std::debug::assert_eq([ pow(8.0,2), 64.0 ]);
Module Functions
A module can contain functions that are accessible within
the module.
By declaring a function as public using the keyword pub, it becomes
available for use outside the module.
// module math
mod math {
// pow cannot be called from outside math
fn pow( x: Scalar, n: Integer ) {
if n == 1 {
x // return x
} else {
x * pow(x, n-1) // return recursive product
}
}
// square is callable from outside math
pub fn square(x: Scalar) {
// call internal pow
pow(x, 2)
}
}
// call square in math
math::square(2.0);
math::pow(2.0, 5); // error: pow is private
Workbench Functions
A workbench can contain functions that are accessible within the module only.
Here is an example which generates a punched disk of a given radius using a
function inner():
sketch PunchedDisk(radius: Length) {
use std::geo2d::Circle;
// calculate inner from radius in a method
fn inner() { return radius/2; }
// generate donut (and call inner)
Circle(radius) - Circle(radius = inner());
}
PunchedDisk(1cm);
Restrictions
There are some restrictions for workbench functions:
Trying to make them public with the keyword pub will result into an error:
part PunchedDisk(radius: Length) {
pub fn inner() { return radius/2; } // error: cant use pub fn inside workbench
}
PunchedDisk(4.0mm);
You cannot create workbench properties within the code body.
part PunchedDisk(radius: Length) {
fn inner() {
prop hole = radius/2; // error: prop not allowed in function
return hole;
}
inner();
}
PunchedDisk(1cm);
Workbenches
Workbench Types
Workbenches are µcad code constructs used to:
- create 2D sketches using
sketch, - build 3D parts using
part, or - apply operations to them using
opworkbenches.
Essentially, a workbench is initialized with a set of parameters and possibly some initialization code, then executes code that generates 2D and 3D objects.
Workbench Declaration
Workbench Elements
A workbench consists of the following elements:
- A leading keyword:
part,sketch, orop, - an identifier that names the workbench,
- a building plan defined by a parameter list following the identifier,
- optional init code, which is executed before any initializer,
- optional initializers, offering alternative ways to initialize the building plan,
- optional functions, acting as subroutines with their own parameters and code bodies,
- optional properties, accessible from outside and set from the inside,
- and typically some building code, which runs after all initialization steps and generates the final objects.
The following code demonstrates most of these elements:
// Sketch with a `radius` as building plan.
// Which will automatically become a property.
sketch Wheel(radius: Length) {
// Init code...
const FACTOR = 2;
// Initializer #1
init(diameter: Length) {
// must set `radius`
radius = diameter / FACTOR;
}
// No code in between!
// Initializer #2
init(r: Length) {
// must set `radius`
radius = r;
}
// Function (sub routine)
fn into_diameter(r: Length) {
return r * FACTOR;
}
// Building code...
// Set a property which can be seen from outside.
prop diameter = into_diameter(radius);
// Local variable `r`
r = radius;
// Create a circle.
std::geo2d::Circle(r);
}
use std::debug::*;
// Call sketch with diameter.
d = Wheel(diameter = 2cm);
// Check radius property.
assert_eq([d.radius, 1cm]);
// Call sketch with radius.
r = Wheel(radius = 2cm);
// Check diameter property.
assert_eq([r.diameter, 4cm]);
d - r;
Building Plan
The building plan is defined by a parameter list that follows the workbench’s identifier. All parameters in that list become properties of the workbench when it is invoked. These properties can be accessed within the building code, inside functions, or externally.
// sketch with a `radius` as building plan
sketch Wheel(radius: Length) {
// access property `radius` from the building plan
std::geo2d::Circle(radius);
}
std::debug::assert_eq([Wheel(5cm).radius, 5cm]);
Initializers
Initializers are defined with the keyword init and a following parameter list.
One may define multiple initializers which must have different parameter lists.
sketch Wheel(radius: Length) {
init( radius: Length ) {} // error: same parameters as in building plan
std::geo2d::Circle(1mm);
}
Wheel(radius = 1.0mm);
However, if an initializer is used, all properties from the building plan must be initialized (except those with default values).
sketch Wheel(radius: Length, thickness: Length) {
// initializer with diameter
init( diameter: Length, thickness: Length ) {
// must set `radius` in code
radius = diameter / 2;
// thickness (from the building plan) does not need
// to be set, because it was automatically set by
// parameter of this initializer
}
// Now radius and thickness can be used
std::geo2d::Circle(radius) - std::geo2d::Circle(radius = radius - thickness);
}
// call with building plan
Wheel(radius=5cm, thickness=1cm);
// call with initializer
Wheel(diameter=10cm, thickness=1cm);
If the building plan is not fully initialized by an initializer you will get an error:
sketch Wheel(radius: Length) {
init( width: Length ) { _ = width; } // error: misses to set `radius` from building plan
}
Wheel(width = 1.0mm);
Init Code
If you use initializers you might write some init code which must be placed on top of the workbench’s body (before any initializers).
The init code is just allowed to define some constants which then can be used in all following code (including code within initializers and functions).
sketch Wheel(radius: Length) {
// init code
const FACTOR = 2.0;
// initializer
init(diameter: Length) { radius = into_radius(diameter); }
// function
fn into_radius( diameter: Length ) {
// use constant FACTOR from init code
return diameter / FACTOR;
}
// set property diameter and use FACTOR from init code
prop diameter = radius * FACTOR;
// code body
std::geo2d::Circle(radius);
}
__builtin::debug::assert(Wheel(radius = 5cm).radius == 5cm);
__builtin::debug::assert(Wheel(radius = 5cm).diameter == 10cm);
__builtin::debug::assert(Wheel(diameter = 10cm).diameter == 10cm);
Init Code Rules
It’s not allowed to write any code between initializers.
sketch Wheel(radius: Length) {
init( width:Length ) { radius = width / 2; }
radius = 1; // error: code between initializers not allowed
init( height:Length ) { radius = height / 2; }
}
Wheel(radius = 1.0mm);
Building Code
The building code is executed after any initialization. Usually it produces one or many 2D or 3D objects on base of the given building plan.
sketch Wheel(radius: Length) {
// building code starts here
std::geo2d::Circle(radius);
}
Wheel(radius = 1.0mm)
If initializers were defined the building code starts below them.
sketch Wheel(radius: Length) {
// initializer
init( diameter: Length ) { radius = diameter / 2; }
// building code starts here
std::geo2d::Circle(radius);
}
Building Code Rules
It’s not allowed to use the sketch, part, op, return nor mod statements within workbench code:
sketch Wheel(radius: Length) {
sketch Axis(length: Length) {} // error
std::geo2d::Circle(radius);
}
Wheel(radius = 1.0mm);
Properties
There are two ways to declare Properties:
- as parameter of the building plan or
- in an assignment within the build code by using the keyword
prop.
In the following example we declare a building plan which consists of a radius which will automatically be a property:
// `outer` will automatically become a property because
// it is declared in the building plan:
sketch Wheel(outer: Length) {
use std::geo2d::Circle;
// `inner` is declared as property and maybe read from
// outside this workbench
prop inner = outer / 2;
// generate wheel (and use property inner)
Circle(r = outer) - Circle(r = inner);
}
// evaluate wheel
t = Wheel(1cm);
// extract and display `outer` and `inner` from generated wheel
std::print("outer: {t.outer}");
std::print("inner: {t.inner}");
If you remove the prop keyword you will fail at accessing inner:
sketch Wheel(outer: Length) {
use std::geo2d::Circle;
// `inner` is declared as variable and may not be read
// from outside this workbench
inner = outer / 2;
Circle(r = outer) - Circle(r = inner);
}
t = Wheel(1cm);
// you can still extract and display `outer`
std::print("outer: {t.outer}");
// but you cannot access `inner` anymore
std::print("inner: {t.inner}"); // error
Sketches
Sketches are similar to parts but in two dimensions only. They can be extruded into three-dimensional parts.
use std::geo2d::*;
sketch MySketch( radius: Length) {
Circle(radius) - Rect(size = radius);
}
MySketch(1cm);
The output is a 2D sketch:
If you generate a 3D model within a sketch you will get an error:
use std::geo3d::*;
sketch MySketch( radius: Length) {
Sphere(radius) - Cube(size = radius); // error
}
MySketch(1cm);
Parts
Parts are workbenches that are used to create 3D objects. If you want to create 2D objects, use sketches.
part MyPart() {}
Operations
Operations process 2D or 3D geometries into 2D or 3D geometries.
Actual operations are workbenches that process or transform their child object nodes to generate a new geometry.
So this would be a neutral operation:
// define operation nop without parameters
op nop() { @input }
// use operation nop on a circle
std::geo2d::Circle(radius = 1cm).nop();
@input
@input is a placeholder to tell where child nodes are nested.
It can also be used to retrieve information about the tree structure
In the above example @input will result in a std::geo2d::Circle(radius = 1cm).
An operation can have multiple children like in this example:
// define operation which takes multiple items
op punched_disk() {
// check number of children
if @input.count() == 2 {
// make hole
@input.subtract();
} else {
std::error("punched_disk must get two objects");
}
}
// use operation punch_disk with two circles
{
std::geo2d::Circle(radius = 1cm);
std::geo2d::Circle(radius = 2cm);
}.punched_disk();
Like other workbenches operations can have parameters too:
// define operation which takes multiple items
op punch_disk(radius: Length) {
if @input.count() == 1 {
{
@input;
std::geo2d::Circle(radius);
}.subtract();
} else {
std::error("punch_disk must get one object");
}
}
// use operation punch_disk on a circle
{
std::geo2d::Circle(radius = 2cm);
}.punch_disk(radius = 1cm);
Expressions
An expression defines a value by combining multiple other expressions. The simplest form of an expression are literals:
5; // Scalar literal
4.0mm; // Quantity literal
"Hello"; // String literal
However — as mentioned before — literals can be combined into larger
expressions.
For example, we can multiply the value 5 by the quantity 4.0mm:
5 * 4.0mm;
The result of this expression would be 20mm.
In this form, the computed value is not yet used for anything, so the examples
above will not produce any visible output or effect.
Things change when an expression generates a model instead of just a value. We call this a model expression:
std::geo2d::Rect(1cm);
This too is an expression — one that contains an element, specifically a
call to Rect, which draws a rectangle
that will actually be rendered into this:
If statements can also be used as an expression, evaluating to the value from the chosen branch.
use std::ops::*;
use std::math::*;
use std::geo2d::*;
sketch MySketch(a: Integer) {
outer = if a == 1 {
Circle(r = 1cm)
} else if a == 2 {
Rect(1cm)
} else {
Hexagon(1cm)
};
outer - Circle(r = 3mm)
}
MySketch([1,2,3]).align(X, 1cm);
Literals
Literals are the simplest form of expressions. In this section, we will look at some literals.
Integer Literals
Integer literals contain a whole number with a sign (but without a unit). Here are a few examples:
50;
1350;
-6
Scalar Literals
Scalar literals contain a floating point number with a sign (but without a unit).
1.23;
0.3252;
.4534;
1.;
-1200.0;
12.0E+12;
50% // = 0.5
Boolean Literals
Booleans contain either the value true or false:
true;
false
String Literals
Strings are texts enclosed in quotation marks:
"Hello, World!"
Quantity Literals
Quantities are like scalars but with a unit and are widely used in microcad if you wanna draw something.
4.0mm; // 4 millimeters
5l; // 5 liters
4m²; // 4 square meters
4m2; // also 4 square meters
10°; // 10 degree angle
10deg // also 10 degree angle
You will find more details about quantities and units in this section.
Operators
There are several operators which can be used to combine expressions with each other:
| Operator | Type | Input types | Result Type | Description |
|---|---|---|---|---|
- | unary | Integer, Scalar, Array | Integer, Scalar, Array | Negation |
+ | binary | Integer, Scalar, Array | Integer, Scalar, Array | Addition |
- | binary | Integer, Scalar, Array | Integer, Scalar, Array | Subtraction |
* | binary | Integer, Scalar, Array | Integer, Scalar, Array | Multiplication |
/ | binary | Integer, Scalar, Array | Integer, Scalar, Array | Division |
^ | binary | Integer, Scalar | Integer, Scalar | Power |
& | binary | Boolean | Boolean | Logical AND |
| | binary | Boolean | Boolean | Logical OR |
> | binary | Integer, Scalar | Boolean | Greater than |
>= | binary | Integer, Scalar | Boolean | Greater or equal |
< | binary | Integer, Scalar | Boolean | Less than |
<= | binary | Integer, Scalar | Boolean | Less or equal |
== | binary | Integer, Scalar | Boolean | Equal |
!= | binary | Integer, Scalar | Boolean | Not equal |
Here are some examples of each operator:
use std::debug::assert; // used for testing
-5; // Negation
assert( 5 + 6 == 11 ); // Addition
assert( 5 - 6 == -1 ); // Subtraction
assert( 5 * 6 == 30 ); // Multiplication
assert( 5 / 6 == 0.83333333333333333 ); // Division
assert( 5 ^ 6 == 15625 ); // Power
assert( true & false == false ); // Logical AND
assert( true | false == true ); // Logical OR
assert( 5 > 6 == false ); // Greater than
assert( 5 >= 6 == false ); // Greater or equal
assert( 5 < 6 == true ); // Less than
assert( 5 <= 6 == true ); // Less or equal
assert( 5 == 6 == false ); // Equal
assert( 5 != 6 == true ); // Not equal
Operators & Arrays
Some of the operators listed above can be used with arrays too. There result then is a new array with each value processed with the operator and the second operand.
use std::debug::assert; // used for testing
assert( -[1, 2, 3, 4] == [-1, -2, -3, -4] ); // Negation
assert( [1, 2, 3, 4] + 5 == [6, 7, 8, 9] ); // Addition
assert( [1, 2, 3, 4] - 5 == [-4, -3, -2, -1] ); // Subtraction
assert( [1, 2, 3, 4] * 5 == [5, 10, 15, 20] ); // Multiplication
assert( [1.0, 2.0, 3.0, 4.0] / 5 == [0.2, 0.4, 0.6, 0.8] ); // Division
Assignments
Assignments
Whenever you use a more complex expression, it is often worthwhile to store it in a variable so that it can be used once or multiple times elsewhere. In µcad, variables are always immutable which means that once they are set, their value cannot be reset in the same context. Therefore, they differ from the variables known in other programming languages.
A value assignment stores a value by a name on the evaluation stack.
The following example defines the variable a which from then is a reserved
name within the scope in which it was defined.
a = 5;
b = a * 2;
std::debug::assert_eq([a,5]);
std::debug::assert_eq([b,10]);
Another assignment of a variable with the same name is not allowed.
a = 5; // warning: unused local
a = a * 2; // error: a already defined in this scope
Value assignments may only be used within code bodies (scopes) - and not within modules or in init code for example:
mod my_module {
a = 1; // error
}
sketch MySketch() {
a = 1; // error
init(_x : Scalar) {}
}
MySketch();
Prefix Underscore
If a value name starts with an underscore (like _this_name) that suppresses any warning about if it is not in use.
Constant Assignments
Unlike value assignments, constant assignments are not stored on the evaluation stack but in the symbol table. Constant assignments can be placed directly within a module — which is not allowed for value assignments — and they can also be declared as public.
const TEXT = "Hello";
mod my_module {
pub const TEXT = "Hello";
}
std::print(TEXT);
std::print(my_module::TEXT);
Additionally, constant assignments are permitted in the init code of a workbench, where value assignments are likewise prohibited.
sketch MySketch(text: String) {
const TEXT = "Hello";
init() {
text = TEXT;
}
std::print(text);
}
MySketch();
Using pub is not allowed in workbenches:
sketch MySketch() {
pub const TEXT = "Hello"; // error
std::print(TEXT);
}
MySketch();
Property Assignments
Program Flow
Microcad is a programming language designed for generating 2D and 3D models. It deliberately avoids common programming constructs such as loops or mutable variables, but instead offers an alternative concept we call Multiplicity.
Conditionals
Conditions lead to different executions paths for different cases.
If statements in workbenches
Inside a workbench block, an if statement can be used to select different shapes or constructions depending on input parameters.
use std::ops::*;
use std::math::*;
use std::geo2d::*;
sketch MySketch(a: Integer) {
if a == 1 {
Circle(r = 1cm)
} else {
Rect(s = 1cm)
}
}
MySketch([1,2]).align(X, 1cm);
Multiple conditions can be chained, allowing more than two alternatives:
use std::ops::*;
use std::math::*;
use std::geo2d::*;
sketch MySketch(a: Integer) {
if a == 1 {
Circle(r = 1cm)
} else if a == 2 {
Rect(s = 1cm)
} else {
Hexagon(r = 1cm)
}
}
MySketch([1,2,3]).align(X, 1cm);
If Statement for functions
fn f(x: Integer) {
if x == 5 or x == 4 {
std::print("match");
} else if x > 0 and x < 4 {
std::print("no match");
} else {
std::print("invalid");
}
}
f(5); // prints "match"
f(1); // prints "no match"
f(-1); // prints "invalid"
f(6); // prints "invalid"
Calls
Calling Functions
A call of a function consists of just the identifier and an argument list. and the result is a value:
// function definition
fn square(x: Scalar) { return x * x; }
// call function square with parameter 2 and store result in s
s = square(x = 2);
// check value
std::debug::assert_eq( [s, 4] );
Calling Workbenches
Workbenches can be called in the same way as functions except that the result is a object node.
// definition of a sketch workbench
sketch Square(size: Length) {
std::geo2d::Rect(size);
}
// call square with a size and store object node in s
s = Square(size=2cm);
// translate object s
s.std::ops::translate(x = 1cm);
Calling Operations
Operations are called differently.
Parameters
Arguments
Function Calls
A function is simply called by appending a parameter list to it’s name:
// function definition
fn f() {}
// function call
f();
// function definition
fn f() {
// return statement
return 1;
}
// function call (and result check)
std::debug::assert_eq([ f(), 1 ]);
// function definition with parameter
fn f(n: Integer) {
return n * 2;
}
// function calls with parameter (and result checks)
std::debug::assert_eq([ f(1), 2 ]);
std::debug::assert_eq([ f(2), 4 ]);
Workbench Calls
Method Calls
Argument Matching
To match call arguments with function or workbench parameters, µcad employs a process known as argument matching.
Important
Parameters in µcad are not positional (which means their order is irrelevant)!
Instead of having so-called positional arguments, µcad has named arguments, which means
that every parameter and every argument must have an identifier.
Like x is in the following example:
fn f(x: Length) -> Length { x*2 }
std::debug::assert_eq([ f( x = 10m ), 20m ]);
Fortunately there are some facilities for your convenience, like:
All those are created to feel natural when using them together. If some explanations in the following sections may sound complicated, you might just go with the examples and “get the feeling”.
Match Priorities
A single parameter can match an argument in several ways, each with a defined priority. These priorities become important when calling workbenches which support overloaded initialization.
| Priority ⭣ high to low | Matches | Example Parameters | Example Arguments |
|---|---|---|---|
| Empty List | Empty arguments with empty parameters | () | () |
| Identifier | Match argument identifier with parameter identifier | (x: Scalar) | (x=1) |
| Shortened Identifier | Match argument identifier with shortened parameter identifier | (max_height: Scalar) | (m_h=1) |
| Type | Match argument type with parameter type | (x: Length) | (1mm) |
| Compatible Type | Match argument type with compatible parameter type | (x: Scalar) | f(1) |
| Default | Match parameter defaults | (x=1mm) | () |
Match Empty List
Matches when both the arguments and parameters are empty.
fn f() {} // no parameters
f(); // no arguments
Match Identifier
The following example demonstrates calling a function f with each argument specified by name:
fn f( width: Length, height: Length ) -> Area { width * height }
x = f(height = 2cm, width = 1cm); // call f() with parameters in arbitrary order
std::debug::assert_eq([ x, 2cm² ]);
Match Short Identifier
A parameter can also be matched using it’s short identifier.
The short form consists of the first letter of each word separated by underscores (_).
fn f( width: Length, height: Length ) -> Area { width * height }
// use short identifiers
std::debug::assert_eq([ f(w = 1cm, h = 2cm), 2cm² ]);
// can be mixed
std::debug::assert_eq([ f(w = 1cm, height = 2cm), 2cm² ]);
Here are some usual examples of short identifiers:
| Identifier | Short Identifier |
|---|---|
parameter | p |
my_parameter | m_p |
my_very_long_parameter_name | m_v_l_p_n |
my_Parameter | m_P |
MyParameter | M |
myParameter | m |
Match Type
Nameless values can be used if all parameter types of the called function (or workbench) are distinct.
fn f( a: Scalar, b: Length, c: Area ) {} // warning: unused a,b,c
// Who needs names?
f(1.0, 2cm, 3cm²);
Match Compatible Type
Nameless arguments can also be compatible with parameter types, even if they are not identical.
fn f( a: Scalar, b: Length, c: Area ) {} // warning: unused a,b,c
// giving an integer `1` to a `Scalar` parameter `a`
f(1, 2cm, 3cm²);
Match Default
If an argument is not provided and its parameter has a default value defined, the default will be used.
fn f( a = 1mm ) {} // warning: unused a
// a has default
f();
Mix’em all
You can combine all these methods.
fn f( a: Scalar, b: Length, c=2cm, d: Area ) -> Volume { } // warning: unused a,b,c,d
// `a` is the only Scalar and `b` is named, so `c` and `d` do not need a name.
f(1, b=2cm, 2cm², 3cm);
Inline Identifiers
In some cases, the parameter name is already included in the argument expression. If there is only one (or multiple identical) identifier(s) within an expression and it matches a parameter of the same type, that parameter will be automatically matched.
fn f(x: Integer, y: Integer) -> Integer { x*y }
x = 1;
f(x, y=2); // matches because argument `x` matches the name of parameter `x`
Even when using a more complex expression a unique identifier can match an argument:
fn f(x: Integer, y: Integer) -> Integer { x*y }
x = 1;
y = 2;
f(x * 2, y * y); // matches because `x` and `y` match parameter names `x` and `y`
fn f(x: Integer, y: Integer) -> Integer { x*y }
x = 1;
y = 2;
f(x * y, y * x); // error: cannot be matched because arguments are not unique.
Tuple Argument Matching
The concept behind named tuple argument matching is to allow functions to accept subsets of parameters in a bundle. This makes it easy to pre-configure parts of arguments:
// Function with three parameters: x, y, and z
fn f( x: Length, y: Length, z: Length ) {}
// Since we do not want to change x and y in the following statements,
// we prepare a tuple named plane:
plane = (x=1cm, y=2cm);
// Then we pass plane to f() three times with different z values
f( plane, z=3cm);
f( plane, z=6cm);
f( plane, z=9cm);
The same function can be called in various ways using named tuples:
fn f( x: Length, y: Length, z: Length ) {}
// Every parameter given by name
f( x=1cm, y=2cm, z=3cm);
// Parameters given by named tuple
f( (x=1cm, y=2cm, z=3cm) );
// Parameters given by named tuple variable
p = (x=1cm, y=2cm, z=3cm);
f( p );
// Parameters given by named tuple and a single value
f( (x=1cm, y=2cm), z=3cm );
f( y=2cm, (x=1cm, z=3cm) );
// Parameters given by named tuple variable and a single value
q = (x=1cm, y=2cm);
f( q, z=3cm );
As you can see, the possibilities are endless.
Multiplicity
A core concept of µcad is called Argument Multiplicity which replace loops like they are known from other languages.
When working with multiplicities, each argument can be provided as an array of elements of a parameter’s type. Each list element will then be evaluated for each of the array’s values. This way, we can intuitively express a call that is executed for each argument variant.
The following example will produce 4 rectangles at different positions:
r = std::geo2d::Rect(width = 2mm, height = 2mm);
r.std::ops::translate(x = [-4mm, 4mm], y = [-4mm, 4mm]);
Because in the above example x and y have two values each, the result
are four (2×2) calls:
r = std::geo2d::Rect(width = 2mm, height = 2mm);
use std::ops::translate;
r.translate(x = -4mm, y = -4mm);
r.translate(x = -4mm, y = 4mm);
r.translate(x = 4mm, y = -4mm);
r.translate(x = 4mm, y = 4mm);
Normally, this would require 2 nested for loops, which are not available in µcad.
Another example uses an array of tuples and produces the same output:
r = std::geo2d::Rect(width = 2mm, height = 2mm);
r.std::ops::translate([(x=-4mm, y=-4mm), (x=-4mm, y=4mm), (x=4mm, y=-4mm), (x=4mm, y=4mm)]);
Matching Errors
If you do not provide all parameters, you will get an error:
fn f( x: Length, y: Length, z: Length ) {}
f( x=1cm, z=3cm); // error: y is missing here
When you provide all parameters but some are redundant, you will get a error too:
fn f( x: Length, y: Length, z: Length ) {}
f( x=1cm, y=2cm, v=5cm, z=3cm); // error: Unexpected argument v
Types
The µcad type system consists of a group built-in types. The type system is static, which means a declared variable has a fixed type that cannot be changed.
These classes of built-in types are supported:
| Built-in type | Description | Example |
|---|---|---|
| Quantity | Numeric values with an optional unit. | a: Length = 4mm |
| Bool | A boolean value. | b: Bool = true |
| Integer | An integer value without a unit. | c: Integer = 4 |
| String | A string. | d: String = "Hello World" |
| Matrix | Matrix types for affine transforms, for internal usage. | — |
| Array | A list of values with a common type. | e: [Integer] = [1,2,3] |
| Tuple | A list of values with a distinct types. | f: (Length, Scalar) = (4mm, 4.0) |
| Named tuple | A sorted list of key-value pairs with distinct types. | g: (x: Scalar, y: Length) = (x = 4.0, y = 4mm) |
| Models | Nodes in the model tree. | h: Models = { Cube(2mm); } |
Declaration
The examples in the table above declare the type explicitly. However, we can use units to declare the implicitly. Using units is recommended and what you get in return is that declarations are quite handy:
x: Length = 4mm; // explicit type declaration
y = 4mm; // implicit type declaration via units.
Declarations without any initializations are not allowed in µcad. Hence, the following example will fail:
x: Length; // parse_error
However, for parameter lists in functions and workbenches, you can declare the type only but also pass a default value:
fn f(x = 4mm) {} // use unit (with default)
fn f(x: Length) {} // use type (without default)
Collections
Collections put many items into one type:
- Arrays store multiple values of the same type
- Tuples can store multiple values of different types which may or may not have names.
Arrays
An array is an ordered collection of values with a common type.
Arrays as list: [1, 2, 3]
[
// First element
1,
// Second element
2
];
You can count the number of elements in an array using std::count:
std::debug::assert_eq([std::count([1,2,3]), 3]);
You can get the head and tail of an array using std::head and std::tail:
std::debug::assert_eq([std::head([1,2,3]), 1]);
std::debug::assert_eq([std::tail([1,2,3]), [2,3]]);
Arrays as range: [1..5]
You can generate an array via range expressions: [1..5].
A µcad range includes both of it’s end points.
std::debug::assert_eq([[1..5], [1,2,3,4,5]]);
std::debug::assert_eq([[-2..2], [-2,-1,0,1,2]]);
The order of the endpoints of a range is important:
[6..1]; // error
[2..-2]; // error
Only Integer can be used as endpoint:
[1.0..2.0]; // parse_error
Array operations
Unit bundling
Array support unit bundling, which means the you can write the unit after the [] brackets.
std::debug::assert_eq([ [1mm, 2mm, 3mm], [1, 2, 3]mm ]);
Addition +
Adding a quantity
std::debug::assert_eq([ [1,2]mm + 2mm, [3,4]mm ]);
Subtraction -
Subtracting a quantity
std::debug::assert_eq([ [1,2]mm - 2mm, [-1,0]mm ]);
Multiplication *
Scaling an array
std::debug::assert_eq([ [-0.5mm,0.5mm]*2, [-1,1]mm ]);
Division /
Dividing an array by a value
std::debug::assert_eq([ [-1.0mm,1.0mm]/2, [-0.5, 0.5]mm ]);
Negation -
std::debug::assert_eq([ [-1.0mm,1.0mm], -[1.0mm, -1.0mm] ]);
Tuples
A tuple is a collection of values, each of which can be of different types. Typically, each value is paired with an identifier, allowing a tuple to function similarly to a key-value store.
use std::debug::assert_eq;
tuple = (width=10cm, depth=10cm, volume=1l);
assert_eq([tuple.width, 10cm]);
assert_eq([tuple.depth, 10cm]);
assert_eq([tuple.volume, 1l]);
assert_eq([tuple, (width=10cm, depth=10cm, volume=1l)]);
Partially Unnamed Tuples
A tuple may lack identifiers for some or even all of its values. In such cases, these unnamed values within the tuple must all be of different types.
tuple = (10cm, 10cm², 1l);
Otherwise, they would be indistinguishable since the values in a tuple do not adhere to a specific order.
// these tuples are equal
std::debug::assert_eq([(1l, 10cm, 10cm²), (10cm, 10cm², 1l)]);
A partially or fully unnamed tuple can only be utilized through argument matching or tuple assignment.
Tuple Assignments
Tuple syntax is also employed on the left side of tuple assignments.
(width, height) = (1m,2m);
// check values of width and height
assert_eq([width,1m]);
assert_eq([height,2m]);
Occasionally, it’s practical to group units together, but this cannot be done directly with a tuple. Instead, you must use an array, which will be converted into a tuple during the assignment.
(width, height) = [1,2]m;
assert_eq([width,1m]);
assert_eq([height,2m]);
This method can generally be used to convert an array into a tuple:
array = [1,2]m;
(width, height) = array;
tuple = (width, height);
assert_eq([tuple,(1m,2m)]);
assert_eq([tuple.width,1m]);
assert_eq([tuple.height,2m]);
Tuple operators
Addition *
Adding two tuples of the same type
a = (x = 1.0mm, y = 2.0mm);
b = (x = 3.0mm, y = 4.0mm);
std::debug::assert_eq([a + b, (x = 4.0mm, y = 6.0mm)]);
a = (x = 1.0mm, y = 2.0mm);
b = (x = 3.0mm, z = 4.0mm);
c = a + b; // error: Tuple type mismatch for +: lhs=(x: Length, y: Length), rhs=(x: Length, z: Length)
std::debug::assert_eq([c, (x = 4.0mm, y = 6.0mm)]); // error: Array elements have different types: [<INVALID TYPE>, (x: Length, y: Length)]
Subtraction -
Subtracting a quantity
a = (x = 2.0mm, y = 3.0mm);
b = (x = 1.0mm, y = 4.0mm);
std::debug::assert_eq([a - b, (x = 1.0mm, y = -1.0mm)]);
Multiplication *
Scaling a tuple
v = (x = 1.0mm, y = 2.0mm);
std::debug::assert_eq([v*2, (x = 2.0mm, y = 4.0mm)]);
Division /
Dividing a tuple by a value
v = (x = 1.0mm, y = 2.0mm);
std::debug::assert_eq([v/2, (x = 0.5mm, y = 1.0mm)]);
Negation -
v = (x = 1.0mm, y = 2.0mm);
std::debug::assert_eq([-v, (x = -1.0mm, y = -2.0mm)]);
Type aliases
Some named tuples are used frequently and thus they have aliases: Color, Vec2 and Vec3.
This feature is also called named-tuple duck-typing.
Color
A named tuple with the fields r, g, b and a will be treated as a color with red, green, blue and alpha channel.
You can use the type alias Color to deal with color values.
color: Color = (r = 100%, g = 50%, b = 25%, a = 100%);
std::debug::assert(color.r == 100%);
Vec2
A named tuple with the fields x and y will be treated as a two-dimensional vector.
You can use the type alias Vec2.
v: Vec2 = (x = 2.0, y = 3.0);
std::debug::assert(v.x + v.y == 5.0);
Vec3
A named tuple with the fields x, y and z will be treated as a three-dimensional vector.
You can use the type alias Vec3.
v: Vec3 = (x = 2.0, y = 3.0, z = 4.0);
std::debug::assert(v.x + v.y + v.z == 9.0);
Quantities
Quantities are numeric values coupled with a unit. Each unit refers to example one quantity type.
The following quantity types are supported:
| Type | Metric Units | Imperial Units |
|---|---|---|
Scalar | -, % | - |
Length | µm, mm, cm, m | in or ", ft or ', yd |
Angle | ° or deg, grad, turn,rad | |
Area | µm²,mm²,cm²,m³ | in², ft² , yd² |
Volume | µm³, mm³,cm³,m³,ml,cl,l, µl | in³, ft³ , yd³ |
Density | g/mm³ | - |
Weight | g, kg | lb, oz |
Note: More units may be implemented.
Literals
Quantities can be declared by literals. This means that your will automatically get the following type if you use the beside units:
// declare variable `height` of type `Length` to 1.4 Meters
height = 1.4m;
// use as *default* value in parameter list
fn f( height = 1m ) {}
// calculate a `Length` called `width` by multiplying the
// `height` with `Scalar` `2` and add ten centimeters
width = height * 2 + 10cm;
Examples
Scalar
The type Scalar contains a floating number (without any unit) and must be written with at least one decimal place (or in percent).
zero = 0;
pi = 3.1415;
percent = 55%;
Length
Length is used to describe a concrete length.
millimeters = 1000mm;
centimeters = 100cm;
meters = 1m;
inches = 39.37007874015748in;
std::debug::assert( [millimeters, centimeters, meters, inches].all_equal() );
Angle
Angles are used with rotations and in constrains when proving measures.
pi = std::math::PI;
radian = 1rad * pi;
degree = 180°;
degree_ = 180deg;
grad = 200grad;
turn = 0.5turns;
std::debug::assert( [degree, degree_, grad, turn, radian].all_equal() );
Area
An Area is a two-dimensional quantity. It is the result when multiplying two Lengths.
a = 3cm;
b = 2cm;
area = a * b;
std::debug::assert(area == 6cm²);
Here is an example of how to use different areal units:
square_millimeter = 100000mm²;
square_centimeter = 1000cm²;
square_meter = 0.1m²;
square_inch = 155in²;
std::debug::assert(square_millimeter == 0.1m²);
std::debug::assert(square_centimeter == 0.1m²);
Volume
A Volume is a three-dimensional quantity. It is the result when multiplying three Lengths.
a = 3mm;
b = 2mm;
c = 4mm;
volume = a * b * c;
std::debug::assert(volume == 24mm³);
Here is an example for units:
cubic_millimeter = 1000000.0mm³;
cubic_centimeter = 100.0cl;
cubic_meter = 0.001m³;
cubic_inch = 61.0237in³;
liter = 1.0l;
centiliter = 100.0cl;
milliliter = 1000.0ml;
std::debug::assert(cubic_millimeter == 1.0l);
std::debug::assert(cubic_centimeter == 1.0l);
std::debug::assert(cubic_meter == 1.0l);
std::debug::assert(centiliter == 1.0l);
std::debug::assert(milliliter == 1.0l);
Density
TODO
Weight
Weights can be calculated by applying volumes to materials.
gram = 1000.0g;
kilogram = 1.0kg;
pound = 2.204623lb;
std::debug::assert([gram, kilogram].all_equal());
Arithmetic
Quantity types can use operators:
use std::debug::assert;
a = 2.0mm;
assert([a, 2mm, 2.0mm].all_equal());
b = 2 * a;
assert([b, 4mm, 4.0mm].all_equal());
c = a / 2;
assert([c, 1mm, 1.0mm].all_equal());
d = a + 2.0mm;
assert([d, 4mm, 4.0mm].all_equal());
e = a - 2.0mm;
assert([e, 0mm, 0.0mm].all_equal());
Primitive Types
Bool
Boolean is the result type of boolean expressions which may just be true or false.
std::debug::assert(true != false);
Boolean values can be combined with or and and operators:
std::debug::assert_eq([true or false, true]);
std::debug::assert_eq([true and false, false]);
std::debug::assert_eq([4 == 4, true]);
std::debug::assert_eq([4 == 5, false]);
std::debug::assert_eq([4 == 5 or 4 == 4, true]);
std::debug::assert_eq([4 == 5 and 4 == 4, false]);
Integer
The type integer contains a natural number.
i = 3;
String
Text can be used to logging or to render text.
text = "Hello µcad!";
std::debug::assert_eq([std::count(text), 11]);
// logging
std::print(text);
Matrix
Matrix types are built-in types and can be defined as:
Matrix2: A 2x2 matrix, commonly used for 2D rotations.Matrix3: A 3x3 matrix, commonly used for rotations.Matrix4: A 4x4 matrix, commonly used for affine transformations.
Note: Currently, matrices are used only internally and cannot be instantiated from µcad code.
Models
Builtin Measures
Measures are builtin methods you can use with 2D or 3D objects.
The following example calculates the area of a circle by using the measure area:
__builtin::debug::assert_eq([
// use measure area() on a circle
std::geo2d::Circle(radius=10mm).area(),
// circle area formula for comparison
10mm * 10mm * std::math::PI
]);
Currently it is not possible to declare measures in µcad.
2D Measures
Builtin measures that can be used on 2D objects.
| Measure | Output Quantity | Description |
|---|---|---|
area(..) | Area | area |
circum(..) | Length | circumference |
center(..) | (x:Length, y:Length) | geometric center |
size(..) | (width:Length, height:Length) | extents |
bounds(..) | (left:Length, right:Length, top:Length, bottom:Length) | bound (from center) |
3D Measures
Builtin measures that can be used on 3D objects.
| Measure | Output Quantity | Description |
|---|---|---|
area(..) | Area | surface area |
center(..) | (x:Length, y:Length, z:Length) | geometric center |
size(..) | (depth:Length, width:Length, height=Length) | extents |
bounds(..) | (front: Length, back: Length, left:Length, right:Length, top:Length, bottom:Length) | bound (from center) |
volume(..) | Volume | volume |
Documentation
Attributes
Attributes are syntax elements that can be used to attach arbitrary data to model nodes.
The attributes will not change the model geometry itself, but might change its appearance when if they are used for viewers or exporters. There can be multiple attributes for a node, but each attribute needs to have a unique ID.
Simple example
Let’s define a model node c.
When viewed or exported, model node c will have a red color, because the color attribute will be set:
#[color = "#FF0000"]
c = std::geo2d::Circle(r = 42.0mm);
std::debug::assert_eq([c#color, (r = 1.0, g = 0.0, b = 0.0, a = 1.0)]);
Syntax
Syntactically, an attribute consists of # prefix and an item.
-
Name-value pair:
#[color = "#FF00FF"],#[resolution = 200%]. Store and retrieve arbitrary values. The name has to be unique. -
Calls:
#[svg("style = fill:none")]. Control the output for a specific exporter. -
Commands:
#[export = "test.svg"],#[measure = width, height]. A certain command to execute on a model, e.g. for export and measurement. Multiple commands are possible.
Color attribute
The color attribute attaches a color to a model node.
In viewer and when exported, the model will be drawn in the specified color.
#[color = "#FFFFFF"]
c = std::geo2d::Circle(r = 42.0mm);
std::debug::assert_eq([c#color, (r = 1.0, g = 1.0, b = 1.0, a = 1.0)]);
Resolution attribute
The resolution attribute sets the rendering resolution of the model.
The model will be rendered in with 200% resolution than the default resolution of 0.1mm.
This means the circle will be rendered with a resolution 0.05mm.
#[resolution = 200%]
c = std::geo2d::Circle(r = 42.0mm);
std::debug::assert_eq([c#resolution, 200%]);
Export command
If you have created a part or a sketch and want to export it to a specific file, you can add an export command:
#[export = "circle.svg"]
c = std::geo2d::Circle(r = 42.0mm);
The exporter is detected automatically depending on the file extension.
See export for more information.
Export nodes
When a µcad file is processed by the interpreter, you can export the resulting nodes in a specific file format.
Export via CLI
To export a µcad file via the CLI, the most simple form is:
µcad export myfile.µcad # -> myfile.svg
You can also give an optional custom name with a specific format:
microcad-cli export myfile.µcad custom_name.svg # -> custom_name.svg
Default formats
The output format depends on the kind of node:
- SVG is the default format for sketches.
- STL is the default format for parts.
- If the content is mixed, there will be an error, unless you mark different nodes with export attributes (see next section).
If you call the command line:
microcad-cli export my_sketch.µcad # This is a sketch and will output `my_sketch.svg`
microcad-cli export my_part.µcad # This is a part and will output `my_part.stl`
Export specific nodes via attributes
Assuming, you have two sketches and want to export each in a specific file. You assign an export attribute with a filename to each sketch. If you omit the file extension, the default export format will be picked automatically.
#[export = "rect.svg"] // Will be exported to `rect.svg`
std::geo2d::Rect(42mm);
#[export = "circle.svg"] // Will be exported to `circle.svg`
std::geo2d::Circle(r = 42mm);
In the CLI, you can select the node specifically:
microcad-cli export myfile.µcad --list # List all exports in this file: `rect, circle.svg`.
microcad-cli export myfile.µcad --target rect # Export rectangle node to `rect.svg`
Import data
Use can import data via std::import function.
Note: This WIP. Currently, only tuples from a TOML file can be imported.
TOML import
Assuming, you have the following data in a TOML file example.toml:
# file: example.toml
[M6]
diameter = 6.0
pitch = 1.0
[M10]
diameter = 10.0
pitch = 1.5
You can then load the TOML file by using std::import and access its values:
data = std::import("example.toml");
std::debug::assert_eq([data.M10.diameter, 10.0]);
std::debug::assert_eq([data.M6.pitch, 1.0]);
Included Libraries
Builtin Library
The Builtin library is the bridge between µcad and Rust and covers all tasks that are not supposed to be in the µcad language itself (like drawing things and more complex calculations). It handles the communications with compiler and the geometry backend during evaluation.
- 2D geometric primitives:
geo2d - 3D geometric primitives:
geo3d - geometric operations:
ops - mathematics:
math - debugging:
debug
Standard Library
The Standard Library is written in µcad and covers builtin functionality and adds more sophisticated interfaces to the core functionalities of the Builtin Library.
So generically spoken you will find all functionalities of __builtin within std but in a handier form.
Modules
The main module of the µcad standard library std consists of these modules which group different kinds of functionalities together:
geo2d: 2D parts (e.g.circle,rect)geo3d: 3D parts (e.g.sphere,cube)ops: Algorithms to manipulate 2D and 3D parts (e.g.translate,difference)math: Mathematical solutions (e.g.abs,pi)debug: Debugging functions
Functions
std::print()
Print is a alias to __builtin::print() which is printing stuff to the output console to be read by the user.
Plugins
(not yet available)
Debugging
Verification
µcad provides several builtin functions that help you to avoid bad input parameters.
Assert
Assertions define constrains of parameters or cases and they bring any rendering to fail immediately.
one form of assertion is a function which gets an expression.
If the expression computes to false a compile error will occur at
that point.
std::debug::assert(true, "You won't see this message");
std::debug::assert(false, "this assertion fails"); // error
Error
std::log::error("this should not have happened"); // error
Todo
todo() is like error() but aims at reminding you to finish code later.
a = 0;
if a == 0 {
std::log::info("a is zero");
} else {
std::log::todo("print proper message");
}
Appendix
Coding Style
Names
| Element | Example | Format |
|---|---|---|
| type | MyType | pascal case |
| const | const MY_CONST = 1 | upper case snake case |
| values | my_var = 1 | snake case |
| module | mod my_lib {} | snake case |
| function | fn my_func() {} | snake case |
| sketch | sketch MySketch() {} | pascal case |
| part | part MyPart () {} | pascal case |
| operation | op my_op() {} | snake case |
Test List
The following table lists all tests included in this documentation.
177 tests have been evaluated with version 0.2.20 of microcad.
Click on the test names to jump to file with the test or click the buttons to get the logs.