* start of parallelizing forign imports Signed-off-by: Jess Frazelle <github@jessfraz.com> * clippy Signed-off-by: Jess Frazelle <github@jessfraz.com> * remove_printlns Signed-off-by: Jess Frazelle <github@jessfraz.com> * remove shit that doesnt work Signed-off-by: Jess Frazelle <github@jessfraz.com> * remove shit that doesnt work Signed-off-by: Jess Frazelle <github@jessfraz.com> * put back Signed-off-by: Jess Frazelle <github@jessfraz.com> * put back Signed-off-by: Jess Frazelle <github@jessfraz.com> * put back Signed-off-by: Jess Frazelle <github@jessfraz.com> * multiple Signed-off-by: Jess Frazelle <github@jessfraz.com> * updates Signed-off-by: Jess Frazelle <github@jessfraz.com> * remove println Signed-off-by: Jess Frazelle <github@jessfraz.com> * updates Signed-off-by: Jess Frazelle <github@jessfraz.com> * clone docs Signed-off-by: Jess Frazelle <github@jessfraz.com> * add whole module import docs Signed-off-by: Jess Frazelle <github@jessfraz.com> * add whole module import docs Signed-off-by: Jess Frazelle <github@jessfraz.com> * updates Signed-off-by: Jess Frazelle <github@jessfraz.com> --------- Signed-off-by: Jess Frazelle <github@jessfraz.com>
		
			
				
	
	
		
			362 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
			
		
		
	
	
			362 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
---
 | 
						||
title: "KCL Modules"
 | 
						||
excerpt: "Documentation of modules for the KCL language for the Zoo Design Studio."
 | 
						||
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
 | 
						||
export fn increment(x) {
 | 
						||
  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)
 | 
						||
```
 | 
						||
 | 
						||
Imported files _must_ be in the same project so that units are uniform across
 | 
						||
modules. This means that it must be in the same directory.
 | 
						||
 | 
						||
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
 | 
						||
export fn increment(x) {
 | 
						||
  return x + 1
 | 
						||
}
 | 
						||
 | 
						||
export fn decrement(x) {
 | 
						||
  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"
 | 
						||
```
 | 
						||
 | 
						||
---
 | 
						||
 | 
						||
## 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)
 | 
						||
    |> startProfileAt([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)
 | 
						||
}
 | 
						||
 | 
						||
myCube = cube([0, 0])
 | 
						||
```
 | 
						||
 | 
						||
*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
 | 
						||
import "tests/inputs/cube.kcl" as cube
 | 
						||
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.
 | 
						||
 | 
						||
You also cannot assign that object to a variable. This is because the module is
 | 
						||
expected to return a single object that can be used as a variable.
 | 
						||
 | 
						||
So for example, this is not allowed:
 | 
						||
 | 
						||
```norun
 | 
						||
... a bunch of code to create cube and cube2 ...
 | 
						||
 | 
						||
myUnion = union([cube, cube2])
 | 
						||
```
 | 
						||
 | 
						||
What you need to do instead is:
 | 
						||
 | 
						||
```norun
 | 
						||
... a bunch of code to create cube and cube2 ...
 | 
						||
 | 
						||
union([cube, cube2])
 | 
						||
```
 | 
						||
 | 
						||
That 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 return the union of the two objects, but it will not be assigned to a
 | 
						||
variable. This is because the module is expected to return a single object that
 | 
						||
can be used as a variable.
 | 
						||
 | 
						||
---
 | 
						||
 | 
						||
## 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
 | 
						||
import cube from "tests/inputs/cube.kcl"
 | 
						||
 | 
						||
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
 | 
						||
import "tests/inputs/cube.step" as cube
 | 
						||
 | 
						||
cube
 | 
						||
  |> translate(x=10)
 | 
						||
clone(cube)
 | 
						||
  |> translate(x=20)
 | 
						||
```
 | 
						||
 | 
						||
---
 | 
						||
 | 
						||
## Importing files from other CAD systems
 | 
						||
 | 
						||
`import` can also be used to import files from other CAD systems. The format of the statement is the
 | 
						||
same as for KCL files. You can only import the whole file, not items from it. E.g.,
 | 
						||
 | 
						||
