set scene units based on a module's default units (#5127)

Signed-off-by: Nick Cameron <nrc@ncameron.org>
This commit is contained in:
Nick Cameron
2025-01-22 15:23:55 +13:00
committed by GitHub
parent 67cc4f5835
commit 10789d9c3c
12 changed files with 229 additions and 112 deletions

View File

@ -11,6 +11,11 @@ pub(super) const SETTINGS: &str = "settings";
pub(super) const SETTINGS_UNIT_LENGTH: &str = "defaultLengthUnit";
pub(super) const SETTINGS_UNIT_ANGLE: &str = "defaultAngleUnit";
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub(super) enum AnnotationScope {
Module,
}
pub(super) fn expect_properties<'a>(
for_key: &'static str,
annotation: &'a NonCodeValue,

View File

@ -622,6 +622,19 @@ impl From<crate::UnitLength> for UnitLen {
}
}
impl From<UnitLen> for crate::UnitLength {
fn from(unit: UnitLen) -> Self {
match unit {
UnitLen::Cm => crate::UnitLength::Cm,
UnitLen::Feet => crate::UnitLength::Ft,
UnitLen::Inches => crate::UnitLength::In,
UnitLen::M => crate::UnitLength::M,
UnitLen::Mm => crate::UnitLength::Mm,
UnitLen::Yards => crate::UnitLength::Yd,
}
}
}
#[derive(Debug, Default, Clone, Copy, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema, Eq)]
#[ts(export)]
#[serde(tag = "type")]

View File

