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
|
||||
to other modules.
|
||||
|
||||
```
|
||||
```kcl
|
||||
// util.kcl
|
||||
export fn increment(x) {
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
```
|
||||
```kcl
|
||||
// util.kcl
|
||||
export fn increment(x) {
|
||||
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"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 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
|
||||
@ -69,25 +274,17 @@ import "tests/inputs/cube.obj"
|
||||
// Use `cube` just like a KCL object.
|
||||
```
|
||||
|
||||
```norun
|
||||
import "tests/inputs/cube-2.sldprt" as cube
|
||||
```kcl
|
||||
import "tests/inputs/cube.sldprt" as cube
|
||||
|
||||
// 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
|
||||
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)
|
||||
import "tests/inputs/cube.obj"
|
||||
```
|
||||
@ -110,97 +307,55 @@ Coordinate systems:
|
||||
- `opengl`, forward: +Z, up: +Y, handedness: right
|
||||
- `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
|
||||
stages—reading, initializing (background render start), and invocation (blocking)
|
||||
—and structure your code to defer blocking operations until the end.
|
||||
|
||||
#### Foreign Import Execution Stages
|
||||
### Foreign import execution stages
|
||||
|
||||
1. **Import (Read) Stage**
|
||||
```norun
|
||||
1. **Import (Read / Initialization) Stage**
|
||||
```kcl
|
||||
import "tests/inputs/cube.step" as cube
|
||||
```
|
||||
- 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**
|
||||
```norun
|
||||
2. **Invocation (Blocking) Stage**
|
||||
```kcl
|
||||
import "tests/inputs/cube.step" as cube
|
||||
|
||||
myCube = cube // <- This line starts background rendering
|
||||
```
|
||||
- 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.
|
||||
|
||||
3. **Invocation (Blocking) Stage**
|
||||
```norun
|
||||
import "tests/inputs/cube.step" as cube
|
||||
|
||||
myCube = cube
|
||||
|
||||
myCube
|
||||
|> translate(z=10) // <- This line blocks
|
||||
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.
|
||||
- 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
|
||||
|
||||
#### Best Practices
|
||||
#### 1. Defer blocking calls
|
||||
|
||||
##### 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
|
||||
```kcl
|
||||
import "tests/inputs/cube.step" as cube // 1) Read / 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
|
||||
|> 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]
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn test_examples() {
|
||||
async fn kcl_test_examples() {
|
||||
let std = walk_prelude();
|
||||
let mut errs = Vec::new();
|
||||
for d in std {
|
||||
|
@ -459,7 +459,7 @@ impl ExecutorContext {
|
||||
exec_state.add_path_to_source_id(resolved_path.clone(), id);
|
||||
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?;
|
||||
exec_state.add_module(id, resolved_path, ModuleRepr::Foreign(geom));
|
||||
exec_state.add_module(id, resolved_path, ModuleRepr::Foreign(geom, None));
|
||||
Ok(id)
|
||||
}
|
||||
ImportPath::Std { .. } => {
|
||||
@ -501,7 +501,7 @@ impl ExecutorContext {
|
||||
*cache = Some((val, er, items.clone()));
|
||||
(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(),
|
||||
source_ranges: vec![geom.source_range],
|
||||
})),
|
||||
@ -546,9 +546,20 @@ impl ExecutorContext {
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
}
|
||||
ModuleRepr::Foreign(geom) => super::import::send_to_engine(geom.clone(), self)
|
||||
ModuleRepr::Foreign(_, Some(imported)) => Ok(Some(imported.clone())),
|
||||
ModuleRepr::Foreign(geom, cached) => {
|
||||
let result = super::import::send_to_engine(geom.clone(), self)
|
||||
.await
|
||||
.map(|geom| Some(KclValue::ImportedGeometry(geom))),
|
||||
.map(|geom| Some(KclValue::ImportedGeometry(geom)));
|
||||
|
||||
match result {
|
||||
Ok(val) => {
|
||||
*cached = val.clone();
|
||||
Ok(val)
|
||||
}
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
}
|
||||
ModuleRepr::Dummy => unreachable!(),
|
||||
};
|
||||
|
||||
|
@ -752,7 +752,12 @@ impl ExecutorContext {
|
||||
let mut universe = std::collections::HashMap::new();
|
||||
|
||||
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(
|
||||
self,
|
||||
&ModuleRepr::Kcl(program.ast.clone(), None),
|
||||
&mut universe,
|
||||
exec_state,
|
||||
)
|
||||
.await
|
||||
.map_err(|err| {
|
||||
let module_id_to_module_path: IndexMap<ModuleId, ModulePath> = exec_state
|
||||
@ -799,16 +804,12 @@ impl ExecutorContext {
|
||||
|
||||
#[allow(clippy::type_complexity)]
|
||||
let (results_tx, mut results_rx): (
|
||||
tokio::sync::mpsc::Sender<(
|
||||
ModuleId,
|
||||
ModulePath,
|
||||
Result<(Option<KclValue>, EnvironmentRef, Vec<String>), KclError>,
|
||||
)>,
|
||||
tokio::sync::mpsc::Sender<(ModuleId, ModulePath, Result<ModuleRepr, KclError>)>,
|
||||
tokio::sync::mpsc::Receiver<_>,
|
||||
) = tokio::sync::mpsc::channel(1);
|
||||
|
||||
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 {
|
||||
message: format!("Module {module} not found in universe"),
|
||||
source_ranges: Default::default(),
|
||||
@ -816,12 +817,41 @@ impl ExecutorContext {
|
||||
};
|
||||
let module_id = *module_id;
|
||||
let module_path = module_path.clone();
|
||||
let program = program.clone();
|
||||
let repr = repr.clone();
|
||||
let exec_state = exec_state.clone();
|
||||
let exec_ctxt = self.clone();
|
||||
let results_tx = results_tx.clone();
|
||||
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")]
|
||||
{
|
||||
wasm_bindgen_futures::spawn_local(async move {
|
||||
@ -829,14 +859,13 @@ impl ExecutorContext {
|
||||
let mut exec_state = exec_state;
|
||||
let exec_ctxt = exec_ctxt;
|
||||
|
||||
let result = exec_ctxt
|
||||
.exec_module_from_ast(
|
||||
&program,
|
||||
let result = exec_module(
|
||||
&exec_ctxt,
|
||||
&repr,
|
||||
module_id,
|
||||
&module_path,
|
||||
&mut exec_state,
|
||||
source_range,
|
||||
false,
|
||||
)
|
||||
.await;
|
||||
|
||||
@ -852,14 +881,13 @@ impl ExecutorContext {
|
||||
let mut exec_state = exec_state;
|
||||
let exec_ctxt = exec_ctxt;
|
||||
|
||||
let result = exec_ctxt
|
||||
.exec_module_from_ast(
|
||||
&program,
|
||||
let result = exec_module(
|
||||
&exec_ctxt,
|
||||
&repr,
|
||||
module_id,
|
||||
&module_path,
|
||||
&mut exec_state,
|
||||
source_range,
|
||||
false,
|
||||
)
|
||||
.await;
|
||||
|
||||
@ -875,13 +903,24 @@ impl ExecutorContext {
|
||||
|
||||
while let Some((module_id, _, result)) = results_rx.recv().await {
|
||||
match result {
|
||||
Ok((val, session_data, variables)) => {
|
||||
Ok(new_repr) => {
|
||||
let mut repr = exec_state.global.module_infos[&module_id].take_repr();
|
||||
|
||||
let ModuleRepr::Kcl(_, cache) = &mut repr else {
|
||||
continue;
|
||||
match &mut repr {
|
||||
ModuleRepr::Kcl(_, cache) => {
|
||||
let ModuleRepr::Kcl(_, session_data) = new_repr else {
|
||||
unreachable!();
|
||||
};
|
||||
*cache = Some((val, session_data, variables));
|
||||
*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);
|
||||
}
|
||||
|
@ -124,7 +124,7 @@ pub enum ModuleRepr {
|
||||
Root,
|
||||
// AST, memory, exported names
|
||||
Kcl(Node<Program>, Option<(Option<KclValue>, EnvironmentRef, Vec<String>)>),
|
||||
Foreign(PreImportedGeometry),
|
||||
Foreign(PreImportedGeometry, Option<KclValue>),
|
||||
Dummy,
|
||||
}
|
||||
|
||||
|
@ -2579,3 +2579,24 @@ mod loop_tag {
|
||||
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::{
|
||||
errors::KclErrorDetails,
|
||||
modules::{ModulePath, ModuleRepr},
|
||||
parsing::ast::types::{ImportPath, ImportStatement, Node as AstNode, NodeRef, Program},
|
||||
parsing::ast::types::{ImportPath, ImportStatement, Node as AstNode},
|
||||
walk::{Node, Visitable},
|
||||
ExecState, ExecutorContext, KclError, ModuleId, SourceRange,
|
||||
};
|
||||
@ -20,7 +20,7 @@ type Dependency = (String, String);
|
||||
|
||||
type Graph = Vec<Dependency>;
|
||||
|
||||
type DependencyInfo = (AstNode<ImportStatement>, ModuleId, ModulePath, AstNode<Program>);
|
||||
type DependencyInfo = (AstNode<ImportStatement>, ModuleId, ModulePath, ModuleRepr);
|
||||
type Universe = HashMap<String, DependencyInfo>;
|
||||
|
||||
/// 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> {
|
||||
let mut graph = Graph::new();
|
||||
|
||||
for (name, (_, _, _, program)) in progs.iter() {
|
||||
for (name, (_, _, _, repr)) in progs.iter() {
|
||||
graph.extend(
|
||||
import_dependencies(program, ctx)?
|
||||
import_dependencies(repr, ctx)?
|
||||
.into_iter()
|
||||
.map(|(dependency, _, _)| (name.clone(), dependency))
|
||||
.collect::<Vec<_>>(),
|
||||
@ -118,18 +118,19 @@ fn topsort(all_modules: &[&str], graph: Graph) -> Result<Vec<Vec<String>>, KclEr
|
||||
|
||||
type ImportDependencies = Vec<(String, AstNode<ImportStatement>, ModulePath)>;
|
||||
|
||||
pub(crate) fn import_dependencies(
|
||||
prog: NodeRef<Program>,
|
||||
ctx: &ExecutorContext,
|
||||
) -> Result<ImportDependencies, KclError> {
|
||||
let ret = Arc::new(Mutex::new(vec![]));
|
||||
pub(crate) fn import_dependencies(repr: &ModuleRepr, ctx: &ExecutorContext) -> Result<ImportDependencies, KclError> {
|
||||
let ModuleRepr::Kcl(prog, _) = repr else {
|
||||
// It has no dependencies, so return an empty list.
|
||||
return Ok(vec![]);
|
||||
};
|
||||
|
||||
let ret = Arc::new(Mutex::new(vec![]));
|
||||
fn walk(ret: Arc<Mutex<ImportDependencies>>, node: Node<'_>, ctx: &ExecutorContext) -> Result<(), KclError> {
|
||||
if let Node::ImportStatement(is) = node {
|
||||
// We only care about Kcl imports for now.
|
||||
if let ImportPath::Kcl { filename } = &is.path {
|
||||
// We only care about Kcl and Foreign imports for now.
|
||||
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.
|
||||
// This is a bit of a hack, but it works for now.
|
||||
ret.lock()
|
||||
@ -141,6 +142,19 @@ pub(crate) fn import_dependencies(
|
||||
})?
|
||||
.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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for child in node.children().iter() {
|
||||
@ -164,11 +178,11 @@ pub(crate) fn import_dependencies(
|
||||
|
||||
pub(crate) async fn import_universe(
|
||||
ctx: &ExecutorContext,
|
||||
prog: NodeRef<'_, Program>,
|
||||
repr: &ModuleRepr,
|
||||
out: &mut Universe,
|
||||
exec_state: &mut ExecState,
|
||||
) -> Result<(), KclError> {
|
||||
let modules = import_dependencies(prog, ctx)?;
|
||||
let modules = import_dependencies(repr, ctx)?;
|
||||
for (filename, import_stmt, module_path) in modules {
|
||||
if out.contains_key(&filename) {
|
||||
continue;
|
||||
@ -178,26 +192,21 @@ pub(crate) async fn import_universe(
|
||||
.open_module(&import_stmt.path, &[], exec_state, Default::default())
|
||||
.await?;
|
||||
|
||||
let program = {
|
||||
let repr = {
|
||||
let Some(module_info) = exec_state.get_module(module_id) else {
|
||||
return Err(KclError::Internal(KclErrorDetails {
|
||||
message: format!("Module {} not found", module_id),
|
||||
source_ranges: vec![import_stmt.into()],
|
||||
}));
|
||||
};
|
||||
let ModuleRepr::Kcl(program, _) = &module_info.repr else {
|
||||
// if it's not a KCL module we can skip it since it has no
|
||||
// dependencies.
|
||||
continue;
|
||||
};
|
||||
program.clone()
|
||||
module_info.repr.clone()
|
||||
};
|
||||
|
||||
out.insert(
|
||||
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(())
|
||||
@ -206,7 +215,7 @@ pub(crate) async fn import_universe(
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::parsing::ast::types::ImportSelector;
|
||||
use crate::parsing::ast::types::{ImportSelector, Program};
|
||||
|
||||
macro_rules! kcl {
|
||||
( $kcl:expr ) => {{
|
||||
@ -224,7 +233,7 @@ mod tests {
|
||||
}),
|
||||
ModuleId::default(),
|
||||
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 |