```norun
 | 
						||
import "tests/inputs/cube.obj"
 | 
						||
 | 
						||
// Use `cube` just like a KCL object.
 | 
						||
```
 | 
						||
 | 
						||
```kcl
 | 
						||
import "tests/inputs/cube.sldprt" as cube
 | 
						||
 | 
						||
// Use `cube` just like a KCL object.
 | 
						||
```
 | 
						||
 | 
						||
For formats lacking unit data (such as STL, OBJ, or PLY files), the default
 | 
						||
unit of measurement is millimeters. Alternatively you may specify the unit
 | 
						||
by using an attribute. Likewise, you can also specify a coordinate system. E.g.,
 | 
						||
 | 
						||
```kcl
 | 
						||
@(unitLength = ft, coords = opengl)
 | 
						||
import "tests/inputs/cube.obj"
 | 
						||
```
 | 
						||
 | 
						||
When importing a GLTF file, the bin file will be imported as well.
 | 
						||
 | 
						||
Import paths are relative to the current project directory. Imports currently only work when
 | 
						||
using the native Design Studio, not in the browser.
 | 
						||
 | 
						||
### Supported values
 | 
						||
 | 
						||
File formats: `fbx`, `gltf`/`glb`, `obj`+, `ply`+, `sldprt`, `step`/`stp`, `stl`+. (Those marked with a
 | 
						||
'+' support customising the length unit and coordinate system).
 | 
						||
 | 
						||
Length units: `mm` (the default), `cm`, `m`, `inch`, `ft`, `yd`.
 | 
						||
 | 
						||
Coordinate systems:
 | 
						||
 | 
						||
- `zoo` (the default), forward: -Y, up: +Z, handedness: right
 | 
						||
- `opengl`, forward: +Z, up: +Y, handedness: right
 | 
						||
- `vulkan`, forward: +Z, up: -Y, handedness: left
 | 
						||
 | 
						||
---
 | 
						||
 | 
						||
## Performance deep‑dive for foreign‑file imports
 | 
						||
 | 
						||
Parallelized foreign‑file imports now let you overlap file reads, initialization,
 | 
						||
and rendering. To maximize throughput, you need to understand the three distinct
 | 
						||
stages—reading, initializing (background render start), and invocation (blocking)
 | 
						||
—and structure your code to defer blocking operations until the end.
 | 
						||
 | 
						||
### Foreign import execution stages
 | 
						||
 | 
						||
1. **Import (Read / Initialization) Stage**
 | 
						||
   ```kcl
 | 
						||
   import "tests/inputs/cube.step" as cube
 | 
						||
   ```
 | 
						||
   - Reads the file from disk and makes its API available.
 | 
						||
   - Starts engine rendering but **does not block** your script.
 | 
						||
   - This kick‑starts the render pipeline while you keep executing other code.
 | 
						||
 | 
						||
2. **Invocation (Blocking) Stage**
 | 
						||
   ```kcl
 | 
						||
   import "tests/inputs/cube.step" as cube
 | 
						||
 | 
						||
   cube
 | 
						||
     |> translate(z=10) // ← blocks here only
 | 
						||
   ```
 | 
						||
   - Any method call (e.g., `translate`, `scale`, `rotate`) waits for the background render to finish before applying transformations.
 | 
						||
 | 
						||
### Best practices
 | 
						||
 | 
						||
#### 1. Defer blocking calls
 | 
						||
 | 
						||
```kcl
 | 
						||
import "tests/inputs/cube.step" as cube     // 1) Read / Background render starts
 | 
						||
 | 
						||
 | 
						||
// --- perform other operations and calculations here ---
 | 
						||
 | 
						||
 | 
						||
cube
 | 
						||
  |> translate(z=10)                        // 2) Blocks only here
 | 
						||
```
 | 
						||
 | 
						||
#### 2. Split heavy work into separate modules
 | 
						||
 | 
						||
Place computationally expensive or IO‑heavy work into its own module so it can render in parallel while `main.kcl` continues.
 | 
						||
 | 
						||
#### Future improvements
 | 
						||
 | 
						||
Upcoming releases will auto‑analyse dependencies and only block when truly necessary. Until then, explicit deferral will give you the best performance.
 | 
						||
 | 
						||
 |