2023-08-24 15:34:51 -07:00
|
|
|
|
//! The executor for the AST.
|
|
|
|
|
|
2023-11-08 20:23:59 -06:00
|
|
|
|
use std::{collections::HashMap, sync::Arc};
|
2023-08-24 15:34:51 -07:00
|
|
|
|
|
|
|
|
|
use anyhow::Result;
|
2023-11-09 09:58:20 -06:00
|
|
|
|
use async_recursion::async_recursion;
|
2023-10-05 14:27:48 -07:00
|
|
|
|
use parse_display::{Display, FromStr};
|
2023-08-25 13:41:04 -07:00
|
|
|
|
use schemars::JsonSchema;
|
2023-08-24 15:34:51 -07:00
|
|
|
|
use serde::{Deserialize, Serialize};
|
Remove just one enum (#1096)
# Problem
This is my proposal for fixing #1107 . I've only done it for one stdlib function, `tangentialArcTo` -- if y'all like it, I'll apply this idea to the rest of the stdlib.
Previously, if users want to put a tag on the arc, the function's parameters change type.
```
// Tag missing: first param is array
tangentialArcTo([x, y], %)
// Tag present: first param is object
tangentialArcTo({to: [x, y], tag: "myTag"}, %)
```
# Solution
My proposal in #1006 is that KCL should have optional values. This means we can change the stdlib `tangentialArcTo` function to use them. In this PR, the calls are now like
```
// Tag missing: first param is array
tangentialArcTo([x, y], %)
// Tag present: first param is array still, but we now pass a tag at the end.
tangentialArcTo([x, y], %, "myTag")
```
This adds an "option" type to KCL typesystem, but it's not really revealed to users (no KCL types are revealed to users right now, they write untyped code and only interact with types when they get type errors upon executing programs). Also adds a None type, which is the default case of the Optional enum.
2023-12-18 23:49:32 -06:00
|
|
|
|
use serde_json::Value as JValue;
|
2023-09-05 16:02:27 -07:00
|
|
|
|
use tower_lsp::lsp_types::{Position as LspPosition, Range as LspRange};
|
2023-08-25 13:41:04 -07:00
|
|
|
|
|
2023-08-24 15:34:51 -07:00
|
|
|
|
use crate::{
|
2024-06-06 16:01:41 -05:00
|
|
|
|
ast::types::{BodyItem, FunctionExpression, KclNone, Program, Value},
|
2024-03-13 12:56:46 -07:00
|
|
|
|
engine::EngineManager,
|
2023-08-24 15:34:51 -07:00
|
|
|
|
errors::{KclError, KclErrorDetails},
|
2024-02-12 12:18:37 -08:00
|
|
|
|
fs::FileManager,
|
2024-06-18 14:38:25 -05:00
|
|
|
|
settings::types::UnitLength,
|
2023-11-09 09:58:20 -06:00
|
|
|
|
std::{FunctionKind, StdLib},
|
2023-08-24 15:34:51 -07:00
|
|
|
|
};
|
|
|
|
|
|
2023-08-25 13:41:04 -07:00
|
|
|
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
2023-08-24 15:34:51 -07:00
|
|
|
|
#[ts(export)]
|
|
|
|
|
#[serde(rename_all = "camelCase")]
|
|
|
|
|
pub struct ProgramMemory {
|
|
|
|
|
pub root: HashMap<String, MemoryItem>,
|
|
|
|
|
#[serde(rename = "return")]
|
|
|
|
|
pub return_: Option<ProgramReturn>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl ProgramMemory {
|
|
|
|
|
pub fn new() -> Self {
|
|
|
|
|
Self {
|
2024-02-11 18:26:09 -08:00
|
|
|
|
root: HashMap::from([
|
|
|
|
|
(
|
|
|
|
|
"ZERO".to_string(),
|
|
|
|
|
MemoryItem::UserVal(UserVal {
|
|
|
|
|
value: serde_json::Value::Number(serde_json::value::Number::from(0)),
|
|
|
|
|
meta: Default::default(),
|
|
|
|
|
}),
|
|
|
|
|
),
|
|
|
|
|
(
|
|
|
|
|
"QUARTER_TURN".to_string(),
|
|
|
|
|
MemoryItem::UserVal(UserVal {
|
|
|
|
|
value: serde_json::Value::Number(serde_json::value::Number::from(90)),
|
|
|
|
|
meta: Default::default(),
|
|
|
|
|
}),
|
|
|
|
|
),
|
|
|
|
|
(
|
|
|
|
|
"HALF_TURN".to_string(),
|
|
|
|
|
MemoryItem::UserVal(UserVal {
|
|
|
|
|
value: serde_json::Value::Number(serde_json::value::Number::from(180)),
|
|
|
|
|
meta: Default::default(),
|
|
|
|
|
}),
|
|
|
|
|
),
|
|
|
|
|
(
|
|
|
|
|
"THREE_QUARTER_TURN".to_string(),
|
|
|
|
|
MemoryItem::UserVal(UserVal {
|
|
|
|
|
value: serde_json::Value::Number(serde_json::value::Number::from(270)),
|
|
|
|
|
meta: Default::default(),
|
|
|
|
|
}),
|
|
|
|
|
),
|
|
|
|
|
]),
|
2023-08-24 15:34:51 -07:00
|
|
|
|
return_: None,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Add to the program memory.
|
2023-08-29 14:12:48 -07:00
|
|
|
|
pub fn add(&mut self, key: &str, value: MemoryItem, source_range: SourceRange) -> Result<(), KclError> {
|
2024-05-02 15:13:00 -07:00
|
|
|
|
if self.root.contains_key(key) {
|
2023-08-24 15:34:51 -07:00
|
|
|
|
return Err(KclError::ValueAlreadyDefined(KclErrorDetails {
|
|
|
|
|
message: format!("Cannot redefine {}", key),
|
|
|
|
|
source_ranges: vec![source_range],
|
|
|
|
|
}));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
self.root.insert(key.to_string(), value);
|
|
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Get a value from the program memory.
|
Remove just one enum (#1096)
# Problem
This is my proposal for fixing #1107 . I've only done it for one stdlib function, `tangentialArcTo` -- if y'all like it, I'll apply this idea to the rest of the stdlib.
Previously, if users want to put a tag on the arc, the function's parameters change type.
```
// Tag missing: first param is array
tangentialArcTo([x, y], %)
// Tag present: first param is object
tangentialArcTo({to: [x, y], tag: "myTag"}, %)
```
# Solution
My proposal in #1006 is that KCL should have optional values. This means we can change the stdlib `tangentialArcTo` function to use them. In this PR, the calls are now like
```
// Tag missing: first param is array
tangentialArcTo([x, y], %)
// Tag present: first param is array still, but we now pass a tag at the end.
tangentialArcTo([x, y], %, "myTag")
```
This adds an "option" type to KCL typesystem, but it's not really revealed to users (no KCL types are revealed to users right now, they write untyped code and only interact with types when they get type errors upon executing programs). Also adds a None type, which is the default case of the Optional enum.
2023-12-18 23:49:32 -06:00
|
|
|
|
/// Return Err if not found.
|
2023-08-24 15:34:51 -07:00
|
|
|
|
pub fn get(&self, key: &str, source_range: SourceRange) -> Result<&MemoryItem, KclError> {
|
|
|
|
|
self.root.get(key).ok_or_else(|| {
|
|
|
|
|
KclError::UndefinedValue(KclErrorDetails {
|
|
|
|
|
message: format!("memory item key `{}` is not defined", key),
|
|
|
|
|
source_ranges: vec![source_range],
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Default for ProgramMemory {
|
|
|
|
|
fn default() -> Self {
|
|
|
|
|
Self::new()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-25 13:41:04 -07:00
|
|
|
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
2023-08-24 15:34:51 -07:00
|
|
|
|
#[ts(export)]
|
|
|
|
|
#[serde(rename_all = "camelCase", untagged)]
|
|
|
|
|
pub enum ProgramReturn {
|
2024-03-05 14:53:29 -06:00
|
|
|
|
Arguments,
|
2023-08-24 15:34:51 -07:00
|
|
|
|
Value(MemoryItem),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl From<ProgramReturn> for Vec<SourceRange> {
|
|
|
|
|
fn from(item: ProgramReturn) -> Self {
|
|
|
|
|
match item {
|
2024-03-05 14:53:29 -06:00
|
|
|
|
ProgramReturn::Arguments => Default::default(),
|
2023-08-24 15:34:51 -07:00
|
|
|
|
ProgramReturn::Value(v) => v.into(),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl ProgramReturn {
|
|
|
|
|
pub fn get_value(&self) -> Result<MemoryItem, KclError> {
|
|
|
|
|
match self {
|
|
|
|
|
ProgramReturn::Value(v) => Ok(v.clone()),
|
2024-03-05 14:53:29 -06:00
|
|
|
|
ProgramReturn::Arguments => Err(KclError::Semantic(KclErrorDetails {
|
|
|
|
|
message: "Cannot get value from arguments".to_owned(),
|
2023-08-24 15:34:51 -07:00
|
|
|
|
source_ranges: self.clone().into(),
|
|
|
|
|
})),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-02-11 15:08:54 -08:00
|
|
|
|
/// A memory item.
|
2023-08-25 13:41:04 -07:00
|
|
|
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
2023-08-24 15:34:51 -07:00
|
|
|
|
#[ts(export)]
|
2023-09-12 18:10:27 -07:00
|
|
|
|
#[serde(tag = "type")]
|
2023-08-24 15:34:51 -07:00
|
|
|
|
pub enum MemoryItem {
|
2023-09-12 18:10:27 -07:00
|
|
|
|
UserVal(UserVal),
|
2023-10-05 14:27:48 -07:00
|
|
|
|
Plane(Box<Plane>),
|
2024-02-12 18:08:42 -08:00
|
|
|
|
Face(Box<Face>),
|
2023-09-19 14:20:14 -07:00
|
|
|
|
SketchGroup(Box<SketchGroup>),
|
2024-02-11 15:08:54 -08:00
|
|
|
|
SketchGroups {
|
|
|
|
|
value: Vec<Box<SketchGroup>>,
|
|
|
|
|
},
|
2023-09-19 14:20:14 -07:00
|
|
|
|
ExtrudeGroup(Box<ExtrudeGroup>),
|
2024-02-11 15:08:54 -08:00
|
|
|
|
ExtrudeGroups {
|
|
|
|
|
value: Vec<Box<ExtrudeGroup>>,
|
|
|
|
|
},
|
2024-02-12 12:18:37 -08:00
|
|
|
|
ImportedGeometry(ImportedGeometry),
|
2023-09-12 18:10:27 -07:00
|
|
|
|
#[ts(skip)]
|
2023-08-24 15:34:51 -07:00
|
|
|
|
Function {
|
|
|
|
|
#[serde(skip)]
|
|
|
|
|
func: Option<MemoryFunction>,
|
|
|
|
|
expression: Box<FunctionExpression>,
|
|
|
|
|
#[serde(rename = "__meta")]
|
|
|
|
|
meta: Vec<Metadata>,
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
2024-06-21 16:44:31 -07:00
|
|
|
|
impl MemoryItem {
|
|
|
|
|
pub fn get_sketch_group_set(&self) -> Result<SketchGroupSet> {
|
|
|
|
|
match self {
|
|
|
|
|
MemoryItem::SketchGroup(s) => Ok(SketchGroupSet::SketchGroup(s.clone())),
|
|
|
|
|
MemoryItem::SketchGroups { value } => Ok(SketchGroupSet::SketchGroups(value.clone())),
|
|
|
|
|
MemoryItem::UserVal(value) => {
|
|
|
|
|
let sg: Vec<Box<SketchGroup>> = serde_json::from_value(value.value.clone())
|
|
|
|
|
.map_err(|e| anyhow::anyhow!("Failed to deserialize array of sketch groups from JSON: {}", e))?;
|
|
|
|
|
Ok(SketchGroupSet::SketchGroups(sg.clone()))
|
|
|
|
|
}
|
|
|
|
|
_ => anyhow::bail!("Not a sketch group or sketch groups: {:?}", self),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn get_extrude_group_set(&self) -> Result<ExtrudeGroupSet> {
|
|
|
|
|
match self {
|
|
|
|
|
MemoryItem::ExtrudeGroup(e) => Ok(ExtrudeGroupSet::ExtrudeGroup(e.clone())),
|
|
|
|
|
MemoryItem::ExtrudeGroups { value } => Ok(ExtrudeGroupSet::ExtrudeGroups(value.clone())),
|
|
|
|
|
MemoryItem::UserVal(value) => {
|
|
|
|
|
let eg: Vec<Box<ExtrudeGroup>> = serde_json::from_value(value.value.clone())
|
|
|
|
|
.map_err(|e| anyhow::anyhow!("Failed to deserialize array of extrude groups from JSON: {}", e))?;
|
|
|
|
|
Ok(ExtrudeGroupSet::ExtrudeGroups(eg.clone()))
|
|
|
|
|
}
|
|
|
|
|
_ => anyhow::bail!("Not a extrude group or extrude groups: {:?}", self),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-02-11 15:08:54 -08:00
|
|
|
|
/// A geometry.
|
|
|
|
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
|
|
|
|
#[ts(export)]
|
|
|
|
|
#[serde(tag = "type")]
|
|
|
|
|
pub enum Geometry {
|
|
|
|
|
SketchGroup(Box<SketchGroup>),
|
|
|
|
|
ExtrudeGroup(Box<ExtrudeGroup>),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Geometry {
|
|
|
|
|
pub fn id(&self) -> uuid::Uuid {
|
|
|
|
|
match self {
|
|
|
|
|
Geometry::SketchGroup(s) => s.id,
|
|
|
|
|
Geometry::ExtrudeGroup(e) => e.id,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// A set of geometry.
|
|
|
|
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
|
|
|
|
#[ts(export)]
|
|
|
|
|
#[serde(tag = "type")]
|
|
|
|
|
pub enum Geometries {
|
|
|
|
|
SketchGroups(Vec<Box<SketchGroup>>),
|
|
|
|
|
ExtrudeGroups(Vec<Box<ExtrudeGroup>>),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// A sketch group or a group of sketch groups.
|
|
|
|
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
|
|
|
|
#[ts(export)]
|
|
|
|
|
#[serde(tag = "type", rename_all = "camelCase")]
|
|
|
|
|
pub enum SketchGroupSet {
|
|
|
|
|
SketchGroup(Box<SketchGroup>),
|
|
|
|
|
SketchGroups(Vec<Box<SketchGroup>>),
|
|
|
|
|
}
|
|
|
|
|
|
2024-04-23 10:31:20 -07:00
|
|
|
|
/// A extrude group or a group of extrude groups.
|
|
|
|
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
|
|
|
|
#[ts(export)]
|
|
|
|
|
#[serde(tag = "type", rename_all = "camelCase")]
|
|
|
|
|
pub enum ExtrudeGroupSet {
|
|
|
|
|
ExtrudeGroup(Box<ExtrudeGroup>),
|
|
|
|
|
ExtrudeGroups(Vec<Box<ExtrudeGroup>>),
|
|
|
|
|
}
|
|
|
|
|
|
2024-02-12 12:18:37 -08:00
|
|
|
|
/// Data for an imported geometry.
|
|
|
|
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
|
|
|
|
#[ts(export)]
|
|
|
|
|
#[serde(rename_all = "camelCase")]
|
|
|
|
|
pub struct ImportedGeometry {
|
|
|
|
|
/// The ID of the imported geometry.
|
|
|
|
|
pub id: uuid::Uuid,
|
|
|
|
|
/// The original file paths.
|
|
|
|
|
pub value: Vec<String>,
|
|
|
|
|
#[serde(rename = "__meta")]
|
|
|
|
|
pub meta: Vec<Metadata>,
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-05 14:27:48 -07:00
|
|
|
|
/// A plane.
|
|
|
|
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
|
|
|
|
#[ts(export)]
|
|
|
|
|
#[serde(rename_all = "camelCase")]
|
|
|
|
|
pub struct Plane {
|
|
|
|
|
/// The id of the plane.
|
|
|
|
|
pub id: uuid::Uuid,
|
|
|
|
|
// The code for the plane either a string or custom.
|
|
|
|
|
pub value: PlaneType,
|
|
|
|
|
/// Origin of the plane.
|
|
|
|
|
pub origin: Point3d,
|
|
|
|
|
/// What should the plane’s X axis be?
|
|
|
|
|
pub x_axis: Point3d,
|
|
|
|
|
/// What should the plane’s Y axis be?
|
|
|
|
|
pub y_axis: Point3d,
|
|
|
|
|
/// The z-axis (normal).
|
|
|
|
|
pub z_axis: Point3d,
|
|
|
|
|
#[serde(rename = "__meta")]
|
|
|
|
|
pub meta: Vec<Metadata>,
|
|
|
|
|
}
|
|
|
|
|
|
2024-04-15 17:18:32 -07:00
|
|
|
|
#[derive(Debug, Default, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
|
|
|
|
#[ts(export)]
|
|
|
|
|
#[serde(rename_all = "camelCase")]
|
|
|
|
|
pub struct DefaultPlanes {
|
|
|
|
|
pub xy: uuid::Uuid,
|
|
|
|
|
pub xz: uuid::Uuid,
|
|
|
|
|
pub yz: uuid::Uuid,
|
|
|
|
|
pub neg_xy: uuid::Uuid,
|
|
|
|
|
pub neg_xz: uuid::Uuid,
|
|
|
|
|
pub neg_yz: uuid::Uuid,
|
|
|
|
|
}
|
|
|
|
|
|
2024-02-12 18:08:42 -08:00
|
|
|
|
/// A face.
|
|
|
|
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
|
|
|
|
#[ts(export)]
|
|
|
|
|
#[serde(rename_all = "camelCase")]
|
|
|
|
|
pub struct Face {
|
|
|
|
|
/// The id of the face.
|
|
|
|
|
pub id: uuid::Uuid,
|
|
|
|
|
/// The tag of the face.
|
|
|
|
|
pub value: String,
|
2024-02-26 14:54:42 -08:00
|
|
|
|
/// The original sketch group id of the object we are sketching on.
|
|
|
|
|
pub sketch_group_id: uuid::Uuid,
|
2024-02-12 18:08:42 -08:00
|
|
|
|
/// What should the face’s X axis be?
|
|
|
|
|
pub x_axis: Point3d,
|
|
|
|
|
/// What should the face’s Y axis be?
|
|
|
|
|
pub y_axis: Point3d,
|
|
|
|
|
/// The z-axis (normal).
|
|
|
|
|
pub z_axis: Point3d,
|
|
|
|
|
#[serde(rename = "__meta")]
|
|
|
|
|
pub meta: Vec<Metadata>,
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-05 14:27:48 -07:00
|
|
|
|
/// Type for a plane.
|
|
|
|
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema, FromStr, Display)]
|
|
|
|
|
#[ts(export)]
|
|
|
|
|
#[serde(rename_all = "camelCase")]
|
|
|
|
|
#[display(style = "camelCase")]
|
|
|
|
|
pub enum PlaneType {
|
|
|
|
|
#[serde(rename = "XY", alias = "xy")]
|
|
|
|
|
#[display("XY")]
|
|
|
|
|
XY,
|
|
|
|
|
#[serde(rename = "XZ", alias = "xz")]
|
|
|
|
|
#[display("XZ")]
|
|
|
|
|
XZ,
|
|
|
|
|
#[serde(rename = "YZ", alias = "yz")]
|
|
|
|
|
#[display("YZ")]
|
|
|
|
|
YZ,
|
|
|
|
|
/// A custom plane.
|
|
|
|
|
#[serde(rename = "Custom")]
|
|
|
|
|
#[display("Custom")]
|
|
|
|
|
Custom,
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-25 13:41:04 -07:00
|
|
|
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
|
|
|
|
#[ts(export)]
|
2023-09-12 18:10:27 -07:00
|
|
|
|
#[serde(tag = "type", rename_all = "camelCase")]
|
|
|
|
|
pub struct UserVal {
|
2024-01-11 15:31:35 -08:00
|
|
|
|
#[ts(type = "any")]
|
2023-09-12 18:10:27 -07:00
|
|
|
|
pub value: serde_json::Value,
|
|
|
|
|
#[serde(rename = "__meta")]
|
|
|
|
|
pub meta: Vec<Metadata>,
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-20 18:27:08 -07:00
|
|
|
|
pub type MemoryFunction =
|
|
|
|
|
fn(
|
|
|
|
|
s: Vec<MemoryItem>,
|
|
|
|
|
memory: ProgramMemory,
|
|
|
|
|
expression: Box<FunctionExpression>,
|
|
|
|
|
metadata: Vec<Metadata>,
|
2023-10-05 14:27:48 -07:00
|
|
|
|
ctx: ExecutorContext,
|
2024-04-12 21:32:57 -07:00
|
|
|
|
) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<Option<ProgramReturn>, KclError>> + Send>>;
|
2023-09-20 18:27:08 -07:00
|
|
|
|
|
|
|
|
|
fn force_memory_function<
|
|
|
|
|
F: Fn(
|
|
|
|
|
Vec<MemoryItem>,
|
|
|
|
|
ProgramMemory,
|
|
|
|
|
Box<FunctionExpression>,
|
|
|
|
|
Vec<Metadata>,
|
2023-10-05 14:27:48 -07:00
|
|
|
|
ExecutorContext,
|
2024-04-12 21:32:57 -07:00
|
|
|
|
) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<Option<ProgramReturn>, KclError>> + Send>>,
|
2023-09-20 18:27:08 -07:00
|
|
|
|
>(
|
|
|
|
|
f: F,
|
|
|
|
|
) -> F {
|
|
|
|
|
f
|
|
|
|
|
}
|
2023-08-24 15:34:51 -07:00
|
|
|
|
|
|
|
|
|
impl From<MemoryItem> for Vec<SourceRange> {
|
|
|
|
|
fn from(item: MemoryItem) -> Self {
|
|
|
|
|
match item {
|
2023-09-12 18:10:27 -07:00
|
|
|
|
MemoryItem::UserVal(u) => u.meta.iter().map(|m| m.source_range).collect(),
|
2023-08-24 15:34:51 -07:00
|
|
|
|
MemoryItem::SketchGroup(s) => s.meta.iter().map(|m| m.source_range).collect(),
|
2024-02-11 15:08:54 -08:00
|
|
|
|
MemoryItem::SketchGroups { value } => value
|
|
|
|
|
.iter()
|
|
|
|
|
.flat_map(|sg| sg.meta.iter().map(|m| m.source_range))
|
|
|
|
|
.collect(),
|
2023-08-24 15:34:51 -07:00
|
|
|
|
MemoryItem::ExtrudeGroup(e) => e.meta.iter().map(|m| m.source_range).collect(),
|
2024-02-11 15:08:54 -08:00
|
|
|
|
MemoryItem::ExtrudeGroups { value } => value
|
|
|
|
|
.iter()
|
|
|
|
|
.flat_map(|eg| eg.meta.iter().map(|m| m.source_range))
|
|
|
|
|
.collect(),
|
2024-02-12 12:18:37 -08:00
|
|
|
|
MemoryItem::ImportedGeometry(i) => i.meta.iter().map(|m| m.source_range).collect(),
|
2023-08-24 15:34:51 -07:00
|
|
|
|
MemoryItem::Function { meta, .. } => meta.iter().map(|m| m.source_range).collect(),
|
2023-10-05 14:27:48 -07:00
|
|
|
|
MemoryItem::Plane(p) => p.meta.iter().map(|m| m.source_range).collect(),
|
2024-02-12 18:08:42 -08:00
|
|
|
|
MemoryItem::Face(f) => f.meta.iter().map(|m| m.source_range).collect(),
|
2023-08-24 15:34:51 -07:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl MemoryItem {
|
|
|
|
|
pub fn get_json_value(&self) -> Result<serde_json::Value, KclError> {
|
2023-09-12 18:10:27 -07:00
|
|
|
|
if let MemoryItem::UserVal(user_val) = self {
|
|
|
|
|
Ok(user_val.value.clone())
|
2023-08-24 15:34:51 -07:00
|
|
|
|
} else {
|
2023-09-19 16:05:53 -07:00
|
|
|
|
serde_json::to_value(self).map_err(|err| {
|
|
|
|
|
KclError::Semantic(KclErrorDetails {
|
|
|
|
|
message: format!("Cannot convert memory item to json value: {:?}", err),
|
|
|
|
|
source_ranges: self.clone().into(),
|
|
|
|
|
})
|
|
|
|
|
})
|
2023-08-24 15:34:51 -07:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
Remove just one enum (#1096)
# Problem
This is my proposal for fixing #1107 . I've only done it for one stdlib function, `tangentialArcTo` -- if y'all like it, I'll apply this idea to the rest of the stdlib.
Previously, if users want to put a tag on the arc, the function's parameters change type.
```
// Tag missing: first param is array
tangentialArcTo([x, y], %)
// Tag present: first param is object
tangentialArcTo({to: [x, y], tag: "myTag"}, %)
```
# Solution
My proposal in #1006 is that KCL should have optional values. This means we can change the stdlib `tangentialArcTo` function to use them. In this PR, the calls are now like
```
// Tag missing: first param is array
tangentialArcTo([x, y], %)
// Tag present: first param is array still, but we now pass a tag at the end.
tangentialArcTo([x, y], %, "myTag")
```
This adds an "option" type to KCL typesystem, but it's not really revealed to users (no KCL types are revealed to users right now, they write untyped code and only interact with types when they get type errors upon executing programs). Also adds a None type, which is the default case of the Optional enum.
2023-12-18 23:49:32 -06:00
|
|
|
|
/// Get a JSON value and deserialize it into some concrete type.
|
|
|
|
|
pub fn get_json<T: serde::de::DeserializeOwned>(&self) -> Result<T, KclError> {
|
|
|
|
|
let json = self.get_json_value()?;
|
|
|
|
|
|
|
|
|
|
serde_json::from_value(json).map_err(|e| {
|
|
|
|
|
KclError::Type(KclErrorDetails {
|
|
|
|
|
message: format!("Failed to deserialize struct from JSON: {}", e),
|
|
|
|
|
source_ranges: self.clone().into(),
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Get a JSON value and deserialize it into some concrete type.
|
|
|
|
|
/// If it's a KCL None, return None. Otherwise return Some.
|
|
|
|
|
pub fn get_json_opt<T: serde::de::DeserializeOwned>(&self) -> Result<Option<T>, KclError> {
|
|
|
|
|
let json = self.get_json_value()?;
|
|
|
|
|
if let JValue::Object(ref o) = json {
|
|
|
|
|
if let Some(JValue::String(s)) = o.get("type") {
|
|
|
|
|
if s == "KclNone" {
|
|
|
|
|
return Ok(None);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
serde_json::from_value(json)
|
|
|
|
|
.map_err(|e| {
|
|
|
|
|
KclError::Type(KclErrorDetails {
|
|
|
|
|
message: format!("Failed to deserialize struct from JSON: {}", e),
|
|
|
|
|
source_ranges: self.clone().into(),
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
.map(Some)
|
|
|
|
|
}
|
|
|
|
|
|
2023-11-08 13:44:31 -06:00
|
|
|
|
/// If this memory item is a function, call it with the given arguments, return its val as Ok.
|
|
|
|
|
/// If it's not a function, return Err.
|
2023-09-20 18:27:08 -07:00
|
|
|
|
pub async fn call_fn(
|
2023-08-24 15:34:51 -07:00
|
|
|
|
&self,
|
2023-09-20 18:27:08 -07:00
|
|
|
|
args: Vec<MemoryItem>,
|
|
|
|
|
memory: ProgramMemory,
|
2023-10-05 14:27:48 -07:00
|
|
|
|
ctx: ExecutorContext,
|
2023-08-24 15:34:51 -07:00
|
|
|
|
) -> Result<Option<ProgramReturn>, KclError> {
|
2023-11-08 13:44:31 -06:00
|
|
|
|
let MemoryItem::Function { func, expression, meta } = &self else {
|
|
|
|
|
return Err(KclError::Semantic(KclErrorDetails {
|
2023-09-20 18:27:08 -07:00
|
|
|
|
message: "not a in memory function".to_string(),
|
2023-08-24 15:34:51 -07:00
|
|
|
|
source_ranges: vec![],
|
2023-11-08 13:44:31 -06:00
|
|
|
|
}));
|
|
|
|
|
};
|
|
|
|
|
let Some(func) = func else {
|
|
|
|
|
return Err(KclError::Semantic(KclErrorDetails {
|
|
|
|
|
message: format!("Not a function: {:?}", expression),
|
|
|
|
|
source_ranges: vec![],
|
|
|
|
|
}));
|
|
|
|
|
};
|
|
|
|
|
func(args, memory, expression.clone(), meta.clone(), ctx).await
|
2023-08-24 15:34:51 -07:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-25 13:41:04 -07:00
|
|
|
|
/// A sketch group is a collection of paths.
|
|
|
|
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
2023-08-24 15:34:51 -07:00
|
|
|
|
#[ts(export)]
|
2023-09-12 18:10:27 -07:00
|
|
|
|
#[serde(tag = "type", rename_all = "camelCase")]
|
2023-08-24 15:34:51 -07:00
|
|
|
|
pub struct SketchGroup {
|
2023-08-25 13:41:04 -07:00
|
|
|
|
/// The id of the sketch group.
|
2023-08-24 15:34:51 -07:00
|
|
|
|
pub id: uuid::Uuid,
|
2023-08-25 13:41:04 -07:00
|
|
|
|
/// The paths in the sketch group.
|
2023-08-24 15:34:51 -07:00
|
|
|
|
pub value: Vec<Path>,
|
2024-02-16 16:42:01 -08:00
|
|
|
|
/// What the sketch is on (can be a plane or a face).
|
|
|
|
|
pub on: SketchSurface,
|
2023-08-25 13:41:04 -07:00
|
|
|
|
/// The starting path.
|
2023-08-24 15:34:51 -07:00
|
|
|
|
pub start: BasePath,
|
2023-08-25 13:41:04 -07:00
|
|
|
|
/// Metadata.
|
2023-08-24 15:34:51 -07:00
|
|
|
|
#[serde(rename = "__meta")]
|
|
|
|
|
pub meta: Vec<Metadata>,
|
|
|
|
|
}
|
|
|
|
|
|
2024-02-16 16:42:01 -08:00
|
|
|
|
/// A sketch group type.
|
|
|
|
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
|
|
|
|
#[ts(export)]
|
|
|
|
|
#[serde(tag = "type", rename_all = "camelCase")]
|
|
|
|
|
pub enum SketchSurface {
|
|
|
|
|
Plane(Box<Plane>),
|
|
|
|
|
Face(Box<Face>),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl SketchSurface {
|
|
|
|
|
pub fn id(&self) -> uuid::Uuid {
|
|
|
|
|
match self {
|
|
|
|
|
SketchSurface::Plane(plane) => plane.id,
|
|
|
|
|
SketchSurface::Face(face) => face.id,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
pub fn x_axis(&self) -> Point3d {
|
|
|
|
|
match self {
|
|
|
|
|
SketchSurface::Plane(plane) => plane.x_axis.clone(),
|
|
|
|
|
SketchSurface::Face(face) => face.x_axis.clone(),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
pub fn y_axis(&self) -> Point3d {
|
|
|
|
|
match self {
|
|
|
|
|
SketchSurface::Plane(plane) => plane.y_axis.clone(),
|
|
|
|
|
SketchSurface::Face(face) => face.y_axis.clone(),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
pub fn z_axis(&self) -> Point3d {
|
|
|
|
|
match self {
|
|
|
|
|
SketchSurface::Plane(plane) => plane.z_axis.clone(),
|
|
|
|
|
SketchSurface::Face(face) => face.z_axis.clone(),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-02-11 12:59:00 +11:00
|
|
|
|
pub struct GetTangentialInfoFromPathsResult {
|
|
|
|
|
pub center_or_tangent_point: [f64; 2],
|
|
|
|
|
pub is_center: bool,
|
|
|
|
|
pub ccw: bool,
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-24 15:34:51 -07:00
|
|
|
|
impl SketchGroup {
|
|
|
|
|
pub fn get_path_by_id(&self, id: &uuid::Uuid) -> Option<&Path> {
|
|
|
|
|
self.value.iter().find(|p| p.get_id() == *id)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn get_path_by_name(&self, name: &str) -> Option<&Path> {
|
|
|
|
|
self.value.iter().find(|p| p.get_name() == name)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn get_base_by_name_or_start(&self, name: &str) -> Option<&BasePath> {
|
|
|
|
|
if self.start.name == name {
|
|
|
|
|
Some(&self.start)
|
|
|
|
|
} else {
|
2023-08-29 14:12:48 -07:00
|
|
|
|
self.value.iter().find(|p| p.get_name() == name).map(|p| p.get_base())
|
2023-08-24 15:34:51 -07:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-30 17:48:59 -05:00
|
|
|
|
/// Get the path most recently sketched.
|
|
|
|
|
pub fn latest_path(&self) -> Option<&Path> {
|
|
|
|
|
self.value.last()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// The "pen" is an imaginary pen drawing the path.
|
|
|
|
|
/// This gets the current point the pen is hovering over, i.e. the point
|
|
|
|
|
/// where the last path segment ends, and the next path segment will begin.
|
|
|
|
|
pub fn current_pen_position(&self) -> Result<Point2d, KclError> {
|
|
|
|
|
let Some(path) = self.latest_path() else {
|
2023-08-24 15:34:51 -07:00
|
|
|
|
return Ok(self.start.to.into());
|
2024-05-30 17:48:59 -05:00
|
|
|
|
};
|
2023-08-24 15:34:51 -07:00
|
|
|
|
|
2024-05-30 17:48:59 -05:00
|
|
|
|
let base = path.get_base();
|
|
|
|
|
Ok(base.to.into())
|
2023-08-24 15:34:51 -07:00
|
|
|
|
}
|
2024-02-11 12:59:00 +11:00
|
|
|
|
|
|
|
|
|
pub fn get_tangential_info_from_paths(&self) -> GetTangentialInfoFromPathsResult {
|
2024-05-30 17:48:59 -05:00
|
|
|
|
let Some(path) = self.latest_path() else {
|
2024-02-11 12:59:00 +11:00
|
|
|
|
return GetTangentialInfoFromPathsResult {
|
|
|
|
|
center_or_tangent_point: self.start.to,
|
|
|
|
|
is_center: false,
|
|
|
|
|
ccw: false,
|
|
|
|
|
};
|
2024-05-30 17:48:59 -05:00
|
|
|
|
};
|
|
|
|
|
match path {
|
|
|
|
|
Path::TangentialArcTo { center, ccw, .. } => GetTangentialInfoFromPathsResult {
|
|
|
|
|
center_or_tangent_point: *center,
|
|
|
|
|
is_center: true,
|
|
|
|
|
ccw: *ccw,
|
|
|
|
|
},
|
|
|
|
|
_ => {
|
|
|
|
|
let base = path.get_base();
|
|
|
|
|
GetTangentialInfoFromPathsResult {
|
|
|
|
|
center_or_tangent_point: base.from,
|
|
|
|
|
is_center: false,
|
|
|
|
|
ccw: false,
|
2024-02-11 12:59:00 +11:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-08-24 15:34:51 -07:00
|
|
|
|
}
|
|
|
|
|
|
2023-08-25 13:41:04 -07:00
|
|
|
|
/// An extrude group is a collection of extrude surfaces.
|
|
|
|
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
2023-08-24 15:34:51 -07:00
|
|
|
|
#[ts(export)]
|
2023-09-12 18:10:27 -07:00
|
|
|
|
#[serde(tag = "type", rename_all = "camelCase")]
|
2023-08-24 15:34:51 -07:00
|
|
|
|
pub struct ExtrudeGroup {
|
2023-08-25 13:41:04 -07:00
|
|
|
|
/// The id of the extrude group.
|
2023-08-24 15:34:51 -07:00
|
|
|
|
pub id: uuid::Uuid,
|
2023-08-25 13:41:04 -07:00
|
|
|
|
/// The extrude surfaces.
|
2023-08-24 15:34:51 -07:00
|
|
|
|
pub value: Vec<ExtrudeSurface>,
|
2024-03-05 11:52:45 -08:00
|
|
|
|
/// The sketch group paths.
|
|
|
|
|
pub sketch_group_values: Vec<Path>,
|
2023-08-25 13:41:04 -07:00
|
|
|
|
/// The height of the extrude group.
|
2023-08-24 15:34:51 -07:00
|
|
|
|
pub height: f64,
|
2024-02-12 18:08:42 -08:00
|
|
|
|
/// The x-axis of the extrude group base plane in the 3D space
|
|
|
|
|
pub x_axis: Point3d,
|
|
|
|
|
/// The y-axis of the extrude group base plane in the 3D space
|
|
|
|
|
pub y_axis: Point3d,
|
|
|
|
|
/// The z-axis of the extrude group base plane in the 3D space
|
|
|
|
|
pub z_axis: Point3d,
|
|
|
|
|
/// The id of the extrusion start cap
|
|
|
|
|
pub start_cap_id: Option<uuid::Uuid>,
|
|
|
|
|
/// The id of the extrusion end cap
|
|
|
|
|
pub end_cap_id: Option<uuid::Uuid>,
|
2023-08-25 13:41:04 -07:00
|
|
|
|
/// Metadata.
|
2023-08-24 15:34:51 -07:00
|
|
|
|
#[serde(rename = "__meta")]
|
|
|
|
|
pub meta: Vec<Metadata>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl ExtrudeGroup {
|
|
|
|
|
pub fn get_path_by_id(&self, id: &uuid::Uuid) -> Option<&ExtrudeSurface> {
|
|
|
|
|
self.value.iter().find(|p| p.get_id() == *id)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn get_path_by_name(&self, name: &str) -> Option<&ExtrudeSurface> {
|
|
|
|
|
self.value.iter().find(|p| p.get_name() == name)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-25 13:41:04 -07:00
|
|
|
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
2023-08-24 15:34:51 -07:00
|
|
|
|
#[ts(export)]
|
|
|
|
|
#[serde(rename_all = "camelCase")]
|
|
|
|
|
pub enum BodyType {
|
|
|
|
|
Root,
|
|
|
|
|
Sketch,
|
|
|
|
|
Block,
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-05 16:02:27 -07:00
|
|
|
|
#[derive(Debug, Default, Deserialize, Serialize, PartialEq, Copy, Clone, ts_rs::TS, JsonSchema, Hash, Eq)]
|
2024-06-19 17:32:08 -07:00
|
|
|
|
#[cfg_attr(feature = "pyo3", pyo3::pyclass)]
|
2023-08-24 15:34:51 -07:00
|
|
|
|
#[ts(export)]
|
2023-09-12 18:10:27 -07:00
|
|
|
|
pub struct SourceRange(#[ts(type = "[number, number]")] pub [usize; 2]);
|
2023-08-24 15:34:51 -07:00
|
|
|
|
|
2023-09-05 16:02:27 -07:00
|
|
|
|
impl SourceRange {
|
|
|
|
|
/// Create a new source range.
|
|
|
|
|
pub fn new(start: usize, end: usize) -> Self {
|
|
|
|
|
Self([start, end])
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Get the start of the range.
|
|
|
|
|
pub fn start(&self) -> usize {
|
|
|
|
|
self.0[0]
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Get the end of the range.
|
|
|
|
|
pub fn end(&self) -> usize {
|
|
|
|
|
self.0[1]
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Check if the range contains a position.
|
|
|
|
|
pub fn contains(&self, pos: usize) -> bool {
|
|
|
|
|
pos >= self.start() && pos <= self.end()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn start_to_lsp_position(&self, code: &str) -> LspPosition {
|
|
|
|
|
// Calculate the line and column of the error from the source range.
|
|
|
|
|
// Lines are zero indexed in vscode so we need to subtract 1.
|
2024-06-21 15:06:01 -07:00
|
|
|
|
let mut line = code.get(..self.start()).unwrap_or_default().lines().count();
|
2023-09-05 16:02:27 -07:00
|
|
|
|
if line > 0 {
|
|
|
|
|
line = line.saturating_sub(1);
|
|
|
|
|
}
|
|
|
|
|
let column = code[..self.start()].lines().last().map(|l| l.len()).unwrap_or_default();
|
|
|
|
|
|
|
|
|
|
LspPosition {
|
|
|
|
|
line: line as u32,
|
|
|
|
|
character: column as u32,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn end_to_lsp_position(&self, code: &str) -> LspPosition {
|
2024-06-21 15:06:01 -07:00
|
|
|
|
let lines = code.get(..self.end()).unwrap_or_default().lines();
|
2024-04-15 17:18:32 -07:00
|
|
|
|
if lines.clone().count() == 0 {
|
|
|
|
|
return LspPosition { line: 0, character: 0 };
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-05 16:02:27 -07:00
|
|
|
|
// Calculate the line and column of the error from the source range.
|
|
|
|
|
// Lines are zero indexed in vscode so we need to subtract 1.
|
2024-04-15 17:18:32 -07:00
|
|
|
|
let line = lines.clone().count() - 1;
|
|
|
|
|
let column = lines.last().map(|l| l.len()).unwrap_or_default();
|
2023-09-05 16:02:27 -07:00
|
|
|
|
|
|
|
|
|
LspPosition {
|
|
|
|
|
line: line as u32,
|
|
|
|
|
character: column as u32,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn to_lsp_range(&self, code: &str) -> LspRange {
|
|
|
|
|
let start = self.start_to_lsp_position(code);
|
|
|
|
|
let end = self.end_to_lsp_position(code);
|
|
|
|
|
LspRange { start, end }
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-14 15:51:26 -06:00
|
|
|
|
#[derive(Debug, Deserialize, Serialize, PartialEq, Clone, Copy, ts_rs::TS, JsonSchema)]
|
2023-08-24 15:34:51 -07:00
|
|
|
|
#[ts(export)]
|
|
|
|
|
pub struct Point2d {
|
|
|
|
|
pub x: f64,
|
|
|
|
|
pub y: f64,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl From<[f64; 2]> for Point2d {
|
|
|
|
|
fn from(p: [f64; 2]) -> Self {
|
|
|
|
|
Self { x: p[0], y: p[1] }
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-31 22:19:23 -07:00
|
|
|
|
impl From<&[f64; 2]> for Point2d {
|
|
|
|
|
fn from(p: &[f64; 2]) -> Self {
|
|
|
|
|
Self { x: p[0], y: p[1] }
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-24 15:34:51 -07:00
|
|
|
|
impl From<Point2d> for [f64; 2] {
|
|
|
|
|
fn from(p: Point2d) -> Self {
|
|
|
|
|
[p.x, p.y]
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-31 22:19:23 -07:00
|
|
|
|
impl From<Point2d> for kittycad::types::Point2D {
|
|
|
|
|
fn from(p: Point2d) -> Self {
|
|
|
|
|
Self { x: p.x, y: p.y }
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-14 15:51:26 -06:00
|
|
|
|
impl Point2d {
|
|
|
|
|
pub const ZERO: Self = Self { x: 0.0, y: 0.0 };
|
|
|
|
|
pub fn scale(self, scalar: f64) -> Self {
|
|
|
|
|
Self {
|
|
|
|
|
x: self.x * scalar,
|
|
|
|
|
y: self.y * scalar,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-06-04 16:28:32 -05:00
|
|
|
|
#[derive(Debug, Deserialize, Serialize, PartialEq, Clone, ts_rs::TS, JsonSchema)]
|
2023-08-24 15:34:51 -07:00
|
|
|
|
#[ts(export)]
|
|
|
|
|
pub struct Point3d {
|
|
|
|
|
pub x: f64,
|
|
|
|
|
pub y: f64,
|
|
|
|
|
pub z: f64,
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-05 14:27:48 -07:00
|
|
|
|
impl Point3d {
|
|
|
|
|
pub fn new(x: f64, y: f64, z: f64) -> Self {
|
|
|
|
|
Self { x, y, z }
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl From<Point3d> for kittycad::types::Point3D {
|
|
|
|
|
fn from(p: Point3d) -> Self {
|
|
|
|
|
Self { x: p.x, y: p.y, z: p.z }
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-25 13:41:04 -07:00
|
|
|
|
/// Metadata.
|
|
|
|
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
2023-08-24 15:34:51 -07:00
|
|
|
|
#[ts(export)]
|
|
|
|
|
#[serde(rename_all = "camelCase")]
|
|
|
|
|
pub struct Metadata {
|
2023-08-25 13:41:04 -07:00
|
|
|
|
/// The source range.
|
2023-08-24 15:34:51 -07:00
|
|
|
|
pub source_range: SourceRange,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl From<SourceRange> for Metadata {
|
|
|
|
|
fn from(source_range: SourceRange) -> Self {
|
|
|
|
|
Self { source_range }
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-25 13:41:04 -07:00
|
|
|
|
/// A base path.
|
|
|
|
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
2023-08-24 15:34:51 -07:00
|
|
|
|
#[ts(export)]
|
|
|
|
|
#[serde(rename_all = "camelCase")]
|
|
|
|
|
pub struct BasePath {
|
2023-08-25 13:41:04 -07:00
|
|
|
|
/// The from point.
|
2023-09-12 18:10:27 -07:00
|
|
|
|
#[ts(type = "[number, number]")]
|
2023-08-24 15:34:51 -07:00
|
|
|
|
pub from: [f64; 2],
|
2023-08-25 13:41:04 -07:00
|
|
|
|
/// The to point.
|
2023-09-12 18:10:27 -07:00
|
|
|
|
#[ts(type = "[number, number]")]
|
2023-08-24 15:34:51 -07:00
|
|
|
|
pub to: [f64; 2],
|
2023-08-25 13:41:04 -07:00
|
|
|
|
/// The name of the path.
|
2023-08-24 15:34:51 -07:00
|
|
|
|
pub name: String,
|
2023-08-25 13:41:04 -07:00
|
|
|
|
/// Metadata.
|
2023-08-24 15:34:51 -07:00
|
|
|
|
#[serde(rename = "__geoMeta")]
|
|
|
|
|
pub geo_meta: GeoMeta,
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-25 13:41:04 -07:00
|
|
|
|
/// Geometry metadata.
|
|
|
|
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
2023-08-24 15:34:51 -07:00
|
|
|
|
#[ts(export)]
|
|
|
|
|
#[serde(rename_all = "camelCase")]
|
|
|
|
|
pub struct GeoMeta {
|
2023-08-25 13:41:04 -07:00
|
|
|
|
/// The id of the geometry.
|
2023-08-24 15:34:51 -07:00
|
|
|
|
pub id: uuid::Uuid,
|
2023-08-25 13:41:04 -07:00
|
|
|
|
/// Metadata.
|
2023-08-24 15:34:51 -07:00
|
|
|
|
#[serde(flatten)]
|
|
|
|
|
pub metadata: Metadata,
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-25 13:41:04 -07:00
|
|
|
|
/// A path.
|
|
|
|
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
2023-08-24 15:34:51 -07:00
|
|
|
|
#[ts(export)]
|
2024-02-11 12:59:00 +11:00
|
|
|
|
#[serde(tag = "type")]
|
2023-08-24 15:34:51 -07:00
|
|
|
|
pub enum Path {
|
2023-08-25 13:41:04 -07:00
|
|
|
|
/// A path that goes to a point.
|
2023-08-24 15:34:51 -07:00
|
|
|
|
ToPoint {
|
|
|
|
|
#[serde(flatten)]
|
|
|
|
|
base: BasePath,
|
|
|
|
|
},
|
2024-02-11 12:59:00 +11:00
|
|
|
|
/// A arc that is tangential to the last path segment that goes to a point
|
|
|
|
|
TangentialArcTo {
|
|
|
|
|
#[serde(flatten)]
|
|
|
|
|
base: BasePath,
|
|
|
|
|
/// the arc's center
|
|
|
|
|
#[ts(type = "[number, number]")]
|
|
|
|
|
center: [f64; 2],
|
|
|
|
|
/// arc's direction
|
|
|
|
|
ccw: bool,
|
|
|
|
|
},
|
2024-02-22 19:07:17 -08:00
|
|
|
|
/// A arc that is tangential to the last path segment
|
|
|
|
|
TangentialArc {
|
|
|
|
|
#[serde(flatten)]
|
|
|
|
|
base: BasePath,
|
|
|
|
|
},
|
2023-08-25 13:41:04 -07:00
|
|
|
|
/// A path that is horizontal.
|
2023-08-24 15:34:51 -07:00
|
|
|
|
Horizontal {
|
|
|
|
|
#[serde(flatten)]
|
|
|
|
|
base: BasePath,
|
2023-08-25 13:41:04 -07:00
|
|
|
|
/// The x coordinate.
|
2023-08-24 15:34:51 -07:00
|
|
|
|
x: f64,
|
|
|
|
|
},
|
2023-08-25 13:41:04 -07:00
|
|
|
|
/// An angled line to.
|
2023-08-24 15:34:51 -07:00
|
|
|
|
AngledLineTo {
|
|
|
|
|
#[serde(flatten)]
|
|
|
|
|
base: BasePath,
|
2023-08-25 13:41:04 -07:00
|
|
|
|
/// The x coordinate.
|
2023-08-24 15:34:51 -07:00
|
|
|
|
x: Option<f64>,
|
2023-08-25 13:41:04 -07:00
|
|
|
|
/// The y coordinate.
|
2023-08-24 15:34:51 -07:00
|
|
|
|
y: Option<f64>,
|
|
|
|
|
},
|
2023-08-25 13:41:04 -07:00
|
|
|
|
/// A base path.
|
2023-08-24 15:34:51 -07:00
|
|
|
|
Base {
|
|
|
|
|
#[serde(flatten)]
|
|
|
|
|
base: BasePath,
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Path {
|
|
|
|
|
pub fn get_id(&self) -> uuid::Uuid {
|
|
|
|
|
match self {
|
|
|
|
|
Path::ToPoint { base } => base.geo_meta.id,
|
|
|
|
|
Path::Horizontal { base, .. } => base.geo_meta.id,
|
|
|
|
|
Path::AngledLineTo { base, .. } => base.geo_meta.id,
|
|
|
|
|
Path::Base { base } => base.geo_meta.id,
|
2024-02-11 12:59:00 +11:00
|
|
|
|
Path::TangentialArcTo { base, .. } => base.geo_meta.id,
|
2024-02-22 19:07:17 -08:00
|
|
|
|
Path::TangentialArc { base } => base.geo_meta.id,
|
2023-08-24 15:34:51 -07:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn get_name(&self) -> String {
|
|
|
|
|
match self {
|
|
|
|
|
Path::ToPoint { base } => base.name.clone(),
|
|
|
|
|
Path::Horizontal { base, .. } => base.name.clone(),
|
|
|
|
|
Path::AngledLineTo { base, .. } => base.name.clone(),
|
|
|
|
|
Path::Base { base } => base.name.clone(),
|
2024-02-11 12:59:00 +11:00
|
|
|
|
Path::TangentialArcTo { base, .. } => base.name.clone(),
|
2024-02-22 19:07:17 -08:00
|
|
|
|
Path::TangentialArc { base } => base.name.clone(),
|
2023-08-24 15:34:51 -07:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn get_base(&self) -> &BasePath {
|
|
|
|
|
match self {
|
|
|
|
|
Path::ToPoint { base } => base,
|
|
|
|
|
Path::Horizontal { base, .. } => base,
|
|
|
|
|
Path::AngledLineTo { base, .. } => base,
|
|
|
|
|
Path::Base { base } => base,
|
2024-02-11 12:59:00 +11:00
|
|
|
|
Path::TangentialArcTo { base, .. } => base,
|
2024-02-22 19:07:17 -08:00
|
|
|
|
Path::TangentialArc { base } => base,
|
2023-08-24 15:34:51 -07:00
|
|
|
|
}
|
|
|
|
|
}
|
2024-02-12 18:08:42 -08:00
|
|
|
|
|
|
|
|
|
pub fn get_base_mut(&mut self) -> Option<&mut BasePath> {
|
|
|
|
|
match self {
|
|
|
|
|
Path::ToPoint { base } => Some(base),
|
|
|
|
|
Path::Horizontal { base, .. } => Some(base),
|
|
|
|
|
Path::AngledLineTo { base, .. } => Some(base),
|
|
|
|
|
Path::Base { base } => Some(base),
|
|
|
|
|
Path::TangentialArcTo { base, .. } => Some(base),
|
2024-02-22 19:07:17 -08:00
|
|
|
|
Path::TangentialArc { base } => Some(base),
|
2024-02-12 18:08:42 -08:00
|
|
|
|
}
|
|
|
|
|
}
|
2023-08-24 15:34:51 -07:00
|
|
|
|
}
|
|
|
|
|
|
2023-08-25 13:41:04 -07:00
|
|
|
|
/// An extrude surface.
|
|
|
|
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
2023-08-24 15:34:51 -07:00
|
|
|
|
#[ts(export)]
|
|
|
|
|
#[serde(tag = "type", rename_all = "camelCase")]
|
|
|
|
|
pub enum ExtrudeSurface {
|
2023-08-25 13:41:04 -07:00
|
|
|
|
/// An extrude plane.
|
2024-02-12 18:08:42 -08:00
|
|
|
|
ExtrudePlane(ExtrudePlane),
|
2024-02-22 19:07:17 -08:00
|
|
|
|
ExtrudeArc(ExtrudeArc),
|
2024-02-12 18:08:42 -08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// An extruded plane.
|
|
|
|
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
|
|
|
|
#[ts(export)]
|
|
|
|
|
#[serde(rename_all = "camelCase")]
|
|
|
|
|
pub struct ExtrudePlane {
|
|
|
|
|
/// The face id for the extrude plane.
|
|
|
|
|
pub face_id: uuid::Uuid,
|
|
|
|
|
/// The name.
|
|
|
|
|
pub name: String,
|
|
|
|
|
/// Metadata.
|
|
|
|
|
#[serde(flatten)]
|
|
|
|
|
pub geo_meta: GeoMeta,
|
2023-08-24 15:34:51 -07:00
|
|
|
|
}
|
|
|
|
|
|
2024-02-22 19:07:17 -08:00
|
|
|
|
/// An extruded arc.
|
|
|
|
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
|
|
|
|
#[ts(export)]
|
|
|
|
|
#[serde(rename_all = "camelCase")]
|
|
|
|
|
pub struct ExtrudeArc {
|
|
|
|
|
/// The face id for the extrude plane.
|
|
|
|
|
pub face_id: uuid::Uuid,
|
|
|
|
|
/// The name.
|
|
|
|
|
pub name: String,
|
|
|
|
|
/// Metadata.
|
|
|
|
|
#[serde(flatten)]
|
|
|
|
|
pub geo_meta: GeoMeta,
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-24 15:34:51 -07:00
|
|
|
|
impl ExtrudeSurface {
|
|
|
|
|
pub fn get_id(&self) -> uuid::Uuid {
|
|
|
|
|
match self {
|
2024-02-12 18:08:42 -08:00
|
|
|
|
ExtrudeSurface::ExtrudePlane(ep) => ep.geo_meta.id,
|
2024-02-22 19:07:17 -08:00
|
|
|
|
ExtrudeSurface::ExtrudeArc(ea) => ea.geo_meta.id,
|
2023-08-24 15:34:51 -07:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn get_name(&self) -> String {
|
|
|
|
|
match self {
|
2024-02-13 10:26:09 -08:00
|
|
|
|
ExtrudeSurface::ExtrudePlane(ep) => ep.name.to_string(),
|
2024-02-22 19:07:17 -08:00
|
|
|
|
ExtrudeSurface::ExtrudeArc(ea) => ea.name.to_string(),
|
2023-08-24 15:34:51 -07:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-25 13:41:04 -07:00
|
|
|
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
2023-08-24 15:34:51 -07:00
|
|
|
|
#[ts(export)]
|
|
|
|
|
#[serde(rename_all = "camelCase")]
|
|
|
|
|
pub struct PipeInfo {
|
2024-03-28 13:11:09 -05:00
|
|
|
|
pub previous_results: Option<MemoryItem>,
|
2023-08-24 15:34:51 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl PipeInfo {
|
|
|
|
|
pub fn new() -> Self {
|
2024-03-28 13:11:09 -05:00
|
|
|
|
Self { previous_results: None }
|
2023-08-24 15:34:51 -07:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Default for PipeInfo {
|
|
|
|
|
fn default() -> Self {
|
|
|
|
|
Self::new()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-05 14:27:48 -07:00
|
|
|
|
/// The executor context.
|
2024-06-06 16:01:41 -05:00
|
|
|
|
/// Cloning will return another handle to the same engine connection/session,
|
|
|
|
|
/// as this uses `Arc` under the hood.
|
2023-10-05 14:27:48 -07:00
|
|
|
|
#[derive(Debug, Clone)]
|
|
|
|
|
pub struct ExecutorContext {
|
2024-03-13 12:56:46 -07:00
|
|
|
|
pub engine: Arc<Box<dyn EngineManager>>,
|
2024-04-15 17:18:32 -07:00
|
|
|
|
pub fs: Arc<FileManager>,
|
2023-11-08 20:23:59 -06:00
|
|
|
|
pub stdlib: Arc<StdLib>,
|
2024-04-25 00:13:09 -07:00
|
|
|
|
pub settings: ExecutorSettings,
|
2024-03-26 19:32:31 -07:00
|
|
|
|
/// Mock mode is only for the modeling app when they just want to mock engine calls and not
|
|
|
|
|
/// actually make them.
|
|
|
|
|
pub is_mock: bool,
|
2023-10-05 14:27:48 -07:00
|
|
|
|
}
|
|
|
|
|
|
2024-04-25 00:13:09 -07:00
|
|
|
|
/// The executor settings.
|
|
|
|
|
#[derive(Debug, Clone)]
|
|
|
|
|
pub struct ExecutorSettings {
|
|
|
|
|
/// The unit to use in modeling dimensions.
|
2024-06-18 14:38:25 -05:00
|
|
|
|
pub units: UnitLength,
|
2024-04-25 00:13:09 -07:00
|
|
|
|
/// Highlight edges of 3D objects?
|
|
|
|
|
pub highlight_edges: bool,
|
2024-04-25 02:31:18 -07:00
|
|
|
|
/// Whether or not Screen Space Ambient Occlusion (SSAO) is enabled.
|
|
|
|
|
pub enable_ssao: bool,
|
2024-04-25 00:13:09 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Default for ExecutorSettings {
|
|
|
|
|
fn default() -> Self {
|
|
|
|
|
Self {
|
|
|
|
|
units: Default::default(),
|
|
|
|
|
highlight_edges: true,
|
2024-04-25 02:31:18 -07:00
|
|
|
|
enable_ssao: false,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl From<crate::settings::types::Configuration> for ExecutorSettings {
|
|
|
|
|
fn from(config: crate::settings::types::Configuration) -> Self {
|
|
|
|
|
Self {
|
|
|
|
|
units: config.settings.modeling.base_unit,
|
|
|
|
|
highlight_edges: config.settings.modeling.highlight_edges.into(),
|
|
|
|
|
enable_ssao: config.settings.modeling.enable_ssao.into(),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl From<crate::settings::types::project::ProjectConfiguration> for ExecutorSettings {
|
|
|
|
|
fn from(config: crate::settings::types::project::ProjectConfiguration) -> Self {
|
|
|
|
|
Self {
|
|
|
|
|
units: config.settings.modeling.base_unit,
|
|
|
|
|
highlight_edges: config.settings.modeling.highlight_edges.into(),
|
|
|
|
|
enable_ssao: config.settings.modeling.enable_ssao.into(),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl From<crate::settings::types::ModelingSettings> for ExecutorSettings {
|
|
|
|
|
fn from(modeling: crate::settings::types::ModelingSettings) -> Self {
|
|
|
|
|
Self {
|
|
|
|
|
units: modeling.base_unit,
|
|
|
|
|
highlight_edges: modeling.highlight_edges.into(),
|
|
|
|
|
enable_ssao: modeling.enable_ssao.into(),
|
2024-04-25 00:13:09 -07:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-02-12 12:18:37 -08:00
|
|
|
|
impl ExecutorContext {
|
|
|
|
|
/// Create a new default executor context.
|
|
|
|
|
#[cfg(not(target_arch = "wasm32"))]
|
2024-04-25 02:31:18 -07:00
|
|
|
|
pub async fn new(client: &kittycad::Client, settings: ExecutorSettings) -> Result<Self> {
|
|
|
|
|
let ws = client
|
|
|
|
|
.modeling()
|
|
|
|
|
.commands_ws(
|
|
|
|
|
None,
|
|
|
|
|
None,
|
|
|
|
|
if settings.enable_ssao {
|
|
|
|
|
Some(kittycad::types::PostEffectType::Ssao)
|
|
|
|
|
} else {
|
|
|
|
|
None
|
|
|
|
|
},
|
|
|
|
|
None,
|
|
|
|
|
None,
|
|
|
|
|
None,
|
|
|
|
|
Some(false),
|
|
|
|
|
)
|
|
|
|
|
.await?;
|
|
|
|
|
|
|
|
|
|
let engine: Arc<Box<dyn EngineManager>> =
|
|
|
|
|
Arc::new(Box::new(crate::engine::conn::EngineConnection::new(ws).await?));
|
|
|
|
|
|
|
|
|
|
// Set the edge visibility.
|
|
|
|
|
engine
|
2024-06-19 13:57:50 -07:00
|
|
|
|
.batch_modeling_cmd(
|
2024-04-25 02:31:18 -07:00
|
|
|
|
uuid::Uuid::new_v4(),
|
|
|
|
|
SourceRange::default(),
|
2024-06-19 13:57:50 -07:00
|
|
|
|
&kittycad::types::ModelingCmd::EdgeLinesVisible {
|
2024-04-25 02:31:18 -07:00
|
|
|
|
hidden: !settings.highlight_edges,
|
|
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
.await?;
|
|
|
|
|
|
2024-02-12 12:18:37 -08:00
|
|
|
|
Ok(Self {
|
2024-04-25 02:31:18 -07:00
|
|
|
|
engine,
|
2024-04-15 17:18:32 -07:00
|
|
|
|
fs: Arc::new(FileManager::new()),
|
2024-02-12 12:18:37 -08:00
|
|
|
|
stdlib: Arc::new(StdLib::new()),
|
2024-04-25 00:13:09 -07:00
|
|
|
|
settings,
|
2024-03-26 19:32:31 -07:00
|
|
|
|
is_mock: false,
|
2024-02-12 12:18:37 -08:00
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
2024-06-18 14:38:25 -05:00
|
|
|
|
/// For executing unit tests.
|
|
|
|
|
#[cfg(not(target_arch = "wasm32"))]
|
|
|
|
|
pub async fn new_for_unit_test(units: UnitLength) -> Result<Self> {
|
|
|
|
|
let user_agent = concat!(env!("CARGO_PKG_NAME"), ".rs/", env!("CARGO_PKG_VERSION"),);
|
|
|
|
|
let http_client = reqwest::Client::builder()
|
|
|
|
|
.user_agent(user_agent)
|
|
|
|
|
// For file conversions we need this to be long.
|
|
|
|
|
.timeout(std::time::Duration::from_secs(600))
|
|
|
|
|
.connect_timeout(std::time::Duration::from_secs(60));
|
|
|
|
|
let ws_client = reqwest::Client::builder()
|
|
|
|
|
.user_agent(user_agent)
|
|
|
|
|
// For file conversions we need this to be long.
|
|
|
|
|
.timeout(std::time::Duration::from_secs(600))
|
|
|
|
|
.connect_timeout(std::time::Duration::from_secs(60))
|
|
|
|
|
.connection_verbose(true)
|
|
|
|
|
.tcp_keepalive(std::time::Duration::from_secs(600))
|
|
|
|
|
.http1_only();
|
|
|
|
|
|
|
|
|
|
let token = std::env::var("KITTYCAD_API_TOKEN").expect("KITTYCAD_API_TOKEN not set");
|
|
|
|
|
|
|
|
|
|
// Create the client.
|
|
|
|
|
let mut client = kittycad::Client::new_from_reqwest(token, http_client, ws_client);
|
|
|
|
|
// Set a local engine address if it's set.
|
|
|
|
|
if let Ok(addr) = std::env::var("LOCAL_ENGINE_ADDR") {
|
|
|
|
|
client.set_base_url(addr);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let ctx = ExecutorContext::new(
|
|
|
|
|
&client,
|
|
|
|
|
ExecutorSettings {
|
|
|
|
|
units,
|
|
|
|
|
highlight_edges: true,
|
|
|
|
|
enable_ssao: false,
|
|
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
.await?;
|
|
|
|
|
Ok(ctx)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Clear everything in the scene.
|
|
|
|
|
pub async fn reset_scene(&self) -> Result<()> {
|
|
|
|
|
self.engine
|
|
|
|
|
.send_modeling_cmd(
|
|
|
|
|
uuid::Uuid::new_v4(),
|
|
|
|
|
SourceRange::default(),
|
|
|
|
|
kittycad::types::ModelingCmd::SceneClearAll {},
|
|
|
|
|
)
|
|
|
|
|
.await?;
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
2024-04-12 21:32:57 -07:00
|
|
|
|
/// Perform the execution of a program.
|
|
|
|
|
/// You can optionally pass in some initialization memory.
|
|
|
|
|
/// Kurt uses this for partial execution.
|
|
|
|
|
pub async fn run(
|
|
|
|
|
&self,
|
|
|
|
|
program: crate::ast::types::Program,
|
|
|
|
|
memory: Option<ProgramMemory>,
|
|
|
|
|
) -> Result<ProgramMemory, KclError> {
|
|
|
|
|
// Before we even start executing the program, set the units.
|
|
|
|
|
self.engine
|
2024-06-19 13:57:50 -07:00
|
|
|
|
.batch_modeling_cmd(
|
2024-04-12 21:32:57 -07:00
|
|
|
|
uuid::Uuid::new_v4(),
|
|
|
|
|
SourceRange::default(),
|
2024-06-19 13:57:50 -07:00
|
|
|
|
&kittycad::types::ModelingCmd::SetSceneUnits {
|
2024-06-19 17:32:08 -07:00
|
|
|
|
unit: self.settings.units.into(),
|
2024-04-12 21:32:57 -07:00
|
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
.await?;
|
|
|
|
|
let mut memory = if let Some(memory) = memory {
|
|
|
|
|
memory.clone()
|
|
|
|
|
} else {
|
|
|
|
|
Default::default()
|
|
|
|
|
};
|
|
|
|
|
self.inner_execute(program, &mut memory, crate::executor::BodyType::Root)
|
|
|
|
|
.await
|
|
|
|
|
}
|
2024-02-20 17:55:06 -08:00
|
|
|
|
|
2024-04-12 21:32:57 -07:00
|
|
|
|
/// Execute an AST's program.
|
|
|
|
|
#[async_recursion]
|
|
|
|
|
pub(crate) async fn inner_execute(
|
|
|
|
|
&self,
|
|
|
|
|
program: crate::ast::types::Program,
|
|
|
|
|
memory: &mut ProgramMemory,
|
2024-05-22 18:50:54 -05:00
|
|
|
|
body_type: BodyType,
|
2024-04-12 21:32:57 -07:00
|
|
|
|
) -> Result<ProgramMemory, KclError> {
|
|
|
|
|
let pipe_info = PipeInfo::default();
|
|
|
|
|
|
|
|
|
|
// Iterate over the body of the program.
|
|
|
|
|
for statement in &program.body {
|
|
|
|
|
match statement {
|
|
|
|
|
BodyItem::ExpressionStatement(expression_statement) => {
|
|
|
|
|
if let Value::PipeExpression(pipe_expr) = &expression_statement.expression {
|
|
|
|
|
pipe_expr.get_result(memory, &pipe_info, self).await?;
|
|
|
|
|
} else if let Value::CallExpression(call_expr) = &expression_statement.expression {
|
|
|
|
|
let fn_name = call_expr.callee.name.to_string();
|
|
|
|
|
let mut args: Vec<MemoryItem> = Vec::new();
|
|
|
|
|
for arg in &call_expr.arguments {
|
2024-05-23 14:50:22 -05:00
|
|
|
|
let metadata = Metadata {
|
|
|
|
|
source_range: SourceRange([arg.start(), arg.end()]),
|
|
|
|
|
};
|
|
|
|
|
let mem_item = self
|
|
|
|
|
.arg_into_mem_item(arg, memory, &pipe_info, &metadata, StatementKind::Expression)
|
|
|
|
|
.await?;
|
|
|
|
|
args.push(mem_item);
|
2024-04-12 21:32:57 -07:00
|
|
|
|
}
|
|
|
|
|
match self.stdlib.get_either(&call_expr.callee.name) {
|
|
|
|
|
FunctionKind::Core(func) => {
|
|
|
|
|
let args = crate::std::Args::new(args, call_expr.into(), self.clone());
|
|
|
|
|
let result = func.std_lib_fn()(args).await?;
|
|
|
|
|
memory.return_ = Some(ProgramReturn::Value(result));
|
|
|
|
|
}
|
|
|
|
|
FunctionKind::Std(func) => {
|
|
|
|
|
let mut newmem = memory.clone();
|
|
|
|
|
let result = self
|
|
|
|
|
.inner_execute(func.program().to_owned(), &mut newmem, BodyType::Block)
|
|
|
|
|
.await?;
|
|
|
|
|
memory.return_ = result.return_;
|
|
|
|
|
}
|
|
|
|
|
FunctionKind::UserDefined => {
|
|
|
|
|
if let Some(func) = memory.clone().root.get(&fn_name) {
|
|
|
|
|
let result = func.call_fn(args.clone(), memory.clone(), self.clone()).await?;
|
|
|
|
|
|
|
|
|
|
memory.return_ = result;
|
|
|
|
|
} else {
|
|
|
|
|
return Err(KclError::Semantic(KclErrorDetails {
|
|
|
|
|
message: format!("No such name {} defined", fn_name),
|
|
|
|
|
source_ranges: vec![call_expr.into()],
|
|
|
|
|
}));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
BodyItem::VariableDeclaration(variable_declaration) => {
|
|
|
|
|
for declaration in &variable_declaration.declarations {
|
|
|
|
|
let var_name = declaration.id.name.to_string();
|
|
|
|
|
let source_range: SourceRange = declaration.init.clone().into();
|
|
|
|
|
let metadata = Metadata { source_range };
|
|
|
|
|
|
2024-05-23 14:50:22 -05:00
|
|
|
|
let memory_item = self
|
|
|
|
|
.arg_into_mem_item(
|
|
|
|
|
&declaration.init,
|
|
|
|
|
memory,
|
|
|
|
|
&pipe_info,
|
|
|
|
|
&metadata,
|
|
|
|
|
StatementKind::Declaration { name: &var_name },
|
|
|
|
|
)
|
|
|
|
|
.await?;
|
|
|
|
|
memory.add(&var_name, memory_item, source_range)?;
|
2023-08-24 15:34:51 -07:00
|
|
|
|
}
|
|
|
|
|
}
|
2024-04-12 21:32:57 -07:00
|
|
|
|
BodyItem::ReturnStatement(return_statement) => match &return_statement.argument {
|
|
|
|
|
Value::BinaryExpression(bin_expr) => {
|
|
|
|
|
let result = bin_expr.get_result(memory, &pipe_info, self).await?;
|
|
|
|
|
memory.return_ = Some(ProgramReturn::Value(result));
|
2023-08-24 15:34:51 -07:00
|
|
|
|
}
|
2024-04-12 21:32:57 -07:00
|
|
|
|
Value::UnaryExpression(unary_expr) => {
|
|
|
|
|
let result = unary_expr.get_result(memory, &pipe_info, self).await?;
|
|
|
|
|
memory.return_ = Some(ProgramReturn::Value(result));
|
|
|
|
|
}
|
|
|
|
|
Value::Identifier(identifier) => {
|
|
|
|
|
let value = memory.get(&identifier.name, identifier.into())?.clone();
|
|
|
|
|
memory.return_ = Some(ProgramReturn::Value(value));
|
|
|
|
|
}
|
|
|
|
|
Value::Literal(literal) => {
|
|
|
|
|
memory.return_ = Some(ProgramReturn::Value(literal.into()));
|
|
|
|
|
}
|
|
|
|
|
Value::ArrayExpression(array_expr) => {
|
|
|
|
|
let result = array_expr.execute(memory, &pipe_info, self).await?;
|
|
|
|
|
memory.return_ = Some(ProgramReturn::Value(result));
|
|
|
|
|
}
|
|
|
|
|
Value::ObjectExpression(obj_expr) => {
|
|
|
|
|
let result = obj_expr.execute(memory, &pipe_info, self).await?;
|
|
|
|
|
memory.return_ = Some(ProgramReturn::Value(result));
|
|
|
|
|
}
|
|
|
|
|
Value::CallExpression(call_expr) => {
|
|
|
|
|
let result = call_expr.execute(memory, &pipe_info, self).await?;
|
|
|
|
|
memory.return_ = Some(ProgramReturn::Value(result));
|
|
|
|
|
}
|
|
|
|
|
Value::MemberExpression(member_expr) => {
|
|
|
|
|
let result = member_expr.get_result(memory)?;
|
|
|
|
|
memory.return_ = Some(ProgramReturn::Value(result));
|
|
|
|
|
}
|
|
|
|
|
Value::PipeExpression(pipe_expr) => {
|
|
|
|
|
let result = pipe_expr.get_result(memory, &pipe_info, self).await?;
|
|
|
|
|
memory.return_ = Some(ProgramReturn::Value(result));
|
|
|
|
|
}
|
|
|
|
|
Value::PipeSubstitution(_) => {}
|
|
|
|
|
Value::FunctionExpression(_) => {}
|
|
|
|
|
Value::None(none) => {
|
|
|
|
|
memory.return_ = Some(ProgramReturn::Value(MemoryItem::from(none)));
|
|
|
|
|
}
|
|
|
|
|
},
|
2023-08-24 15:34:51 -07:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-22 18:50:54 -05:00
|
|
|
|
if BodyType::Root == body_type {
|
|
|
|
|
// Flush the batch queue.
|
|
|
|
|
self.engine.flush_batch(SourceRange([program.end, program.end])).await?;
|
|
|
|
|
}
|
2024-04-12 21:32:57 -07:00
|
|
|
|
|
|
|
|
|
Ok(memory.clone())
|
|
|
|
|
}
|
2024-03-23 15:45:55 -07:00
|
|
|
|
|
2024-05-23 14:50:22 -05:00
|
|
|
|
pub async fn arg_into_mem_item<'a>(
|
|
|
|
|
&self,
|
|
|
|
|
init: &Value,
|
|
|
|
|
memory: &mut ProgramMemory,
|
|
|
|
|
pipe_info: &PipeInfo,
|
|
|
|
|
metadata: &Metadata,
|
|
|
|
|
statement_kind: StatementKind<'a>,
|
|
|
|
|
) -> Result<MemoryItem, KclError> {
|
|
|
|
|
let item = match init {
|
|
|
|
|
Value::None(none) => none.into(),
|
|
|
|
|
Value::Literal(literal) => literal.into(),
|
|
|
|
|
Value::Identifier(identifier) => {
|
|
|
|
|
let value = memory.get(&identifier.name, identifier.into())?;
|
|
|
|
|
value.clone()
|
|
|
|
|
}
|
|
|
|
|
Value::BinaryExpression(binary_expression) => binary_expression.get_result(memory, pipe_info, self).await?,
|
|
|
|
|
Value::FunctionExpression(function_expression) => {
|
|
|
|
|
let mem_func = force_memory_function(
|
|
|
|
|
|args: Vec<MemoryItem>,
|
|
|
|
|
memory: ProgramMemory,
|
|
|
|
|
function_expression: Box<FunctionExpression>,
|
|
|
|
|
_metadata: Vec<Metadata>,
|
|
|
|
|
ctx: ExecutorContext| {
|
|
|
|
|
Box::pin(async move {
|
|
|
|
|
let mut fn_memory = assign_args_to_params(&function_expression, args, memory.clone())?;
|
|
|
|
|
|
|
|
|
|
let result = ctx
|
|
|
|
|
.inner_execute(function_expression.body.clone(), &mut fn_memory, BodyType::Block)
|
|
|
|
|
.await?;
|
|
|
|
|
|
|
|
|
|
Ok(result.return_)
|
|
|
|
|
})
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
MemoryItem::Function {
|
|
|
|
|
expression: function_expression.clone(),
|
|
|
|
|
meta: vec![metadata.to_owned()],
|
|
|
|
|
func: Some(mem_func),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
Value::CallExpression(call_expression) => call_expression.execute(memory, pipe_info, self).await?,
|
|
|
|
|
Value::PipeExpression(pipe_expression) => pipe_expression.get_result(memory, pipe_info, self).await?,
|
|
|
|
|
Value::PipeSubstitution(pipe_substitution) => match statement_kind {
|
|
|
|
|
StatementKind::Declaration { name } => {
|
|
|
|
|
let message = format!(
|
|
|
|
|
"you cannot declare variable {name} as %, because % can only be used in function calls"
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
return Err(KclError::Semantic(KclErrorDetails {
|
|
|
|
|
message,
|
|
|
|
|
source_ranges: vec![pipe_substitution.into()],
|
|
|
|
|
}));
|
|
|
|
|
}
|
|
|
|
|
StatementKind::Expression => match pipe_info.previous_results.clone() {
|
|
|
|
|
Some(x) => x,
|
|
|
|
|
None => {
|
|
|
|
|
return Err(KclError::Semantic(KclErrorDetails {
|
|
|
|
|
message: "cannot use % outside a pipe expression".to_owned(),
|
|
|
|
|
source_ranges: vec![pipe_substitution.into()],
|
|
|
|
|
}));
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
Value::ArrayExpression(array_expression) => array_expression.execute(memory, pipe_info, self).await?,
|
|
|
|
|
Value::ObjectExpression(object_expression) => object_expression.execute(memory, pipe_info, self).await?,
|
|
|
|
|
Value::MemberExpression(member_expression) => member_expression.get_result(memory)?,
|
|
|
|
|
Value::UnaryExpression(unary_expression) => unary_expression.get_result(memory, pipe_info, self).await?,
|
|
|
|
|
};
|
|
|
|
|
Ok(item)
|
|
|
|
|
}
|
|
|
|
|
|
2024-04-12 21:32:57 -07:00
|
|
|
|
/// Update the units for the executor.
|
2024-06-18 14:38:25 -05:00
|
|
|
|
pub fn update_units(&mut self, units: UnitLength) {
|
2024-04-25 00:13:09 -07:00
|
|
|
|
self.settings.units = units;
|
2024-04-12 21:32:57 -07:00
|
|
|
|
}
|
2024-06-06 16:01:41 -05:00
|
|
|
|
|
|
|
|
|
/// Execute the program, then get a PNG screenshot.
|
|
|
|
|
pub async fn execute_and_prepare_snapshot(&self, program: Program) -> Result<kittycad::types::TakeSnapshot> {
|
|
|
|
|
let _ = self.run(program, None).await?;
|
|
|
|
|
|
|
|
|
|
// Zoom to fit.
|
|
|
|
|
self.engine
|
|
|
|
|
.send_modeling_cmd(
|
|
|
|
|
uuid::Uuid::new_v4(),
|
|
|
|
|
crate::executor::SourceRange::default(),
|
|
|
|
|
kittycad::types::ModelingCmd::ZoomToFit {
|
|
|
|
|
object_ids: Default::default(),
|
|
|
|
|
padding: 0.1,
|
|
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
.await?;
|
|
|
|
|
|
|
|
|
|
// Send a snapshot request to the engine.
|
|
|
|
|
let resp = self
|
|
|
|
|
.engine
|
|
|
|
|
.send_modeling_cmd(
|
|
|
|
|
uuid::Uuid::new_v4(),
|
|
|
|
|
crate::executor::SourceRange::default(),
|
|
|
|
|
kittycad::types::ModelingCmd::TakeSnapshot {
|
|
|
|
|
format: kittycad::types::ImageFormat::Png,
|
|
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
.await?;
|
|
|
|
|
|
|
|
|
|
let kittycad::types::OkWebSocketResponseData::Modeling {
|
|
|
|
|
modeling_response: kittycad::types::OkModelingCmdResponse::TakeSnapshot { data },
|
|
|
|
|
} = resp
|
|
|
|
|
else {
|
|
|
|
|
anyhow::bail!("Unexpected response from engine: {:?}", resp);
|
|
|
|
|
};
|
|
|
|
|
Ok(data)
|
|
|
|
|
}
|
2023-08-24 15:34:51 -07:00
|
|
|
|
}
|
|
|
|
|
|
Remove just one enum (#1096)
# Problem
This is my proposal for fixing #1107 . I've only done it for one stdlib function, `tangentialArcTo` -- if y'all like it, I'll apply this idea to the rest of the stdlib.
Previously, if users want to put a tag on the arc, the function's parameters change type.
```
// Tag missing: first param is array
tangentialArcTo([x, y], %)
// Tag present: first param is object
tangentialArcTo({to: [x, y], tag: "myTag"}, %)
```
# Solution
My proposal in #1006 is that KCL should have optional values. This means we can change the stdlib `tangentialArcTo` function to use them. In this PR, the calls are now like
```
// Tag missing: first param is array
tangentialArcTo([x, y], %)
// Tag present: first param is array still, but we now pass a tag at the end.
tangentialArcTo([x, y], %, "myTag")
```
This adds an "option" type to KCL typesystem, but it's not really revealed to users (no KCL types are revealed to users right now, they write untyped code and only interact with types when they get type errors upon executing programs). Also adds a None type, which is the default case of the Optional enum.
2023-12-18 23:49:32 -06:00
|
|
|
|
/// For each argument given,
|
|
|
|
|
/// assign it to a parameter of the function, in the given block of function memory.
|
|
|
|
|
/// Returns Err if too few/too many arguments were given for the function.
|
|
|
|
|
fn assign_args_to_params(
|
|
|
|
|
function_expression: &FunctionExpression,
|
|
|
|
|
args: Vec<MemoryItem>,
|
|
|
|
|
mut fn_memory: ProgramMemory,
|
|
|
|
|
) -> Result<ProgramMemory, KclError> {
|
|
|
|
|
let num_args = function_expression.number_of_args();
|
|
|
|
|
let (min_params, max_params) = num_args.into_inner();
|
|
|
|
|
let n = args.len();
|
|
|
|
|
|
|
|
|
|
// Check if the user supplied too many arguments
|
|
|
|
|
// (we'll check for too few arguments below).
|
|
|
|
|
let err_wrong_number_args = KclError::Semantic(KclErrorDetails {
|
|
|
|
|
message: if min_params == max_params {
|
|
|
|
|
format!("Expected {min_params} arguments, got {n}")
|
|
|
|
|
} else {
|
|
|
|
|
format!("Expected {min_params}-{max_params} arguments, got {n}")
|
|
|
|
|
},
|
|
|
|
|
source_ranges: vec![function_expression.into()],
|
|
|
|
|
});
|
|
|
|
|
if n > max_params {
|
|
|
|
|
return Err(err_wrong_number_args);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Add the arguments to the memory.
|
|
|
|
|
for (index, param) in function_expression.params.iter().enumerate() {
|
|
|
|
|
if let Some(arg) = args.get(index) {
|
|
|
|
|
// Argument was provided.
|
|
|
|
|
fn_memory.add(¶m.identifier.name, arg.clone(), (¶m.identifier).into())?;
|
|
|
|
|
} else {
|
|
|
|
|
// Argument was not provided.
|
|
|
|
|
if param.optional {
|
|
|
|
|
// If the corresponding parameter is optional,
|
|
|
|
|
// then it's fine, the user doesn't need to supply it.
|
|
|
|
|
let none = KclNone {
|
|
|
|
|
start: param.identifier.start,
|
|
|
|
|
end: param.identifier.end,
|
|
|
|
|
};
|
|
|
|
|
fn_memory.add(
|
|
|
|
|
¶m.identifier.name,
|
|
|
|
|
MemoryItem::from(&none),
|
|
|
|
|
(¶m.identifier).into(),
|
|
|
|
|
)?;
|
|
|
|
|
} else {
|
|
|
|
|
// But if the corresponding parameter was required,
|
|
|
|
|
// then the user has called with too few arguments.
|
|
|
|
|
return Err(err_wrong_number_args);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
Ok(fn_memory)
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-23 14:50:22 -05:00
|
|
|
|
pub enum StatementKind<'a> {
|
|
|
|
|
Declaration { name: &'a str },
|
|
|
|
|
Expression,
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-24 15:34:51 -07:00
|
|
|
|
#[cfg(test)]
|
|
|
|
|
mod tests {
|
2024-03-13 12:56:46 -07:00
|
|
|
|
use std::sync::Arc;
|
|
|
|
|
|
2023-08-24 15:34:51 -07:00
|
|
|
|
use pretty_assertions::assert_eq;
|
|
|
|
|
|
2023-08-29 14:12:48 -07:00
|
|
|
|
use super::*;
|
2024-02-11 15:08:54 -08:00
|
|
|
|
use crate::ast::types::{Identifier, Parameter};
|
2023-08-29 14:12:48 -07:00
|
|
|
|
|
2023-08-24 15:34:51 -07:00
|
|
|
|
pub async fn parse_execute(code: &str) -> Result<ProgramMemory> {
|
2024-04-15 17:18:32 -07:00
|
|
|
|
let tokens = crate::token::lexer(code)?;
|
2023-09-05 16:02:27 -07:00
|
|
|
|
let parser = crate::parser::Parser::new(tokens);
|
|
|
|
|
let program = parser.ast()?;
|
2024-03-13 12:56:46 -07:00
|
|
|
|
let ctx = ExecutorContext {
|
|
|
|
|
engine: Arc::new(Box::new(crate::engine::conn_mock::EngineConnection::new().await?)),
|
2024-04-15 17:18:32 -07:00
|
|
|
|
fs: Arc::new(crate::fs::FileManager::new()),
|
2024-03-13 12:56:46 -07:00
|
|
|
|
stdlib: Arc::new(crate::std::StdLib::new()),
|
2024-04-25 00:13:09 -07:00
|
|
|
|
settings: Default::default(),
|
2024-05-15 10:17:29 -07:00
|
|
|
|
is_mock: true,
|
2024-03-13 12:56:46 -07:00
|
|
|
|
};
|
2024-04-12 21:32:57 -07:00
|
|
|
|
let memory = ctx.run(program, None).await?;
|
2023-08-24 15:34:51 -07:00
|
|
|
|
|
|
|
|
|
Ok(memory)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[tokio::test(flavor = "multi_thread")]
|
|
|
|
|
async fn test_execute_assign_two_variables() {
|
|
|
|
|
let ast = r#"const myVar = 5
|
|
|
|
|
const newVar = myVar + 1"#;
|
|
|
|
|
let memory = parse_execute(ast).await.unwrap();
|
|
|
|
|
assert_eq!(
|
|
|
|
|
serde_json::json!(5),
|
|
|
|
|
memory.root.get("myVar").unwrap().get_json_value().unwrap()
|
|
|
|
|
);
|
|
|
|
|
assert_eq!(
|
|
|
|
|
serde_json::json!(6.0),
|
|
|
|
|
memory.root.get("newVar").unwrap().get_json_value().unwrap()
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[tokio::test(flavor = "multi_thread")]
|
|
|
|
|
async fn test_execute_angled_line_that_intersects() {
|
|
|
|
|
let ast_fn = |offset: &str| -> String {
|
|
|
|
|
format!(
|
2023-10-05 14:27:48 -07:00
|
|
|
|
r#"const part001 = startSketchOn('XY')
|
|
|
|
|
|> startProfileAt([0, 0], %)
|
2024-03-15 17:03:42 -04:00
|
|
|
|
|> lineTo([2, 2], %, "yo")
|
2023-08-24 15:34:51 -07:00
|
|
|
|
|> lineTo([3, 1], %)
|
|
|
|
|
|> angledLineThatIntersects({{
|
|
|
|
|
angle: 180,
|
|
|
|
|
intersectTag: 'yo',
|
|
|
|
|
offset: {},
|
2024-03-15 17:03:42 -04:00
|
|
|
|
}}, %, 'yo2')
|
2024-03-01 17:16:18 -08:00
|
|
|
|
const intersect = segEndX('yo2', part001)"#,
|
2023-08-24 15:34:51 -07:00
|
|
|
|
offset
|
|
|
|
|
)
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let memory = parse_execute(&ast_fn("-1")).await.unwrap();
|
|
|
|
|
assert_eq!(
|
|
|
|
|
serde_json::json!(1.0 + 2.0f64.sqrt()),
|
2023-08-29 14:12:48 -07:00
|
|
|
|
memory.root.get("intersect").unwrap().get_json_value().unwrap()
|
2023-08-24 15:34:51 -07:00
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
let memory = parse_execute(&ast_fn("0")).await.unwrap();
|
|
|
|
|
assert_eq!(
|
|
|
|
|
serde_json::json!(1.0000000000000002),
|
2023-08-29 14:12:48 -07:00
|
|
|
|
memory.root.get("intersect").unwrap().get_json_value().unwrap()
|
2023-08-24 15:34:51 -07:00
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[tokio::test(flavor = "multi_thread")]
|
|
|
|
|
async fn test_execute_fn_definitions() {
|
2023-09-13 11:42:09 -07:00
|
|
|
|
let ast = r#"fn def = (x) => {
|
2023-08-24 15:34:51 -07:00
|
|
|
|
return x
|
|
|
|
|
}
|
2023-09-13 11:42:09 -07:00
|
|
|
|
fn ghi = (x) => {
|
2023-08-24 15:34:51 -07:00
|
|
|
|
return x
|
|
|
|
|
}
|
2023-09-13 11:42:09 -07:00
|
|
|
|
fn jkl = (x) => {
|
2023-08-24 15:34:51 -07:00
|
|
|
|
return x
|
|
|
|
|
}
|
2023-09-13 11:42:09 -07:00
|
|
|
|
fn hmm = (x) => {
|
2023-08-24 15:34:51 -07:00
|
|
|
|
return x
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const yo = 5 + 6
|
|
|
|
|
|
|
|
|
|
const abc = 3
|
|
|
|
|
const identifierGuy = 5
|
2023-10-05 14:27:48 -07:00
|
|
|
|
const part001 = startSketchOn('XY')
|
|
|
|
|
|> startProfileAt([-1.2, 4.83], %)
|
2023-08-24 15:34:51 -07:00
|
|
|
|
|> line([2.8, 0], %)
|
|
|
|
|
|> angledLine([100 + 100, 3.01], %)
|
|
|
|
|
|> angledLine([abc, 3.02], %)
|
|
|
|
|
|> angledLine([def(yo), 3.03], %)
|
|
|
|
|
|> angledLine([ghi(2), 3.04], %)
|
|
|
|
|
|> angledLine([jkl(yo) + 2, 3.05], %)
|
|
|
|
|
|> close(%)
|
2024-03-01 17:16:18 -08:00
|
|
|
|
const yo2 = hmm([identifierGuy + 5])"#;
|
2023-08-24 15:34:51 -07:00
|
|
|
|
|
|
|
|
|
parse_execute(ast).await.unwrap();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[tokio::test(flavor = "multi_thread")]
|
|
|
|
|
async fn test_execute_with_pipe_substitutions_unary() {
|
|
|
|
|
let ast = r#"const myVar = 3
|
2023-10-05 14:27:48 -07:00
|
|
|
|
const part001 = startSketchOn('XY')
|
|
|
|
|
|> startProfileAt([0, 0], %)
|
2024-03-15 17:03:42 -04:00
|
|
|
|
|> line([3, 4], %, 'seg01')
|
2023-08-24 15:34:51 -07:00
|
|
|
|
|> line([
|
|
|
|
|
min(segLen('seg01', %), myVar),
|
|
|
|
|
-legLen(segLen('seg01', %), myVar)
|
|
|
|
|
], %)
|
2024-03-01 17:16:18 -08:00
|
|
|
|
"#;
|
2023-08-24 15:34:51 -07:00
|
|
|
|
|
|
|
|
|
parse_execute(ast).await.unwrap();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[tokio::test(flavor = "multi_thread")]
|
|
|
|
|
async fn test_execute_with_pipe_substitutions() {
|
|
|
|
|
let ast = r#"const myVar = 3
|
2023-10-05 14:27:48 -07:00
|
|
|
|
const part001 = startSketchOn('XY')
|
|
|
|
|
|> startProfileAt([0, 0], %)
|
2024-03-15 17:03:42 -04:00
|
|
|
|
|> line([3, 4], %, 'seg01')
|
2023-08-24 15:34:51 -07:00
|
|
|
|
|> line([
|
|
|
|
|
min(segLen('seg01', %), myVar),
|
|
|
|
|
legLen(segLen('seg01', %), myVar)
|
|
|
|
|
], %)
|
2024-03-01 17:16:18 -08:00
|
|
|
|
"#;
|
2023-09-05 16:02:27 -07:00
|
|
|
|
|
|
|
|
|
parse_execute(ast).await.unwrap();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[tokio::test(flavor = "multi_thread")]
|
|
|
|
|
async fn test_execute_with_inline_comment() {
|
|
|
|
|
let ast = r#"const baseThick = 1
|
|
|
|
|
const armAngle = 60
|
|
|
|
|
|
|
|
|
|
const baseThickHalf = baseThick / 2
|
|
|
|
|
const halfArmAngle = armAngle / 2
|
|
|
|
|
|
|
|
|
|
const arrExpShouldNotBeIncluded = [1, 2, 3]
|
|
|
|
|
const objExpShouldNotBeIncluded = { a: 1, b: 2, c: 3 }
|
|
|
|
|
|
2023-10-05 14:27:48 -07:00
|
|
|
|
const part001 = startSketchOn('XY')
|
|
|
|
|
|> startProfileAt([0, 0], %)
|
2023-09-05 16:02:27 -07:00
|
|
|
|
|> yLineTo(1, %)
|
|
|
|
|
|> xLine(3.84, %) // selection-range-7ish-before-this
|
|
|
|
|
|
|
|
|
|
const variableBelowShouldNotBeIncluded = 3
|
2024-03-01 17:16:18 -08:00
|
|
|
|
"#;
|
2023-08-24 15:34:51 -07:00
|
|
|
|
|
|
|
|
|
parse_execute(ast).await.unwrap();
|
|
|
|
|
}
|
2023-09-11 15:15:37 -07:00
|
|
|
|
|
|
|
|
|
#[tokio::test(flavor = "multi_thread")]
|
|
|
|
|
async fn test_execute_with_function_literal_in_pipe() {
|
|
|
|
|
let ast = r#"const w = 20
|
|
|
|
|
const l = 8
|
|
|
|
|
const h = 10
|
|
|
|
|
|
|
|
|
|
fn thing = () => {
|
|
|
|
|
return -8
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-05 14:27:48 -07:00
|
|
|
|
const firstExtrude = startSketchOn('XY')
|
|
|
|
|
|> startProfileAt([0,0], %)
|
2023-09-11 15:15:37 -07:00
|
|
|
|
|> line([0, l], %)
|
|
|
|
|
|> line([w, 0], %)
|
|
|
|
|
|> line([0, thing()], %)
|
|
|
|
|
|> close(%)
|
2024-03-01 17:16:18 -08:00
|
|
|
|
|> extrude(h, %)"#;
|
2023-09-11 15:15:37 -07:00
|
|
|
|
|
|
|
|
|
parse_execute(ast).await.unwrap();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[tokio::test(flavor = "multi_thread")]
|
|
|
|
|
async fn test_execute_with_function_unary_in_pipe() {
|
|
|
|
|
let ast = r#"const w = 20
|
|
|
|
|
const l = 8
|
|
|
|
|
const h = 10
|
|
|
|
|
|
|
|
|
|
fn thing = (x) => {
|
|
|
|
|
return -x
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-05 14:27:48 -07:00
|
|
|
|
const firstExtrude = startSketchOn('XY')
|
|
|
|
|
|> startProfileAt([0,0], %)
|
2023-09-11 15:15:37 -07:00
|
|
|
|
|> line([0, l], %)
|
|
|
|
|
|> line([w, 0], %)
|
|
|
|
|
|> line([0, thing(8)], %)
|
|
|
|
|
|> close(%)
|
2024-03-01 17:16:18 -08:00
|
|
|
|
|> extrude(h, %)"#;
|
2023-09-11 15:15:37 -07:00
|
|
|
|
|
|
|
|
|
parse_execute(ast).await.unwrap();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[tokio::test(flavor = "multi_thread")]
|
|
|
|
|
async fn test_execute_with_function_array_in_pipe() {
|
|
|
|
|
let ast = r#"const w = 20
|
|
|
|
|
const l = 8
|
|
|
|
|
const h = 10
|
|
|
|
|
|
|
|
|
|
fn thing = (x) => {
|
|
|
|
|
return [0, -x]
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-05 14:27:48 -07:00
|
|
|
|
const firstExtrude = startSketchOn('XY')
|
|
|
|
|
|> startProfileAt([0,0], %)
|
2023-09-11 15:15:37 -07:00
|
|
|
|
|> line([0, l], %)
|
|
|
|
|
|> line([w, 0], %)
|
|
|
|
|
|> line(thing(8), %)
|
|
|
|
|
|> close(%)
|
2024-03-01 17:16:18 -08:00
|
|
|
|
|> extrude(h, %)"#;
|
2023-09-11 15:15:37 -07:00
|
|
|
|
|
|
|
|
|
parse_execute(ast).await.unwrap();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[tokio::test(flavor = "multi_thread")]
|
|
|
|
|
async fn test_execute_with_function_call_in_pipe() {
|
|
|
|
|
let ast = r#"const w = 20
|
|
|
|
|
const l = 8
|
|
|
|
|
const h = 10
|
|
|
|
|
|
|
|
|
|
fn other_thing = (y) => {
|
|
|
|
|
return -y
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn thing = (x) => {
|
|
|
|
|
return other_thing(x)
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-05 14:27:48 -07:00
|
|
|
|
const firstExtrude = startSketchOn('XY')
|
|
|
|
|
|> startProfileAt([0,0], %)
|
2023-09-11 15:15:37 -07:00
|
|
|
|
|> line([0, l], %)
|
|
|
|
|
|> line([w, 0], %)
|
|
|
|
|
|> line([0, thing(8)], %)
|
|
|
|
|
|> close(%)
|
2024-03-01 17:16:18 -08:00
|
|
|
|
|> extrude(h, %)"#;
|
2023-09-11 15:15:37 -07:00
|
|
|
|
|
|
|
|
|
parse_execute(ast).await.unwrap();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[tokio::test(flavor = "multi_thread")]
|
|
|
|
|
async fn test_execute_with_function_sketch() {
|
2023-09-13 11:42:09 -07:00
|
|
|
|
let ast = r#"fn box = (h, l, w) => {
|
2023-10-05 14:27:48 -07:00
|
|
|
|
const myBox = startSketchOn('XY')
|
|
|
|
|
|> startProfileAt([0,0], %)
|
2023-09-11 15:15:37 -07:00
|
|
|
|
|> line([0, l], %)
|
|
|
|
|
|> line([w, 0], %)
|
|
|
|
|
|> line([0, -l], %)
|
|
|
|
|
|> close(%)
|
|
|
|
|
|> extrude(h, %)
|
|
|
|
|
|
|
|
|
|
return myBox
|
|
|
|
|
}
|
|
|
|
|
|
2024-03-01 17:16:18 -08:00
|
|
|
|
const fnBox = box(3, 6, 10)"#;
|
2023-09-11 15:15:37 -07:00
|
|
|
|
|
|
|
|
|
parse_execute(ast).await.unwrap();
|
|
|
|
|
}
|
2023-09-13 07:23:14 -07:00
|
|
|
|
|
|
|
|
|
#[tokio::test(flavor = "multi_thread")]
|
|
|
|
|
async fn test_get_member_of_object_with_function_period() {
|
2023-09-13 11:42:09 -07:00
|
|
|
|
let ast = r#"fn box = (obj) => {
|
2023-10-05 14:27:48 -07:00
|
|
|
|
let myBox = startSketchOn('XY')
|
|
|
|
|
|> startProfileAt(obj.start, %)
|
2023-09-13 07:23:14 -07:00
|
|
|
|
|> line([0, obj.l], %)
|
|
|
|
|
|> line([obj.w, 0], %)
|
|
|
|
|
|> line([0, -obj.l], %)
|
|
|
|
|
|> close(%)
|
|
|
|
|
|> extrude(obj.h, %)
|
|
|
|
|
|
|
|
|
|
return myBox
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const thisBox = box({start: [0,0], l: 6, w: 10, h: 3})
|
|
|
|
|
"#;
|
|
|
|
|
parse_execute(ast).await.unwrap();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[tokio::test(flavor = "multi_thread")]
|
|
|
|
|
async fn test_get_member_of_object_with_function_brace() {
|
2023-09-13 11:42:09 -07:00
|
|
|
|
let ast = r#"fn box = (obj) => {
|
2023-10-05 14:27:48 -07:00
|
|
|
|
let myBox = startSketchOn('XY')
|
|
|
|
|
|> startProfileAt(obj["start"], %)
|
2023-09-13 07:23:14 -07:00
|
|
|
|
|> line([0, obj["l"]], %)
|
|
|
|
|
|> line([obj["w"], 0], %)
|
|
|
|
|
|> line([0, -obj["l"]], %)
|
|
|
|
|
|> close(%)
|
|
|
|
|
|> extrude(obj["h"], %)
|
|
|
|
|
|
|
|
|
|
return myBox
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const thisBox = box({start: [0,0], l: 6, w: 10, h: 3})
|
|
|
|
|
"#;
|
|
|
|
|
parse_execute(ast).await.unwrap();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[tokio::test(flavor = "multi_thread")]
|
|
|
|
|
async fn test_get_member_of_object_with_function_mix_period_brace() {
|
2023-09-13 11:42:09 -07:00
|
|
|
|
let ast = r#"fn box = (obj) => {
|
2023-10-05 14:27:48 -07:00
|
|
|
|
let myBox = startSketchOn('XY')
|
|
|
|
|
|> startProfileAt(obj["start"], %)
|
2023-09-13 07:23:14 -07:00
|
|
|
|
|> line([0, obj["l"]], %)
|
|
|
|
|
|> line([obj["w"], 0], %)
|
|
|
|
|
|> line([10 - obj["w"], -obj.l], %)
|
|
|
|
|
|> close(%)
|
|
|
|
|
|> extrude(obj["h"], %)
|
|
|
|
|
|
|
|
|
|
return myBox
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const thisBox = box({start: [0,0], l: 6, w: 10, h: 3})
|
|
|
|
|
"#;
|
|
|
|
|
parse_execute(ast).await.unwrap();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[tokio::test(flavor = "multi_thread")]
|
|
|
|
|
#[ignore] // ignore til we get loops
|
|
|
|
|
async fn test_execute_with_function_sketch_loop_objects() {
|
2023-09-13 11:42:09 -07:00
|
|
|
|
let ast = r#"fn box = (obj) => {
|
2023-10-05 14:27:48 -07:00
|
|
|
|
let myBox = startSketchOn('XY')
|
|
|
|
|
|> startProfileAt(obj.start, %)
|
2023-09-13 07:23:14 -07:00
|
|
|
|
|> line([0, obj.l], %)
|
|
|
|
|
|> line([obj.w, 0], %)
|
|
|
|
|
|> line([0, -obj.l], %)
|
|
|
|
|
|> close(%)
|
|
|
|
|
|> extrude(obj.h, %)
|
|
|
|
|
|
|
|
|
|
return myBox
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for var in [{start: [0,0], l: 6, w: 10, h: 3}, {start: [-10,-10], l: 3, w: 5, h: 1.5}] {
|
|
|
|
|
const thisBox = box(var)
|
|
|
|
|
}"#;
|
|
|
|
|
|
|
|
|
|
parse_execute(ast).await.unwrap();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[tokio::test(flavor = "multi_thread")]
|
|
|
|
|
#[ignore] // ignore til we get loops
|
|
|
|
|
async fn test_execute_with_function_sketch_loop_array() {
|
2023-09-13 11:42:09 -07:00
|
|
|
|
let ast = r#"fn box = (h, l, w, start) => {
|
2023-10-05 14:27:48 -07:00
|
|
|
|
const myBox = startSketchOn('XY')
|
|
|
|
|
|> startProfileAt([0,0], %)
|
2023-09-13 07:23:14 -07:00
|
|
|
|
|> line([0, l], %)
|
|
|
|
|
|> line([w, 0], %)
|
|
|
|
|
|> line([0, -l], %)
|
|
|
|
|
|> close(%)
|
|
|
|
|
|> extrude(h, %)
|
|
|
|
|
|
|
|
|
|
return myBox
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for var in [[3, 6, 10, [0,0]], [1.5, 3, 5, [-10,-10]]] {
|
|
|
|
|
const thisBox = box(var[0], var[1], var[2], var[3])
|
|
|
|
|
}"#;
|
|
|
|
|
|
|
|
|
|
parse_execute(ast).await.unwrap();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[tokio::test(flavor = "multi_thread")]
|
|
|
|
|
async fn test_get_member_of_array_with_function() {
|
2023-09-13 11:42:09 -07:00
|
|
|
|
let ast = r#"fn box = (array) => {
|
2023-10-05 14:27:48 -07:00
|
|
|
|
let myBox =startSketchOn('XY')
|
|
|
|
|
|> startProfileAt(array[0], %)
|
2023-09-13 09:23:24 -07:00
|
|
|
|
|> line([0, array[1]], %)
|
2023-09-13 07:23:14 -07:00
|
|
|
|
|> line([array[2], 0], %)
|
|
|
|
|
|> line([0, -array[1]], %)
|
|
|
|
|
|> close(%)
|
|
|
|
|
|> extrude(array[3], %)
|
|
|
|
|
|
|
|
|
|
return myBox
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const thisBox = box([[0,0], 6, 10, 3])
|
|
|
|
|
|
|
|
|
|
"#;
|
|
|
|
|
parse_execute(ast).await.unwrap();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[tokio::test(flavor = "multi_thread")]
|
|
|
|
|
async fn test_math_execute_with_functions() {
|
|
|
|
|
let ast = r#"const myVar = 2 + min(100, -1 + legLen(5, 3))"#;
|
|
|
|
|
let memory = parse_execute(ast).await.unwrap();
|
|
|
|
|
assert_eq!(
|
|
|
|
|
serde_json::json!(5.0),
|
|
|
|
|
memory.root.get("myVar").unwrap().get_json_value().unwrap()
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[tokio::test(flavor = "multi_thread")]
|
|
|
|
|
async fn test_math_execute() {
|
|
|
|
|
let ast = r#"const myVar = 1 + 2 * (3 - 4) / -5 + 6"#;
|
|
|
|
|
let memory = parse_execute(ast).await.unwrap();
|
|
|
|
|
assert_eq!(
|
|
|
|
|
serde_json::json!(7.4),
|
|
|
|
|
memory.root.get("myVar").unwrap().get_json_value().unwrap()
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[tokio::test(flavor = "multi_thread")]
|
|
|
|
|
async fn test_math_execute_start_negative() {
|
|
|
|
|
let ast = r#"const myVar = -5 + 6"#;
|
|
|
|
|
let memory = parse_execute(ast).await.unwrap();
|
|
|
|
|
assert_eq!(
|
|
|
|
|
serde_json::json!(1.0),
|
|
|
|
|
memory.root.get("myVar").unwrap().get_json_value().unwrap()
|
|
|
|
|
);
|
|
|
|
|
}
|
2023-09-13 13:10:55 -07:00
|
|
|
|
|
2023-09-19 16:05:53 -07:00
|
|
|
|
#[tokio::test(flavor = "multi_thread")]
|
|
|
|
|
async fn test_math_execute_with_pi() {
|
|
|
|
|
let ast = r#"const myVar = pi() * 2"#;
|
|
|
|
|
let memory = parse_execute(ast).await.unwrap();
|
|
|
|
|
assert_eq!(
|
|
|
|
|
serde_json::json!(std::f64::consts::TAU),
|
|
|
|
|
memory.root.get("myVar").unwrap().get_json_value().unwrap()
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-13 13:10:55 -07:00
|
|
|
|
#[tokio::test(flavor = "multi_thread")]
|
|
|
|
|
async fn test_math_define_decimal_without_leading_zero() {
|
|
|
|
|
let ast = r#"let thing = .4 + 7"#;
|
|
|
|
|
let memory = parse_execute(ast).await.unwrap();
|
|
|
|
|
assert_eq!(
|
|
|
|
|
serde_json::json!(7.4),
|
|
|
|
|
memory.root.get("thing").unwrap().get_json_value().unwrap()
|
|
|
|
|
);
|
|
|
|
|
}
|
2023-09-15 13:19:53 -07:00
|
|
|
|
|
2023-09-20 10:51:49 -05:00
|
|
|
|
#[tokio::test(flavor = "multi_thread")]
|
|
|
|
|
async fn test_zero_param_fn() {
|
|
|
|
|
let ast = r#"const sigmaAllow = 35000 // psi
|
|
|
|
|
const leg1 = 5 // inches
|
|
|
|
|
const leg2 = 8 // inches
|
|
|
|
|
fn thickness = () => { return 0.56 }
|
|
|
|
|
|
2023-10-05 14:27:48 -07:00
|
|
|
|
const bracket = startSketchOn('XY')
|
|
|
|
|
|> startProfileAt([0,0], %)
|
2023-09-20 10:51:49 -05:00
|
|
|
|
|> line([0, leg1], %)
|
|
|
|
|
|> line([leg2, 0], %)
|
|
|
|
|
|> line([0, -thickness()], %)
|
|
|
|
|
|> line([-leg2 + thickness(), 0], %)
|
|
|
|
|
"#;
|
|
|
|
|
parse_execute(ast).await.unwrap();
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-15 13:19:53 -07:00
|
|
|
|
#[tokio::test(flavor = "multi_thread")]
|
|
|
|
|
async fn test_math_negative_variable_in_binary_expression() {
|
|
|
|
|
let ast = r#"const sigmaAllow = 35000 // psi
|
|
|
|
|
const width = 1 // inch
|
|
|
|
|
|
|
|
|
|
const p = 150 // lbs
|
|
|
|
|
const distance = 6 // inches
|
|
|
|
|
const FOS = 2
|
|
|
|
|
|
|
|
|
|
const leg1 = 5 // inches
|
|
|
|
|
const leg2 = 8 // inches
|
|
|
|
|
|
|
|
|
|
const thickness_squared = distance * p * FOS * 6 / sigmaAllow
|
|
|
|
|
const thickness = 0.56 // inches. App does not support square root function yet
|
|
|
|
|
|
2023-10-05 14:27:48 -07:00
|
|
|
|
const bracket = startSketchOn('XY')
|
|
|
|
|
|> startProfileAt([0,0], %)
|
2023-09-15 13:19:53 -07:00
|
|
|
|
|> line([0, leg1], %)
|
|
|
|
|
|> line([leg2, 0], %)
|
|
|
|
|
|> line([0, -thickness], %)
|
|
|
|
|
|> line([-leg2 + thickness, 0], %)
|
2023-09-15 17:40:57 -07:00
|
|
|
|
"#;
|
|
|
|
|
parse_execute(ast).await.unwrap();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[tokio::test(flavor = "multi_thread")]
|
|
|
|
|
async fn test_math_doubly_nested_parens() {
|
|
|
|
|
let ast = r#"const sigmaAllow = 35000 // psi
|
|
|
|
|
const width = 4 // inch
|
|
|
|
|
const p = 150 // Force on shelf - lbs
|
|
|
|
|
const distance = 6 // inches
|
|
|
|
|
const FOS = 2
|
|
|
|
|
const leg1 = 5 // inches
|
|
|
|
|
const leg2 = 8 // inches
|
|
|
|
|
const thickness_squared = (distance * p * FOS * 6 / (sigmaAllow - width))
|
|
|
|
|
const thickness = 0.32 // inches. App does not support square root function yet
|
2023-10-05 14:27:48 -07:00
|
|
|
|
const bracket = startSketchOn('XY')
|
|
|
|
|
|> startProfileAt([0,0], %)
|
2023-09-15 17:40:57 -07:00
|
|
|
|
|> line([0, leg1], %)
|
|
|
|
|
|> line([leg2, 0], %)
|
|
|
|
|
|> line([0, -thickness], %)
|
|
|
|
|
|> line([-1 * leg2 + thickness, 0], %)
|
|
|
|
|
|> line([0, -1 * leg1 + thickness], %)
|
|
|
|
|
|> close(%)
|
|
|
|
|
|> extrude(width, %)
|
|
|
|
|
"#;
|
|
|
|
|
parse_execute(ast).await.unwrap();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[tokio::test(flavor = "multi_thread")]
|
|
|
|
|
async fn test_math_nested_parens_one_less() {
|
|
|
|
|
let ast = r#"const sigmaAllow = 35000 // psi
|
|
|
|
|
const width = 4 // inch
|
|
|
|
|
const p = 150 // Force on shelf - lbs
|
|
|
|
|
const distance = 6 // inches
|
|
|
|
|
const FOS = 2
|
|
|
|
|
const leg1 = 5 // inches
|
|
|
|
|
const leg2 = 8 // inches
|
|
|
|
|
const thickness_squared = distance * p * FOS * 6 / (sigmaAllow - width)
|
|
|
|
|
const thickness = 0.32 // inches. App does not support square root function yet
|
2023-10-05 14:27:48 -07:00
|
|
|
|
const bracket = startSketchOn('XY')
|
|
|
|
|
|> startProfileAt([0,0], %)
|
2023-09-15 17:40:57 -07:00
|
|
|
|
|> line([0, leg1], %)
|
|
|
|
|
|> line([leg2, 0], %)
|
|
|
|
|
|> line([0, -thickness], %)
|
|
|
|
|
|> line([-1 * leg2 + thickness, 0], %)
|
|
|
|
|
|> line([0, -1 * leg1 + thickness], %)
|
|
|
|
|
|> close(%)
|
|
|
|
|
|> extrude(width, %)
|
2023-09-15 13:19:53 -07:00
|
|
|
|
"#;
|
|
|
|
|
parse_execute(ast).await.unwrap();
|
|
|
|
|
}
|
Remove just one enum (#1096)
# Problem
This is my proposal for fixing #1107 . I've only done it for one stdlib function, `tangentialArcTo` -- if y'all like it, I'll apply this idea to the rest of the stdlib.
Previously, if users want to put a tag on the arc, the function's parameters change type.
```
// Tag missing: first param is array
tangentialArcTo([x, y], %)
// Tag present: first param is object
tangentialArcTo({to: [x, y], tag: "myTag"}, %)
```
# Solution
My proposal in #1006 is that KCL should have optional values. This means we can change the stdlib `tangentialArcTo` function to use them. In this PR, the calls are now like
```
// Tag missing: first param is array
tangentialArcTo([x, y], %)
// Tag present: first param is array still, but we now pass a tag at the end.
tangentialArcTo([x, y], %, "myTag")
```
This adds an "option" type to KCL typesystem, but it's not really revealed to users (no KCL types are revealed to users right now, they write untyped code and only interact with types when they get type errors upon executing programs). Also adds a None type, which is the default case of the Optional enum.
2023-12-18 23:49:32 -06:00
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_assign_args_to_params() {
|
|
|
|
|
// Set up a little framework for this test.
|
|
|
|
|
fn mem(number: usize) -> MemoryItem {
|
|
|
|
|
MemoryItem::UserVal(UserVal {
|
|
|
|
|
value: number.into(),
|
|
|
|
|
meta: Default::default(),
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
fn ident(s: &'static str) -> Identifier {
|
|
|
|
|
Identifier {
|
|
|
|
|
start: 0,
|
|
|
|
|
end: 0,
|
|
|
|
|
name: s.to_owned(),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
fn opt_param(s: &'static str) -> Parameter {
|
|
|
|
|
Parameter {
|
|
|
|
|
identifier: ident(s),
|
2024-03-21 17:14:30 -07:00
|
|
|
|
type_: None,
|
Remove just one enum (#1096)
# Problem
This is my proposal for fixing #1107 . I've only done it for one stdlib function, `tangentialArcTo` -- if y'all like it, I'll apply this idea to the rest of the stdlib.
Previously, if users want to put a tag on the arc, the function's parameters change type.
```
// Tag missing: first param is array
tangentialArcTo([x, y], %)
// Tag present: first param is object
tangentialArcTo({to: [x, y], tag: "myTag"}, %)
```
# Solution
My proposal in #1006 is that KCL should have optional values. This means we can change the stdlib `tangentialArcTo` function to use them. In this PR, the calls are now like
```
// Tag missing: first param is array
tangentialArcTo([x, y], %)
// Tag present: first param is array still, but we now pass a tag at the end.
tangentialArcTo([x, y], %, "myTag")
```
This adds an "option" type to KCL typesystem, but it's not really revealed to users (no KCL types are revealed to users right now, they write untyped code and only interact with types when they get type errors upon executing programs). Also adds a None type, which is the default case of the Optional enum.
2023-12-18 23:49:32 -06:00
|
|
|
|
optional: true,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
fn req_param(s: &'static str) -> Parameter {
|
|
|
|
|
Parameter {
|
|
|
|
|
identifier: ident(s),
|
2024-03-21 17:14:30 -07:00
|
|
|
|
type_: None,
|
Remove just one enum (#1096)
# Problem
This is my proposal for fixing #1107 . I've only done it for one stdlib function, `tangentialArcTo` -- if y'all like it, I'll apply this idea to the rest of the stdlib.
Previously, if users want to put a tag on the arc, the function's parameters change type.
```
// Tag missing: first param is array
tangentialArcTo([x, y], %)
// Tag present: first param is object
tangentialArcTo({to: [x, y], tag: "myTag"}, %)
```
# Solution
My proposal in #1006 is that KCL should have optional values. This means we can change the stdlib `tangentialArcTo` function to use them. In this PR, the calls are now like
```
// Tag missing: first param is array
tangentialArcTo([x, y], %)
// Tag present: first param is array still, but we now pass a tag at the end.
tangentialArcTo([x, y], %, "myTag")
```
This adds an "option" type to KCL typesystem, but it's not really revealed to users (no KCL types are revealed to users right now, they write untyped code and only interact with types when they get type errors upon executing programs). Also adds a None type, which is the default case of the Optional enum.
2023-12-18 23:49:32 -06:00
|
|
|
|
optional: false,
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-02-11 18:26:09 -08:00
|
|
|
|
fn additional_program_memory(items: &[(String, MemoryItem)]) -> ProgramMemory {
|
|
|
|
|
let mut program_memory = ProgramMemory::new();
|
|
|
|
|
for (name, item) in items {
|
|
|
|
|
program_memory.root.insert(name.to_string(), item.clone());
|
|
|
|
|
}
|
|
|
|
|
program_memory
|
|
|
|
|
}
|
Remove just one enum (#1096)
# Problem
This is my proposal for fixing #1107 . I've only done it for one stdlib function, `tangentialArcTo` -- if y'all like it, I'll apply this idea to the rest of the stdlib.
Previously, if users want to put a tag on the arc, the function's parameters change type.
```
// Tag missing: first param is array
tangentialArcTo([x, y], %)
// Tag present: first param is object
tangentialArcTo({to: [x, y], tag: "myTag"}, %)
```
# Solution
My proposal in #1006 is that KCL should have optional values. This means we can change the stdlib `tangentialArcTo` function to use them. In this PR, the calls are now like
```
// Tag missing: first param is array
tangentialArcTo([x, y], %)
// Tag present: first param is array still, but we now pass a tag at the end.
tangentialArcTo([x, y], %, "myTag")
```
This adds an "option" type to KCL typesystem, but it's not really revealed to users (no KCL types are revealed to users right now, they write untyped code and only interact with types when they get type errors upon executing programs). Also adds a None type, which is the default case of the Optional enum.
2023-12-18 23:49:32 -06:00
|
|
|
|
// Declare the test cases.
|
|
|
|
|
for (test_name, params, args, expected) in [
|
|
|
|
|
("empty", Vec::new(), Vec::new(), Ok(ProgramMemory::new())),
|
|
|
|
|
(
|
|
|
|
|
"all params required, and all given, should be OK",
|
|
|
|
|
vec![req_param("x")],
|
|
|
|
|
vec![mem(1)],
|
2024-02-11 18:26:09 -08:00
|
|
|
|
Ok(additional_program_memory(&[("x".to_owned(), mem(1))])),
|
Remove just one enum (#1096)
# Problem
This is my proposal for fixing #1107 . I've only done it for one stdlib function, `tangentialArcTo` -- if y'all like it, I'll apply this idea to the rest of the stdlib.
Previously, if users want to put a tag on the arc, the function's parameters change type.
```
// Tag missing: first param is array
tangentialArcTo([x, y], %)
// Tag present: first param is object
tangentialArcTo({to: [x, y], tag: "myTag"}, %)
```
# Solution
My proposal in #1006 is that KCL should have optional values. This means we can change the stdlib `tangentialArcTo` function to use them. In this PR, the calls are now like
```
// Tag missing: first param is array
tangentialArcTo([x, y], %)
// Tag present: first param is array still, but we now pass a tag at the end.
tangentialArcTo([x, y], %, "myTag")
```
This adds an "option" type to KCL typesystem, but it's not really revealed to users (no KCL types are revealed to users right now, they write untyped code and only interact with types when they get type errors upon executing programs). Also adds a None type, which is the default case of the Optional enum.
2023-12-18 23:49:32 -06:00
|
|
|
|
),
|
|
|
|
|
(
|
|
|
|
|
"all params required, none given, should error",
|
|
|
|
|
vec![req_param("x")],
|
|
|
|
|
vec![],
|
|
|
|
|
Err(KclError::Semantic(KclErrorDetails {
|
|
|
|
|
source_ranges: vec![SourceRange([0, 0])],
|
|
|
|
|
message: "Expected 1 arguments, got 0".to_owned(),
|
|
|
|
|
})),
|
|
|
|
|
),
|
|
|
|
|
(
|
|
|
|
|
"all params optional, none given, should be OK",
|
|
|
|
|
vec![opt_param("x")],
|
|
|
|
|
vec![],
|
2024-02-11 18:26:09 -08:00
|
|
|
|
Ok(additional_program_memory(&[(
|
|
|
|
|
"x".to_owned(),
|
|
|
|
|
MemoryItem::from(&KclNone::default()),
|
|
|
|
|
)])),
|
Remove just one enum (#1096)
# Problem
This is my proposal for fixing #1107 . I've only done it for one stdlib function, `tangentialArcTo` -- if y'all like it, I'll apply this idea to the rest of the stdlib.
Previously, if users want to put a tag on the arc, the function's parameters change type.
```
// Tag missing: first param is array
tangentialArcTo([x, y], %)
// Tag present: first param is object
tangentialArcTo({to: [x, y], tag: "myTag"}, %)
```
# Solution
My proposal in #1006 is that KCL should have optional values. This means we can change the stdlib `tangentialArcTo` function to use them. In this PR, the calls are now like
```
// Tag missing: first param is array
tangentialArcTo([x, y], %)
// Tag present: first param is array still, but we now pass a tag at the end.
tangentialArcTo([x, y], %, "myTag")
```
This adds an "option" type to KCL typesystem, but it's not really revealed to users (no KCL types are revealed to users right now, they write untyped code and only interact with types when they get type errors upon executing programs). Also adds a None type, which is the default case of the Optional enum.
2023-12-18 23:49:32 -06:00
|
|
|
|
),
|
|
|
|
|
(
|
|
|
|
|
"mixed params, too few given",
|
|
|
|
|
vec![req_param("x"), opt_param("y")],
|
|
|
|
|
vec![],
|
|
|
|
|
Err(KclError::Semantic(KclErrorDetails {
|
|
|
|
|
source_ranges: vec![SourceRange([0, 0])],
|
|
|
|
|
message: "Expected 1-2 arguments, got 0".to_owned(),
|
|
|
|
|
})),
|
|
|
|
|
),
|
|
|
|
|
(
|
|
|
|
|
"mixed params, minimum given, should be OK",
|
|
|
|
|
vec![req_param("x"), opt_param("y")],
|
|
|
|
|
vec![mem(1)],
|
2024-02-11 18:26:09 -08:00
|
|
|
|
Ok(additional_program_memory(&[
|
|
|
|
|
("x".to_owned(), mem(1)),
|
|
|
|
|
("y".to_owned(), MemoryItem::from(&KclNone::default())),
|
|
|
|
|
])),
|
Remove just one enum (#1096)
# Problem
This is my proposal for fixing #1107 . I've only done it for one stdlib function, `tangentialArcTo` -- if y'all like it, I'll apply this idea to the rest of the stdlib.
Previously, if users want to put a tag on the arc, the function's parameters change type.
```
// Tag missing: first param is array
tangentialArcTo([x, y], %)
// Tag present: first param is object
tangentialArcTo({to: [x, y], tag: "myTag"}, %)
```
# Solution
My proposal in #1006 is that KCL should have optional values. This means we can change the stdlib `tangentialArcTo` function to use them. In this PR, the calls are now like
```
// Tag missing: first param is array
tangentialArcTo([x, y], %)
// Tag present: first param is array still, but we now pass a tag at the end.
tangentialArcTo([x, y], %, "myTag")
```
This adds an "option" type to KCL typesystem, but it's not really revealed to users (no KCL types are revealed to users right now, they write untyped code and only interact with types when they get type errors upon executing programs). Also adds a None type, which is the default case of the Optional enum.
2023-12-18 23:49:32 -06:00
|
|
|
|
),
|
|
|
|
|
(
|
|
|
|
|
"mixed params, maximum given, should be OK",
|
|
|
|
|
vec![req_param("x"), opt_param("y")],
|
|
|
|
|
vec![mem(1), mem(2)],
|
2024-02-11 18:26:09 -08:00
|
|
|
|
Ok(additional_program_memory(&[
|
|
|
|
|
("x".to_owned(), mem(1)),
|
|
|
|
|
("y".to_owned(), mem(2)),
|
|
|
|
|
])),
|
Remove just one enum (#1096)
# Problem
This is my proposal for fixing #1107 . I've only done it for one stdlib function, `tangentialArcTo` -- if y'all like it, I'll apply this idea to the rest of the stdlib.
Previously, if users want to put a tag on the arc, the function's parameters change type.
```
// Tag missing: first param is array
tangentialArcTo([x, y], %)
// Tag present: first param is object
tangentialArcTo({to: [x, y], tag: "myTag"}, %)
```
# Solution
My proposal in #1006 is that KCL should have optional values. This means we can change the stdlib `tangentialArcTo` function to use them. In this PR, the calls are now like
```
// Tag missing: first param is array
tangentialArcTo([x, y], %)
// Tag present: first param is array still, but we now pass a tag at the end.
tangentialArcTo([x, y], %, "myTag")
```
This adds an "option" type to KCL typesystem, but it's not really revealed to users (no KCL types are revealed to users right now, they write untyped code and only interact with types when they get type errors upon executing programs). Also adds a None type, which is the default case of the Optional enum.
2023-12-18 23:49:32 -06:00
|
|
|
|
),
|
|
|
|
|
(
|
|
|
|
|
"mixed params, too many given",
|
|
|
|
|
vec![req_param("x"), opt_param("y")],
|
|
|
|
|
vec![mem(1), mem(2), mem(3)],
|
|
|
|
|
Err(KclError::Semantic(KclErrorDetails {
|
|
|
|
|
source_ranges: vec![SourceRange([0, 0])],
|
|
|
|
|
message: "Expected 1-2 arguments, got 3".to_owned(),
|
|
|
|
|
})),
|
|
|
|
|
),
|
|
|
|
|
] {
|
|
|
|
|
// Run each test.
|
|
|
|
|
let func_expr = &FunctionExpression {
|
|
|
|
|
start: 0,
|
|
|
|
|
end: 0,
|
|
|
|
|
params,
|
|
|
|
|
body: crate::ast::types::Program {
|
|
|
|
|
start: 0,
|
|
|
|
|
end: 0,
|
|
|
|
|
body: Vec::new(),
|
|
|
|
|
non_code_meta: Default::default(),
|
|
|
|
|
},
|
2024-03-21 17:14:30 -07:00
|
|
|
|
return_type: None,
|
Remove just one enum (#1096)
# Problem
This is my proposal for fixing #1107 . I've only done it for one stdlib function, `tangentialArcTo` -- if y'all like it, I'll apply this idea to the rest of the stdlib.
Previously, if users want to put a tag on the arc, the function's parameters change type.
```
// Tag missing: first param is array
tangentialArcTo([x, y], %)
// Tag present: first param is object
tangentialArcTo({to: [x, y], tag: "myTag"}, %)
```
# Solution
My proposal in #1006 is that KCL should have optional values. This means we can change the stdlib `tangentialArcTo` function to use them. In this PR, the calls are now like
```
// Tag missing: first param is array
tangentialArcTo([x, y], %)
// Tag present: first param is array still, but we now pass a tag at the end.
tangentialArcTo([x, y], %, "myTag")
```
This adds an "option" type to KCL typesystem, but it's not really revealed to users (no KCL types are revealed to users right now, they write untyped code and only interact with types when they get type errors upon executing programs). Also adds a None type, which is the default case of the Optional enum.
2023-12-18 23:49:32 -06:00
|
|
|
|
};
|
|
|
|
|
let actual = assign_args_to_params(func_expr, args, ProgramMemory::new());
|
|
|
|
|
assert_eq!(
|
|
|
|
|
actual, expected,
|
|
|
|
|
"failed test '{test_name}':\ngot {actual:?}\nbut expected\n{expected:?}"
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-02-11 15:08:54 -08:00
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_serialize_memory_item() {
|
|
|
|
|
let mem = MemoryItem::ExtrudeGroups {
|
|
|
|
|
value: Default::default(),
|
|
|
|
|
};
|
|
|
|
|
let json = serde_json::to_string(&mem).unwrap();
|
|
|
|
|
assert_eq!(json, r#"{"type":"ExtrudeGroups","value":[]}"#);
|
|
|
|
|
}
|
2023-08-24 15:34:51 -07:00
|
|
|
|
}
|