Display numeric units in the variables pane (#6683)

This commit is contained in:
Jonathan Tran
2025-05-05 23:40:18 -04:00
committed by GitHub
parent 32db31e6c3
commit 1e056cfd8a
69 changed files with 602 additions and 163 deletions

View File

@ -126685,7 +126685,8 @@
{
"type": "object",
"required": [
"type"
"type",
"value"
],
"properties": {
"type": {
@ -126693,6 +126694,9 @@
"enum": [
"Function"
]
},
"value": {
"$ref": "#/components/schemas/FunctionSource"
}
}
},
@ -128804,6 +128808,9 @@
}
}
},
"FunctionSource": {
"type": "null"
},
"ModuleId": {
"description": "Identifier of a source file. Uses a u32 to keep the size small.",
"type": "integer",
@ -129165,7 +129172,8 @@
{
"type": "object",
"required": [
"type"
"type",
"value"
],
"properties": {
"type": {
@ -129173,6 +129181,9 @@
"enum": [
"Function"
]
},
"value": {
"$ref": "#/components/schemas/FunctionSource"
}
}
},
@ -131284,6 +131295,9 @@
}
}
},
"FunctionSource": {
"type": "null"
},
"ModuleId": {
"description": "Identifier of a source file. Uses a u32 to keep the size small.",
"type": "integer",
@ -131649,7 +131663,8 @@
{
"type": "object",
"required": [
"type"
"type",
"value"
],
"properties": {
"type": {
@ -131657,6 +131672,9 @@
"enum": [
"Function"
]
},
"value": {
"$ref": "#/components/schemas/FunctionSource"
}
}
},
@ -133768,6 +133786,9 @@
}
}
},
"FunctionSource": {
"type": "null"
},
"ModuleId": {
"description": "Identifier of a source file. Uses a u32 to keep the size small.",
"type": "integer",
@ -203436,7 +203457,8 @@
{
"type": "object",
"required": [
"type"
"type",
"value"
],
"properties": {
"type": {
@ -203444,6 +203466,9 @@
"enum": [
"Function"
]
},
"value": {
"$ref": "#/components/schemas/FunctionSource"
}
}
},
@ -205555,6 +205580,9 @@
}
}
},
"FunctionSource": {
"type": "null"
},
"ModuleId": {
"description": "Identifier of a source file. Uses a u32 to keep the size small.",
"type": "integer",
@ -205914,7 +205942,8 @@
{
"type": "object",
"required": [
"type"
"type",
"value"
],
"properties": {
"type": {
@ -205922,6 +205951,9 @@
"enum": [
"Function"
]
},
"value": {
"$ref": "#/components/schemas/FunctionSource"
}
}
},
@ -206708,7 +206740,8 @@
{
"type": "object",
"required": [
"type"
"type",
"value"
],
"properties": {
"type": {
@ -206716,6 +206749,9 @@
"enum": [
"Function"
]
},
"value": {
"$ref": "#/components/schemas/FunctionSource"
}
}
},
@ -208419,6 +208455,9 @@
}
}
},
"FunctionSource": {
"type": "null"
},
"ModuleId": {
"description": "Identifier of a source file. Uses a u32 to keep the size small.",
"type": "integer",
@ -213896,7 +213935,8 @@
{
"type": "object",
"required": [
"type"
"type",
"value"
],
"properties": {
"type": {
@ -213904,6 +213944,9 @@
"enum": [
"Function"
]
},
"value": {
"$ref": "#/components/schemas/FunctionSource"
}
}
},
@ -216015,6 +216058,9 @@
}
}
},
"FunctionSource": {
"type": "null"
},
"ModuleId": {
"description": "Identifier of a source file. Uses a u32 to keep the size small.",
"type": "integer",
@ -216373,7 +216419,8 @@
{
"type": "object",
"required": [
"type"
"type",
"value"
],
"properties": {
"type": {
@ -216381,6 +216428,9 @@
"enum": [
"Function"
]
},
"value": {
"$ref": "#/components/schemas/FunctionSource"
}
}
},
@ -216759,7 +216809,8 @@
{
"type": "object",
"required": [
"type"
"type",
"value"
],
"properties": {
"type": {
@ -216767,6 +216818,9 @@
"enum": [
"Function"
]
},
"value": {
"$ref": "#/components/schemas/FunctionSource"
}
}
},
@ -218878,6 +218932,9 @@
}
}
},
"FunctionSource": {
"type": "null"
},
"ModuleId": {
"description": "Identifier of a source file. Uses a u32 to keep the size small.",
"type": "integer",
@ -219237,7 +219294,8 @@
{
"type": "object",
"required": [
"type"
"type",
"value"
],
"properties": {
"type": {
@ -219245,6 +219303,9 @@
"enum": [
"Function"
]
},
"value": {
"$ref": "#/components/schemas/FunctionSource"
}
}
},
@ -220031,7 +220092,8 @@
{
"type": "object",
"required": [
"type"
"type",
"value"
],
"properties": {
"type": {
@ -220039,6 +220101,9 @@
"enum": [
"Function"
]
},
"value": {
"$ref": "#/components/schemas/FunctionSource"
}
}
},
@ -221742,6 +221807,9 @@
}
}
},
"FunctionSource": {
"type": "null"
},
"ModuleId": {
"description": "Identifier of a source file. Uses a u32 to keep the size small.",
"type": "integer",
@ -222123,7 +222191,8 @@
{
"type": "object",
"required": [
"type"
"type",
"value"
],
"properties": {
"type": {
@ -222131,6 +222200,9 @@
"enum": [
"Function"
]
},
"value": {
"$ref": "#/components/schemas/FunctionSource"
}
}
},
@ -224242,6 +224314,9 @@
}
}
},
"FunctionSource": {
"type": "null"
},
"ModuleId": {
"description": "Identifier of a source file. Uses a u32 to keep the size small.",
"type": "integer",
@ -224600,7 +224675,8 @@
{
"type": "object",
"required": [
"type"
"type",
"value"
],
"properties": {
"type": {
@ -224608,6 +224684,9 @@
"enum": [
"Function"
]
},
"value": {
"$ref": "#/components/schemas/FunctionSource"
}
}
},
@ -224986,7 +225065,8 @@
{
"type": "object",
"required": [
"type"
"type",
"value"
],
"properties": {
"type": {
@ -224994,6 +225074,9 @@
"enum": [
"Function"
]
},
"value": {
"$ref": "#/components/schemas/FunctionSource"
}
}
},
@ -227105,6 +227188,9 @@
}
}
},
"FunctionSource": {
"type": "null"
},
"ModuleId": {
"description": "Identifier of a source file. Uses a u32 to keep the size small.",
"type": "integer",
@ -227466,7 +227552,8 @@
{
"type": "object",
"required": [
"type"
"type",
"value"
],
"properties": {
"type": {
@ -227474,6 +227561,9 @@
"enum": [
"Function"
]
},
"value": {
"$ref": "#/components/schemas/FunctionSource"
}
}
},
@ -229585,6 +229675,9 @@
}
}
},
"FunctionSource": {
"type": "null"
},
"ModuleId": {
"description": "Identifier of a source file. Uses a u32 to keep the size small.",
"type": "integer",
@ -229944,7 +230037,8 @@
{
"type": "object",
"required": [
"type"
"type",
"value"
],
"properties": {
"type": {
@ -229952,6 +230046,9 @@
"enum": [
"Function"
]
},
"value": {
"$ref": "#/components/schemas/FunctionSource"
}
}
},
@ -230738,7 +230835,8 @@
{
"type": "object",
"required": [
"type"
"type",
"value"
],
"properties": {
"type": {
@ -230746,6 +230844,9 @@
"enum": [
"Function"
]
},
"value": {
"$ref": "#/components/schemas/FunctionSource"
}
}
},
@ -232449,6 +232550,9 @@
}
}
},
"FunctionSource": {
"type": "null"
},
"ModuleId": {
"description": "Identifier of a source file. Uses a u32 to keep the size small.",
"type": "integer",

View File

@ -170,7 +170,9 @@ extrude001 = extrude(sketch001, length = 50)
await variablesTabButton.click()
// expect to see "myVar:5"
await expect(
page.locator('.pretty-json-container >> text=myVar:5')
// There's a double quote before the number value since the units are
// formatted into a string.
page.locator('.pretty-json-container >> text=myVar:"5')
).toBeVisible()
// change 5 to 67
@ -180,7 +182,7 @@ extrude001 = extrude(sketch001, length = 50)
await page.keyboard.type('67')
await expect(
page.locator('.pretty-json-container >> text=myVar:67')
page.locator('.pretty-json-container >> text=myVar:"67')
).toBeVisible()
})
test('ProgramMemory can be serialised', async ({ page, homePage }) => {

View File

@ -83,9 +83,9 @@ pub enum KclValue {
value: Box<Helix>,
},
ImportedGeometry(ImportedGeometry),
#[ts(skip)]
Function {
#[serde(skip)]
#[serde(serialize_with = "function_value_stub")]
#[ts(type = "null")]
value: FunctionSource,
#[serde(skip)]
meta: Vec<Metadata>,
@ -109,6 +109,13 @@ pub enum KclValue {
},
}
fn function_value_stub<S>(_value: &FunctionSource, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_unit()
}
#[derive(Debug, Clone, PartialEq, Default)]
pub enum FunctionSource {
#[default]

View File

@ -399,7 +399,7 @@ impl fmt::Display for PrimitiveType {
}
}
#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)]
#[serde(tag = "type")]
pub enum NumericType {

137
rust/kcl-lib/src/fmt.rs Normal file
View File

@ -0,0 +1,137 @@
use serde::Serialize;
use crate::{execution::types::NumericType, pretty::NumericSuffix};
/// For the UI, display a number and its type for debugging purposes. This is
/// used by TS.
pub fn human_display_number(value: f64, ty: NumericType) -> String {
match ty {
NumericType::Known(unit_type) => format!("{value}: number({unit_type})"),
NumericType::Default { len, angle } => format!("{value} (no units, defaulting to {len} or {angle})"),
NumericType::Unknown => format!("{value} (number with unknown units)"),
NumericType::Any => format!("{value} (number with any units)"),
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, thiserror::Error)]
#[serde(tag = "type")]
pub enum FormatNumericSuffixError {
#[error("Invalid numeric suffix: {0}")]
Invalid(NumericSuffix),
}
/// For UI code generation, format a number with a suffix. The result must parse
/// as a literal. If it can't be done, returns an error.
///
/// This is used by TS.
pub fn format_number_literal(value: f64, suffix: NumericSuffix) -> Result<String, FormatNumericSuffixError> {
match suffix {
// There isn't a syntactic suffix for these. For unknown, we don't want
// to ever generate the unknown suffix. We currently warn on it, and we
// may remove it in the future.
NumericSuffix::Length | NumericSuffix::Angle | NumericSuffix::Unknown => {
Err(FormatNumericSuffixError::Invalid(suffix))
}
NumericSuffix::None
| NumericSuffix::Count
| NumericSuffix::Mm
| NumericSuffix::Cm
| NumericSuffix::M
| NumericSuffix::Inch
| NumericSuffix::Ft
| NumericSuffix::Yd
| NumericSuffix::Deg
| NumericSuffix::Rad => Ok(format!("{value}{suffix}")),
}
}
#[cfg(test)]
mod tests {
use pretty_assertions::assert_eq;
use super::*;
use crate::execution::types::{UnitAngle, UnitLen, UnitType};
#[test]
fn test_human_display_number() {
assert_eq!(
human_display_number(1.0, NumericType::Known(UnitType::Count)),
"1: number(Count)"
);
assert_eq!(
human_display_number(1.0, NumericType::Known(UnitType::Length(UnitLen::M))),
"1: number(m)"
);
assert_eq!(
human_display_number(1.0, NumericType::Known(UnitType::Length(UnitLen::Mm))),
"1: number(mm)"
);
assert_eq!(
human_display_number(1.0, NumericType::Known(UnitType::Length(UnitLen::Inches))),
"1: number(in)"
);
assert_eq!(
human_display_number(1.0, NumericType::Known(UnitType::Length(UnitLen::Feet))),
"1: number(ft)"
);
assert_eq!(
human_display_number(1.0, NumericType::Known(UnitType::Angle(UnitAngle::Degrees))),
"1: number(deg)"
);
assert_eq!(
human_display_number(1.0, NumericType::Known(UnitType::Angle(UnitAngle::Radians))),
"1: number(rad)"
);
assert_eq!(
human_display_number(
1.0,
NumericType::Default {
len: UnitLen::Mm,
angle: UnitAngle::Degrees,
}
),
"1 (no units, defaulting to mm or deg)"
);
assert_eq!(
human_display_number(
1.0,
NumericType::Default {
len: UnitLen::Feet,
angle: UnitAngle::Radians,
}
),
"1 (no units, defaulting to ft or rad)"
);
assert_eq!(
human_display_number(1.0, NumericType::Unknown),
"1 (number with unknown units)"
);
assert_eq!(human_display_number(1.0, NumericType::Any), "1 (number with any units)");
}
#[test]
fn test_format_number_literal() {
assert_eq!(
format_number_literal(1.0, NumericSuffix::Length),
Err(FormatNumericSuffixError::Invalid(NumericSuffix::Length))
);
assert_eq!(
format_number_literal(1.0, NumericSuffix::Angle),
Err(FormatNumericSuffixError::Invalid(NumericSuffix::Angle))
);
assert_eq!(format_number_literal(1.0, NumericSuffix::None), Ok("1".to_owned()));
assert_eq!(format_number_literal(1.0, NumericSuffix::Count), Ok("1_".to_owned()));
assert_eq!(format_number_literal(1.0, NumericSuffix::Mm), Ok("1mm".to_owned()));
assert_eq!(format_number_literal(1.0, NumericSuffix::Cm), Ok("1cm".to_owned()));
assert_eq!(format_number_literal(1.0, NumericSuffix::M), Ok("1m".to_owned()));
assert_eq!(format_number_literal(1.0, NumericSuffix::Inch), Ok("1in".to_owned()));
assert_eq!(format_number_literal(1.0, NumericSuffix::Ft), Ok("1ft".to_owned()));
assert_eq!(format_number_literal(1.0, NumericSuffix::Yd), Ok("1yd".to_owned()));
assert_eq!(format_number_literal(1.0, NumericSuffix::Deg), Ok("1deg".to_owned()));
assert_eq!(format_number_literal(1.0, NumericSuffix::Rad), Ok("1rad".to_owned()));
assert_eq!(
format_number_literal(1.0, NumericSuffix::Unknown),
Err(FormatNumericSuffixError::Invalid(NumericSuffix::Unknown))
);
}
}

View File

@ -61,6 +61,7 @@ mod docs;
mod engine;
mod errors;
mod execution;
mod fmt;
mod fs;
pub mod lint;
mod log;
@ -108,7 +109,10 @@ pub use unparser::{recast_dir, walk_dir};
pub mod exec {
#[cfg(feature = "artifact-graph")]
pub use crate::execution::ArtifactCommand;
pub use crate::execution::{DefaultPlanes, IdGenerator, KclValue, PlaneType, Sketch};
pub use crate::execution::{
types::{NumericType, UnitAngle, UnitLen, UnitType},
DefaultPlanes, IdGenerator, KclValue, PlaneType, Sketch,
};
}
#[cfg(target_arch = "wasm32")]
@ -134,7 +138,10 @@ pub mod std_utils {
}
pub mod pretty {
pub use crate::{parsing::token::NumericSuffix, unparser::format_number};
pub use crate::{
fmt::{format_number_literal, human_display_number},
parsing::token::NumericSuffix,
};
}
#[cfg(feature = "cli")]

View File

@ -8,9 +8,7 @@ use crate::parsing::{
MemberExpression, MemberObject, Node, NonCodeNode, NonCodeValue, ObjectExpression, Parameter, PipeExpression,
Program, TagDeclarator, TypeDeclaration, UnaryExpression, VariableDeclaration, VariableKind,
},
deprecation,
token::NumericSuffix,
DeprecationKind, PIPE_OPERATOR,
deprecation, DeprecationKind, PIPE_OPERATOR,
};
impl Program {
@ -465,11 +463,6 @@ impl TypeDeclaration {
}
}
// Used by TS.
pub fn format_number(value: f64, suffix: NumericSuffix) -> String {
format!("{value}{suffix}")
}
impl Literal {
fn recast(&self) -> String {
match self.value {

View File

@ -4,7 +4,8 @@ description: Variables in memory after executing add_lots.kcl
---
{
"f": {
"type": "Function"
"type": "Function",
"value": null
},
"x": {
"type": "Number",

View File

@ -4,7 +4,8 @@ description: Variables in memory after executing cube.kcl
---
{
"cube": {
"type": "Function"
"type": "Function",
"value": null
},
"myCube": {
"type": "Solid",

View File

@ -4,7 +4,8 @@ description: Variables in memory after executing double_map_fn.kcl
---
{
"increment": {
"type": "Function"
"type": "Function",
"value": null
},
"xs": {
"type": "MixedArray",

View File

@ -435,7 +435,8 @@ description: Variables in memory after executing fillet-and-shell.kcl
}
},
"m25Screw": {
"type": "Function"
"type": "Function",
"value": null
},
"microUsb1Distance": {
"type": "Number",

View File

@ -4,7 +4,8 @@ description: Variables in memory after executing function_sketch.kcl
---
{
"box": {
"type": "Function"
"type": "Function",
"value": null
},
"fnBox": {
"type": "Solid",

View File

@ -4,7 +4,8 @@ description: Variables in memory after executing function_sketch_with_position.k
---
{
"box": {
"type": "Function"
"type": "Function",
"value": null
},
"thing": {
"type": "Solid",

View File

@ -2201,7 +2201,8 @@ description: Variables in memory after executing import_async.kcl
}
},
"leftInvolute": {
"type": "Function"
"type": "Function",
"value": null
},
"module": {
"type": "Number",
@ -2263,7 +2264,8 @@ description: Variables in memory after executing import_async.kcl
}
},
"rightInvolute": {
"type": "Function"
"type": "Function",
"value": null
},
"rs": {
"type": "MixedArray",

View File

@ -4,9 +4,11 @@ description: Variables in memory after executing import_function_not_sketch.kcl
---
{
"one": {
"type": "Function"
"type": "Function",
"value": null
},
"two": {
"type": "Function"
"type": "Function",
"value": null
}
}

View File

@ -4,7 +4,8 @@ description: Variables in memory after executing import_glob.kcl
---
{
"foo": {
"type": "Function"
"type": "Function",
"value": null
},
"three": {
"type": "Number",

View File

@ -4,6 +4,7 @@ description: Variables in memory after executing import_side_effect.kcl
---
{
"foo": {
"type": "Function"
"type": "Function",
"value": null
}
}

View File

@ -4,7 +4,8 @@ description: Variables in memory after executing intersect_cubes.kcl
---
{
"cube": {
"type": "Function"
"type": "Function",
"value": null
},
"fullPart": {
"type": "Solid",

View File

@ -4,6 +4,7 @@ description: Variables in memory after executing 80-20-rail.kcl
---
{
"rail8020": {
"type": "Function"
"type": "Function",
"value": null
}
}

View File

@ -4,10 +4,12 @@ description: Variables in memory after executing bench.kcl
---
{
"armRest": {
"type": "Function"
"type": "Function",
"value": null
},
"backSlats": {
"type": "Function"
"type": "Function",
"value": null
},
"benchLength": {
"type": "Number",
@ -23,10 +25,12 @@ description: Variables in memory after executing bench.kcl
}
},
"connector": {
"type": "Function"
"type": "Function",
"value": null
},
"divider": {
"type": "Function"
"type": "Function",
"value": null
},
"dividerThickness": {
"type": "Number",
@ -42,6 +46,7 @@ description: Variables in memory after executing bench.kcl
}
},
"seatSlats": {
"type": "Function"
"type": "Function",
"value": null
}
}

View File

@ -197,7 +197,8 @@ description: Variables in memory after executing color-cube.kcl
}
},
"sketchRectangle": {
"type": "Function"
"type": "Function",
"value": null
},
"tealPlane": {
"type": "Plane",

View File

@ -4,6 +4,7 @@ description: Variables in memory after executing cycloidal-gear.kcl
---
{
"cycloidalGear": {
"type": "Function"
"type": "Function",
"value": null
}
}

View File

@ -4,13 +4,16 @@ description: Variables in memory after executing dodecahedron.kcl
---
{
"calculateArrayLength": {
"type": "Function"
"type": "Function",
"value": null
},
"createFaceTemplate": {
"type": "Function"
"type": "Function",
"value": null
},
"createIntersection": {
"type": "Function"
"type": "Function",
"value": null
},
"dihedral": {
"type": "Number",

View File

@ -1352,7 +1352,8 @@ description: Variables in memory after executing enclosure.kcl
}
},
"function001": {
"type": "Function"
"type": "Function",
"value": null
},
"height": {
"type": "Number",

View File

@ -774,7 +774,8 @@ description: Variables in memory after executing exhaust-manifold.kcl
}
},
"primaryTube": {
"type": "Function"
"type": "Function",
"value": null
},
"primaryTubeDiameter": {
"type": "Number",

View File

@ -673,7 +673,8 @@ description: Variables in memory after executing focusrite-scarlett-mounting-bra
}
},
"bracketSketch": {
"type": "Function"
"type": "Function",
"value": null
},
"bs": {
"type": "Sketch",

View File

@ -2832,7 +2832,8 @@ description: Variables in memory after executing food-service-spatula.kcl
}
},
"slot": {
"type": "Function"
"type": "Function",
"value": null
},
"slotProfile000": {
"type": "Sketch",

View File

@ -19763,7 +19763,8 @@ description: Variables in memory after executing gear-rack.kcl
]
},
"tooth": {
"type": "Function"
"type": "Function",
"value": null
},
"width": {
"type": "Number",

View File

@ -2201,7 +2201,8 @@ description: Variables in memory after executing gear.kcl
}
},
"leftInvolute": {
"type": "Function"
"type": "Function",
"value": null
},
"module": {
"type": "Number",
@ -2256,7 +2257,8 @@ description: Variables in memory after executing gear.kcl
}
},
"rightInvolute": {
"type": "Function"
"type": "Function",
"value": null
},
"rs": {
"type": "MixedArray",

View File

@ -10850,7 +10850,8 @@ description: Variables in memory after executing gridfinity-baseplate-magnets.kc
}
},
"face": {
"type": "Function"
"type": "Function",
"value": null
},
"firstStep": {
"type": "Number",
@ -10938,10 +10939,12 @@ description: Variables in memory after executing gridfinity-baseplate-magnets.kc
}
},
"magnetBase": {
"type": "Function"
"type": "Function",
"value": null
},
"magnetCenterCutout": {
"type": "Function"
"type": "Function",
"value": null
},
"magnetCutoutExtrude": {
"type": "Solid",

View File

@ -10850,7 +10850,8 @@ description: Variables in memory after executing gridfinity-baseplate.kcl
}
},
"face": {
"type": "Function"
"type": "Function",
"value": null
},
"firstStep": {
"type": "Number",

View File

@ -14220,7 +14220,8 @@ description: Variables in memory after executing gridfinity-bins-stacking-lip.kc
}
},
"face": {
"type": "Function"
"type": "Function",
"value": null
},
"firstStep": {
"type": "Number",
@ -15659,7 +15660,8 @@ description: Variables in memory after executing gridfinity-bins-stacking-lip.kc
]
},
"lipFace": {
"type": "Function"
"type": "Function",
"value": null
},
"lipHeight": {
"type": "Number",

View File

@ -14148,7 +14148,8 @@ description: Variables in memory after executing gridfinity-bins.kcl
}
},
"face": {
"type": "Function"
"type": "Function",
"value": null
},
"firstStep": {
"type": "Number",

View File

@ -17,7 +17,8 @@ description: Variables in memory after executing hex-nut.kcl
}
},
"hexNut": {
"type": "Function"
"type": "Function",
"value": null
},
"thickness": {
"type": "Number",

View File

@ -29,7 +29,8 @@ description: Variables in memory after executing keyboard.kcl
}
},
"keyFn": {
"type": "Function"
"type": "Function",
"value": null
},
"keyHeight": {
"type": "Number",
@ -45,7 +46,8 @@ description: Variables in memory after executing keyboard.kcl
}
},
"o": {
"type": "Function"
"type": "Function",
"value": null
},
"plane001": {
"type": "Object",
@ -2885,6 +2887,7 @@ description: Variables in memory after executing keyboard.kcl
}
},
"z": {
"type": "Function"
"type": "Function",
"value": null
}
}

