diff --git a/src/lang/wasm.ts b/src/lang/wasm.ts index d8ff96591..ba9241c86 100644 --- a/src/lang/wasm.ts +++ b/src/lang/wasm.ts @@ -18,6 +18,7 @@ import { default_project_settings, base64_decode, clear_scene_and_bust_cache, + change_kcl_settings, reloadModule, } from 'lib/wasm_lib_wrapper' @@ -56,6 +57,7 @@ import { ArtifactGraph as RustArtifactGraph } from 'wasm-lib/kcl/bindings/Artifa import { Artifact } from './std/artifactGraph' import { getNodePathFromSourceRange } from 'lang/queryAstNodePathUtils' import { NumericSuffix } from 'wasm-lib/kcl/bindings/NumericSuffix' +import { MetaSettings } from 'wasm-lib/kcl/bindings/MetaSettings' export type { Artifact } from 'wasm-lib/kcl/bindings/Artifact' export type { ArtifactCommand } from 'wasm-lib/kcl/bindings/Artifact' @@ -844,3 +846,17 @@ export function base64Decode(base64: string): ArrayBuffer | Error { return new Error('Caught error decoding base64 string: ' + e) } } + +/// Change the meta settings for the kcl file. +/// Returns the new kcl string with the updated settings. +export function changeKclSettings( + kcl: string, + settings: MetaSettings +): string | Error { + try { + return change_kcl_settings(kcl, JSON.stringify(settings)) + } catch (e) { + console.error('Caught error changing kcl settings: ' + e) + return new Error('Caught error changing kcl settings: ' + e) + } +} diff --git a/src/lib/wasm_lib_wrapper.ts b/src/lib/wasm_lib_wrapper.ts index e55a78d58..93d670e7f 100644 --- a/src/lib/wasm_lib_wrapper.ts +++ b/src/lib/wasm_lib_wrapper.ts @@ -26,6 +26,7 @@ import { default_project_settings as DefaultProjectSettings, base64_decode as Base64Decode, clear_scene_and_bust_cache as ClearSceneAndBustCache, + change_kcl_settings as ChangeKclSettings, } from '../wasm-lib/pkg/wasm_lib' type ModuleType = typeof import('../wasm-lib/pkg/wasm_lib') @@ -110,3 +111,6 @@ export const clear_scene_and_bust_cache: typeof ClearSceneAndBustCache = ( ) => { return getModule().clear_scene_and_bust_cache(...args) } +export const change_kcl_settings: typeof ChangeKclSettings = (...args) => { + return getModule().change_kcl_settings(...args) +} diff --git a/src/wasm-lib/Cargo.lock b/src/wasm-lib/Cargo.lock index 0baa2689b..4f43d2d06 100644 --- a/src/wasm-lib/Cargo.lock +++ b/src/wasm-lib/Cargo.lock @@ -1710,7 +1710,7 @@ dependencies = [ [[package]] name = "kcl-lib" -version = "0.2.32" +version = "0.2.33" dependencies = [ "anyhow", "approx 0.5.1", diff --git a/src/wasm-lib/kcl/Cargo.toml b/src/wasm-lib/kcl/Cargo.toml index 8de7c1bb9..76983d4d4 100644 --- a/src/wasm-lib/kcl/Cargo.toml +++ b/src/wasm-lib/kcl/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "kcl-lib" description = "KittyCAD Language implementation and tools" -version = "0.2.32" +version = "0.2.33" edition = "2021" license = "MIT" repository = "https://github.com/KittyCAD/modeling-app" diff --git a/src/wasm-lib/kcl/src/docs/mod.rs b/src/wasm-lib/kcl/src/docs/mod.rs index 35513bfc3..c6ec18344 100644 --- a/src/wasm-lib/kcl/src/docs/mod.rs +++ b/src/wasm-lib/kcl/src/docs/mod.rs @@ -13,9 +13,7 @@ use tower_lsp::lsp_types::{ MarkupKind, ParameterInformation, ParameterLabel, SignatureHelp, SignatureInformation, }; -use crate::execution::Sketch; - -use crate::std::Primitive; +use crate::{execution::Sketch, std::Primitive}; #[derive(Debug, Clone, Deserialize, Serialize, PartialEq, JsonSchema, ts_rs::TS)] #[ts(export)] diff --git a/src/wasm-lib/kcl/src/execution/annotations.rs b/src/wasm-lib/kcl/src/execution/annotations.rs index c8d9b75d7..969c52fbe 100644 --- a/src/wasm-lib/kcl/src/execution/annotations.rs +++ b/src/wasm-lib/kcl/src/execution/annotations.rs @@ -7,9 +7,9 @@ use crate::{ KclError, SourceRange, }; -pub(super) const SETTINGS: &str = "settings"; -pub(super) const SETTINGS_UNIT_LENGTH: &str = "defaultLengthUnit"; -pub(super) const SETTINGS_UNIT_ANGLE: &str = "defaultAngleUnit"; +pub(crate) const SETTINGS: &str = "settings"; +pub(crate) const SETTINGS_UNIT_LENGTH: &str = "defaultLengthUnit"; +pub(crate) const SETTINGS_UNIT_ANGLE: &str = "defaultAngleUnit"; #[derive(Copy, Clone, Debug, Eq, PartialEq)] pub(super) enum AnnotationScope { diff --git a/src/wasm-lib/kcl/src/execution/exec_ast.rs b/src/wasm-lib/kcl/src/execution/exec_ast.rs index 6b39d2be8..ff72cd880 100644 --- a/src/wasm-lib/kcl/src/execution/exec_ast.rs +++ b/src/wasm-lib/kcl/src/execution/exec_ast.rs @@ -2,6 +2,7 @@ use std::collections::HashMap; use async_recursion::async_recursion; +use super::cad_op::{OpArg, Operation}; use crate::{ errors::{KclError, KclErrorDetails}, execution::{ @@ -19,8 +20,6 @@ use crate::{ }, }; -use super::cad_op::{OpArg, Operation}; - impl BinaryPart { #[async_recursion] pub async fn get_result(&self, exec_state: &mut ExecState, ctx: &ExecutorContext) -> Result { diff --git a/src/wasm-lib/kcl/src/execution/import.rs b/src/wasm-lib/kcl/src/execution/import.rs index 62d69397d..729f7365d 100644 --- a/src/wasm-lib/kcl/src/execution/import.rs +++ b/src/wasm-lib/kcl/src/execution/import.rs @@ -15,6 +15,7 @@ use kittycad_modeling_cmds as kcmc; use serde::{Deserialize, Serialize}; use uuid::Uuid; +use super::ExecutorContext; use crate::{ errors::{KclError, KclErrorDetails}, execution::{ExecState, ImportedGeometry}, @@ -22,8 +23,6 @@ use crate::{ source_range::SourceRange, }; -use super::ExecutorContext; - // Zoo co-ordinate system. // // * Forward: -Y diff --git a/src/wasm-lib/kcl/src/execution/kcl_value.rs b/src/wasm-lib/kcl/src/execution/kcl_value.rs index 39caf9ebd..e4f2b9fba 100644 --- a/src/wasm-lib/kcl/src/execution/kcl_value.rs +++ b/src/wasm-lib/kcl/src/execution/kcl_value.rs @@ -581,10 +581,11 @@ impl KclValue { } // TODO called UnitLen so as not to clash with UnitLength in settings) -#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema, Eq)] +#[derive(Debug, Default, Clone, Copy, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema, Eq)] #[ts(export)] #[serde(tag = "type")] pub enum UnitLen { + #[default] Mm, Cm, M, @@ -593,6 +594,19 @@ pub enum UnitLen { Yards, } +impl std::fmt::Display for UnitLen { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + UnitLen::Mm => write!(f, "mm"), + UnitLen::Cm => write!(f, "cm"), + UnitLen::M => write!(f, "m"), + UnitLen::Inches => write!(f, "in"), + UnitLen::Feet => write!(f, "ft"), + UnitLen::Yards => write!(f, "yd"), + } + } +} + impl TryFrom for UnitLen { type Error = (); @@ -644,6 +658,15 @@ pub enum UnitAngle { Radians, } +impl std::fmt::Display for UnitAngle { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + UnitAngle::Degrees => write!(f, "deg"), + UnitAngle::Radians => write!(f, "rad"), + } + } +} + impl TryFrom for UnitAngle { type Error = (); diff --git a/src/wasm-lib/kcl/src/execution/mod.rs b/src/wasm-lib/kcl/src/execution/mod.rs index b6352fdda..1137c5765 100644 --- a/src/wasm-lib/kcl/src/execution/mod.rs +++ b/src/wasm-lib/kcl/src/execution/mod.rs @@ -13,8 +13,7 @@ use kcmc::{ websocket::{ModelingSessionData, OkWebSocketResponseData}, ImageFormat, ModelingCmd, }; -use kittycad_modeling_cmds::length_unit::LengthUnit; -use kittycad_modeling_cmds::{self as kcmc, websocket::WebSocketResponse}; +use kittycad_modeling_cmds::{self as kcmc, length_unit::LengthUnit, websocket::WebSocketResponse}; use parse_display::{Display, FromStr}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; @@ -27,14 +26,18 @@ pub(crate) use import::{import_foreign, send_to_engine as send_import_to_engine, pub use kcl_value::{KclObjectFields, KclValue, UnitAngle, UnitLen}; use uuid::Uuid; -mod annotations; +pub(crate) mod annotations; mod artifact; pub(crate) mod cache; mod cad_op; mod exec_ast; mod function_param; mod import; -mod kcl_value; +pub(crate) mod kcl_value; + +// Re-exports. +pub use artifact::{Artifact, ArtifactCommand, ArtifactGraph, ArtifactId}; +pub use cad_op::Operation; use crate::{ engine::{EngineManager, ExecutionKind}, @@ -52,10 +55,6 @@ use crate::{ ExecError, KclErrorWithOutputs, Program, }; -// Re-exports. -pub use artifact::{Artifact, ArtifactCommand, ArtifactGraph, ArtifactId}; -pub use cad_op::Operation; - /// State for executing a program. #[derive(Debug, Clone, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] @@ -247,7 +246,7 @@ impl ModuleState { } } -#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)] +#[derive(Debug, Default, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)] #[ts(export)] #[serde(rename_all = "camelCase")] pub struct MetaSettings { @@ -256,7 +255,11 @@ pub struct MetaSettings { } impl MetaSettings { - fn update_from_annotation(&mut self, annotation: &NonCodeValue, source_range: SourceRange) -> Result<(), KclError> { + pub fn update_from_annotation( + &mut self, + annotation: &NonCodeValue, + source_range: SourceRange, + ) -> Result<(), KclError> { let properties = annotations::expect_properties(annotations::SETTINGS, annotation, source_range)?; for p in properties { diff --git a/src/wasm-lib/kcl/src/lib.rs b/src/wasm-lib/kcl/src/lib.rs index 83bf4fd21..7e83c9777 100644 --- a/src/wasm-lib/kcl/src/lib.rs +++ b/src/wasm-lib/kcl/src/lib.rs @@ -84,7 +84,7 @@ pub use engine::{EngineManager, ExecutionKind}; pub use errors::{CompilationError, ConnectionError, ExecError, KclError, KclErrorWithOutputs}; pub use execution::{ cache::{CacheInformation, OldAstState}, - ExecState, ExecutorContext, ExecutorSettings, Point2d, + ExecState, ExecutorContext, ExecutorSettings, MetaSettings, Point2d, }; pub use lsp::{ copilot::Backend as CopilotLspBackend, @@ -121,8 +121,7 @@ pub mod std_utils { } pub mod pretty { - pub use crate::parsing::token::NumericSuffix; - pub use crate::unparser::format_number; + pub use crate::{parsing::token::NumericSuffix, unparser::format_number}; } use serde::{Deserialize, Serialize}; @@ -162,6 +161,18 @@ impl Program { self.ast.compute_digest() } + /// Get the meta settings for the kcl file from the annotations. + pub fn get_meta_settings(&self) -> Result, KclError> { + self.ast.get_meta_settings() + } + + /// Change the meta settings for the kcl file. + pub fn change_meta_settings(&mut self, settings: crate::MetaSettings) -> Result { + Ok(Self { + ast: self.ast.change_meta_settings(settings)?, + }) + } + pub fn lint_all(&self) -> Result, anyhow::Error> { self.ast.lint_all() } diff --git a/src/wasm-lib/kcl/src/parsing/ast/modify.rs b/src/wasm-lib/kcl/src/parsing/ast/modify.rs index 83f752659..aef9c0cbc 100644 --- a/src/wasm-lib/kcl/src/parsing/ast/modify.rs +++ b/src/wasm-lib/kcl/src/parsing/ast/modify.rs @@ -6,6 +6,7 @@ use kcmc::{ }; use kittycad_modeling_cmds as kcmc; +use super::types::LiteralValue; use crate::{ engine::EngineManager, errors::{KclError, KclErrorDetails}, @@ -18,8 +19,6 @@ use crate::{ Program, }; -use super::types::LiteralValue; - type Point3d = kcmc::shared::Point3d; #[derive(Debug)] diff --git a/src/wasm-lib/kcl/src/parsing/ast/types/mod.rs b/src/wasm-lib/kcl/src/parsing/ast/types/mod.rs index e5bd13b80..f9fedcb5d 100644 --- a/src/wasm-lib/kcl/src/parsing/ast/types/mod.rs +++ b/src/wasm-lib/kcl/src/parsing/ast/types/mod.rs @@ -17,7 +17,6 @@ use tower_lsp::lsp_types::{ CompletionItem, CompletionItemKind, DocumentSymbol, FoldingRange, FoldingRangeKind, Range as LspRange, SymbolKind, }; -use super::digest::Digest; pub use crate::parsing::ast::types::{ condition::{ElseIf, IfExpression}, literal_value::LiteralValue, @@ -26,7 +25,8 @@ pub use crate::parsing::ast::types::{ use crate::{ docs::StdLibFn, errors::KclError, - execution::{KclValue, Metadata, TagIdentifier}, + execution::{annotations, KclValue, Metadata, TagIdentifier}, + parsing::ast::digest::Digest, parsing::PIPE_OPERATOR, source_range::{ModuleId, SourceRange}, }; @@ -254,6 +254,52 @@ impl Node { } Ok(findings) } + + /// Get the annotations for the meta settings from the kcl file. + pub fn get_meta_settings(&self) -> Result, KclError> { + let annotations = self + .non_code_meta + .start_nodes + .iter() + .filter_map(|n| n.annotation().map(|result| (result, n.as_source_range()))); + for (annotation, source_range) in annotations { + if annotation.annotation_name() == Some(annotations::SETTINGS) { + let mut meta_settings = crate::execution::MetaSettings::default(); + meta_settings.update_from_annotation(annotation, source_range)?; + return Ok(Some(meta_settings)); + } + } + + Ok(None) + } + + pub fn change_meta_settings(&mut self, settings: crate::execution::MetaSettings) -> Result { + let mut new_program = self.clone(); + let mut found = false; + for node in &mut new_program.non_code_meta.start_nodes { + if let Some(annotation) = node.annotation() { + if annotation.annotation_name() == Some(annotations::SETTINGS) { + let annotation = NonCodeValue::new_from_meta_settings(&settings); + *node = Node::no_src(NonCodeNode { + value: annotation, + digest: None, + }); + found = true; + break; + } + } + } + + if !found { + let annotation = NonCodeValue::new_from_meta_settings(&settings); + new_program.non_code_meta.start_nodes.push(Node::no_src(NonCodeNode { + value: annotation, + digest: None, + })); + } + + Ok(new_program) + } } impl Program { @@ -1078,6 +1124,24 @@ impl NonCodeValue { _ => None, } } + + pub fn new_from_meta_settings(settings: &crate::execution::MetaSettings) -> NonCodeValue { + let mut properties: Vec> = vec![ObjectProperty::new( + Identifier::new(annotations::SETTINGS_UNIT_LENGTH), + Expr::Identifier(Box::new(Identifier::new(&settings.default_length_units.to_string()))), + )]; + + if settings.default_angle_units != Default::default() { + properties.push(ObjectProperty::new( + Identifier::new(annotations::SETTINGS_UNIT_ANGLE), + Expr::Identifier(Box::new(Identifier::new(&settings.default_angle_units.to_string()))), + )); + } + NonCodeValue::Annotation { + name: Identifier::new(annotations::SETTINGS), + properties: Some(properties), + } + } } #[derive(Debug, Default, Clone, Serialize, PartialEq, ts_rs::TS, JsonSchema)] @@ -2337,6 +2401,14 @@ impl Node { } impl ObjectProperty { + pub fn new(key: Node, value: Expr) -> Node { + Node::no_src(Self { + key, + value, + digest: None, + }) + } + /// Returns a hover value that includes the given character position. pub fn get_hover_value_for_position(&self, pos: usize, code: &str) -> Option { let value_source_range: SourceRange = self.value.clone().into(); @@ -3756,4 +3828,98 @@ const cylinder = startSketchOn('-XZ') assert_eq!(l.raw, "false"); } + + #[tokio::test(flavor = "multi_thread")] + async fn test_parse_get_meta_settings_inch() { + let some_program_string = r#"@settings(defaultLengthUnit = inch) + +startSketchOn('XY')"#; + let program = crate::parsing::top_level_parse(some_program_string).unwrap(); + let result = program.get_meta_settings().unwrap(); + assert!(result.is_some()); + let meta_settings = result.unwrap(); + + assert_eq!( + meta_settings.default_length_units, + crate::execution::kcl_value::UnitLen::Inches + ); + } + + #[tokio::test(flavor = "multi_thread")] + async fn test_parse_get_meta_settings_inch_to_mm() { + let some_program_string = r#"@settings(defaultLengthUnit = inch) + +startSketchOn('XY')"#; + let mut program = crate::parsing::top_level_parse(some_program_string).unwrap(); + let result = program.get_meta_settings().unwrap(); + assert!(result.is_some()); + let meta_settings = result.unwrap(); + + assert_eq!( + meta_settings.default_length_units, + crate::execution::kcl_value::UnitLen::Inches + ); + + // Edit the ast. + let new_program = program + .change_meta_settings(crate::execution::MetaSettings { + default_length_units: crate::execution::kcl_value::UnitLen::Mm, + ..Default::default() + }) + .unwrap(); + + let result = new_program.get_meta_settings().unwrap(); + assert!(result.is_some()); + let meta_settings = result.unwrap(); + + assert_eq!( + meta_settings.default_length_units, + crate::execution::kcl_value::UnitLen::Mm + ); + + let formatted = new_program.recast(&Default::default(), 0); + + assert_eq!( + formatted, + r#"@settings(defaultLengthUnit = mm) + + +startSketchOn('XY') +"# + ); + } + + #[tokio::test(flavor = "multi_thread")] + async fn test_parse_get_meta_settings_nothing_to_mm() { + let some_program_string = r#"startSketchOn('XY')"#; + let mut program = crate::parsing::top_level_parse(some_program_string).unwrap(); + let result = program.get_meta_settings().unwrap(); + assert!(result.is_none()); + + // Edit the ast. + let new_program = program + .change_meta_settings(crate::execution::MetaSettings { + default_length_units: crate::execution::kcl_value::UnitLen::Mm, + ..Default::default() + }) + .unwrap(); + + let result = new_program.get_meta_settings().unwrap(); + assert!(result.is_some()); + let meta_settings = result.unwrap(); + + assert_eq!( + meta_settings.default_length_units, + crate::execution::kcl_value::UnitLen::Mm + ); + + let formatted = new_program.recast(&Default::default(), 0); + + assert_eq!( + formatted, + r#"@settings(defaultLengthUnit = mm) +startSketchOn('XY') +"# + ); + } } diff --git a/src/wasm-lib/kcl/src/std/math.rs b/src/wasm-lib/kcl/src/std/math.rs index 72ad04672..c639940c8 100644 --- a/src/wasm-lib/kcl/src/std/math.rs +++ b/src/wasm-lib/kcl/src/std/math.rs @@ -3,14 +3,13 @@ use anyhow::Result; use derive_docs::stdlib; +use super::args::FromArgs; use crate::{ errors::{KclError, KclErrorDetails}, execution::{ExecState, KclValue}, std::Args, }; -use super::args::FromArgs; - /// Compute the remainder after dividing `num` by `div`. /// If `num` is negative, the result will be too. pub async fn rem(_exec_state: &mut ExecState, args: Args) -> Result { diff --git a/src/wasm-lib/kcl/src/std/sketch.rs b/src/wasm-lib/kcl/src/std/sketch.rs index bb841f547..37a01720f 100644 --- a/src/wasm-lib/kcl/src/std/sketch.rs +++ b/src/wasm-lib/kcl/src/std/sketch.rs @@ -11,12 +11,11 @@ use parse_display::{Display, FromStr}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use crate::execution::{Artifact, ArtifactId}; use crate::{ errors::{KclError, KclErrorDetails}, execution::{ - BasePath, ExecState, Face, GeoMeta, KclValue, Path, Plane, Point2d, Point3d, Sketch, SketchSet, SketchSurface, - Solid, TagEngineInfo, TagIdentifier, + Artifact, ArtifactId, BasePath, ExecState, Face, GeoMeta, KclValue, Path, Plane, Point2d, Point3d, Sketch, + SketchSet, SketchSurface, Solid, TagEngineInfo, TagIdentifier, }, parsing::ast::types::TagNode, std::{ @@ -2250,7 +2249,10 @@ mod tests { use pretty_assertions::assert_eq; - use crate::{execution::TagIdentifier, std::sketch::PlaneData, std::utils::calculate_circle_center}; + use crate::{ + execution::TagIdentifier, + std::{sketch::PlaneData, utils::calculate_circle_center}, + }; #[test] fn test_deserialize_plane_data() { diff --git a/src/wasm-lib/src/wasm.rs b/src/wasm-lib/src/wasm.rs index 7f4ff7f4b..a5b63be63 100644 --- a/src/wasm-lib/src/wasm.rs +++ b/src/wasm-lib/src/wasm.rs @@ -614,3 +614,18 @@ pub fn calculate_circle_from_3_points(ax: f64, ay: f64, bx: f64, by: f64, cx: f6 radius: result.radius, } } + +/// Takes a kcl string and Meta settings and changes the meta settings in the kcl string. +#[wasm_bindgen] +pub fn change_kcl_settings(code: &str, settings_str: &str) -> Result { + console_error_panic_hook::set_once(); + + let settings: kcl_lib::MetaSettings = serde_json::from_str(settings_str).map_err(|e| e.to_string())?; + let mut program = Program::parse_no_errs(code).map_err(|e| e.to_string())?; + + let new_program = program.change_meta_settings(settings).map_err(|e| e.to_string())?; + + let formatted = new_program.recast(); + + Ok(formatted) +}