Foreign imports go thru parallelization too (#6488)
* 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>
@ -10,7 +10,7 @@ isolated from other files as a separate module.
|
|||||||
When you define a function, you can use `export` before it to make it available
|
When you define a function, you can use `export` before it to make it available
|
||||||
to other modules.
|
to other modules.
|
||||||
|
|
||||||
```
|
```kcl
|
||||||
// util.kcl
|
// util.kcl
|
||||||
export fn increment(x) {
|
export fn increment(x) {
|
||||||
return x + 1
|
return x + 1
|
||||||
@ -31,11 +31,11 @@ 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.
|
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
|
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.
|
an `import` statement inside a function or in the body of an if‑else.
|
||||||
|
|
||||||
Multiple functions can be exported in a file.
|
Multiple functions can be exported in a file.
|
||||||
|
|
||||||
```
|
```kcl
|
||||||
// util.kcl
|
// util.kcl
|
||||||
export fn increment(x) {
|
export fn increment(x) {
|
||||||
return x + 1
|
return x + 1
|
||||||
@ -58,6 +58,211 @@ Imported symbols can be renamed for convenience or to avoid name collisions.
|
|||||||
import increment as inc, decrement as dec from "util.kcl"
|
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
|
## 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
|
`import` can also be used to import files from other CAD systems. The format of the statement is the
|
||||||
@ -69,25 +274,17 @@ import "tests/inputs/cube.obj"
|
|||||||
// Use `cube` just like a KCL object.
|
// Use `cube` just like a KCL object.
|
||||||
```
|
```
|
||||||
|
|
||||||
```norun
|
```kcl
|
||||||
import "tests/inputs/cube-2.sldprt" as cube
|
import "tests/inputs/cube.sldprt" as cube
|
||||||
|
|
||||||
// Use `cube` just like a KCL object.
|
// Use `cube` just like a KCL object.
|
||||||
```
|
```
|
||||||
|
|
||||||
You can make the file format explicit using a format attribute (useful if using a different
|
|
||||||
extension), e.g.,
|
|
||||||
|
|
||||||
```norun
|
|
||||||
@(format = obj)
|
|
||||||
import "tests/inputs/cube"
|
|
||||||
```
|
|
||||||
|
|
||||||
For formats lacking unit data (such as STL, OBJ, or PLY files), the default
|
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
|
unit of measurement is millimeters. Alternatively you may specify the unit
|
||||||
by using an attirbute. Likewise, you can also specify a coordinate system. E.g.,
|
by using an attribute. Likewise, you can also specify a coordinate system. E.g.,
|
||||||
|
|
||||||
```norun
|
```kcl
|
||||||
@(unitLength = ft, coords = opengl)
|
@(unitLength = ft, coords = opengl)
|
||||||
import "tests/inputs/cube.obj"
|
import "tests/inputs/cube.obj"
|
||||||
```
|
```
|
||||||
@ -110,97 +307,55 @@ Coordinate systems:
|
|||||||
- `opengl`, forward: +Z, up: +Y, handedness: right
|
- `opengl`, forward: +Z, up: +Y, handedness: right
|
||||||
- `vulkan`, forward: +Z, up: -Y, handedness: left
|
- `vulkan`, forward: +Z, up: -Y, handedness: left
|
||||||
|
|
||||||
### Performance
|
---
|
||||||
|
|
||||||
Parallelized foreign-file imports now let you overlap file reads, initialization,
|
## 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
|
and rendering. To maximize throughput, you need to understand the three distinct
|
||||||
stages—reading, initializing (background render start), and invocation (blocking)
|
stages—reading, initializing (background render start), and invocation (blocking)
|
||||||
—and structure your code to defer blocking operations until the end.
|
—and structure your code to defer blocking operations until the end.
|
||||||
|
|
||||||
#### Foreign Import Execution Stages
|
### Foreign import execution stages
|
||||||
|
|
||||||
1. **Import (Read) Stage**
|
1. **Import (Read / Initialization) Stage**
|
||||||
```norun
|
```kcl
|
||||||
import "tests/inputs/cube.step" as cube
|
import "tests/inputs/cube.step" as cube
|
||||||
```
|
```
|
||||||
- Reads the file from disk and makes its API available.
|
- Reads the file from disk and makes its API available.
|
||||||
- **Does _not_** start Engine rendering or block your script.
|
- Starts engine rendering but **does not block** your script.
|
||||||
|
- This kick‑starts the render pipeline while you keep executing other code.
|
||||||
|
|
||||||
2. **Initialization (Background Render) Stage**
|
2. **Invocation (Blocking) Stage**
|
||||||
```norun
|
```kcl
|
||||||
import "tests/inputs/cube.step" as cube
|
import "tests/inputs/cube.step" as cube
|
||||||
|
|
||||||
myCube = cube // <- This line starts background rendering
|
cube
|
||||||
```
|
|> translate(z=10) // ← blocks here only
|
||||||
- Invoking the imported symbol (assignment or plain call) triggers Engine rendering _in the background_.
|
```
|
||||||
- This kick‑starts the render pipeline but doesn’t block—you can continue other work while the Engine processes the model.
|
- Any method call (e.g., `translate`, `scale`, `rotate`) waits for the background render to finish before applying transformations.
|
||||||
|
|
||||||
3. **Invocation (Blocking) Stage**
|
### Best practices
|
||||||
```norun
|
|
||||||
import "tests/inputs/cube.step" as cube
|
|
||||||
|
|
||||||
myCube = cube
|
#### 1. Defer blocking calls
|
||||||
|
|
||||||
myCube
|
```kcl
|
||||||
|> translate(z=10) // <- This line blocks
|
import "tests/inputs/cube.step" as cube // 1) Read / Background render starts
|
||||||
```
|
|
||||||
- Any method call (e.g., `translate`, `scale`, `rotate`) waits for the background render to finish before applying transformations.
|
|
||||||
- This is the only point where your script will block.
|
|
||||||
|
|
||||||
> **Nuance:** Foreign imports differ from pure KCL modules—calling the same import symbol multiple times (e.g., `screw` twice) starts background rendering twice.
|
|
||||||
|
|
||||||
#### Best Practices
|
|
||||||
|
|
||||||
##### 1. Defer Blocking Calls
|
|
||||||
Initialize early but delay all transformations until after your heavy computation:
|
|
||||||
```norun
|
|
||||||
import "tests/inputs/cube.step" as cube // 1) Read
|
|
||||||
|
|
||||||
myCube = cube // 2) Background render starts
|
|
||||||
|
|
||||||
|
|
||||||
// --- perform other operations and calculations or setup here ---
|
// --- perform other operations and calculations here ---
|
||||||
|
|
||||||
|
|
||||||
myCube
|
|
||||||
|> translate(z=10) // 3) Blocks only here
|
|
||||||
```
|
|
||||||
|
|
||||||
##### 2. Encapsulate Imports in Modules
|
|
||||||
Keep `main.kcl` free of reads and initialization; wrap them:
|
|
||||||
|
|
||||||
```norun
|
|
||||||
// imports.kcl
|
|
||||||
import "tests/inputs/cube.step" as cube // Read only
|
|
||||||
|
|
||||||
|
|
||||||
export myCube = cube // Kick off rendering
|
|
||||||
```
|
|
||||||
|
|
||||||
```norun
|
|
||||||
// main.kcl
|
|
||||||
import myCube from "imports.kcl" // Import the initialized object
|
|
||||||
|
|
||||||
|
|
||||||
// ... computations ...
|
|
||||||
|
|
||||||
|
|
||||||
myCube
|
|
||||||
|> translate(z=10) // Blocking call at the end
|
|
||||||
```
|
|
||||||
|
|
||||||
##### 3. Avoid Immediate Method Calls
|
|
||||||
|
|
||||||
```norun
|
|
||||||
import "tests/inputs/cube.step" as cube
|
|
||||||
|
|
||||||
cube
|
cube
|
||||||
|> translate(z=10) // Blocks immediately, negating parallelism
|
|> translate(z=10) // 2) Blocks only here
|
||||||
```
|
```
|
||||||
|
|
||||||
Both calling methods right on `cube` immediately or leaving an implicit import without assignment introduce blocking.
|
#### 2. Split heavy work into separate modules
|
||||||
|
|
||||||
#### Future Improvements
|
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.
|
||||||
|
|
||||||
Upcoming releases will auto‑analyze dependencies and only block when truly necessary. Until then, explicit deferral and modular wrapping give you the best performance.
|
|
||||||
|
|
||||||
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 25 KiB |
Before Width: | Height: | Size: 90 KiB After Width: | Height: | Size: 90 KiB |
Before Width: | Height: | Size: 68 KiB After Width: | Height: | Size: 68 KiB |
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 23 KiB |
@ -1076,7 +1076,7 @@ mod test {
|
|||||||
|
|
||||||
#[for_each_std_mod]
|
#[for_each_std_mod]
|
||||||
#[tokio::test(flavor = "multi_thread")]
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
async fn test_examples() {
|
async fn kcl_test_examples() {
|
||||||
let std = walk_prelude();
|
let std = walk_prelude();
|
||||||
let mut errs = Vec::new();
|
let mut errs = Vec::new();
|
||||||
for d in std {
|
for d in std {
|
||||||
|
@ -459,7 +459,7 @@ impl ExecutorContext {
|
|||||||
exec_state.add_path_to_source_id(resolved_path.clone(), id);
|
exec_state.add_path_to_source_id(resolved_path.clone(), id);
|
||||||
let format = super::import::format_from_annotations(attrs, path, source_range)?;
|
let format = super::import::format_from_annotations(attrs, path, source_range)?;
|
||||||
let geom = super::import::import_foreign(path, format, exec_state, self, source_range).await?;
|
let geom = super::import::import_foreign(path, format, exec_state, self, source_range).await?;
|
||||||
exec_state.add_module(id, resolved_path, ModuleRepr::Foreign(geom));
|
exec_state.add_module(id, resolved_path, ModuleRepr::Foreign(geom, None));
|
||||||
Ok(id)
|
Ok(id)
|
||||||
}
|
}
|
||||||
ImportPath::Std { .. } => {
|
ImportPath::Std { .. } => {
|
||||||
@ -501,7 +501,7 @@ impl ExecutorContext {
|
|||||||
*cache = Some((val, er, items.clone()));
|
*cache = Some((val, er, items.clone()));
|
||||||
(er, items)
|
(er, items)
|
||||||
}),
|
}),
|
||||||
ModuleRepr::Foreign(geom) => Err(KclError::Semantic(KclErrorDetails {
|
ModuleRepr::Foreign(geom, _) => Err(KclError::Semantic(KclErrorDetails {
|
||||||
message: "Cannot import items from foreign modules".to_owned(),
|
message: "Cannot import items from foreign modules".to_owned(),
|
||||||
source_ranges: vec![geom.source_range],
|
source_ranges: vec![geom.source_range],
|
||||||
})),
|
})),
|
||||||
@ -546,9 +546,20 @@ impl ExecutorContext {
|
|||||||
Err(e) => Err(e),
|
Err(e) => Err(e),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ModuleRepr::Foreign(geom) => super::import::send_to_engine(geom.clone(), self)
|
ModuleRepr::Foreign(_, Some(imported)) => Ok(Some(imported.clone())),
|
||||||
.await
|
ModuleRepr::Foreign(geom, cached) => {
|
||||||
.map(|geom| Some(KclValue::ImportedGeometry(geom))),
|
let result = super::import::send_to_engine(geom.clone(), self)
|
||||||
|
.await
|
||||||
|
.map(|geom| Some(KclValue::ImportedGeometry(geom)));
|
||||||
|
|
||||||
|
match result {
|
||||||
|
Ok(val) => {
|
||||||
|
*cached = val.clone();
|
||||||
|
Ok(val)
|
||||||
|
}
|
||||||
|
Err(e) => Err(e),
|
||||||
|
}
|
||||||
|
}
|
||||||
ModuleRepr::Dummy => unreachable!(),
|
ModuleRepr::Dummy => unreachable!(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -752,26 +752,31 @@ impl ExecutorContext {
|
|||||||
let mut universe = std::collections::HashMap::new();
|
let mut universe = std::collections::HashMap::new();
|
||||||
|
|
||||||
let default_planes = self.engine.get_default_planes().read().await.clone();
|
let default_planes = self.engine.get_default_planes().read().await.clone();
|
||||||
crate::walk::import_universe(self, &program.ast, &mut universe, exec_state)
|
crate::walk::import_universe(
|
||||||
.await
|
self,
|
||||||
.map_err(|err| {
|
&ModuleRepr::Kcl(program.ast.clone(), None),
|
||||||
let module_id_to_module_path: IndexMap<ModuleId, ModulePath> = exec_state
|
&mut universe,
|
||||||
.global
|
exec_state,
|
||||||
.path_to_source_id
|
)
|
||||||
.iter()
|
.await
|
||||||
.map(|(k, v)| ((*v), k.clone()))
|
.map_err(|err| {
|
||||||
.collect();
|
let module_id_to_module_path: IndexMap<ModuleId, ModulePath> = exec_state
|
||||||
|
.global
|
||||||
|
.path_to_source_id
|
||||||
|
.iter()
|
||||||
|
.map(|(k, v)| ((*v), k.clone()))
|
||||||
|
.collect();
|
||||||
|
|
||||||
KclErrorWithOutputs::new(
|
KclErrorWithOutputs::new(
|
||||||
err,
|
err,
|
||||||
exec_state.global.operations.clone(),
|
exec_state.global.operations.clone(),
|
||||||
exec_state.global.artifact_commands.clone(),
|
exec_state.global.artifact_commands.clone(),
|
||||||
exec_state.global.artifact_graph.clone(),
|
exec_state.global.artifact_graph.clone(),
|
||||||
module_id_to_module_path,
|
module_id_to_module_path,
|
||||||
exec_state.global.id_to_source.clone(),
|
exec_state.global.id_to_source.clone(),
|
||||||
default_planes.clone(),
|
default_planes.clone(),
|
||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
for modules in crate::walk::import_graph(&universe, self)
|
for modules in crate::walk::import_graph(&universe, self)
|
||||||
.map_err(|err| {
|
.map_err(|err| {
|
||||||
@ -799,16 +804,12 @@ impl ExecutorContext {
|
|||||||
|
|
||||||
#[allow(clippy::type_complexity)]
|
#[allow(clippy::type_complexity)]
|
||||||
let (results_tx, mut results_rx): (
|
let (results_tx, mut results_rx): (
|
||||||
tokio::sync::mpsc::Sender<(
|
tokio::sync::mpsc::Sender<(ModuleId, ModulePath, Result<ModuleRepr, KclError>)>,
|
||||||
ModuleId,
|
|
||||||
ModulePath,
|
|
||||||
Result<(Option<KclValue>, EnvironmentRef, Vec<String>), KclError>,
|
|
||||||
)>,
|
|
||||||
tokio::sync::mpsc::Receiver<_>,
|
tokio::sync::mpsc::Receiver<_>,
|
||||||
) = tokio::sync::mpsc::channel(1);
|
) = tokio::sync::mpsc::channel(1);
|
||||||
|
|
||||||
for module in modules {
|
for module in modules {
|
||||||
let Some((import_stmt, module_id, module_path, program)) = universe.get(&module) else {
|
let Some((import_stmt, module_id, module_path, repr)) = universe.get(&module) else {
|
||||||
return Err(KclErrorWithOutputs::no_outputs(KclError::Internal(KclErrorDetails {
|
return Err(KclErrorWithOutputs::no_outputs(KclError::Internal(KclErrorDetails {
|
||||||
message: format!("Module {module} not found in universe"),
|
message: format!("Module {module} not found in universe"),
|
||||||
source_ranges: Default::default(),
|
source_ranges: Default::default(),
|
||||||
@ -816,12 +817,41 @@ impl ExecutorContext {
|
|||||||
};
|
};
|
||||||
let module_id = *module_id;
|
let module_id = *module_id;
|
||||||
let module_path = module_path.clone();
|
let module_path = module_path.clone();
|
||||||
let program = program.clone();
|
let repr = repr.clone();
|
||||||
let exec_state = exec_state.clone();
|
let exec_state = exec_state.clone();
|
||||||
let exec_ctxt = self.clone();
|
let exec_ctxt = self.clone();
|
||||||
let results_tx = results_tx.clone();
|
let results_tx = results_tx.clone();
|
||||||
let source_range = SourceRange::from(import_stmt);
|
let source_range = SourceRange::from(import_stmt);
|
||||||
|
|
||||||
|
let exec_module = async |exec_ctxt: &ExecutorContext,
|
||||||
|
repr: &ModuleRepr,
|
||||||
|
module_id: ModuleId,
|
||||||
|
module_path: &ModulePath,
|
||||||
|
exec_state: &mut ExecState,
|
||||||
|
source_range: SourceRange|
|
||||||
|
-> Result<ModuleRepr, KclError> {
|
||||||
|
match repr {
|
||||||
|
ModuleRepr::Kcl(program, _) => {
|
||||||
|
let result = exec_ctxt
|
||||||
|
.exec_module_from_ast(program, module_id, module_path, exec_state, source_range, false)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
result.map(|val| ModuleRepr::Kcl(program.clone(), Some(val)))
|
||||||
|
}
|
||||||
|
ModuleRepr::Foreign(geom, _) => {
|
||||||
|
let result = crate::execution::import::send_to_engine(geom.clone(), exec_ctxt)
|
||||||
|
.await
|
||||||
|
.map(|geom| Some(KclValue::ImportedGeometry(geom)));
|
||||||
|
|
||||||
|
result.map(|val| ModuleRepr::Foreign(geom.clone(), val))
|
||||||
|
}
|
||||||
|
ModuleRepr::Dummy | ModuleRepr::Root => Err(KclError::Internal(KclErrorDetails {
|
||||||
|
message: format!("Module {module_path} not found in universe"),
|
||||||
|
source_ranges: vec![source_range],
|
||||||
|
})),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
#[cfg(target_arch = "wasm32")]
|
#[cfg(target_arch = "wasm32")]
|
||||||
{
|
{
|
||||||
wasm_bindgen_futures::spawn_local(async move {
|
wasm_bindgen_futures::spawn_local(async move {
|
||||||
@ -829,16 +859,15 @@ impl ExecutorContext {
|
|||||||
let mut exec_state = exec_state;
|
let mut exec_state = exec_state;
|
||||||
let exec_ctxt = exec_ctxt;
|
let exec_ctxt = exec_ctxt;
|
||||||
|
|
||||||
let result = exec_ctxt
|
let result = exec_module(
|
||||||
.exec_module_from_ast(
|
&exec_ctxt,
|
||||||
&program,
|
&repr,
|
||||||
module_id,
|
module_id,
|
||||||
&module_path,
|
&module_path,
|
||||||
&mut exec_state,
|
&mut exec_state,
|
||||||
source_range,
|
source_range,
|
||||||
false,
|
)
|
||||||
)
|
.await;
|
||||||
.await;
|
|
||||||
|
|
||||||
results_tx
|
results_tx
|
||||||
.send((module_id, module_path, result))
|
.send((module_id, module_path, result))
|
||||||
@ -852,16 +881,15 @@ impl ExecutorContext {
|
|||||||
let mut exec_state = exec_state;
|
let mut exec_state = exec_state;
|
||||||
let exec_ctxt = exec_ctxt;
|
let exec_ctxt = exec_ctxt;
|
||||||
|
|
||||||
let result = exec_ctxt
|
let result = exec_module(
|
||||||
.exec_module_from_ast(
|
&exec_ctxt,
|
||||||
&program,
|
&repr,
|
||||||
module_id,
|
module_id,
|
||||||
&module_path,
|
&module_path,
|
||||||
&mut exec_state,
|
&mut exec_state,
|
||||||
source_range,
|
source_range,
|
||||||
false,
|
)
|
||||||
)
|
.await;
|
||||||
.await;
|
|
||||||
|
|
||||||
results_tx
|
results_tx
|
||||||
.send((module_id, module_path, result))
|
.send((module_id, module_path, result))
|
||||||
@ -875,13 +903,24 @@ impl ExecutorContext {
|
|||||||
|
|
||||||
while let Some((module_id, _, result)) = results_rx.recv().await {
|
while let Some((module_id, _, result)) = results_rx.recv().await {
|
||||||
match result {
|
match result {
|
||||||
Ok((val, session_data, variables)) => {
|
Ok(new_repr) => {
|
||||||
let mut repr = exec_state.global.module_infos[&module_id].take_repr();
|
let mut repr = exec_state.global.module_infos[&module_id].take_repr();
|
||||||
|
|
||||||
let ModuleRepr::Kcl(_, cache) = &mut repr else {
|
match &mut repr {
|
||||||
continue;
|
ModuleRepr::Kcl(_, cache) => {
|
||||||
};
|
let ModuleRepr::Kcl(_, session_data) = new_repr else {
|
||||||
*cache = Some((val, session_data, variables));
|
unreachable!();
|
||||||
|
};
|
||||||
|
*cache = session_data;
|
||||||
|
}
|
||||||
|
ModuleRepr::Foreign(_, cache) => {
|
||||||
|
let ModuleRepr::Foreign(_, session_data) = new_repr else {
|
||||||
|
unreachable!();
|
||||||
|
};
|
||||||
|
*cache = session_data;
|
||||||
|
}
|
||||||
|
ModuleRepr::Dummy | ModuleRepr::Root => unreachable!(),
|
||||||
|
}
|
||||||
|
|
||||||
exec_state.global.module_infos[&module_id].restore_repr(repr);
|
exec_state.global.module_infos[&module_id].restore_repr(repr);
|
||||||
}
|
}
|
||||||
|
@ -124,7 +124,7 @@ pub enum ModuleRepr {
|
|||||||
Root,
|
Root,
|
||||||
// AST, memory, exported names
|
// AST, memory, exported names
|
||||||
Kcl(Node<Program>, Option<(Option<KclValue>, EnvironmentRef, Vec<String>)>),
|
Kcl(Node<Program>, Option<(Option<KclValue>, EnvironmentRef, Vec<String>)>),
|
||||||
Foreign(PreImportedGeometry),
|
Foreign(PreImportedGeometry, Option<KclValue>),
|
||||||
Dummy,
|
Dummy,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2579,3 +2579,24 @@ mod loop_tag {
|
|||||||
super::execute(TEST_NAME, true).await
|
super::execute(TEST_NAME, true).await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
mod multiple_foreign_imports_all_render {
|
||||||
|
const TEST_NAME: &str = "multiple-foreign-imports-all-render";
|
||||||
|
|
||||||
|
/// Test parsing KCL.
|
||||||
|
#[test]
|
||||||
|
fn parse() {
|
||||||
|
super::parse(TEST_NAME)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Test that parsing and unparsing KCL produces the original KCL input.
|
||||||
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
|
async fn unparse() {
|
||||||
|
super::unparse(TEST_NAME).await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Test that KCL is executed correctly.
|
||||||
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
|
async fn kcl_test_execute() {
|
||||||
|
super::execute(TEST_NAME, true).await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -8,7 +8,7 @@ use anyhow::Result;
|
|||||||
use crate::{
|
use crate::{
|
||||||
errors::KclErrorDetails,
|
errors::KclErrorDetails,
|
||||||
modules::{ModulePath, ModuleRepr},
|
modules::{ModulePath, ModuleRepr},
|
||||||
parsing::ast::types::{ImportPath, ImportStatement, Node as AstNode, NodeRef, Program},
|
parsing::ast::types::{ImportPath, ImportStatement, Node as AstNode},
|
||||||
walk::{Node, Visitable},
|
walk::{Node, Visitable},
|
||||||
ExecState, ExecutorContext, KclError, ModuleId, SourceRange,
|
ExecState, ExecutorContext, KclError, ModuleId, SourceRange,
|
||||||
};
|
};
|
||||||
@ -20,7 +20,7 @@ type Dependency = (String, String);
|
|||||||
|
|
||||||
type Graph = Vec<Dependency>;
|
type Graph = Vec<Dependency>;
|
||||||
|
|
||||||
type DependencyInfo = (AstNode<ImportStatement>, ModuleId, ModulePath, AstNode<Program>);
|
type DependencyInfo = (AstNode<ImportStatement>, ModuleId, ModulePath, ModuleRepr);
|
||||||
type Universe = HashMap<String, DependencyInfo>;
|
type Universe = HashMap<String, DependencyInfo>;
|
||||||
|
|
||||||
/// Process a number of programs, returning the graph of dependencies.
|
/// Process a number of programs, returning the graph of dependencies.
|
||||||
@ -32,9 +32,9 @@ type Universe = HashMap<String, DependencyInfo>;
|
|||||||
pub fn import_graph(progs: &Universe, ctx: &ExecutorContext) -> Result<Vec<Vec<String>>, KclError> {
|
pub fn import_graph(progs: &Universe, ctx: &ExecutorContext) -> Result<Vec<Vec<String>>, KclError> {
|
||||||
let mut graph = Graph::new();
|
let mut graph = Graph::new();
|
||||||
|
|
||||||
for (name, (_, _, _, program)) in progs.iter() {
|
for (name, (_, _, _, repr)) in progs.iter() {
|
||||||
graph.extend(
|
graph.extend(
|
||||||
import_dependencies(program, ctx)?
|
import_dependencies(repr, ctx)?
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|(dependency, _, _)| (name.clone(), dependency))
|
.map(|(dependency, _, _)| (name.clone(), dependency))
|
||||||
.collect::<Vec<_>>(),
|
.collect::<Vec<_>>(),
|
||||||
@ -118,28 +118,42 @@ fn topsort(all_modules: &[&str], graph: Graph) -> Result<Vec<Vec<String>>, KclEr
|
|||||||
|
|
||||||
type ImportDependencies = Vec<(String, AstNode<ImportStatement>, ModulePath)>;
|
type ImportDependencies = Vec<(String, AstNode<ImportStatement>, ModulePath)>;
|
||||||
|
|
||||||
pub(crate) fn import_dependencies(
|
pub(crate) fn import_dependencies(repr: &ModuleRepr, ctx: &ExecutorContext) -> Result<ImportDependencies, KclError> {
|
||||||
prog: NodeRef<Program>,
|
let ModuleRepr::Kcl(prog, _) = repr else {
|
||||||
ctx: &ExecutorContext,
|
// It has no dependencies, so return an empty list.
|
||||||
) -> Result<ImportDependencies, KclError> {
|
return Ok(vec![]);
|
||||||
let ret = Arc::new(Mutex::new(vec![]));
|
};
|
||||||
|
|
||||||
|
let ret = Arc::new(Mutex::new(vec![]));
|
||||||
fn walk(ret: Arc<Mutex<ImportDependencies>>, node: Node<'_>, ctx: &ExecutorContext) -> Result<(), KclError> {
|
fn walk(ret: Arc<Mutex<ImportDependencies>>, node: Node<'_>, ctx: &ExecutorContext) -> Result<(), KclError> {
|
||||||
if let Node::ImportStatement(is) = node {
|
if let Node::ImportStatement(is) = node {
|
||||||
// We only care about Kcl imports for now.
|
// We only care about Kcl and Foreign imports for now.
|
||||||
if let ImportPath::Kcl { filename } = &is.path {
|
let resolved_path = ModulePath::from_import_path(&is.path, &ctx.settings.project_directory);
|
||||||
let resolved_path = ModulePath::from_import_path(&is.path, &ctx.settings.project_directory);
|
match &is.path {
|
||||||
|
ImportPath::Kcl { filename } => {
|
||||||
// We need to lock the mutex to push the dependency.
|
// We need to lock the mutex to push the dependency.
|
||||||
// This is a bit of a hack, but it works for now.
|
// This is a bit of a hack, but it works for now.
|
||||||
ret.lock()
|
ret.lock()
|
||||||
.map_err(|err| {
|
.map_err(|err| {
|
||||||
KclError::Internal(KclErrorDetails {
|
KclError::Internal(KclErrorDetails {
|
||||||
message: format!("Failed to lock mutex: {}", err),
|
message: format!("Failed to lock mutex: {}", err),
|
||||||
source_ranges: Default::default(),
|
source_ranges: Default::default(),
|
||||||
})
|
})
|
||||||
})?
|
})?
|
||||||
.push((filename.to_string(), is.clone(), resolved_path));
|
.push((filename.to_string(), is.clone(), resolved_path));
|
||||||
|
}
|
||||||
|
ImportPath::Foreign { path } => {
|
||||||
|
ret.lock()
|
||||||
|
.map_err(|err| {
|
||||||
|
KclError::Internal(KclErrorDetails {
|
||||||
|
message: format!("Failed to lock mutex: {}", err),
|
||||||
|
source_ranges: Default::default(),
|
||||||
|
})
|
||||||
|
})?
|
||||||
|
.push((path.to_string(), is.clone(), resolved_path));
|
||||||
|
}
|
||||||
|
ImportPath::Std { .. } => { // do nothing
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -164,11 +178,11 @@ pub(crate) fn import_dependencies(
|
|||||||
|
|
||||||
pub(crate) async fn import_universe(
|
pub(crate) async fn import_universe(
|
||||||
ctx: &ExecutorContext,
|
ctx: &ExecutorContext,
|
||||||
prog: NodeRef<'_, Program>,
|
repr: &ModuleRepr,
|
||||||
out: &mut Universe,
|
out: &mut Universe,
|
||||||
exec_state: &mut ExecState,
|
exec_state: &mut ExecState,
|
||||||
) -> Result<(), KclError> {
|
) -> Result<(), KclError> {
|
||||||
let modules = import_dependencies(prog, ctx)?;
|
let modules = import_dependencies(repr, ctx)?;
|
||||||
for (filename, import_stmt, module_path) in modules {
|
for (filename, import_stmt, module_path) in modules {
|
||||||
if out.contains_key(&filename) {
|
if out.contains_key(&filename) {
|
||||||
continue;
|
continue;
|
||||||
@ -178,26 +192,21 @@ pub(crate) async fn import_universe(
|
|||||||
.open_module(&import_stmt.path, &[], exec_state, Default::default())
|
.open_module(&import_stmt.path, &[], exec_state, Default::default())
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let program = {
|
let repr = {
|
||||||
let Some(module_info) = exec_state.get_module(module_id) else {
|
let Some(module_info) = exec_state.get_module(module_id) else {
|
||||||
return Err(KclError::Internal(KclErrorDetails {
|
return Err(KclError::Internal(KclErrorDetails {
|
||||||
message: format!("Module {} not found", module_id),
|
message: format!("Module {} not found", module_id),
|
||||||
source_ranges: vec![import_stmt.into()],
|
source_ranges: vec![import_stmt.into()],
|
||||||
}));
|
}));
|
||||||
};
|
};
|
||||||
let ModuleRepr::Kcl(program, _) = &module_info.repr else {
|
module_info.repr.clone()
|
||||||
// if it's not a KCL module we can skip it since it has no
|
|
||||||
// dependencies.
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
program.clone()
|
|
||||||
};
|
};
|
||||||
|
|
||||||
out.insert(
|
out.insert(
|
||||||
filename.clone(),
|
filename.clone(),
|
||||||
(import_stmt.clone(), module_id, module_path.clone(), program.clone()),
|
(import_stmt.clone(), module_id, module_path.clone(), repr.clone()),
|
||||||
);
|
);
|
||||||
Box::pin(import_universe(ctx, &program, out, exec_state)).await?;
|
Box::pin(import_universe(ctx, &repr, out, exec_state)).await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@ -206,7 +215,7 @@ pub(crate) async fn import_universe(
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::parsing::ast::types::ImportSelector;
|
use crate::parsing::ast::types::{ImportSelector, Program};
|
||||||
|
|
||||||
macro_rules! kcl {
|
macro_rules! kcl {
|
||||||
( $kcl:expr ) => {{
|
( $kcl:expr ) => {{
|
||||||
@ -224,7 +233,7 @@ mod tests {
|
|||||||
}),
|
}),
|
||||||
ModuleId::default(),
|
ModuleId::default(),
|
||||||
ModulePath::Local { value: "".into() },
|
ModulePath::Local { value: "".into() },
|
||||||
program,
|
ModuleRepr::Kcl(program, None),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 25 KiB |
Before Width: | Height: | Size: 90 KiB After Width: | Height: | Size: 90 KiB |
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 23 KiB |
@ -0,0 +1,3 @@
|
|||||||
|
import "../inputs/cube.step" as cube
|
||||||
|
|
||||||
|
clone(cube)
|
@ -0,0 +1,6 @@
|
|||||||
|
---
|
||||||
|
source: kcl-lib/src/simulation_tests.rs
|
||||||
|
description: Artifact graph flowchart multiple-foreign-imports-all-render.kcl
|
||||||
|
extension: md
|
||||||
|
snapshot_kind: binary
|
||||||
|
---
|
@ -0,0 +1,3 @@
|
|||||||
|
```mermaid
|
||||||
|
flowchart LR
|
||||||
|
```
|
504
rust/kcl-lib/tests/multiple-foreign-imports-all-render/ast.snap
Normal file
@ -0,0 +1,504 @@
|
|||||||
|
---
|
||||||
|
source: kcl-lib/src/simulation_tests.rs
|
||||||
|
description: Result of parsing multiple-foreign-imports-all-render.kcl
|
||||||
|
---
|
||||||
|
{
|
||||||
|
"Ok": {
|
||||||
|
"body": [
|
||||||
|
{
|
||||||
|
"commentStart": 0,
|
||||||
|
"end": 0,
|
||||||
|
"path": {
|
||||||
|
"type": "Foreign",
|
||||||
|
"path": "../inputs/cube.step"
|
||||||
|
},
|
||||||
|
"selector": {
|
||||||
|
"type": "None",
|
||||||
|
"alias": {
|
||||||
|
"commentStart": 0,
|
||||||
|
"end": 0,
|
||||||
|
"name": "cube",
|
||||||
|
"start": 0,
|
||||||
|
"type": "Identifier"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"start": 0,
|
||||||
|
"type": "ImportStatement",
|
||||||
|
"type": "ImportStatement"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"commentStart": 0,
|
||||||
|
"end": 0,
|
||||||
|
"path": {
|
||||||
|
"type": "Kcl",
|
||||||
|
"filename": "othercube.kcl"
|
||||||
|
},
|
||||||
|
"selector": {
|
||||||
|
"type": "None",
|
||||||
|
"alias": {
|
||||||
|
"commentStart": 0,
|
||||||
|
"end": 0,
|
||||||
|
"name": "othercube",
|
||||||
|
"start": 0,
|
||||||
|
"type": "Identifier"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"start": 0,
|
||||||
|
"type": "ImportStatement",
|
||||||
|
"type": "ImportStatement"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"commentStart": 0,
|
||||||
|
"end": 0,
|
||||||
|
"path": {
|
||||||
|
"type": "Kcl",
|
||||||
|
"filename": "anothercube.kcl"
|
||||||
|
},
|
||||||
|
"selector": {
|
||||||
|
"type": "None",
|
||||||
|
"alias": {
|
||||||
|
"commentStart": 0,
|
||||||
|
"end": 0,
|
||||||
|
"name": "anothercube",
|
||||||
|
"start": 0,
|
||||||
|
"type": "Identifier"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"start": 0,
|
||||||
|
"type": "ImportStatement",
|
||||||
|
"type": "ImportStatement"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"commentStart": 0,
|
||||||
|
"declaration": {
|
||||||
|
"commentStart": 0,
|
||||||
|
"end": 0,
|
||||||
|
"id": {
|
||||||
|
"commentStart": 0,
|
||||||
|
"end": 0,
|
||||||
|
"name": "model",
|
||||||
|
"start": 0,
|
||||||
|
"type": "Identifier"
|
||||||
|
},
|
||||||
|
"init": {
|
||||||
|
"abs_path": false,
|
||||||
|
"commentStart": 0,
|
||||||
|
"end": 0,
|
||||||
|
"name": {
|
||||||
|
"commentStart": 0,
|
||||||
|
"end": 0,
|
||||||
|
"name": "cube",
|
||||||
|
"start": 0,
|
||||||
|
"type": "Identifier"
|
||||||
|
},
|
||||||
|
"path": [],
|
||||||
|
"start": 0,
|
||||||
|
"type": "Name",
|
||||||
|
"type": "Name"
|
||||||
|
},
|
||||||
|
"start": 0,
|
||||||
|
"type": "VariableDeclarator"
|
||||||
|
},
|
||||||
|
"end": 0,
|
||||||
|
"kind": "const",
|
||||||
|
"start": 0,
|
||||||
|
"type": "VariableDeclaration",
|
||||||
|
"type": "VariableDeclaration"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"commentStart": 0,
|
||||||
|
"end": 0,
|
||||||
|
"expression": {
|
||||||
|
"body": [
|
||||||
|
{
|
||||||
|
"abs_path": false,
|
||||||
|
"commentStart": 0,
|
||||||
|
"end": 0,
|
||||||
|
"name": {
|
||||||
|
"commentStart": 0,
|
||||||
|
"end": 0,
|
||||||
|
"name": "othercube",
|
||||||
|
"start": 0,
|
||||||
|
"type": "Identifier"
|
||||||
|
},
|
||||||
|
"path": [],
|
||||||
|
"start": 0,
|
||||||
|
"type": "Name",
|
||||||
|
"type": "Name"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"arguments": [
|
||||||
|
{
|
||||||
|
"type": "LabeledArg",
|
||||||
|
"label": {
|
||||||
|
"commentStart": 0,
|
||||||
|
"end": 0,
|
||||||
|
"name": "x",
|
||||||
|
"start": 0,
|
||||||
|
"type": "Identifier"
|
||||||
|
},
|
||||||
|
"arg": {
|
||||||
|
"commentStart": 0,
|
||||||
|
"end": 0,
|
||||||
|
"raw": "1020",
|
||||||
|
"start": 0,
|
||||||
|
"type": "Literal",
|
||||||
|
"type": "Literal",
|
||||||
|
"value": {
|
||||||
|
"value": 1020.0,
|
||||||
|
"suffix": "None"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"callee": {
|
||||||
|
"abs_path": false,
|
||||||
|
"commentStart": 0,
|
||||||
|
"end": 0,
|
||||||
|
"name": {
|
||||||
|
"commentStart": 0,
|
||||||
|
"end": 0,
|
||||||
|
"name": "translate",
|
||||||
|
"start": 0,
|
||||||
|
"type": "Identifier"
|
||||||
|
},
|
||||||
|
"path": [],
|
||||||
|
"start": 0,
|
||||||
|
"type": "Name"
|
||||||
|
},
|
||||||
|
"commentStart": 0,
|
||||||
|
"end": 0,
|
||||||
|
"start": 0,
|
||||||
|
"type": "CallExpressionKw",
|
||||||
|
"type": "CallExpressionKw",
|
||||||
|
"unlabeled": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"arguments": [
|
||||||
|
{
|
||||||
|
"type": "LabeledArg",
|
||||||
|
"label": {
|
||||||
|
"commentStart": 0,
|
||||||
|
"end": 0,
|
||||||
|
"name": "color",
|
||||||
|
"start": 0,
|
||||||
|
"type": "Identifier"
|
||||||
|
},
|
||||||
|
"arg": {
|
||||||
|
"commentStart": 0,
|
||||||
|
"end": 0,
|
||||||
|
"raw": "\"#ff001f\"",
|
||||||
|
"start": 0,
|
||||||
|
"type": "Literal",
|
||||||
|
"type": "Literal",
|
||||||
|
"value": "#ff001f"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "LabeledArg",
|
||||||
|
"label": {
|
||||||
|
"commentStart": 0,
|
||||||
|
"end": 0,
|
||||||
|
"name": "metalness",
|
||||||
|
"start": 0,
|
||||||
|
"type": "Identifier"
|
||||||
|
},
|
||||||
|
"arg": {
|
||||||
|
"commentStart": 0,
|
||||||
|
"end": 0,
|
||||||
|
"raw": "50",
|
||||||
|
"start": 0,
|
||||||
|
"type": "Literal",
|
||||||
|
"type": "Literal",
|
||||||
|
"value": {
|
||||||
|
"value": 50.0,
|
||||||
|
"suffix": "None"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "LabeledArg",
|
||||||
|
"label": {
|
||||||
|
"commentStart": 0,
|
||||||
|
"end": 0,
|
||||||
|
"name": "roughness",
|
||||||
|
"start": 0,
|
||||||
|
"type": "Identifier"
|
||||||
|
},
|
||||||
|
"arg": {
|
||||||
|
"commentStart": 0,
|
||||||
|
"end": 0,
|
||||||
|
"raw": "50",
|
||||||
|
"start": 0,
|
||||||
|
"type": "Literal",
|
||||||
|
"type": "Literal",
|
||||||
|
"value": {
|
||||||
|
"value": 50.0,
|
||||||
|
"suffix": "None"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"callee": {
|
||||||
|
"abs_path": false,
|
||||||
|
"commentStart": 0,
|
||||||
|
"end": 0,
|
||||||
|
"name": {
|
||||||
|
"commentStart": 0,
|
||||||
|
"end": 0,
|
||||||
|
"name": "appearance",
|
||||||
|
"start": 0,
|
||||||
|
"type": "Identifier"
|
||||||
|
},
|
||||||
|
"path": [],
|
||||||
|
"start": 0,
|
||||||
|
"type": "Name"
|
||||||
|
},
|
||||||
|
"commentStart": 0,
|
||||||
|
"end": 0,
|
||||||
|
"start": 0,
|
||||||
|
"type": "CallExpressionKw",
|
||||||
|
"type": "CallExpressionKw",
|
||||||
|
"unlabeled": null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"commentStart": 0,
|
||||||
|
"end": 0,
|
||||||
|
"start": 0,
|
||||||
|
"type": "PipeExpression",
|
||||||
|
"type": "PipeExpression"
|
||||||
|
},
|
||||||
|
"start": 0,
|
||||||
|
"type": "ExpressionStatement",
|
||||||
|
"type": "ExpressionStatement"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"commentStart": 0,
|
||||||
|
"end": 0,
|
||||||
|
"expression": {
|
||||||
|
"body": [
|
||||||
|
{
|
||||||
|
"abs_path": false,
|
||||||
|
"commentStart": 0,
|
||||||
|
"end": 0,
|
||||||
|
"name": {
|
||||||
|
"commentStart": 0,
|
||||||
|
"end": 0,
|
||||||
|
"name": "anothercube",
|
||||||
|
"start": 0,
|
||||||
|
"type": "Identifier"
|
||||||
|
},
|
||||||
|
"path": [],
|
||||||
|
"start": 0,
|
||||||
|
"type": "Name",
|
||||||
|
"type": "Name"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"arguments": [
|
||||||
|
{
|
||||||
|
"type": "LabeledArg",
|
||||||
|
"label": {
|
||||||
|
"commentStart": 0,
|
||||||
|
"end": 0,
|
||||||
|
"name": "x",
|
||||||
|
"start": 0,
|
||||||
|
"type": "Identifier"
|
||||||
|
},
|
||||||
|
"arg": {
|
||||||
|
"argument": {
|
||||||
|
"commentStart": 0,
|
||||||
|
"end": 0,
|
||||||
|
"raw": "1020",
|
||||||
|
"start": 0,
|
||||||
|
"type": "Literal",
|
||||||
|
"type": "Literal",
|
||||||
|
"value": {
|
||||||
|
"value": 1020.0,
|
||||||
|
"suffix": "None"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"commentStart": 0,
|
||||||
|
"end": 0,
|
||||||
|
"operator": "-",
|
||||||
|
"start": 0,
|
||||||
|
"type": "UnaryExpression",
|
||||||
|
"type": "UnaryExpression"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"callee": {
|
||||||
|
"abs_path": false,
|
||||||
|
"commentStart": 0,
|
||||||
|
"end": 0,
|
||||||
|
"name": {
|
||||||
|
"commentStart": 0,
|
||||||
|
"end": 0,
|
||||||
|
"name": "translate",
|
||||||
|
"start": 0,
|
||||||
|
"type": "Identifier"
|
||||||
|
},
|
||||||
|
"path": [],
|
||||||
|
"start": 0,
|
||||||
|
"type": "Name"
|
||||||
|
},
|
||||||
|
"commentStart": 0,
|
||||||
|
"end": 0,
|
||||||
|
"start": 0,
|
||||||
|
"type": "CallExpressionKw",
|
||||||
|
"type": "CallExpressionKw",
|
||||||
|
"unlabeled": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"arguments": [
|
||||||
|
{
|
||||||
|
"type": "LabeledArg",
|
||||||
|
"label": {
|
||||||
|
"commentStart": 0,
|
||||||
|
"end": 0,
|
||||||
|
"name": "color",
|
||||||
|
"start": 0,
|
||||||
|
"type": "Identifier"
|
||||||
|
},
|
||||||
|
"arg": {
|
||||||
|
"commentStart": 0,
|
||||||
|
"end": 0,
|
||||||
|
"raw": "\"#ff0000\"",
|
||||||
|
"start": 0,
|
||||||
|
"type": "Literal",
|
||||||
|
"type": "Literal",
|
||||||
|
"value": "#ff0000"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "LabeledArg",
|
||||||
|
"label": {
|
||||||
|
"commentStart": 0,
|
||||||
|
"end": 0,
|
||||||
|
"name": "metalness",
|
||||||
|
"start": 0,
|
||||||
|
"type": "Identifier"
|
||||||
|
},
|
||||||
|
"arg": {
|
||||||
|
"commentStart": 0,
|
||||||
|
"end": 0,
|
||||||
|
"raw": "50",
|
||||||
|
"start": 0,
|
||||||
|
"type": "Literal",
|
||||||
|
"type": "Literal",
|
||||||
|
"value": {
|
||||||
|
"value": 50.0,
|
||||||
|
"suffix": "None"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "LabeledArg",
|
||||||
|
"label": {
|
||||||
|
"commentStart": 0,
|
||||||
|
"end": 0,
|
||||||
|
"name": "roughness",
|
||||||
|
"start": 0,
|
||||||
|
"type": "Identifier"
|
||||||
|
},
|
||||||
|
"arg": {
|
||||||
|
"commentStart": 0,
|
||||||
|
"end": 0,
|
||||||
|
"raw": "50",
|
||||||
|
"start": 0,
|
||||||
|
"type": "Literal",
|
||||||
|
"type": "Literal",
|
||||||
|
"value": {
|
||||||
|
"value": 50.0,
|
||||||
|
"suffix": "None"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"callee": {
|
||||||
|
"abs_path": false,
|
||||||
|
"commentStart": 0,
|
||||||
|
"end": 0,
|
||||||
|
"name": {
|
||||||
|
"commentStart": 0,
|
||||||
|
"end": 0,
|
||||||
|
"name": "appearance",
|
||||||
|
"start": 0,
|
||||||
|
"type": "Identifier"
|
||||||
|
},
|
||||||
|
"path": [],
|
||||||
|
"start": 0,
|
||||||
|
"type": "Name"
|
||||||
|
},
|
||||||
|
"commentStart": 0,
|
||||||
|
"end": 0,
|
||||||
|
"start": 0,
|
||||||
|
"type": "CallExpressionKw",
|
||||||
|
"type": "CallExpressionKw",
|
||||||
|
"unlabeled": null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"commentStart": 0,
|
||||||
|
"end": 0,
|
||||||
|
"start": 0,
|
||||||
|
"type": "PipeExpression",
|
||||||
|
"type": "PipeExpression"
|
||||||
|
},
|
||||||
|
"start": 0,
|
||||||
|
"type": "ExpressionStatement",
|
||||||
|
"type": "ExpressionStatement"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"commentStart": 0,
|
||||||
|
"end": 0,
|
||||||
|
"nonCodeMeta": {
|
||||||
|
"nonCodeNodes": {
|
||||||
|
"2": [
|
||||||
|
{
|
||||||
|
"commentStart": 0,
|
||||||
|
"end": 0,
|
||||||
|
"start": 0,
|
||||||
|
"type": "NonCodeNode",
|
||||||
|
"value": {
|
||||||
|
"type": "newLine"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"3": [
|
||||||
|
{
|
||||||
|
"commentStart": 0,
|
||||||
|
"end": 0,
|
||||||
|
"start": 0,
|
||||||
|
"type": "NonCodeNode",
|
||||||
|
"value": {
|
||||||
|
"type": "newLine"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"4": [
|
||||||
|
{
|
||||||
|
"commentStart": 0,
|
||||||
|
"end": 0,
|
||||||
|
"start": 0,
|
||||||
|
"type": "NonCodeNode",
|
||||||
|
"value": {
|
||||||
|
"type": "newLine"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"5": [
|
||||||
|
{
|
||||||
|
"commentStart": 0,
|
||||||
|
"end": 0,
|
||||||
|
"start": 0,
|
||||||
|
"type": "NonCodeNode",
|
||||||
|
"value": {
|
||||||
|
"type": "newLine"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"startNodes": []
|
||||||
|
},
|
||||||
|
"start": 0
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,22 @@
|
|||||||
|
import "../inputs/cube.step" as cube
|
||||||
|
import "othercube.kcl" as othercube
|
||||||
|
import "anothercube.kcl" as anothercube
|
||||||
|
|
||||||
|
model = cube
|
||||||
|
|
||||||
|
othercube
|
||||||
|
|> translate(x=1020)
|
||||||
|
|> appearance(
|
||||||
|
color = "#ff001f",
|
||||||
|
metalness = 50,
|
||||||
|
roughness = 50
|
||||||
|
)
|
||||||
|
|
||||||
|
anothercube
|
||||||
|
|> translate(x=-1020)
|
||||||
|
|> appearance(
|
||||||
|
color = "#ff0000",
|
||||||
|
metalness = 50,
|
||||||
|
roughness = 50
|
||||||
|
)
|
||||||
|
|
@ -0,0 +1,96 @@
|
|||||||
|
---
|
||||||
|
source: kcl-lib/src/simulation_tests.rs
|
||||||
|
description: Operations executed multiple-foreign-imports-all-render.kcl
|
||||||
|
---
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"type": "GroupBegin",
|
||||||
|
"group": {
|
||||||
|
"type": "ModuleInstance",
|
||||||
|
"name": "cube",
|
||||||
|
"moduleId": 6
|
||||||
|
},
|
||||||
|
"sourceRange": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "GroupEnd"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "GroupBegin",
|
||||||
|
"group": {
|
||||||
|
"type": "ModuleInstance",
|
||||||
|
"name": "othercube",
|
||||||
|
"moduleId": 7
|
||||||
|
},
|
||||||
|
"sourceRange": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "GroupBegin",
|
||||||
|
"group": {
|
||||||
|
"type": "ModuleInstance",
|
||||||
|
"name": "cube",
|
||||||
|
"moduleId": 6
|
||||||
|
},
|
||||||
|
"sourceRange": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "GroupEnd"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"labeledArgs": {
|
||||||
|
"geometry": {
|
||||||
|
"value": {
|
||||||
|
"type": "ImportedGeometry",
|
||||||
|
"artifact_id": "[uuid]"
|
||||||
|
},
|
||||||
|
"sourceRange": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"name": "clone",
|
||||||
|
"sourceRange": [],
|
||||||
|
"type": "StdLibCall",
|
||||||
|
"unlabeledArg": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "GroupEnd"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "GroupBegin",
|
||||||
|
"group": {
|
||||||
|
"type": "ModuleInstance",
|
||||||
|
"name": "anothercube",
|
||||||
|
"moduleId": 8
|
||||||
|
},
|
||||||
|
"sourceRange": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "GroupBegin",
|
||||||
|
"group": {
|
||||||
|
"type": "ModuleInstance",
|
||||||
|
"name": "cube",
|
||||||
|
"moduleId": 6
|
||||||
|
},
|
||||||
|
"sourceRange": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "GroupEnd"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"labeledArgs": {
|
||||||
|
"geometry": {
|
||||||
|
"value": {
|
||||||
|
"type": "ImportedGeometry",
|
||||||
|
"artifact_id": "[uuid]"
|
||||||
|
},
|
||||||
|
"sourceRange": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"name": "clone",
|
||||||
|
"sourceRange": [],
|
||||||
|
"type": "StdLibCall",
|
||||||
|
"unlabeledArg": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "GroupEnd"
|
||||||
|
}
|
||||||
|
]
|
@ -0,0 +1,3 @@
|
|||||||
|
import "../inputs/cube.step" as cube
|
||||||
|
|
||||||
|
clone(cube)
|
@ -0,0 +1,25 @@
|
|||||||
|
---
|
||||||
|
source: kcl-lib/src/simulation_tests.rs
|
||||||
|
description: Variables in memory after executing multiple-foreign-imports-all-render.kcl
|
||||||
|
---
|
||||||
|
{
|
||||||
|
"anothercube": {
|
||||||
|
"type": "Module",
|
||||||
|
"value": 8
|
||||||
|
},
|
||||||
|
"cube": {
|
||||||
|
"type": "Module",
|
||||||
|
"value": 6
|
||||||
|
},
|
||||||
|
"model": {
|
||||||
|
"type": "ImportedGeometry",
|
||||||
|
"id": "[uuid]",
|
||||||
|
"value": [
|
||||||
|
"cube.step"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"othercube": {
|
||||||
|
"type": "Module",
|
||||||
|
"value": 7
|
||||||
|
}
|
||||||
|
}
|
After Width: | Height: | Size: 81 KiB |
@ -0,0 +1,17 @@
|
|||||||
|
---
|
||||||
|
source: kcl-lib/src/simulation_tests.rs
|
||||||
|
description: Result of unparsing multiple-foreign-imports-all-render.kcl
|
||||||
|
---
|
||||||
|
import "../inputs/cube.step" as cube
|
||||||
|
import "othercube.kcl" as othercube
|
||||||
|
import "anothercube.kcl" as anothercube
|
||||||
|
|
||||||
|
model = cube
|
||||||
|
|
||||||
|
othercube
|
||||||
|
|> translate(x = 1020)
|
||||||
|
|> appearance(color = "#ff001f", metalness = 50, roughness = 50)
|
||||||
|
|
||||||
|
anothercube
|
||||||
|
|> translate(x = -1020)
|
||||||
|
|> appearance(color = "#ff0000", metalness = 50, roughness = 50)
|
@ -0,0 +1,7 @@
|
|||||||
|
---
|
||||||
|
source: kcl-lib/src/simulation_tests.rs
|
||||||
|
description: Result of unparsing tests/multiple-foreign-imports-all-render/anothercube.kcl
|
||||||
|
---
|
||||||
|
import "../inputs/cube.step" as cube
|
||||||
|
|
||||||
|
clone(cube)
|
@ -0,0 +1,7 @@
|
|||||||
|
---
|
||||||
|
source: kcl-lib/src/simulation_tests.rs
|
||||||
|
description: Result of unparsing tests/multiple-foreign-imports-all-render/othercube.kcl
|
||||||
|
---
|
||||||
|
import "../inputs/cube.step" as cube
|
||||||
|
|
||||||
|
clone(cube)
|
Before Width: | Height: | Size: 61 KiB After Width: | Height: | Size: 61 KiB |
Before Width: | Height: | Size: 100 KiB After Width: | Height: | Size: 100 KiB |
Before Width: | Height: | Size: 72 KiB After Width: | Height: | Size: 72 KiB |
Before Width: | Height: | Size: 104 KiB After Width: | Height: | Size: 104 KiB |
Before Width: | Height: | Size: 87 KiB After Width: | Height: | Size: 87 KiB |