@ -2,6 +2,7 @@
use std::{path::PathBuf, sync::Arc};
use annotations::AnnotationScope;
use anyhow::Result;
use artifact::build_artifact_graph;
use async_recursion::async_recursion;
@ -2316,6 +2317,36 @@ impl ExecutorContext {
}
}
async fn handle_annotations(
&self,
annotations: impl Iterator<Item = (&NonCodeValue, SourceRange)>,
scope: AnnotationScope,
exec_state: &mut ExecState,
) -> Result<(), KclError> {
for (annotation, source_range) in annotations {
if annotation.annotation_name() == Some(annotations::SETTINGS) {
if scope == AnnotationScope::Module {
let old_units = exec_state.length_unit();
exec_state
.mod_local
.settings
.update_from_annotation(annotation, source_range)?;
let new_units = exec_state.length_unit();
if old_units != new_units {
self.engine.set_units(new_units.into(), source_range).await?;
}
} else {
return Err(KclError::Semantic(KclErrorDetails {
message: "Settings can only be modified at the top level scope of a file".to_owned(),
source_ranges: vec![source_range],
}));
}
}
// TODO warn on unknown annotations
}
Ok(())
}
/// Execute an AST's program.
#[async_recursion]
pub(crate) async fn inner_execute<'a>(
@ -2324,21 +2355,16 @@ impl ExecutorContext {
exec_state: &mut ExecState,
body_type: BodyType,
) -> Result<Option<KclValue>, KclError> {
if let Some((annotation, source_range)) = program
.non_code_meta
.start_nodes
.iter()
.filter_map(|n| {
n.annotation(annotations::SETTINGS)
.map(|result| (result, n.as_source_range()))
})
.next()
{
exec_state
.mod_local
.settings
.update_from_annotation(annotation, source_range)?;
}
self.handle_annotations(
program
.non_code_meta
.start_nodes
.iter()
.filter_map(|n| n.annotation().map(|result| (result, n.as_source_range()))),
AnnotationScope::Module,
exec_state,
)
.await?;
let mut last_expr = None;
// Iterate over the body of the program.
@ -2521,6 +2547,7 @@ impl ExecutorContext {
exec_kind: ExecutionKind,
source_range: SourceRange,
) -> Result<(Option<KclValue>, ProgramMemory, Vec<String>), KclError> {
let old_units = exec_state.length_unit();
// TODO It sucks that we have to clone the whole module AST here
let info = exec_state.global.module_infos[&module_id].clone();
@ -2537,7 +2564,11 @@ impl ExecutorContext {
.inner_execute(&info.parsed.unwrap(), exec_state, crate::execution::BodyType::Root)
.await;
let new_units = exec_state.length_unit();
std::mem::swap(&mut exec_state.mod_local, &mut local_state);
if new_units != old_units {
self.engine.set_units(old_units.into(), Default::default()).await?;
}
self.engine.replace_execution_kind(original_execution);
let result = result.map_err(|err| {

View File

@ -1011,9 +1011,9 @@ impl NonCodeNode {
}
}
pub fn annotation(&self, expected_name: &str) -> Option<&NonCodeValue> {
pub fn annotation(&self) -> Option<&NonCodeValue> {
match &self.value {
a @ NonCodeValue::Annotation { name, .. } if name.name == expected_name => Some(a),
a @ NonCodeValue::Annotation { .. } => Some(a),
_ => None,
}
}
@ -1071,6 +1071,15 @@ pub enum NonCodeValue {
},
}
impl NonCodeValue {
pub fn annotation_name(&self) -> Option<&str> {
match self {
NonCodeValue::Annotation { name, .. } => Some(&name.name),
_ => None,
}
}
}
#[derive(Debug, Default, Clone, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)]
#[serde(rename_all = "camelCase")]

View File

@ -119,7 +119,7 @@ async fn execute(test_name: &str, render_to_png: bool) {
.global
.artifact_graph
.to_mermaid_flowchart()
.unwrap_or_else(|e| format!("Failed to convert artifact graph to mind map: {e}"));
.unwrap_or_else(|e| format!("Failed to convert artifact graph to flowchart: {e}"));
// Change the snapshot suffix so that it is rendered as a
// Markdown file in GitHub.
insta::assert_binary_snapshot!("artifact_graph_flowchart.md", flowchart.as_bytes().to_owned());

View File

@ -1,7 +1,6 @@
---
source: kcl/src/simulation_tests.rs
description: Artifact commands import_whole.kcl
snapshot_kind: text
---
[
{
@ -286,7 +285,19 @@ snapshot_kind: text
"cmdId": "[uuid]",
"range": [
0,
19,
35,
1
],
"command": {
"type": "set_scene_units",
"unit": "in"
}
},
{
"cmdId": "[uuid]",
"range": [
36,
55,
1
],
"command": {
@ -314,8 +325,8 @@ snapshot_kind: text
{
"cmdId": "[uuid]",
"range": [
25,
68,
61,
104,
1
],
"command": {
@ -334,8 +345,8 @@ snapshot_kind: text
{
"cmdId": "[uuid]",
"range": [
25,
68,
61,
104,
1
],
"command": {
@ -345,8 +356,8 @@ snapshot_kind: text
{
"cmdId": "[uuid]",
"range": [
25,
68,
61,
104,
1
],
"command": {
@ -362,8 +373,8 @@ snapshot_kind: text
{
"cmdId": "[uuid]",
"range": [
25,
68,
61,
104,
1
],
"command": {
@ -391,8 +402,8 @@ snapshot_kind: text
{
"cmdId": "[uuid]",
"range": [
25,
68,
61,
104,
1
],
"command": {
@ -403,8 +414,8 @@ snapshot_kind: text
{
"cmdId": "[uuid]",
"range": [
74,
88,
110,
124,
1
],
"command": {
@ -423,8 +434,8 @@ snapshot_kind: text
{
"cmdId": "[uuid]",
"range": [
74,
88,
110,
124,
1
],
"command": {
@ -437,8 +448,8 @@ snapshot_kind: text
{
"cmdId": "[uuid]",
"range": [
74,
88,
110,
124,
1
],
"command": {
@ -448,8 +459,8 @@ snapshot_kind: text
{
"cmdId": "[uuid]",
"range": [
74,
88,
110,
124,
1
],
"command": {
@ -460,8 +471,8 @@ snapshot_kind: text
{
"cmdId": "[uuid]",
"range": [
74,
88,
110,
124,
1
],
"command": {
@ -473,8 +484,8 @@ snapshot_kind: text
{
"cmdId": "[uuid]",
"range": [
74,
88,
110,
124,
1
],
"command": {
@ -487,8 +498,8 @@ snapshot_kind: text
{
"cmdId": "[uuid]",
"range": [
74,
88,
110,
124,
1
],
"command": {
@ -501,8 +512,20 @@ snapshot_kind: text
{
"cmdId": "[uuid]",
"range": [
49,
96,
0,
0,
0
],
"command": {
"type": "set_scene_units",
"unit": "mm"
}
},
{
"cmdId": "[uuid]",
"range": [
83,
130,
0
],
"command": {

View File

@ -1,12 +1,12 @@
```mermaid
flowchart LR
subgraph path2 [Path]
2["Path<br>[25, 68, 1]"]
3["Segment<br>[25, 68, 1]"]
2["Path<br>[61, 104, 1]"]
3["Segment<br>[61, 104, 1]"]
4[Solid2d]
end
1["Plane<br>[0, 19, 1]"]
5["Sweep Extrusion<br>[74, 88, 1]"]
1["Plane<br>[36, 55, 1]"]
5["Sweep Extrusion<br>[110, 124, 1]"]
6[Wall]
7["Cap Start"]
8["Cap End"]

View File

@ -6,85 +6,85 @@ description: Result of parsing import_whole.kcl
"Ok": {
"body": [
{
"end": 32,
"end": 66,
"path": "exported_mod.kcl",
"selector": {
"type": "None",
"alias": {
"end": 32,
"end": 66,
"name": "foo",
"start": 29,
"start": 63,
"type": "Identifier"
}
},
"start": 0,
"start": 34,
"type": "ImportStatement",
"type": "ImportStatement"
},
{
"declaration": {
"end": 96,
"end": 130,
"id": {
"end": 37,
"end": 71,
"name": "bar",
"start": 34,
"start": 68,
"type": "Identifier"
},
"init": {
"body": [
{
"end": 43,
"end": 77,
"name": "foo",
"start": 40,
"start": 74,
"type": "Identifier",
"type": "Identifier"
},
{
"arguments": [
{
"end": 92,
"end": 126,
"properties": [
{
"end": 72,
"end": 106,
"key": {
"end": 62,
"end": 96,
"name": "faces",
"start": 57,
"start": 91,
"type": "Identifier"
},
"start": 57,
"start": 91,
"type": "ObjectProperty",
"value": {
"elements": [
{
"end": 71,
"end": 105,
"raw": "'end'",
"start": 66,
"start": 100,
"type": "Literal",
"type": "Literal",
"value": "end"
}
],
"end": 72,
"start": 65,
"end": 106,
"start": 99,
"type": "ArrayExpression",
"type": "ArrayExpression"
}
},
{
"end": 90,
"end": 124,
"key": {
"end": 83,
"end": 117,
"name": "thickness",
"start": 74,
"start": 108,
"type": "Identifier"
},
"start": 74,
"start": 108,
"type": "ObjectProperty",
"value": {
"end": 90,
"end": 124,
"raw": "0.25",
"start": 86,
"start": 120,
"type": "Literal",
"type": "Literal",
"value": {
@ -94,51 +94,51 @@ description: Result of parsing import_whole.kcl
}
}
],
"start": 55,
"start": 89,
"type": "ObjectExpression",
"type": "ObjectExpression"
},
{
"end": 95,
"start": 94,
"end": 129,
"start": 128,
"type": "PipeSubstitution",
"type": "PipeSubstitution"
}
],
"callee": {
"end": 54,
"end": 88,
"name": "shell",
"start": 49,
"start": 83,
"type": "Identifier"
},
"end": 96,
"start": 49,
"end": 130,
"start": 83,
"type": "CallExpression",
"type": "CallExpression"
}
],
"end": 96,
"start": 40,
"end": 130,
"start": 74,
"type": "PipeExpression",
"type": "PipeExpression"
},
"start": 34,
"start": 68,
"type": "VariableDeclarator"
},
"end": 96,
"end": 130,
"kind": "const",
"start": 34,
"start": 68,
"type": "VariableDeclaration",
"type": "VariableDeclaration"
}
],
"end": 97,
"end": 131,
"nonCodeMeta": {
"nonCodeNodes": {
"0": [
{
"end": 34,
"start": 32,
"end": 68,
"start": 66,
"type": "NonCodeNode",
"value": {
"type": "newLine"
@ -146,7 +146,42 @@ description: Result of parsing import_whole.kcl
}
]
},
"startNodes": []
"startNodes": [
{
"end": 33,
"start": 0,
"type": "NonCodeNode",
"value": {
"type": "annotation",
"name": {
"end": 9,
"name": "settings",
"start": 1,
"type": "Identifier"
},
"properties": [
{
"end": 32,
"key": {
"end": 27,
"name": "defaultLengthUnit",
"start": 10,
"type": "Identifier"
},
"start": 10,
"type": "ObjectProperty",
"value": {
"end": 32,
"name": "mm",
"start": 30,
"type": "Identifier",
"type": "Identifier"
}
}
]
}
}
]
},
"start": 0
}

View File

@ -1,3 +1,4 @@
@settings(defaultLengthUnit = inch)
startSketchOn('XY')
|> circle({ center = [5, 5], radius = 10 }, %)
|> extrude(10, %)

View File

@ -1,3 +1,4 @@
@settings(defaultLengthUnit = mm)
import "exported_mod.kcl" as foo
bar = foo

View File

@ -1,30 +1,29 @@
---
source: kcl/src/simulation_tests.rs
description: Operations executed import_whole.kcl
snapshot_kind: text
---
[
{
"labeledArgs": {
"data": {
"sourceRange": [
55,
92,
89,
126,
0
]
},
"solid_set": {
"sourceRange": [
94,
95,
128,
129,
0
]
}
},
"name": "shell",
"sourceRange": [
49,
96,
83,
130,
0
],
"type": "StdLibCall",

View File

@ -36,8 +36,8 @@ description: Program memory after executing import_whole.kcl
"faceId": "[uuid]",
"id": "[uuid]",
"sourceRange": [
25,
68,
61,
104,
1
],
"tag": null,
@ -52,8 +52,8 @@ description: Program memory after executing import_whole.kcl
"__geoMeta": {
"id": "[uuid]",
"sourceRange": [
25,
68,
61,
104,
1
]
},
@ -100,7 +100,7 @@ description: Program memory after executing import_whole.kcl
"z": 1.0
},
"units": {
"type": "Mm"
"type": "Inches"
},
"__meta": []
},
@ -117,20 +117,20 @@ description: Program memory after executing import_whole.kcl
"__geoMeta": {
"id": "[uuid]",
"sourceRange": [
25,
68,
61,
104,
1
]
}
},
"units": {
"type": "Mm"
"type": "Inches"
},
"__meta": [
{
"sourceRange": [
25,
68,
61,
104,
1
]
}
@ -140,13 +140,13 @@ description: Program memory after executing import_whole.kcl
"startCapId": "[uuid]",
"endCapId": "[uuid]",
"units": {
"type": "Mm"
"type": "Inches"
},
"__meta": [
{
"sourceRange": [
25,
68,
61,
104,
1
]
}
@ -159,8 +159,8 @@ description: Program memory after executing import_whole.kcl
"__meta": [
{
"sourceRange": [
0,
32,
34,
66,
0
]
}