2025-05-02 03:56:27 +12:00
---
2025-05-12 12:59:45 +12:00
title: "Projects and modules"
2025-05-11 19:32:33 +12:00
excerpt: "Documentation of the KCL language for the Zoo Design Studio."
2025-05-02 03:56:27 +12:00
layout: manual
---
`KCL` allows splitting code up into multiple files. Each file is somewhat
isolated from other files as a separate module.
When you define a function, you can use `export` before it to make it available
to other modules.
```kcl
// util.kcl
2025-05-02 16:00:27 +12:00
export fn increment(@x ) {
2025-05-02 03:56:27 +12:00
return x + 1
}
```
Other files in the project can now import functions that have been exported.
This makes them available to use in another file.
```norun
// main.kcl
import increment from "util.kcl"
answer = increment(41)
```
Import statements must be at the top-level of a file. It is not allowed to have
an `import` statement inside a function or in the body of an if‑ else.
Multiple functions can be exported in a file.
```kcl
// util.kcl
2025-05-02 16:00:27 +12:00
export fn increment(@x ) {
2025-05-02 03:56:27 +12:00
return x + 1
}
2025-05-02 16:00:27 +12:00
export fn decrement(@x ) {
2025-05-02 03:56:27 +12:00
return x - 1
}
```
When importing, you can import multiple functions at once.
```norun
import increment, decrement from "util.kcl"
```
Imported symbols can be renamed for convenience or to avoid name collisions.
```norun
import increment as inc, decrement as dec from "util.kcl"
```
2025-05-20 18:13:17 +12:00
You can import files from the current directory or from subdirectories, but if importing from a
subdirectory you can only import `main.kcl` .
2025-05-02 03:56:27 +12:00
---
## Functions vs `clone`
There are two common patterns for re‑ using geometry:
1. **Wrap the construction in a function** – flexible and fully parametric.
2. **Duplicate an existing object with `clone`** – lightning‑ fast, but an exact
duplicate.
### Parametric function example
```kcl
fn cube(center) {
return startSketchOn(XY)
|> startProfile(at = [center[0] - 10, center[1] - 10])
|> line(endAbsolute = [center[0] + 10, center[1] - 10])
|> line(endAbsolute = [center[0] + 10, center[1] + 10])
|> line(endAbsolute = [center[0] - 10, center[1] + 10])
|> close()
|> extrude(length = 10)
}
2025-05-02 16:00:27 +12:00
myCube = cube(center = [0, 0])
2025-05-02 03:56:27 +12:00
```
*Pros*
- Any argument can be a parameter – size, position, appearance, etc.
- Works great inside loops, arrays, or optimisation sweeps.
*Cons*
- Every invocation rebuilds the entire feature tree.
- **Slower** than a straight duplicate – each call is its own render job.
### `clone` example
```kcl
sketch001 = startSketchOn(-XZ)
|> circle(center = [0, 0], radius = 10)
|> extrude(length = 5)
|> appearance(color = "#ff0000 ", metalness = 90, roughness = 90)
sketch002 = clone(sketch001) // ✓ instant copy
```
*Pros*
- Roughly an O(1) operation – we just duplicate the underlying engine handle.
- Perfect when you need ten identical bolts or two copies of the same imported STEP file.
*Cons*
- **Not parametric** – the clone is exactly the same shape as the source.
- If you need to tweak dimensions per‑ instance, you’ re back to a function.
> **Rule of thumb** – Reach for `clone` when the geometry is already what you want. Reach for a function when you need customisation.
---
## Module‑ level parallelism
Under the hood, the Design Studio runs **every module in parallel** where it can. This means:
- The top‑ level code of `foo.kcl` , `bar.kcl` , and `baz.kcl` all start executing immediately and concurrently.
- Imports that read foreign files (STEP/OBJ/…) overlap their I/O and background render.
- CPU‑ bound calculations in separate modules get their own worker threads.
### Why modules beat one‑ big‑ file
If you shoe‑ horn everything into `main.kcl` , each statement runs sequentially:
```norun
import "big.step" as gizmo // blocks main while reading
gizmo |> translate(x=50) // blocks again while waiting for render
```
Split `gizmo` into its own file and the read/render can overlap whatever else `main.kcl` is doing.
```norun
// gizmo.kcl (worker A)
import "big.step"
// main.kcl (worker B)
import "gizmo.kcl" as gizmo // non‑ blocking
// ... other setup ...
gizmo |> translate(x=50) // only blocks here
```
### Gotcha: defining but **not** calling functions
Defining a function inside a module is instantaneous – we just record the byte‑ code. The heavy lifting happens when the function is **called** . So:
```norun
// util.kcl
export fn makeBolt(size) { /* … expensive CAD … */ }
```
If `main.kcl` waits until the very end to call `makeBolt` , *none* of that work was parallelised – you’ ve pushed the cost back onto the serial tail of your script.
**Better:** call it early or move the invocation into another module.
```norun
// bolt_instance.kcl
import makeBolt from "util.kcl"
bolt = makeBolt(5) // executed in parallel
bolt
```
Now `main.kcl` can `import "bolt_instance.kcl" as bolt` and get the result that was rendered while it was busy doing other things.
---
## Whole module import
You can also import the whole module. This is useful if you want to use the
result of a module as a variable, like a part.
```norun
2025-05-19 16:50:15 +12:00
import "cube.kcl"
2025-05-02 03:56:27 +12:00
cube
|> translate(x=10)
```
This imports the whole module and makes it available as `cube` . You can then
use it like any other object. The `cube` variable is now a reference to the
result of the module. This means that if you change the module, the `cube`
variable will change as well.
In `cube.kcl` , you cannot have multiple objects. It has to be a single part. If
you have multiple objects, you will get an error. This is because the module is
expected to return a single object that can be used as a variable.
The last expression or variable definition becomes the module's return value.
The module is expected to return a single object that can be used as a variable
by whatever imports it.
So for example, this is allowed:
```norun
... a bunch of code to create cube and cube2 ...
myUnion = union([cube, cube2])
```
You can also do this:
```norun
... a bunch of code to create cube and cube2 ...
union([cube, cube2])
```
Either way, the last line will return the union of the two objects.
Or what you could do instead is:
```norun
... a bunch of code to create cube and cube2 ...
myUnion = union([cube, cube2])
myUnion
```
This will assign the union of the two objects to a variable, and then return it
on the last statement. It's simply another way of doing the same thing.
The final statement is what's important because it's the return value of the
entire module. The module is expected to return a single object that can be used
as a variable by the file that imports it.
2025-05-20 18:13:17 +12:00
The name of the file or subdirectory is used as the name of the variable within the importing program.
If you want to use a different name, you can do so by using the `as` keyword:
```kcl,norun
import "cube.kcl" // Introduces a new variable called `cube` .
import "cube.kcl" as block // Introduces a new variable called `block` .
import "cube/main.kcl" // Introduces a new variable called `cube` .
import "cube/main.kcl" as block // Introduces a new variable called `block` .
```
If the filename includes hyphens (`-` ) or starts with an underscore (`_` ), then you must specify a
variable name.
2025-05-02 03:56:27 +12:00
---
## Multiple instances of the same import
Whether you are importing a file from another CAD system or a KCL file, that
file represents object(s) in memory. If you import the same file multiple times,
it will only be rendered once.
If you want to have multiple instances of the same object, you can use the
[`clone` ](/docs/kcl/clone ) function. This will render a new instance of the object in memory.
```norun
2025-05-19 16:50:15 +12:00
import cube from "cube.kcl"
2025-05-02 03:56:27 +12:00
cube
|> translate(x=10)
clone(cube)
|> translate(x=20)
```
In the sample above, the `cube` object is imported from a KCL file. The first
instance is translated 10 units in the x direction. The second instance is
cloned and translated 20 units in the x direction. The two instances are now
separate objects in memory, and can be manipulated independently.
Here is an example with a file from another CAD system:
```kcl
2025-05-19 16:50:15 +12:00
import "tests/inputs/cube.step"
2025-05-02 03:56:27 +12:00
cube
|> translate(x=10)
clone(cube)
|> translate(x=20)
```