messing around with arc and bezier (#363)

updates



fixes



updates



add another test



updates



updates



updates



updates



updates



updates



add test for error;



updates



updates



fixups



updates



updates



fixes



updates



fixes



updates



fixes



updates



updates



updates



bump

Signed-off-by: Jess Frazelle <github@jessfraz.com>
This commit is contained in:
Jess Frazelle
2023-08-31 22:19:23 -07:00
committed by GitHub
parent ff1be34f54
commit 46cc67e2db
15 changed files with 2938 additions and 49 deletions

File diff suppressed because it is too large Load Diff

View File

@ -33,6 +33,8 @@
* [`angledLineThatIntersects`](#angledLineThatIntersects) * [`angledLineThatIntersects`](#angledLineThatIntersects)
* [`startSketchAt`](#startSketchAt) * [`startSketchAt`](#startSketchAt)
* [`close`](#close) * [`close`](#close)
* [`arc`](#arc)
* [`bezierCurve`](#bezierCurve)
## Functions ## Functions
@ -3046,3 +3048,351 @@ close(sketch_group: SketchGroup) -> SketchGroup
### arc
Draw an arc.
```
arc(data: ArcData, sketch_group: SketchGroup) -> SketchGroup
```
#### Arguments
* `data`: `ArcData` - Data to draw an arc.
```
{
// The end angle.
"angle_end": number,
// The start angle.
"angle_start": number,
// The radius.
"radius": number,
// The tag.
"tag": string,
} |
{
// The end angle.
"angle_end": number,
// The start angle.
"angle_start": number,
// The radius.
"radius": number,
} |
{
// The center.
"center": [number],
// The radius.
"radius": number,
// The tag.
"tag": string,
// The to point.
"to": [number],
} |
{
// The center.
"center": [number],
// The radius.
"radius": number,
// The to point.
"to": [number],
}
```
* `sketch_group`: `SketchGroup` - A sketch group is a collection of paths.
```
{
// The id of the sketch group.
"id": uuid,
// The position of the sketch group.
"position": [number],
// The rotation of the sketch group.
"rotation": [number],
// The starting path.
"start": {
// The from point.
"from": [number],
// The name of the path.
"name": string,
// The to point.
"to": [number],
},
// The paths in the sketch group.
"value": [{
// The from point.
"from": [number],
// The name of the path.
"name": string,
// The to point.
"to": [number],
"type": string,
} |
{
// The from point.
"from": [number],
// The name of the path.
"name": string,
// The to point.
"to": [number],
"type": string,
// The x coordinate.
"x": number,
} |
{
// The from point.
"from": [number],
// The name of the path.
"name": string,
// The to point.
"to": [number],
"type": string,
// The x coordinate.
"x": number,
// The y coordinate.
"y": number,
} |
{
// The from point.
"from": [number],
// The name of the path.
"name": string,
// The to point.
"to": [number],
"type": string,
}],
}
```
#### Returns
* `SketchGroup` - A sketch group is a collection of paths.
```
{
// The id of the sketch group.
"id": uuid,
// The position of the sketch group.
"position": [number],
// The rotation of the sketch group.
"rotation": [number],
// The starting path.
"start": {
// The from point.
"from": [number],
// The name of the path.
"name": string,
// The to point.
"to": [number],
},
// The paths in the sketch group.
"value": [{
// The from point.
"from": [number],
// The name of the path.
"name": string,
// The to point.
"to": [number],
"type": string,
} |
{
// The from point.
"from": [number],
// The name of the path.
"name": string,
// The to point.
"to": [number],
"type": string,
// The x coordinate.
"x": number,
} |
{
// The from point.
"from": [number],
// The name of the path.
"name": string,
// The to point.
"to": [number],
"type": string,
// The x coordinate.
"x": number,
// The y coordinate.
"y": number,
} |
{
// The from point.
"from": [number],
// The name of the path.
"name": string,
// The to point.
"to": [number],
"type": string,
}],
}
```
### bezierCurve
Draw a bezier curve.
```
bezierCurve(data: BezierData, sketch_group: SketchGroup) -> SketchGroup
```
#### Arguments
* `data`: `BezierData` - Data to draw a bezier curve.
```
{
// The first control point.
"control1": [number],
// The second control point.
"control2": [number],
// The tag.
"tag": string,
// The to point.
"to": [number],
} |
{
// The first control point.
"control1": [number],
// The second control point.
"control2": [number],
// The to point.
"to": [number],
}
```
* `sketch_group`: `SketchGroup` - A sketch group is a collection of paths.
```
{
// The id of the sketch group.
"id": uuid,
// The position of the sketch group.
"position": [number],
// The rotation of the sketch group.
"rotation": [number],
// The starting path.
"start": {
// The from point.
"from": [number],
// The name of the path.
"name": string,
// The to point.
"to": [number],
},
// The paths in the sketch group.
"value": [{
// The from point.
"from": [number],
// The name of the path.
"name": string,
// The to point.
"to": [number],
"type": string,
} |
{
// The from point.
"from": [number],
// The name of the path.
"name": string,
// The to point.
"to": [number],
"type": string,
// The x coordinate.
"x": number,
} |
{
// The from point.
"from": [number],
// The name of the path.
"name": string,
// The to point.
"to": [number],
"type": string,
// The x coordinate.
"x": number,
// The y coordinate.
"y": number,
} |
{
// The from point.
"from": [number],
// The name of the path.
"name": string,
// The to point.
"to": [number],
"type": string,
}],
}
```
#### Returns
* `SketchGroup` - A sketch group is a collection of paths.
```
{
// The id of the sketch group.
"id": uuid,
// The position of the sketch group.
"position": [number],
// The rotation of the sketch group.
"rotation": [number],
// The starting path.
"start": {
// The from point.
"from": [number],
// The name of the path.
"name": string,
// The to point.
"to": [number],
},
// The paths in the sketch group.
"value": [{
// The from point.
"from": [number],
// The name of the path.
"name": string,
// The to point.
"to": [number],
"type": string,
} |
{
// The from point.
"from": [number],
// The name of the path.
"name": string,
// The to point.
"to": [number],
"type": string,
// The x coordinate.
"x": number,
} |
{
// The from point.
"from": [number],
// The name of the path.
"name": string,
// The to point.
"to": [number],
"type": string,
// The x coordinate.
"x": number,
// The y coordinate.
"y": number,
} |
{
// The from point.
"from": [number],
// The name of the path.
"name": string,
// The to point.
"to": [number],
"type": string,
}],
}
```

View File

@ -57,13 +57,13 @@
"build:both:local": "yarn build:wasm && vite build", "build:both:local": "yarn build:wasm && vite build",
"test": "vitest --mode development", "test": "vitest --mode development",
"test:nowatch": "vitest run --mode development", "test:nowatch": "vitest run --mode development",
"test:rust": "(cd src/wasm-lib && cargo test && cargo clippy)", "test:rust": "(cd src/wasm-lib && cargo test --all && cargo clippy --all --tests)",
"test:cov": "vitest run --coverage --mode development", "test:cov": "vitest run --coverage --mode development",
"simpleserver:ci": "http-server ./public --cors -p 3000 &", "simpleserver:ci": "http-server ./public --cors -p 3000 &",
"simpleserver": "http-server ./public --cors -p 3000", "simpleserver": "http-server ./public --cors -p 3000",
"fmt": "prettier --write ./src", "fmt": "prettier --write ./src",
"fmt-check": "prettier --check ./src", "fmt-check": "prettier --check ./src",
"build:wasm": "yarn wasm-prep && (cd src/wasm-lib && wasm-pack build --target web --out-dir pkg && cargo test --all) && cp src/wasm-lib/pkg/wasm_lib_bg.wasm public && yarn fmt && yarn remove-importmeta", "build:wasm": "yarn wasm-prep && (cd src/wasm-lib && wasm-pack build --target web --out-dir pkg && cargo test -p kcl-lib export_bindings) && cp src/wasm-lib/pkg/wasm_lib_bg.wasm public && yarn fmt && yarn remove-importmeta",
"remove-importmeta": "sed -i 's/import.meta.url//g' \"./src/wasm-lib/pkg/wasm_lib.js\"; sed -i '' 's/import.meta.url//g' \"./src/wasm-lib/pkg/wasm_lib.js\" || echo \"sed for both mac and linux\"", "remove-importmeta": "sed -i 's/import.meta.url//g' \"./src/wasm-lib/pkg/wasm_lib.js\"; sed -i '' 's/import.meta.url//g' \"./src/wasm-lib/pkg/wasm_lib.js\" || echo \"sed for both mac and linux\"",
"wasm-prep": "rm -rf src/wasm-lib/pkg && mkdir src/wasm-lib/pkg && rm -rf src/wasm-lib/kcl/bindings", "wasm-prep": "rm -rf src/wasm-lib/pkg && mkdir src/wasm-lib/pkg && rm -rf src/wasm-lib/kcl/bindings",
"lint": "eslint --fix src", "lint": "eslint --fix src",

View File

@ -948,7 +948,7 @@ dependencies = [
[[package]] [[package]]
name = "kcl-lib" name = "kcl-lib"
version = "0.1.3" version = "0.1.10"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"bson", "bson",

View File

@ -1,7 +1,7 @@
[package] [package]
name = "kcl-lib" name = "kcl-lib"
description = "KittyCAD Language" description = "KittyCAD Language"
version = "0.1.3" version = "0.1.10"
edition = "2021" edition = "2021"
license = "MIT" license = "MIT"
@ -33,6 +33,10 @@ reqwest = { version = "0.11.20", default-features = false }
tokio = { version = "1.32.0", features = ["full"] } tokio = { version = "1.32.0", features = ["full"] }
tokio-tungstenite = { version = "0.20.0", features = ["rustls-tls-native-roots"] } tokio-tungstenite = { version = "0.20.0", features = ["rustls-tls-native-roots"] }
[features]
default = ["engine"]
engine = []
[profile.release] [profile.release]
panic = "abort" panic = "abort"
debug = true debug = true

View File

@ -2,6 +2,7 @@
use std::collections::HashMap; use std::collections::HashMap;
use parse_display::{Display, FromStr};
use schemars::JsonSchema; use schemars::JsonSchema;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_json::Map; use serde_json::Map;
@ -383,11 +384,22 @@ pub struct VariableDeclaration {
pub start: usize, pub start: usize,
pub end: usize, pub end: usize,
pub declarations: Vec<VariableDeclarator>, pub declarations: Vec<VariableDeclarator>,
pub kind: String, // Change to enum if there are specific values pub kind: VariableKind, // Change to enum if there are specific values
} }
impl_value_meta!(VariableDeclaration); impl_value_meta!(VariableDeclaration);
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema, FromStr, Display)]
#[ts(export)]
#[serde(rename_all = "snake_case")]
#[display(style = "snake_case")]
pub enum VariableKind {
Let,
Const,
Fn,
Var,
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)] #[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)] #[ts(export)]
#[serde(tag = "type")] #[serde(tag = "type")]

View File

@ -44,6 +44,11 @@ impl StdLibFnArg {
get_type_string_from_schema(&self.schema) get_type_string_from_schema(&self.schema)
} }
#[allow(dead_code)]
pub fn get_autocomplete_string(&self) -> Result<String> {
get_autocomplete_string_from_schema(&self.schema)
}
#[allow(dead_code)] #[allow(dead_code)]
pub fn description(&self) -> Option<String> { pub fn description(&self) -> Option<String> {
get_description_string_from_schema(&self.schema) get_description_string_from_schema(&self.schema)
@ -93,9 +98,24 @@ pub trait StdLibFn {
deprecated: self.deprecated(), deprecated: self.deprecated(),
}) })
} }
fn fn_signature(&self) -> String {
let mut signature = String::new();
signature.push_str(&format!("{}(", self.name()));
for (i, arg) in self.args().iter().enumerate() {
if i > 0 {
signature.push_str(", ");
}
signature.push_str(&format!("{}: {}", arg.name, arg.type_));
}
signature.push_str(") -> ");
signature.push_str(&self.return_value().type_);
signature
}
} }
fn get_description_string_from_schema(schema: &schemars::schema::Schema) -> Option<String> { pub fn get_description_string_from_schema(schema: &schemars::schema::Schema) -> Option<String> {
if let schemars::schema::Schema::Object(o) = schema { if let schemars::schema::Schema::Object(o) = schema {
if let Some(metadata) = &o.metadata { if let Some(metadata) = &o.metadata {
if let Some(description) = &metadata.description { if let Some(description) = &metadata.description {
@ -107,7 +127,7 @@ fn get_description_string_from_schema(schema: &schemars::schema::Schema) -> Opti
None None
} }
fn get_type_string_from_schema(schema: &schemars::schema::Schema) -> Result<(String, bool)> { pub fn get_type_string_from_schema(schema: &schemars::schema::Schema) -> Result<(String, bool)> {
match schema { match schema {
schemars::schema::Schema::Object(o) => { schemars::schema::Schema::Object(o) => {
if let Some(format) = &o.format { if let Some(format) = &o.format {
@ -187,3 +207,78 @@ fn get_type_string_from_schema(schema: &schemars::schema::Schema) -> Result<(Str
schemars::schema::Schema::Bool(_) => Ok((Primitive::Bool.to_string(), false)), schemars::schema::Schema::Bool(_) => Ok((Primitive::Bool.to_string(), false)),
} }
} }
pub fn get_autocomplete_string_from_schema(schema: &schemars::schema::Schema) -> Result<String> {
match schema {
schemars::schema::Schema::Object(o) => {
if let Some(format) = &o.format {
if format == "uuid" {
return Ok(Primitive::Uuid.to_string());
} else if format == "double" || format == "uint" {
return Ok(Primitive::Number.to_string());
} else {
anyhow::bail!("unknown format: {}", format);
}
}
if let Some(obj_val) = &o.object {
let mut fn_docs = String::new();
fn_docs.push_str("{\n");
// Let's print out the object's properties.
for (prop_name, prop) in obj_val.properties.iter() {
if prop_name.starts_with('_') {
continue;
}
if let Some(description) = get_description_string_from_schema(prop) {
fn_docs.push_str(&format!("\t// {}\n", description));
}
fn_docs.push_str(&format!(
"\t\"{}\": {},\n",
prop_name,
get_autocomplete_string_from_schema(prop)?,
));
}
fn_docs.push('}');
return Ok(fn_docs);
}
if let Some(array_val) = &o.array {
if let Some(schemars::schema::SingleOrVec::Single(items)) = &array_val.items {
// Let's print out the object's properties.
return Ok(format!("[{}]", get_autocomplete_string_from_schema(items)?));
} else if let Some(items) = &array_val.contains {
return Ok(format!("[{}]", get_autocomplete_string_from_schema(items)?));
}
}
if let Some(subschemas) = &o.subschemas {
let mut fn_docs = String::new();
if let Some(items) = &subschemas.one_of {
if let Some(item) = items.iter().next() {
// Let's print out the object's properties.
fn_docs.push_str(&get_autocomplete_string_from_schema(item)?);
}
} else if let Some(items) = &subschemas.any_of {
if let Some(item) = items.iter().next() {
// Let's print out the object's properties.
fn_docs.push_str(&get_autocomplete_string_from_schema(item)?);
}
} else {
anyhow::bail!("unknown subschemas: {:#?}", subschemas);
}
return Ok(fn_docs);
}
if let Some(schemars::schema::SingleOrVec::Single(_string)) = &o.instance_type {
return Ok(Primitive::String.to_string());
}
anyhow::bail!("unknown type: {:#?}", o)
}
schemars::schema::Schema::Bool(_) => Ok(Primitive::Bool.to_string()),
}
}

View File

@ -4,16 +4,20 @@ use wasm_bindgen::prelude::*;
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]
#[cfg(not(test))] #[cfg(not(test))]
#[cfg(feature = "engine")]
pub mod conn; pub mod conn;
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]
#[cfg(not(test))] #[cfg(not(test))]
#[cfg(feature = "engine")]
pub use conn::EngineConnection; pub use conn::EngineConnection;
#[cfg(target_arch = "wasm32")] #[cfg(target_arch = "wasm32")]
#[cfg(not(test))] #[cfg(not(test))]
#[cfg(feature = "engine")]
pub mod conn_wasm; pub mod conn_wasm;
#[cfg(target_arch = "wasm32")] #[cfg(target_arch = "wasm32")]
#[cfg(not(test))] #[cfg(not(test))]
#[cfg(feature = "engine")]
pub use conn_wasm::EngineConnection; pub use conn_wasm::EngineConnection;
#[cfg(test)] #[cfg(test)]
@ -21,6 +25,13 @@ pub mod conn_mock;
#[cfg(test)] #[cfg(test)]
pub use conn_mock::EngineConnection; pub use conn_mock::EngineConnection;
#[cfg(not(feature = "engine"))]
#[cfg(not(test))]
pub mod conn_mock;
#[cfg(not(feature = "engine"))]
#[cfg(not(test))]
pub use conn_mock::EngineConnection;
use crate::executor::SourceRange; use crate::executor::SourceRange;
#[derive(Debug)] #[derive(Debug)]
@ -33,6 +44,7 @@ pub struct EngineManager {
impl EngineManager { impl EngineManager {
#[cfg(target_arch = "wasm32")] #[cfg(target_arch = "wasm32")]
#[cfg(not(test))] #[cfg(not(test))]
#[cfg(feature = "engine")]
#[wasm_bindgen(constructor)] #[wasm_bindgen(constructor)]
pub async fn new(manager: conn_wasm::EngineCommandManager) -> EngineManager { pub async fn new(manager: conn_wasm::EngineCommandManager) -> EngineManager {
EngineManager { EngineManager {

View File

@ -298,12 +298,24 @@ impl From<[f64; 2]> for Point2d {
} }
} }
impl From<&[f64; 2]> for Point2d {
fn from(p: &[f64; 2]) -> Self {
Self { x: p[0], y: p[1] }
}
}
impl From<Point2d> for [f64; 2] { impl From<Point2d> for [f64; 2] {
fn from(p: Point2d) -> Self { fn from(p: Point2d) -> Self {
[p.x, p.y] [p.x, p.y]
} }
} }
impl From<Point2d> for kittycad::types::Point2D {
fn from(p: Point2d) -> Self {
Self { x: p.x, y: p.y }
}
}
#[derive(Debug, Deserialize, Serialize, PartialEq, Clone, ts_rs::TS, JsonSchema)] #[derive(Debug, Deserialize, Serialize, PartialEq, Clone, ts_rs::TS, JsonSchema)]
#[ts(export)] #[ts(export)]
pub struct Point3d { pub struct Point3d {

View File

@ -1,5 +1,5 @@
pub mod abstract_syntax_tree_types; pub mod abstract_syntax_tree_types;
mod docs; pub mod docs;
pub mod engine; pub mod engine;
pub mod errors; pub mod errors;
pub mod executor; pub mod executor;

View File

@ -315,23 +315,25 @@ fn build_tree(
}))); })));
return build_tree(&reverse_polish_notation_tokens[1..], new_stack); return build_tree(&reverse_polish_notation_tokens[1..], new_stack);
} else if current_token.token_type == TokenType::Word { } else if current_token.token_type == TokenType::Word {
if reverse_polish_notation_tokens[1].token_type == TokenType::Brace if reverse_polish_notation_tokens.len() > 1 {
&& reverse_polish_notation_tokens[1].value == "(" if reverse_polish_notation_tokens[1].token_type == TokenType::Brace
{ && reverse_polish_notation_tokens[1].value == "("
let closing_brace = find_closing_brace(reverse_polish_notation_tokens, 1, 0, "")?; {
let closing_brace = find_closing_brace(reverse_polish_notation_tokens, 1, 0, "")?;
let mut new_stack = stack;
new_stack.push(MathExpression::CallExpression(Box::new(
make_call_expression(reverse_polish_notation_tokens, 0)?.expression,
)));
return build_tree(&reverse_polish_notation_tokens[closing_brace + 1..], new_stack);
}
let mut new_stack = stack; let mut new_stack = stack;
new_stack.push(MathExpression::CallExpression(Box::new( new_stack.push(MathExpression::Identifier(Box::new(Identifier {
make_call_expression(reverse_polish_notation_tokens, 0)?.expression, name: current_token.value.clone(),
))); start: current_token.start,
return build_tree(&reverse_polish_notation_tokens[closing_brace + 1..], new_stack); end: current_token.end,
})));
return build_tree(&reverse_polish_notation_tokens[1..], new_stack);
} }
let mut new_stack = stack;
new_stack.push(MathExpression::Identifier(Box::new(Identifier {
name: current_token.value.clone(),
start: current_token.start,
end: current_token.end,
})));
return build_tree(&reverse_polish_notation_tokens[1..], new_stack);
} else if current_token.token_type == TokenType::Brace && current_token.value == "(" { } else if current_token.token_type == TokenType::Brace && current_token.value == "(" {
let mut new_stack = stack; let mut new_stack = stack;
new_stack.push(MathExpression::ParenthesisToken(Box::new(ParenthesisToken { new_stack.push(MathExpression::ParenthesisToken(Box::new(ParenthesisToken {
@ -424,6 +426,14 @@ fn build_tree(
new_stack.push(expression); new_stack.push(expression);
return build_tree(&reverse_polish_notation_tokens[1..], new_stack); return build_tree(&reverse_polish_notation_tokens[1..], new_stack);
} }
if stack.len() < 2 {
return Err(KclError::Syntax(KclErrorDetails {
source_ranges: vec![current_token.into()],
message: "unexpected end of expression".to_string(),
}));
}
let left: (BinaryPart, usize) = match &stack[stack.len() - 2] { let left: (BinaryPart, usize) = match &stack[stack.len() - 2] {
MathExpression::ExtendedBinaryExpression(bin_exp) => ( MathExpression::ExtendedBinaryExpression(bin_exp) => (
BinaryPart::BinaryExpression(Box::new(BinaryExpression { BinaryPart::BinaryExpression(Box::new(BinaryExpression {

View File

@ -1,11 +1,11 @@
use std::collections::HashMap; use std::{collections::HashMap, str::FromStr};
use crate::{ use crate::{
abstract_syntax_tree_types::{ abstract_syntax_tree_types::{
ArrayExpression, BinaryExpression, BinaryPart, BodyItem, CallExpression, ExpressionStatement, ArrayExpression, BinaryExpression, BinaryPart, BodyItem, CallExpression, ExpressionStatement,
FunctionExpression, Identifier, Literal, LiteralIdentifier, MemberExpression, MemberObject, NoneCodeMeta, FunctionExpression, Identifier, Literal, LiteralIdentifier, MemberExpression, MemberObject, NoneCodeMeta,
NoneCodeNode, ObjectExpression, ObjectKeyInfo, ObjectProperty, PipeExpression, PipeSubstitution, Program, NoneCodeNode, ObjectExpression, ObjectKeyInfo, ObjectProperty, PipeExpression, PipeSubstitution, Program,
ReturnStatement, UnaryExpression, Value, VariableDeclaration, VariableDeclarator, ReturnStatement, UnaryExpression, Value, VariableDeclaration, VariableDeclarator, VariableKind,
}, },
errors::{KclError, KclErrorDetails}, errors::{KclError, KclErrorDetails},
math_parser::parse_expression, math_parser::parse_expression,
@ -145,7 +145,12 @@ pub fn find_closing_brace(
search_opening_brace: &str, search_opening_brace: &str,
) -> Result<usize, KclError> { ) -> Result<usize, KclError> {
let closing_brace_map: HashMap<&str, &str> = [("(", ")"), ("{", "}"), ("[", "]")].iter().cloned().collect(); let closing_brace_map: HashMap<&str, &str> = [("(", ")"), ("{", "}"), ("[", "]")].iter().cloned().collect();
let current_token = &tokens[index]; let Some(current_token) = tokens.get(index) else {
return Err(KclError::Syntax(KclErrorDetails {
source_ranges: vec![tokens.last().unwrap().into()],
message: "unexpected end".to_string(),
}));
};
let mut search_opening_brace = search_opening_brace; let mut search_opening_brace = search_opening_brace;
let is_first_call = search_opening_brace.is_empty() && brace_count == 0; let is_first_call = search_opening_brace.is_empty() && brace_count == 0;
if is_first_call { if is_first_call {
@ -966,13 +971,12 @@ fn make_variable_declaration(tokens: &[Token], index: usize) -> Result<VariableD
declaration: VariableDeclaration { declaration: VariableDeclaration {
start: current_token.start, start: current_token.start,
end: variable_declarators_result.declarations[variable_declarators_result.declarations.len() - 1].end, end: variable_declarators_result.declarations[variable_declarators_result.declarations.len() - 1].end,
kind: if current_token.value == "const" { kind: VariableKind::from_str(&current_token.value).map_err(|_| {
"const".to_string() KclError::Syntax(KclErrorDetails {
} else if current_token.value == "fn" { source_ranges: vec![current_token.into()],
"fn".to_string() message: "Unexpected token".to_string(),
} else { })
"unkown".to_string() })?,
},
declarations: variable_declarators_result.declarations, declarations: variable_declarators_result.declarations,
}, },
last_index: variable_declarators_result.last_index, last_index: variable_declarators_result.last_index,
@ -2479,7 +2483,7 @@ show(mySk1)"#;
|> close(%)"#, |> close(%)"#,
); );
let result = make_variable_declaration(&tokens, 0).unwrap(); let result = make_variable_declaration(&tokens, 0).unwrap();
assert_eq!(result.declaration.kind, "const"); assert_eq!(result.declaration.kind.to_string(), "const");
assert_eq!(result.declaration.declarations.len(), 1); assert_eq!(result.declaration.declarations.len(), 1);
assert_eq!(result.declaration.declarations[0].id.name, "yo"); assert_eq!(result.declaration.declarations[0].id.name, "yo");
let declaration = result.declaration.declarations[0].clone(); let declaration = result.declaration.declarations[0].clone();

View File

@ -27,8 +27,7 @@ pub type FnMap = HashMap<String, StdFn>;
pub type StdFn = fn(&mut Args) -> Result<MemoryItem, KclError>; pub type StdFn = fn(&mut Args) -> Result<MemoryItem, KclError>;
pub struct StdLib { pub struct StdLib {
#[allow(dead_code)] pub internal_fn_names: Vec<Box<(dyn crate::docs::StdLibFn)>>,
internal_fn_names: Vec<Box<(dyn crate::docs::StdLibFn)>>,
pub fns: FnMap, pub fns: FnMap,
} }
@ -64,6 +63,8 @@ impl StdLib {
Box::new(crate::std::sketch::AngledLineThatIntersects), Box::new(crate::std::sketch::AngledLineThatIntersects),
Box::new(crate::std::sketch::StartSketchAt), Box::new(crate::std::sketch::StartSketchAt),
Box::new(crate::std::sketch::Close), Box::new(crate::std::sketch::Close),
Box::new(crate::std::sketch::Arc),
Box::new(crate::std::sketch::BezierCurve),
]; ];
let mut fns = HashMap::new(); let mut fns = HashMap::new();
@ -536,15 +537,8 @@ mod tests {
fn_docs.push_str(&format!("{}\n\n", internal_fn.description())); fn_docs.push_str(&format!("{}\n\n", internal_fn.description()));
fn_docs.push_str("```\n"); fn_docs.push_str("```\n");
fn_docs.push_str(&format!("{}(", internal_fn.name())); let signature = internal_fn.fn_signature();
for (i, arg) in internal_fn.args().iter().enumerate() { fn_docs.push_str(&signature);
if i > 0 {
fn_docs.push_str(", ");
}
fn_docs.push_str(&format!("{}: {}", arg.name, arg.type_));
}
fn_docs.push_str(") -> ");
fn_docs.push_str(&internal_fn.return_value().type_);
fn_docs.push_str("\n```\n\n"); fn_docs.push_str("\n```\n\n");
fn_docs.push_str("#### Arguments\n\n"); fn_docs.push_str("#### Arguments\n\n");

View File

@ -10,7 +10,7 @@ use crate::{
errors::{KclError, KclErrorDetails}, errors::{KclError, KclErrorDetails},
executor::{BasePath, GeoMeta, MemoryItem, Path, Point2d, Position, Rotation, SketchGroup}, executor::{BasePath, GeoMeta, MemoryItem, Path, Point2d, Position, Rotation, SketchGroup},
std::{ std::{
utils::{get_x_component, get_y_component, intersection_with_parallel_line}, utils::{arc_angles, arc_center_and_end, get_x_component, get_y_component, intersection_with_parallel_line},
Args, Args,
}, },
}; };
@ -43,7 +43,7 @@ pub fn line_to(args: &mut Args) -> Result<MemoryItem, KclError> {
#[stdlib { #[stdlib {
name = "lineTo", name = "lineTo",
}] }]
fn inner_line_to(data: LineToData, sketch_group: SketchGroup, args: &Args) -> Result<SketchGroup, KclError> { fn inner_line_to(data: LineToData, sketch_group: SketchGroup, args: &mut Args) -> Result<SketchGroup, KclError> {
let from = sketch_group.get_coords_from_paths()?; let from = sketch_group.get_coords_from_paths()?;
let to = match data { let to = match data {
LineToData::PointWithTag { to, .. } => to, LineToData::PointWithTag { to, .. } => to,
@ -51,6 +51,21 @@ fn inner_line_to(data: LineToData, sketch_group: SketchGroup, args: &Args) -> Re
}; };
let id = uuid::Uuid::new_v4(); let id = uuid::Uuid::new_v4();
args.send_modeling_cmd(
id,
ModelingCmd::ExtendPath {
path: sketch_group.id,
segment: kittycad::types::PathSegment::Line {
end: Point3D {
x: to[0],
y: to[1],
z: 0.0,
},
},
},
)?;
let current_path = Path::ToPoint { let current_path = Path::ToPoint {
base: BasePath { base: BasePath {
from: from.into(), from: from.into(),
@ -101,7 +116,7 @@ pub fn x_line_to(args: &mut Args) -> Result<MemoryItem, KclError> {
#[stdlib { #[stdlib {
name = "xLineTo", name = "xLineTo",
}] }]
fn inner_x_line_to(data: AxisLineToData, sketch_group: SketchGroup, args: &Args) -> Result<SketchGroup, KclError> { fn inner_x_line_to(data: AxisLineToData, sketch_group: SketchGroup, args: &mut Args) -> Result<SketchGroup, KclError> {
let from = sketch_group.get_coords_from_paths()?; let from = sketch_group.get_coords_from_paths()?;
let line_to_data = match data { let line_to_data = match data {
@ -126,7 +141,7 @@ pub fn y_line_to(args: &mut Args) -> Result<MemoryItem, KclError> {
#[stdlib { #[stdlib {
name = "yLineTo", name = "yLineTo",
}] }]
fn inner_y_line_to(data: AxisLineToData, sketch_group: SketchGroup, args: &Args) -> Result<SketchGroup, KclError> { fn inner_y_line_to(data: AxisLineToData, sketch_group: SketchGroup, args: &mut Args) -> Result<SketchGroup, KclError> {
let from = sketch_group.get_coords_from_paths()?; let from = sketch_group.get_coords_from_paths()?;
let line_to_data = match data { let line_to_data = match data {
@ -716,6 +731,248 @@ fn inner_close(sketch_group: SketchGroup, args: &mut Args) -> Result<SketchGroup
Ok(new_sketch_group) Ok(new_sketch_group)
} }
/// Data to draw an arc.
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)]
#[serde(rename_all = "camelCase", untagged)]
pub enum ArcData {
/// Angles and radius with a tag.
AnglesAndRadiusWithTag {
/// The start angle.
angle_start: f64,
/// The end angle.
angle_end: f64,
/// The radius.
radius: f64,
/// The tag.
tag: String,
},
/// Angles and radius.
AnglesAndRadius {
/// The start angle.
angle_start: f64,
/// The end angle.
angle_end: f64,
/// The radius.
radius: f64,
},
/// Center, to and radius with a tag.
CenterToRadiusWithTag {
/// The center.
center: [f64; 2],
/// The to point.
to: [f64; 2],
/// The radius.
radius: f64,
/// The tag.
tag: String,
},
/// Center, to and radius.
CenterToRadius {
/// The center.
center: [f64; 2],
/// The to point.
to: [f64; 2],
/// The radius.
radius: f64,
},
}
/// Draw an arc.
pub fn arc(args: &mut Args) -> Result<MemoryItem, KclError> {
let (data, sketch_group): (ArcData, SketchGroup) = args.get_data_and_sketch_group()?;
let new_sketch_group = inner_arc(data, sketch_group, args)?;
Ok(MemoryItem::SketchGroup(new_sketch_group))
}
/// Draw an arc.
#[stdlib {
name = "arc",
}]
fn inner_arc(data: ArcData, sketch_group: SketchGroup, args: &mut Args) -> Result<SketchGroup, KclError> {
let from = sketch_group.get_coords_from_paths()?;
let (center, angle_start, angle_end, radius, end) = match &data {
ArcData::AnglesAndRadiusWithTag {
angle_start,
angle_end,
radius,
..
} => {
let (center, end) = arc_center_and_end(&from, *angle_start, *angle_end, *radius);
(center, *angle_start, *angle_end, *radius, end)
}
ArcData::AnglesAndRadius {
angle_start,
angle_end,
radius,
} => {
let (center, end) = arc_center_and_end(&from, *angle_start, *angle_end, *radius);
(center, *angle_start, *angle_end, *radius, end)
}
ArcData::CenterToRadiusWithTag { center, to, radius, .. } => {
let (angle_start, angle_end) = arc_angles(&from, &center.into(), &to.into(), *radius, args.source_range)?;
(center.into(), angle_start, angle_end, *radius, to.into())
}
ArcData::CenterToRadius { center, to, radius } => {
let (angle_start, angle_end) = arc_angles(&from, &center.into(), &to.into(), *radius, args.source_range)?;
(center.into(), angle_start, angle_end, *radius, to.into())
}
};
let id = uuid::Uuid::new_v4();
args.send_modeling_cmd(
id,
ModelingCmd::ExtendPath {
path: sketch_group.id,
segment: kittycad::types::PathSegment::Arc {
angle_start,
angle_end,
center: center.into(),
radius,
},
},
)?;
// Move the path pen to the end of the arc.
// Since that is where we want to draw the next path.
// TODO: the engine should automatically move the pen to the end of the arc.
// This just seems inefficient.
args.send_modeling_cmd(
id,
ModelingCmd::MovePathPen {
path: sketch_group.id,
to: Point3D {
x: end.x,
y: end.y,
z: 0.0,
},
},
)?;
let current_path = Path::ToPoint {
base: BasePath {
from: from.into(),
to: end.into(),
name: match data {
ArcData::AnglesAndRadiusWithTag { tag, .. } => tag.to_string(),
ArcData::AnglesAndRadius { .. } => "".to_string(),
ArcData::CenterToRadiusWithTag { tag, .. } => tag.to_string(),
ArcData::CenterToRadius { .. } => "".to_string(),
},
geo_meta: GeoMeta {
id,
metadata: args.source_range.into(),
},
},
};
let mut new_sketch_group = sketch_group.clone();
new_sketch_group.value.push(current_path);
Ok(new_sketch_group)
}
/// Data to draw a bezier curve.
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)]
#[serde(rename_all = "camelCase", untagged)]
pub enum BezierData {
/// Points with a tag.
PointsWithTag {
/// The to point.
to: [f64; 2],
/// The first control point.
control1: [f64; 2],
/// The second control point.
control2: [f64; 2],
/// The tag.
tag: String,
},
/// Points.
Points {
/// The to point.
to: [f64; 2],
/// The first control point.
control1: [f64; 2],
/// The second control point.
control2: [f64; 2],
},
}
/// Draw a bezier curve.
pub fn bezier_curve(args: &mut Args) -> Result<MemoryItem, KclError> {
let (data, sketch_group): (BezierData, SketchGroup) = args.get_data_and_sketch_group()?;
let new_sketch_group = inner_bezier_curve(data, sketch_group, args)?;
Ok(MemoryItem::SketchGroup(new_sketch_group))
}
/// Draw a bezier curve.
#[stdlib {
name = "bezierCurve",
}]
fn inner_bezier_curve(data: BezierData, sketch_group: SketchGroup, args: &mut Args) -> Result<SketchGroup, KclError> {
let from = sketch_group.get_coords_from_paths()?;
let (to, control1, control2) = match &data {
BezierData::PointsWithTag {
to, control1, control2, ..
} => (to, control1, control2),
BezierData::Points { to, control1, control2 } => (to, control1, control2),
};
let to = [from.x + to[0], from.y + to[1]];
let id = uuid::Uuid::new_v4();
args.send_modeling_cmd(
id,
ModelingCmd::ExtendPath {
path: sketch_group.id,
segment: kittycad::types::PathSegment::Bezier {
control1: Point3D {
x: from.x + control1[0],
y: from.y + control1[1],
z: 0.0,
},
control2: Point3D {
x: from.x + control2[0],
y: from.y + control2[1],
z: 0.0,
},
end: Point3D {
x: to[0],
y: to[1],
z: 0.0,
},
},
},
)?;
let current_path = Path::ToPoint {
base: BasePath {
from: from.into(),
to,
name: if let BezierData::PointsWithTag { tag, .. } = data {
tag.to_string()
} else {
"".to_string()
},
geo_meta: GeoMeta {
id,
metadata: args.source_range.into(),
},
},
};
let mut new_sketch_group = sketch_group.clone();
new_sketch_group.value.push(current_path);
Ok(new_sketch_group)
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {

View File

@ -1,3 +1,8 @@
use crate::{
errors::{KclError, KclErrorDetails},
executor::{Point2d, SourceRange},
};
pub fn get_angle(a: &[f64; 2], b: &[f64; 2]) -> f64 { pub fn get_angle(a: &[f64; 2], b: &[f64; 2]) -> f64 {
let x = b[0] - a[0]; let x = b[0] - a[0];
let y = b[1] - a[1]; let y = b[1] - a[1];
@ -160,12 +165,80 @@ pub fn get_x_component(angle_degree: f64, y_component: f64) -> [f64; 2] {
[sign * x_component, sign * y_component] [sign * x_component, sign * y_component]
} }
pub fn arc_center_and_end(from: &Point2d, start_angle_deg: f64, end_angle_deg: f64, radius: f64) -> (Point2d, Point2d) {
let start_angle = start_angle_deg * (std::f64::consts::PI / 180.0);
let end_angle = end_angle_deg * (std::f64::consts::PI / 180.0);
let center = Point2d {
x: -1.0 * (radius * start_angle.cos() - from.x),
y: -1.0 * (radius * start_angle.sin() - from.y),
};
let end = Point2d {
x: center.x + radius * end_angle.cos(),
y: center.y + radius * end_angle.sin(),
};
(center, end)
}
pub fn arc_angles(
from: &Point2d,
to: &Point2d,
center: &Point2d,
radius: f64,
source_range: SourceRange,
) -> Result<(f64, f64), KclError> {
// First make sure that the points are on the circumference of the circle.
// If not, we'll return an error.
if !is_on_circumference(center, from, radius) {
return Err(KclError::Semantic(KclErrorDetails {
message: format!(
"Point {:?} is not on the circumference of the circle with center {:?} and radius {}.",
from, center, radius
),
source_ranges: vec![source_range],
}));
}
if !is_on_circumference(center, to, radius) {
return Err(KclError::Semantic(KclErrorDetails {
message: format!(
"Point {:?} is not on the circumference of the circle with center {:?} and radius {}.",
to, center, radius
),
source_ranges: vec![source_range],
}));
}
let start_angle = (from.y - center.y).atan2(from.x - center.x);
let end_angle = (to.y - center.y).atan2(to.x - center.x);
let start_angle_deg = start_angle * (180.0 / std::f64::consts::PI);
let end_angle_deg = end_angle * (180.0 / std::f64::consts::PI);
Ok((start_angle_deg, end_angle_deg))
}
pub fn is_on_circumference(center: &Point2d, point: &Point2d, radius: f64) -> bool {
let dx = point.x - center.x;
let dy = point.y - center.y;
let distance_squared = dx.powi(2) + dy.powi(2);
// We'll check if the distance squared is approximately equal to radius squared.
// Due to potential floating point inaccuracies, we'll check if the difference
// is very small (e.g., 1e-9) rather than checking for strict equality.
(distance_squared - radius.powi(2)).abs() < 1e-9
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
// Here you can bring your functions into scope // Here you can bring your functions into scope
use pretty_assertions::assert_eq; use pretty_assertions::assert_eq;
use super::{get_x_component, get_y_component}; use super::{get_x_component, get_y_component};
use crate::executor::SourceRange;
static EACH_QUAD: [(i32, [i32; 2]); 12] = [ static EACH_QUAD: [(i32, [i32; 2]); 12] = [
(-315, [1, 1]), (-315, [1, 1]),
@ -241,4 +314,77 @@ mod tests {
assert!((result[0] - 0.0).abs() < f64::EPSILON); assert!((result[0] - 0.0).abs() < f64::EPSILON);
assert_eq!(result[1] as i32, -1); assert_eq!(result[1] as i32, -1);
} }
#[test]
fn test_arc_center_and_end() {
let (center, end) = super::arc_center_and_end(&super::Point2d { x: 0.0, y: 0.0 }, 0.0, 90.0, 1.0);
assert_eq!(center.x.round(), -1.0);
assert_eq!(center.y, 0.0);
assert_eq!(end.x.round(), -1.0);
assert_eq!(end.y, 1.0);
let (center, end) = super::arc_center_and_end(&super::Point2d { x: 0.0, y: 0.0 }, 0.0, 180.0, 1.0);
assert_eq!(center.x.round(), -1.0);
assert_eq!(center.y, 0.0);
assert_eq!(end.x.round(), -2.0);
assert_eq!(end.y.round(), 0.0);
let (center, end) = super::arc_center_and_end(&super::Point2d { x: 0.0, y: 0.0 }, 0.0, 180.0, 10.0);
assert_eq!(center.x.round(), -10.0);
assert_eq!(center.y, 0.0);
assert_eq!(end.x.round(), -20.0);
assert_eq!(end.y.round(), 0.0);
}
#[test]
fn test_arc_angles() {
let (angle_start, angle_end) = super::arc_angles(
&super::Point2d { x: 0.0, y: 0.0 },
&super::Point2d { x: -1.0, y: 1.0 },
&super::Point2d { x: -1.0, y: 0.0 },
1.0,
SourceRange(Default::default()),
)
.unwrap();
assert_eq!(angle_start.round(), 0.0);
assert_eq!(angle_end.round(), 90.0);
let (angle_start, angle_end) = super::arc_angles(
&super::Point2d { x: 0.0, y: 0.0 },
&super::Point2d { x: -2.0, y: 0.0 },
&super::Point2d { x: -1.0, y: 0.0 },
1.0,
SourceRange(Default::default()),
)
.unwrap();
assert_eq!(angle_start.round(), 0.0);
assert_eq!(angle_end.round(), 180.0);
let (angle_start, angle_end) = super::arc_angles(
&super::Point2d { x: 0.0, y: 0.0 },
&super::Point2d { x: -20.0, y: 0.0 },
&super::Point2d { x: -10.0, y: 0.0 },
10.0,
SourceRange(Default::default()),
)
.unwrap();
assert_eq!(angle_start.round(), 0.0);
assert_eq!(angle_end.round(), 180.0);
let result = super::arc_angles(
&super::Point2d { x: 0.0, y: 5.0 },
&super::Point2d { x: 5.0, y: 5.0 },
&super::Point2d { x: 10.0, y: -10.0 },
10.0,
SourceRange(Default::default()),
);
if let Err(err) = result {
assert!(err.to_string().contains( "Point Point2d { x: 0.0, y: 5.0 } is not on the circumference of the circle with center Point2d { x: 10.0, y: -10.0 } and radius 10."));
} else {
panic!("Expected error");
}
assert_eq!(angle_start.round(), 0.0);
assert_eq!(angle_end.round(), 180.0);
}
} }