View File

@ -6262,7 +6262,8 @@ description: Variables in memory after executing kitt.kcl
}
},
"kitEar": {
"type": "Function"
"type": "Function",
"value": null
},
"kitEarDepth": {
"type": "Number",
@ -13308,7 +13309,8 @@ description: Variables in memory after executing kitt.kcl
}
},
"kitLeg": {
"type": "Function"
"type": "Function",
"value": null
},
"kitLegOffset": {
"type": "Number",
@ -22321,7 +22323,8 @@ description: Variables in memory after executing kitt.kcl
}
},
"pixelBox": {
"type": "Function"
"type": "Function",
"value": null
},
"seg01": {
"type": "TagIdentifier",

View File

@ -43,7 +43,8 @@ description: Variables in memory after executing makeup-mirror.kcl
}
},
"armFn": {
"type": "Function"
"type": "Function",
"value": null
},
"armLength": {
"type": "Number",
@ -288,7 +289,8 @@ description: Variables in memory after executing makeup-mirror.kcl
}
},
"hingeFn": {
"type": "Function"
"type": "Function",
"value": null
},
"hingeGap": {
"type": "Number",
@ -1194,7 +1196,8 @@ description: Variables in memory after executing makeup-mirror.kcl
}
},
"mirrorFn": {
"type": "Function"
"type": "Function",
"value": null
},
"mirrorRadius": {
"type": "Number",

View File

@ -435,7 +435,8 @@ description: Variables in memory after executing mounting-plate.kcl
}
},
"rectShape": {
"type": "Function"
"type": "Function",
"value": null
},
"rs": {
"type": "Sketch",

View File

@ -4,7 +4,8 @@ description: Variables in memory after executing pipe-flange-assembly.kcl
---
{
"bolt": {
"type": "Function"
"type": "Function",
"value": null
},
"boltDiameter": {
"type": "Number",
@ -103,7 +104,8 @@ description: Variables in memory after executing pipe-flange-assembly.kcl
"value": "filletEdge"
},
"flange": {
"type": "Function"
"type": "Function",
"value": null
},
"flangeBackDiameter": {
"type": "Number",
@ -240,7 +242,8 @@ description: Variables in memory after executing pipe-flange-assembly.kcl
}
},
"hexNut": {
"type": "Function"
"type": "Function",
"value": null
},
"hexNutDiameter": {
"type": "Number",
@ -321,7 +324,8 @@ description: Variables in memory after executing pipe-flange-assembly.kcl
}
},
"pipe": {
"type": "Function"
"type": "Function",
"value": null
},
"pipeDiameter": {
"type": "Number",
@ -376,7 +380,8 @@ description: Variables in memory after executing pipe-flange-assembly.kcl
}
},
"washer": {
"type": "Function"
"type": "Function",
"value": null
},
"washerInnerDia": {
"type": "Number",

View File

@ -77,7 +77,8 @@ description: Variables in memory after executing walkie-talkie.kcl
"value": 2
},
"button": {
"type": "Function"
"type": "Function",
"value": null
},
"buttonHeight": {
"type": "Number",

View File

@ -4,10 +4,12 @@ description: Variables in memory after executing kw_fn.kcl
---
{
"add": {
"type": "Function"
"type": "Function",
"value": null
},
"increment": {
"type": "Function"
"type": "Function",
"value": null
},
"three": {
"type": "Number",

View File

@ -4,7 +4,8 @@ description: Variables in memory after executing kw_fn_with_defaults.kcl
---
{
"increment": {
"type": "Function"
"type": "Function",
"value": null
},
"twentyOne": {
"type": "Number",

View File

@ -17,7 +17,8 @@ description: Variables in memory after executing loop_tag.kcl
}
},
"calculatePoint": {
"type": "Function"
"type": "Function",
"value": null
},
"closedSketch": {
"type": "Sketch",

View File

@ -4,6 +4,7 @@ description: Variables in memory after executing multi_transform.kcl
---
{
"transform": {
"type": "Function"
"type": "Function",
"value": null
}
}

View File

@ -4,6 +4,7 @@ description: Variables in memory after executing pattern_circular_in_module.kcl
---
{
"thing": {
"type": "Function"
"type": "Function",
"value": null
}
}

View File

@ -4,6 +4,7 @@ description: Variables in memory after executing pattern_linear_in_module.kcl
---
{
"thing": {
"type": "Function"
"type": "Function",
"value": null
}
}

View File

@ -652,7 +652,8 @@ description: Variables in memory after executing pentagon_fillet_sugar.kcl
}
},
"circl": {
"type": "Function"
"type": "Function",
"value": null
},
"p": {
"type": "Solid",

View File

@ -4,10 +4,12 @@ description: Variables in memory after executing pipe_as_arg.kcl
---
{
"cube": {
"type": "Function"
"type": "Function",
"value": null
},
"double": {
"type": "Function"
"type": "Function",
"value": null
},
"myCube": {
"type": "Number",
@ -23,6 +25,7 @@ description: Variables in memory after executing pipe_as_arg.kcl
}
},
"width": {
"type": "Function"
"type": "Function",
"value": null
}
}

View File

@ -223,7 +223,8 @@ description: Variables in memory after executing riddle_small.kcl
}
},
"t": {
"type": "Function"
"type": "Function",
"value": null
},
"xs": {
"type": "Number",

View File

@ -4,7 +4,8 @@ description: Variables in memory after executing rotate_after_fillet.kcl
---
{
"bolt": {
"type": "Function"
"type": "Function",
"value": null
},
"boltDiameter": {
"type": "Number",

View File

@ -4,7 +4,8 @@ description: Variables in memory after executing scale_after_fillet.kcl
---
{
"bolt": {
"type": "Function"
"type": "Function",
"value": null
},
"boltDiameter": {
"type": "Number",

View File

@ -4,10 +4,12 @@ description: Variables in memory after executing sketch_in_object.kcl
---
{
"test": {
"type": "Function"
"type": "Function",
"value": null
},
"test2": {
"type": "Function"
"type": "Function",
"value": null
},
"x": {
"type": "Sketch",

View File

@ -4,7 +4,8 @@ description: Variables in memory after executing sketch_on_face_circle_tagged.kc
---
{
"cube": {
"type": "Function"
"type": "Function",
"value": null
},
"myCircle": {
"type": "TagIdentifier",

View File

@ -4,7 +4,8 @@ description: Variables in memory after executing sketch_on_face_end.kcl
---
{
"cube": {
"type": "Function"
"type": "Function",
"value": null
},
"part001": {
"type": "Solid",

View File

@ -4,7 +4,8 @@ description: Variables in memory after executing sketch_on_face_end_negative_ext
---
{
"cube": {
"type": "Function"
"type": "Function",
"value": null
},
"part001": {
"type": "Solid",

View File

@ -4,7 +4,8 @@ description: Variables in memory after executing sketch_on_face_start.kcl
---
{
"cube": {
"type": "Function"
"type": "Function",
"value": null
},
"foo": {
"type": "Solid",

View File

@ -4,7 +4,8 @@ description: Variables in memory after executing subtract_cylinder_from_cube.kcl
---
{
"cube": {
"type": "Function"
"type": "Function",
"value": null
},
"fullPart": {
"type": "Solid",

View File

@ -4,7 +4,8 @@ description: Variables in memory after executing subtract_doesnt_need_brackets.k
---
{
"cube": {
"type": "Function"
"type": "Function",
"value": null
},
"part001": {
"type": "Solid",

View File

@ -4,7 +4,8 @@ description: Variables in memory after executing translate_after_fillet.kcl
---
{
"bolt": {
"type": "Function"
"type": "Function",
"value": null
},
"boltDiameter": {
"type": "Number",

View File

@ -4,7 +4,8 @@ description: Variables in memory after executing union_cubes.kcl
---
{
"cube": {
"type": "Function"
"type": "Function",
"value": null
},
"fullPart": {
"type": "Solid",

View File

@ -1,7 +1,11 @@
//! Wasm bindings for `kcl`.
use gloo_utils::format::JsValueSerdeExt;
use kcl_lib::{pretty::NumericSuffix, CoreDump, Program, SourceRange};
use kcl_lib::{
exec::{NumericType, UnitAngle, UnitLen, UnitType},
pretty::NumericSuffix,
CoreDump, Program, SourceRange,
};
use wasm_bindgen::prelude::*;
// wasm_bindgen wrapper for lint
@ -50,11 +54,34 @@ pub fn recast_wasm(json_str: &str) -> Result<JsValue, JsError> {
}
#[wasm_bindgen]
pub fn format_number(value: f64, suffix_json: &str) -> Result<String, JsError> {
pub fn format_number_literal(value: f64, suffix_json: &str) -> Result<String, JsError> {
console_error_panic_hook::set_once();
let suffix: NumericSuffix = serde_json::from_str(suffix_json).map_err(JsError::from)?;
Ok(kcl_lib::pretty::format_number(value, suffix))
kcl_lib::pretty::format_number_literal(value, suffix).map_err(JsError::from)
}
#[wasm_bindgen]
pub fn human_display_number(value: f64, ty_json: &str) -> Result<String, String> {
console_error_panic_hook::set_once();
// ts-rs can't handle tuple types, so it mashes all of these types together.
if let Ok(ty) = serde_json::from_str::<NumericType>(ty_json) {
return Ok(kcl_lib::pretty::human_display_number(value, ty));
}
if let Ok(unit_type) = serde_json::from_str::<UnitType>(ty_json) {
let ty = NumericType::Known(unit_type);
return Ok(kcl_lib::pretty::human_display_number(value, ty));
}
if let Ok(unit_len) = serde_json::from_str::<UnitLen>(ty_json) {
let ty = NumericType::Known(UnitType::Length(unit_len));
return Ok(kcl_lib::pretty::human_display_number(value, ty));
}
if let Ok(unit_angle) = serde_json::from_str::<UnitAngle>(ty_json) {
let ty = NumericType::Known(UnitType::Angle(unit_angle));
return Ok(kcl_lib::pretty::human_display_number(value, ty));
}
Err(format!("Invalid type: {ty_json}"))
}
#[wasm_bindgen]

View File

@ -16,6 +16,13 @@ describe('processMemory', () => {
return a - 2
}
otherVar = myFn(5)
nFeet = 2ft
nInches = 2in
nMm = 2mm
nDegrees = 2deg
nRadians = 2rad
nCount = 2_
nUnknown = 1rad * PI
theExtrude = startSketchOn(XY)
|> startProfile(at = [0, 0])
@ -32,8 +39,17 @@ describe('processMemory', () => {
const ast = assertParse(code)
const execState = await enginelessExecutor(ast)
const output = processMemory(execState.variables)
expect(output.myVar).toEqual(5)
expect(output.otherVar).toEqual(3)
expect(output.nFeet).toEqual('2: number(ft)')
expect(output.nInches).toEqual('2: number(in)')
expect(output.nMm).toEqual('2: number(mm)')
expect(output.nDegrees).toEqual('2: number(deg)')
expect(output.nRadians).toEqual('2: number(rad)')
expect(output.nCount).toEqual('2: number(Count)')
expect(output.nUnknown).toEqual(
'3.141592653589793 (number with unknown units)'
)
expect(output.myVar).toEqual('5 (no units, defaulting to mm or deg)')
expect(output.otherVar).toEqual('3 (no units, defaulting to mm or deg)')
expect(output.myFn).toEqual('__function__')
expect(output.theExtrude).toEqual([
{

View File

@ -11,7 +11,7 @@ import { useModelingContext } from '@src/hooks/useModelingContext'
import { useResolvedTheme } from '@src/hooks/useResolvedTheme'
import { useKclContext } from '@src/lang/KclProvider'
import type { VariableMap } from '@src/lang/wasm'
import { sketchFromKclValueOptional } from '@src/lang/wasm'
import { humanDisplayNumber, sketchFromKclValueOptional } from '@src/lang/wasm'
import { Reason, trap } from '@src/lib/trap'
export const MemoryPaneMenu = () => {
@ -81,14 +81,12 @@ export const MemoryPane = () => {
}
export const processMemory = (variables: VariableMap) => {
const processedMemory: any = {}
const processedMemory: Record<
string,
string | number | boolean | object | undefined
> = {}
for (const [key, val] of Object.entries(variables)) {
if (val === undefined) continue
if (
val.type === 'Sketch' ||
// @ts-ignore
val.type !== 'Function'
) {
const sk = sketchFromKclValueOptional(val, key)
if (val.type === 'Solid') {
processedMemory[key] = val.value.value.map(
@ -100,13 +98,13 @@ export const processMemory = (variables: VariableMap) => {
processedMemory[key] = sk.paths.map(({ __geoMeta, ...rest }: Path) => {
return rest
})
} else if (val.type === 'Function') {
processedMemory[key] = '__function__'
} else if (val.type === 'Number') {
processedMemory[key] = humanDisplayNumber(val.value, val.ty)
} else {
processedMemory[key] = val.value
}
//@ts-ignore
} else if (val.type === 'Function') {
processedMemory[key] = `__function__`
}
}
return processedMemory
}

View File

@ -31,28 +31,48 @@ import type {
VariableDeclaration,
VariableDeclarator,
} from '@src/lang/wasm'
import { formatNumber } from '@src/lang/wasm'
import { formatNumberLiteral } from '@src/lang/wasm'
import { err } from '@src/lib/trap'
export function createLiteral(value: number | string | boolean): Node<Literal> {
// TODO: Should we handle string escape sequences?
return {
type: 'Literal',
start: 0,
end: 0,
moduleId: 0,
value: typeof value === 'number' ? { value, suffix: 'None' } : value,
raw: `${value}`,
outerAttrs: [],
preComments: [],
commentStart: 0,
}
}
/**
* Note: This depends on WASM, but it's not async. Callers are responsible for
* awaiting init of the WASM module.
*/
export function createLiteral(value: LiteralValue | number): Node<Literal> {
if (typeof value === 'number') {
value = { value, suffix: 'None' }
export function createLiteralMaybeSuffix(
value: LiteralValue
): Node<Literal> | Error {
if (typeof value === 'string' || typeof value === 'boolean') {
return createLiteral(value)
}
let raw: string
if (typeof value === 'string') {
// TODO: Should we handle escape sequences?
raw = `${value}`
} else if (typeof value === 'boolean') {
raw = `${value}`
} else if (typeof value.value === 'number' && value.suffix === 'None') {
if (typeof value.value === 'number' && value.suffix === 'None') {
// Fast path for numbers when there are no units.
raw = `${value.value}`
} else {
raw = formatNumber(value.value, value.suffix)
const formatted = formatNumberLiteral(value.value, value.suffix)
if (err(formatted)) {
return new Error(
`Invalid number literal: value=${value.value}, suffix=${value.suffix}`,
{ cause: formatted }
)
}
raw = formatted
}
return {
type: 'Literal',

View File

@ -4,6 +4,7 @@ import {
createArrayExpression,
createIdentifier,
createLiteral,
createLiteralMaybeSuffix,
createObjectExpression,
createPipeExpression,
createPipeSubstitution,
@ -35,6 +36,7 @@ import { initPromise } from '@src/lang/wasmUtils'
import { enginelessExecutor } from '@src/lib/testHelpers'
import { err } from '@src/lib/trap'
import { deleteFromSelection } from '@src/lang/modifyAst/deleteFromSelection'
import { assertNotErr } from '@src/unitTestUtils'
beforeAll(async () => {
await initPromise
@ -50,7 +52,8 @@ describe('Testing createLiteral', () => {
})
it('should create a literal number with units', () => {
const lit: LiteralValue = { value: 5, suffix: 'Mm' }
const result = createLiteral(lit)
const result = createLiteralMaybeSuffix(lit)
assertNotErr(result)
expect(result.type).toBe('Literal')
expect((result as any).value.value).toBe(5)
expect((result as any).value.suffix).toBe('Mm')

View File

@ -3,7 +3,7 @@ import type { Program } from '@rust/kcl-lib/bindings/Program'
import type { ParseResult } from '@src/lang/wasm'
import {
formatNumber,
formatNumberLiteral,
parse,
errFromErrWithOutputs,
rustImplPathToNode,
@ -33,12 +33,15 @@ it('can execute parsed AST', async () => {
})
it('formats numbers with units', () => {
expect(formatNumber(1, 'None')).toEqual('1')
expect(formatNumber(1, 'Count')).toEqual('1_')
expect(formatNumber(1, 'Mm')).toEqual('1mm')
expect(formatNumber(1, 'Inch')).toEqual('1in')
expect(formatNumber(0.5, 'Mm')).toEqual('0.5mm')
expect(formatNumber(-0.5, 'Mm')).toEqual('-0.5mm')
expect(formatNumberLiteral(1, 'None')).toEqual('1')
expect(formatNumberLiteral(1, 'Count')).toEqual('1_')
expect(formatNumberLiteral(1, 'Mm')).toEqual('1mm')
expect(formatNumberLiteral(1, 'Inch')).toEqual('1in')
expect(formatNumberLiteral(0.5, 'Mm')).toEqual('0.5mm')
expect(formatNumberLiteral(-0.5, 'Mm')).toEqual('-0.5mm')
expect(formatNumberLiteral(1, 'Unknown')).toEqual(
new Error('Error formatting number literal: value=1, suffix=Unknown')
)
})
describe('test errFromErrWithOutputs', () => {

View File

@ -47,9 +47,10 @@ import {
coredump,
default_app_settings,
default_project_settings,
format_number,
format_number_literal,
get_kcl_version,
get_tangential_arc_to_info,
human_display_number,
is_kcl_empty_or_only_settings,
is_points_ccw,
kcl_lint,
@ -67,6 +68,7 @@ import {
LABELED_ARG_FIELD,
UNLABELED_ARG,
} from '@src/lang/queryAstConstants'
import type { NumericType } from '@rust/kcl-lib/bindings/NumericType'
export type { ArrayExpression } from '@rust/kcl-lib/bindings/ArrayExpression'
export type {
@ -440,8 +442,35 @@ export const recast = (ast: Program): string | Error => {
/**
* Format a number with suffix as KCL.
*/
export function formatNumber(value: number, suffix: NumericSuffix): string {
return format_number(value, JSON.stringify(suffix))
export function formatNumberLiteral(
value: number,
suffix: NumericSuffix
): string | Error {
try {
return format_number_literal(value, JSON.stringify(suffix))
} catch (e) {
return new Error(
`Error formatting number literal: value=${value}, suffix=${suffix}`,
{ cause: e }
)
}
}
/**
* Debug display a number with suffix, for human consumption only.
*/
export function humanDisplayNumber(
value: number,
ty: NumericType
): string | Error {
try {
return human_display_number(value, JSON.stringify(ty))
} catch (e) {
return new Error(
`Error formatting number for human display: value=${value}, ty=${JSON.stringify(ty)}`,
{ cause: e }
)
}
}
export function isPointsCCW(points: Coords2d[]): number {

View File

@ -12,7 +12,8 @@ import type {
coredump as CoreDump,
default_app_settings as DefaultAppSettings,
default_project_settings as DefaultProjectSettings,
format_number as FormatNumber,
format_number_literal as FormatNumberLiteral,
human_display_number as HumanDisplayNumber,
get_kcl_version as GetKclVersion,
get_tangential_arc_to_info as GetTangentialArcToInfo,
import_file_extensions as ImportFileExtensions,
@ -55,8 +56,11 @@ export const parse_wasm: typeof ParseWasm = (...args) => {
export const recast_wasm: typeof RecastWasm = (...args) => {
return getModule().recast_wasm(...args)
}
export const format_number: typeof FormatNumber = (...args) => {
return getModule().format_number(...args)
export const format_number_literal: typeof FormatNumberLiteral = (...args) => {
return getModule().format_number_literal(...args)
}
export const human_display_number: typeof HumanDisplayNumber = (...args) => {
return getModule().human_display_number(...args)
}
export const kcl_lint: typeof KclLint = (...args) => {
return getModule().kcl_lint(...args)

View File

@ -10,6 +10,16 @@ import { createArrayExpression } from '@src/lang/create'
import { findKwArg, findKwArgAny } from '@src/lang/util'
import type { CallExpressionKw, Expr } from '@src/lang/wasm'
/**
* Throw x if it's an Error. Only use this in tests.
*/
export function assertNotErr<T>(x: T): asserts x is Exclude<T, Error> {
if (x instanceof Error) {
// eslint-disable-next-line suggest-no-throw/suggest-no-throw
throw x
}
}
/**
Find the angle and some sort of length parameter from an angledLine-ish call.
E.g. finds the (angle, length) in angledLine or the (angle, endAbsoluteX) in angledLineToX