merge main
This commit is contained in:
@ -2,48 +2,11 @@ use std::{collections::HashMap, fs, path::Path};
|
||||
|
||||
use anyhow::Result;
|
||||
use base64::Engine;
|
||||
use convert_case::Casing;
|
||||
use indexmap::IndexMap;
|
||||
use itertools::Itertools;
|
||||
use serde_json::json;
|
||||
use tokio::task::JoinSet;
|
||||
|
||||
use super::kcl_doc::{ConstData, DocData, ExampleProperties, FnData, ModData, TyData};
|
||||
use crate::{
|
||||
docs::{StdLibFn, DECLARED_TYPES},
|
||||
std::StdLib,
|
||||
ExecutorContext,
|
||||
};
|
||||
|
||||
// Types with special handling.
|
||||
const SPECIAL_TYPES: [&str; 4] = ["TagDeclarator", "TagIdentifier", "Start", "End"];
|
||||
|
||||
const TYPE_REWRITES: [(&str, &str); 11] = [
|
||||
("TagNode", "TagDeclarator"),
|
||||
("SketchData", "Plane | Solid"),
|
||||
("SketchOrSurface", "Sketch | Plane | Face"),
|
||||
("SketchSurface", "Plane | Face"),
|
||||
("SolidOrImportedGeometry", "[Solid] | ImportedGeometry"),
|
||||
(
|
||||
"SolidOrSketchOrImportedGeometry",
|
||||
"[Solid] | [Sketch] | ImportedGeometry",
|
||||
),
|
||||
("KclValue", "any"),
|
||||
("[KclValue]", "[any]"),
|
||||
("FaceTag", "TagIdentifier | Start | End"),
|
||||
("GeometryWithImportedGeometry", "Solid | Sketch | ImportedGeometry"),
|
||||
("SweepPath", "Sketch | Helix"),
|
||||
];
|
||||
|
||||
fn rename_type(input: &str) -> &str {
|
||||
for (i, o) in TYPE_REWRITES {
|
||||
if input == i {
|
||||
return o;
|
||||
}
|
||||
}
|
||||
|
||||
input
|
||||
}
|
||||
use crate::ExecutorContext;
|
||||
|
||||
fn init_handlebars() -> Result<handlebars::Handlebars<'static>> {
|
||||
let mut hbs = handlebars::Handlebars::new();
|
||||
@ -104,7 +67,7 @@ fn init_handlebars() -> Result<handlebars::Handlebars<'static>> {
|
||||
Ok(hbs)
|
||||
}
|
||||
|
||||
fn generate_index(combined: &IndexMap<String, Box<dyn StdLibFn>>, kcl_lib: &ModData) -> Result<()> {
|
||||
fn generate_index(kcl_lib: &ModData) -> Result<()> {
|
||||
let hbs = init_handlebars()?;
|
||||
|
||||
let mut functions = HashMap::new();
|
||||
@ -115,31 +78,6 @@ fn generate_index(combined: &IndexMap<String, Box<dyn StdLibFn>>, kcl_lib: &ModD
|
||||
let mut types = HashMap::new();
|
||||
types.insert("Primitive types".to_owned(), Vec::new());
|
||||
|
||||
for key in combined.keys() {
|
||||
let internal_fn = combined
|
||||
.get(key)
|
||||
.ok_or_else(|| anyhow::anyhow!("Failed to get internal function: {}", key))?;
|
||||
|
||||
if internal_fn.unpublished() || internal_fn.deprecated() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let tags = internal_fn.tags();
|
||||
let module = tags.first().map(|s| format!("std::{s}")).unwrap_or("std".to_owned());
|
||||
|
||||
functions
|
||||
.entry(module.to_owned())
|
||||
.or_default()
|
||||
.push((internal_fn.name(), format!("/docs/kcl-std/{}", internal_fn.name())));
|
||||
}
|
||||
|
||||
for name in SPECIAL_TYPES {
|
||||
types
|
||||
.get_mut("Primitive types")
|
||||
.unwrap()
|
||||
.push((name.to_owned(), format!("/docs/kcl-lang/types#{name}")));
|
||||
}
|
||||
|
||||
for d in kcl_lib.all_docs() {
|
||||
if d.hide() {
|
||||
continue;
|
||||
@ -257,8 +195,8 @@ fn generate_example(index: usize, src: &str, props: &ExampleProperties, file_nam
|
||||
}))
|
||||
}
|
||||
|
||||
fn generate_type_from_kcl(ty: &TyData, file_name: String, example_name: String) -> Result<()> {
|
||||
if ty.properties.doc_hidden || !DECLARED_TYPES.contains(&&*ty.name) {
|
||||
fn generate_type_from_kcl(ty: &TyData, file_name: String, example_name: String, kcl_std: &ModData) -> Result<()> {
|
||||
if ty.properties.doc_hidden {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
@ -282,18 +220,14 @@ fn generate_type_from_kcl(ty: &TyData, file_name: String, example_name: String)
|
||||
});
|
||||
|
||||
let output = hbs.render("kclType", &data)?;
|
||||
let output = cleanup_types(&output);
|
||||
let output = cleanup_types(&output, kcl_std);
|
||||
expectorate::assert_contents(format!("../../docs/kcl-std/{}.md", file_name), &output);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn generate_mod_from_kcl(m: &ModData, file_name: String, combined: &IndexMap<String, Box<dyn StdLibFn>>) -> Result<()> {
|
||||
fn list_items(
|
||||
m: &ModData,
|
||||
namespace: &str,
|
||||
combined: &IndexMap<String, Box<dyn StdLibFn>>,
|
||||
) -> Vec<gltf_json::Value> {
|
||||
fn generate_mod_from_kcl(m: &ModData, file_name: String) -> Result<()> {
|
||||
fn list_items(m: &ModData, namespace: &str) -> Vec<gltf_json::Value> {
|
||||
let mut items: Vec<_> = m
|
||||
.children
|
||||
.iter()
|
||||
@ -301,25 +235,6 @@ fn generate_mod_from_kcl(m: &ModData, file_name: String, combined: &IndexMap<Str
|
||||
.map(|(_, v)| (v.preferred_name().to_owned(), v.file_name()))
|
||||
.collect();
|
||||
|
||||
if namespace == "I:" {
|
||||
// Add in functions declared in Rust
|
||||
items.extend(
|
||||
combined
|
||||
.values()
|
||||
.filter(|f| {
|
||||
if f.unpublished() || f.deprecated() {
|
||||
return false;
|
||||
}
|
||||
|
||||
let tags = f.tags();
|
||||
let module = tags.first().map(|s| format!("std::{s}")).unwrap_or("std".to_owned());
|
||||
|
||||
module == m.qual_name
|
||||
})
|
||||
.map(|f| (f.name(), f.name())),
|
||||
)
|
||||
}
|
||||
|
||||
items.sort();
|
||||
items
|
||||
.into_iter()
|
||||
@ -333,9 +248,9 @@ fn generate_mod_from_kcl(m: &ModData, file_name: String, combined: &IndexMap<Str
|
||||
}
|
||||
let hbs = init_handlebars()?;
|
||||
|
||||
let functions = list_items(m, "I:", combined);
|
||||
let modules = list_items(m, "M:", combined);
|
||||
let types = list_items(m, "T:", combined);
|
||||
let functions = list_items(m, "I:");
|
||||
let modules = list_items(m, "M:");
|
||||
let types = list_items(m, "T:");
|
||||
|
||||
let data = json!({
|
||||
"name": m.name,
|
||||
@ -391,7 +306,7 @@ fn generate_function_from_kcl(
|
||||
json!({
|
||||
"name": arg.name,
|
||||
"type_": arg.ty,
|
||||
"description": docs.or_else(|| arg.ty.as_ref().and_then(|t| super::docs_for_type(t, kcl_std))).unwrap_or_default(),
|
||||
"description": docs.or_else(|| arg.ty.as_ref().and_then(|t| docs_for_type(t, kcl_std))).unwrap_or_default(),
|
||||
"required": arg.kind.required(),
|
||||
})
|
||||
}).collect::<Vec<_>>();
|
||||
@ -408,18 +323,30 @@ fn generate_function_from_kcl(
|
||||
"return_value": function.return_type.as_ref().map(|t| {
|
||||
json!({
|
||||
"type_": t,
|
||||
"description": super::docs_for_type(t, kcl_std).unwrap_or_default(),
|
||||
"description": docs_for_type(t, kcl_std).unwrap_or_default(),
|
||||
})
|
||||
}),
|
||||
});
|
||||
|
||||
let output = hbs.render("function", &data)?;
|
||||
let output = &cleanup_types(&output);
|
||||
let output = &cleanup_types(&output, kcl_std);
|
||||
expectorate::assert_contents(format!("../../docs/kcl-std/{}.md", file_name), output);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn docs_for_type(ty: &str, kcl_std: &ModData) -> Option<String> {
|
||||
let key = if ty.starts_with("number") { "number" } else { ty };
|
||||
|
||||
if !key.contains('|') && !key.contains('[') {
|
||||
if let Some(data) = kcl_std.find_by_name(key) {
|
||||
return data.summary().cloned();
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
fn generate_const_from_kcl(cnst: &ConstData, file_name: String, example_name: String) -> Result<()> {
|
||||
if cnst.properties.doc_hidden {
|
||||
return Ok(());
|
||||
@ -450,83 +377,7 @@ fn generate_const_from_kcl(cnst: &ConstData, file_name: String, example_name: St
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn generate_function(internal_fn: Box<dyn StdLibFn>, kcl_std: &ModData) -> Result<()> {
|
||||
let hbs = init_handlebars()?;
|
||||
|
||||
if internal_fn.unpublished() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let fn_name = internal_fn.name();
|
||||
let snake_case_name = clean_function_name(&fn_name);
|
||||
|
||||
let examples: Vec<serde_json::Value> = internal_fn
|
||||
.examples()
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(index, (example, norun))| {
|
||||
let image_base64 = if !norun {
|
||||
let image_path = format!(
|
||||
"{}/tests/outputs/serial_test_example_{}{}.png",
|
||||
env!("CARGO_MANIFEST_DIR"),
|
||||
snake_case_name,
|
||||
index
|
||||
);
|
||||
let image_data =
|
||||
std::fs::read(&image_path).unwrap_or_else(|_| panic!("Failed to read image file: {}", image_path));
|
||||
base64::engine::general_purpose::STANDARD.encode(&image_data)
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
|
||||
json!({
|
||||
"content": example,
|
||||
"image_base64": image_base64,
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
|
||||
let tags = internal_fn.tags();
|
||||
let module = tags
|
||||
.first()
|
||||
.map(|s| &**s)
|
||||
.map(|m| format!("std::{m}"))
|
||||
.unwrap_or("std".to_owned());
|
||||
|
||||
let data = json!({
|
||||
"name": fn_name,
|
||||
"module": module,
|
||||
"summary": internal_fn.summary(),
|
||||
"description": internal_fn.description(),
|
||||
"deprecated": internal_fn.deprecated(),
|
||||
"fn_signature": internal_fn.fn_signature(true),
|
||||
"examples": examples,
|
||||
"args": internal_fn.args(false).iter().map(|arg| {
|
||||
json!({
|
||||
"name": arg.name,
|
||||
"type_": rename_type(&arg.type_),
|
||||
"description": arg.description(Some(kcl_std)),
|
||||
"required": arg.required,
|
||||
})
|
||||
}).collect::<Vec<_>>(),
|
||||
"return_value": internal_fn.return_value(false).map(|ret| {
|
||||
json!({
|
||||
"type_": rename_type(&ret.type_),
|
||||
"description": ret.description(Some(kcl_std)),
|
||||
})
|
||||
}),
|
||||
});
|
||||
|
||||
let mut output = hbs.render("function", &data)?;
|
||||
// Fix the links to the types.
|
||||
output = cleanup_types(&output);
|
||||
|
||||
expectorate::assert_contents(format!("../../docs/kcl-std/{}.md", fn_name), &output);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn cleanup_types(input: &str) -> String {
|
||||
fn cleanup_types(input: &str, kcl_std: &ModData) -> String {
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
|
||||
enum State {
|
||||
Text,
|
||||
@ -550,7 +401,7 @@ fn cleanup_types(input: &str) -> String {
|
||||
if code_type.starts_with(' ') {
|
||||
code.push(' ');
|
||||
}
|
||||
code.push_str(&cleanup_type_string(code_type.trim(), false));
|
||||
code.push_str(&cleanup_type_string(code_type.trim(), false, kcl_std));
|
||||
if code_type.ends_with(' ') {
|
||||
code.push(' ');
|
||||
}
|
||||
@ -586,7 +437,7 @@ fn cleanup_types(input: &str) -> String {
|
||||
}
|
||||
ticks = 0;
|
||||
} else if state == State::Text && ticks == 2 && !code.is_empty() {
|
||||
output.push_str(&cleanup_type_string(&code, true));
|
||||
output.push_str(&cleanup_type_string(&code, true, kcl_std));
|
||||
code = String::new();
|
||||
ticks = 0;
|
||||
} else if state == State::CodeBlock {
|
||||
@ -631,14 +482,12 @@ fn cleanup_types(input: &str) -> String {
|
||||
output
|
||||
}
|
||||
|
||||
fn cleanup_type_string(input: &str, fmt_for_text: bool) -> String {
|
||||
fn cleanup_type_string(input: &str, fmt_for_text: bool, kcl_std: &ModData) -> String {
|
||||
assert!(
|
||||
!(input.starts_with('[') && input.ends_with(']') && input.contains('|')),
|
||||
"Arrays of unions are not supported"
|
||||
);
|
||||
|
||||
let input = rename_type(input);
|
||||
|
||||
let tys: Vec<_> = input
|
||||
.split('|')
|
||||
.map(|ty| {
|
||||
@ -676,9 +525,7 @@ fn cleanup_type_string(input: &str, fmt_for_text: bool) -> String {
|
||||
format!("[{prefix}{ty}{suffix}](/docs/kcl-std/types/std-types-number)")
|
||||
} else if fmt_for_text && ty.starts_with("fn") {
|
||||
format!("[{prefix}{ty}{suffix}](/docs/kcl-std/types/std-types-fn)")
|
||||
} else if fmt_for_text && SPECIAL_TYPES.contains(&ty) {
|
||||
format!("[{prefix}{ty}{suffix}](/docs/kcl-lang/types#{ty})")
|
||||
} else if fmt_for_text && DECLARED_TYPES.contains(&ty) {
|
||||
} else if fmt_for_text && matches!(kcl_std.find_by_name(ty), Some(DocData::Ty(_))) {
|
||||
format!("[{prefix}{ty}{suffix}](/docs/kcl-std/types/std-types-{ty})")
|
||||
} else {
|
||||
format!("{prefix}{ty}{suffix}")
|
||||
@ -689,73 +536,22 @@ fn cleanup_type_string(input: &str, fmt_for_text: bool) -> String {
|
||||
tys.join(if fmt_for_text { " or " } else { " | " })
|
||||
}
|
||||
|
||||
fn clean_function_name(name: &str) -> String {
|
||||
// Convert from camel case to snake case.
|
||||
let mut fn_name = name.to_case(convert_case::Case::Snake);
|
||||
// Clean the fn name.
|
||||
if fn_name.starts_with("last_seg_") {
|
||||
fn_name = fn_name.replace("last_seg_", "last_segment_");
|
||||
} else if fn_name.contains("_2_d") {
|
||||
fn_name = fn_name.replace("_2_d", "_2d");
|
||||
} else if fn_name.contains("_3_d") {
|
||||
fn_name = fn_name.replace("_3_d", "_3d");
|
||||
} else if fn_name == "seg_ang" {
|
||||
fn_name = "segment_angle".to_string();
|
||||
} else if fn_name == "seg_len" {
|
||||
fn_name = "segment_length".to_string();
|
||||
} else if fn_name.starts_with("seg_") {
|
||||
fn_name = fn_name.replace("seg_", "segment_");
|
||||
}
|
||||
|
||||
fn_name
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_generate_stdlib_markdown_docs() {
|
||||
let stdlib = StdLib::new();
|
||||
let combined = stdlib.combined();
|
||||
let kcl_std = crate::docs::kcl_doc::walk_prelude();
|
||||
|
||||
// Generate the index which is the table of contents.
|
||||
generate_index(&combined, &kcl_std).unwrap();
|
||||
|
||||
for key in combined.keys().sorted() {
|
||||
let internal_fn = combined.get(key).unwrap();
|
||||
generate_function(internal_fn.clone(), &kcl_std).unwrap();
|
||||
}
|
||||
generate_index(&kcl_std).unwrap();
|
||||
|
||||
for d in kcl_std.all_docs() {
|
||||
match d {
|
||||
DocData::Fn(f) => generate_function_from_kcl(f, d.file_name(), d.example_name(), &kcl_std).unwrap(),
|
||||
DocData::Const(c) => generate_const_from_kcl(c, d.file_name(), d.example_name()).unwrap(),
|
||||
DocData::Ty(t) => generate_type_from_kcl(t, d.file_name(), d.example_name()).unwrap(),
|
||||
DocData::Mod(m) => generate_mod_from_kcl(m, d.file_name(), &combined).unwrap(),
|
||||
DocData::Ty(t) => generate_type_from_kcl(t, d.file_name(), d.example_name(), &kcl_std).unwrap(),
|
||||
DocData::Mod(m) => generate_mod_from_kcl(m, d.file_name()).unwrap(),
|
||||
}
|
||||
}
|
||||
generate_mod_from_kcl(&kcl_std, "modules/std".to_owned(), &combined).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_generate_stdlib_json_schema() {
|
||||
// If this test fails and you've modified the AST or something else which affects the json repr
|
||||
// of stdlib functions, you should rerun the test with `EXPECTORATE=overwrite` to create new
|
||||
// test data, then check `/docs/kcl-std/std.json` to ensure the changes are expected.
|
||||
// Alternatively, run `just redo-kcl-stdlib-docs` (make sure to have just installed).
|
||||
let stdlib = StdLib::new();
|
||||
let combined = stdlib.combined();
|
||||
|
||||
let json_data: Vec<_> = combined
|
||||
.keys()
|
||||
.sorted()
|
||||
.map(|key| {
|
||||
let internal_fn = combined.get(key).unwrap();
|
||||
internal_fn.to_json().unwrap()
|
||||
})
|
||||
.collect();
|
||||
expectorate::assert_contents(
|
||||
"../../docs/kcl-std/std.json",
|
||||
&serde_json::to_string_pretty(&json_data).unwrap(),
|
||||
);
|
||||
generate_mod_from_kcl(&kcl_std, "modules/std".to_owned()).unwrap();
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
|
@ -302,6 +302,7 @@ impl DocData {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub(super) fn summary(&self) -> Option<&String> {
|
||||
match self {
|
||||
DocData::Fn(f) => f.summary.as_ref(),
|
||||
@ -462,6 +463,7 @@ impl ModData {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn find_by_name(&self, name: &str) -> Option<&DocData> {
|
||||
if let Some(result) = self
|
||||
.children
|
||||
@ -644,13 +646,13 @@ impl FnData {
|
||||
format!("{}({})", self.preferred_name, args.join(", "))
|
||||
}
|
||||
|
||||
fn to_signature_help(&self) -> SignatureHelp {
|
||||
pub(crate) fn to_signature_help(&self) -> SignatureHelp {
|
||||
// TODO Fill this in based on the current position of the cursor.
|
||||
let active_parameter = None;
|
||||
|
||||
SignatureHelp {
|
||||
signatures: vec![SignatureInformation {
|
||||
label: self.preferred_name.clone(),
|
||||
label: self.preferred_name.clone() + &self.fn_signature(),
|
||||
documentation: self.short_docs().map(|s| {
|
||||
Documentation::MarkupContent(MarkupContent {
|
||||
kind: MarkupKind::Markdown,
|
||||
@ -744,12 +746,12 @@ impl ArgData {
|
||||
} = &attr.inner
|
||||
{
|
||||
for p in props {
|
||||
if p.key.name == "include_in_snippet" {
|
||||
if p.key.name == "includeInSnippet" {
|
||||
if let Some(b) = p.value.literal_bool() {
|
||||
result.override_in_snippet = Some(b);
|
||||
} else {
|
||||
panic!(
|
||||
"Invalid value for `include_in_snippet`, expected bool literal, found {:?}",
|
||||
"Invalid value for `includeInSnippet`, expected bool literal, found {:?}",
|
||||
p.value
|
||||
);
|
||||
}
|
||||
@ -812,6 +814,7 @@ impl ArgData {
|
||||
return Some((index + n - 1, snippet));
|
||||
}
|
||||
match self.ty.as_deref() {
|
||||
Some("Sketch") if self.kind == ArgKind::Special => None,
|
||||
Some(s) if s.starts_with("number") => Some((index, format!(r#"{label}${{{}:10}}"#, index))),
|
||||
Some("Point2d") => Some((index + 1, format!(r#"{label}[${{{}:0}}, ${{{}:0}}]"#, index, index + 1))),
|
||||
Some("Point3d") => Some((
|
||||
@ -824,13 +827,18 @@ impl ArgData {
|
||||
),
|
||||
)),
|
||||
Some("Axis2d | Edge") | Some("Axis3d | Edge") => Some((index, format!(r#"{label}${{{index}:X}}"#))),
|
||||
Some("Sketch") | Some("Sketch | Helix") => Some((index, format!(r#"{label}${{{index}:sketch000}}"#))),
|
||||
Some("Edge") => Some((index, format!(r#"{label}${{{index}:tag_or_edge_fn}}"#))),
|
||||
Some("[Edge; 1+]") => Some((index, format!(r#"{label}[${{{index}:tag_or_edge_fn}}]"#))),
|
||||
Some("Plane") => Some((index, format!(r#"{label}${{{}:XY}}"#, index))),
|
||||
Some("Plane") | Some("Solid | Plane") => Some((index, format!(r#"{label}${{{}:XY}}"#, index))),
|
||||
Some("[tag; 2]") => Some((
|
||||
index + 1,
|
||||
format!(r#"{label}[${{{}:tag}}, ${{{}:tag}}]"#, index, index + 1),
|
||||
)),
|
||||
|
||||
Some("string") => {
|
||||
if self.name == "color" {
|
||||
Some((index, format!(r#"{label}${{{}:"ff0000"}}"#, index)))
|
||||
Some((index, format!(r"{label}${{{}:{}}}", index, "\"#ff0000\"")))
|
||||
} else {
|
||||
Some((index, format!(r#"{label}${{{}:"string"}}"#, index)))
|
||||
}
|
||||
@ -984,7 +992,7 @@ trait ApplyMeta {
|
||||
}
|
||||
|
||||
let mut summary = None;
|
||||
let mut description = None;
|
||||
let mut description: Option<String> = None;
|
||||
let mut example: Option<(String, ExampleProperties)> = None;
|
||||
let mut examples = Vec::new();
|
||||
for l in comments.iter().filter(|l| l.starts_with("///")).map(|l| {
|
||||
@ -994,22 +1002,6 @@ trait ApplyMeta {
|
||||
&l[3..]
|
||||
}
|
||||
}) {
|
||||
if description.is_none() && summary.is_none() {
|
||||
summary = Some(l.to_owned());
|
||||
continue;
|
||||
}
|
||||
if description.is_none() {
|
||||
if l.is_empty() {
|
||||
description = Some(String::new());
|
||||
} else {
|
||||
description = summary;
|
||||
summary = None;
|
||||
let d = description.as_mut().unwrap();
|
||||
d.push('\n');
|
||||
d.push_str(l);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
#[allow(clippy::manual_strip)]
|
||||
if l.starts_with("```") {
|
||||
if let Some((e, p)) = example {
|
||||
@ -1045,12 +1037,36 @@ trait ApplyMeta {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// An empty line outside of an example. This either starts the description (with or
|
||||
// without a summary) or adds a blank line to the description.
|
||||
if l.is_empty() {
|
||||
match &mut description {
|
||||
Some(d) => {
|
||||
d.push('\n');
|
||||
}
|
||||
None => description = Some(String::new()),
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// Our first line, start the summary.
|
||||
if description.is_none() && summary.is_none() {
|
||||
summary = Some(l.to_owned());
|
||||
continue;
|
||||
}
|
||||
|
||||
// Append the line to either the description or summary.
|
||||
match &mut description {
|
||||
Some(d) => {
|
||||
d.push_str(l);
|
||||
d.push('\n');
|
||||
}
|
||||
None => unreachable!(),
|
||||
None => {
|
||||
let s = summary.as_mut().unwrap();
|
||||
s.push(' ');
|
||||
s.push_str(l);
|
||||
}
|
||||
}
|
||||
}
|
||||
assert!(example.is_none());
|
||||
@ -1276,7 +1292,10 @@ mod test {
|
||||
continue;
|
||||
};
|
||||
|
||||
for i in 0..f.examples.len() {
|
||||
for (i, (_, props)) in f.examples.iter().enumerate() {
|
||||
if props.norun {
|
||||
continue;
|
||||
}
|
||||
let name = format!("{}-{i}", f.qual_name.replace("::", "-"));
|
||||
assert!(TEST_NAMES.contains(&&*name), "Missing test for example \"{name}\", maybe need to update kcl-derive-docs/src/example_tests.rs?")
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -67,6 +67,7 @@ pub struct TcpRead {
|
||||
|
||||
/// Occurs when client couldn't read from the WebSocket to the engine.
|
||||
// #[derive(Debug)]
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
pub enum WebSocketReadError {
|
||||
/// Could not read a message due to WebSocket errors.
|
||||
Read(tokio_tungstenite::tungstenite::Error),
|
||||
@ -206,7 +207,7 @@ impl EngineConnection {
|
||||
async fn inner_send_to_engine(request: WebSocketRequest, tcp_write: &mut WebSocketTcpWrite) -> Result<()> {
|
||||
let msg = serde_json::to_string(&request).map_err(|e| anyhow!("could not serialize json: {e}"))?;
|
||||
tcp_write
|
||||
.send(WsMsg::Text(msg))
|
||||
.send(WsMsg::Text(msg.into()))
|
||||
.await
|
||||
.map_err(|e| anyhow!("could not send json over websocket: {e}"))?;
|
||||
Ok(())
|
||||
@ -216,19 +217,17 @@ impl EngineConnection {
|
||||
async fn inner_send_to_engine_binary(request: WebSocketRequest, tcp_write: &mut WebSocketTcpWrite) -> Result<()> {
|
||||
let msg = bson::to_vec(&request).map_err(|e| anyhow!("could not serialize bson: {e}"))?;
|
||||
tcp_write
|
||||
.send(WsMsg::Binary(msg))
|
||||
.send(WsMsg::Binary(msg.into()))
|
||||
.await
|
||||
.map_err(|e| anyhow!("could not send json over websocket: {e}"))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn new(ws: reqwest::Upgraded) -> Result<EngineConnection> {
|
||||
let wsconfig = tokio_tungstenite::tungstenite::protocol::WebSocketConfig {
|
||||
let wsconfig = tokio_tungstenite::tungstenite::protocol::WebSocketConfig::default()
|
||||
// 4294967296 bytes, which is around 4.2 GB.
|
||||
max_message_size: Some(usize::MAX),
|
||||
max_frame_size: Some(usize::MAX),
|
||||
..Default::default()
|
||||
};
|
||||
.max_message_size(Some(usize::MAX))
|
||||
.max_frame_size(Some(usize::MAX));
|
||||
|
||||
let ws_stream = tokio_tungstenite::WebSocketStream::from_raw_socket(
|
||||
ws,
|
||||
@ -439,7 +438,7 @@ impl EngineManager for EngineConnection {
|
||||
request_sent: tx,
|
||||
})
|
||||
.await
|
||||
.map_err(|e| KclError::Engine(KclErrorDetails::new(format!("Failed to send debug: {}", e), vec![])))?;
|
||||
.map_err(|e| KclError::new_engine(KclErrorDetails::new(format!("Failed to send debug: {}", e), vec![])))?;
|
||||
|
||||
let _ = rx.await;
|
||||
Ok(())
|
||||
@ -474,7 +473,7 @@ impl EngineManager for EngineConnection {
|
||||
})
|
||||
.await
|
||||
.map_err(|e| {
|
||||
KclError::Engine(KclErrorDetails::new(
|
||||
KclError::new_engine(KclErrorDetails::new(
|
||||
format!("Failed to send modeling command: {}", e),
|
||||
vec![source_range],
|
||||
))
|
||||
@ -483,13 +482,13 @@ impl EngineManager for EngineConnection {
|
||||
// Wait for the request to be sent.
|
||||
rx.await
|
||||
.map_err(|e| {
|
||||
KclError::Engine(KclErrorDetails::new(
|
||||
KclError::new_engine(KclErrorDetails::new(
|
||||
format!("could not send request to the engine actor: {e}"),
|
||||
vec![source_range],
|
||||
))
|
||||
})?
|
||||
.map_err(|e| {
|
||||
KclError::Engine(KclErrorDetails::new(
|
||||
KclError::new_engine(KclErrorDetails::new(
|
||||
format!("could not send request to the engine: {e}"),
|
||||
vec![source_range],
|
||||
))
|
||||
@ -516,12 +515,12 @@ impl EngineManager for EngineConnection {
|
||||
// Check if we have any pending errors.
|
||||
let pe = self.pending_errors.read().await;
|
||||
if !pe.is_empty() {
|
||||
return Err(KclError::Engine(KclErrorDetails::new(
|
||||
return Err(KclError::new_engine(KclErrorDetails::new(
|
||||
pe.join(", ").to_string(),
|
||||
vec![source_range],
|
||||
)));
|
||||
} else {
|
||||
return Err(KclError::Engine(KclErrorDetails::new(
|
||||
return Err(KclError::new_engine(KclErrorDetails::new(
|
||||
"Modeling command failed: websocket closed early".to_string(),
|
||||
vec![source_range],
|
||||
)));
|
||||
@ -543,7 +542,7 @@ impl EngineManager for EngineConnection {
|
||||
}
|
||||
}
|
||||
|
||||
Err(KclError::Engine(KclErrorDetails::new(
|
||||
Err(KclError::new_engine(KclErrorDetails::new(
|
||||
format!("Modeling command timed out `{}`", id),
|
||||
vec![source_range],
|
||||
)))
|
||||
|
@ -80,12 +80,12 @@ impl ResponseContext {
|
||||
}
|
||||
|
||||
// Add a response to the context.
|
||||
pub async fn send_response(&self, data: js_sys::Uint8Array) -> Result<(), JsValue> {
|
||||
pub async fn send_response(&self, data: js_sys::Uint8Array) {
|
||||
let ws_result: WebSocketResponse = match bson::from_slice(&data.to_vec()) {
|
||||
Ok(res) => res,
|
||||
Err(_) => {
|
||||
// We don't care about the error if we can't parse it.
|
||||
return Ok(());
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
@ -96,13 +96,11 @@ impl ResponseContext {
|
||||
|
||||
let Some(id) = id else {
|
||||
// We only care if we have an id.
|
||||
return Ok(());
|
||||
return;
|
||||
};
|
||||
|
||||
// Add this response to our responses.
|
||||
self.add(id, ws_result.clone()).await;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@ -147,19 +145,19 @@ impl EngineConnection {
|
||||
id_to_source_range: HashMap<uuid::Uuid, SourceRange>,
|
||||
) -> Result<(), KclError> {
|
||||
let source_range_str = serde_json::to_string(&source_range).map_err(|e| {
|
||||
KclError::Engine(KclErrorDetails::new(
|
||||
KclError::new_engine(KclErrorDetails::new(
|
||||
format!("Failed to serialize source range: {:?}", e),
|
||||
vec![source_range],
|
||||
))
|
||||
})?;
|
||||
let cmd_str = serde_json::to_string(&cmd).map_err(|e| {
|
||||
KclError::Engine(KclErrorDetails::new(
|
||||
KclError::new_engine(KclErrorDetails::new(
|
||||
format!("Failed to serialize modeling command: {:?}", e),
|
||||
vec![source_range],
|
||||
))
|
||||
})?;
|
||||
let id_to_source_range_str = serde_json::to_string(&id_to_source_range).map_err(|e| {
|
||||
KclError::Engine(KclErrorDetails::new(
|
||||
KclError::new_engine(KclErrorDetails::new(
|
||||
format!("Failed to serialize id to source range: {:?}", e),
|
||||
vec![source_range],
|
||||
))
|
||||
@ -167,7 +165,7 @@ impl EngineConnection {
|
||||
|
||||
self.manager
|
||||
.fire_modeling_cmd_from_wasm(id.to_string(), source_range_str, cmd_str, id_to_source_range_str)
|
||||
.map_err(|e| KclError::Engine(KclErrorDetails::new(e.to_string().into(), vec![source_range])))?;
|
||||
.map_err(|e| KclError::new_engine(KclErrorDetails::new(e.to_string().into(), vec![source_range])))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@ -180,19 +178,19 @@ impl EngineConnection {
|
||||
id_to_source_range: HashMap<uuid::Uuid, SourceRange>,
|
||||
) -> Result<WebSocketResponse, KclError> {
|
||||
let source_range_str = serde_json::to_string(&source_range).map_err(|e| {
|
||||
KclError::Engine(KclErrorDetails::new(
|
||||
KclError::new_engine(KclErrorDetails::new(
|
||||
format!("Failed to serialize source range: {:?}", e),
|
||||
vec![source_range],
|
||||
))
|
||||
})?;
|
||||
let cmd_str = serde_json::to_string(&cmd).map_err(|e| {
|
||||
KclError::Engine(KclErrorDetails::new(
|
||||
KclError::new_engine(KclErrorDetails::new(
|
||||
format!("Failed to serialize modeling command: {:?}", e),
|
||||
vec![source_range],
|
||||
))
|
||||
})?;
|
||||
let id_to_source_range_str = serde_json::to_string(&id_to_source_range).map_err(|e| {
|
||||
KclError::Engine(KclErrorDetails::new(
|
||||
KclError::new_engine(KclErrorDetails::new(
|
||||
format!("Failed to serialize id to source range: {:?}", e),
|
||||
vec![source_range],
|
||||
))
|
||||
@ -201,7 +199,7 @@ impl EngineConnection {
|
||||
let promise = self
|
||||
.manager
|
||||
.send_modeling_cmd_from_wasm(id.to_string(), source_range_str, cmd_str, id_to_source_range_str)
|
||||
.map_err(|e| KclError::Engine(KclErrorDetails::new(e.to_string().into(), vec![source_range])))?;
|
||||
.map_err(|e| KclError::new_engine(KclErrorDetails::new(e.to_string().into(), vec![source_range])))?;
|
||||
|
||||
let value = crate::wasm::JsFuture::from(promise).await.map_err(|e| {
|
||||
// Try to parse the error as an engine error.
|
||||
@ -209,7 +207,7 @@ impl EngineConnection {
|
||||
if let Ok(kittycad_modeling_cmds::websocket::FailureWebSocketResponse { errors, .. }) =
|
||||
serde_json::from_str(&err_str)
|
||||
{
|
||||
KclError::Engine(KclErrorDetails::new(
|
||||
KclError::new_engine(KclErrorDetails::new(
|
||||
errors.iter().map(|e| e.message.clone()).collect::<Vec<_>>().join("\n"),
|
||||
vec![source_range],
|
||||
))
|
||||
@ -218,7 +216,7 @@ impl EngineConnection {
|
||||
{
|
||||
if let Some(data) = data.first() {
|
||||
// It could also be an array of responses.
|
||||
KclError::Engine(KclErrorDetails::new(
|
||||
KclError::new_engine(KclErrorDetails::new(
|
||||
data.errors
|
||||
.iter()
|
||||
.map(|e| e.message.clone())
|
||||
@ -227,13 +225,13 @@ impl EngineConnection {
|
||||
vec![source_range],
|
||||
))
|
||||
} else {
|
||||
KclError::Engine(KclErrorDetails::new(
|
||||
KclError::new_engine(KclErrorDetails::new(
|
||||
"Received empty response from engine".into(),
|
||||
vec![source_range],
|
||||
))
|
||||
}
|
||||
} else {
|
||||
KclError::Engine(KclErrorDetails::new(
|
||||
KclError::new_engine(KclErrorDetails::new(
|
||||
format!("Failed to wait for promise from send modeling command: {:?}", e),
|
||||
vec![source_range],
|
||||
))
|
||||
@ -241,7 +239,7 @@ impl EngineConnection {
|
||||
})?;
|
||||
|
||||
if value.is_null() || value.is_undefined() {
|
||||
return Err(KclError::Engine(KclErrorDetails::new(
|
||||
return Err(KclError::new_engine(KclErrorDetails::new(
|
||||
"Received null or undefined response from engine".into(),
|
||||
vec![source_range],
|
||||
)));
|
||||
@ -251,7 +249,7 @@ impl EngineConnection {
|
||||
let data = js_sys::Uint8Array::from(value);
|
||||
|
||||
let ws_result: WebSocketResponse = bson::from_slice(&data.to_vec()).map_err(|e| {
|
||||
KclError::Engine(KclErrorDetails::new(
|
||||
KclError::new_engine(KclErrorDetails::new(
|
||||
format!("Failed to deserialize bson response from engine: {:?}", e),
|
||||
vec![source_range],
|
||||
))
|
||||
@ -308,10 +306,10 @@ impl crate::engine::EngineManager for EngineConnection {
|
||||
let promise = self
|
||||
.manager
|
||||
.start_new_session()
|
||||
.map_err(|e| KclError::Engine(KclErrorDetails::new(e.to_string().into(), vec![source_range])))?;
|
||||
.map_err(|e| KclError::new_engine(KclErrorDetails::new(e.to_string().into(), vec![source_range])))?;
|
||||
|
||||
crate::wasm::JsFuture::from(promise).await.map_err(|e| {
|
||||
KclError::Engine(KclErrorDetails::new(
|
||||
KclError::new_engine(KclErrorDetails::new(
|
||||
format!("Failed to wait for promise from start new session: {:?}", e),
|
||||
vec![source_range],
|
||||
))
|
||||
|
@ -276,7 +276,7 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
|
||||
{
|
||||
let duration = instant::Duration::from_millis(1);
|
||||
wasm_timer::Delay::new(duration).await.map_err(|err| {
|
||||
KclError::Internal(KclErrorDetails::new(
|
||||
KclError::new_internal(KclErrorDetails::new(
|
||||
format!("Failed to sleep: {:?}", err),
|
||||
vec![source_range],
|
||||
))
|
||||
@ -293,7 +293,7 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
|
||||
return Ok(response);
|
||||
}
|
||||
|
||||
Err(KclError::Engine(KclErrorDetails::new(
|
||||
Err(KclError::new_engine(KclErrorDetails::new(
|
||||
"async command timed out".to_string(),
|
||||
vec![source_range],
|
||||
)))
|
||||
@ -547,7 +547,7 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
|
||||
id_to_source_range.insert(Uuid::from(*cmd_id), *range);
|
||||
}
|
||||
_ => {
|
||||
return Err(KclError::Engine(KclErrorDetails::new(
|
||||
return Err(KclError::new_engine(KclErrorDetails::new(
|
||||
format!("The request is not a modeling command: {:?}", req),
|
||||
vec![*range],
|
||||
)));
|
||||
@ -595,7 +595,7 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
|
||||
self.parse_batch_responses(last_id.into(), id_to_source_range, responses)
|
||||
} else {
|
||||
// We should never get here.
|
||||
Err(KclError::Engine(KclErrorDetails::new(
|
||||
Err(KclError::new_engine(KclErrorDetails::new(
|
||||
format!("Failed to get batch response: {:?}", response),
|
||||
vec![source_range],
|
||||
)))
|
||||
@ -610,7 +610,7 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
|
||||
// request so we need the original request source range in case the engine returns
|
||||
// an error.
|
||||
let source_range = id_to_source_range.get(cmd_id.as_ref()).cloned().ok_or_else(|| {
|
||||
KclError::Engine(KclErrorDetails::new(
|
||||
KclError::new_engine(KclErrorDetails::new(
|
||||
format!("Failed to get source range for command ID: {:?}", cmd_id),
|
||||
vec![],
|
||||
))
|
||||
@ -620,7 +620,7 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
|
||||
.await?;
|
||||
self.parse_websocket_response(ws_resp, source_range)
|
||||
}
|
||||
_ => Err(KclError::Engine(KclErrorDetails::new(
|
||||
_ => Err(KclError::new_engine(KclErrorDetails::new(
|
||||
format!("The final request is not a modeling command: {:?}", final_req),
|
||||
vec![source_range],
|
||||
))),
|
||||
@ -729,7 +729,7 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
|
||||
for (name, plane_id, color) in plane_settings {
|
||||
let info = DEFAULT_PLANE_INFO.get(&name).ok_or_else(|| {
|
||||
// We should never get here.
|
||||
KclError::Engine(KclErrorDetails::new(
|
||||
KclError::new_engine(KclErrorDetails::new(
|
||||
format!("Failed to get default plane info for: {:?}", name),
|
||||
vec![source_range],
|
||||
))
|
||||
@ -763,7 +763,7 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
|
||||
WebSocketResponse::Success(success) => Ok(success.resp),
|
||||
WebSocketResponse::Failure(fail) => {
|
||||
let _request_id = fail.request_id;
|
||||
Err(KclError::Engine(KclErrorDetails::new(
|
||||
Err(KclError::new_engine(KclErrorDetails::new(
|
||||
fail.errors
|
||||
.iter()
|
||||
.map(|e| e.message.clone())
|
||||
@ -805,12 +805,12 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
|
||||
BatchResponse::Failure { errors } => {
|
||||
// Get the source range for the command.
|
||||
let source_range = id_to_source_range.get(cmd_id).cloned().ok_or_else(|| {
|
||||
KclError::Engine(KclErrorDetails::new(
|
||||
KclError::new_engine(KclErrorDetails::new(
|
||||
format!("Failed to get source range for command ID: {:?}", cmd_id),
|
||||
vec![],
|
||||
))
|
||||
})?;
|
||||
return Err(KclError::Engine(KclErrorDetails::new(
|
||||
return Err(KclError::new_engine(KclErrorDetails::new(
|
||||
errors.iter().map(|e| e.message.clone()).collect::<Vec<_>>().join("\n"),
|
||||
vec![source_range],
|
||||
)));
|
||||
@ -820,7 +820,7 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
|
||||
|
||||
// Return an error that we did not get an error or the response we wanted.
|
||||
// This should never happen but who knows.
|
||||
Err(KclError::Engine(KclErrorDetails::new(
|
||||
Err(KclError::new_engine(KclErrorDetails::new(
|
||||
format!("Failed to find response for command ID: {:?}", id),
|
||||
vec![],
|
||||
)))
|
||||
|
@ -91,30 +91,33 @@ pub enum ConnectionError {
|
||||
#[ts(export)]
|
||||
#[serde(tag = "kind", rename_all = "snake_case")]
|
||||
pub enum KclError {
|
||||
#[error("lexical: {0:?}")]
|
||||
Lexical(KclErrorDetails),
|
||||
#[error("syntax: {0:?}")]
|
||||
Syntax(KclErrorDetails),
|
||||
#[error("semantic: {0:?}")]
|
||||
Semantic(KclErrorDetails),
|
||||
#[error("import cycle: {0:?}")]
|
||||
ImportCycle(KclErrorDetails),
|
||||
#[error("type: {0:?}")]
|
||||
Type(KclErrorDetails),
|
||||
#[error("i/o: {0:?}")]
|
||||
Io(KclErrorDetails),
|
||||
#[error("unexpected: {0:?}")]
|
||||
Unexpected(KclErrorDetails),
|
||||
#[error("value already defined: {0:?}")]
|
||||
ValueAlreadyDefined(KclErrorDetails),
|
||||
#[error("undefined value: {0:?}")]
|
||||
UndefinedValue(KclErrorDetails),
|
||||
#[error("invalid expression: {0:?}")]
|
||||
InvalidExpression(KclErrorDetails),
|
||||
#[error("engine: {0:?}")]
|
||||
Engine(KclErrorDetails),
|
||||
#[error("internal error, please report to KittyCAD team: {0:?}")]
|
||||
Internal(KclErrorDetails),
|
||||
#[error("lexical: {details:?}")]
|
||||
Lexical { details: KclErrorDetails },
|
||||
#[error("syntax: {details:?}")]
|
||||
Syntax { details: KclErrorDetails },
|
||||
#[error("semantic: {details:?}")]
|
||||
Semantic { details: KclErrorDetails },
|
||||
#[error("import cycle: {details:?}")]
|
||||
ImportCycle { details: KclErrorDetails },
|
||||
#[error("type: {details:?}")]
|
||||
Type { details: KclErrorDetails },
|
||||
#[error("i/o: {details:?}")]
|
||||
Io { details: KclErrorDetails },
|
||||
#[error("unexpected: {details:?}")]
|
||||
Unexpected { details: KclErrorDetails },
|
||||
#[error("value already defined: {details:?}")]
|
||||
ValueAlreadyDefined { details: KclErrorDetails },
|
||||
#[error("undefined value: {details:?}")]
|
||||
UndefinedValue {
|
||||
details: KclErrorDetails,
|
||||
name: Option<String>,
|
||||
},
|
||||
#[error("invalid expression: {details:?}")]
|
||||
InvalidExpression { details: KclErrorDetails },
|
||||
#[error("engine: {details:?}")]
|
||||
Engine { details: KclErrorDetails },
|
||||
#[error("internal error, please report to KittyCAD team: {details:?}")]
|
||||
Internal { details: KclErrorDetails },
|
||||
}
|
||||
|
||||
impl From<KclErrorWithOutputs> for KclError {
|
||||
@ -296,18 +299,18 @@ pub struct ReportWithOutputs {
|
||||
impl miette::Diagnostic for ReportWithOutputs {
|
||||
fn code<'a>(&'a self) -> Option<Box<dyn std::fmt::Display + 'a>> {
|
||||
let family = match self.error.error {
|
||||
KclError::Lexical(_) => "Lexical",
|
||||
KclError::Syntax(_) => "Syntax",
|
||||
KclError::Semantic(_) => "Semantic",
|
||||
KclError::ImportCycle(_) => "ImportCycle",
|
||||
KclError::Type(_) => "Type",
|
||||
KclError::Io(_) => "I/O",
|
||||
KclError::Unexpected(_) => "Unexpected",
|
||||
KclError::ValueAlreadyDefined(_) => "ValueAlreadyDefined",
|
||||
KclError::UndefinedValue(_) => "UndefinedValue",
|
||||
KclError::InvalidExpression(_) => "InvalidExpression",
|
||||
KclError::Engine(_) => "Engine",
|
||||
KclError::Internal(_) => "Internal",
|
||||
KclError::Lexical { .. } => "Lexical",
|
||||
KclError::Syntax { .. } => "Syntax",
|
||||
KclError::Semantic { .. } => "Semantic",
|
||||
KclError::ImportCycle { .. } => "ImportCycle",
|
||||
KclError::Type { .. } => "Type",
|
||||
KclError::Io { .. } => "I/O",
|
||||
KclError::Unexpected { .. } => "Unexpected",
|
||||
KclError::ValueAlreadyDefined { .. } => "ValueAlreadyDefined",
|
||||
KclError::UndefinedValue { .. } => "UndefinedValue",
|
||||
KclError::InvalidExpression { .. } => "InvalidExpression",
|
||||
KclError::Engine { .. } => "Engine",
|
||||
KclError::Internal { .. } => "Internal",
|
||||
};
|
||||
let error_string = format!("KCL {family} error");
|
||||
Some(Box::new(error_string))
|
||||
@ -346,18 +349,18 @@ pub struct Report {
|
||||
impl miette::Diagnostic for Report {
|
||||
fn code<'a>(&'a self) -> Option<Box<dyn std::fmt::Display + 'a>> {
|
||||
let family = match self.error {
|
||||
KclError::Lexical(_) => "Lexical",
|
||||
KclError::Syntax(_) => "Syntax",
|
||||
KclError::Semantic(_) => "Semantic",
|
||||
KclError::ImportCycle(_) => "ImportCycle",
|
||||
KclError::Type(_) => "Type",
|
||||
KclError::Io(_) => "I/O",
|
||||
KclError::Unexpected(_) => "Unexpected",
|
||||
KclError::ValueAlreadyDefined(_) => "ValueAlreadyDefined",
|
||||
KclError::UndefinedValue(_) => "UndefinedValue",
|
||||
KclError::InvalidExpression(_) => "InvalidExpression",
|
||||
KclError::Engine(_) => "Engine",
|
||||
KclError::Internal(_) => "Internal",
|
||||
KclError::Lexical { .. } => "Lexical",
|
||||
KclError::Syntax { .. } => "Syntax",
|
||||
KclError::Semantic { .. } => "Semantic",
|
||||
KclError::ImportCycle { .. } => "ImportCycle",
|
||||
KclError::Type { .. } => "Type",
|
||||
KclError::Io { .. } => "I/O",
|
||||
KclError::Unexpected { .. } => "Unexpected",
|
||||
KclError::ValueAlreadyDefined { .. } => "ValueAlreadyDefined",
|
||||
KclError::UndefinedValue { .. } => "UndefinedValue",
|
||||
KclError::InvalidExpression { .. } => "InvalidExpression",
|
||||
KclError::Engine { .. } => "Engine",
|
||||
KclError::Internal { .. } => "Internal",
|
||||
};
|
||||
let error_string = format!("KCL {family} error");
|
||||
Some(Box::new(error_string))
|
||||
@ -410,11 +413,53 @@ impl KclErrorDetails {
|
||||
|
||||
impl KclError {
|
||||
pub fn internal(message: String) -> KclError {
|
||||
KclError::Internal(KclErrorDetails {
|
||||
source_ranges: Default::default(),
|
||||
backtrace: Default::default(),
|
||||
message,
|
||||
})
|
||||
KclError::Internal {
|
||||
details: KclErrorDetails {
|
||||
source_ranges: Default::default(),
|
||||
backtrace: Default::default(),
|
||||
message,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_internal(details: KclErrorDetails) -> KclError {
|
||||
KclError::Internal { details }
|
||||
}
|
||||
|
||||
pub fn new_import_cycle(details: KclErrorDetails) -> KclError {
|
||||
KclError::ImportCycle { details }
|
||||
}
|
||||
|
||||
pub fn new_semantic(details: KclErrorDetails) -> KclError {
|
||||
KclError::Semantic { details }
|
||||
}
|
||||
|
||||
pub fn new_value_already_defined(details: KclErrorDetails) -> KclError {
|
||||
KclError::ValueAlreadyDefined { details }
|
||||
}
|
||||
|
||||
pub fn new_syntax(details: KclErrorDetails) -> KclError {
|
||||
KclError::Syntax { details }
|
||||
}
|
||||
|
||||
pub fn new_io(details: KclErrorDetails) -> KclError {
|
||||
KclError::Io { details }
|
||||
}
|
||||
|
||||
pub fn new_engine(details: KclErrorDetails) -> KclError {
|
||||
KclError::Engine { details }
|
||||
}
|
||||
|
||||
pub fn new_lexical(details: KclErrorDetails) -> KclError {
|
||||
KclError::Lexical { details }
|
||||
}
|
||||
|
||||
pub fn new_undefined_value(details: KclErrorDetails, name: Option<String>) -> KclError {
|
||||
KclError::UndefinedValue { details, name }
|
||||
}
|
||||
|
||||
pub fn new_type(details: KclErrorDetails) -> KclError {
|
||||
KclError::Type { details }
|
||||
}
|
||||
|
||||
/// Get the error message.
|
||||
@ -424,88 +469,88 @@ impl KclError {
|
||||
|
||||
pub fn error_type(&self) -> &'static str {
|
||||
match self {
|
||||
KclError::Lexical(_) => "lexical",
|
||||
KclError::Syntax(_) => "syntax",
|
||||
KclError::Semantic(_) => "semantic",
|
||||
KclError::ImportCycle(_) => "import cycle",
|
||||
KclError::Type(_) => "type",
|
||||
KclError::Io(_) => "i/o",
|
||||
KclError::Unexpected(_) => "unexpected",
|
||||
KclError::ValueAlreadyDefined(_) => "value already defined",
|
||||
KclError::UndefinedValue(_) => "undefined value",
|
||||
KclError::InvalidExpression(_) => "invalid expression",
|
||||
KclError::Engine(_) => "engine",
|
||||
KclError::Internal(_) => "internal",
|
||||
KclError::Lexical { .. } => "lexical",
|
||||
KclError::Syntax { .. } => "syntax",
|
||||
KclError::Semantic { .. } => "semantic",
|
||||
KclError::ImportCycle { .. } => "import cycle",
|
||||
KclError::Type { .. } => "type",
|
||||
KclError::Io { .. } => "i/o",
|
||||
KclError::Unexpected { .. } => "unexpected",
|
||||
KclError::ValueAlreadyDefined { .. } => "value already defined",
|
||||
KclError::UndefinedValue { .. } => "undefined value",
|
||||
KclError::InvalidExpression { .. } => "invalid expression",
|
||||
KclError::Engine { .. } => "engine",
|
||||
KclError::Internal { .. } => "internal",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn source_ranges(&self) -> Vec<SourceRange> {
|
||||
match &self {
|
||||
KclError::Lexical(e) => e.source_ranges.clone(),
|
||||
KclError::Syntax(e) => e.source_ranges.clone(),
|
||||
KclError::Semantic(e) => e.source_ranges.clone(),
|
||||
KclError::ImportCycle(e) => e.source_ranges.clone(),
|
||||
KclError::Type(e) => e.source_ranges.clone(),
|
||||
KclError::Io(e) => e.source_ranges.clone(),
|
||||
KclError::Unexpected(e) => e.source_ranges.clone(),
|
||||
KclError::ValueAlreadyDefined(e) => e.source_ranges.clone(),
|
||||
KclError::UndefinedValue(e) => e.source_ranges.clone(),
|
||||
KclError::InvalidExpression(e) => e.source_ranges.clone(),
|
||||
KclError::Engine(e) => e.source_ranges.clone(),
|
||||
KclError::Internal(e) => e.source_ranges.clone(),
|
||||
KclError::Lexical { details: e } => e.source_ranges.clone(),
|
||||
KclError::Syntax { details: e } => e.source_ranges.clone(),
|
||||
KclError::Semantic { details: e } => e.source_ranges.clone(),
|
||||
KclError::ImportCycle { details: e } => e.source_ranges.clone(),
|
||||
KclError::Type { details: e } => e.source_ranges.clone(),
|
||||
KclError::Io { details: e } => e.source_ranges.clone(),
|
||||
KclError::Unexpected { details: e } => e.source_ranges.clone(),
|
||||
KclError::ValueAlreadyDefined { details: e } => e.source_ranges.clone(),
|
||||
KclError::UndefinedValue { details: e, .. } => e.source_ranges.clone(),
|
||||
KclError::InvalidExpression { details: e } => e.source_ranges.clone(),
|
||||
KclError::Engine { details: e } => e.source_ranges.clone(),
|
||||
KclError::Internal { details: e } => e.source_ranges.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the inner error message.
|
||||
pub fn message(&self) -> &str {
|
||||
match &self {
|
||||
KclError::Lexical(e) => &e.message,
|
||||
KclError::Syntax(e) => &e.message,
|
||||
KclError::Semantic(e) => &e.message,
|
||||
KclError::ImportCycle(e) => &e.message,
|
||||
KclError::Type(e) => &e.message,
|
||||
KclError::Io(e) => &e.message,
|
||||
KclError::Unexpected(e) => &e.message,
|
||||
KclError::ValueAlreadyDefined(e) => &e.message,
|
||||
KclError::UndefinedValue(e) => &e.message,
|
||||
KclError::InvalidExpression(e) => &e.message,
|
||||
KclError::Engine(e) => &e.message,
|
||||
KclError::Internal(e) => &e.message,
|
||||
KclError::Lexical { details: e } => &e.message,
|
||||
KclError::Syntax { details: e } => &e.message,
|
||||
KclError::Semantic { details: e } => &e.message,
|
||||
KclError::ImportCycle { details: e } => &e.message,
|
||||
KclError::Type { details: e } => &e.message,
|
||||
KclError::Io { details: e } => &e.message,
|
||||
KclError::Unexpected { details: e } => &e.message,
|
||||
KclError::ValueAlreadyDefined { details: e } => &e.message,
|
||||
KclError::UndefinedValue { details: e, .. } => &e.message,
|
||||
KclError::InvalidExpression { details: e } => &e.message,
|
||||
KclError::Engine { details: e } => &e.message,
|
||||
KclError::Internal { details: e } => &e.message,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn backtrace(&self) -> Vec<BacktraceItem> {
|
||||
match self {
|
||||
KclError::Lexical(e)
|
||||
| KclError::Syntax(e)
|
||||
| KclError::Semantic(e)
|
||||
| KclError::ImportCycle(e)
|
||||
| KclError::Type(e)
|
||||
| KclError::Io(e)
|
||||
| KclError::Unexpected(e)
|
||||
| KclError::ValueAlreadyDefined(e)
|
||||
| KclError::UndefinedValue(e)
|
||||
| KclError::InvalidExpression(e)
|
||||
| KclError::Engine(e)
|
||||
| KclError::Internal(e) => e.backtrace.clone(),
|
||||
KclError::Lexical { details: e }
|
||||
| KclError::Syntax { details: e }
|
||||
| KclError::Semantic { details: e }
|
||||
| KclError::ImportCycle { details: e }
|
||||
| KclError::Type { details: e }
|
||||
| KclError::Io { details: e }
|
||||
| KclError::Unexpected { details: e }
|
||||
| KclError::ValueAlreadyDefined { details: e }
|
||||
| KclError::UndefinedValue { details: e, .. }
|
||||
| KclError::InvalidExpression { details: e }
|
||||
| KclError::Engine { details: e }
|
||||
| KclError::Internal { details: e } => e.backtrace.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn override_source_ranges(&self, source_ranges: Vec<SourceRange>) -> Self {
|
||||
let mut new = self.clone();
|
||||
match &mut new {
|
||||
KclError::Lexical(e)
|
||||
| KclError::Syntax(e)
|
||||
| KclError::Semantic(e)
|
||||
| KclError::ImportCycle(e)
|
||||
| KclError::Type(e)
|
||||
| KclError::Io(e)
|
||||
| KclError::Unexpected(e)
|
||||
| KclError::ValueAlreadyDefined(e)
|
||||
| KclError::UndefinedValue(e)
|
||||
| KclError::InvalidExpression(e)
|
||||
| KclError::Engine(e)
|
||||
| KclError::Internal(e) => {
|
||||
KclError::Lexical { details: e }
|
||||
| KclError::Syntax { details: e }
|
||||
| KclError::Semantic { details: e }
|
||||
| KclError::ImportCycle { details: e }
|
||||
| KclError::Type { details: e }
|
||||
| KclError::Io { details: e }
|
||||
| KclError::Unexpected { details: e }
|
||||
| KclError::ValueAlreadyDefined { details: e }
|
||||
| KclError::UndefinedValue { details: e, .. }
|
||||
| KclError::InvalidExpression { details: e }
|
||||
| KclError::Engine { details: e }
|
||||
| KclError::Internal { details: e } => {
|
||||
e.backtrace = source_ranges
|
||||
.iter()
|
||||
.map(|s| BacktraceItem {
|
||||
@ -520,45 +565,21 @@ impl KclError {
|
||||
new
|
||||
}
|
||||
|
||||
pub(crate) fn set_last_backtrace_fn_name(&self, last_fn_name: Option<String>) -> Self {
|
||||
let mut new = self.clone();
|
||||
match &mut new {
|
||||
KclError::Lexical(e)
|
||||
| KclError::Syntax(e)
|
||||
| KclError::Semantic(e)
|
||||
| KclError::ImportCycle(e)
|
||||
| KclError::Type(e)
|
||||
| KclError::Io(e)
|
||||
| KclError::Unexpected(e)
|
||||
| KclError::ValueAlreadyDefined(e)
|
||||
| KclError::UndefinedValue(e)
|
||||
| KclError::InvalidExpression(e)
|
||||
| KclError::Engine(e)
|
||||
| KclError::Internal(e) => {
|
||||
if let Some(item) = e.backtrace.last_mut() {
|
||||
item.fn_name = last_fn_name;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
new
|
||||
}
|
||||
|
||||
pub(crate) fn add_unwind_location(&self, last_fn_name: Option<String>, source_range: SourceRange) -> Self {
|
||||
let mut new = self.clone();
|
||||
match &mut new {
|
||||
KclError::Lexical(e)
|
||||
| KclError::Syntax(e)
|
||||
| KclError::Semantic(e)
|
||||
| KclError::ImportCycle(e)
|
||||
| KclError::Type(e)
|
||||
| KclError::Io(e)
|
||||
| KclError::Unexpected(e)
|
||||
| KclError::ValueAlreadyDefined(e)
|
||||
| KclError::UndefinedValue(e)
|
||||
| KclError::InvalidExpression(e)
|
||||
| KclError::Engine(e)
|
||||
| KclError::Internal(e) => {
|
||||
KclError::Lexical { details: e }
|
||||
| KclError::Syntax { details: e }
|
||||
| KclError::Semantic { details: e }
|
||||
| KclError::ImportCycle { details: e }
|
||||
| KclError::Type { details: e }
|
||||
| KclError::Io { details: e }
|
||||
| KclError::Unexpected { details: e }
|
||||
| KclError::ValueAlreadyDefined { details: e }
|
||||
| KclError::UndefinedValue { details: e, .. }
|
||||
| KclError::InvalidExpression { details: e }
|
||||
| KclError::Engine { details: e }
|
||||
| KclError::Internal { details: e } => {
|
||||
if let Some(item) = e.backtrace.last_mut() {
|
||||
item.fn_name = last_fn_name;
|
||||
}
|
||||
@ -645,7 +666,7 @@ impl From<String> for KclError {
|
||||
#[cfg(feature = "pyo3")]
|
||||
impl From<pyo3::PyErr> for KclError {
|
||||
fn from(error: pyo3::PyErr) -> Self {
|
||||
KclError::Internal(KclErrorDetails {
|
||||
KclError::new_internal(KclErrorDetails {
|
||||
source_ranges: vec![],
|
||||
backtrace: Default::default(),
|
||||
message: error.to_string(),
|
||||
|
@ -70,7 +70,7 @@ pub(super) fn expect_properties<'a>(
|
||||
) -> Result<&'a [Node<ObjectProperty>], KclError> {
|
||||
assert_eq!(annotation.name().unwrap(), for_key);
|
||||
Ok(&**annotation.properties.as_ref().ok_or_else(|| {
|
||||
KclError::Semantic(KclErrorDetails::new(
|
||||
KclError::new_semantic(KclErrorDetails::new(
|
||||
format!("Empty `{for_key}` annotation"),
|
||||
vec![annotation.as_source_range()],
|
||||
))
|
||||
@ -84,7 +84,7 @@ pub(super) fn expect_ident(expr: &Expr) -> Result<&str, KclError> {
|
||||
}
|
||||
}
|
||||
|
||||
Err(KclError::Semantic(KclErrorDetails::new(
|
||||
Err(KclError::new_semantic(KclErrorDetails::new(
|
||||
"Unexpected settings value, expected a simple name, e.g., `mm`".to_owned(),
|
||||
vec![expr.into()],
|
||||
)))
|
||||
@ -98,7 +98,7 @@ pub(super) fn expect_number(expr: &Expr) -> Result<String, KclError> {
|
||||
}
|
||||
}
|
||||
|
||||
Err(KclError::Semantic(KclErrorDetails::new(
|
||||
Err(KclError::new_semantic(KclErrorDetails::new(
|
||||
"Unexpected settings value, expected a number, e.g., `1.0`".to_owned(),
|
||||
vec![expr.into()],
|
||||
)))
|
||||
@ -113,7 +113,7 @@ pub(super) fn get_impl(annotations: &[Node<Annotation>], source_range: SourceRan
|
||||
if &*p.key.name == IMPL {
|
||||
if let Some(s) = p.value.ident_name() {
|
||||
return Impl::from_str(s).map(Some).map_err(|_| {
|
||||
KclError::Semantic(KclErrorDetails::new(
|
||||
KclError::new_semantic(KclErrorDetails::new(
|
||||
format!(
|
||||
"Invalid value for {} attribute, expected one of: {}",
|
||||
IMPL,
|
||||
@ -139,7 +139,7 @@ impl UnitLen {
|
||||
"inch" | "in" => Ok(UnitLen::Inches),
|
||||
"ft" => Ok(UnitLen::Feet),
|
||||
"yd" => Ok(UnitLen::Yards),
|
||||
value => Err(KclError::Semantic(KclErrorDetails::new(
|
||||
value => Err(KclError::new_semantic(KclErrorDetails::new(
|
||||
format!(
|
||||
"Unexpected value for length units: `{value}`; expected one of `mm`, `cm`, `m`, `in`, `ft`, `yd`"
|
||||
),
|
||||
@ -154,7 +154,7 @@ impl UnitAngle {
|
||||
match s {
|
||||
"deg" => Ok(UnitAngle::Degrees),
|
||||
"rad" => Ok(UnitAngle::Radians),
|
||||
value => Err(KclError::Semantic(KclErrorDetails::new(
|
||||
value => Err(KclError::new_semantic(KclErrorDetails::new(
|
||||
format!("Unexpected value for angle units: `{value}`; expected one of `deg`, `rad`"),
|
||||
vec![source_range],
|
||||
))),
|
||||
|
@ -24,7 +24,7 @@ macro_rules! internal_error {
|
||||
($range:expr, $($rest:tt)*) => {{
|
||||
let message = format!($($rest)*);
|
||||
debug_assert!(false, "{}", &message);
|
||||
return Err(KclError::Internal(KclErrorDetails::new(message, vec![$range])));
|
||||
return Err(KclError::new_internal(KclErrorDetails::new(message, vec![$range])));
|
||||
}};
|
||||
}
|
||||
|
||||
@ -676,6 +676,7 @@ impl EdgeCut {
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ArtifactGraph {
|
||||
map: IndexMap<ArtifactId, Artifact>,
|
||||
item_count: usize,
|
||||
}
|
||||
|
||||
impl ArtifactGraph {
|
||||
@ -711,10 +712,10 @@ pub(super) fn build_artifact_graph(
|
||||
artifact_commands: &[ArtifactCommand],
|
||||
responses: &IndexMap<Uuid, WebSocketResponse>,
|
||||
ast: &Node<Program>,
|
||||
cached_body_items: usize,
|
||||
exec_artifacts: &mut IndexMap<ArtifactId, Artifact>,
|
||||
initial_graph: ArtifactGraph,
|
||||
) -> Result<ArtifactGraph, KclError> {
|
||||
let item_count = initial_graph.item_count;
|
||||
let mut map = initial_graph.into_map();
|
||||
|
||||
let mut path_to_plane_id_map = FnvHashMap::default();
|
||||
@ -725,7 +726,7 @@ pub(super) fn build_artifact_graph(
|
||||
for exec_artifact in exec_artifacts.values_mut() {
|
||||
// Note: We only have access to the new AST. So if these artifacts
|
||||
// somehow came from cached AST, this won't fill in anything.
|
||||
fill_in_node_paths(exec_artifact, ast, cached_body_items);
|
||||
fill_in_node_paths(exec_artifact, ast, item_count);
|
||||
}
|
||||
|
||||
for artifact_command in artifact_commands {
|
||||
@ -752,7 +753,7 @@ pub(super) fn build_artifact_graph(
|
||||
&flattened_responses,
|
||||
&path_to_plane_id_map,
|
||||
ast,
|
||||
cached_body_items,
|
||||
item_count,
|
||||
exec_artifacts,
|
||||
)?;
|
||||
for artifact in artifact_updates {
|
||||
@ -765,7 +766,10 @@ pub(super) fn build_artifact_graph(
|
||||
merge_artifact_into_map(&mut map, exec_artifact.clone());
|
||||
}
|
||||
|
||||
Ok(ArtifactGraph { map })
|
||||
Ok(ArtifactGraph {
|
||||
map,
|
||||
item_count: item_count + ast.body.len(),
|
||||
})
|
||||
}
|
||||
|
||||
/// These may have been created with placeholder `CodeRef`s because we didn't
|
||||
@ -949,7 +953,7 @@ fn artifacts_to_update(
|
||||
ModelingCmd::StartPath(_) => {
|
||||
let mut return_arr = Vec::new();
|
||||
let current_plane_id = path_to_plane_id_map.get(&artifact_command.cmd_id).ok_or_else(|| {
|
||||
KclError::Internal(KclErrorDetails::new(
|
||||
KclError::new_internal(KclErrorDetails::new(
|
||||
format!("Expected a current plane ID when processing StartPath command, but we have none: {id:?}"),
|
||||
vec![range],
|
||||
))
|
||||
@ -1137,7 +1141,7 @@ fn artifacts_to_update(
|
||||
// TODO: Using the first one. Make sure to revisit this
|
||||
// choice, don't think it matters for now.
|
||||
path_id: ArtifactId::new(*loft_cmd.section_ids.first().ok_or_else(|| {
|
||||
KclError::Internal(KclErrorDetails::new(
|
||||
KclError::new_internal(KclErrorDetails::new(
|
||||
format!("Expected at least one section ID in Loft command: {id:?}; cmd={cmd:?}"),
|
||||
vec![range],
|
||||
))
|
||||
@ -1180,7 +1184,7 @@ fn artifacts_to_update(
|
||||
};
|
||||
last_path = Some(path);
|
||||
let path_sweep_id = path.sweep_id.ok_or_else(|| {
|
||||
KclError::Internal(KclErrorDetails::new(
|
||||
KclError::new_internal(KclErrorDetails::new(
|
||||
format!(
|
||||
"Expected a sweep ID on the path when processing Solid3dGetExtrusionFaceInfo command, but we have none: {id:?}, {path:?}"
|
||||
),
|
||||
@ -1234,7 +1238,7 @@ fn artifacts_to_update(
|
||||
continue;
|
||||
};
|
||||
let path_sweep_id = path.sweep_id.ok_or_else(|| {
|
||||
KclError::Internal(KclErrorDetails::new(
|
||||
KclError::new_internal(KclErrorDetails::new(
|
||||
format!(
|
||||
"Expected a sweep ID on the path when processing last path's Solid3dGetExtrusionFaceInfo command, but we have none: {id:?}, {path:?}"
|
||||
),
|
||||
|
@ -6,25 +6,31 @@ use itertools::{EitherOrBoth, Itertools};
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
use crate::{
|
||||
execution::{annotations, memory::Stack, state::ModuleInfoMap, EnvironmentRef, ExecState, ExecutorSettings},
|
||||
execution::{
|
||||
annotations,
|
||||
memory::Stack,
|
||||
state::{self as exec_state, ModuleInfoMap},
|
||||
EnvironmentRef, ExecutorSettings,
|
||||
},
|
||||
parsing::ast::types::{Annotation, Node, Program},
|
||||
walk::Node as WalkNode,
|
||||
ExecOutcome, ExecutorContext,
|
||||
};
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
/// A static mutable lock for updating the last successful execution state for the cache.
|
||||
static ref OLD_AST: Arc<RwLock<Option<OldAstState>>> = Default::default();
|
||||
static ref OLD_AST: Arc<RwLock<Option<GlobalState>>> = Default::default();
|
||||
// The last successful run's memory. Not cleared after an unssuccessful run.
|
||||
static ref PREV_MEMORY: Arc<RwLock<Option<(Stack, ModuleInfoMap)>>> = Default::default();
|
||||
}
|
||||
|
||||
/// Read the old ast memory from the lock.
|
||||
pub(crate) async fn read_old_ast() -> Option<OldAstState> {
|
||||
pub(super) async fn read_old_ast() -> Option<GlobalState> {
|
||||
let old_ast = OLD_AST.read().await;
|
||||
old_ast.clone()
|
||||
}
|
||||
|
||||
pub(super) async fn write_old_ast(old_state: OldAstState) {
|
||||
pub(super) async fn write_old_ast(old_state: GlobalState) {
|
||||
let mut old_ast = OLD_AST.write().await;
|
||||
*old_ast = Some(old_state);
|
||||
}
|
||||
@ -34,7 +40,7 @@ pub(crate) async fn read_old_memory() -> Option<(Stack, ModuleInfoMap)> {
|
||||
old_mem.clone()
|
||||
}
|
||||
|
||||
pub(super) async fn write_old_memory(mem: (Stack, ModuleInfoMap)) {
|
||||
pub(crate) async fn write_old_memory(mem: (Stack, ModuleInfoMap)) {
|
||||
let mut old_mem = PREV_MEMORY.write().await;
|
||||
*old_mem = Some(mem);
|
||||
}
|
||||
@ -56,16 +62,73 @@ pub struct CacheInformation<'a> {
|
||||
pub settings: &'a ExecutorSettings,
|
||||
}
|
||||
|
||||
/// The old ast and program memory.
|
||||
/// The cached state of the whole program.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct OldAstState {
|
||||
/// The ast.
|
||||
pub ast: Node<Program>,
|
||||
pub(super) struct GlobalState {
|
||||
pub(super) main: ModuleState,
|
||||
/// The exec state.
|
||||
pub exec_state: ExecState,
|
||||
pub(super) exec_state: exec_state::GlobalState,
|
||||
/// The last settings used for execution.
|
||||
pub settings: crate::execution::ExecutorSettings,
|
||||
pub result_env: EnvironmentRef,
|
||||
pub(super) settings: ExecutorSettings,
|
||||
}
|
||||
|
||||
impl GlobalState {
|
||||
pub fn new(
|
||||
state: exec_state::ExecState,
|
||||
settings: ExecutorSettings,
|
||||
ast: Node<Program>,
|
||||
result_env: EnvironmentRef,
|
||||
) -> Self {
|
||||
Self {
|
||||
main: ModuleState {
|
||||
ast,
|
||||
exec_state: state.mod_local,
|
||||
result_env,
|
||||
},
|
||||
exec_state: state.global,
|
||||
settings,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_settings(mut self, settings: ExecutorSettings) -> GlobalState {
|
||||
self.settings = settings;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn reconstitute_exec_state(&self) -> exec_state::ExecState {
|
||||
exec_state::ExecState {
|
||||
global: self.exec_state.clone(),
|
||||
mod_local: self.main.exec_state.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn into_exec_outcome(self, ctx: &ExecutorContext) -> ExecOutcome {
|
||||
// Fields are opt-in so that we don't accidentally leak private internal
|
||||
// state when we add more to ExecState.
|
||||
ExecOutcome {
|
||||
variables: self.main.exec_state.variables(self.main.result_env),
|
||||
filenames: self.exec_state.filenames(),
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
operations: self.exec_state.artifacts.operations,
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
artifact_commands: self.exec_state.artifacts.commands,
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
artifact_graph: self.exec_state.artifacts.graph,
|
||||
errors: self.exec_state.errors,
|
||||
default_planes: ctx.engine.get_default_planes().read().await.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Per-module cached state
|
||||
#[derive(Debug, Clone)]
|
||||
pub(super) struct ModuleState {
|
||||
/// The AST of the module.
|
||||
pub(super) ast: Node<Program>,
|
||||
/// The ExecState of the module.
|
||||
pub(super) exec_state: exec_state::ModuleState,
|
||||
/// The memory env for the module.
|
||||
pub(super) result_env: EnvironmentRef,
|
||||
}
|
||||
|
||||
/// The result of a cache check.
|
||||
@ -79,9 +142,6 @@ pub(super) enum CacheResult {
|
||||
reapply_settings: bool,
|
||||
/// The program that needs to be executed.
|
||||
program: Node<Program>,
|
||||
/// The number of body items that were cached and omitted from the
|
||||
/// program that needs to be executed. Used to compute [`crate::NodePath`].
|
||||
cached_body_items: usize,
|
||||
},
|
||||
/// Check only the imports, and not the main program.
|
||||
/// Before sending this we already checked the main program and it is the same.
|
||||
@ -146,7 +206,6 @@ pub(super) async fn get_changed_program(old: CacheInformation<'_>, new: CacheInf
|
||||
// We know they have the same imports because the ast is the same.
|
||||
// If we have no imports, we can skip this.
|
||||
if !old.ast.has_import_statements() {
|
||||
println!("No imports, no need to check.");
|
||||
return CacheResult::NoAction(reapply_settings);
|
||||
}
|
||||
|
||||
@ -194,7 +253,6 @@ pub(super) async fn get_changed_program(old: CacheInformation<'_>, new: CacheInf
|
||||
clear_scene: true,
|
||||
reapply_settings: true,
|
||||
program: new.ast.clone(),
|
||||
cached_body_items: 0,
|
||||
};
|
||||
}
|
||||
|
||||
@ -223,7 +281,6 @@ fn generate_changed_program(old_ast: Node<Program>, mut new_ast: Node<Program>,
|
||||
clear_scene: true,
|
||||
reapply_settings,
|
||||
program: new_ast,
|
||||
cached_body_items: 0,
|
||||
};
|
||||
}
|
||||
|
||||
@ -244,7 +301,6 @@ fn generate_changed_program(old_ast: Node<Program>, mut new_ast: Node<Program>,
|
||||
clear_scene: true,
|
||||
reapply_settings,
|
||||
program: new_ast,
|
||||
cached_body_items: 0,
|
||||
}
|
||||
}
|
||||
std::cmp::Ordering::Greater => {
|
||||
@ -261,7 +317,6 @@ fn generate_changed_program(old_ast: Node<Program>, mut new_ast: Node<Program>,
|
||||
clear_scene: false,
|
||||
reapply_settings,
|
||||
program: new_ast,
|
||||
cached_body_items: old_ast.body.len(),
|
||||
}
|
||||
}
|
||||
std::cmp::Ordering::Equal => {
|
||||
@ -600,7 +655,6 @@ startSketchOn(XY)
|
||||
clear_scene: true,
|
||||
reapply_settings: true,
|
||||
program: new_program.ast,
|
||||
cached_body_items: 0,
|
||||
}
|
||||
);
|
||||
}
|
||||
@ -639,7 +693,6 @@ startSketchOn(XY)
|
||||
clear_scene: true,
|
||||
reapply_settings: true,
|
||||
program: new_program.ast,
|
||||
cached_body_items: 0,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
@ -131,7 +131,7 @@ impl ExecutorContext {
|
||||
match statement {
|
||||
BodyItem::ImportStatement(import_stmt) => {
|
||||
if !matches!(body_type, BodyType::Root) {
|
||||
return Err(KclError::Semantic(KclErrorDetails::new(
|
||||
return Err(KclError::new_semantic(KclErrorDetails::new(
|
||||
"Imports are only supported at the top-level of a file.".to_owned(),
|
||||
vec![import_stmt.into()],
|
||||
)));
|
||||
@ -164,15 +164,18 @@ impl ExecutorContext {
|
||||
let mut mod_value = mem.get_from(&mod_name, env_ref, import_item.into(), 0).cloned();
|
||||
|
||||
if value.is_err() && ty.is_err() && mod_value.is_err() {
|
||||
return Err(KclError::UndefinedValue(KclErrorDetails::new(
|
||||
format!("{} is not defined in module", import_item.name.name),
|
||||
vec![SourceRange::from(&import_item.name)],
|
||||
)));
|
||||
return Err(KclError::new_undefined_value(
|
||||
KclErrorDetails::new(
|
||||
format!("{} is not defined in module", import_item.name.name),
|
||||
vec![SourceRange::from(&import_item.name)],
|
||||
),
|
||||
None,
|
||||
));
|
||||
}
|
||||
|
||||
// Check that the item is allowed to be imported (in at least one namespace).
|
||||
if value.is_ok() && !module_exports.contains(&import_item.name.name) {
|
||||
value = Err(KclError::Semantic(KclErrorDetails::new(
|
||||
value = Err(KclError::new_semantic(KclErrorDetails::new(
|
||||
format!(
|
||||
"Cannot import \"{}\" from module because it is not exported. Add \"export\" before the definition to export it.",
|
||||
import_item.name.name
|
||||
@ -182,7 +185,7 @@ impl ExecutorContext {
|
||||
}
|
||||
|
||||
if ty.is_ok() && !module_exports.contains(&ty_name) {
|
||||
ty = Err(KclError::Semantic(KclErrorDetails::new(format!(
|
||||
ty = Err(KclError::new_semantic(KclErrorDetails::new(format!(
|
||||
"Cannot import \"{}\" from module because it is not exported. Add \"export\" before the definition to export it.",
|
||||
import_item.name.name
|
||||
),
|
||||
@ -190,7 +193,7 @@ impl ExecutorContext {
|
||||
}
|
||||
|
||||
if mod_value.is_ok() && !module_exports.contains(&mod_name) {
|
||||
mod_value = Err(KclError::Semantic(KclErrorDetails::new(format!(
|
||||
mod_value = Err(KclError::new_semantic(KclErrorDetails::new(format!(
|
||||
"Cannot import \"{}\" from module because it is not exported. Add \"export\" before the definition to export it.",
|
||||
import_item.name.name
|
||||
),
|
||||
@ -253,7 +256,7 @@ impl ExecutorContext {
|
||||
.memory
|
||||
.get_from(name, env_ref, source_range, 0)
|
||||
.map_err(|_err| {
|
||||
KclError::Internal(KclErrorDetails::new(
|
||||
KclError::new_internal(KclErrorDetails::new(
|
||||
format!("{} is not defined in module (but was exported?)", name),
|
||||
vec![source_range],
|
||||
))
|
||||
@ -301,7 +304,12 @@ impl ExecutorContext {
|
||||
|
||||
let annotations = &variable_declaration.outer_attrs;
|
||||
|
||||
let value = self
|
||||
// During the evaluation of the variable's RHS, set context that this is all happening inside a variable
|
||||
// declaration, for the given name. This helps improve user-facing error messages.
|
||||
let lhs = variable_declaration.inner.name().to_owned();
|
||||
let prev_being_declared = exec_state.mod_local.being_declared.clone();
|
||||
exec_state.mod_local.being_declared = Some(lhs);
|
||||
let rhs_result = self
|
||||
.execute_expr(
|
||||
&variable_declaration.declaration.init,
|
||||
exec_state,
|
||||
@ -309,10 +317,14 @@ impl ExecutorContext {
|
||||
annotations,
|
||||
StatementKind::Declaration { name: &var_name },
|
||||
)
|
||||
.await?;
|
||||
.await;
|
||||
// Declaration over, so unset this context.
|
||||
exec_state.mod_local.being_declared = prev_being_declared;
|
||||
let rhs = rhs_result?;
|
||||
|
||||
exec_state
|
||||
.mut_stack()
|
||||
.add(var_name.clone(), value.clone(), source_range)?;
|
||||
.add(var_name.clone(), rhs.clone(), source_range)?;
|
||||
|
||||
// Track exports.
|
||||
if let ItemVisibility::Export = variable_declaration.visibility {
|
||||
@ -326,7 +338,7 @@ impl ExecutorContext {
|
||||
}
|
||||
}
|
||||
// Variable declaration can be the return value of a module.
|
||||
last_expr = matches!(body_type, BodyType::Root).then_some(value);
|
||||
last_expr = matches!(body_type, BodyType::Root).then_some(rhs);
|
||||
}
|
||||
BodyItem::TypeDeclaration(ty) => {
|
||||
let metadata = Metadata::from(&**ty);
|
||||
@ -336,7 +348,7 @@ impl ExecutorContext {
|
||||
let std_path = match &exec_state.mod_local.path {
|
||||
ModulePath::Std { value } => value,
|
||||
ModulePath::Local { .. } | ModulePath::Main => {
|
||||
return Err(KclError::Semantic(KclErrorDetails::new(
|
||||
return Err(KclError::new_semantic(KclErrorDetails::new(
|
||||
"User-defined types are not yet supported.".to_owned(),
|
||||
vec![metadata.source_range],
|
||||
)));
|
||||
@ -352,7 +364,7 @@ impl ExecutorContext {
|
||||
.mut_stack()
|
||||
.add(name_in_mem.clone(), value, metadata.source_range)
|
||||
.map_err(|_| {
|
||||
KclError::Semantic(KclErrorDetails::new(
|
||||
KclError::new_semantic(KclErrorDetails::new(
|
||||
format!("Redefinition of type {}.", ty.name.name),
|
||||
vec![metadata.source_range],
|
||||
))
|
||||
@ -373,7 +385,7 @@ impl ExecutorContext {
|
||||
exec_state,
|
||||
metadata.source_range,
|
||||
)
|
||||
.map_err(|e| KclError::Semantic(e.into()))?,
|
||||
.map_err(|e| KclError::new_semantic(e.into()))?,
|
||||
),
|
||||
meta: vec![metadata],
|
||||
};
|
||||
@ -382,7 +394,7 @@ impl ExecutorContext {
|
||||
.mut_stack()
|
||||
.add(name_in_mem.clone(), value, metadata.source_range)
|
||||
.map_err(|_| {
|
||||
KclError::Semantic(KclErrorDetails::new(
|
||||
KclError::new_semantic(KclErrorDetails::new(
|
||||
format!("Redefinition of type {}.", ty.name.name),
|
||||
vec![metadata.source_range],
|
||||
))
|
||||
@ -393,7 +405,7 @@ impl ExecutorContext {
|
||||
}
|
||||
}
|
||||
None => {
|
||||
return Err(KclError::Semantic(KclErrorDetails::new(
|
||||
return Err(KclError::new_semantic(KclErrorDetails::new(
|
||||
"User-defined types are not yet supported.".to_owned(),
|
||||
vec![metadata.source_range],
|
||||
)))
|
||||
@ -407,7 +419,7 @@ impl ExecutorContext {
|
||||
let metadata = Metadata::from(return_statement);
|
||||
|
||||
if matches!(body_type, BodyType::Root) {
|
||||
return Err(KclError::Semantic(KclErrorDetails::new(
|
||||
return Err(KclError::new_semantic(KclErrorDetails::new(
|
||||
"Cannot return from outside a function.".to_owned(),
|
||||
vec![metadata.source_range],
|
||||
)));
|
||||
@ -426,7 +438,7 @@ impl ExecutorContext {
|
||||
.mut_stack()
|
||||
.add(memory::RETURN_NAME.to_owned(), value, metadata.source_range)
|
||||
.map_err(|_| {
|
||||
KclError::Semantic(KclErrorDetails::new(
|
||||
KclError::new_semantic(KclErrorDetails::new(
|
||||
"Multiple returns from a single function.".to_owned(),
|
||||
vec![metadata.source_range],
|
||||
))
|
||||
@ -531,7 +543,7 @@ impl ExecutorContext {
|
||||
*cache = Some((val, er, items.clone()));
|
||||
(er, items)
|
||||
}),
|
||||
ModuleRepr::Foreign(geom, _) => Err(KclError::Semantic(KclErrorDetails::new(
|
||||
ModuleRepr::Foreign(geom, _) => Err(KclError::new_semantic(KclErrorDetails::new(
|
||||
"Cannot import items from foreign modules".to_owned(),
|
||||
vec![geom.source_range],
|
||||
))),
|
||||
@ -605,12 +617,12 @@ impl ExecutorContext {
|
||||
exec_state.global.mod_loader.leave_module(path);
|
||||
|
||||
result.map_err(|err| {
|
||||
if let KclError::ImportCycle(_) = err {
|
||||
if let KclError::ImportCycle { .. } = err {
|
||||
// It was an import cycle. Keep the original message.
|
||||
err.override_source_ranges(vec![source_range])
|
||||
} else {
|
||||
// TODO would be great to have line/column for the underlying error here
|
||||
KclError::Semantic(KclErrorDetails::new(
|
||||
KclError::new_semantic(KclErrorDetails::new(
|
||||
format!(
|
||||
"Error loading imported file ({path}). Open it to view more details.\n {}",
|
||||
err.message()
|
||||
@ -635,7 +647,12 @@ impl ExecutorContext {
|
||||
Expr::Literal(literal) => KclValue::from_literal((**literal).clone(), exec_state),
|
||||
Expr::TagDeclarator(tag) => tag.execute(exec_state).await?,
|
||||
Expr::Name(name) => {
|
||||
let value = name.get_result(exec_state, self).await?.clone();
|
||||
let being_declared = exec_state.mod_local.being_declared.clone();
|
||||
let value = name
|
||||
.get_result(exec_state, self)
|
||||
.await
|
||||
.map_err(|e| var_in_own_ref_err(e, &being_declared))?
|
||||
.clone();
|
||||
if let KclValue::Module { value: module_id, meta } = value {
|
||||
self.exec_module_for_result(
|
||||
module_id,
|
||||
@ -677,7 +694,7 @@ impl ExecutorContext {
|
||||
meta: vec![metadata.to_owned()],
|
||||
}
|
||||
} else {
|
||||
return Err(KclError::Semantic(KclErrorDetails::new(
|
||||
return Err(KclError::new_semantic(KclErrorDetails::new(
|
||||
"Rust implementation of functions is restricted to the standard library".to_owned(),
|
||||
vec![metadata.source_range],
|
||||
)));
|
||||
@ -704,7 +721,7 @@ impl ExecutorContext {
|
||||
"you cannot declare variable {name} as %, because % can only be used in function calls"
|
||||
);
|
||||
|
||||
return Err(KclError::Semantic(KclErrorDetails::new(
|
||||
return Err(KclError::new_semantic(KclErrorDetails::new(
|
||||
message,
|
||||
vec![pipe_substitution.into()],
|
||||
)));
|
||||
@ -712,7 +729,7 @@ impl ExecutorContext {
|
||||
StatementKind::Expression => match exec_state.mod_local.pipe_value.clone() {
|
||||
Some(x) => x,
|
||||
None => {
|
||||
return Err(KclError::Semantic(KclErrorDetails::new(
|
||||
return Err(KclError::new_semantic(KclErrorDetails::new(
|
||||
"cannot use % outside a pipe expression".to_owned(),
|
||||
vec![pipe_substitution.into()],
|
||||
)));
|
||||
@ -741,6 +758,24 @@ impl ExecutorContext {
|
||||
}
|
||||
}
|
||||
|
||||
/// If the error is about an undefined name, and that name matches the name being defined,
|
||||
/// make the error message more specific.
|
||||
fn var_in_own_ref_err(e: KclError, being_declared: &Option<String>) -> KclError {
|
||||
let KclError::UndefinedValue { name, mut details } = e else {
|
||||
return e;
|
||||
};
|
||||
// TODO after June 26th: replace this with a let-chain,
|
||||
// which will be available in Rust 1.88
|
||||
// https://rust-lang.github.io/rfcs/2497-if-let-chains.html
|
||||
match (&being_declared, &name) {
|
||||
(Some(name0), Some(name1)) if name0 == name1 => {
|
||||
details.message = format!("You can't use `{name0}` because you're currently trying to define it. Use a different variable here instead.");
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
KclError::UndefinedValue { details, name }
|
||||
}
|
||||
|
||||
impl Node<AscribedExpression> {
|
||||
#[async_recursion]
|
||||
pub async fn get_result(&self, exec_state: &mut ExecState, ctx: &ExecutorContext) -> Result<KclValue, KclError> {
|
||||
@ -761,7 +796,7 @@ fn apply_ascription(
|
||||
source_range: SourceRange,
|
||||
) -> Result<KclValue, KclError> {
|
||||
let ty = RuntimeType::from_parsed(ty.inner.clone(), exec_state, value.into())
|
||||
.map_err(|e| KclError::Semantic(e.into()))?;
|
||||
.map_err(|e| KclError::new_semantic(e.into()))?;
|
||||
|
||||
value.coerce(&ty, false, exec_state).map_err(|_| {
|
||||
let suggestion = if ty == RuntimeType::length() {
|
||||
@ -771,7 +806,7 @@ fn apply_ascription(
|
||||
} else {
|
||||
""
|
||||
};
|
||||
KclError::Semantic(KclErrorDetails::new(
|
||||
KclError::new_semantic(KclErrorDetails::new(
|
||||
format!(
|
||||
"could not coerce value of type {} to type {ty}{suggestion}",
|
||||
value.human_friendly_type()
|
||||
@ -802,9 +837,20 @@ impl Node<Name> {
|
||||
&self,
|
||||
exec_state: &'a mut ExecState,
|
||||
ctx: &ExecutorContext,
|
||||
) -> Result<&'a KclValue, KclError> {
|
||||
let being_declared = exec_state.mod_local.being_declared.clone();
|
||||
self.get_result_inner(exec_state, ctx)
|
||||
.await
|
||||
.map_err(|e| var_in_own_ref_err(e, &being_declared))
|
||||
}
|
||||
|
||||
async fn get_result_inner<'a>(
|
||||
&self,
|
||||
exec_state: &'a mut ExecState,
|
||||
ctx: &ExecutorContext,
|
||||
) -> Result<&'a KclValue, KclError> {
|
||||
if self.abs_path {
|
||||
return Err(KclError::Semantic(KclErrorDetails::new(
|
||||
return Err(KclError::new_semantic(KclErrorDetails::new(
|
||||
"Absolute paths (names beginning with `::` are not yet supported)".to_owned(),
|
||||
self.as_source_ranges(),
|
||||
)));
|
||||
@ -825,7 +871,7 @@ impl Node<Name> {
|
||||
let value = match mem_spec {
|
||||
Some((env, exports)) => {
|
||||
if !exports.contains(&p.name) {
|
||||
return Err(KclError::Semantic(KclErrorDetails::new(
|
||||
return Err(KclError::new_semantic(KclErrorDetails::new(
|
||||
format!("Item {} not found in module's exported items", p.name),
|
||||
p.as_source_ranges(),
|
||||
)));
|
||||
@ -842,7 +888,7 @@ impl Node<Name> {
|
||||
};
|
||||
|
||||
let KclValue::Module { value: module_id, .. } = value else {
|
||||
return Err(KclError::Semantic(KclErrorDetails::new(
|
||||
return Err(KclError::new_semantic(KclErrorDetails::new(
|
||||
format!(
|
||||
"Identifier in path must refer to a module, found {}",
|
||||
value.human_friendly_type()
|
||||
@ -888,7 +934,7 @@ impl Node<Name> {
|
||||
|
||||
// Either item or module is defined, but not exported.
|
||||
debug_assert!((item_value.is_ok() && !item_exported) || (mod_value.is_ok() && !mod_exported));
|
||||
Err(KclError::Semantic(KclErrorDetails::new(
|
||||
Err(KclError::new_semantic(KclErrorDetails::new(
|
||||
format!("Item {} not found in module's exported items", self.name.name),
|
||||
self.name.as_source_ranges(),
|
||||
)))
|
||||
@ -913,14 +959,17 @@ impl Node<MemberExpression> {
|
||||
if let Some(value) = map.get(&property) {
|
||||
Ok(value.to_owned())
|
||||
} else {
|
||||
Err(KclError::UndefinedValue(KclErrorDetails::new(
|
||||
format!("Property '{property}' not found in object"),
|
||||
vec![self.clone().into()],
|
||||
)))
|
||||
Err(KclError::new_undefined_value(
|
||||
KclErrorDetails::new(
|
||||
format!("Property '{property}' not found in object"),
|
||||
vec![self.clone().into()],
|
||||
),
|
||||
None,
|
||||
))
|
||||
}
|
||||
}
|
||||
(KclValue::Object { .. }, Property::String(property), true) => {
|
||||
Err(KclError::Semantic(KclErrorDetails::new(
|
||||
Err(KclError::new_semantic(KclErrorDetails::new(
|
||||
format!("Cannot index object with string; use dot notation instead, e.g. `obj.{property}`"),
|
||||
vec![self.clone().into()],
|
||||
)))
|
||||
@ -928,7 +977,7 @@ impl Node<MemberExpression> {
|
||||
(KclValue::Object { .. }, p, _) => {
|
||||
let t = p.type_name();
|
||||
let article = article_for(t);
|
||||
Err(KclError::Semantic(KclErrorDetails::new(
|
||||
Err(KclError::new_semantic(KclErrorDetails::new(
|
||||
format!("Only strings can be used as the property of an object, but you're using {article} {t}",),
|
||||
vec![self.clone().into()],
|
||||
)))
|
||||
@ -938,10 +987,13 @@ impl Node<MemberExpression> {
|
||||
if let Some(value) = value_of_arr {
|
||||
Ok(value.to_owned())
|
||||
} else {
|
||||
Err(KclError::UndefinedValue(KclErrorDetails::new(
|
||||
format!("The array doesn't have any item at index {index}"),
|
||||
vec![self.clone().into()],
|
||||
)))
|
||||
Err(KclError::new_undefined_value(
|
||||
KclErrorDetails::new(
|
||||
format!("The array doesn't have any item at index {index}"),
|
||||
vec![self.clone().into()],
|
||||
),
|
||||
None,
|
||||
))
|
||||
}
|
||||
}
|
||||
// Singletons and single-element arrays should be interchangeable, but only indexing by 0 should work.
|
||||
@ -950,7 +1002,7 @@ impl Node<MemberExpression> {
|
||||
(KclValue::HomArray { .. }, p, _) => {
|
||||
let t = p.type_name();
|
||||
let article = article_for(t);
|
||||
Err(KclError::Semantic(KclErrorDetails::new(
|
||||
Err(KclError::new_semantic(KclErrorDetails::new(
|
||||
format!("Only integers >= 0 can be used as the index of an array, but you're using {article} {t}",),
|
||||
vec![self.clone().into()],
|
||||
)))
|
||||
@ -971,7 +1023,7 @@ impl Node<MemberExpression> {
|
||||
(being_indexed, _, _) => {
|
||||
let t = being_indexed.human_friendly_type();
|
||||
let article = article_for(&t);
|
||||
Err(KclError::Semantic(KclErrorDetails::new(
|
||||
Err(KclError::new_semantic(KclErrorDetails::new(
|
||||
format!("Only arrays can be indexed, but you're trying to index {article} {t}"),
|
||||
vec![self.clone().into()],
|
||||
)))
|
||||
@ -1049,7 +1101,7 @@ impl Node<BinaryExpression> {
|
||||
meta: _,
|
||||
} = left_value
|
||||
else {
|
||||
return Err(KclError::Semantic(KclErrorDetails::new(
|
||||
return Err(KclError::new_semantic(KclErrorDetails::new(
|
||||
format!(
|
||||
"Cannot apply logical operator to non-boolean value: {}",
|
||||
left_value.human_friendly_type()
|
||||
@ -1062,7 +1114,7 @@ impl Node<BinaryExpression> {
|
||||
meta: _,
|
||||
} = right_value
|
||||
else {
|
||||
return Err(KclError::Semantic(KclErrorDetails::new(
|
||||
return Err(KclError::new_semantic(KclErrorDetails::new(
|
||||
format!(
|
||||
"Cannot apply logical operator to non-boolean value: {}",
|
||||
right_value.human_friendly_type()
|
||||
@ -1168,7 +1220,7 @@ impl Node<UnaryExpression> {
|
||||
meta: _,
|
||||
} = value
|
||||
else {
|
||||
return Err(KclError::Semantic(KclErrorDetails::new(
|
||||
return Err(KclError::new_semantic(KclErrorDetails::new(
|
||||
format!(
|
||||
"Cannot apply unary operator ! to non-boolean value: {}",
|
||||
value.human_friendly_type()
|
||||
@ -1189,7 +1241,7 @@ impl Node<UnaryExpression> {
|
||||
|
||||
let value = &self.argument.get_result(exec_state, ctx).await?;
|
||||
let err = || {
|
||||
KclError::Semantic(KclErrorDetails::new(
|
||||
KclError::new_semantic(KclErrorDetails::new(
|
||||
format!(
|
||||
"You can only negate numbers, planes, or lines, but this is a {}",
|
||||
value.human_friendly_type()
|
||||
@ -1292,7 +1344,7 @@ pub(crate) async fn execute_pipe_body(
|
||||
ctx: &ExecutorContext,
|
||||
) -> Result<KclValue, KclError> {
|
||||
let Some((first, body)) = body.split_first() else {
|
||||
return Err(KclError::Semantic(KclErrorDetails::new(
|
||||
return Err(KclError::new_semantic(KclErrorDetails::new(
|
||||
"Pipe expressions cannot be empty".to_owned(),
|
||||
vec![source_range],
|
||||
)));
|
||||
@ -1311,7 +1363,7 @@ pub(crate) async fn execute_pipe_body(
|
||||
// Now that we've evaluated the first child expression in the pipeline, following child expressions
|
||||
// should use the previous child expression for %.
|
||||
// This means there's no more need for the previous pipe_value from the parent AST node above this one.
|
||||
let previous_pipe_value = std::mem::replace(&mut exec_state.mod_local.pipe_value, Some(output));
|
||||
let previous_pipe_value = exec_state.mod_local.pipe_value.replace(output);
|
||||
// Evaluate remaining elements.
|
||||
let result = inner_execute_pipe_body(exec_state, body, ctx).await;
|
||||
// Restore the previous pipe value.
|
||||
@ -1330,7 +1382,7 @@ async fn inner_execute_pipe_body(
|
||||
) -> Result<KclValue, KclError> {
|
||||
for expression in body {
|
||||
if let Expr::TagDeclarator(_) = expression {
|
||||
return Err(KclError::Semantic(KclErrorDetails::new(
|
||||
return Err(KclError::new_semantic(KclErrorDetails::new(
|
||||
format!("This cannot be in a PipeExpression: {:?}", expression),
|
||||
vec![expression.into()],
|
||||
)));
|
||||
@ -1404,7 +1456,7 @@ impl Node<ArrayRangeExpression> {
|
||||
.await?;
|
||||
let (start, start_ty) = start_val
|
||||
.as_int_with_ty()
|
||||
.ok_or(KclError::Semantic(KclErrorDetails::new(
|
||||
.ok_or(KclError::new_semantic(KclErrorDetails::new(
|
||||
format!("Expected int but found {}", start_val.human_friendly_type()),
|
||||
vec![self.into()],
|
||||
)))?;
|
||||
@ -1412,24 +1464,26 @@ impl Node<ArrayRangeExpression> {
|
||||
let end_val = ctx
|
||||
.execute_expr(&self.end_element, exec_state, &metadata, &[], StatementKind::Expression)
|
||||
.await?;
|
||||
let (end, end_ty) = end_val.as_int_with_ty().ok_or(KclError::Semantic(KclErrorDetails::new(
|
||||
format!("Expected int but found {}", end_val.human_friendly_type()),
|
||||
vec![self.into()],
|
||||
)))?;
|
||||
let (end, end_ty) = end_val
|
||||
.as_int_with_ty()
|
||||
.ok_or(KclError::new_semantic(KclErrorDetails::new(
|
||||
format!("Expected int but found {}", end_val.human_friendly_type()),
|
||||
vec![self.into()],
|
||||
)))?;
|
||||
|
||||
if start_ty != end_ty {
|
||||
let start = start_val.as_ty_f64().unwrap_or(TyF64 { n: 0.0, ty: start_ty });
|
||||
let start = fmt::human_display_number(start.n, start.ty);
|
||||
let end = end_val.as_ty_f64().unwrap_or(TyF64 { n: 0.0, ty: end_ty });
|
||||
let end = fmt::human_display_number(end.n, end.ty);
|
||||
return Err(KclError::Semantic(KclErrorDetails::new(
|
||||
return Err(KclError::new_semantic(KclErrorDetails::new(
|
||||
format!("Range start and end must be of the same type, but found {start} and {end}"),
|
||||
vec![self.into()],
|
||||
)));
|
||||
}
|
||||
|
||||
if end < start {
|
||||
return Err(KclError::Semantic(KclErrorDetails::new(
|
||||
return Err(KclError::new_semantic(KclErrorDetails::new(
|
||||
format!("Range start is greater than range end: {start} .. {end}"),
|
||||
vec![self.into()],
|
||||
)));
|
||||
@ -1493,7 +1547,7 @@ fn article_for<S: AsRef<str>>(s: S) -> &'static str {
|
||||
fn number_as_f64(v: &KclValue, source_range: SourceRange) -> Result<TyF64, KclError> {
|
||||
v.as_ty_f64().ok_or_else(|| {
|
||||
let actual_type = v.human_friendly_type();
|
||||
KclError::Semantic(KclErrorDetails::new(
|
||||
KclError::new_semantic(KclErrorDetails::new(
|
||||
format!("Expected a number, but found {actual_type}",),
|
||||
vec![source_range],
|
||||
))
|
||||
@ -1585,13 +1639,13 @@ impl Property {
|
||||
if let Some(x) = crate::try_f64_to_usize(value) {
|
||||
Ok(Property::UInt(x))
|
||||
} else {
|
||||
Err(KclError::Semantic(KclErrorDetails::new(
|
||||
Err(KclError::new_semantic(KclErrorDetails::new(
|
||||
format!("{value} is not a valid index, indices must be whole numbers >= 0"),
|
||||
property_sr,
|
||||
)))
|
||||
}
|
||||
}
|
||||
_ => Err(KclError::Semantic(KclErrorDetails::new(
|
||||
_ => Err(KclError::new_semantic(KclErrorDetails::new(
|
||||
"Only numbers (>= 0) can be indexes".to_owned(),
|
||||
vec![sr],
|
||||
))),
|
||||
@ -1602,7 +1656,8 @@ impl Property {
|
||||
}
|
||||
|
||||
fn jvalue_to_prop(value: &KclValue, property_sr: Vec<SourceRange>, name: &str) -> Result<Property, KclError> {
|
||||
let make_err = |message: String| Err::<Property, _>(KclError::Semantic(KclErrorDetails::new(message, property_sr)));
|
||||
let make_err =
|
||||
|message: String| Err::<Property, _>(KclError::new_semantic(KclErrorDetails::new(message, property_sr)));
|
||||
match value {
|
||||
KclValue::Number{value: num, .. } => {
|
||||
let num = *num;
|
||||
@ -1846,7 +1901,7 @@ d = b + c
|
||||
crate::engine::conn_mock::EngineConnection::new()
|
||||
.await
|
||||
.map_err(|err| {
|
||||
KclError::Internal(KclErrorDetails::new(
|
||||
KclError::new_internal(KclErrorDetails::new(
|
||||
format!("Failed to create mock engine connection: {}", err),
|
||||
vec![SourceRange::default()],
|
||||
))
|
||||
@ -1858,7 +1913,6 @@ d = b + c
|
||||
project_directory: Some(crate::TypedPath(tmpdir.path().into())),
|
||||
..Default::default()
|
||||
},
|
||||
stdlib: Arc::new(crate::std::StdLib::new()),
|
||||
context_type: ContextType::Mock,
|
||||
};
|
||||
let mut exec_state = ExecState::new(&exec_ctxt);
|
||||
|
@ -2,7 +2,6 @@ use async_recursion::async_recursion;
|
||||
use indexmap::IndexMap;
|
||||
|
||||
use crate::{
|
||||
docs::StdLibFn,
|
||||
errors::{KclError, KclErrorDetails},
|
||||
execution::{
|
||||
cad_op::{Group, OpArg, OpKclValue, Operation},
|
||||
@ -184,40 +183,6 @@ impl<'a> From<&'a FunctionSource> for FunctionDefinition<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&dyn StdLibFn> for FunctionDefinition<'static> {
|
||||
fn from(value: &dyn StdLibFn) -> Self {
|
||||
let mut input_arg = None;
|
||||
let mut named_args = IndexMap::new();
|
||||
for a in value.args(false) {
|
||||
if !a.label_required {
|
||||
input_arg = Some((a.name.clone(), None));
|
||||
continue;
|
||||
}
|
||||
|
||||
named_args.insert(
|
||||
a.name.clone(),
|
||||
(
|
||||
if a.required {
|
||||
None
|
||||
} else {
|
||||
Some(DefaultParamVal::none())
|
||||
},
|
||||
None,
|
||||
),
|
||||
);
|
||||
}
|
||||
FunctionDefinition {
|
||||
input_arg,
|
||||
named_args,
|
||||
return_type: None,
|
||||
deprecated: value.deprecated(),
|
||||
include_in_feature_tree: value.feature_tree_operation(),
|
||||
is_std: true,
|
||||
body: FunctionBody::Rust(value.std_lib_fn()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Node<CallExpressionKw> {
|
||||
#[async_recursion]
|
||||
pub async fn execute(&self, exec_state: &mut ExecState, ctx: &ExecutorContext) -> Result<KclValue, KclError> {
|
||||
@ -274,59 +239,44 @@ impl Node<CallExpressionKw> {
|
||||
exec_state.pipe_value().map(|v| Arg::new(v.clone(), callsite)),
|
||||
);
|
||||
|
||||
match ctx.stdlib.get_rust_function(fn_name) {
|
||||
Some(func) => {
|
||||
let def: FunctionDefinition = (&*func).into();
|
||||
// All std lib functions return a value, so the unwrap is safe.
|
||||
def.call_kw(Some(func.name()), exec_state, ctx, args, callsite)
|
||||
.await
|
||||
.map(Option::unwrap)
|
||||
.map_err(|e| {
|
||||
// This is used for the backtrace display. We don't add
|
||||
// another location the way we do for user-defined
|
||||
// functions because the error uses the Args, which
|
||||
// already points here.
|
||||
e.set_last_backtrace_fn_name(Some(func.name()))
|
||||
})
|
||||
}
|
||||
None => {
|
||||
// Clone the function so that we can use a mutable reference to
|
||||
// exec_state.
|
||||
let func = fn_name.get_result(exec_state, ctx).await?.clone();
|
||||
// Clone the function so that we can use a mutable reference to
|
||||
// exec_state.
|
||||
let func = fn_name.get_result(exec_state, ctx).await?.clone();
|
||||
|
||||
let Some(fn_src) = func.as_function() else {
|
||||
return Err(KclError::Semantic(KclErrorDetails::new(
|
||||
"cannot call this because it isn't a function".to_string(),
|
||||
vec![callsite],
|
||||
)));
|
||||
};
|
||||
let Some(fn_src) = func.as_function() else {
|
||||
return Err(KclError::new_semantic(KclErrorDetails::new(
|
||||
"cannot call this because it isn't a function".to_string(),
|
||||
vec![callsite],
|
||||
)));
|
||||
};
|
||||
|
||||
let return_value = fn_src
|
||||
.call_kw(Some(fn_name.to_string()), exec_state, ctx, args, callsite)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
// Add the call expression to the source ranges.
|
||||
//
|
||||
// TODO: Use the name that the function was defined
|
||||
// with, not the identifier it was used with.
|
||||
e.add_unwind_location(Some(fn_name.name.name.clone()), callsite)
|
||||
})?;
|
||||
let return_value = fn_src
|
||||
.call_kw(Some(fn_name.to_string()), exec_state, ctx, args, callsite)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
// Add the call expression to the source ranges.
|
||||
//
|
||||
// TODO: Use the name that the function was defined
|
||||
// with, not the identifier it was used with.
|
||||
e.add_unwind_location(Some(fn_name.name.name.clone()), callsite)
|
||||
})?;
|
||||
|
||||
let result = return_value.ok_or_else(move || {
|
||||
let mut source_ranges: Vec<SourceRange> = vec![callsite];
|
||||
// We want to send the source range of the original function.
|
||||
if let KclValue::Function { meta, .. } = func {
|
||||
source_ranges = meta.iter().map(|m| m.source_range).collect();
|
||||
};
|
||||
KclError::UndefinedValue(KclErrorDetails::new(
|
||||
format!("Result of user-defined function {} is undefined", fn_name),
|
||||
source_ranges,
|
||||
))
|
||||
})?;
|
||||
let result = return_value.ok_or_else(move || {
|
||||
let mut source_ranges: Vec<SourceRange> = vec![callsite];
|
||||
// We want to send the source range of the original function.
|
||||
if let KclValue::Function { meta, .. } = func {
|
||||
source_ranges = meta.iter().map(|m| m.source_range).collect();
|
||||
};
|
||||
KclError::new_undefined_value(
|
||||
KclErrorDetails::new(
|
||||
format!("Result of user-defined function {} is undefined", fn_name),
|
||||
source_ranges,
|
||||
),
|
||||
None,
|
||||
)
|
||||
})?;
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
|
||||
@ -500,7 +450,7 @@ fn update_memory_for_tags_of_geometry(result: &mut KclValue, exec_state: &mut Ex
|
||||
let tag_id = if let Some(t) = value.sketch.tags.get(&tag.name) {
|
||||
let mut t = t.clone();
|
||||
let Some(info) = t.get_cur_info() else {
|
||||
return Err(KclError::Internal(KclErrorDetails::new(
|
||||
return Err(KclError::new_internal(KclErrorDetails::new(
|
||||
format!("Tag {} does not have path info", tag.name),
|
||||
vec![tag.into()],
|
||||
)));
|
||||
@ -600,30 +550,33 @@ fn type_check_params_kw(
|
||||
|
||||
for (label, arg) in &mut args.labeled {
|
||||
match fn_def.named_args.get(label) {
|
||||
Some((_, ty)) => {
|
||||
if let Some(ty) = ty {
|
||||
arg.value = arg
|
||||
.value
|
||||
.coerce(
|
||||
&RuntimeType::from_parsed(ty.clone(), exec_state, arg.source_range).map_err(|e| KclError::Semantic(e.into()))?,
|
||||
true,
|
||||
exec_state,
|
||||
)
|
||||
.map_err(|e| {
|
||||
let mut message = format!(
|
||||
"{label} requires a value with type `{}`, but found {}",
|
||||
ty,
|
||||
arg.value.human_friendly_type(),
|
||||
);
|
||||
if let Some(ty) = e.explicit_coercion {
|
||||
// TODO if we have access to the AST for the argument we could choose which example to suggest.
|
||||
message = format!("{message}\n\nYou may need to add information about the type of the argument, for example:\n using a numeric suffix: `42{ty}`\n or using type ascription: `foo(): number({ty})`");
|
||||
}
|
||||
KclError::Semantic(KclErrorDetails::new(
|
||||
message,
|
||||
vec![arg.source_range],
|
||||
))
|
||||
})?;
|
||||
Some((def, ty)) => {
|
||||
// For optional args, passing None should be the same as not passing an arg.
|
||||
if !(def.is_some() && matches!(arg.value, KclValue::KclNone { .. })) {
|
||||
if let Some(ty) = ty {
|
||||
arg.value = arg
|
||||
.value
|
||||
.coerce(
|
||||
&RuntimeType::from_parsed(ty.clone(), exec_state, arg.source_range).map_err(|e| KclError::new_semantic(e.into()))?,
|
||||
true,
|
||||
exec_state,
|
||||
)
|
||||
.map_err(|e| {
|
||||
let mut message = format!(
|
||||
"{label} requires a value with type `{}`, but found {}",
|
||||
ty,
|
||||
arg.value.human_friendly_type(),
|
||||
);
|
||||
if let Some(ty) = e.explicit_coercion {
|
||||
// TODO if we have access to the AST for the argument we could choose which example to suggest.
|
||||
message = format!("{message}\n\nYou may need to add information about the type of the argument, for example:\n using a numeric suffix: `42{ty}`\n or using type ascription: `foo(): number({ty})`");
|
||||
}
|
||||
KclError::new_semantic(KclErrorDetails::new(
|
||||
message,
|
||||
vec![arg.source_range],
|
||||
))
|
||||
})?;
|
||||
}
|
||||
}
|
||||
}
|
||||
None => {
|
||||
@ -670,7 +623,7 @@ fn type_check_params_kw(
|
||||
let first = errors.next().unwrap();
|
||||
errors.for_each(|e| exec_state.err(e));
|
||||
|
||||
return Err(KclError::Semantic(first.into()));
|
||||
return Err(KclError::new_semantic(first.into()));
|
||||
}
|
||||
|
||||
if let Some(arg) = &mut args.unlabeled {
|
||||
@ -680,12 +633,12 @@ fn type_check_params_kw(
|
||||
.value
|
||||
.coerce(
|
||||
&RuntimeType::from_parsed(ty.clone(), exec_state, arg.1.source_range)
|
||||
.map_err(|e| KclError::Semantic(e.into()))?,
|
||||
.map_err(|e| KclError::new_semantic(e.into()))?,
|
||||
true,
|
||||
exec_state,
|
||||
)
|
||||
.map_err(|_| {
|
||||
KclError::Semantic(KclErrorDetails::new(
|
||||
KclError::new_semantic(KclErrorDetails::new(
|
||||
format!(
|
||||
"The input argument of {} requires a value with type `{}`, but found {}",
|
||||
fn_name
|
||||
@ -703,7 +656,7 @@ fn type_check_params_kw(
|
||||
exec_state.err(CompilationError::err(
|
||||
arg.source_range,
|
||||
format!(
|
||||
"{} expects an unlabeled first parameter (`@{name}`), but it is labelled in the call",
|
||||
"{} expects an unlabeled first argument (`@{name}`), but it is labelled in the call",
|
||||
fn_name
|
||||
.map(|n| format!("The function `{}`", n))
|
||||
.unwrap_or_else(|| "This function".to_owned()),
|
||||
@ -742,7 +695,7 @@ fn assign_args_to_params_kw(
|
||||
.add(name.clone(), value, default_val.source_range())?;
|
||||
}
|
||||
None => {
|
||||
return Err(KclError::Semantic(KclErrorDetails::new(
|
||||
return Err(KclError::new_semantic(KclErrorDetails::new(
|
||||
format!(
|
||||
"This function requires a parameter {}, but you haven't passed it one.",
|
||||
name
|
||||
@ -759,12 +712,12 @@ fn assign_args_to_params_kw(
|
||||
|
||||
let Some(unlabeled) = unlabelled else {
|
||||
return Err(if args.kw_args.labeled.contains_key(param_name) {
|
||||
KclError::Semantic(KclErrorDetails::new(
|
||||
KclError::new_semantic(KclErrorDetails::new(
|
||||
format!("The function does declare a parameter named '{param_name}', but this parameter doesn't use a label. Try removing the `{param_name}:`"),
|
||||
source_ranges,
|
||||
))
|
||||
} else {
|
||||
KclError::Semantic(KclErrorDetails::new(
|
||||
KclError::new_semantic(KclErrorDetails::new(
|
||||
"This function expects an unlabeled first parameter, but you haven't passed it one.".to_owned(),
|
||||
source_ranges,
|
||||
))
|
||||
@ -788,9 +741,9 @@ fn coerce_result_type(
|
||||
if let Ok(Some(val)) = result {
|
||||
if let Some(ret_ty) = &fn_def.return_type {
|
||||
let ty = RuntimeType::from_parsed(ret_ty.inner.clone(), exec_state, ret_ty.as_source_range())
|
||||
.map_err(|e| KclError::Semantic(e.into()))?;
|
||||
.map_err(|e| KclError::new_semantic(e.into()))?;
|
||||
let val = val.coerce(&ty, true, exec_state).map_err(|_| {
|
||||
KclError::Semantic(KclErrorDetails::new(
|
||||
KclError::new_semantic(KclErrorDetails::new(
|
||||
format!(
|
||||
"This function requires its result to be of type `{}`, but found {}",
|
||||
ty.human_friendly_type(),
|
||||
@ -874,7 +827,7 @@ mod test {
|
||||
"all params required, none given, should error",
|
||||
vec![req_param("x")],
|
||||
vec![],
|
||||
Err(KclError::Semantic(KclErrorDetails::new(
|
||||
Err(KclError::new_semantic(KclErrorDetails::new(
|
||||
"This function requires a parameter x, but you haven't passed it one.".to_owned(),
|
||||
vec![SourceRange::default()],
|
||||
))),
|
||||
@ -889,7 +842,7 @@ mod test {
|
||||
"mixed params, too few given",
|
||||
vec![req_param("x"), opt_param("y")],
|
||||
vec![],
|
||||
Err(KclError::Semantic(KclErrorDetails::new(
|
||||
Err(KclError::new_semantic(KclErrorDetails::new(
|
||||
"This function requires a parameter x, but you haven't passed it one.".to_owned(),
|
||||
vec![SourceRange::default()],
|
||||
))),
|
||||
@ -937,7 +890,6 @@ mod test {
|
||||
crate::engine::conn_mock::EngineConnection::new().await.unwrap(),
|
||||
)),
|
||||
fs: Arc::new(crate::fs::FileManager::new()),
|
||||
stdlib: Arc::new(crate::std::StdLib::new()),
|
||||
settings: Default::default(),
|
||||
context_type: ContextType::Mock,
|
||||
};
|
||||
|
@ -24,6 +24,7 @@ type Point3D = kcmc::shared::Point3d<f64>;
|
||||
#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
||||
#[ts(export)]
|
||||
#[serde(tag = "type")]
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
pub enum Geometry {
|
||||
Sketch(Sketch),
|
||||
Solid(Solid),
|
||||
@ -52,6 +53,7 @@ impl Geometry {
|
||||
#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
||||
#[ts(export)]
|
||||
#[serde(tag = "type")]
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
pub enum GeometryWithImportedGeometry {
|
||||
Sketch(Sketch),
|
||||
Solid(Solid),
|
||||
@ -469,7 +471,7 @@ impl TryFrom<PlaneData> for PlaneInfo {
|
||||
PlaneData::NegYZ => PlaneName::NegYz,
|
||||
PlaneData::Plane(_) => {
|
||||
// We will never get here since we already checked for PlaneData::Plane.
|
||||
return Err(KclError::Internal(KclErrorDetails::new(
|
||||
return Err(KclError::new_internal(KclErrorDetails::new(
|
||||
format!("PlaneData {:?} not found", value),
|
||||
Default::default(),
|
||||
)));
|
||||
@ -477,7 +479,7 @@ impl TryFrom<PlaneData> for PlaneInfo {
|
||||
};
|
||||
|
||||
let info = DEFAULT_PLANE_INFO.get(&name).ok_or_else(|| {
|
||||
KclError::Internal(KclErrorDetails::new(
|
||||
KclError::new_internal(KclErrorDetails::new(
|
||||
format!("Plane {} not found", name),
|
||||
Default::default(),
|
||||
))
|
||||
|
@ -37,25 +37,25 @@ pub async fn import_foreign(
|
||||
) -> Result<PreImportedGeometry, KclError> {
|
||||
// Make sure the file exists.
|
||||
if !ctxt.fs.exists(file_path, source_range).await? {
|
||||
return Err(KclError::Semantic(KclErrorDetails::new(
|
||||
return Err(KclError::new_semantic(KclErrorDetails::new(
|
||||
format!("File `{}` does not exist.", file_path.display()),
|
||||
vec![source_range],
|
||||
)));
|
||||
}
|
||||
|
||||
let ext_format = get_import_format_from_extension(file_path.extension().ok_or_else(|| {
|
||||
KclError::Semantic(KclErrorDetails::new(
|
||||
KclError::new_semantic(KclErrorDetails::new(
|
||||
format!("No file extension found for `{}`", file_path.display()),
|
||||
vec![source_range],
|
||||
))
|
||||
})?)
|
||||
.map_err(|e| KclError::Semantic(KclErrorDetails::new(e.to_string(), vec![source_range])))?;
|
||||
.map_err(|e| KclError::new_semantic(KclErrorDetails::new(e.to_string(), vec![source_range])))?;
|
||||
|
||||
// Get the format type from the extension of the file.
|
||||
let format = if let Some(format) = format {
|
||||
// Validate the given format with the extension format.
|
||||
validate_extension_format(ext_format, format.clone())
|
||||
.map_err(|e| KclError::Semantic(KclErrorDetails::new(e.to_string(), vec![source_range])))?;
|
||||
.map_err(|e| KclError::new_semantic(KclErrorDetails::new(e.to_string(), vec![source_range])))?;
|
||||
format
|
||||
} else {
|
||||
ext_format
|
||||
@ -66,11 +66,11 @@ pub async fn import_foreign(
|
||||
.fs
|
||||
.read(file_path, source_range)
|
||||
.await
|
||||
.map_err(|e| KclError::Semantic(KclErrorDetails::new(e.to_string(), vec![source_range])))?;
|
||||
.map_err(|e| KclError::new_semantic(KclErrorDetails::new(e.to_string(), vec![source_range])))?;
|
||||
|
||||
// We want the file_path to be without the parent.
|
||||
let file_name = file_path.file_name().map(|p| p.to_string()).ok_or_else(|| {
|
||||
KclError::Semantic(KclErrorDetails::new(
|
||||
KclError::new_semantic(KclErrorDetails::new(
|
||||
format!("Could not get the file name from the path `{}`", file_path.display()),
|
||||
vec![source_range],
|
||||
))
|
||||
@ -87,7 +87,7 @@ pub async fn import_foreign(
|
||||
// file.
|
||||
if !file_contents.starts_with(b"glTF") {
|
||||
let json = gltf_json::Root::from_slice(&file_contents)
|
||||
.map_err(|e| KclError::Semantic(KclErrorDetails::new(e.to_string(), vec![source_range])))?;
|
||||
.map_err(|e| KclError::new_semantic(KclErrorDetails::new(e.to_string(), vec![source_range])))?;
|
||||
|
||||
// Read the gltf file and check if there is a bin file.
|
||||
for buffer in json.buffers.iter() {
|
||||
@ -95,16 +95,15 @@ pub async fn import_foreign(
|
||||
if !uri.starts_with("data:") {
|
||||
// We want this path relative to the file_path given.
|
||||
let bin_path = file_path.parent().map(|p| p.join(uri)).ok_or_else(|| {
|
||||
KclError::Semantic(KclErrorDetails::new(
|
||||
KclError::new_semantic(KclErrorDetails::new(
|
||||
format!("Could not get the parent path of the file `{}`", file_path.display()),
|
||||
vec![source_range],
|
||||
))
|
||||
})?;
|
||||
|
||||
let bin_contents =
|
||||
ctxt.fs.read(&bin_path, source_range).await.map_err(|e| {
|
||||
KclError::Semantic(KclErrorDetails::new(e.to_string(), vec![source_range]))
|
||||
})?;
|
||||
let bin_contents = ctxt.fs.read(&bin_path, source_range).await.map_err(|e| {
|
||||
KclError::new_semantic(KclErrorDetails::new(e.to_string(), vec![source_range]))
|
||||
})?;
|
||||
|
||||
import_files.push(ImportFile {
|
||||
path: uri.to_string(),
|
||||
@ -141,7 +140,7 @@ pub(super) fn format_from_annotations(
|
||||
if p.key.name == annotations::IMPORT_FORMAT {
|
||||
result = Some(
|
||||
get_import_format_from_extension(annotations::expect_ident(&p.value)?).map_err(|_| {
|
||||
KclError::Semantic(KclErrorDetails::new(
|
||||
KclError::new_semantic(KclErrorDetails::new(
|
||||
format!(
|
||||
"Unknown format for import, expected one of: {}",
|
||||
crate::IMPORT_FILE_EXTENSIONS.join(", ")
|
||||
@ -159,7 +158,7 @@ pub(super) fn format_from_annotations(
|
||||
path.extension()
|
||||
.and_then(|ext| get_import_format_from_extension(ext).ok())
|
||||
})
|
||||
.ok_or(KclError::Semantic(KclErrorDetails::new(
|
||||
.ok_or(KclError::new_semantic(KclErrorDetails::new(
|
||||
"Unknown or missing extension, and no specified format for imported file".to_owned(),
|
||||
vec![import_source_range],
|
||||
)))?;
|
||||
@ -174,7 +173,7 @@ pub(super) fn format_from_annotations(
|
||||
}
|
||||
annotations::IMPORT_FORMAT => {}
|
||||
_ => {
|
||||
return Err(KclError::Semantic(KclErrorDetails::new(
|
||||
return Err(KclError::new_semantic(KclErrorDetails::new(
|
||||
format!(
|
||||
"Unexpected annotation for import, expected one of: {}, {}, {}",
|
||||
annotations::IMPORT_FORMAT,
|
||||
@ -199,7 +198,7 @@ fn set_coords(fmt: &mut InputFormat3d, coords_str: &str, source_range: SourceRan
|
||||
}
|
||||
|
||||
let Some(coords) = coords else {
|
||||
return Err(KclError::Semantic(KclErrorDetails::new(
|
||||
return Err(KclError::new_semantic(KclErrorDetails::new(
|
||||
format!(
|
||||
"Unknown coordinate system: {coords_str}, expected one of: {}",
|
||||
annotations::IMPORT_COORDS_VALUES
|
||||
@ -217,7 +216,7 @@ fn set_coords(fmt: &mut InputFormat3d, coords_str: &str, source_range: SourceRan
|
||||
InputFormat3d::Ply(opts) => opts.coords = coords,
|
||||
InputFormat3d::Stl(opts) => opts.coords = coords,
|
||||
_ => {
|
||||
return Err(KclError::Semantic(KclErrorDetails::new(
|
||||
return Err(KclError::new_semantic(KclErrorDetails::new(
|
||||
format!(
|
||||
"`{}` option cannot be applied to the specified format",
|
||||
annotations::IMPORT_COORDS
|
||||
@ -238,7 +237,7 @@ fn set_length_unit(fmt: &mut InputFormat3d, units_str: &str, source_range: Sourc
|
||||
InputFormat3d::Ply(opts) => opts.units = units.into(),
|
||||
InputFormat3d::Stl(opts) => opts.units = units.into(),
|
||||
_ => {
|
||||
return Err(KclError::Semantic(KclErrorDetails::new(
|
||||
return Err(KclError::new_semantic(KclErrorDetails::new(
|
||||
format!(
|
||||
"`{}` option cannot be applied to the specified format",
|
||||
annotations::IMPORT_LENGTH_UNIT
|
||||
|
@ -31,7 +31,7 @@ pub(crate) type Universe = HashMap<String, DependencyInfo>;
|
||||
/// run concurrently. Each "stage" is blocking in this model, which will
|
||||
/// change in the future. Don't use this function widely, yet.
|
||||
#[allow(clippy::iter_over_hash_type)]
|
||||
pub fn import_graph(progs: &Universe, ctx: &ExecutorContext) -> Result<Vec<Vec<String>>, KclError> {
|
||||
pub(crate) fn import_graph(progs: &Universe, ctx: &ExecutorContext) -> Result<Vec<Vec<String>>, KclError> {
|
||||
let mut graph = Graph::new();
|
||||
|
||||
for (name, (_, _, path, repr)) in progs.iter() {
|
||||
@ -96,7 +96,7 @@ fn topsort(all_modules: &[&str], graph: Graph) -> Result<Vec<Vec<String>>, KclEr
|
||||
if stage_modules.is_empty() {
|
||||
waiting_modules.sort();
|
||||
|
||||
return Err(KclError::ImportCycle(KclErrorDetails::new(
|
||||
return Err(KclError::new_import_cycle(KclErrorDetails::new(
|
||||
format!("circular import of modules not allowed: {}", waiting_modules.join(", ")),
|
||||
// TODO: we can get the right import lines from the AST, but we don't
|
||||
vec![SourceRange::default()],
|
||||
@ -120,7 +120,7 @@ fn topsort(all_modules: &[&str], graph: Graph) -> Result<Vec<Vec<String>>, KclEr
|
||||
|
||||
type ImportDependencies = Vec<(String, AstNode<ImportStatement>, ModulePath)>;
|
||||
|
||||
pub(crate) fn import_dependencies(
|
||||
fn import_dependencies(
|
||||
path: &ModulePath,
|
||||
repr: &ModuleRepr,
|
||||
ctx: &ExecutorContext,
|
||||
@ -146,7 +146,7 @@ pub(crate) fn import_dependencies(
|
||||
// This is a bit of a hack, but it works for now.
|
||||
ret.lock()
|
||||
.map_err(|err| {
|
||||
KclError::Internal(KclErrorDetails::new(
|
||||
KclError::new_internal(KclErrorDetails::new(
|
||||
format!("Failed to lock mutex: {}", err),
|
||||
Default::default(),
|
||||
))
|
||||
@ -156,7 +156,7 @@ pub(crate) fn import_dependencies(
|
||||
ImportPath::Foreign { path } => {
|
||||
ret.lock()
|
||||
.map_err(|err| {
|
||||
KclError::Internal(KclErrorDetails::new(
|
||||
KclError::new_internal(KclErrorDetails::new(
|
||||
format!("Failed to lock mutex: {}", err),
|
||||
Default::default(),
|
||||
))
|
||||
@ -178,7 +178,7 @@ pub(crate) fn import_dependencies(
|
||||
walk(ret.clone(), prog.into(), path, ctx)?;
|
||||
|
||||
let ret = ret.lock().map_err(|err| {
|
||||
KclError::Internal(KclErrorDetails::new(
|
||||
KclError::new_internal(KclErrorDetails::new(
|
||||
format!("Failed to lock mutex: {}", err),
|
||||
Default::default(),
|
||||
))
|
||||
@ -223,7 +223,7 @@ pub(crate) async fn import_universe(
|
||||
|
||||
let repr = {
|
||||
let Some(module_info) = exec_state.get_module(module_id) else {
|
||||
return Err(KclError::Internal(KclErrorDetails::new(
|
||||
return Err(KclError::new_internal(KclErrorDetails::new(
|
||||
format!("Module {} not found", module_id),
|
||||
vec![import_stmt.into()],
|
||||
)));
|
@ -574,7 +574,7 @@ impl KclValue {
|
||||
pub fn get_tag_identifier(&self) -> Result<TagIdentifier, KclError> {
|
||||
match self {
|
||||
KclValue::TagIdentifier(t) => Ok(*t.clone()),
|
||||
_ => Err(KclError::Semantic(KclErrorDetails::new(
|
||||
_ => Err(KclError::new_semantic(KclErrorDetails::new(
|
||||
format!("Not a tag identifier: {:?}", self),
|
||||
self.clone().into(),
|
||||
))),
|
||||
@ -585,7 +585,7 @@ impl KclValue {
|
||||
pub fn get_tag_declarator(&self) -> Result<TagNode, KclError> {
|
||||
match self {
|
||||
KclValue::TagDeclarator(t) => Ok((**t).clone()),
|
||||
_ => Err(KclError::Semantic(KclErrorDetails::new(
|
||||
_ => Err(KclError::new_semantic(KclErrorDetails::new(
|
||||
format!("Not a tag declarator: {:?}", self),
|
||||
self.clone().into(),
|
||||
))),
|
||||
@ -595,7 +595,7 @@ impl KclValue {
|
||||
/// If this KCL value is a bool, retrieve it.
|
||||
pub fn get_bool(&self) -> Result<bool, KclError> {
|
||||
self.as_bool().ok_or_else(|| {
|
||||
KclError::Type(KclErrorDetails::new(
|
||||
KclError::new_type(KclErrorDetails::new(
|
||||
format!("Expected bool, found {}", self.human_friendly_type()),
|
||||
self.into(),
|
||||
))
|
||||
|
@ -367,10 +367,10 @@ impl ProgramMemory {
|
||||
|
||||
let name = var.trim_start_matches(TYPE_PREFIX).trim_start_matches(MODULE_PREFIX);
|
||||
|
||||
Err(KclError::UndefinedValue(KclErrorDetails::new(
|
||||
format!("`{name}` is not defined"),
|
||||
vec![source_range],
|
||||
)))
|
||||
Err(KclError::new_undefined_value(
|
||||
KclErrorDetails::new(format!("`{name}` is not defined"), vec![source_range]),
|
||||
Some(name.to_owned()),
|
||||
))
|
||||
}
|
||||
|
||||
/// Iterate over all key/value pairs in the specified environment which satisfy the provided
|
||||
@ -488,10 +488,10 @@ impl ProgramMemory {
|
||||
};
|
||||
}
|
||||
|
||||
Err(KclError::UndefinedValue(KclErrorDetails::new(
|
||||
format!("`{}` is not defined", var),
|
||||
vec![],
|
||||
)))
|
||||
Err(KclError::new_undefined_value(
|
||||
KclErrorDetails::new(format!("`{}` is not defined", var), vec![]),
|
||||
Some(var.to_owned()),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
@ -646,7 +646,7 @@ impl Stack {
|
||||
pub fn add(&mut self, key: String, value: KclValue, source_range: SourceRange) -> Result<(), KclError> {
|
||||
let env = self.memory.get_env(self.current_env.index());
|
||||
if env.contains_key(&key) {
|
||||
return Err(KclError::ValueAlreadyDefined(KclErrorDetails::new(
|
||||
return Err(KclError::new_value_already_defined(KclErrorDetails::new(
|
||||
format!("Cannot redefine `{}`", key),
|
||||
vec![source_range],
|
||||
)));
|
||||
|
@ -5,9 +5,8 @@ use std::sync::Arc;
|
||||
use anyhow::Result;
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
pub use artifact::{Artifact, ArtifactCommand, ArtifactGraph, CodeRef, StartSketchOnFace, StartSketchOnPlane};
|
||||
use cache::OldAstState;
|
||||
use cache::GlobalState;
|
||||
pub use cache::{bust_cache, clear_mem_cache};
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
pub use cad_op::{Group, Operation};
|
||||
pub use geometry::*;
|
||||
pub use id_generator::IdGenerator;
|
||||
@ -27,13 +26,12 @@ use serde::{Deserialize, Serialize};
|
||||
pub use state::{ExecState, MetaSettings};
|
||||
use uuid::Uuid;
|
||||
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
use crate::execution::artifact::build_artifact_graph;
|
||||
use crate::{
|
||||
engine::EngineManager,
|
||||
errors::{KclError, KclErrorDetails},
|
||||
execution::{
|
||||
cache::{CacheInformation, CacheResult},
|
||||
import_graph::{Universe, UniverseMap},
|
||||
typed_path::TypedPath,
|
||||
types::{UnitAngle, UnitLen},
|
||||
},
|
||||
@ -41,8 +39,6 @@ use crate::{
|
||||
modules::{ModuleId, ModulePath, ModuleRepr},
|
||||
parsing::ast::types::{Expr, ImportPath, NodeRef},
|
||||
source_range::SourceRange,
|
||||
std::StdLib,
|
||||
walk::{Universe, UniverseMap},
|
||||
CompilationError, ExecError, KclErrorWithOutputs,
|
||||
};
|
||||
|
||||
@ -56,6 +52,7 @@ pub mod fn_call;
|
||||
mod geometry;
|
||||
mod id_generator;
|
||||
mod import;
|
||||
mod import_graph;
|
||||
pub(crate) mod kcl_value;
|
||||
mod memory;
|
||||
mod state;
|
||||
@ -273,7 +270,6 @@ pub enum ContextType {
|
||||
pub struct ExecutorContext {
|
||||
pub engine: Arc<Box<dyn EngineManager>>,
|
||||
pub fs: Arc<FileManager>,
|
||||
pub stdlib: Arc<StdLib>,
|
||||
pub settings: ExecutorSettings,
|
||||
pub context_type: ContextType,
|
||||
}
|
||||
@ -412,7 +408,6 @@ impl ExecutorContext {
|
||||
Ok(Self {
|
||||
engine,
|
||||
fs: Arc::new(FileManager::new()),
|
||||
stdlib: Arc::new(StdLib::new()),
|
||||
settings,
|
||||
context_type: ContextType::Live,
|
||||
})
|
||||
@ -423,7 +418,6 @@ impl ExecutorContext {
|
||||
ExecutorContext {
|
||||
engine,
|
||||
fs,
|
||||
stdlib: Arc::new(StdLib::new()),
|
||||
settings,
|
||||
context_type: ContextType::Live,
|
||||
}
|
||||
@ -436,7 +430,6 @@ impl ExecutorContext {
|
||||
crate::engine::conn_mock::EngineConnection::new().await.unwrap(),
|
||||
)),
|
||||
fs: Arc::new(FileManager::new()),
|
||||
stdlib: Arc::new(StdLib::new()),
|
||||
settings: settings.unwrap_or_default(),
|
||||
context_type: ContextType::Mock,
|
||||
}
|
||||
@ -447,7 +440,6 @@ impl ExecutorContext {
|
||||
ExecutorContext {
|
||||
engine,
|
||||
fs,
|
||||
stdlib: Arc::new(StdLib::new()),
|
||||
settings,
|
||||
context_type: ContextType::Mock,
|
||||
}
|
||||
@ -458,7 +450,6 @@ impl ExecutorContext {
|
||||
ExecutorContext {
|
||||
engine,
|
||||
fs: Arc::new(FileManager::new()),
|
||||
stdlib: Arc::new(StdLib::new()),
|
||||
settings: Default::default(),
|
||||
context_type: ContextType::MockCustomForwarded,
|
||||
}
|
||||
@ -575,7 +566,7 @@ impl ExecutorContext {
|
||||
// part of the scene).
|
||||
exec_state.mut_stack().push_new_env_for_scope();
|
||||
|
||||
let result = self.inner_run(&program, 0, &mut exec_state, true).await?;
|
||||
let result = self.inner_run(&program, &mut exec_state, true).await?;
|
||||
|
||||
// Restore any temporary variables, then save any newly created variables back to
|
||||
// memory in case another run wants to use them. Note this is just saved to the preserved
|
||||
@ -583,7 +574,7 @@ impl ExecutorContext {
|
||||
|
||||
let mut mem = exec_state.stack().clone();
|
||||
let module_infos = exec_state.global.module_infos.clone();
|
||||
let outcome = exec_state.to_mock_exec_outcome(result.0).await;
|
||||
let outcome = exec_state.to_mock_exec_outcome(result.0, self).await;
|
||||
|
||||
mem.squash_env(result.0);
|
||||
cache::write_old_memory((mem, module_infos)).await;
|
||||
@ -594,169 +585,176 @@ impl ExecutorContext {
|
||||
pub async fn run_with_caching(&self, program: crate::Program) -> Result<ExecOutcome, KclErrorWithOutputs> {
|
||||
assert!(!self.is_mock());
|
||||
|
||||
let (program, mut exec_state, preserve_mem, cached_body_items, imports_info) = if let Some(OldAstState {
|
||||
ast: old_ast,
|
||||
exec_state: mut old_state,
|
||||
settings: old_settings,
|
||||
result_env,
|
||||
}) =
|
||||
cache::read_old_ast().await
|
||||
{
|
||||
let old = CacheInformation {
|
||||
ast: &old_ast,
|
||||
settings: &old_settings,
|
||||
};
|
||||
let new = CacheInformation {
|
||||
ast: &program.ast,
|
||||
settings: &self.settings,
|
||||
};
|
||||
|
||||
// Get the program that actually changed from the old and new information.
|
||||
let (clear_scene, program, body_items, import_check_info) = match cache::get_changed_program(old, new).await
|
||||
{
|
||||
CacheResult::ReExecute {
|
||||
clear_scene,
|
||||
reapply_settings,
|
||||
program: changed_program,
|
||||
cached_body_items,
|
||||
} => {
|
||||
if reapply_settings
|
||||
&& self
|
||||
.engine
|
||||
.reapply_settings(&self.settings, Default::default(), old_state.id_generator())
|
||||
.await
|
||||
.is_err()
|
||||
{
|
||||
(true, program, cached_body_items, None)
|
||||
} else {
|
||||
(
|
||||
clear_scene,
|
||||
crate::Program {
|
||||
ast: changed_program,
|
||||
original_file_contents: program.original_file_contents,
|
||||
},
|
||||
cached_body_items,
|
||||
None,
|
||||
)
|
||||
}
|
||||
}
|
||||
CacheResult::CheckImportsOnly {
|
||||
reapply_settings,
|
||||
ast: changed_program,
|
||||
} => {
|
||||
if reapply_settings
|
||||
&& self
|
||||
.engine
|
||||
.reapply_settings(&self.settings, Default::default(), old_state.id_generator())
|
||||
.await
|
||||
.is_err()
|
||||
{
|
||||
(true, program, old_ast.body.len(), None)
|
||||
} else {
|
||||
// We need to check our imports to see if they changed.
|
||||
let mut new_exec_state = ExecState::new(self);
|
||||
let (new_universe, new_universe_map) = self.get_universe(&program, &mut new_exec_state).await?;
|
||||
let mut clear_scene = false;
|
||||
|
||||
let mut keys = new_universe.keys().clone().collect::<Vec<_>>();
|
||||
keys.sort();
|
||||
for key in keys {
|
||||
let (_, id, _, _) = &new_universe[key];
|
||||
if let (Some(source0), Some(source1)) =
|
||||
(old_state.get_source(*id), new_exec_state.get_source(*id))
|
||||
{
|
||||
if source0.source != source1.source {
|
||||
clear_scene = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !clear_scene {
|
||||
// Return early we don't need to clear the scene.
|
||||
let outcome = old_state.to_exec_outcome(result_env).await;
|
||||
return Ok(outcome);
|
||||
}
|
||||
|
||||
(
|
||||
clear_scene,
|
||||
crate::Program {
|
||||
ast: changed_program,
|
||||
original_file_contents: program.original_file_contents,
|
||||
},
|
||||
old_ast.body.len(),
|
||||
// We only care about this if we are clearing the scene.
|
||||
if clear_scene {
|
||||
Some((new_universe, new_universe_map, new_exec_state))
|
||||
} else {
|
||||
None
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
CacheResult::NoAction(true) => {
|
||||
if self
|
||||
.engine
|
||||
.reapply_settings(&self.settings, Default::default(), old_state.id_generator())
|
||||
.await
|
||||
.is_ok()
|
||||
{
|
||||
// We need to update the old ast state with the new settings!!
|
||||
cache::write_old_ast(OldAstState {
|
||||
ast: old_ast,
|
||||
exec_state: old_state.clone(),
|
||||
settings: self.settings.clone(),
|
||||
result_env,
|
||||
})
|
||||
.await;
|
||||
|
||||
let outcome = old_state.to_exec_outcome(result_env).await;
|
||||
return Ok(outcome);
|
||||
}
|
||||
(true, program, old_ast.body.len(), None)
|
||||
}
|
||||
CacheResult::NoAction(false) => {
|
||||
let outcome = old_state.to_exec_outcome(result_env).await;
|
||||
return Ok(outcome);
|
||||
}
|
||||
};
|
||||
|
||||
let (exec_state, preserve_mem, universe_info) =
|
||||
if let Some((new_universe, new_universe_map, mut new_exec_state)) = import_check_info {
|
||||
// Clear the scene if the imports changed.
|
||||
self.send_clear_scene(&mut new_exec_state, Default::default())
|
||||
.await
|
||||
.map_err(KclErrorWithOutputs::no_outputs)?;
|
||||
|
||||
(new_exec_state, false, Some((new_universe, new_universe_map)))
|
||||
} else if clear_scene {
|
||||
// Pop the execution state, since we are starting fresh.
|
||||
let mut exec_state = old_state;
|
||||
exec_state.reset(self);
|
||||
|
||||
self.send_clear_scene(&mut exec_state, Default::default())
|
||||
.await
|
||||
.map_err(KclErrorWithOutputs::no_outputs)?;
|
||||
|
||||
(exec_state, false, None)
|
||||
} else {
|
||||
old_state.mut_stack().restore_env(result_env);
|
||||
|
||||
(old_state, true, None)
|
||||
let (program, exec_state, result) = match cache::read_old_ast().await {
|
||||
Some(mut cached_state) => {
|
||||
let old = CacheInformation {
|
||||
ast: &cached_state.main.ast,
|
||||
settings: &cached_state.settings,
|
||||
};
|
||||
let new = CacheInformation {
|
||||
ast: &program.ast,
|
||||
settings: &self.settings,
|
||||
};
|
||||
|
||||
(program, exec_state, preserve_mem, body_items, universe_info)
|
||||
} else {
|
||||
let mut exec_state = ExecState::new(self);
|
||||
self.send_clear_scene(&mut exec_state, Default::default())
|
||||
.await
|
||||
.map_err(KclErrorWithOutputs::no_outputs)?;
|
||||
(program, exec_state, false, 0, None)
|
||||
};
|
||||
// Get the program that actually changed from the old and new information.
|
||||
let (clear_scene, program, import_check_info) = match cache::get_changed_program(old, new).await {
|
||||
CacheResult::ReExecute {
|
||||
clear_scene,
|
||||
reapply_settings,
|
||||
program: changed_program,
|
||||
} => {
|
||||
if reapply_settings
|
||||
&& self
|
||||
.engine
|
||||
.reapply_settings(
|
||||
&self.settings,
|
||||
Default::default(),
|
||||
&mut cached_state.main.exec_state.id_generator,
|
||||
)
|
||||
.await
|
||||
.is_err()
|
||||
{
|
||||
(true, program, None)
|
||||
} else {
|
||||
(
|
||||
clear_scene,
|
||||
crate::Program {
|
||||
ast: changed_program,
|
||||
original_file_contents: program.original_file_contents,
|
||||
},
|
||||
None,
|
||||
)
|
||||
}
|
||||
}
|
||||
CacheResult::CheckImportsOnly {
|
||||
reapply_settings,
|
||||
ast: changed_program,
|
||||
} => {
|
||||
if reapply_settings
|
||||
&& self
|
||||
.engine
|
||||
.reapply_settings(
|
||||
&self.settings,
|
||||
Default::default(),
|
||||
&mut cached_state.main.exec_state.id_generator,
|
||||
)
|
||||
.await
|
||||
.is_err()
|
||||
{
|
||||
(true, program, None)
|
||||
} else {
|
||||
// We need to check our imports to see if they changed.
|
||||
let mut new_exec_state = ExecState::new(self);
|
||||
let (new_universe, new_universe_map) =
|
||||
self.get_universe(&program, &mut new_exec_state).await?;
|
||||
|
||||
let result = self
|
||||
.run_concurrent(&program, cached_body_items, &mut exec_state, imports_info, preserve_mem)
|
||||
.await;
|
||||
let clear_scene = new_universe.keys().any(|key| {
|
||||
let id = new_universe[key].1;
|
||||
match (
|
||||
cached_state.exec_state.get_source(id),
|
||||
new_exec_state.global.get_source(id),
|
||||
) {
|
||||
(Some(s0), Some(s1)) => s0.source != s1.source,
|
||||
_ => false,
|
||||
}
|
||||
});
|
||||
|
||||
if !clear_scene {
|
||||
// Return early we don't need to clear the scene.
|
||||
return Ok(cached_state.into_exec_outcome(self).await);
|
||||
}
|
||||
|
||||
(
|
||||
true,
|
||||
crate::Program {
|
||||
ast: changed_program,
|
||||
original_file_contents: program.original_file_contents,
|
||||
},
|
||||
Some((new_universe, new_universe_map, new_exec_state)),
|
||||
)
|
||||
}
|
||||
}
|
||||
CacheResult::NoAction(true) => {
|
||||
if self
|
||||
.engine
|
||||
.reapply_settings(
|
||||
&self.settings,
|
||||
Default::default(),
|
||||
&mut cached_state.main.exec_state.id_generator,
|
||||
)
|
||||
.await
|
||||
.is_ok()
|
||||
{
|
||||
// We need to update the old ast state with the new settings!!
|
||||
cache::write_old_ast(GlobalState::with_settings(
|
||||
cached_state.clone(),
|
||||
self.settings.clone(),
|
||||
))
|
||||
.await;
|
||||
|
||||
return Ok(cached_state.into_exec_outcome(self).await);
|
||||
}
|
||||
(true, program, None)
|
||||
}
|
||||
CacheResult::NoAction(false) => {
|
||||
return Ok(cached_state.into_exec_outcome(self).await);
|
||||
}
|
||||
};
|
||||
|
||||
let (exec_state, result) = match import_check_info {
|
||||
Some((new_universe, new_universe_map, mut new_exec_state)) => {
|
||||
// Clear the scene if the imports changed.
|
||||
self.send_clear_scene(&mut new_exec_state, Default::default())
|
||||
.await
|
||||
.map_err(KclErrorWithOutputs::no_outputs)?;
|
||||
|
||||
let result = self
|
||||
.run_concurrent(
|
||||
&program,
|
||||
&mut new_exec_state,
|
||||
Some((new_universe, new_universe_map)),
|
||||
false,
|
||||
)
|
||||
.await;
|
||||
|
||||
(new_exec_state, result)
|
||||
}
|
||||
None if clear_scene => {
|
||||
// Pop the execution state, since we are starting fresh.
|
||||
let mut exec_state = cached_state.reconstitute_exec_state();
|
||||
exec_state.reset(self);
|
||||
|
||||
self.send_clear_scene(&mut exec_state, Default::default())
|
||||
.await
|
||||
.map_err(KclErrorWithOutputs::no_outputs)?;
|
||||
|
||||
let result = self.run_concurrent(&program, &mut exec_state, None, false).await;
|
||||
|
||||
(exec_state, result)
|
||||
}
|
||||
None => {
|
||||
let mut exec_state = cached_state.reconstitute_exec_state();
|
||||
exec_state.mut_stack().restore_env(cached_state.main.result_env);
|
||||
|
||||
let result = self.run_concurrent(&program, &mut exec_state, None, true).await;
|
||||
|
||||
(exec_state, result)
|
||||
}
|
||||
};
|
||||
|
||||
(program, exec_state, result)
|
||||
}
|
||||
None => {
|
||||
let mut exec_state = ExecState::new(self);
|
||||
self.send_clear_scene(&mut exec_state, Default::default())
|
||||
.await
|
||||
.map_err(KclErrorWithOutputs::no_outputs)?;
|
||||
|
||||
let result = self.run_concurrent(&program, &mut exec_state, None, false).await;
|
||||
|
||||
(program, exec_state, result)
|
||||
}
|
||||
};
|
||||
|
||||
if result.is_err() {
|
||||
cache::bust_cache().await;
|
||||
@ -766,15 +764,15 @@ impl ExecutorContext {
|
||||
let result = result?;
|
||||
|
||||
// Save this as the last successful execution to the cache.
|
||||
cache::write_old_ast(OldAstState {
|
||||
ast: program.ast,
|
||||
exec_state: exec_state.clone(),
|
||||
settings: self.settings.clone(),
|
||||
result_env: result.0,
|
||||
})
|
||||
cache::write_old_ast(GlobalState::new(
|
||||
exec_state.clone(),
|
||||
self.settings.clone(),
|
||||
program.ast,
|
||||
result.0,
|
||||
))
|
||||
.await;
|
||||
|
||||
let outcome = exec_state.to_exec_outcome(result.0).await;
|
||||
let outcome = exec_state.to_exec_outcome(result.0, self).await;
|
||||
Ok(outcome)
|
||||
}
|
||||
|
||||
@ -789,11 +787,11 @@ impl ExecutorContext {
|
||||
program: &crate::Program,
|
||||
exec_state: &mut ExecState,
|
||||
) -> Result<(EnvironmentRef, Option<ModelingSessionData>), KclErrorWithOutputs> {
|
||||
self.run_concurrent(program, 0, exec_state, None, false).await
|
||||
self.run_concurrent(program, exec_state, None, false).await
|
||||
}
|
||||
|
||||
/// Perform the execution of a program using a concurrent
|
||||
/// execution model. This has the same signature as [Self::run].
|
||||
/// execution model.
|
||||
///
|
||||
/// You can optionally pass in some initialization memory for partial
|
||||
/// execution.
|
||||
@ -802,13 +800,12 @@ impl ExecutorContext {
|
||||
pub async fn run_concurrent(
|
||||
&self,
|
||||
program: &crate::Program,
|
||||
cached_body_items: usize,
|
||||
exec_state: &mut ExecState,
|
||||
universe_info: Option<(Universe, UniverseMap)>,
|
||||
preserve_mem: bool,
|
||||
) -> Result<(EnvironmentRef, Option<ModelingSessionData>), KclErrorWithOutputs> {
|
||||
// Reuse our cached universe if we have one.
|
||||
#[allow(unused_variables)]
|
||||
|
||||
let (universe, universe_map) = if let Some((universe, universe_map)) = universe_info {
|
||||
(universe, universe_map)
|
||||
} else {
|
||||
@ -822,29 +819,8 @@ impl ExecutorContext {
|
||||
.await
|
||||
.map_err(KclErrorWithOutputs::no_outputs)?;
|
||||
|
||||
for modules in crate::walk::import_graph(&universe, self)
|
||||
.map_err(|err| {
|
||||
let module_id_to_module_path: IndexMap<ModuleId, ModulePath> = exec_state
|
||||
.global
|
||||
.path_to_source_id
|
||||
.iter()
|
||||
.map(|(k, v)| ((*v), k.clone()))
|
||||
.collect();
|
||||
|
||||
KclErrorWithOutputs::new(
|
||||
err,
|
||||
exec_state.errors().to_vec(),
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
exec_state.global.operations.clone(),
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
exec_state.global.artifact_commands.clone(),
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
exec_state.global.artifact_graph.clone(),
|
||||
module_id_to_module_path,
|
||||
exec_state.global.id_to_source.clone(),
|
||||
default_planes.clone(),
|
||||
)
|
||||
})?
|
||||
for modules in import_graph::import_graph(&universe, self)
|
||||
.map_err(|err| exec_state.error_with_outputs(err, default_planes.clone()))?
|
||||
.into_iter()
|
||||
{
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
@ -858,7 +834,7 @@ impl ExecutorContext {
|
||||
|
||||
for module in modules {
|
||||
let Some((import_stmt, module_id, module_path, repr)) = universe.get(&module) else {
|
||||
return Err(KclErrorWithOutputs::no_outputs(KclError::Internal(
|
||||
return Err(KclErrorWithOutputs::no_outputs(KclError::new_internal(
|
||||
KclErrorDetails::new(format!("Module {module} not found in universe"), Default::default()),
|
||||
)));
|
||||
};
|
||||
@ -866,7 +842,6 @@ impl ExecutorContext {
|
||||
let module_path = module_path.clone();
|
||||
let source_range = SourceRange::from(import_stmt);
|
||||
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
match &module_path {
|
||||
ModulePath::Main => {
|
||||
// This should never happen.
|
||||
@ -875,7 +850,7 @@ impl ExecutorContext {
|
||||
// We only want to display the top-level module imports in
|
||||
// the Feature Tree, not transitive imports.
|
||||
if universe_map.contains_key(value) {
|
||||
exec_state.global.operations.push(Operation::GroupBegin {
|
||||
exec_state.push_op(Operation::GroupBegin {
|
||||
group: Group::ModuleInstance {
|
||||
name: value.file_name().unwrap_or_default(),
|
||||
module_id,
|
||||
@ -885,7 +860,7 @@ impl ExecutorContext {
|
||||
// Due to concurrent execution, we cannot easily
|
||||
// group operations by module. So we leave the
|
||||
// group empty and close it immediately.
|
||||
exec_state.global.operations.push(Operation::GroupEnd);
|
||||
exec_state.push_op(Operation::GroupEnd);
|
||||
}
|
||||
}
|
||||
ModulePath::Std { .. } => {
|
||||
@ -920,7 +895,7 @@ impl ExecutorContext {
|
||||
|
||||
result.map(|val| ModuleRepr::Foreign(geom.clone(), val))
|
||||
}
|
||||
ModuleRepr::Dummy | ModuleRepr::Root => Err(KclError::Internal(KclErrorDetails::new(
|
||||
ModuleRepr::Dummy | ModuleRepr::Root => Err(KclError::new_internal(KclErrorDetails::new(
|
||||
format!("Module {module_path} not found in universe"),
|
||||
vec![source_range],
|
||||
))),
|
||||
@ -930,7 +905,6 @@ impl ExecutorContext {
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
{
|
||||
wasm_bindgen_futures::spawn_local(async move {
|
||||
//set.spawn(async move {
|
||||
let mut exec_state = exec_state;
|
||||
let exec_ctxt = exec_ctxt;
|
||||
|
||||
@ -1000,33 +974,13 @@ impl ExecutorContext {
|
||||
exec_state.global.module_infos[&module_id].restore_repr(repr);
|
||||
}
|
||||
Err(e) => {
|
||||
let module_id_to_module_path: IndexMap<ModuleId, ModulePath> = exec_state
|
||||
.global
|
||||
.path_to_source_id
|
||||
.iter()
|
||||
.map(|(k, v)| ((*v), k.clone()))
|
||||
.collect();
|
||||
|
||||
return Err(KclErrorWithOutputs::new(
|
||||
e,
|
||||
exec_state.errors().to_vec(),
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
exec_state.global.operations.clone(),
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
exec_state.global.artifact_commands.clone(),
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
exec_state.global.artifact_graph.clone(),
|
||||
module_id_to_module_path,
|
||||
exec_state.global.id_to_source.clone(),
|
||||
default_planes,
|
||||
));
|
||||
return Err(exec_state.error_with_outputs(e, default_planes));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.inner_run(program, cached_body_items, exec_state, preserve_mem)
|
||||
.await
|
||||
self.inner_run(program, exec_state, preserve_mem).await
|
||||
}
|
||||
|
||||
/// Get the universe & universe map of the program.
|
||||
@ -1042,7 +996,7 @@ impl ExecutorContext {
|
||||
|
||||
let default_planes = self.engine.get_default_planes().read().await.clone();
|
||||
|
||||
let root_imports = crate::walk::import_universe(
|
||||
let root_imports = import_graph::import_universe(
|
||||
self,
|
||||
&ModulePath::Main,
|
||||
&ModuleRepr::Kcl(program.ast.clone(), None),
|
||||
@ -1050,29 +1004,7 @@ impl ExecutorContext {
|
||||
exec_state,
|
||||
)
|
||||
.await
|
||||
.map_err(|err| {
|
||||
println!("Error: {err:?}");
|
||||
let module_id_to_module_path: IndexMap<ModuleId, ModulePath> = exec_state
|
||||
.global
|
||||
.path_to_source_id
|
||||
.iter()
|
||||
.map(|(k, v)| ((*v), k.clone()))
|
||||
.collect();
|
||||
|
||||
KclErrorWithOutputs::new(
|
||||
err,
|
||||
exec_state.errors().to_vec(),
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
exec_state.global.operations.clone(),
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
exec_state.global.artifact_commands.clone(),
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
exec_state.global.artifact_graph.clone(),
|
||||
module_id_to_module_path,
|
||||
exec_state.global.id_to_source.clone(),
|
||||
default_planes,
|
||||
)
|
||||
})?;
|
||||
.map_err(|err| exec_state.error_with_outputs(err, default_planes))?;
|
||||
|
||||
Ok((universe, root_imports))
|
||||
}
|
||||
@ -1082,7 +1014,6 @@ impl ExecutorContext {
|
||||
async fn inner_run(
|
||||
&self,
|
||||
program: &crate::Program,
|
||||
cached_body_items: usize,
|
||||
exec_state: &mut ExecState,
|
||||
preserve_mem: bool,
|
||||
) -> Result<(EnvironmentRef, Option<ModelingSessionData>), KclErrorWithOutputs> {
|
||||
@ -1096,7 +1027,7 @@ impl ExecutorContext {
|
||||
|
||||
let default_planes = self.engine.get_default_planes().read().await.clone();
|
||||
let result = self
|
||||
.execute_and_build_graph(&program.ast, cached_body_items, exec_state, preserve_mem)
|
||||
.execute_and_build_graph(&program.ast, exec_state, preserve_mem)
|
||||
.await;
|
||||
|
||||
crate::log::log(format!(
|
||||
@ -1105,28 +1036,7 @@ impl ExecutorContext {
|
||||
));
|
||||
crate::log::log(format!("Engine stats: {:?}", self.engine.stats()));
|
||||
|
||||
let env_ref = result.map_err(|e| {
|
||||
let module_id_to_module_path: IndexMap<ModuleId, ModulePath> = exec_state
|
||||
.global
|
||||
.path_to_source_id
|
||||
.iter()
|
||||
.map(|(k, v)| ((*v), k.clone()))
|
||||
.collect();
|
||||
|
||||
KclErrorWithOutputs::new(
|
||||
e,
|
||||
exec_state.errors().to_vec(),
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
exec_state.global.operations.clone(),
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
exec_state.global.artifact_commands.clone(),
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
exec_state.global.artifact_graph.clone(),
|
||||
module_id_to_module_path,
|
||||
exec_state.global.id_to_source.clone(),
|
||||
default_planes.clone(),
|
||||
)
|
||||
})?;
|
||||
let env_ref = result.map_err(|e| exec_state.error_with_outputs(e, default_planes))?;
|
||||
|
||||
if !self.is_mock() {
|
||||
let mut mem = exec_state.stack().deep_clone();
|
||||
@ -1143,7 +1053,6 @@ impl ExecutorContext {
|
||||
async fn execute_and_build_graph(
|
||||
&self,
|
||||
program: NodeRef<'_, crate::parsing::ast::types::Program>,
|
||||
#[cfg_attr(not(feature = "artifact-graph"), expect(unused))] cached_body_items: usize,
|
||||
exec_state: &mut ExecState,
|
||||
preserve_mem: bool,
|
||||
) -> Result<EnvironmentRef, KclError> {
|
||||
@ -1170,40 +1079,9 @@ impl ExecutorContext {
|
||||
// and should be dropped.
|
||||
self.engine.clear_queues().await;
|
||||
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
{
|
||||
let new_commands = self.engine.take_artifact_commands().await;
|
||||
let new_responses = self.engine.take_responses().await;
|
||||
let initial_graph = exec_state.global.artifact_graph.clone();
|
||||
|
||||
// Build the artifact graph.
|
||||
let graph_result = build_artifact_graph(
|
||||
&new_commands,
|
||||
&new_responses,
|
||||
program,
|
||||
cached_body_items,
|
||||
&mut exec_state.global.artifacts,
|
||||
initial_graph,
|
||||
);
|
||||
// Move the artifact commands and responses into ExecState to
|
||||
// simplify cache management and error creation.
|
||||
exec_state.global.artifact_commands.extend(new_commands);
|
||||
exec_state.global.artifact_responses.extend(new_responses);
|
||||
|
||||
match graph_result {
|
||||
Ok(artifact_graph) => {
|
||||
exec_state.global.artifact_graph = artifact_graph;
|
||||
exec_result.map(|(_, env_ref, _)| env_ref)
|
||||
}
|
||||
Err(err) => {
|
||||
// Prefer the exec error.
|
||||
exec_result.and(Err(err))
|
||||
}
|
||||
}
|
||||
}
|
||||
#[cfg(not(feature = "artifact-graph"))]
|
||||
{
|
||||
exec_result.map(|(_, env_ref, _)| env_ref)
|
||||
match exec_state.build_artifact_graph(&self.engine, program).await {
|
||||
Ok(_) => exec_result.map(|(_, env_ref, _)| env_ref),
|
||||
Err(err) => exec_result.and(Err(err)),
|
||||
}
|
||||
}
|
||||
|
||||
@ -1283,7 +1161,7 @@ impl ExecutorContext {
|
||||
.await?;
|
||||
|
||||
let kittycad_modeling_cmds::websocket::OkWebSocketResponseData::Export { files } = resp else {
|
||||
return Err(KclError::Internal(crate::errors::KclErrorDetails::new(
|
||||
return Err(KclError::new_internal(crate::errors::KclErrorDetails::new(
|
||||
format!("Expected Export response, got {resp:?}",),
|
||||
vec![SourceRange::default()],
|
||||
)));
|
||||
@ -1303,7 +1181,7 @@ impl ExecutorContext {
|
||||
coords: *kittycad_modeling_cmds::coord::KITTYCAD,
|
||||
created: if deterministic_time {
|
||||
Some("2021-01-01T00:00:00Z".parse().map_err(|e| {
|
||||
KclError::Internal(crate::errors::KclErrorDetails::new(
|
||||
KclError::new_internal(crate::errors::KclErrorDetails::new(
|
||||
format!("Failed to parse date: {}", e),
|
||||
vec![SourceRange::default()],
|
||||
))
|
||||
@ -1383,14 +1261,13 @@ pub(crate) async fn parse_execute_with_project_dir(
|
||||
let exec_ctxt = ExecutorContext {
|
||||
engine: Arc::new(Box::new(
|
||||
crate::engine::conn_mock::EngineConnection::new().await.map_err(|err| {
|
||||
KclError::Internal(crate::errors::KclErrorDetails::new(
|
||||
KclError::new_internal(crate::errors::KclErrorDetails::new(
|
||||
format!("Failed to create mock engine connection: {}", err),
|
||||
vec![SourceRange::default()],
|
||||
))
|
||||
})?,
|
||||
)),
|
||||
fs: Arc::new(crate::fs::FileManager::new()),
|
||||
stdlib: Arc::new(crate::std::StdLib::new()),
|
||||
settings: ExecutorSettings {
|
||||
project_directory,
|
||||
..Default::default()
|
||||
@ -1799,7 +1676,7 @@ foo
|
||||
let err = result.unwrap_err();
|
||||
assert_eq!(
|
||||
err,
|
||||
KclError::Syntax(KclErrorDetails::new(
|
||||
KclError::new_syntax(KclErrorDetails::new(
|
||||
"Unexpected token: #".to_owned(),
|
||||
vec![SourceRange::new(14, 15, ModuleId::default())],
|
||||
)),
|
||||
@ -2058,7 +1935,7 @@ notTagIdentifier = !myTag";
|
||||
// TODO: We don't currently parse this, but we should. It should be
|
||||
// a runtime error instead.
|
||||
parse_execute(code10).await.unwrap_err(),
|
||||
KclError::Syntax(KclErrorDetails::new(
|
||||
KclError::new_syntax(KclErrorDetails::new(
|
||||
"Unexpected token: !".to_owned(),
|
||||
vec![SourceRange::new(10, 11, ModuleId::default())],
|
||||
))
|
||||
@ -2071,9 +1948,9 @@ notPipeSub = 1 |> identity(!%))";
|
||||
// TODO: We don't currently parse this, but we should. It should be
|
||||
// a runtime error instead.
|
||||
parse_execute(code11).await.unwrap_err(),
|
||||
KclError::Syntax(KclErrorDetails::new(
|
||||
"Unexpected token: |>".to_owned(),
|
||||
vec![SourceRange::new(44, 46, ModuleId::default())],
|
||||
KclError::new_syntax(KclErrorDetails::new(
|
||||
"There was an unexpected !. Try removing it.".to_owned(),
|
||||
vec![SourceRange::new(56, 57, ModuleId::default())],
|
||||
))
|
||||
);
|
||||
|
||||
@ -2206,7 +2083,7 @@ w = f() + f()
|
||||
}
|
||||
|
||||
// Get the id_generator from the first execution.
|
||||
let id_generator = cache::read_old_ast().await.unwrap().exec_state.mod_local.id_generator;
|
||||
let id_generator = cache::read_old_ast().await.unwrap().main.exec_state.id_generator;
|
||||
|
||||
let code = r#"sketch001 = startSketchOn(XZ)
|
||||
|> startProfile(at = [62.74, 206.13])
|
||||
@ -2227,7 +2104,7 @@ w = f() + f()
|
||||
// Execute the program.
|
||||
ctx.run_with_caching(program).await.unwrap();
|
||||
|
||||
let new_id_generator = cache::read_old_ast().await.unwrap().exec_state.mod_local.id_generator;
|
||||
let new_id_generator = cache::read_old_ast().await.unwrap().main.exec_state.id_generator;
|
||||
|
||||
assert_eq!(id_generator, new_id_generator);
|
||||
}
|
||||
|
@ -12,19 +12,19 @@ use uuid::Uuid;
|
||||
use crate::execution::{Artifact, ArtifactCommand, ArtifactGraph, ArtifactId};
|
||||
use crate::{
|
||||
errors::{KclError, KclErrorDetails, Severity},
|
||||
exec::DefaultPlanes,
|
||||
execution::{
|
||||
annotations,
|
||||
cad_op::Operation,
|
||||
id_generator::IdGenerator,
|
||||
memory::{ProgramMemory, Stack},
|
||||
types,
|
||||
types::NumericType,
|
||||
types::{self, NumericType},
|
||||
EnvironmentRef, ExecOutcome, ExecutorSettings, KclValue, UnitAngle, UnitLen,
|
||||
},
|
||||
modules::{ModuleId, ModuleInfo, ModuleLoader, ModulePath, ModuleRepr, ModuleSource},
|
||||
parsing::ast::types::Annotation,
|
||||
parsing::ast::types::{Annotation, NodeRef},
|
||||
source_range::SourceRange,
|
||||
CompilationError,
|
||||
CompilationError, EngineManager, ExecutorContext, KclErrorWithOutputs,
|
||||
};
|
||||
|
||||
/// State for executing a program.
|
||||
@ -32,7 +32,6 @@ use crate::{
|
||||
pub struct ExecState {
|
||||
pub(super) global: GlobalState,
|
||||
pub(super) mod_local: ModuleState,
|
||||
pub(super) exec_context: Option<super::ExecutorContext>,
|
||||
}
|
||||
|
||||
pub type ModuleInfoMap = IndexMap<ModuleId, ModuleInfo>;
|
||||
@ -45,33 +44,39 @@ pub(super) struct GlobalState {
|
||||
pub id_to_source: IndexMap<ModuleId, ModuleSource>,
|
||||
/// Map from module ID to module info.
|
||||
pub module_infos: ModuleInfoMap,
|
||||
/// Output map of UUIDs to artifacts.
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
pub artifacts: IndexMap<ArtifactId, Artifact>,
|
||||
/// Output commands to allow building the artifact graph by the caller.
|
||||
/// These are accumulated in the [`ExecutorContext`] but moved here for
|
||||
/// convenience of the execution cache.
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
pub artifact_commands: Vec<ArtifactCommand>,
|
||||
/// Responses from the engine for `artifact_commands`. We need to cache
|
||||
/// this so that we can build the artifact graph. These are accumulated in
|
||||
/// the [`ExecutorContext`] but moved here for convenience of the execution
|
||||
/// cache.
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
pub artifact_responses: IndexMap<Uuid, WebSocketResponse>,
|
||||
/// Output artifact graph.
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
pub artifact_graph: ArtifactGraph,
|
||||
/// Operations that have been performed in execution order, for display in
|
||||
/// the Feature Tree.
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
pub operations: Vec<Operation>,
|
||||
/// Module loader.
|
||||
pub mod_loader: ModuleLoader,
|
||||
/// Errors and warnings.
|
||||
pub errors: Vec<CompilationError>,
|
||||
#[allow(dead_code)]
|
||||
pub artifacts: ArtifactState,
|
||||
}
|
||||
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub(super) struct ArtifactState {
|
||||
/// Output map of UUIDs to artifacts.
|
||||
pub artifacts: IndexMap<ArtifactId, Artifact>,
|
||||
/// Output commands to allow building the artifact graph by the caller.
|
||||
/// These are accumulated in the [`ExecutorContext`] but moved here for
|
||||
/// convenience of the execution cache.
|
||||
pub commands: Vec<ArtifactCommand>,
|
||||
/// Responses from the engine for `artifact_commands`. We need to cache
|
||||
/// this so that we can build the artifact graph. These are accumulated in
|
||||
/// the [`ExecutorContext`] but moved here for convenience of the execution
|
||||
/// cache.
|
||||
pub responses: IndexMap<Uuid, WebSocketResponse>,
|
||||
/// Output artifact graph.
|
||||
pub graph: ArtifactGraph,
|
||||
/// Operations that have been performed in execution order, for display in
|
||||
/// the Feature Tree.
|
||||
pub operations: Vec<Operation>,
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "artifact-graph"))]
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub(super) struct ArtifactState {}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub(super) struct ModuleState {
|
||||
/// The id generator for this module.
|
||||
@ -80,6 +85,11 @@ pub(super) struct ModuleState {
|
||||
/// The current value of the pipe operator returned from the previous
|
||||
/// expression. If we're not currently in a pipeline, this will be None.
|
||||
pub pipe_value: Option<KclValue>,
|
||||
/// The closest variable declaration being executed in any parent node in the AST.
|
||||
/// This is used to provide better error messages, e.g. noticing when the user is trying
|
||||
/// to use the variable `length` inside the RHS of its own definition, like `length = tan(length)`.
|
||||
/// TODO: Make this a reference.
|
||||
pub being_declared: Option<String>,
|
||||
/// Identifiers that have been exported from the current module.
|
||||
pub module_exports: Vec<String>,
|
||||
/// Settings specified from annotations.
|
||||
@ -93,7 +103,6 @@ impl ExecState {
|
||||
ExecState {
|
||||
global: GlobalState::new(&exec_context.settings),
|
||||
mod_local: ModuleState::new(ModulePath::Main, ProgramMemory::new(), Default::default()),
|
||||
exec_context: Some(exec_context.clone()),
|
||||
}
|
||||
}
|
||||
|
||||
@ -103,7 +112,6 @@ impl ExecState {
|
||||
*self = ExecState {
|
||||
global,
|
||||
mod_local: ModuleState::new(self.mod_local.path.clone(), ProgramMemory::new(), Default::default()),
|
||||
exec_context: Some(exec_context.clone()),
|
||||
};
|
||||
}
|
||||
|
||||
@ -125,45 +133,26 @@ impl ExecState {
|
||||
/// Convert to execution outcome when running in WebAssembly. We want to
|
||||
/// reduce the amount of data that crosses the WASM boundary as much as
|
||||
/// possible.
|
||||
pub async fn to_exec_outcome(self, main_ref: EnvironmentRef) -> ExecOutcome {
|
||||
pub async fn to_exec_outcome(self, main_ref: EnvironmentRef, ctx: &ExecutorContext) -> ExecOutcome {
|
||||
// Fields are opt-in so that we don't accidentally leak private internal
|
||||
// state when we add more to ExecState.
|
||||
ExecOutcome {
|
||||
variables: self
|
||||
.stack()
|
||||
.find_all_in_env(main_ref)
|
||||
.map(|(k, v)| (k.clone(), v.clone()))
|
||||
.collect(),
|
||||
variables: self.mod_local.variables(main_ref),
|
||||
filenames: self.global.filenames(),
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
operations: self.global.operations,
|
||||
operations: self.global.artifacts.operations,
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
artifact_commands: self.global.artifact_commands,
|
||||
artifact_commands: self.global.artifacts.commands,
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
artifact_graph: self.global.artifact_graph,
|
||||
artifact_graph: self.global.artifacts.graph,
|
||||
errors: self.global.errors,
|
||||
filenames: self
|
||||
.global
|
||||
.path_to_source_id
|
||||
.iter()
|
||||
.map(|(k, v)| ((*v), k.clone()))
|
||||
.collect(),
|
||||
default_planes: if let Some(ctx) = &self.exec_context {
|
||||
ctx.engine.get_default_planes().read().await.clone()
|
||||
} else {
|
||||
None
|
||||
},
|
||||
default_planes: ctx.engine.get_default_planes().read().await.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn to_mock_exec_outcome(self, main_ref: EnvironmentRef) -> ExecOutcome {
|
||||
// Fields are opt-in so that we don't accidentally leak private internal
|
||||
// state when we add more to ExecState.
|
||||
pub async fn to_mock_exec_outcome(self, main_ref: EnvironmentRef, ctx: &ExecutorContext) -> ExecOutcome {
|
||||
ExecOutcome {
|
||||
variables: self
|
||||
.stack()
|
||||
.find_all_in_env(main_ref)
|
||||
.map(|(k, v)| (k.clone(), v.clone()))
|
||||
.collect(),
|
||||
variables: self.mod_local.variables(main_ref),
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
operations: Default::default(),
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
@ -172,11 +161,7 @@ impl ExecState {
|
||||
artifact_graph: Default::default(),
|
||||
errors: self.global.errors,
|
||||
filenames: Default::default(),
|
||||
default_planes: if let Some(ctx) = &self.exec_context {
|
||||
ctx.engine.get_default_planes().read().await.clone()
|
||||
} else {
|
||||
None
|
||||
},
|
||||
default_planes: ctx.engine.get_default_planes().read().await.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -199,12 +184,12 @@ impl ExecState {
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
pub(crate) fn add_artifact(&mut self, artifact: Artifact) {
|
||||
let id = artifact.id();
|
||||
self.global.artifacts.insert(id, artifact);
|
||||
self.global.artifacts.artifacts.insert(id, artifact);
|
||||
}
|
||||
|
||||
pub(crate) fn push_op(&mut self, op: Operation) {
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
self.global.operations.push(op);
|
||||
self.global.artifacts.operations.push(op);
|
||||
#[cfg(not(feature = "artifact-graph"))]
|
||||
drop(op);
|
||||
}
|
||||
@ -246,10 +231,6 @@ impl ExecState {
|
||||
self.global.id_to_source.insert(id, source.clone());
|
||||
}
|
||||
|
||||
pub(super) fn get_source(&self, id: ModuleId) -> Option<&ModuleSource> {
|
||||
self.global.id_to_source.get(&id)
|
||||
}
|
||||
|
||||
pub(super) fn add_module(&mut self, id: ModuleId, path: ModulePath, repr: ModuleRepr) {
|
||||
debug_assert!(self.global.path_to_source_id.contains_key(&path));
|
||||
let module_info = ModuleInfo { id, repr, path };
|
||||
@ -276,7 +257,7 @@ impl ExecState {
|
||||
}
|
||||
|
||||
pub(super) fn circular_import_error(&self, path: &ModulePath, source_range: SourceRange) -> KclError {
|
||||
KclError::ImportCycle(KclErrorDetails::new(
|
||||
KclError::new_import_cycle(KclErrorDetails::new(
|
||||
format!(
|
||||
"circular import of modules is not allowed: {} -> {}",
|
||||
self.global
|
||||
@ -295,6 +276,71 @@ impl ExecState {
|
||||
pub(crate) fn pipe_value(&self) -> Option<&KclValue> {
|
||||
self.mod_local.pipe_value.as_ref()
|
||||
}
|
||||
|
||||
pub(crate) fn error_with_outputs(
|
||||
&self,
|
||||
error: KclError,
|
||||
default_planes: Option<DefaultPlanes>,
|
||||
) -> KclErrorWithOutputs {
|
||||
let module_id_to_module_path: IndexMap<ModuleId, ModulePath> = self
|
||||
.global
|
||||
.path_to_source_id
|
||||
.iter()
|
||||
.map(|(k, v)| ((*v), k.clone()))
|
||||
.collect();
|
||||
|
||||
KclErrorWithOutputs::new(
|
||||
error,
|
||||
self.errors().to_vec(),
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
self.global.artifacts.operations.clone(),
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
self.global.artifacts.commands.clone(),
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
self.global.artifacts.graph.clone(),
|
||||
module_id_to_module_path,
|
||||
self.global.id_to_source.clone(),
|
||||
default_planes,
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
pub(crate) async fn build_artifact_graph(
|
||||
&mut self,
|
||||
engine: &Arc<Box<dyn EngineManager>>,
|
||||
program: NodeRef<'_, crate::parsing::ast::types::Program>,
|
||||
) -> Result<(), KclError> {
|
||||
let new_commands = engine.take_artifact_commands().await;
|
||||
let new_responses = engine.take_responses().await;
|
||||
let initial_graph = self.global.artifacts.graph.clone();
|
||||
|
||||
// Build the artifact graph.
|
||||
let graph_result = crate::execution::artifact::build_artifact_graph(
|
||||
&new_commands,
|
||||
&new_responses,
|
||||
program,
|
||||
&mut self.global.artifacts.artifacts,
|
||||
initial_graph,
|
||||
);
|
||||
// Move the artifact commands and responses into ExecState to
|
||||
// simplify cache management and error creation.
|
||||
self.global.artifacts.commands.extend(new_commands);
|
||||
self.global.artifacts.responses.extend(new_responses);
|
||||
|
||||
let artifact_graph = graph_result?;
|
||||
self.global.artifacts.graph = artifact_graph;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "artifact-graph"))]
|
||||
pub(crate) async fn build_artifact_graph(
|
||||
&mut self,
|
||||
_engine: &Arc<Box<dyn EngineManager>>,
|
||||
_program: NodeRef<'_, crate::parsing::ast::types::Program>,
|
||||
) -> Result<(), KclError> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl GlobalState {
|
||||
@ -302,16 +348,7 @@ impl GlobalState {
|
||||
let mut global = GlobalState {
|
||||
path_to_source_id: Default::default(),
|
||||
module_infos: Default::default(),
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
artifacts: Default::default(),
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
artifact_commands: Default::default(),
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
artifact_responses: Default::default(),
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
artifact_graph: Default::default(),
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
operations: Default::default(),
|
||||
mod_loader: Default::default(),
|
||||
errors: Default::default(),
|
||||
id_to_source: Default::default(),
|
||||
@ -334,6 +371,14 @@ impl GlobalState {
|
||||
.insert(ModulePath::Local { value: root_path }, root_id);
|
||||
global
|
||||
}
|
||||
|
||||
pub(super) fn filenames(&self) -> IndexMap<ModuleId, ModulePath> {
|
||||
self.path_to_source_id.iter().map(|(k, v)| ((*v), k.clone())).collect()
|
||||
}
|
||||
|
||||
pub(super) fn get_source(&self, id: ModuleId) -> Option<&ModuleSource> {
|
||||
self.id_to_source.get(&id)
|
||||
}
|
||||
}
|
||||
|
||||
impl ModuleState {
|
||||
@ -342,6 +387,7 @@ impl ModuleState {
|
||||
id_generator: IdGenerator::new(module_id),
|
||||
stack: memory.new_stack(),
|
||||
pipe_value: Default::default(),
|
||||
being_declared: Default::default(),
|
||||
module_exports: Default::default(),
|
||||
explicit_length_units: false,
|
||||
path,
|
||||
@ -352,6 +398,13 @@ impl ModuleState {
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn variables(&self, main_ref: EnvironmentRef) -> IndexMap<String, KclValue> {
|
||||
self.stack
|
||||
.find_all_in_env(main_ref)
|
||||
.map(|(k, v)| (k.clone(), v.clone()))
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, Deserialize, Serialize, PartialEq, Eq, ts_rs::TS, JsonSchema)]
|
||||
@ -389,7 +442,7 @@ impl MetaSettings {
|
||||
self.kcl_version = value;
|
||||
}
|
||||
name => {
|
||||
return Err(KclError::Semantic(KclErrorDetails::new(
|
||||
return Err(KclError::new_semantic(KclErrorDetails::new(
|
||||
format!(
|
||||
"Unexpected settings key: `{name}`; expected one of `{}`, `{}`",
|
||||
annotations::SETTINGS_UNIT_LENGTH,
|
||||
|
@ -220,6 +220,7 @@ impl schemars::JsonSchema for TypedPath {
|
||||
///
|
||||
/// * Does **not** touch `..` or symlinks – call `canonicalize()` if you need that.
|
||||
/// * Returns an owned `PathBuf` only when normalisation was required.
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
fn normalise_import<S: AsRef<str>>(raw: S) -> std::path::PathBuf {
|
||||
let s = raw.as_ref();
|
||||
// On Unix we need to swap `\` → `/`. On Windows we leave it alone.
|
||||
|
@ -44,6 +44,10 @@ impl RuntimeType {
|
||||
RuntimeType::Primitive(PrimitiveType::Sketch)
|
||||
}
|
||||
|
||||
pub fn sketch_or_surface() -> Self {
|
||||
RuntimeType::Union(vec![Self::sketch(), Self::plane(), Self::face()])
|
||||
}
|
||||
|
||||
/// `[Sketch; 1+]`
|
||||
pub fn sketches() -> Self {
|
||||
RuntimeType::Array(
|
||||
@ -183,7 +187,7 @@ impl RuntimeType {
|
||||
};
|
||||
RuntimeType::Primitive(PrimitiveType::Number(ty))
|
||||
}
|
||||
AstPrimitiveType::Named(name) => Self::from_alias(&name.name, exec_state, source_range)?,
|
||||
AstPrimitiveType::Named { id } => Self::from_alias(&id.name, exec_state, source_range)?,
|
||||
AstPrimitiveType::Tag => RuntimeType::Primitive(PrimitiveType::Tag),
|
||||
AstPrimitiveType::ImportedGeometry => RuntimeType::Primitive(PrimitiveType::ImportedGeometry),
|
||||
AstPrimitiveType::Function(_) => RuntimeType::Primitive(PrimitiveType::Function),
|
||||
|
@ -28,7 +28,7 @@ impl Default for FileManager {
|
||||
impl FileSystem for FileManager {
|
||||
async fn read(&self, path: &TypedPath, source_range: SourceRange) -> Result<Vec<u8>, KclError> {
|
||||
tokio::fs::read(&path.0).await.map_err(|e| {
|
||||
KclError::Io(KclErrorDetails::new(
|
||||
KclError::new_io(KclErrorDetails::new(
|
||||
format!("Failed to read file `{}`: {}", path.display(), e),
|
||||
vec![source_range],
|
||||
))
|
||||
@ -37,7 +37,7 @@ impl FileSystem for FileManager {
|
||||
|
||||
async fn read_to_string(&self, path: &TypedPath, source_range: SourceRange) -> Result<String, KclError> {
|
||||
tokio::fs::read_to_string(&path.0).await.map_err(|e| {
|
||||
KclError::Io(KclErrorDetails::new(
|
||||
KclError::new_io(KclErrorDetails::new(
|
||||
format!("Failed to read file `{}`: {}", path.display(), e),
|
||||
vec![source_range],
|
||||
))
|
||||
@ -49,7 +49,7 @@ impl FileSystem for FileManager {
|
||||
if e.kind() == std::io::ErrorKind::NotFound {
|
||||
Ok(false)
|
||||
} else {
|
||||
Err(KclError::Io(KclErrorDetails::new(
|
||||
Err(KclError::new_io(KclErrorDetails::new(
|
||||
format!("Failed to check if file `{}` exists: {}", path.display(), e),
|
||||
vec![source_range],
|
||||
)))
|
||||
@ -71,7 +71,7 @@ impl FileSystem for FileManager {
|
||||
}
|
||||
|
||||
let mut read_dir = tokio::fs::read_dir(&path).await.map_err(|e| {
|
||||
KclError::Io(KclErrorDetails::new(
|
||||
KclError::new_io(KclErrorDetails::new(
|
||||
format!("Failed to read directory `{}`: {}", path.display(), e),
|
||||
vec![source_range],
|
||||
))
|
||||
|
@ -49,10 +49,10 @@ impl FileSystem for FileManager {
|
||||
let promise = self
|
||||
.manager
|
||||
.read_file(path.to_string_lossy())
|
||||
.map_err(|e| KclError::Engine(KclErrorDetails::new(e.to_string().into(), vec![source_range])))?;
|
||||
.map_err(|e| KclError::new_engine(KclErrorDetails::new(e.to_string().into(), vec![source_range])))?;
|
||||
|
||||
let value = JsFuture::from(promise).await.map_err(|e| {
|
||||
KclError::Engine(KclErrorDetails::new(
|
||||
KclError::new_engine(KclErrorDetails::new(
|
||||
format!("Failed to wait for promise from engine: {:?}", e),
|
||||
vec![source_range],
|
||||
))
|
||||
@ -67,7 +67,7 @@ impl FileSystem for FileManager {
|
||||
async fn read_to_string(&self, path: &TypedPath, source_range: SourceRange) -> Result<String, KclError> {
|
||||
let bytes = self.read(path, source_range).await?;
|
||||
let string = String::from_utf8(bytes).map_err(|e| {
|
||||
KclError::Engine(KclErrorDetails::new(
|
||||
KclError::new_engine(KclErrorDetails::new(
|
||||
format!("Failed to convert bytes to string: {:?}", e),
|
||||
vec![source_range],
|
||||
))
|
||||
@ -80,17 +80,17 @@ impl FileSystem for FileManager {
|
||||
let promise = self
|
||||
.manager
|
||||
.exists(path.to_string_lossy())
|
||||
.map_err(|e| KclError::Engine(KclErrorDetails::new(e.to_string().into(), vec![source_range])))?;
|
||||
.map_err(|e| KclError::new_engine(KclErrorDetails::new(e.to_string().into(), vec![source_range])))?;
|
||||
|
||||
let value = JsFuture::from(promise).await.map_err(|e| {
|
||||
KclError::Engine(KclErrorDetails::new(
|
||||
KclError::new_engine(KclErrorDetails::new(
|
||||
format!("Failed to wait for promise from engine: {:?}", e),
|
||||
vec![source_range],
|
||||
))
|
||||
})?;
|
||||
|
||||
let it_exists = value.as_bool().ok_or_else(|| {
|
||||
KclError::Engine(KclErrorDetails::new(
|
||||
KclError::new_engine(KclErrorDetails::new(
|
||||
"Failed to convert value to bool".to_string(),
|
||||
vec![source_range],
|
||||
))
|
||||
@ -107,24 +107,24 @@ impl FileSystem for FileManager {
|
||||
let promise = self
|
||||
.manager
|
||||
.get_all_files(path.to_string_lossy())
|
||||
.map_err(|e| KclError::Engine(KclErrorDetails::new(e.to_string().into(), vec![source_range])))?;
|
||||
.map_err(|e| KclError::new_engine(KclErrorDetails::new(e.to_string().into(), vec![source_range])))?;
|
||||
|
||||
let value = JsFuture::from(promise).await.map_err(|e| {
|
||||
KclError::Engine(KclErrorDetails::new(
|
||||
KclError::new_engine(KclErrorDetails::new(
|
||||
format!("Failed to wait for promise from javascript: {:?}", e),
|
||||
vec![source_range],
|
||||
))
|
||||
})?;
|
||||
|
||||
let s = value.as_string().ok_or_else(|| {
|
||||
KclError::Engine(KclErrorDetails::new(
|
||||
KclError::new_engine(KclErrorDetails::new(
|
||||
format!("Failed to get string from response from javascript: `{:?}`", value),
|
||||
vec![source_range],
|
||||
))
|
||||
})?;
|
||||
|
||||
let files: Vec<String> = serde_json::from_str(&s).map_err(|e| {
|
||||
KclError::Engine(KclErrorDetails::new(
|
||||
KclError::new_engine(KclErrorDetails::new(
|
||||
format!("Failed to parse json from javascript: `{}` `{:?}`", s, e),
|
||||
vec![source_range],
|
||||
))
|
||||
|
@ -175,11 +175,10 @@ impl Backend {
|
||||
zoo_client: kittycad::Client,
|
||||
can_send_telemetry: bool,
|
||||
) -> Result<Self, String> {
|
||||
let stdlib = crate::std::StdLib::new();
|
||||
let kcl_std = crate::docs::kcl_doc::walk_prelude();
|
||||
let stdlib_completions = get_completions_from_stdlib(&stdlib, &kcl_std).map_err(|e| e.to_string())?;
|
||||
let stdlib_signatures = get_signatures_from_stdlib(&stdlib, &kcl_std);
|
||||
let stdlib_args = get_arg_maps_from_stdlib(&stdlib, &kcl_std);
|
||||
let stdlib_completions = get_completions_from_stdlib(&kcl_std).map_err(|e| e.to_string())?;
|
||||
let stdlib_signatures = get_signatures_from_stdlib(&kcl_std);
|
||||
let stdlib_args = get_arg_maps_from_stdlib(&kcl_std);
|
||||
|
||||
Ok(Self {
|
||||
client,
|
||||
@ -1189,7 +1188,6 @@ impl LanguageServer for Backend {
|
||||
}
|
||||
|
||||
async fn completion(&self, params: CompletionParams) -> RpcResult<Option<CompletionResponse>> {
|
||||
// ADAM: This is the entrypoint.
|
||||
let mut completions = vec![CompletionItem {
|
||||
label: PIPE_OPERATOR.to_string(),
|
||||
label_details: None,
|
||||
@ -1635,16 +1633,8 @@ impl LanguageServer for Backend {
|
||||
}
|
||||
|
||||
/// Get completions from our stdlib.
|
||||
pub fn get_completions_from_stdlib(
|
||||
stdlib: &crate::std::StdLib,
|
||||
kcl_std: &ModData,
|
||||
) -> Result<HashMap<String, CompletionItem>> {
|
||||
pub fn get_completions_from_stdlib(kcl_std: &ModData) -> Result<HashMap<String, CompletionItem>> {
|
||||
let mut completions = HashMap::new();
|
||||
let combined = stdlib.combined();
|
||||
|
||||
for internal_fn in combined.values() {
|
||||
completions.insert(internal_fn.name(), internal_fn.to_completion_item()?);
|
||||
}
|
||||
|
||||
for d in kcl_std.all_docs() {
|
||||
if let Some(ci) = d.to_completion_item() {
|
||||
@ -1661,13 +1651,8 @@ pub fn get_completions_from_stdlib(
|
||||
}
|
||||
|
||||
/// Get signatures from our stdlib.
|
||||
pub fn get_signatures_from_stdlib(stdlib: &crate::std::StdLib, kcl_std: &ModData) -> HashMap<String, SignatureHelp> {
|
||||
pub fn get_signatures_from_stdlib(kcl_std: &ModData) -> HashMap<String, SignatureHelp> {
|
||||
let mut signatures = HashMap::new();
|
||||
let combined = stdlib.combined();
|
||||
|
||||
for internal_fn in combined.values() {
|
||||
signatures.insert(internal_fn.name(), internal_fn.to_signature_help());
|
||||
}
|
||||
|
||||
for d in kcl_std.all_docs() {
|
||||
if let Some(sig) = d.to_signature_help() {
|
||||
@ -1679,44 +1664,32 @@ pub fn get_signatures_from_stdlib(stdlib: &crate::std::StdLib, kcl_std: &ModData
|
||||
}
|
||||
|
||||
/// Get signatures from our stdlib.
|
||||
pub fn get_arg_maps_from_stdlib(
|
||||
stdlib: &crate::std::StdLib,
|
||||
kcl_std: &ModData,
|
||||
) -> HashMap<String, HashMap<String, String>> {
|
||||
pub fn get_arg_maps_from_stdlib(kcl_std: &ModData) -> HashMap<String, HashMap<String, String>> {
|
||||
let mut result = HashMap::new();
|
||||
let combined = stdlib.combined();
|
||||
|
||||
for internal_fn in combined.values() {
|
||||
let arg_map: HashMap<String, String> = internal_fn
|
||||
.args(false)
|
||||
.into_iter()
|
||||
for d in kcl_std.all_docs() {
|
||||
let crate::docs::kcl_doc::DocData::Fn(f) = d else {
|
||||
continue;
|
||||
};
|
||||
let arg_map: HashMap<String, String> = f
|
||||
.args
|
||||
.iter()
|
||||
.map(|data| {
|
||||
let mut tip = "```\n".to_owned();
|
||||
tip.push_str(&data.name.clone());
|
||||
if !data.required {
|
||||
tip.push('?');
|
||||
}
|
||||
if !data.type_.is_empty() {
|
||||
tip.push_str(": ");
|
||||
tip.push_str(&data.type_);
|
||||
}
|
||||
tip.push_str(&data.to_string());
|
||||
tip.push_str("\n```");
|
||||
if !data.description.is_empty() {
|
||||
if let Some(docs) = &data.docs {
|
||||
tip.push_str("\n\n");
|
||||
tip.push_str(&data.description);
|
||||
tip.push_str(docs);
|
||||
}
|
||||
(data.name, tip)
|
||||
(data.name.clone(), tip)
|
||||
})
|
||||
.collect();
|
||||
if !arg_map.is_empty() {
|
||||
result.insert(internal_fn.name(), arg_map);
|
||||
result.insert(f.name.clone(), arg_map);
|
||||
}
|
||||
}
|
||||
|
||||
for _d in kcl_std.all_docs() {
|
||||
// TODO add KCL std fns
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
|
@ -5,11 +5,10 @@ use tower_lsp::LanguageServer;
|
||||
|
||||
// Create a fake kcl lsp server for testing.
|
||||
pub async fn kcl_lsp_server(execute: bool) -> Result<crate::lsp::kcl::Backend> {
|
||||
let stdlib = crate::std::StdLib::new();
|
||||
let kcl_std = crate::docs::kcl_doc::walk_prelude();
|
||||
let stdlib_completions = crate::lsp::kcl::get_completions_from_stdlib(&stdlib, &kcl_std)?;
|
||||
let stdlib_signatures = crate::lsp::kcl::get_signatures_from_stdlib(&stdlib, &kcl_std);
|
||||
let stdlib_args = crate::lsp::kcl::get_arg_maps_from_stdlib(&stdlib, &kcl_std);
|
||||
let stdlib_completions = crate::lsp::kcl::get_completions_from_stdlib(&kcl_std)?;
|
||||
let stdlib_signatures = crate::lsp::kcl::get_signatures_from_stdlib(&kcl_std);
|
||||
let stdlib_args = crate::lsp::kcl::get_arg_maps_from_stdlib(&kcl_std);
|
||||
|
||||
let zoo_client = crate::engine::new_zoo_client(None, None)?;
|
||||
|
||||
|
@ -928,7 +928,7 @@ startSketchOn(XY)
|
||||
match hover.unwrap().contents {
|
||||
tower_lsp::lsp_types::HoverContents::Markup(tower_lsp::lsp_types::MarkupContent { value, .. }) => {
|
||||
assert!(value.contains("startSketchOn"));
|
||||
assert!(value.contains(": SketchSurface"));
|
||||
assert!(value.contains(": Plane | Face"));
|
||||
assert!(value.contains("Start a new 2-dimensional sketch on a specific"));
|
||||
}
|
||||
_ => unreachable!(),
|
||||
@ -1113,13 +1113,7 @@ async fn test_kcl_lsp_signature_help() {
|
||||
"Expected one signature, got {:?}",
|
||||
signature_help.signatures
|
||||
);
|
||||
assert_eq!(
|
||||
signature_help.signatures[0].label,
|
||||
r#"startSketchOn(
|
||||
@planeOrSolid: SketchData,
|
||||
face?: FaceTag,
|
||||
): SketchSurface"#
|
||||
);
|
||||
assert!(signature_help.signatures[0].label.starts_with("startSketchOn"));
|
||||
} else {
|
||||
panic!("Expected signature help");
|
||||
}
|
||||
@ -1202,17 +1196,7 @@ a1 = startSketchOn(offsetPlane(XY, offset = 10))
|
||||
"Expected one signature, got {:?}",
|
||||
signature_help.signatures
|
||||
);
|
||||
assert_eq!(
|
||||
signature_help.signatures[0].label,
|
||||
r#"extrude(
|
||||
@sketches: [Sketch],
|
||||
length: number,
|
||||
symmetric?: bool,
|
||||
bidirectionalLength?: number,
|
||||
tagStart?: TagNode,
|
||||
tagEnd?: TagNode,
|
||||
): [Solid]"#
|
||||
);
|
||||
assert!(signature_help.signatures[0].label.starts_with("extrude"));
|
||||
} else {
|
||||
panic!("Expected signature help");
|
||||
}
|
||||
@ -1300,17 +1284,7 @@ a1 = startSketchOn(offsetPlane(XY, offset = 10))
|
||||
"Expected one signature, got {:?}",
|
||||
signature_help.signatures
|
||||
);
|
||||
assert_eq!(
|
||||
signature_help.signatures[0].label,
|
||||
r#"extrude(
|
||||
@sketches: [Sketch],
|
||||
length: number,
|
||||
symmetric?: bool,
|
||||
bidirectionalLength?: number,
|
||||
tagStart?: TagNode,
|
||||
tagEnd?: TagNode,
|
||||
): [Solid]"#
|
||||
);
|
||||
assert!(signature_help.signatures[0].label.starts_with("extrude"));
|
||||
} else {
|
||||
panic!("Expected signature help");
|
||||
}
|
||||
@ -1393,17 +1367,7 @@ a1 = startSketchOn(offsetPlane(XY, offset = 10))
|
||||
"Expected one signature, got {:?}",
|
||||
signature_help.signatures
|
||||
);
|
||||
assert_eq!(
|
||||
signature_help.signatures[0].label,
|
||||
r#"extrude(
|
||||
@sketches: [Sketch],
|
||||
length: number,
|
||||
symmetric?: bool,
|
||||
bidirectionalLength?: number,
|
||||
tagStart?: TagNode,
|
||||
tagEnd?: TagNode,
|
||||
): [Solid]"#
|
||||
);
|
||||
assert!(signature_help.signatures[0].label.starts_with("extrude"));
|
||||
} else {
|
||||
panic!("Expected signature help");
|
||||
}
|
||||
@ -1491,17 +1455,7 @@ a1 = startSketchOn(offsetPlane(XY, offset = 10))
|
||||
"Expected one signature, got {:?}",
|
||||
signature_help.signatures
|
||||
);
|
||||
assert_eq!(
|
||||
signature_help.signatures[0].label,
|
||||
r#"extrude(
|
||||
@sketches: [Sketch],
|
||||
length: number,
|
||||
symmetric?: bool,
|
||||
bidirectionalLength?: number,
|
||||
tagStart?: TagNode,
|
||||
tagEnd?: TagNode,
|
||||
): [Solid]"#
|
||||
);
|
||||
assert!(signature_help.signatures[0].label.starts_with("extrude"));
|
||||
} else {
|
||||
panic!("Expected signature help");
|
||||
}
|
||||
@ -3924,7 +3878,7 @@ startSketchOn(XY)
|
||||
match hover.unwrap().contents {
|
||||
tower_lsp::lsp_types::HoverContents::Markup(tower_lsp::lsp_types::MarkupContent { value, .. }) => {
|
||||
assert!(value.contains("startSketchOn"));
|
||||
assert!(value.contains(": SketchSurface"));
|
||||
assert!(value.contains(": Plane | Face"));
|
||||
assert!(value.contains("Start a new 2-dimensional sketch on a specific"));
|
||||
}
|
||||
_ => unreachable!(),
|
||||
|
@ -58,7 +58,7 @@ impl ModuleLoader {
|
||||
}
|
||||
|
||||
pub(crate) fn import_cycle_error(&self, path: &ModulePath, source_range: SourceRange) -> KclError {
|
||||
KclError::ImportCycle(KclErrorDetails::new(
|
||||
KclError::new_import_cycle(KclErrorDetails::new(
|
||||
format!(
|
||||
"circular import of modules is not allowed: {} -> {}",
|
||||
self.import_stack
|
||||
@ -163,7 +163,7 @@ impl ModulePath {
|
||||
ModulePath::Std { value: name } => Ok(ModuleSource {
|
||||
source: read_std(name)
|
||||
.ok_or_else(|| {
|
||||
KclError::Semantic(KclErrorDetails::new(
|
||||
KclError::new_semantic(KclErrorDetails::new(
|
||||
format!("Cannot find standard library module to import: std::{name}."),
|
||||
vec![source_range],
|
||||
))
|
||||
@ -202,7 +202,7 @@ impl ModulePath {
|
||||
ModulePath::Std { .. } => {
|
||||
let message = format!("Cannot import a non-std KCL file from std: {path}.");
|
||||
debug_assert!(false, "{}", &message);
|
||||
return Err(KclError::Internal(KclErrorDetails::new(message, vec![])));
|
||||
return Err(KclError::new_internal(KclErrorDetails::new(message, vec![])));
|
||||
}
|
||||
};
|
||||
|
||||
@ -217,7 +217,7 @@ impl ModulePath {
|
||||
if path.len() != 2 || path[0] != "std" {
|
||||
let message = format!("Invalid std import path: {path:?}.");
|
||||
debug_assert!(false, "{}", &message);
|
||||
return Err(KclError::Internal(KclErrorDetails::new(message, vec![])));
|
||||
return Err(KclError::new_internal(KclErrorDetails::new(message, vec![])));
|
||||
}
|
||||
|
||||
Ok(ModulePath::Std { value: path[1].clone() })
|
||||
|
@ -228,7 +228,7 @@ impl PrimitiveType {
|
||||
let mut hasher = Sha256::new();
|
||||
match self {
|
||||
PrimitiveType::Any => hasher.update(b"any"),
|
||||
PrimitiveType::Named(id) => hasher.update(id.compute_digest()),
|
||||
PrimitiveType::Named { id } => hasher.update(id.compute_digest()),
|
||||
PrimitiveType::String => hasher.update(b"string"),
|
||||
PrimitiveType::Number(suffix) => hasher.update(suffix.digestable_id()),
|
||||
PrimitiveType::Boolean => hasher.update(b"bool"),
|
||||
|
@ -25,7 +25,6 @@ pub use crate::parsing::ast::types::{
|
||||
none::KclNone,
|
||||
};
|
||||
use crate::{
|
||||
docs::StdLibFn,
|
||||
errors::KclError,
|
||||
execution::{
|
||||
annotations,
|
||||
@ -454,7 +453,7 @@ impl Node<Program> {
|
||||
alpha: c.a,
|
||||
},
|
||||
};
|
||||
if colors.borrow().iter().any(|c| *c == color) {
|
||||
if colors.borrow().contains(&color) {
|
||||
return;
|
||||
}
|
||||
colors.borrow_mut().push(color);
|
||||
@ -529,7 +528,7 @@ impl Node<Program> {
|
||||
let new_color = csscolorparser::Color::new(color.red, color.green, color.blue, color.alpha);
|
||||
Ok(Some(ColorPresentation {
|
||||
// The label will be what they replace the color with.
|
||||
label: new_color.to_hex_string(),
|
||||
label: new_color.to_css_hex(),
|
||||
text_edit: None,
|
||||
additional_text_edits: None,
|
||||
}))
|
||||
@ -1950,6 +1949,10 @@ impl CallExpressionKw {
|
||||
}
|
||||
|
||||
pub fn replace_value(&mut self, source_range: SourceRange, new_value: Expr) {
|
||||
if let Some(unlabeled) = &mut self.unlabeled {
|
||||
unlabeled.replace_value(source_range, new_value.clone());
|
||||
}
|
||||
|
||||
for arg in &mut self.arguments {
|
||||
arg.arg.replace_value(source_range, new_value.clone());
|
||||
}
|
||||
@ -1959,37 +1962,16 @@ impl CallExpressionKw {
|
||||
fn rename_identifiers(&mut self, old_name: &str, new_name: &str) {
|
||||
self.callee.rename(old_name, new_name);
|
||||
|
||||
if let Some(unlabeled) = &mut self.unlabeled {
|
||||
unlabeled.rename_identifiers(old_name, new_name);
|
||||
}
|
||||
|
||||
for arg in &mut self.arguments {
|
||||
arg.arg.rename_identifiers(old_name, new_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A function declaration.
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize, ts_rs::TS, JsonSchema)]
|
||||
#[ts(export)]
|
||||
#[serde(tag = "type")]
|
||||
pub enum Function {
|
||||
/// A stdlib function written in Rust (aka core lib).
|
||||
StdLib {
|
||||
/// The function.
|
||||
func: Box<dyn StdLibFn>,
|
||||
},
|
||||
/// A function that is defined in memory.
|
||||
#[default]
|
||||
InMemory,
|
||||
}
|
||||
|
||||
impl PartialEq for Function {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
match (self, other) {
|
||||
(Function::StdLib { func: func1 }, Function::StdLib { func: func2 }) => func1.name() == func2.name(),
|
||||
(Function::InMemory, Function::InMemory) => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, Copy, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema, FromStr, Display)]
|
||||
#[ts(export)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
@ -3222,7 +3204,7 @@ pub enum PrimitiveType {
|
||||
/// `fn`, type of functions.
|
||||
Function(FunctionType),
|
||||
/// An identifier used as a type (not really a primitive type, but whatever).
|
||||
Named(Node<Identifier>),
|
||||
Named { id: Node<Identifier> },
|
||||
}
|
||||
|
||||
impl PrimitiveType {
|
||||
@ -3278,7 +3260,7 @@ impl fmt::Display for PrimitiveType {
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
PrimitiveType::Named(n) => write!(f, "{}", n.name),
|
||||
PrimitiveType::Named { id: n } => write!(f, "{}", n.name),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -4343,7 +4325,7 @@ startSketchOn(XY)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn module_name() {
|
||||
fn test_module_name() {
|
||||
#[track_caller]
|
||||
fn assert_mod_name(stmt: &str, name: &str) {
|
||||
let tokens = crate::parsing::token::lex(stmt, ModuleId::default()).unwrap();
|
||||
@ -4357,4 +4339,36 @@ startSketchOn(XY)
|
||||
assert_mod_name("import 'foo/main.kcl'", "foo");
|
||||
assert_mod_name("import 'foo\\bar\\main.kcl'", "bar");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_rename_in_math_in_std_function() {
|
||||
let code = r#"rise = 4.5
|
||||
run = 8
|
||||
angle = atan(rise / run)"#;
|
||||
let mut program = crate::parsing::top_level_parse(code).unwrap();
|
||||
|
||||
// We want to rename `run` to `run2`.
|
||||
let run = program.body.get(1).unwrap().clone();
|
||||
let BodyItem::VariableDeclaration(var_decl) = &run else {
|
||||
panic!("expected a variable declaration")
|
||||
};
|
||||
let Expr::Literal(lit) = &var_decl.declaration.init else {
|
||||
panic!("expected a literal");
|
||||
};
|
||||
assert_eq!(lit.raw, "8");
|
||||
|
||||
// Rename it.
|
||||
program.rename_symbol("yoyo", var_decl.as_source_range().start() + 1);
|
||||
|
||||
// Recast the program to a string.
|
||||
let formatted = program.recast(&Default::default(), 0);
|
||||
|
||||
assert_eq!(
|
||||
formatted,
|
||||
r#"rise = 4.5
|
||||
yoyo = 8
|
||||
angle = atan(rise / yoyo)
|
||||
"#
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -51,7 +51,7 @@ pub fn parse_tokens(mut tokens: TokenStream) -> ParseResult {
|
||||
} else {
|
||||
format!("found unknown tokens [{}]", token_list.join(", "))
|
||||
};
|
||||
return KclError::Lexical(KclErrorDetails::new(message, source_ranges)).into();
|
||||
return KclError::new_lexical(KclErrorDetails::new(message, source_ranges)).into();
|
||||
}
|
||||
|
||||
// Important, to not call this before the unknown tokens check.
|
||||
@ -110,7 +110,7 @@ impl ParseResult {
|
||||
let (p, errs) = self.0?;
|
||||
|
||||
if let Some(err) = errs.iter().find(|e| e.severity.is_err()) {
|
||||
return Err(KclError::Syntax(err.clone().into()));
|
||||
return Err(KclError::new_syntax(err.clone().into()));
|
||||
}
|
||||
match p {
|
||||
Some(p) => Ok(p),
|
||||
|
@ -979,12 +979,18 @@ fn property_separator(i: &mut TokenSlice) -> ModalResult<()> {
|
||||
}
|
||||
|
||||
/// Match something that separates the labeled arguments of a fn call.
|
||||
fn labeled_arg_separator(i: &mut TokenSlice) -> ModalResult<()> {
|
||||
/// Returns the source range of the erroneous separator, if any was found.
|
||||
fn labeled_arg_separator(i: &mut TokenSlice) -> ModalResult<Option<SourceRange>> {
|
||||
alt((
|
||||
// Normally you need a comma.
|
||||
comma_sep,
|
||||
comma_sep.map(|_| None),
|
||||
// But, if the argument list is ending, no need for a comma.
|
||||
peek(preceded(opt(whitespace), close_paren)).void(),
|
||||
peek(preceded(opt(whitespace), close_paren)).void().map(|_| None),
|
||||
whitespace.map(|mut tokens| {
|
||||
// Safe to unwrap here because `whitespace` is guaranteed to return at least 1 whitespace.
|
||||
let first_token = tokens.pop().unwrap();
|
||||
Some(SourceRange::from(&first_token))
|
||||
}),
|
||||
))
|
||||
.parse_next(i)
|
||||
}
|
||||
@ -2474,32 +2480,13 @@ impl TryFrom<Token> for Node<TagDeclarator> {
|
||||
}
|
||||
}
|
||||
|
||||
impl Node<TagDeclarator> {
|
||||
fn into_valid_binding_name(self) -> Result<Self, CompilationError> {
|
||||
// Make sure they are not assigning a variable to a stdlib function.
|
||||
if crate::std::name_in_stdlib(&self.name) {
|
||||
return Err(CompilationError::fatal(
|
||||
SourceRange::from(&self),
|
||||
format!("Cannot assign a tag to a reserved keyword: {}", self.name),
|
||||
));
|
||||
}
|
||||
Ok(self)
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse a Kcl tag that starts with a `$`.
|
||||
fn tag(i: &mut TokenSlice) -> ModalResult<Node<TagDeclarator>> {
|
||||
dollar.parse_next(i)?;
|
||||
let tag_declarator = any
|
||||
.try_map(Node::<TagDeclarator>::try_from)
|
||||
any.try_map(Node::<TagDeclarator>::try_from)
|
||||
.context(expected("a tag, e.g. '$seg01' or '$line01'"))
|
||||
.parse_next(i)
|
||||
.map_err(|e: ErrMode<ContextError>| e.cut())?;
|
||||
// Now that we've parsed a tag declarator, verify that it's not a stdlib
|
||||
// name. If it is, stop backtracking.
|
||||
tag_declarator
|
||||
.into_valid_binding_name()
|
||||
.map_err(|e| ErrMode::Cut(ContextError::from(e)))
|
||||
.map_err(|e: ErrMode<ContextError>| e.cut())
|
||||
}
|
||||
|
||||
/// Helper function. Matches any number of whitespace tokens and ignores them.
|
||||
@ -2932,7 +2919,7 @@ fn primitive_type(i: &mut TokenSlice) -> ModalResult<Node<PrimitiveType>> {
|
||||
(identifier, opt(delimited(open_paren, uom_for_type, close_paren))).map(|(ident, suffix)| {
|
||||
let mut result = Node::new(PrimitiveType::Boolean, ident.start, ident.end, ident.module_id);
|
||||
result.inner =
|
||||
PrimitiveType::primitive_from_str(&ident.name, suffix).unwrap_or(PrimitiveType::Named(ident));
|
||||
PrimitiveType::primitive_from_str(&ident.name, suffix).unwrap_or(PrimitiveType::Named { id: ident });
|
||||
result
|
||||
}),
|
||||
))
|
||||
@ -3135,7 +3122,7 @@ fn binding_name(i: &mut TokenSlice) -> ModalResult<Node<Identifier>> {
|
||||
|
||||
/// Either a positional or keyword function call.
|
||||
fn fn_call_pos_or_kw(i: &mut TokenSlice) -> ModalResult<Expr> {
|
||||
alt((fn_call_kw.map(Box::new).map(Expr::CallExpressionKw),)).parse_next(i)
|
||||
fn_call_kw.map(Box::new).map(Expr::CallExpressionKw).parse_next(i)
|
||||
}
|
||||
|
||||
fn labelled_fn_call(i: &mut TokenSlice) -> ModalResult<Expr> {
|
||||
@ -3198,7 +3185,7 @@ fn fn_call_kw(i: &mut TokenSlice) -> ModalResult<Node<CallExpressionKw>> {
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
enum ArgPlace {
|
||||
NonCode(Node<NonCodeNode>),
|
||||
LabeledArg(LabeledArg),
|
||||
LabeledArg((LabeledArg, Option<SourceRange>)),
|
||||
UnlabeledArg(Expr),
|
||||
Keyword(Token),
|
||||
}
|
||||
@ -3208,7 +3195,7 @@ fn fn_call_kw(i: &mut TokenSlice) -> ModalResult<Node<CallExpressionKw>> {
|
||||
alt((
|
||||
terminated(non_code_node.map(ArgPlace::NonCode), whitespace),
|
||||
terminated(any_keyword.map(ArgPlace::Keyword), whitespace),
|
||||
terminated(labeled_argument, labeled_arg_separator).map(ArgPlace::LabeledArg),
|
||||
(labeled_argument, labeled_arg_separator).map(ArgPlace::LabeledArg),
|
||||
expression.map(ArgPlace::UnlabeledArg),
|
||||
)),
|
||||
)
|
||||
@ -3220,7 +3207,16 @@ fn fn_call_kw(i: &mut TokenSlice) -> ModalResult<Node<CallExpressionKw>> {
|
||||
ArgPlace::NonCode(x) => {
|
||||
non_code_nodes.insert(index, vec![x]);
|
||||
}
|
||||
ArgPlace::LabeledArg(x) => {
|
||||
ArgPlace::LabeledArg((x, bad_token_source_range)) => {
|
||||
if let Some(bad_token_source_range) = bad_token_source_range {
|
||||
return Err(ErrMode::Cut(
|
||||
CompilationError::fatal(
|
||||
bad_token_source_range,
|
||||
"Missing comma between arguments, try adding a comma in",
|
||||
)
|
||||
.into(),
|
||||
));
|
||||
}
|
||||
args.push(x);
|
||||
}
|
||||
ArgPlace::Keyword(kw) => {
|
||||
@ -3255,7 +3251,22 @@ fn fn_call_kw(i: &mut TokenSlice) -> ModalResult<Node<CallExpressionKw>> {
|
||||
)?;
|
||||
ignore_whitespace(i);
|
||||
opt(comma_sep).parse_next(i)?;
|
||||
let end = close_paren.parse_next(i)?.end;
|
||||
let end = match close_paren.parse_next(i) {
|
||||
Ok(tok) => tok.end,
|
||||
Err(e) => {
|
||||
if let Some(tok) = i.next_token() {
|
||||
return Err(ErrMode::Cut(
|
||||
CompilationError::fatal(
|
||||
SourceRange::from(&tok),
|
||||
format!("There was an unexpected {}. Try removing it.", tok.value),
|
||||
)
|
||||
.into(),
|
||||
));
|
||||
} else {
|
||||
return Err(e);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Validate there aren't any duplicate labels.
|
||||
let mut counted_labels = IndexMap::with_capacity(args.len());
|
||||
@ -3376,8 +3387,7 @@ mod tests {
|
||||
fn kw_call_as_operand() {
|
||||
let tokens = crate::parsing::token::lex("f(x = 1)", ModuleId::default()).unwrap();
|
||||
let tokens = tokens.as_slice();
|
||||
let op = operand.parse(tokens).unwrap();
|
||||
println!("{op:#?}");
|
||||
operand.parse(tokens).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -4383,7 +4393,7 @@ secondExtrude = startSketchOn(XY)
|
||||
#[test]
|
||||
fn test_parse_parens_unicode() {
|
||||
let result = crate::parsing::top_level_parse("(ޜ");
|
||||
let KclError::Lexical(details) = result.0.unwrap_err() else {
|
||||
let KclError::Lexical { details } = result.0.unwrap_err() else {
|
||||
panic!();
|
||||
};
|
||||
// TODO: Better errors when program cannot tokenize.
|
||||
@ -4417,8 +4427,8 @@ z(-[["#,
|
||||
assert_err(
|
||||
r#"z
|
||||
(--#"#,
|
||||
"Unexpected token: (",
|
||||
[2, 3],
|
||||
"There was an unexpected -. Try removing it.",
|
||||
[3, 4],
|
||||
);
|
||||
}
|
||||
|
||||
@ -4869,19 +4879,6 @@ let myBox = box(p=[0,0], h=-3, l=-16, w=-10)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_tag_named_std_lib() {
|
||||
let some_program_string = r#"startSketchOn(XY)
|
||||
|> startProfile(at = [0, 0])
|
||||
|> line(%, end = [5, 5], tag = $xLine)
|
||||
"#;
|
||||
assert_err(
|
||||
some_program_string,
|
||||
"Cannot assign a tag to a reserved keyword: xLine",
|
||||
[86, 92],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_empty_tag_brace() {
|
||||
let some_program_string = r#"startSketchOn(XY)
|
||||
@ -5133,6 +5130,27 @@ bar = 1
|
||||
assert_eq!(actual.operator, UnaryOperator::Not);
|
||||
crate::parsing::top_level_parse(some_program_string).unwrap(); // Updated import path
|
||||
}
|
||||
#[test]
|
||||
fn test_sensible_error_when_missing_comma_between_fn_args() {
|
||||
let program_source = "startSketchOn(XY)
|
||||
|> arc(
|
||||
endAbsolute = [0, 50]
|
||||
interiorAbsolute = [-50, 0]
|
||||
)";
|
||||
let expected_src_start = program_source.find("]").unwrap();
|
||||
let tokens = crate::parsing::token::lex(program_source, ModuleId::default()).unwrap();
|
||||
ParseContext::init();
|
||||
let err = program
|
||||
.parse(tokens.as_slice())
|
||||
.expect_err("Program succeeded, but it should have failed");
|
||||
let cause = err
|
||||
.inner()
|
||||
.cause
|
||||
.as_ref()
|
||||
.expect("Found an error, but there was no cause. Add a cause.");
|
||||
assert_eq!(cause.message, "Missing comma between arguments, try adding a comma in",);
|
||||
assert_eq!(cause.source_range.start() - 1, expected_src_start);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sensible_error_when_missing_rhs_of_kw_arg() {
|
||||
@ -5152,14 +5170,32 @@ bar = 1
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sensible_error_when_unexpected_token_in_fn_call() {
|
||||
let program_source = "1
|
||||
|> extrude(
|
||||
length=depth,
|
||||
})";
|
||||
let expected_src_start = program_source.find("}").expect("Program should have an extraneous }");
|
||||
let tokens = crate::parsing::token::lex(program_source, ModuleId::default()).unwrap();
|
||||
ParseContext::init();
|
||||
let err = program.parse(tokens.as_slice()).unwrap_err();
|
||||
let cause = err
|
||||
.inner()
|
||||
.cause
|
||||
.as_ref()
|
||||
.expect("Found an error, but there was no cause. Add a cause.");
|
||||
assert_eq!(cause.message, "There was an unexpected }. Try removing it.",);
|
||||
assert_eq!(cause.source_range.start(), expected_src_start);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sensible_error_when_using_keyword_as_arg_label() {
|
||||
for (i, program) in ["pow(2, fn = 8)"].into_iter().enumerate() {
|
||||
let tokens = crate::parsing::token::lex(program, ModuleId::default()).unwrap();
|
||||
let err = match fn_call_kw.parse(tokens.as_slice()) {
|
||||
Err(e) => e,
|
||||
Ok(ast) => {
|
||||
eprintln!("{ast:#?}");
|
||||
Ok(_ast) => {
|
||||
panic!("Expected this to error but it didn't");
|
||||
}
|
||||
};
|
||||
|
@ -597,7 +597,7 @@ impl From<ParseError<Input<'_>, winnow::error::ContextError>> for KclError {
|
||||
// This is an offset, not an index, and may point to
|
||||
// the end of input (input.len()) on eof errors.
|
||||
|
||||
return KclError::Lexical(crate::errors::KclErrorDetails::new(
|
||||
return KclError::new_lexical(crate::errors::KclErrorDetails::new(
|
||||
"unexpected EOF while parsing".to_owned(),
|
||||
vec![SourceRange::new(offset, offset, module_id)],
|
||||
));
|
||||
@ -608,7 +608,7 @@ impl From<ParseError<Input<'_>, winnow::error::ContextError>> for KclError {
|
||||
let bad_token = &input[offset];
|
||||
// TODO: Add the Winnow parser context to the error.
|
||||
// See https://github.com/KittyCAD/modeling-app/issues/784
|
||||
KclError::Lexical(crate::errors::KclErrorDetails::new(
|
||||
KclError::new_lexical(crate::errors::KclErrorDetails::new(
|
||||
format!("found unknown token '{}'", bad_token),
|
||||
vec![SourceRange::new(offset, offset + 1, module_id)],
|
||||
))
|
||||
|
@ -163,7 +163,7 @@ async fn execute_test(test: &Test, render_to_png: bool, export_step: bool) {
|
||||
// Run the program.
|
||||
let exec_res = crate::test_server::execute_and_snapshot_ast(ast, Some(test.entry_point.clone()), export_step).await;
|
||||
match exec_res {
|
||||
Ok((exec_state, env_ref, png, step)) => {
|
||||
Ok((exec_state, ctx, env_ref, png, step)) => {
|
||||
let fail_path = test.output_dir.join("execution_error.snap");
|
||||
if std::fs::exists(&fail_path).unwrap() {
|
||||
panic!("This test case is expected to fail, but it passed. If this is intended, and the test should actually be passing now, please delete kcl-lib/{}", fail_path.to_string_lossy())
|
||||
@ -181,7 +181,7 @@ async fn execute_test(test: &Test, render_to_png: bool, export_step: bool) {
|
||||
panic!("Step data was empty");
|
||||
}
|
||||
}
|
||||
let outcome = exec_state.to_exec_outcome(env_ref).await;
|
||||
let outcome = exec_state.to_exec_outcome(env_ref, &ctx).await;
|
||||
|
||||
let mem_result = catch_unwind(AssertUnwindSafe(|| {
|
||||
assert_snapshot(test, "Variables in memory after executing", || {
|
||||
@ -3483,3 +3483,66 @@ mod spheres {
|
||||
super::execute(TEST_NAME, true).await
|
||||
}
|
||||
}
|
||||
mod var_ref_in_own_def {
|
||||
const TEST_NAME: &str = "var_ref_in_own_def";
|
||||
|
||||
/// Test parsing KCL.
|
||||
#[test]
|
||||
fn parse() {
|
||||
super::parse(TEST_NAME)
|
||||
}
|
||||
|
||||
/// Test that parsing and unparsing KCL produces the original KCL input.
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn unparse() {
|
||||
super::unparse(TEST_NAME).await
|
||||
}
|
||||
|
||||
/// Test that KCL is executed correctly.
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn kcl_test_execute() {
|
||||
super::execute(TEST_NAME, true).await
|
||||
}
|
||||
}
|
||||
mod ascription_unknown_type {
|
||||
const TEST_NAME: &str = "ascription_unknown_type";
|
||||
|
||||
/// Test parsing KCL.
|
||||
#[test]
|
||||
fn parse() {
|
||||
super::parse(TEST_NAME)
|
||||
}
|
||||
|
||||
/// Test that parsing and unparsing KCL produces the original KCL input.
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn unparse() {
|
||||
super::unparse(TEST_NAME).await
|
||||
}
|
||||
|
||||
/// Test that KCL is executed correctly.
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn kcl_test_execute() {
|
||||
super::execute(TEST_NAME, true).await
|
||||
}
|
||||
}
|
||||
mod var_ref_in_own_def_decl {
|
||||
const TEST_NAME: &str = "var_ref_in_own_def_decl";
|
||||
|
||||
/// Test parsing KCL.
|
||||
#[test]
|
||||
fn parse() {
|
||||
super::parse(TEST_NAME)
|
||||
}
|
||||
|
||||
/// Test that parsing and unparsing KCL produces the original KCL input.
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn unparse() {
|
||||
super::unparse(TEST_NAME).await
|
||||
}
|
||||
|
||||
/// Test that KCL is executed correctly.
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn kcl_test_execute() {
|
||||
super::execute(TEST_NAME, true).await
|
||||
}
|
||||
}
|
||||
|
@ -30,7 +30,7 @@ pub async fn hex_string(exec_state: &mut ExecState, args: Args) -> Result<KclVal
|
||||
|
||||
// Make sure the color if set is valid.
|
||||
if let Some(component) = rgb.iter().find(|component| component.n < 0.0 || component.n > 255.0) {
|
||||
return Err(KclError::Semantic(KclErrorDetails::new(
|
||||
return Err(KclError::new_semantic(KclErrorDetails::new(
|
||||
format!("Colors are given between 0 and 255, so {} is invalid", component.n),
|
||||
vec![args.source_range],
|
||||
)));
|
||||
@ -62,7 +62,7 @@ pub async fn appearance(exec_state: &mut ExecState, args: Args) -> Result<KclVal
|
||||
|
||||
// Make sure the color if set is valid.
|
||||
if !HEX_REGEX.is_match(&color) {
|
||||
return Err(KclError::Semantic(KclErrorDetails::new(
|
||||
return Err(KclError::new_semantic(KclErrorDetails::new(
|
||||
format!("Invalid hex color (`{}`), try something like `#fff000`", color),
|
||||
vec![args.source_range],
|
||||
)));
|
||||
@ -93,7 +93,7 @@ async fn inner_appearance(
|
||||
for solid_id in solids.ids(&args.ctx).await? {
|
||||
// Set the material properties.
|
||||
let rgb = rgba_simple::RGB::<f32>::from_hex(&color).map_err(|err| {
|
||||
KclError::Semantic(KclErrorDetails::new(
|
||||
KclError::new_semantic(KclErrorDetails::new(
|
||||
format!("Invalid hex color (`{color}`): {err}"),
|
||||
vec![args.source_range],
|
||||
))
|
||||
|
@ -28,6 +28,8 @@ use crate::{
|
||||
ModuleId,
|
||||
};
|
||||
|
||||
use super::fillet::EdgeReference;
|
||||
|
||||
const ERROR_STRING_SKETCH_TO_SOLID_HELPER: &str =
|
||||
"You can convert a sketch (2D) into a Solid (3D) by calling a function like `extrude` or `revolve`";
|
||||
|
||||
@ -121,7 +123,7 @@ impl Args {
|
||||
}
|
||||
|
||||
T::from_kcl_val(&arg.value).map(Some).ok_or_else(|| {
|
||||
KclError::Type(KclErrorDetails::new(
|
||||
KclError::new_type(KclErrorDetails::new(
|
||||
format!(
|
||||
"The arg {label} was given, but it was the wrong type. It should be type {} but it was {}",
|
||||
tynm::type_name::<T>(),
|
||||
@ -141,9 +143,14 @@ impl Args {
|
||||
where
|
||||
T: for<'a> FromKclValue<'a>,
|
||||
{
|
||||
if self.kw_args.labeled.get(label).is_none() {
|
||||
return Ok(None);
|
||||
};
|
||||
match self.kw_args.labeled.get(label) {
|
||||
None => return Ok(None),
|
||||
Some(a) => {
|
||||
if let KclValue::KclNone { .. } = &a.value {
|
||||
return Ok(None);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.get_kw_arg_typed(label, ty, exec_state).map(Some)
|
||||
}
|
||||
@ -154,7 +161,7 @@ impl Args {
|
||||
T: FromKclValue<'a>,
|
||||
{
|
||||
self.get_kw_arg_opt(label)?.ok_or_else(|| {
|
||||
KclError::Semantic(KclErrorDetails::new(
|
||||
KclError::new_semantic(KclErrorDetails::new(
|
||||
format!("This function requires a keyword argument '{label}'"),
|
||||
vec![self.source_range],
|
||||
))
|
||||
@ -171,8 +178,8 @@ impl Args {
|
||||
T: for<'a> FromKclValue<'a>,
|
||||
{
|
||||
let Some(arg) = self.kw_args.labeled.get(label) else {
|
||||
return Err(KclError::Semantic(KclErrorDetails::new(
|
||||
format!("This function requires a keyword argument '{label}'"),
|
||||
return Err(KclError::new_semantic(KclErrorDetails::new(
|
||||
format!("This function requires a keyword argument `{label}`"),
|
||||
vec![self.source_range],
|
||||
)));
|
||||
};
|
||||
@ -184,7 +191,7 @@ impl Args {
|
||||
.map(|t| t.to_string())
|
||||
.unwrap_or_else(|| arg.value.human_friendly_type().to_owned());
|
||||
let msg_base = format!(
|
||||
"This function expected the input argument to be {} but it's actually of type {actual_type_name}",
|
||||
"This function expected its `{label}` argument to be {} but it's actually of type {actual_type_name}",
|
||||
ty.human_friendly_type(),
|
||||
);
|
||||
let suggestion = match (ty, actual_type) {
|
||||
@ -205,7 +212,7 @@ impl Args {
|
||||
if message.contains("one or more Solids or imported geometry but it's actually of type Sketch") {
|
||||
message = format!("{message}. {ERROR_STRING_SKETCH_TO_SOLID_HELPER}");
|
||||
}
|
||||
KclError::Semantic(KclErrorDetails::new(message, arg.source_ranges()))
|
||||
KclError::new_semantic(KclErrorDetails::new(message, arg.source_ranges()))
|
||||
})?;
|
||||
|
||||
// TODO unnecessary cloning
|
||||
@ -214,12 +221,12 @@ impl Args {
|
||||
|
||||
/// Get a labelled keyword arg, check it's an array, and return all items in the array
|
||||
/// plus their source range.
|
||||
pub(crate) fn kw_arg_array_and_source<T>(&self, label: &str) -> Result<Vec<(T, SourceRange)>, KclError>
|
||||
where
|
||||
T: for<'a> FromKclValue<'a>,
|
||||
{
|
||||
pub(crate) fn kw_arg_edge_array_and_source(
|
||||
&self,
|
||||
label: &str,
|
||||
) -> Result<Vec<(EdgeReference, SourceRange)>, KclError> {
|
||||
let Some(arg) = self.kw_args.labeled.get(label) else {
|
||||
let err = KclError::Semantic(KclErrorDetails::new(
|
||||
let err = KclError::new_semantic(KclErrorDetails::new(
|
||||
format!("This function requires a keyword argument '{label}'"),
|
||||
vec![self.source_range],
|
||||
));
|
||||
@ -232,12 +239,8 @@ impl Args {
|
||||
.map(|item| {
|
||||
let source = SourceRange::from(item);
|
||||
let val = FromKclValue::from_kcl_val(item).ok_or_else(|| {
|
||||
KclError::Semantic(KclErrorDetails::new(
|
||||
format!(
|
||||
"Expected a {} but found {}",
|
||||
tynm::type_name::<T>(),
|
||||
arg.value.human_friendly_type()
|
||||
),
|
||||
KclError::new_semantic(KclErrorDetails::new(
|
||||
format!("Expected an Edge but found {}", arg.value.human_friendly_type()),
|
||||
arg.source_ranges(),
|
||||
))
|
||||
})?;
|
||||
@ -259,30 +262,6 @@ impl Args {
|
||||
})
|
||||
}
|
||||
|
||||
/// Get the unlabeled keyword argument. If not set, returns Err. If it
|
||||
/// can't be converted to the given type, returns Err.
|
||||
pub(crate) fn get_unlabeled_kw_arg<'a, T>(&'a self, label: &str) -> Result<T, KclError>
|
||||
where
|
||||
T: FromKclValue<'a>,
|
||||
{
|
||||
let arg = self
|
||||
.unlabeled_kw_arg_unconverted()
|
||||
.ok_or(KclError::Semantic(KclErrorDetails::new(
|
||||
format!("This function requires a value for the special unlabeled first parameter, '{label}'"),
|
||||
vec![self.source_range],
|
||||
)))?;
|
||||
|
||||
T::from_kcl_val(&arg.value).ok_or_else(|| {
|
||||
let expected_type_name = tynm::type_name::<T>();
|
||||
let actual_type_name = arg.value.human_friendly_type();
|
||||
let message = format!("This function expected the input argument to be of type {expected_type_name} but it's actually of type {actual_type_name}");
|
||||
KclError::Semantic(KclErrorDetails::new(
|
||||
message,
|
||||
arg.source_ranges(),
|
||||
))
|
||||
})
|
||||
}
|
||||
|
||||
/// Get the unlabeled keyword argument. If not set, returns Err. If it
|
||||
/// can't be converted to the given type, returns Err.
|
||||
pub(crate) fn get_unlabeled_kw_arg_typed<T>(
|
||||
@ -296,7 +275,7 @@ impl Args {
|
||||
{
|
||||
let arg = self
|
||||
.unlabeled_kw_arg_unconverted()
|
||||
.ok_or(KclError::Semantic(KclErrorDetails::new(
|
||||
.ok_or(KclError::new_semantic(KclErrorDetails::new(
|
||||
format!("This function requires a value for the special unlabeled first parameter, '{label}'"),
|
||||
vec![self.source_range],
|
||||
)))?;
|
||||
@ -330,11 +309,11 @@ impl Args {
|
||||
if message.contains("one or more Solids or imported geometry but it's actually of type Sketch") {
|
||||
message = format!("{message}. {ERROR_STRING_SKETCH_TO_SOLID_HELPER}");
|
||||
}
|
||||
KclError::Semantic(KclErrorDetails::new(message, arg.source_ranges()))
|
||||
KclError::new_semantic(KclErrorDetails::new(message, arg.source_ranges()))
|
||||
})?;
|
||||
|
||||
T::from_kcl_val(&arg).ok_or_else(|| {
|
||||
KclError::Internal(KclErrorDetails::new(
|
||||
KclError::new_internal(KclErrorDetails::new(
|
||||
format!("Mismatch between type coercion and value extraction (this isn't your fault).\nTo assist in bug-reporting, expected type: {ty:?}; actual value: {arg:?}"),
|
||||
vec![self.source_range],
|
||||
))
|
||||
@ -380,14 +359,14 @@ impl Args {
|
||||
exec_state.stack().get_from_call_stack(&tag.value, self.source_range)?
|
||||
{
|
||||
let info = t.get_info(epoch).ok_or_else(|| {
|
||||
KclError::Type(KclErrorDetails::new(
|
||||
KclError::new_type(KclErrorDetails::new(
|
||||
format!("Tag `{}` does not have engine info", tag.value),
|
||||
vec![self.source_range],
|
||||
))
|
||||
})?;
|
||||
Ok(info)
|
||||
} else {
|
||||
Err(KclError::Type(KclErrorDetails::new(
|
||||
Err(KclError::new_type(KclErrorDetails::new(
|
||||
format!("Tag `{}` does not exist", tag.value),
|
||||
vec![self.source_range],
|
||||
)))
|
||||
@ -519,7 +498,7 @@ impl Args {
|
||||
must_be_planar: bool,
|
||||
) -> Result<uuid::Uuid, KclError> {
|
||||
if tag.value.is_empty() {
|
||||
return Err(KclError::Type(KclErrorDetails::new(
|
||||
return Err(KclError::new_type(KclErrorDetails::new(
|
||||
"Expected a non-empty tag for the face".to_string(),
|
||||
vec![self.source_range],
|
||||
)));
|
||||
@ -528,7 +507,7 @@ impl Args {
|
||||
let engine_info = self.get_tag_engine_info_check_surface(exec_state, tag)?;
|
||||
|
||||
let surface = engine_info.surface.as_ref().ok_or_else(|| {
|
||||
KclError::Type(KclErrorDetails::new(
|
||||
KclError::new_type(KclErrorDetails::new(
|
||||
format!("Tag `{}` does not have a surface", tag.value),
|
||||
vec![self.source_range],
|
||||
))
|
||||
@ -547,7 +526,7 @@ impl Args {
|
||||
}
|
||||
}
|
||||
// The must be planar check must be called before the arc check.
|
||||
ExtrudeSurface::ExtrudeArc(_) if must_be_planar => Some(Err(KclError::Type(KclErrorDetails::new(
|
||||
ExtrudeSurface::ExtrudeArc(_) if must_be_planar => Some(Err(KclError::new_type(KclErrorDetails::new(
|
||||
format!("Tag `{}` is a non-planar surface", tag.value),
|
||||
vec![self.source_range],
|
||||
)))),
|
||||
@ -574,7 +553,7 @@ impl Args {
|
||||
}
|
||||
}
|
||||
// The must be planar check must be called before the fillet check.
|
||||
ExtrudeSurface::Fillet(_) if must_be_planar => Some(Err(KclError::Type(KclErrorDetails::new(
|
||||
ExtrudeSurface::Fillet(_) if must_be_planar => Some(Err(KclError::new_type(KclErrorDetails::new(
|
||||
format!("Tag `{}` is a non-planar surface", tag.value),
|
||||
vec![self.source_range],
|
||||
)))),
|
||||
@ -594,7 +573,7 @@ impl Args {
|
||||
}
|
||||
|
||||
// If we still haven't found the face, return an error.
|
||||
Err(KclError::Type(KclErrorDetails::new(
|
||||
Err(KclError::new_type(KclErrorDetails::new(
|
||||
format!("Expected a face with the tag `{}`", tag.value),
|
||||
vec![self.source_range],
|
||||
)))
|
||||
@ -619,13 +598,13 @@ where
|
||||
{
|
||||
fn from_args(args: &'a Args, i: usize) -> Result<Self, KclError> {
|
||||
let Some(arg) = args.args.get(i) else {
|
||||
return Err(KclError::Semantic(KclErrorDetails::new(
|
||||
return Err(KclError::new_semantic(KclErrorDetails::new(
|
||||
format!("Expected an argument at index {i}"),
|
||||
vec![args.source_range],
|
||||
)));
|
||||
};
|
||||
let Some(val) = T::from_kcl_val(&arg.value) else {
|
||||
return Err(KclError::Semantic(KclErrorDetails::new(
|
||||
return Err(KclError::new_semantic(KclErrorDetails::new(
|
||||
format!(
|
||||
"Argument at index {i} was supposed to be type {} but found {}",
|
||||
tynm::type_name::<T>(),
|
||||
@ -648,7 +627,7 @@ where
|
||||
return Ok(None);
|
||||
}
|
||||
let Some(val) = T::from_kcl_val(&arg.value) else {
|
||||
return Err(KclError::Semantic(KclErrorDetails::new(
|
||||
return Err(KclError::new_semantic(KclErrorDetails::new(
|
||||
format!(
|
||||
"Argument at index {i} was supposed to be type Option<{}> but found {}",
|
||||
tynm::type_name::<T>(),
|
||||
|
@ -58,7 +58,7 @@ async fn call_map_closure(
|
||||
let output = map_fn.call_kw(None, exec_state, ctxt, args, source_range).await?;
|
||||
let source_ranges = vec![source_range];
|
||||
let output = output.ok_or_else(|| {
|
||||
KclError::Semantic(KclErrorDetails::new(
|
||||
KclError::new_semantic(KclErrorDetails::new(
|
||||
"Map function must return a value".to_owned(),
|
||||
source_ranges,
|
||||
))
|
||||
@ -70,7 +70,7 @@ async fn call_map_closure(
|
||||
pub async fn reduce(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
||||
let array: Vec<KclValue> = args.get_unlabeled_kw_arg_typed("array", &RuntimeType::any_array(), exec_state)?;
|
||||
let f: &FunctionSource = args.get_kw_arg("f")?;
|
||||
let initial: KclValue = args.get_kw_arg("initial")?;
|
||||
let initial: KclValue = args.get_kw_arg_typed("initial", &RuntimeType::any(), exec_state)?;
|
||||
inner_reduce(array, initial, f, exec_state, &args).await
|
||||
}
|
||||
|
||||
@ -118,7 +118,7 @@ async fn call_reduce_closure(
|
||||
// Unpack the returned transform object.
|
||||
let source_ranges = vec![source_range];
|
||||
let out = transform_fn_return.ok_or_else(|| {
|
||||
KclError::Semantic(KclErrorDetails::new(
|
||||
KclError::new_semantic(KclErrorDetails::new(
|
||||
"Reducer function must return a value".to_string(),
|
||||
source_ranges.clone(),
|
||||
))
|
||||
@ -128,7 +128,7 @@ async fn call_reduce_closure(
|
||||
|
||||
pub async fn push(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
||||
let (mut array, ty) = args.get_unlabeled_kw_arg_array_and_type("array", exec_state)?;
|
||||
let item: KclValue = args.get_kw_arg("item")?;
|
||||
let item: KclValue = args.get_kw_arg_typed("item", &RuntimeType::any(), exec_state)?;
|
||||
|
||||
array.push(item);
|
||||
|
||||
@ -138,7 +138,7 @@ pub async fn push(exec_state: &mut ExecState, args: Args) -> Result<KclValue, Kc
|
||||
pub async fn pop(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
||||
let (mut array, ty) = args.get_unlabeled_kw_arg_array_and_type("array", exec_state)?;
|
||||
if array.is_empty() {
|
||||
return Err(KclError::Semantic(KclErrorDetails::new(
|
||||
return Err(KclError::new_semantic(KclErrorDetails::new(
|
||||
"Cannot pop from an empty array".to_string(),
|
||||
vec![args.source_range],
|
||||
)));
|
||||
|
@ -1,18 +1,17 @@
|
||||
//! Standard library assert functions.
|
||||
|
||||
use anyhow::Result;
|
||||
use kcl_derive_docs::stdlib;
|
||||
|
||||
use super::args::TyF64;
|
||||
use crate::{
|
||||
errors::{KclError, KclErrorDetails},
|
||||
execution::{ExecState, KclValue},
|
||||
execution::{types::RuntimeType, ExecState, KclValue},
|
||||
std::Args,
|
||||
};
|
||||
|
||||
async fn _assert(value: bool, message: &str, args: &Args) -> Result<(), KclError> {
|
||||
if !value {
|
||||
return Err(KclError::Type(KclErrorDetails::new(
|
||||
return Err(KclError::new_type(KclErrorDetails::new(
|
||||
format!("assert failed: {}", message),
|
||||
vec![args.source_range],
|
||||
)));
|
||||
@ -20,41 +19,28 @@ async fn _assert(value: bool, message: &str, args: &Args) -> Result<(), KclError
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn assert_is(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
||||
let actual = args.get_unlabeled_kw_arg("actual")?;
|
||||
let error = args.get_kw_arg_opt("error")?;
|
||||
pub async fn assert_is(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
||||
let actual = args.get_unlabeled_kw_arg_typed("actual", &RuntimeType::bool(), exec_state)?;
|
||||
let error = args.get_kw_arg_opt_typed("error", &RuntimeType::string(), exec_state)?;
|
||||
inner_assert_is(actual, error, &args).await?;
|
||||
Ok(KclValue::none())
|
||||
}
|
||||
|
||||
/// Check that the provided value is true, or raise a [KclError]
|
||||
/// with the provided description.
|
||||
pub async fn assert(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
||||
let actual = args.get_unlabeled_kw_arg("actual")?;
|
||||
let gt = args.get_kw_arg_opt("isGreaterThan")?;
|
||||
let lt = args.get_kw_arg_opt("isLessThan")?;
|
||||
let gte = args.get_kw_arg_opt("isGreaterThanOrEqual")?;
|
||||
let lte = args.get_kw_arg_opt("isLessThanOrEqual")?;
|
||||
let eq = args.get_kw_arg_opt("isEqualTo")?;
|
||||
let tolerance = args.get_kw_arg_opt("tolerance")?;
|
||||
let error = args.get_kw_arg_opt("error")?;
|
||||
pub async fn assert(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
||||
let actual = args.get_unlabeled_kw_arg_typed("actual", &RuntimeType::num_any(), exec_state)?;
|
||||
let gt = args.get_kw_arg_opt_typed("isGreaterThan", &RuntimeType::num_any(), exec_state)?;
|
||||
let lt = args.get_kw_arg_opt_typed("isLessThan", &RuntimeType::num_any(), exec_state)?;
|
||||
let gte = args.get_kw_arg_opt_typed("isGreaterThanOrEqual", &RuntimeType::num_any(), exec_state)?;
|
||||
let lte = args.get_kw_arg_opt_typed("isLessThanOrEqual", &RuntimeType::num_any(), exec_state)?;
|
||||
let eq = args.get_kw_arg_opt_typed("isEqualTo", &RuntimeType::num_any(), exec_state)?;
|
||||
let tolerance = args.get_kw_arg_opt_typed("tolerance", &RuntimeType::num_any(), exec_state)?;
|
||||
let error = args.get_kw_arg_opt_typed("error", &RuntimeType::string(), exec_state)?;
|
||||
inner_assert(actual, gt, lt, gte, lte, eq, tolerance, error, &args).await?;
|
||||
Ok(KclValue::none())
|
||||
}
|
||||
|
||||
/// Asserts that a value is the boolean value true.
|
||||
/// ```no_run
|
||||
/// kclIsFun = true
|
||||
/// assertIs(kclIsFun)
|
||||
/// ```
|
||||
#[stdlib{
|
||||
name = "assertIs",
|
||||
unlabeled_first = true,
|
||||
args = {
|
||||
actual = { docs = "Value to check. If this is the boolean value true, assert passes. Otherwise it fails." },
|
||||
error = { docs = "If the value was false, the program will terminate with this error message" },
|
||||
}
|
||||
}]
|
||||
async fn inner_assert_is(actual: bool, error: Option<String>, args: &Args) -> Result<(), KclError> {
|
||||
let error_msg = match &error {
|
||||
Some(x) => x,
|
||||
@ -63,29 +49,6 @@ async fn inner_assert_is(actual: bool, error: Option<String>, args: &Args) -> Re
|
||||
_assert(actual, error_msg, args).await
|
||||
}
|
||||
|
||||
/// Check a value meets some expected conditions at runtime. Program terminates with an error if conditions aren't met.
|
||||
/// If you provide multiple conditions, they will all be checked and all must be met.
|
||||
///
|
||||
/// ```no_run
|
||||
/// n = 10
|
||||
/// assert(n, isEqualTo = 10)
|
||||
/// assert(n, isGreaterThanOrEqual = 0, isLessThan = 100, error = "number should be between 0 and 100")
|
||||
/// assert(1.0000000000012, isEqualTo = 1, tolerance = 0.0001, error = "number should be almost exactly 1")
|
||||
/// ```
|
||||
#[stdlib {
|
||||
name = "assert",
|
||||
unlabeled_first = true,
|
||||
args = {
|
||||
actual = { docs = "Value to check. It will be compared with one of the comparison arguments." },
|
||||
is_greater_than = { docs = "Comparison argument. If given, checks the `actual` value is greater than this." },
|
||||
is_less_than = { docs = "Comparison argument. If given, checks the `actual` value is less than this." },
|
||||
is_greater_than_or_equal = { docs = "Comparison argument. If given, checks the `actual` value is greater than or equal to this." },
|
||||
is_less_than_or_equal = { docs = "Comparison argument. If given, checks the `actual` value is less than or equal to this." },
|
||||
is_equal_to = { docs = "Comparison argument. If given, checks the `actual` value is less than or equal to this.", include_in_snippet = true },
|
||||
tolerance = { docs = "If `isEqualTo` is used, this is the tolerance to allow for the comparison. This tolerance is used because KCL's number system has some floating-point imprecision when used with very large decimal places." },
|
||||
error = { docs = "If the value was false, the program will terminate with this error message" },
|
||||
}
|
||||
}]
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
async fn inner_assert(
|
||||
actual: TyF64,
|
||||
@ -109,14 +72,14 @@ async fn inner_assert(
|
||||
.iter()
|
||||
.all(|cond| cond.is_none());
|
||||
if no_condition_given {
|
||||
return Err(KclError::Type(KclErrorDetails::new(
|
||||
return Err(KclError::new_type(KclErrorDetails::new(
|
||||
"You must provide at least one condition in this assert (for example, isEqualTo)".to_owned(),
|
||||
vec![args.source_range],
|
||||
)));
|
||||
}
|
||||
|
||||
if tolerance.is_some() && is_equal_to.is_none() {
|
||||
return Err(KclError::Type(KclErrorDetails::new(
|
||||
return Err(KclError::new_type(KclErrorDetails::new(
|
||||
"The `tolerance` arg is only used with `isEqualTo`. Either remove `tolerance` or add an `isEqualTo` arg."
|
||||
.to_owned(),
|
||||
vec![args.source_range],
|
||||
|
@ -21,7 +21,7 @@ pub(crate) const DEFAULT_TOLERANCE: f64 = 0.0000001;
|
||||
pub async fn chamfer(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
||||
let solid = args.get_unlabeled_kw_arg_typed("solid", &RuntimeType::Primitive(PrimitiveType::Solid), exec_state)?;
|
||||
let length: TyF64 = args.get_kw_arg_typed("length", &RuntimeType::length(), exec_state)?;
|
||||
let tags = args.kw_arg_array_and_source::<EdgeReference>("tags")?;
|
||||
let tags = args.kw_arg_edge_array_and_source("tags")?;
|
||||
let tag = args.get_kw_arg_opt("tag")?;
|
||||
|
||||
super::fillet::validate_unique(&tags)?;
|
||||
@ -41,7 +41,7 @@ async fn inner_chamfer(
|
||||
// If you try and tag multiple edges with a tagged chamfer, we want to return an
|
||||
// error to the user that they can only tag one edge at a time.
|
||||
if tag.is_some() && tags.len() > 1 {
|
||||
return Err(KclError::Type(KclErrorDetails::new(
|
||||
return Err(KclError::new_type(KclErrorDetails::new(
|
||||
"You can only tag one edge at a time with a tagged chamfer. Either delete the tag for the chamfer fn if you don't need it OR separate into individual chamfer functions for each tag.".to_string(),
|
||||
vec![args.source_range],
|
||||
)));
|
||||
|
@ -84,7 +84,7 @@ async fn inner_clone(
|
||||
fix_tags_and_references(&mut new_geometry, old_id, exec_state, &args)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
KclError::Internal(KclErrorDetails::new(
|
||||
KclError::new_internal(KclErrorDetails::new(
|
||||
format!("failed to fix tags and references: {:?}", e),
|
||||
vec![args.source_range],
|
||||
))
|
||||
|
@ -23,7 +23,7 @@ pub async fn union(exec_state: &mut ExecState, args: Args) -> Result<KclValue, K
|
||||
let tolerance: Option<TyF64> = args.get_kw_arg_opt_typed("tolerance", &RuntimeType::length(), exec_state)?;
|
||||
|
||||
if solids.len() < 2 {
|
||||
return Err(KclError::UndefinedValue(KclErrorDetails::new(
|
||||
return Err(KclError::new_semantic(KclErrorDetails::new(
|
||||
"At least two solids are required for a union operation.".to_string(),
|
||||
vec![args.source_range],
|
||||
)));
|
||||
@ -66,7 +66,7 @@ pub(crate) async fn inner_union(
|
||||
modeling_response: OkModelingCmdResponse::BooleanUnion(BooleanUnion { extra_solid_ids }),
|
||||
} = result
|
||||
else {
|
||||
return Err(KclError::Internal(KclErrorDetails::new(
|
||||
return Err(KclError::new_internal(KclErrorDetails::new(
|
||||
"Failed to get the result of the union operation.".to_string(),
|
||||
vec![args.source_range],
|
||||
)));
|
||||
@ -88,7 +88,7 @@ pub async fn intersect(exec_state: &mut ExecState, args: Args) -> Result<KclValu
|
||||
let tolerance: Option<TyF64> = args.get_kw_arg_opt_typed("tolerance", &RuntimeType::length(), exec_state)?;
|
||||
|
||||
if solids.len() < 2 {
|
||||
return Err(KclError::UndefinedValue(KclErrorDetails::new(
|
||||
return Err(KclError::new_semantic(KclErrorDetails::new(
|
||||
"At least two solids are required for an intersect operation.".to_string(),
|
||||
vec![args.source_range],
|
||||
)));
|
||||
@ -131,7 +131,7 @@ pub(crate) async fn inner_intersect(
|
||||
modeling_response: OkModelingCmdResponse::BooleanIntersection(BooleanIntersection { extra_solid_ids }),
|
||||
} = result
|
||||
else {
|
||||
return Err(KclError::Internal(KclErrorDetails::new(
|
||||
return Err(KclError::new_internal(KclErrorDetails::new(
|
||||
"Failed to get the result of the intersection operation.".to_string(),
|
||||
vec![args.source_range],
|
||||
)));
|
||||
@ -193,7 +193,7 @@ pub(crate) async fn inner_subtract(
|
||||
modeling_response: OkModelingCmdResponse::BooleanSubtract(BooleanSubtract { extra_solid_ids }),
|
||||
} = result
|
||||
else {
|
||||
return Err(KclError::Internal(KclErrorDetails::new(
|
||||
return Err(KclError::new_internal(KclErrorDetails::new(
|
||||
"Failed to get the result of the subtract operation.".to_string(),
|
||||
vec![args.source_range],
|
||||
)));
|
||||
|
@ -1,14 +1,16 @@
|
||||
//! Edge helper functions.
|
||||
|
||||
use anyhow::Result;
|
||||
use kcl_derive_docs::stdlib;
|
||||
use kcmc::{each_cmd as mcmd, ok_response::OkModelingCmdResponse, websocket::OkWebSocketResponseData, ModelingCmd};
|
||||
use kittycad_modeling_cmds as kcmc;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::{
|
||||
errors::{KclError, KclErrorDetails},
|
||||
execution::{types::RuntimeType, ExecState, ExtrudeSurface, KclValue, TagIdentifier},
|
||||
execution::{
|
||||
types::{ArrayLen, RuntimeType},
|
||||
ExecState, ExtrudeSurface, KclValue, TagIdentifier,
|
||||
},
|
||||
std::Args,
|
||||
};
|
||||
|
||||
@ -23,42 +25,6 @@ pub async fn get_opposite_edge(exec_state: &mut ExecState, args: Args) -> Result
|
||||
})
|
||||
}
|
||||
|
||||
/// Get the opposite edge to the edge given.
|
||||
///
|
||||
/// ```no_run
|
||||
/// exampleSketch = startSketchOn(XZ)
|
||||
/// |> startProfile(at = [0, 0])
|
||||
/// |> line(end = [10, 0])
|
||||
/// |> angledLine(
|
||||
/// angle = 60,
|
||||
/// length = 10,
|
||||
/// )
|
||||
/// |> angledLine(
|
||||
/// angle = 120,
|
||||
/// length = 10,
|
||||
/// )
|
||||
/// |> line(end = [-10, 0])
|
||||
/// |> angledLine(
|
||||
/// angle = 240,
|
||||
/// length = 10,
|
||||
/// tag = $referenceEdge,
|
||||
/// )
|
||||
/// |> close()
|
||||
///
|
||||
/// example = extrude(exampleSketch, length = 5)
|
||||
/// |> fillet(
|
||||
/// radius = 3,
|
||||
/// tags = [getOppositeEdge(referenceEdge)],
|
||||
/// )
|
||||
/// ```
|
||||
#[stdlib {
|
||||
name = "getOppositeEdge",
|
||||
unlabeled_first = true,
|
||||
args = {
|
||||
edge = { docs = "The tag of the edge you want to find the opposite edge of." },
|
||||
},
|
||||
tags = ["sketch"]
|
||||
}]
|
||||
async fn inner_get_opposite_edge(
|
||||
edge: TagIdentifier,
|
||||
exec_state: &mut ExecState,
|
||||
@ -86,7 +52,7 @@ async fn inner_get_opposite_edge(
|
||||
modeling_response: OkModelingCmdResponse::Solid3dGetOppositeEdge(opposite_edge),
|
||||
} = &resp
|
||||
else {
|
||||
return Err(KclError::Engine(KclErrorDetails::new(
|
||||
return Err(KclError::new_engine(KclErrorDetails::new(
|
||||
format!("mcmd::Solid3dGetOppositeEdge response was not as expected: {:?}", resp),
|
||||
vec![args.source_range],
|
||||
)));
|
||||
@ -106,42 +72,6 @@ pub async fn get_next_adjacent_edge(exec_state: &mut ExecState, args: Args) -> R
|
||||
})
|
||||
}
|
||||
|
||||
/// Get the next adjacent edge to the edge given.
|
||||
///
|
||||
/// ```no_run
|
||||
/// exampleSketch = startSketchOn(XZ)
|
||||
/// |> startProfile(at = [0, 0])
|
||||
/// |> line(end = [10, 0])
|
||||
/// |> angledLine(
|
||||
/// angle = 60,
|
||||
/// length = 10,
|
||||
/// )
|
||||
/// |> angledLine(
|
||||
/// angle = 120,
|
||||
/// length = 10,
|
||||
/// )
|
||||
/// |> line(end = [-10, 0])
|
||||
/// |> angledLine(
|
||||
/// angle = 240,
|
||||
/// length = 10,
|
||||
/// tag = $referenceEdge,
|
||||
/// )
|
||||
/// |> close()
|
||||
///
|
||||
/// example = extrude(exampleSketch, length = 5)
|
||||
/// |> fillet(
|
||||
/// radius = 3,
|
||||
/// tags = [getNextAdjacentEdge(referenceEdge)],
|
||||
/// )
|
||||
/// ```
|
||||
#[stdlib {
|
||||
name = "getNextAdjacentEdge",
|
||||
unlabeled_first = true,
|
||||
args = {
|
||||
edge = { docs = "The tag of the edge you want to find the next adjacent edge of." },
|
||||
},
|
||||
tags = ["sketch"]
|
||||
}]
|
||||
async fn inner_get_next_adjacent_edge(
|
||||
edge: TagIdentifier,
|
||||
exec_state: &mut ExecState,
|
||||
@ -170,7 +100,7 @@ async fn inner_get_next_adjacent_edge(
|
||||
modeling_response: OkModelingCmdResponse::Solid3dGetNextAdjacentEdge(adjacent_edge),
|
||||
} = &resp
|
||||
else {
|
||||
return Err(KclError::Engine(KclErrorDetails::new(
|
||||
return Err(KclError::new_engine(KclErrorDetails::new(
|
||||
format!(
|
||||
"mcmd::Solid3dGetNextAdjacentEdge response was not as expected: {:?}",
|
||||
resp
|
||||
@ -180,7 +110,7 @@ async fn inner_get_next_adjacent_edge(
|
||||
};
|
||||
|
||||
adjacent_edge.edge.ok_or_else(|| {
|
||||
KclError::Type(KclErrorDetails::new(
|
||||
KclError::new_type(KclErrorDetails::new(
|
||||
format!("No edge found next adjacent to tag: `{}`", edge.value),
|
||||
vec![args.source_range],
|
||||
))
|
||||
@ -198,42 +128,6 @@ pub async fn get_previous_adjacent_edge(exec_state: &mut ExecState, args: Args)
|
||||
})
|
||||
}
|
||||
|
||||
/// Get the previous adjacent edge to the edge given.
|
||||
///
|
||||
/// ```no_run
|
||||
/// exampleSketch = startSketchOn(XZ)
|
||||
/// |> startProfile(at = [0, 0])
|
||||
/// |> line(end = [10, 0])
|
||||
/// |> angledLine(
|
||||
/// angle = 60,
|
||||
/// length = 10,
|
||||
/// )
|
||||
/// |> angledLine(
|
||||
/// angle = 120,
|
||||
/// length = 10,
|
||||
/// )
|
||||
/// |> line(end = [-10, 0])
|
||||
/// |> angledLine(
|
||||
/// angle = 240,
|
||||
/// length = 10,
|
||||
/// tag = $referenceEdge,
|
||||
/// )
|
||||
/// |> close()
|
||||
///
|
||||
/// example = extrude(exampleSketch, length = 5)
|
||||
/// |> fillet(
|
||||
/// radius = 3,
|
||||
/// tags = [getPreviousAdjacentEdge(referenceEdge)],
|
||||
/// )
|
||||
/// ```
|
||||
#[stdlib {
|
||||
name = "getPreviousAdjacentEdge",
|
||||
unlabeled_first = true,
|
||||
args = {
|
||||
edge = { docs = "The tag of the edge you want to find the previous adjacent edge of." },
|
||||
},
|
||||
tags = ["sketch"]
|
||||
}]
|
||||
async fn inner_get_previous_adjacent_edge(
|
||||
edge: TagIdentifier,
|
||||
exec_state: &mut ExecState,
|
||||
@ -261,7 +155,7 @@ async fn inner_get_previous_adjacent_edge(
|
||||
modeling_response: OkModelingCmdResponse::Solid3dGetPrevAdjacentEdge(adjacent_edge),
|
||||
} = &resp
|
||||
else {
|
||||
return Err(KclError::Engine(KclErrorDetails::new(
|
||||
return Err(KclError::new_engine(KclErrorDetails::new(
|
||||
format!(
|
||||
"mcmd::Solid3dGetPrevAdjacentEdge response was not as expected: {:?}",
|
||||
resp
|
||||
@ -271,7 +165,7 @@ async fn inner_get_previous_adjacent_edge(
|
||||
};
|
||||
|
||||
adjacent_edge.edge.ok_or_else(|| {
|
||||
KclError::Type(KclErrorDetails::new(
|
||||
KclError::new_type(KclErrorDetails::new(
|
||||
format!("No edge found previous adjacent to tag: `{}`", edge.value),
|
||||
vec![args.source_range],
|
||||
))
|
||||
@ -280,7 +174,11 @@ async fn inner_get_previous_adjacent_edge(
|
||||
|
||||
/// Get the shared edge between two faces.
|
||||
pub async fn get_common_edge(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
||||
let faces: Vec<TagIdentifier> = args.get_kw_arg("faces")?;
|
||||
let faces: Vec<TagIdentifier> = args.get_kw_arg_typed(
|
||||
"faces",
|
||||
&RuntimeType::Array(Box::new(RuntimeType::tag_identifier()), ArrayLen::Known(2)),
|
||||
exec_state,
|
||||
)?;
|
||||
|
||||
let edge = inner_get_common_edge(faces, exec_state, args.clone()).await?;
|
||||
Ok(KclValue::Uuid {
|
||||
@ -289,38 +187,6 @@ pub async fn get_common_edge(exec_state: &mut ExecState, args: Args) -> Result<K
|
||||
})
|
||||
}
|
||||
|
||||
/// Get the shared edge between two faces.
|
||||
///
|
||||
/// ```no_run
|
||||
/// // Get an edge shared between two faces, created after a chamfer.
|
||||
///
|
||||
/// scale = 20
|
||||
/// part001 = startSketchOn(XY)
|
||||
/// |> startProfile(at = [0, 0])
|
||||
/// |> line(end = [0, scale])
|
||||
/// |> line(end = [scale, 0])
|
||||
/// |> line(end = [0, -scale])
|
||||
/// |> close(tag = $line0)
|
||||
/// |> extrude(length = 20, tagEnd = $end0)
|
||||
/// // We tag the chamfer to reference it later.
|
||||
/// |> chamfer(length = 10, tags = [getOppositeEdge(line0)], tag = $chamfer0)
|
||||
///
|
||||
/// // Get the shared edge between the chamfer and the extrusion.
|
||||
/// commonEdge = getCommonEdge(faces = [chamfer0, end0])
|
||||
///
|
||||
/// // Chamfer the shared edge.
|
||||
/// // TODO: uncomment this when ssi for fillets lands
|
||||
/// // chamfer(part001, length = 5, tags = [commonEdge])
|
||||
/// ```
|
||||
#[stdlib {
|
||||
name = "getCommonEdge",
|
||||
feature_tree_operation = false,
|
||||
unlabeled_first = false,
|
||||
args = {
|
||||
faces = { docs = "The tags of the faces you want to find the common edge between" },
|
||||
},
|
||||
tags = ["sketch"]
|
||||
}]
|
||||
async fn inner_get_common_edge(
|
||||
faces: Vec<TagIdentifier>,
|
||||
exec_state: &mut ExecState,
|
||||
@ -332,7 +198,7 @@ async fn inner_get_common_edge(
|
||||
}
|
||||
|
||||
if faces.len() != 2 {
|
||||
return Err(KclError::Type(KclErrorDetails::new(
|
||||
return Err(KclError::new_type(KclErrorDetails::new(
|
||||
"getCommonEdge requires exactly two tags for faces".to_string(),
|
||||
vec![args.source_range],
|
||||
)));
|
||||
@ -344,7 +210,7 @@ async fn inner_get_common_edge(
|
||||
let second_tagged_path = args.get_tag_engine_info(exec_state, &faces[1])?;
|
||||
|
||||
if first_tagged_path.sketch != second_tagged_path.sketch {
|
||||
return Err(KclError::Type(KclErrorDetails::new(
|
||||
return Err(KclError::new_type(KclErrorDetails::new(
|
||||
"getCommonEdge requires the faces to be in the same original sketch".to_string(),
|
||||
vec![args.source_range],
|
||||
)));
|
||||
@ -373,14 +239,14 @@ async fn inner_get_common_edge(
|
||||
modeling_response: OkModelingCmdResponse::Solid3dGetCommonEdge(common_edge),
|
||||
} = &resp
|
||||
else {
|
||||
return Err(KclError::Engine(KclErrorDetails::new(
|
||||
return Err(KclError::new_engine(KclErrorDetails::new(
|
||||
format!("mcmd::Solid3dGetCommonEdge response was not as expected: {:?}", resp),
|
||||
vec![args.source_range],
|
||||
)));
|
||||
};
|
||||
|
||||
common_edge.edge.ok_or_else(|| {
|
||||
KclError::Type(KclErrorDetails::new(
|
||||
KclError::new_type(KclErrorDetails::new(
|
||||
format!(
|
||||
"No common edge was found between `{}` and `{}`",
|
||||
faces[0].value, faces[1].value
|
||||
|
@ -3,7 +3,6 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use anyhow::Result;
|
||||
use kcl_derive_docs::stdlib;
|
||||
use kcmc::{
|
||||
each_cmd as mcmd,
|
||||
length_unit::LengthUnit,
|
||||
@ -31,7 +30,7 @@ use crate::{
|
||||
pub async fn extrude(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
||||
let sketches = args.get_unlabeled_kw_arg_typed("sketches", &RuntimeType::sketches(), exec_state)?;
|
||||
let length: TyF64 = args.get_kw_arg_typed("length", &RuntimeType::length(), exec_state)?;
|
||||
let symmetric = args.get_kw_arg_opt("symmetric")?;
|
||||
let symmetric = args.get_kw_arg_opt_typed("symmetric", &RuntimeType::bool(), exec_state)?;
|
||||
let bidirectional_length: Option<TyF64> =
|
||||
args.get_kw_arg_opt_typed("bidirectionalLength", &RuntimeType::length(), exec_state)?;
|
||||
let tag_start = args.get_kw_arg_opt("tagStart")?;
|
||||
@ -52,113 +51,6 @@ pub async fn extrude(exec_state: &mut ExecState, args: Args) -> Result<KclValue,
|
||||
Ok(result.into())
|
||||
}
|
||||
|
||||
/// Extend a 2-dimensional sketch through a third dimension in order to
|
||||
/// create new 3-dimensional volume, or if extruded into an existing volume,
|
||||
/// cut into an existing solid.
|
||||
///
|
||||
/// You can provide more than one sketch to extrude, and they will all be
|
||||
/// extruded in the same direction.
|
||||
///
|
||||
/// ```no_run
|
||||
/// example = startSketchOn(XZ)
|
||||
/// |> startProfile(at = [0, 0])
|
||||
/// |> line(end = [10, 0])
|
||||
/// |> arc(
|
||||
/// angleStart = 120,
|
||||
/// angleEnd = 0,
|
||||
/// radius = 5,
|
||||
/// )
|
||||
/// |> line(end = [5, 0])
|
||||
/// |> line(end = [0, 10])
|
||||
/// |> bezierCurve(
|
||||
/// control1 = [-10, 0],
|
||||
/// control2 = [2, 10],
|
||||
/// end = [-5, 10],
|
||||
/// )
|
||||
/// |> line(end = [-5, -2])
|
||||
/// |> close()
|
||||
/// |> extrude(length = 10)
|
||||
/// ```
|
||||
///
|
||||
/// ```no_run
|
||||
/// exampleSketch = startSketchOn(XZ)
|
||||
/// |> startProfile(at = [-10, 0])
|
||||
/// |> arc(
|
||||
/// angleStart = 120,
|
||||
/// angleEnd = -60,
|
||||
/// radius = 5,
|
||||
/// )
|
||||
/// |> line(end = [10, 0])
|
||||
/// |> line(end = [5, 0])
|
||||
/// |> bezierCurve(
|
||||
/// control1 = [-3, 0],
|
||||
/// control2 = [2, 10],
|
||||
/// end = [-5, 10],
|
||||
/// )
|
||||
/// |> line(end = [-4, 10])
|
||||
/// |> line(end = [-5, -2])
|
||||
/// |> close()
|
||||
///
|
||||
/// example = extrude(exampleSketch, length = 10)
|
||||
/// ```
|
||||
///
|
||||
/// ```no_run
|
||||
/// exampleSketch = startSketchOn(XZ)
|
||||
/// |> startProfile(at = [-10, 0])
|
||||
/// |> arc(
|
||||
/// angleStart = 120,
|
||||
/// angleEnd = -60,
|
||||
/// radius = 5,
|
||||
/// )
|
||||
/// |> line(end = [10, 0])
|
||||
/// |> line(end = [5, 0])
|
||||
/// |> bezierCurve(
|
||||
/// control1 = [-3, 0],
|
||||
/// control2 = [2, 10],
|
||||
/// end = [-5, 10],
|
||||
/// )
|
||||
/// |> line(end = [-4, 10])
|
||||
/// |> line(end = [-5, -2])
|
||||
/// |> close()
|
||||
///
|
||||
/// example = extrude(exampleSketch, length = 20, symmetric = true)
|
||||
/// ```
|
||||
///
|
||||
/// ```no_run
|
||||
/// exampleSketch = startSketchOn(XZ)
|
||||
/// |> startProfile(at = [-10, 0])
|
||||
/// |> arc(
|
||||
/// angleStart = 120,
|
||||
/// angleEnd = -60,
|
||||
/// radius = 5,
|
||||
/// )
|
||||
/// |> line(end = [10, 0])
|
||||
/// |> line(end = [5, 0])
|
||||
/// |> bezierCurve(
|
||||
/// control1 = [-3, 0],
|
||||
/// control2 = [2, 10],
|
||||
/// end = [-5, 10],
|
||||
/// )
|
||||
/// |> line(end = [-4, 10])
|
||||
/// |> line(end = [-5, -2])
|
||||
/// |> close()
|
||||
///
|
||||
/// example = extrude(exampleSketch, length = 10, bidirectionalLength = 50)
|
||||
/// ```
|
||||
#[stdlib {
|
||||
name = "extrude",
|
||||
feature_tree_operation = true,
|
||||
unlabeled_first = true,
|
||||
args = {
|
||||
sketches = { docs = "Which sketch or sketches should be extruded"},
|
||||
length = { docs = "How far to extrude the given sketches"},
|
||||
symmetric = { docs = "If true, the extrusion will happen symmetrically around the sketch. Otherwise, the extrusion will happen on only one side of the sketch." },
|
||||
bidirectional_length = { docs = "If specified, will also extrude in the opposite direction to 'distance' to the specified distance. If 'symmetric' is true, this value is ignored."},
|
||||
tag_start = { docs = "A named tag for the face at the start of the extrusion, i.e. the original sketch" },
|
||||
tag_end = { docs = "A named tag for the face at the end of the extrusion, i.e. the new face created by extruding the original sketch" },
|
||||
},
|
||||
tags = ["sketch"]
|
||||
}]
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
async fn inner_extrude(
|
||||
sketches: Vec<Sketch>,
|
||||
@ -174,7 +66,7 @@ async fn inner_extrude(
|
||||
let mut solids = Vec::new();
|
||||
|
||||
if symmetric.unwrap_or(false) && bidirectional_length.is_some() {
|
||||
return Err(KclError::Semantic(KclErrorDetails::new(
|
||||
return Err(KclError::new_semantic(KclErrorDetails::new(
|
||||
"You cannot give both `symmetric` and `bidirectional` params, you have to choose one or the other"
|
||||
.to_owned(),
|
||||
vec![args.source_range],
|
||||
@ -261,7 +153,7 @@ pub(crate) async fn do_post_extrude<'a>(
|
||||
// The "get extrusion face info" API call requires *any* edge on the sketch being extruded.
|
||||
// So, let's just use the first one.
|
||||
let Some(any_edge_id) = sketch.paths.first().map(|edge| edge.get_base().geo_meta.id) else {
|
||||
return Err(KclError::Type(KclErrorDetails::new(
|
||||
return Err(KclError::new_type(KclErrorDetails::new(
|
||||
"Expected a non-empty sketch".to_owned(),
|
||||
vec![args.source_range],
|
||||
)));
|
||||
@ -389,7 +281,7 @@ pub(crate) async fn do_post_extrude<'a>(
|
||||
// Add the tags for the start or end caps.
|
||||
if let Some(tag_start) = named_cap_tags.start {
|
||||
let Some(start_cap_id) = start_cap_id else {
|
||||
return Err(KclError::Type(KclErrorDetails::new(
|
||||
return Err(KclError::new_type(KclErrorDetails::new(
|
||||
format!(
|
||||
"Expected a start cap ID for tag `{}` for extrusion of sketch {:?}",
|
||||
tag_start.name, sketch.id
|
||||
@ -409,7 +301,7 @@ pub(crate) async fn do_post_extrude<'a>(
|
||||
}
|
||||
if let Some(tag_end) = named_cap_tags.end {
|
||||
let Some(end_cap_id) = end_cap_id else {
|
||||
return Err(KclError::Type(KclErrorDetails::new(
|
||||
return Err(KclError::new_type(KclErrorDetails::new(
|
||||
format!(
|
||||
"Expected an end cap ID for tag `{}` for extrusion of sketch {:?}",
|
||||
tag_end.name, sketch.id
|
||||
|
@ -49,7 +49,7 @@ pub(super) fn validate_unique<T: Eq + std::hash::Hash>(tags: &[(T, SourceRange)]
|
||||
}
|
||||
}
|
||||
if !duplicate_tags_source.is_empty() {
|
||||
return Err(KclError::Type(KclErrorDetails::new(
|
||||
return Err(KclError::new_type(KclErrorDetails::new(
|
||||
"The same edge ID is being referenced multiple times, which is not allowed. Please select a different edge"
|
||||
.to_string(),
|
||||
duplicate_tags_source,
|
||||
@ -63,7 +63,7 @@ pub async fn fillet(exec_state: &mut ExecState, args: Args) -> Result<KclValue,
|
||||
let solid = args.get_unlabeled_kw_arg_typed("solid", &RuntimeType::solid(), exec_state)?;
|
||||
let radius: TyF64 = args.get_kw_arg_typed("radius", &RuntimeType::length(), exec_state)?;
|
||||
let tolerance: Option<TyF64> = args.get_kw_arg_opt_typed("tolerance", &RuntimeType::length(), exec_state)?;
|
||||
let tags = args.kw_arg_array_and_source::<EdgeReference>("tags")?;
|
||||
let tags = args.kw_arg_edge_array_and_source("tags")?;
|
||||
let tag = args.get_kw_arg_opt("tag")?;
|
||||
|
||||
// Run the function.
|
||||
@ -85,14 +85,14 @@ async fn inner_fillet(
|
||||
// If you try and tag multiple edges with a tagged fillet, we want to return an
|
||||
// error to the user that they can only tag one edge at a time.
|
||||
if tag.is_some() && tags.len() > 1 {
|
||||
return Err(KclError::Type(KclErrorDetails {
|
||||
return Err(KclError::new_type(KclErrorDetails {
|
||||
message: "You can only tag one edge at a time with a tagged fillet. Either delete the tag for the fillet fn if you don't need it OR separate into individual fillet functions for each tag.".to_string(),
|
||||
source_ranges: vec![args.source_range],
|
||||
backtrace: Default::default(),
|
||||
}));
|
||||
}
|
||||
if tags.is_empty() {
|
||||
return Err(KclError::Semantic(KclErrorDetails {
|
||||
return Err(KclError::new_semantic(KclErrorDetails {
|
||||
source_ranges: vec![args.source_range],
|
||||
message: "You must fillet at least one tag".to_owned(),
|
||||
backtrace: Default::default(),
|
||||
|
@ -18,7 +18,7 @@ use crate::{
|
||||
pub async fn helix(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
||||
let angle_start: TyF64 = args.get_kw_arg_typed("angleStart", &RuntimeType::degrees(), exec_state)?;
|
||||
let revolutions: TyF64 = args.get_kw_arg_typed("revolutions", &RuntimeType::count(), exec_state)?;
|
||||
let ccw = args.get_kw_arg_opt("ccw")?;
|
||||
let ccw = args.get_kw_arg_opt_typed("ccw", &RuntimeType::bool(), exec_state)?;
|
||||
let radius: Option<TyF64> = args.get_kw_arg_opt_typed("radius", &RuntimeType::length(), exec_state)?;
|
||||
let axis: Option<Axis3dOrEdgeReference> = args.get_kw_arg_opt_typed(
|
||||
"axis",
|
||||
@ -33,7 +33,7 @@ pub async fn helix(exec_state: &mut ExecState, args: Args) -> Result<KclValue, K
|
||||
|
||||
// Make sure we have a radius if we don't have a cylinder.
|
||||
if radius.is_none() && cylinder.is_none() {
|
||||
return Err(KclError::Semantic(crate::errors::KclErrorDetails::new(
|
||||
return Err(KclError::new_semantic(crate::errors::KclErrorDetails::new(
|
||||
"Radius is required when creating a helix without a cylinder.".to_string(),
|
||||
vec![args.source_range],
|
||||
)));
|
||||
@ -41,7 +41,7 @@ pub async fn helix(exec_state: &mut ExecState, args: Args) -> Result<KclValue, K
|
||||
|
||||
// Make sure we don't have a radius if we have a cylinder.
|
||||
if radius.is_some() && cylinder.is_some() {
|
||||
return Err(KclError::Semantic(crate::errors::KclErrorDetails::new(
|
||||
return Err(KclError::new_semantic(crate::errors::KclErrorDetails::new(
|
||||
"Radius is not allowed when creating a helix with a cylinder.".to_string(),
|
||||
vec![args.source_range],
|
||||
)));
|
||||
@ -49,7 +49,7 @@ pub async fn helix(exec_state: &mut ExecState, args: Args) -> Result<KclValue, K
|
||||
|
||||
// Make sure we have an axis if we don't have a cylinder.
|
||||
if axis.is_none() && cylinder.is_none() {
|
||||
return Err(KclError::Semantic(crate::errors::KclErrorDetails::new(
|
||||
return Err(KclError::new_semantic(crate::errors::KclErrorDetails::new(
|
||||
"Axis is required when creating a helix without a cylinder.".to_string(),
|
||||
vec![args.source_range],
|
||||
)));
|
||||
@ -57,7 +57,7 @@ pub async fn helix(exec_state: &mut ExecState, args: Args) -> Result<KclValue, K
|
||||
|
||||
// Make sure we don't have an axis if we have a cylinder.
|
||||
if axis.is_some() && cylinder.is_some() {
|
||||
return Err(KclError::Semantic(crate::errors::KclErrorDetails::new(
|
||||
return Err(KclError::new_semantic(crate::errors::KclErrorDetails::new(
|
||||
"Axis is not allowed when creating a helix with a cylinder.".to_string(),
|
||||
vec![args.source_range],
|
||||
)));
|
||||
@ -65,7 +65,7 @@ pub async fn helix(exec_state: &mut ExecState, args: Args) -> Result<KclValue, K
|
||||
|
||||
// Make sure we have a radius if we have an axis.
|
||||
if radius.is_none() && axis.is_some() {
|
||||
return Err(KclError::Semantic(crate::errors::KclErrorDetails::new(
|
||||
return Err(KclError::new_semantic(crate::errors::KclErrorDetails::new(
|
||||
"Radius is required when creating a helix around an axis.".to_string(),
|
||||
vec![args.source_range],
|
||||
)));
|
||||
@ -73,7 +73,7 @@ pub async fn helix(exec_state: &mut ExecState, args: Args) -> Result<KclValue, K
|
||||
|
||||
// Make sure we have an axis if we have a radius.
|
||||
if axis.is_none() && radius.is_some() {
|
||||
return Err(KclError::Semantic(crate::errors::KclErrorDetails::new(
|
||||
return Err(KclError::new_semantic(crate::errors::KclErrorDetails::new(
|
||||
"Axis is required when creating a helix around an axis.".to_string(),
|
||||
vec![args.source_range],
|
||||
)));
|
||||
@ -140,7 +140,7 @@ async fn inner_helix(
|
||||
Axis3dOrEdgeReference::Axis { direction, origin } => {
|
||||
// Make sure they gave us a length.
|
||||
let Some(length) = length else {
|
||||
return Err(KclError::Semantic(KclErrorDetails::new(
|
||||
return Err(KclError::new_semantic(KclErrorDetails::new(
|
||||
"Length is required when creating a helix around an axis.".to_owned(),
|
||||
vec![args.source_range],
|
||||
)));
|
||||
|
@ -3,7 +3,6 @@
|
||||
use std::num::NonZeroU32;
|
||||
|
||||
use anyhow::Result;
|
||||
use kcl_derive_docs::stdlib;
|
||||
use kcmc::{each_cmd as mcmd, length_unit::LengthUnit, ModelingCmd};
|
||||
use kittycad_modeling_cmds as kcmc;
|
||||
|
||||
@ -24,14 +23,17 @@ const DEFAULT_V_DEGREE: u32 = 2;
|
||||
pub async fn loft(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
||||
let sketches = args.get_unlabeled_kw_arg_typed("sketches", &RuntimeType::sketches(), exec_state)?;
|
||||
let v_degree: NonZeroU32 = args
|
||||
.get_kw_arg_opt("vDegree")?
|
||||
.get_kw_arg_opt_typed("vDegree", &RuntimeType::count(), exec_state)?
|
||||
.unwrap_or(NonZeroU32::new(DEFAULT_V_DEGREE).unwrap());
|
||||
// Attempt to approximate rational curves (such as arcs) using a bezier.
|
||||
// This will remove banding around interpolations between arcs and non-arcs. It may produce errors in other scenarios
|
||||
// Over time, this field won't be necessary.
|
||||
let bez_approximate_rational = args.get_kw_arg_opt("bezApproximateRational")?.unwrap_or(false);
|
||||
let bez_approximate_rational = args
|
||||
.get_kw_arg_opt_typed("bezApproximateRational", &RuntimeType::bool(), exec_state)?
|
||||
.unwrap_or(false);
|
||||
// This can be set to override the automatically determined topological base curve, which is usually the first section encountered.
|
||||
let base_curve_index: Option<u32> = args.get_kw_arg_opt("baseCurveIndex")?;
|
||||
let base_curve_index: Option<u32> =
|
||||
args.get_kw_arg_opt_typed("baseCurveIndex", &RuntimeType::count(), exec_state)?;
|
||||
// Tolerance for the loft operation.
|
||||
let tolerance: Option<TyF64> = args.get_kw_arg_opt_typed("tolerance", &RuntimeType::length(), exec_state)?;
|
||||
let tag_start = args.get_kw_arg_opt("tagStart")?;
|
||||
@ -52,87 +54,6 @@ pub async fn loft(exec_state: &mut ExecState, args: Args) -> Result<KclValue, Kc
|
||||
Ok(KclValue::Solid { value })
|
||||
}
|
||||
|
||||
/// Create a 3D surface or solid by interpolating between two or more sketches.
|
||||
///
|
||||
/// The sketches need to closed and on the same plane.
|
||||
///
|
||||
/// ```no_run
|
||||
/// // Loft a square and a triangle.
|
||||
/// squareSketch = startSketchOn(XY)
|
||||
/// |> startProfile(at = [-100, 200])
|
||||
/// |> line(end = [200, 0])
|
||||
/// |> line(end = [0, -200])
|
||||
/// |> line(end = [-200, 0])
|
||||
/// |> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||
/// |> close()
|
||||
///
|
||||
/// triangleSketch = startSketchOn(offsetPlane(XY, offset = 75))
|
||||
/// |> startProfile(at = [0, 125])
|
||||
/// |> line(end = [-15, -30])
|
||||
/// |> line(end = [30, 0])
|
||||
/// |> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||
/// |> close()
|
||||
///
|
||||
/// loft([triangleSketch, squareSketch])
|
||||
/// ```
|
||||
///
|
||||
/// ```no_run
|
||||
/// // Loft a square, a circle, and another circle.
|
||||
/// squareSketch = startSketchOn(XY)
|
||||
/// |> startProfile(at = [-100, 200])
|
||||
/// |> line(end = [200, 0])
|
||||
/// |> line(end = [0, -200])
|
||||
/// |> line(end = [-200, 0])
|
||||
/// |> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||
/// |> close()
|
||||
///
|
||||
/// circleSketch0 = startSketchOn(offsetPlane(XY, offset = 75))
|
||||
/// |> circle( center = [0, 100], radius = 50 )
|
||||
///
|
||||
/// circleSketch1 = startSketchOn(offsetPlane(XY, offset = 150))
|
||||
/// |> circle( center = [0, 100], radius = 20 )
|
||||
///
|
||||
/// loft([squareSketch, circleSketch0, circleSketch1])
|
||||
/// ```
|
||||
///
|
||||
/// ```no_run
|
||||
/// // Loft a square, a circle, and another circle with options.
|
||||
/// squareSketch = startSketchOn(XY)
|
||||
/// |> startProfile(at = [-100, 200])
|
||||
/// |> line(end = [200, 0])
|
||||
/// |> line(end = [0, -200])
|
||||
/// |> line(end = [-200, 0])
|
||||
/// |> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||
/// |> close()
|
||||
///
|
||||
/// circleSketch0 = startSketchOn(offsetPlane(XY, offset = 75))
|
||||
/// |> circle( center = [0, 100], radius = 50 )
|
||||
///
|
||||
/// circleSketch1 = startSketchOn(offsetPlane(XY, offset = 150))
|
||||
/// |> circle( center = [0, 100], radius = 20 )
|
||||
///
|
||||
/// loft([squareSketch, circleSketch0, circleSketch1],
|
||||
/// baseCurveIndex = 0,
|
||||
/// bezApproximateRational = false,
|
||||
/// tolerance = 0.000001,
|
||||
/// vDegree = 2,
|
||||
/// )
|
||||
/// ```
|
||||
#[stdlib {
|
||||
name = "loft",
|
||||
feature_tree_operation = true,
|
||||
unlabeled_first = true,
|
||||
args = {
|
||||
sketches = {docs = "Which sketches to loft. Must include at least 2 sketches."},
|
||||
v_degree = {docs = "Degree of the interpolation. Must be greater than zero. For example, use 2 for quadratic, or 3 for cubic interpolation in the V direction. This defaults to 2, if not specified."},
|
||||
bez_approximate_rational = {docs = "Attempt to approximate rational curves (such as arcs) using a bezier. This will remove banding around interpolations between arcs and non-arcs. It may produce errors in other scenarios Over time, this field won't be necessary."},
|
||||
base_curve_index = {docs = "This can be set to override the automatically determined topological base curve, which is usually the first section encountered."},
|
||||
tolerance = {docs = "Tolerance for the loft operation."},
|
||||
tag_start = { docs = "A named tag for the face at the start of the loft, i.e. the original sketch" },
|
||||
tag_end = { docs = "A named tag for the face at the end of the loft, i.e. the last sketch" },
|
||||
},
|
||||
tags = ["sketch"]
|
||||
}]
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
async fn inner_loft(
|
||||
sketches: Vec<Sketch>,
|
||||
@ -147,7 +68,7 @@ async fn inner_loft(
|
||||
) -> Result<Box<Solid>, KclError> {
|
||||
// Make sure we have at least two sketches.
|
||||
if sketches.len() < 2 {
|
||||
return Err(KclError::Semantic(KclErrorDetails::new(
|
||||
return Err(KclError::new_semantic(KclErrorDetails::new(
|
||||
format!(
|
||||
"Loft requires at least two sketches, but only {} were provided.",
|
||||
sketches.len()
|
||||
|
@ -56,7 +56,7 @@ pub async fn sqrt(exec_state: &mut ExecState, args: Args) -> Result<KclValue, Kc
|
||||
let input: TyF64 = args.get_unlabeled_kw_arg_typed("input", &RuntimeType::num_any(), exec_state)?;
|
||||
|
||||
if input.n < 0.0 {
|
||||
return Err(KclError::Semantic(KclErrorDetails::new(
|
||||
return Err(KclError::new_semantic(KclErrorDetails::new(
|
||||
format!(
|
||||
"Attempt to take square root (`sqrt`) of a number less than zero ({})",
|
||||
input.n
|
||||
|
@ -101,7 +101,7 @@ async fn inner_mirror_2d(
|
||||
OkModelingCmdResponse::EntityGetAllChildUuids(EntityGetAllChildUuids { entity_ids: child_ids }),
|
||||
} = response
|
||||
else {
|
||||
return Err(KclError::Internal(KclErrorDetails::new(
|
||||
return Err(KclError::new_internal(KclErrorDetails::new(
|
||||
"Expected a successful response from EntityGetAllChildUuids".to_string(),
|
||||
vec![args.source_range],
|
||||
)));
|
||||
@ -112,7 +112,7 @@ async fn inner_mirror_2d(
|
||||
let child_id = child_ids[1];
|
||||
sketch.mirror = Some(child_id);
|
||||
} else {
|
||||
return Err(KclError::Type(KclErrorDetails::new(
|
||||
return Err(KclError::new_type(KclErrorDetails::new(
|
||||
"Expected child uuids to be >= 2".to_string(),
|
||||
vec![args.source_range],
|
||||
)));
|
||||
|
@ -28,14 +28,10 @@ pub mod utils;
|
||||
|
||||
use anyhow::Result;
|
||||
pub use args::Args;
|
||||
use indexmap::IndexMap;
|
||||
use lazy_static::lazy_static;
|
||||
|
||||
use crate::{
|
||||
docs::StdLibFn,
|
||||
errors::KclError,
|
||||
execution::{types::PrimitiveType, ExecState, KclValue},
|
||||
parsing::ast::types::Name,
|
||||
};
|
||||
|
||||
pub type StdFn = fn(
|
||||
@ -43,70 +39,6 @@ pub type StdFn = fn(
|
||||
Args,
|
||||
) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<KclValue, KclError>> + Send + '_>>;
|
||||
|
||||
lazy_static! {
|
||||
static ref CORE_FNS: Vec<Box<dyn StdLibFn>> = vec![
|
||||
Box::new(crate::std::extrude::Extrude),
|
||||
Box::new(crate::std::segment::SegEnd),
|
||||
Box::new(crate::std::segment::SegEndX),
|
||||
Box::new(crate::std::segment::SegEndY),
|
||||
Box::new(crate::std::segment::SegStart),
|
||||
Box::new(crate::std::segment::SegStartX),
|
||||
Box::new(crate::std::segment::SegStartY),
|
||||
Box::new(crate::std::segment::LastSegX),
|
||||
Box::new(crate::std::segment::LastSegY),
|
||||
Box::new(crate::std::segment::SegLen),
|
||||
Box::new(crate::std::segment::SegAng),
|
||||
Box::new(crate::std::segment::TangentToEnd),
|
||||
Box::new(crate::std::shapes::CircleThreePoint),
|
||||
// Box::new(crate::std::shapes::Ellipse),
|
||||
Box::new(crate::std::shapes::Polygon),
|
||||
Box::new(crate::std::sketch::Conic),
|
||||
Box::new(crate::std::sketch::Elliptic),
|
||||
Box::new(crate::std::sketch::EllipticPoint),
|
||||
Box::new(crate::std::sketch::Hyperbolic),
|
||||
Box::new(crate::std::sketch::HyperbolicPoint),
|
||||
Box::new(crate::std::sketch::InvoluteCircular),
|
||||
Box::new(crate::std::sketch::Line),
|
||||
Box::new(crate::std::sketch::Parabolic),
|
||||
Box::new(crate::std::sketch::ParabolicPoint),
|
||||
Box::new(crate::std::sketch::XLine),
|
||||
Box::new(crate::std::sketch::YLine),
|
||||
Box::new(crate::std::sketch::AngledLine),
|
||||
Box::new(crate::std::sketch::AngledLineThatIntersects),
|
||||
Box::new(crate::std::sketch::StartSketchOn),
|
||||
Box::new(crate::std::sketch::StartProfile),
|
||||
Box::new(crate::std::sketch::ProfileStartX),
|
||||
Box::new(crate::std::sketch::ProfileStartY),
|
||||
Box::new(crate::std::sketch::ProfileStart),
|
||||
Box::new(crate::std::sketch::Close),
|
||||
Box::new(crate::std::sketch::Arc),
|
||||
Box::new(crate::std::sketch::TangentialArc),
|
||||
Box::new(crate::std::sketch::BezierCurve),
|
||||
Box::new(crate::std::sketch::Subtract2D),
|
||||
Box::new(crate::std::patterns::PatternLinear2D),
|
||||
Box::new(crate::std::patterns::PatternCircular2D),
|
||||
Box::new(crate::std::edge::GetOppositeEdge),
|
||||
Box::new(crate::std::edge::GetNextAdjacentEdge),
|
||||
Box::new(crate::std::edge::GetPreviousAdjacentEdge),
|
||||
Box::new(crate::std::edge::GetCommonEdge),
|
||||
Box::new(crate::std::sweep::Sweep),
|
||||
Box::new(crate::std::loft::Loft),
|
||||
Box::new(crate::std::assert::Assert),
|
||||
Box::new(crate::std::assert::AssertIs),
|
||||
Box::new(crate::std::transform::Scale),
|
||||
Box::new(crate::std::transform::Translate),
|
||||
Box::new(crate::std::transform::Rotate),
|
||||
];
|
||||
}
|
||||
|
||||
pub fn name_in_stdlib(name: &str) -> bool {
|
||||
CORE_FNS.iter().any(|f| f.name() == name)
|
||||
}
|
||||
|
||||
pub fn get_stdlib_fn(name: &str) -> Option<Box<dyn StdLibFn>> {
|
||||
CORE_FNS.iter().find(|f| f.name() == name).cloned()
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct StdFnProps {
|
||||
pub name: String,
|
||||
@ -240,14 +172,30 @@ pub(crate) fn std_fn(path: &str, fn_name: &str) -> (crate::std::StdFn, StdFnProp
|
||||
|e, a| Box::pin(crate::std::mirror::mirror_2d(e, a)),
|
||||
StdFnProps::default("std::transform::mirror2d"),
|
||||
),
|
||||
("sketch", "revolve") => (
|
||||
|e, a| Box::pin(crate::std::revolve::revolve(e, a)),
|
||||
StdFnProps::default("std::sketch::revolve").include_in_feature_tree(),
|
||||
("transform", "translate") => (
|
||||
|e, a| Box::pin(crate::std::transform::translate(e, a)),
|
||||
StdFnProps::default("std::transform::translate").include_in_feature_tree(),
|
||||
),
|
||||
("transform", "rotate") => (
|
||||
|e, a| Box::pin(crate::std::transform::rotate(e, a)),
|
||||
StdFnProps::default("std::transform::rotate").include_in_feature_tree(),
|
||||
),
|
||||
("transform", "scale") => (
|
||||
|e, a| Box::pin(crate::std::transform::scale(e, a)),
|
||||
StdFnProps::default("std::transform::scale").include_in_feature_tree(),
|
||||
),
|
||||
("prelude", "offsetPlane") => (
|
||||
|e, a| Box::pin(crate::std::planes::offset_plane(e, a)),
|
||||
StdFnProps::default("std::offsetPlane").include_in_feature_tree(),
|
||||
),
|
||||
("prelude", "assert") => (
|
||||
|e, a| Box::pin(crate::std::assert::assert(e, a)),
|
||||
StdFnProps::default("std::assert"),
|
||||
),
|
||||
("prelude", "assertIs") => (
|
||||
|e, a| Box::pin(crate::std::assert::assert_is(e, a)),
|
||||
StdFnProps::default("std::assertIs"),
|
||||
),
|
||||
("solid", "fillet") => (
|
||||
|e, a| Box::pin(crate::std::fillet::fillet(e, a)),
|
||||
StdFnProps::default("std::solid::fillet").include_in_feature_tree(),
|
||||
@ -312,10 +260,170 @@ pub(crate) fn std_fn(path: &str, fn_name: &str) -> (crate::std::StdFn, StdFnProp
|
||||
|e, a| Box::pin(crate::std::clone::clone(e, a)),
|
||||
StdFnProps::default("std::clone").include_in_feature_tree(),
|
||||
),
|
||||
("sketch", "circle") => (
|
||||
|e, a| Box::pin(crate::std::shapes::circle(e, a)),
|
||||
StdFnProps::default("std::sketch::circle"),
|
||||
),
|
||||
("sketch", "extrude") => (
|
||||
|e, a| Box::pin(crate::std::extrude::extrude(e, a)),
|
||||
StdFnProps::default("std::sketch::extrude").include_in_feature_tree(),
|
||||
),
|
||||
("sketch", "patternTransform2d") => (
|
||||
|e, a| Box::pin(crate::std::patterns::pattern_transform_2d(e, a)),
|
||||
StdFnProps::default("std::sketch::patternTransform2d"),
|
||||
),
|
||||
("sketch", "revolve") => (
|
||||
|e, a| Box::pin(crate::std::revolve::revolve(e, a)),
|
||||
StdFnProps::default("std::sketch::revolve").include_in_feature_tree(),
|
||||
),
|
||||
("sketch", "sweep") => (
|
||||
|e, a| Box::pin(crate::std::sweep::sweep(e, a)),
|
||||
StdFnProps::default("std::sketch::sweep").include_in_feature_tree(),
|
||||
),
|
||||
("sketch", "loft") => (
|
||||
|e, a| Box::pin(crate::std::loft::loft(e, a)),
|
||||
StdFnProps::default("std::sketch::loft").include_in_feature_tree(),
|
||||
),
|
||||
("sketch", "polygon") => (
|
||||
|e, a| Box::pin(crate::std::shapes::polygon(e, a)),
|
||||
StdFnProps::default("std::sketch::polygon"),
|
||||
),
|
||||
("sketch", "circleThreePoint") => (
|
||||
|e, a| Box::pin(crate::std::shapes::circle_three_point(e, a)),
|
||||
StdFnProps::default("std::sketch::circleThreePoint"),
|
||||
),
|
||||
("sketch", "getCommonEdge") => (
|
||||
|e, a| Box::pin(crate::std::edge::get_common_edge(e, a)),
|
||||
StdFnProps::default("std::sketch::getCommonEdge"),
|
||||
),
|
||||
("sketch", "getNextAdjacentEdge") => (
|
||||
|e, a| Box::pin(crate::std::edge::get_next_adjacent_edge(e, a)),
|
||||
StdFnProps::default("std::sketch::getNextAdjacentEdge"),
|
||||
),
|
||||
("sketch", "getOppositeEdge") => (
|
||||
|e, a| Box::pin(crate::std::edge::get_opposite_edge(e, a)),
|
||||
StdFnProps::default("std::sketch::revolve"),
|
||||
),
|
||||
("sketch", "getPreviousAdjacentEdge") => (
|
||||
|e, a| Box::pin(crate::std::edge::get_previous_adjacent_edge(e, a)),
|
||||
StdFnProps::default("std::sketch::getPreviousAdjacentEdge"),
|
||||
),
|
||||
("sketch", "patternLinear2d") => (
|
||||
|e, a| Box::pin(crate::std::patterns::pattern_linear_2d(e, a)),
|
||||
StdFnProps::default("std::sketch::patternLinear2d"),
|
||||
),
|
||||
("sketch", "patternCircular2d") => (
|
||||
|e, a| Box::pin(crate::std::patterns::pattern_circular_2d(e, a)),
|
||||
StdFnProps::default("std::sketch::patternCircular2d"),
|
||||
),
|
||||
("sketch", "segEnd") => (
|
||||
|e, a| Box::pin(crate::std::segment::segment_end(e, a)),
|
||||
StdFnProps::default("std::sketch::segEnd"),
|
||||
),
|
||||
("sketch", "segEndX") => (
|
||||
|e, a| Box::pin(crate::std::segment::segment_end_x(e, a)),
|
||||
StdFnProps::default("std::sketch::segEndX"),
|
||||
),
|
||||
("sketch", "segEndY") => (
|
||||
|e, a| Box::pin(crate::std::segment::segment_end_y(e, a)),
|
||||
StdFnProps::default("std::sketch::segEndY"),
|
||||
),
|
||||
("sketch", "segStart") => (
|
||||
|e, a| Box::pin(crate::std::segment::segment_start(e, a)),
|
||||
StdFnProps::default("std::sketch::segStart"),
|
||||
),
|
||||
("sketch", "segStartX") => (
|
||||
|e, a| Box::pin(crate::std::segment::segment_start_x(e, a)),
|
||||
StdFnProps::default("std::sketch::segStartX"),
|
||||
),
|
||||
("sketch", "segStartY") => (
|
||||
|e, a| Box::pin(crate::std::segment::segment_start_y(e, a)),
|
||||
StdFnProps::default("std::sketch::segStartY"),
|
||||
),
|
||||
("sketch", "lastSegX") => (
|
||||
|e, a| Box::pin(crate::std::segment::last_segment_x(e, a)),
|
||||
StdFnProps::default("std::sketch::lastSegX"),
|
||||
),
|
||||
("sketch", "lastSegY") => (
|
||||
|e, a| Box::pin(crate::std::segment::last_segment_y(e, a)),
|
||||
StdFnProps::default("std::sketch::lastSegY"),
|
||||
),
|
||||
("sketch", "segLen") => (
|
||||
|e, a| Box::pin(crate::std::segment::segment_length(e, a)),
|
||||
StdFnProps::default("std::sketch::segLen"),
|
||||
),
|
||||
("sketch", "segAng") => (
|
||||
|e, a| Box::pin(crate::std::segment::segment_angle(e, a)),
|
||||
StdFnProps::default("std::sketch::segAng"),
|
||||
),
|
||||
("sketch", "tangentToEnd") => (
|
||||
|e, a| Box::pin(crate::std::segment::tangent_to_end(e, a)),
|
||||
StdFnProps::default("std::sketch::tangentToEnd"),
|
||||
),
|
||||
("sketch", "profileStart") => (
|
||||
|e, a| Box::pin(crate::std::sketch::profile_start(e, a)),
|
||||
StdFnProps::default("std::sketch::profileStart"),
|
||||
),
|
||||
("sketch", "profileStartX") => (
|
||||
|e, a| Box::pin(crate::std::sketch::profile_start_x(e, a)),
|
||||
StdFnProps::default("std::sketch::profileStartX"),
|
||||
),
|
||||
("sketch", "profileStartY") => (
|
||||
|e, a| Box::pin(crate::std::sketch::profile_start_y(e, a)),
|
||||
StdFnProps::default("std::sketch::profileStartY"),
|
||||
),
|
||||
("sketch", "startSketchOn") => (
|
||||
|e, a| Box::pin(crate::std::sketch::start_sketch_on(e, a)),
|
||||
StdFnProps::default("std::sketch::startSketchOn").include_in_feature_tree(),
|
||||
),
|
||||
("sketch", "startProfile") => (
|
||||
|e, a| Box::pin(crate::std::sketch::start_profile(e, a)),
|
||||
StdFnProps::default("std::sketch::startProfile"),
|
||||
),
|
||||
("sketch", "involuteCircular") => (
|
||||
|e, a| Box::pin(crate::std::sketch::involute_circular(e, a)),
|
||||
StdFnProps::default("std::sketch::startProfile"),
|
||||
),
|
||||
("sketch", "line") => (
|
||||
|e, a| Box::pin(crate::std::sketch::line(e, a)),
|
||||
StdFnProps::default("std::sketch::startProfile"),
|
||||
),
|
||||
("sketch", "xLine") => (
|
||||
|e, a| Box::pin(crate::std::sketch::x_line(e, a)),
|
||||
StdFnProps::default("std::sketch::startProfile"),
|
||||
),
|
||||
("sketch", "yLine") => (
|
||||
|e, a| Box::pin(crate::std::sketch::y_line(e, a)),
|
||||
StdFnProps::default("std::sketch::startProfile"),
|
||||
),
|
||||
("sketch", "angledLine") => (
|
||||
|e, a| Box::pin(crate::std::sketch::angled_line(e, a)),
|
||||
StdFnProps::default("std::sketch::startProfile"),
|
||||
),
|
||||
("sketch", "angledLineThatIntersects") => (
|
||||
|e, a| Box::pin(crate::std::sketch::angled_line_that_intersects(e, a)),
|
||||
StdFnProps::default("std::sketch::startProfile"),
|
||||
),
|
||||
("sketch", "close") => (
|
||||
|e, a| Box::pin(crate::std::sketch::close(e, a)),
|
||||
StdFnProps::default("std::sketch::startProfile"),
|
||||
),
|
||||
("sketch", "arc") => (
|
||||
|e, a| Box::pin(crate::std::sketch::arc(e, a)),
|
||||
StdFnProps::default("std::sketch::startProfile"),
|
||||
),
|
||||
("sketch", "tangentialArc") => (
|
||||
|e, a| Box::pin(crate::std::sketch::tangential_arc(e, a)),
|
||||
StdFnProps::default("std::sketch::startProfile"),
|
||||
),
|
||||
("sketch", "bezierCurve") => (
|
||||
|e, a| Box::pin(crate::std::sketch::bezier_curve(e, a)),
|
||||
StdFnProps::default("std::sketch::startProfile"),
|
||||
),
|
||||
("sketch", "subtract2d") => (
|
||||
|e, a| Box::pin(crate::std::sketch::subtract_2d(e, a)),
|
||||
StdFnProps::default("std::sketch::startProfile").include_in_feature_tree(),
|
||||
),
|
||||
("appearance", "hexString") => (
|
||||
|e, a| Box::pin(crate::std::appearance::hex_string(e, a)),
|
||||
StdFnProps::default("std::appearance::hexString"),
|
||||
@ -340,56 +448,5 @@ pub(crate) fn std_ty(path: &str, fn_name: &str) -> (PrimitiveType, StdFnProps) {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct StdLib {
|
||||
pub fns: IndexMap<String, Box<dyn StdLibFn>>,
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for StdLib {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("StdLib").field("fns.len()", &self.fns.len()).finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl StdLib {
|
||||
pub fn new() -> Self {
|
||||
let fns = CORE_FNS
|
||||
.clone()
|
||||
.into_iter()
|
||||
.map(|internal_fn| (internal_fn.name(), internal_fn))
|
||||
.collect();
|
||||
|
||||
Self { fns }
|
||||
}
|
||||
|
||||
// Get the combined hashmaps.
|
||||
pub fn combined(&self) -> IndexMap<String, Box<dyn StdLibFn>> {
|
||||
self.fns.clone()
|
||||
}
|
||||
|
||||
pub fn get(&self, name: &str) -> Option<Box<dyn StdLibFn>> {
|
||||
self.fns.get(name).cloned()
|
||||
}
|
||||
|
||||
pub fn get_rust_function(&self, name: &Name) -> Option<Box<dyn StdLibFn>> {
|
||||
if let Some(name) = name.local_ident() {
|
||||
if let Some(f) = self.get(name.inner) {
|
||||
return Some(f);
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
pub fn contains_key(&self, key: &str) -> bool {
|
||||
self.fns.contains_key(key)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for StdLib {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
/// The default tolerance for modeling commands in [`kittycad_modeling_cmds::length_unit::LengthUnit`].
|
||||
const DEFAULT_TOLERANCE: f64 = 0.0000001;
|
||||
|
@ -3,7 +3,6 @@
|
||||
use std::cmp::Ordering;
|
||||
|
||||
use anyhow::Result;
|
||||
use kcl_derive_docs::stdlib;
|
||||
use kcmc::{
|
||||
each_cmd as mcmd, length_unit::LengthUnit, ok_response::OkModelingCmdResponse, shared::Transform,
|
||||
websocket::OkWebSocketResponseData, ModelingCmd,
|
||||
@ -37,9 +36,9 @@ const MUST_HAVE_ONE_INSTANCE: &str = "There must be at least 1 instance of your
|
||||
/// Repeat some 3D solid, changing each repetition slightly.
|
||||
pub async fn pattern_transform(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
||||
let solids = args.get_unlabeled_kw_arg_typed("solids", &RuntimeType::solids(), exec_state)?;
|
||||
let instances: u32 = args.get_kw_arg("instances")?;
|
||||
let instances: u32 = args.get_kw_arg_typed("instances", &RuntimeType::count(), exec_state)?;
|
||||
let transform: &FunctionSource = args.get_kw_arg("transform")?;
|
||||
let use_original: Option<bool> = args.get_kw_arg_opt("useOriginal")?;
|
||||
let use_original = args.get_kw_arg_opt_typed("useOriginal", &RuntimeType::bool(), exec_state)?;
|
||||
|
||||
let solids = inner_pattern_transform(solids, instances, transform, use_original, exec_state, &args).await?;
|
||||
Ok(solids.into())
|
||||
@ -48,9 +47,9 @@ pub async fn pattern_transform(exec_state: &mut ExecState, args: Args) -> Result
|
||||
/// Repeat some 2D sketch, changing each repetition slightly.
|
||||
pub async fn pattern_transform_2d(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
||||
let sketches = args.get_unlabeled_kw_arg_typed("sketches", &RuntimeType::sketches(), exec_state)?;
|
||||
let instances: u32 = args.get_kw_arg("instances")?;
|
||||
let instances: u32 = args.get_kw_arg_typed("instances", &RuntimeType::count(), exec_state)?;
|
||||
let transform: &FunctionSource = args.get_kw_arg("transform")?;
|
||||
let use_original: Option<bool> = args.get_kw_arg_opt("useOriginal")?;
|
||||
let use_original = args.get_kw_arg_opt_typed("useOriginal", &RuntimeType::bool(), exec_state)?;
|
||||
|
||||
let sketches = inner_pattern_transform_2d(sketches, instances, transform, use_original, exec_state, &args).await?;
|
||||
Ok(sketches.into())
|
||||
@ -67,7 +66,7 @@ async fn inner_pattern_transform<'a>(
|
||||
// Build the vec of transforms, one for each repetition.
|
||||
let mut transform_vec = Vec::with_capacity(usize::try_from(instances).unwrap());
|
||||
if instances < 1 {
|
||||
return Err(KclError::Semantic(KclErrorDetails::new(
|
||||
return Err(KclError::new_semantic(KclErrorDetails::new(
|
||||
MUST_HAVE_ONE_INSTANCE.to_owned(),
|
||||
vec![args.source_range],
|
||||
)));
|
||||
@ -97,7 +96,7 @@ async fn inner_pattern_transform_2d<'a>(
|
||||
// Build the vec of transforms, one for each repetition.
|
||||
let mut transform_vec = Vec::with_capacity(usize::try_from(instances).unwrap());
|
||||
if instances < 1 {
|
||||
return Err(KclError::Semantic(KclErrorDetails::new(
|
||||
return Err(KclError::new_semantic(KclErrorDetails::new(
|
||||
MUST_HAVE_ONE_INSTANCE.to_owned(),
|
||||
vec![args.source_range],
|
||||
)));
|
||||
@ -177,7 +176,7 @@ async fn send_pattern_transform<T: GeometryTrait>(
|
||||
}
|
||||
&mock_ids
|
||||
} else {
|
||||
return Err(KclError::Engine(KclErrorDetails::new(
|
||||
return Err(KclError::new_engine(KclErrorDetails::new(
|
||||
format!("EntityLinearPattern response was not as expected: {:?}", resp),
|
||||
vec![args.source_range],
|
||||
)));
|
||||
@ -223,7 +222,7 @@ async fn make_transform<T: GeometryTrait>(
|
||||
// Unpack the returned transform object.
|
||||
let source_ranges = vec![source_range];
|
||||
let transform_fn_return = transform_fn_return.ok_or_else(|| {
|
||||
KclError::Semantic(KclErrorDetails::new(
|
||||
KclError::new_semantic(KclErrorDetails::new(
|
||||
"Transform function must return a value".to_string(),
|
||||
source_ranges.clone(),
|
||||
))
|
||||
@ -234,7 +233,7 @@ async fn make_transform<T: GeometryTrait>(
|
||||
let transforms: Vec<_> = value
|
||||
.into_iter()
|
||||
.map(|val| {
|
||||
val.into_object().ok_or(KclError::Semantic(KclErrorDetails::new(
|
||||
val.into_object().ok_or(KclError::new_semantic(KclErrorDetails::new(
|
||||
"Transform function must return a transform object".to_string(),
|
||||
source_ranges.clone(),
|
||||
)))
|
||||
@ -243,7 +242,7 @@ async fn make_transform<T: GeometryTrait>(
|
||||
transforms
|
||||
}
|
||||
_ => {
|
||||
return Err(KclError::Semantic(KclErrorDetails::new(
|
||||
return Err(KclError::new_semantic(KclErrorDetails::new(
|
||||
"Transform function must return a transform object".to_string(),
|
||||
source_ranges.clone(),
|
||||
)))
|
||||
@ -266,7 +265,7 @@ fn transform_from_obj_fields<T: GeometryTrait>(
|
||||
Some(KclValue::Bool { value: true, .. }) => true,
|
||||
Some(KclValue::Bool { value: false, .. }) => false,
|
||||
Some(_) => {
|
||||
return Err(KclError::Semantic(KclErrorDetails::new(
|
||||
return Err(KclError::new_semantic(KclErrorDetails::new(
|
||||
"The 'replicate' key must be a bool".to_string(),
|
||||
source_ranges.clone(),
|
||||
)));
|
||||
@ -298,7 +297,7 @@ fn transform_from_obj_fields<T: GeometryTrait>(
|
||||
let mut rotation = Rotation::default();
|
||||
if let Some(rot) = transform.get("rotation") {
|
||||
let KclValue::Object { value: rot, meta: _ } = rot else {
|
||||
return Err(KclError::Semantic(KclErrorDetails::new(
|
||||
return Err(KclError::new_semantic(KclErrorDetails::new(
|
||||
"The 'rotation' key must be an object (with optional fields 'angle', 'axis' and 'origin')".to_owned(),
|
||||
source_ranges.clone(),
|
||||
)));
|
||||
@ -312,7 +311,7 @@ fn transform_from_obj_fields<T: GeometryTrait>(
|
||||
rotation.angle = Angle::from_degrees(*number);
|
||||
}
|
||||
_ => {
|
||||
return Err(KclError::Semantic(KclErrorDetails::new(
|
||||
return Err(KclError::new_semantic(KclErrorDetails::new(
|
||||
"The 'rotation.angle' key must be a number (of degrees)".to_owned(),
|
||||
source_ranges.clone(),
|
||||
)));
|
||||
@ -346,7 +345,7 @@ fn array_to_point3d(
|
||||
) -> Result<[TyF64; 3], KclError> {
|
||||
val.coerce(&RuntimeType::point3d(), true, exec_state)
|
||||
.map_err(|e| {
|
||||
KclError::Semantic(KclErrorDetails::new(
|
||||
KclError::new_semantic(KclErrorDetails::new(
|
||||
format!(
|
||||
"Expected an array of 3 numbers (i.e., a 3D point), found {}",
|
||||
e.found
|
||||
@ -366,7 +365,7 @@ fn array_to_point2d(
|
||||
) -> Result<[TyF64; 2], KclError> {
|
||||
val.coerce(&RuntimeType::point2d(), true, exec_state)
|
||||
.map_err(|e| {
|
||||
KclError::Semantic(KclErrorDetails::new(
|
||||
KclError::new_semantic(KclErrorDetails::new(
|
||||
format!(
|
||||
"Expected an array of 2 numbers (i.e., a 2D point), found {}",
|
||||
e.found
|
||||
@ -521,7 +520,7 @@ mod tests {
|
||||
/// A linear pattern on a 2D sketch.
|
||||
pub async fn pattern_linear_2d(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
||||
let sketches = args.get_unlabeled_kw_arg_typed("sketches", &RuntimeType::sketches(), exec_state)?;
|
||||
let instances: u32 = args.get_kw_arg("instances")?;
|
||||
let instances: u32 = args.get_kw_arg_typed("instances", &RuntimeType::count(), exec_state)?;
|
||||
let distance: TyF64 = args.get_kw_arg_typed("distance", &RuntimeType::length(), exec_state)?;
|
||||
let axis: Axis2dOrPoint2d = args.get_kw_arg_typed(
|
||||
"axis",
|
||||
@ -531,11 +530,11 @@ pub async fn pattern_linear_2d(exec_state: &mut ExecState, args: Args) -> Result
|
||||
]),
|
||||
exec_state,
|
||||
)?;
|
||||
let use_original: Option<bool> = args.get_kw_arg_opt("useOriginal")?;
|
||||
let use_original = args.get_kw_arg_opt_typed("useOriginal", &RuntimeType::bool(), exec_state)?;
|
||||
|
||||
let axis = axis.to_point2d();
|
||||
if axis[0].n == 0.0 && axis[1].n == 0.0 {
|
||||
return Err(KclError::Semantic(KclErrorDetails::new(
|
||||
return Err(KclError::new_semantic(KclErrorDetails::new(
|
||||
"The axis of the linear pattern cannot be the zero vector. Otherwise they will just duplicate in place."
|
||||
.to_owned(),
|
||||
vec![args.source_range],
|
||||
@ -546,47 +545,6 @@ pub async fn pattern_linear_2d(exec_state: &mut ExecState, args: Args) -> Result
|
||||
Ok(sketches.into())
|
||||
}
|
||||
|
||||
/// Repeat a 2-dimensional sketch along some dimension, with a dynamic amount
|
||||
/// of distance between each repetition, some specified number of times.
|
||||
///
|
||||
/// ```no_run
|
||||
/// /// Pattern using a named axis.
|
||||
///
|
||||
/// exampleSketch = startSketchOn(XZ)
|
||||
/// |> circle(center = [0, 0], radius = 1)
|
||||
/// |> patternLinear2d(
|
||||
/// axis = X,
|
||||
/// instances = 7,
|
||||
/// distance = 4
|
||||
/// )
|
||||
///
|
||||
/// example = extrude(exampleSketch, length = 1)
|
||||
/// ```
|
||||
///
|
||||
/// ```no_run
|
||||
/// /// Pattern using a raw axis.
|
||||
///
|
||||
/// exampleSketch = startSketchOn(XZ)
|
||||
/// |> circle(center = [0, 0], radius = 1)
|
||||
/// |> patternLinear2d(
|
||||
/// axis = [1, 0],
|
||||
/// instances = 7,
|
||||
/// distance = 4
|
||||
/// )
|
||||
///
|
||||
/// example = extrude(exampleSketch, length = 1)
|
||||
/// ```
|
||||
#[stdlib {
|
||||
name = "patternLinear2d",
|
||||
unlabeled_first = true,
|
||||
args = {
|
||||
sketches = { docs = "The sketch(es) to duplicate" },
|
||||
instances = { docs = "The number of total instances. Must be greater than or equal to 1. This includes the original entity. For example, if instances is 2, there will be two copies -- the original, and one new copy. If instances is 1, this has no effect." },
|
||||
distance = { docs = "Distance between each repetition. Also known as 'spacing'."},
|
||||
axis = { docs = "The axis of the pattern. A 2D vector.", snippet_value_array = ["1", "0"] },
|
||||
use_original = { docs = "If the target was sketched on an extrusion, setting this will use the original sketch as the target, not the entire joined solid. Defaults to false." },
|
||||
}
|
||||
}]
|
||||
async fn inner_pattern_linear_2d(
|
||||
sketches: Vec<Sketch>,
|
||||
instances: u32,
|
||||
@ -622,7 +580,7 @@ async fn inner_pattern_linear_2d(
|
||||
/// A linear pattern on a 3D model.
|
||||
pub async fn pattern_linear_3d(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
||||
let solids = args.get_unlabeled_kw_arg_typed("solids", &RuntimeType::solids(), exec_state)?;
|
||||
let instances: u32 = args.get_kw_arg("instances")?;
|
||||
let instances: u32 = args.get_kw_arg_typed("instances", &RuntimeType::count(), exec_state)?;
|
||||
let distance: TyF64 = args.get_kw_arg_typed("distance", &RuntimeType::length(), exec_state)?;
|
||||
let axis: Axis3dOrPoint3d = args.get_kw_arg_typed(
|
||||
"axis",
|
||||
@ -632,11 +590,11 @@ pub async fn pattern_linear_3d(exec_state: &mut ExecState, args: Args) -> Result
|
||||
]),
|
||||
exec_state,
|
||||
)?;
|
||||
let use_original: Option<bool> = args.get_kw_arg_opt("useOriginal")?;
|
||||
let use_original = args.get_kw_arg_opt_typed("useOriginal", &RuntimeType::bool(), exec_state)?;
|
||||
|
||||
let axis = axis.to_point3d();
|
||||
if axis[0].n == 0.0 && axis[1].n == 0.0 && axis[2].n == 0.0 {
|
||||
return Err(KclError::Semantic(KclErrorDetails::new(
|
||||
return Err(KclError::new_semantic(KclErrorDetails::new(
|
||||
"The axis of the linear pattern cannot be the zero vector. Otherwise they will just duplicate in place."
|
||||
.to_owned(),
|
||||
vec![args.source_range],
|
||||
@ -790,11 +748,11 @@ impl CircularPattern {
|
||||
/// A circular pattern on a 2D sketch.
|
||||
pub async fn pattern_circular_2d(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
||||
let sketches = args.get_unlabeled_kw_arg_typed("sketches", &RuntimeType::sketches(), exec_state)?;
|
||||
let instances: u32 = args.get_kw_arg("instances")?;
|
||||
let instances: u32 = args.get_kw_arg_typed("instances", &RuntimeType::count(), exec_state)?;
|
||||
let center: [TyF64; 2] = args.get_kw_arg_typed("center", &RuntimeType::point2d(), exec_state)?;
|
||||
let arc_degrees: Option<TyF64> = args.get_kw_arg_opt_typed("arcDegrees", &RuntimeType::degrees(), exec_state)?;
|
||||
let rotate_duplicates: Option<bool> = args.get_kw_arg_opt("rotateDuplicates")?;
|
||||
let use_original: Option<bool> = args.get_kw_arg_opt("useOriginal")?;
|
||||
let rotate_duplicates = args.get_kw_arg_opt_typed("rotateDuplicates", &RuntimeType::bool(), exec_state)?;
|
||||
let use_original = args.get_kw_arg_opt_typed("useOriginal", &RuntimeType::bool(), exec_state)?;
|
||||
|
||||
let sketches = inner_pattern_circular_2d(
|
||||
sketches,
|
||||
@ -810,40 +768,6 @@ pub async fn pattern_circular_2d(exec_state: &mut ExecState, args: Args) -> Resu
|
||||
Ok(sketches.into())
|
||||
}
|
||||
|
||||
/// Repeat a 2-dimensional sketch some number of times along a partial or
|
||||
/// complete circle some specified number of times. Each object may
|
||||
/// additionally be rotated along the circle, ensuring orientation of the
|
||||
/// solid with respect to the center of the circle is maintained.
|
||||
///
|
||||
/// ```no_run
|
||||
/// exampleSketch = startSketchOn(XZ)
|
||||
/// |> startProfile(at = [.5, 25])
|
||||
/// |> line(end = [0, 5])
|
||||
/// |> line(end = [-1, 0])
|
||||
/// |> line(end = [0, -5])
|
||||
/// |> close()
|
||||
/// |> patternCircular2d(
|
||||
/// center = [0, 0],
|
||||
/// instances = 13,
|
||||
/// arcDegrees = 360,
|
||||
/// rotateDuplicates = true
|
||||
/// )
|
||||
///
|
||||
/// example = extrude(exampleSketch, length = 1)
|
||||
/// ```
|
||||
#[stdlib {
|
||||
name = "patternCircular2d",
|
||||
unlabeled_first = true,
|
||||
args = {
|
||||
sketch_set = { docs = "Which sketch(es) to pattern" },
|
||||
instances = { docs = "The number of total instances. Must be greater than or equal to 1. This includes the original entity. For example, if instances is 2, there will be two copies -- the original, and one new copy. If instances is 1, this has no effect."},
|
||||
center = { docs = "The center about which to make the pattern. This is a 2D vector.", snippet_value_array = ["0", "0"]},
|
||||
arc_degrees = { docs = "The arc angle (in degrees) to place the repetitions. Must be greater than 0. Defaults to 360."},
|
||||
rotate_duplicates= { docs = "Whether or not to rotate the duplicates as they are copied. Defaults to true."},
|
||||
use_original= { docs = "If the target was sketched on an extrusion, setting this will use the original sketch as the target, not the entire joined solid. Defaults to false."},
|
||||
},
|
||||
tags = ["sketch"]
|
||||
}]
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
async fn inner_pattern_circular_2d(
|
||||
sketch_set: Vec<Sketch>,
|
||||
@ -879,7 +803,7 @@ async fn inner_pattern_circular_2d(
|
||||
.await?;
|
||||
|
||||
let Geometries::Sketches(new_sketches) = geometries else {
|
||||
return Err(KclError::Semantic(KclErrorDetails::new(
|
||||
return Err(KclError::new_semantic(KclErrorDetails::new(
|
||||
"Expected a vec of sketches".to_string(),
|
||||
vec![args.source_range],
|
||||
)));
|
||||
@ -915,10 +839,10 @@ pub async fn pattern_circular_3d(exec_state: &mut ExecState, args: Args) -> Resu
|
||||
// The arc angle (in degrees) to place the repetitions. Must be greater than 0.
|
||||
let arc_degrees: Option<TyF64> = args.get_kw_arg_opt_typed("arcDegrees", &RuntimeType::degrees(), exec_state)?;
|
||||
// Whether or not to rotate the duplicates as they are copied.
|
||||
let rotate_duplicates: Option<bool> = args.get_kw_arg_opt("rotateDuplicates")?;
|
||||
let rotate_duplicates = args.get_kw_arg_opt_typed("rotateDuplicates", &RuntimeType::bool(), exec_state)?;
|
||||
// If the target being patterned is itself a pattern, then, should you use the original solid,
|
||||
// or the pattern?
|
||||
let use_original: Option<bool> = args.get_kw_arg_opt("useOriginal")?;
|
||||
let use_original = args.get_kw_arg_opt_typed("useOriginal", &RuntimeType::bool(), exec_state)?;
|
||||
|
||||
let solids = inner_pattern_circular_3d(
|
||||
solids,
|
||||
@ -977,7 +901,7 @@ async fn inner_pattern_circular_3d(
|
||||
.await?;
|
||||
|
||||
let Geometries::Solids(new_solids) = geometries else {
|
||||
return Err(KclError::Semantic(KclErrorDetails::new(
|
||||
return Err(KclError::new_semantic(KclErrorDetails::new(
|
||||
"Expected a vec of solids".to_string(),
|
||||
vec![args.source_range],
|
||||
)));
|
||||
@ -1002,7 +926,7 @@ async fn pattern_circular(
|
||||
return Ok(Geometries::from(geometry));
|
||||
}
|
||||
RepetitionsNeeded::Invalid => {
|
||||
return Err(KclError::Semantic(KclErrorDetails::new(
|
||||
return Err(KclError::new_semantic(KclErrorDetails::new(
|
||||
MUST_HAVE_ONE_INSTANCE.to_owned(),
|
||||
vec![args.source_range],
|
||||
)));
|
||||
@ -1047,7 +971,7 @@ async fn pattern_circular(
|
||||
}
|
||||
&mock_ids
|
||||
} else {
|
||||
return Err(KclError::Engine(KclErrorDetails::new(
|
||||
return Err(KclError::new_engine(KclErrorDetails::new(
|
||||
format!("EntityCircularPattern response was not as expected: {:?}", resp),
|
||||
vec![args.source_range],
|
||||
)));
|
||||
|
@ -12,7 +12,7 @@ use crate::{
|
||||
|
||||
/// Offset a plane by a distance along its normal.
|
||||
pub async fn offset_plane(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
||||
let std_plane = args.get_unlabeled_kw_arg("plane")?;
|
||||
let std_plane = args.get_unlabeled_kw_arg_typed("plane", &RuntimeType::plane(), exec_state)?;
|
||||
let offset: TyF64 = args.get_kw_arg_typed("offset", &RuntimeType::length(), exec_state)?;
|
||||
let plane = inner_offset_plane(std_plane, offset, exec_state, &args).await?;
|
||||
Ok(KclValue::Plane { value: Box::new(plane) })
|
||||
|
@ -37,7 +37,7 @@ pub async fn revolve(exec_state: &mut ExecState, args: Args) -> Result<KclValue,
|
||||
let tolerance: Option<TyF64> = args.get_kw_arg_opt_typed("tolerance", &RuntimeType::length(), exec_state)?;
|
||||
let tag_start = args.get_kw_arg_opt("tagStart")?;
|
||||
let tag_end = args.get_kw_arg_opt("tagEnd")?;
|
||||
let symmetric = args.get_kw_arg_opt("symmetric")?;
|
||||
let symmetric = args.get_kw_arg_opt_typed("symmetric", &RuntimeType::bool(), exec_state)?;
|
||||
let bidirectional_angle: Option<TyF64> =
|
||||
args.get_kw_arg_opt_typed("bidirectionalAngle", &RuntimeType::angle(), exec_state)?;
|
||||
|
||||
@ -75,7 +75,7 @@ async fn inner_revolve(
|
||||
// We don't use validate() here because we want to return a specific error message that is
|
||||
// nice and we use the other data in the docs, so we still need use the derive above for the json schema.
|
||||
if !(-360.0..=360.0).contains(&angle) || angle == 0.0 {
|
||||
return Err(KclError::Semantic(KclErrorDetails::new(
|
||||
return Err(KclError::new_semantic(KclErrorDetails::new(
|
||||
format!("Expected angle to be between -360 and 360 and not 0, found `{}`", angle),
|
||||
vec![args.source_range],
|
||||
)));
|
||||
@ -87,7 +87,7 @@ async fn inner_revolve(
|
||||
// We don't use validate() here because we want to return a specific error message that is
|
||||
// nice and we use the other data in the docs, so we still need use the derive above for the json schema.
|
||||
if !(-360.0..=360.0).contains(&bidirectional_angle) || bidirectional_angle == 0.0 {
|
||||
return Err(KclError::Semantic(KclErrorDetails::new(
|
||||
return Err(KclError::new_semantic(KclErrorDetails::new(
|
||||
format!(
|
||||
"Expected bidirectional angle to be between -360 and 360 and not 0, found `{}`",
|
||||
bidirectional_angle
|
||||
@ -99,7 +99,7 @@ async fn inner_revolve(
|
||||
if let Some(angle) = angle {
|
||||
let ang = angle.signum() * bidirectional_angle + angle;
|
||||
if !(-360.0..=360.0).contains(&ang) {
|
||||
return Err(KclError::Semantic(KclErrorDetails::new(
|
||||
return Err(KclError::new_semantic(KclErrorDetails::new(
|
||||
format!(
|
||||
"Combined angle and bidirectional must be between -360 and 360, found '{}'",
|
||||
ang
|
||||
@ -111,7 +111,7 @@ async fn inner_revolve(
|
||||
}
|
||||
|
||||
if symmetric.unwrap_or(false) && bidirectional_angle.is_some() {
|
||||
return Err(KclError::Semantic(KclErrorDetails::new(
|
||||
return Err(KclError::new_semantic(KclErrorDetails::new(
|
||||
"You cannot give both `symmetric` and `bidirectional` params, you have to choose one or the other"
|
||||
.to_owned(),
|
||||
vec![args.source_range],
|
||||
|
@ -1,7 +1,6 @@
|
||||
//! Functions related to line segments.
|
||||
|
||||
use anyhow::Result;
|
||||
use kcl_derive_docs::stdlib;
|
||||
use kittycad_modeling_cmds::shared::Angle;
|
||||
|
||||
use super::utils::untype_point;
|
||||
@ -16,49 +15,16 @@ use crate::{
|
||||
|
||||
/// Returns the point at the end of the given segment.
|
||||
pub async fn segment_end(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
||||
let tag: TagIdentifier = args.get_unlabeled_kw_arg("tag")?;
|
||||
let tag: TagIdentifier = args.get_unlabeled_kw_arg_typed("tag", &RuntimeType::tag_identifier(), exec_state)?;
|
||||
let pt = inner_segment_end(&tag, exec_state, args.clone())?;
|
||||
|
||||
args.make_kcl_val_from_point([pt[0].n, pt[1].n], pt[0].ty.clone())
|
||||
}
|
||||
|
||||
/// Compute the ending point of the provided line segment.
|
||||
///
|
||||
/// ```no_run
|
||||
/// w = 15
|
||||
/// cube = startSketchOn(XY)
|
||||
/// |> startProfile(at = [0, 0])
|
||||
/// |> line(end = [w, 0], tag = $line1)
|
||||
/// |> line(end = [0, w], tag = $line2)
|
||||
/// |> line(end = [-w, 0], tag = $line3)
|
||||
/// |> line(end = [0, -w], tag = $line4)
|
||||
/// |> close()
|
||||
/// |> extrude(length = 5)
|
||||
///
|
||||
/// fn cylinder(radius, tag) {
|
||||
/// return startSketchOn(XY)
|
||||
/// |> startProfile(at = [0, 0])
|
||||
/// |> circle(radius = radius, center = segEnd(tag) )
|
||||
/// |> extrude(length = radius)
|
||||
/// }
|
||||
///
|
||||
/// cylinder(radius = 1, tag = line1)
|
||||
/// cylinder(radius = 2, tag = line2)
|
||||
/// cylinder(radius = 3, tag = line3)
|
||||
/// cylinder(radius = 4, tag = line4)
|
||||
/// ```
|
||||
#[stdlib {
|
||||
name = "segEnd",
|
||||
unlabeled_first = true,
|
||||
args = {
|
||||
tag = { docs = "The line segment being queried by its tag"},
|
||||
},
|
||||
tags = ["sketch"]
|
||||
}]
|
||||
fn inner_segment_end(tag: &TagIdentifier, exec_state: &mut ExecState, args: Args) -> Result<[TyF64; 2], KclError> {
|
||||
let line = args.get_tag_engine_info(exec_state, tag)?;
|
||||
let path = line.path.clone().ok_or_else(|| {
|
||||
KclError::Type(KclErrorDetails::new(
|
||||
KclError::new_type(KclErrorDetails::new(
|
||||
format!("Expected a line segment with a path, found `{:?}`", line),
|
||||
vec![args.source_range],
|
||||
))
|
||||
@ -72,37 +38,16 @@ fn inner_segment_end(tag: &TagIdentifier, exec_state: &mut ExecState, args: Args
|
||||
|
||||
/// Returns the segment end of x.
|
||||
pub async fn segment_end_x(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
||||
let tag: TagIdentifier = args.get_unlabeled_kw_arg("tag")?;
|
||||
let tag: TagIdentifier = args.get_unlabeled_kw_arg_typed("tag", &RuntimeType::tag_identifier(), exec_state)?;
|
||||
let result = inner_segment_end_x(&tag, exec_state, args.clone())?;
|
||||
|
||||
Ok(args.make_user_val_from_f64_with_type(result))
|
||||
}
|
||||
|
||||
/// Compute the ending point of the provided line segment along the 'x' axis.
|
||||
///
|
||||
/// ```no_run
|
||||
/// exampleSketch = startSketchOn(XZ)
|
||||
/// |> startProfile(at = [0, 0])
|
||||
/// |> line(end = [20, 0], tag = $thing)
|
||||
/// |> line(end = [0, 5])
|
||||
/// |> line(end = [segEndX(thing), 0])
|
||||
/// |> line(end = [-20, 10])
|
||||
/// |> close()
|
||||
///
|
||||
/// example = extrude(exampleSketch, length = 5)
|
||||
/// ```
|
||||
#[stdlib {
|
||||
name = "segEndX",
|
||||
unlabeled_first = true,
|
||||
args = {
|
||||
tag = { docs = "The line segment being queried by its tag"},
|
||||
},
|
||||
tags = ["sketch"]
|
||||
}]
|
||||
fn inner_segment_end_x(tag: &TagIdentifier, exec_state: &mut ExecState, args: Args) -> Result<TyF64, KclError> {
|
||||
let line = args.get_tag_engine_info(exec_state, tag)?;
|
||||
let path = line.path.clone().ok_or_else(|| {
|
||||
KclError::Type(KclErrorDetails::new(
|
||||
KclError::new_type(KclErrorDetails::new(
|
||||
format!("Expected a line segment with a path, found `{:?}`", line),
|
||||
vec![args.source_range],
|
||||
))
|
||||
@ -113,38 +58,16 @@ fn inner_segment_end_x(tag: &TagIdentifier, exec_state: &mut ExecState, args: Ar
|
||||
|
||||
/// Returns the segment end of y.
|
||||
pub async fn segment_end_y(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
||||
let tag: TagIdentifier = args.get_unlabeled_kw_arg("tag")?;
|
||||
let tag: TagIdentifier = args.get_unlabeled_kw_arg_typed("tag", &RuntimeType::tag_identifier(), exec_state)?;
|
||||
let result = inner_segment_end_y(&tag, exec_state, args.clone())?;
|
||||
|
||||
Ok(args.make_user_val_from_f64_with_type(result))
|
||||
}
|
||||
|
||||
/// Compute the ending point of the provided line segment along the 'y' axis.
|
||||
///
|
||||
/// ```no_run
|
||||
/// exampleSketch = startSketchOn(XZ)
|
||||
/// |> startProfile(at = [0, 0])
|
||||
/// |> line(end = [20, 0])
|
||||
/// |> line(end = [0, 3], tag = $thing)
|
||||
/// |> line(end = [-10, 0])
|
||||
/// |> line(end = [0, segEndY(thing)])
|
||||
/// |> line(end = [-10, 0])
|
||||
/// |> close()
|
||||
///
|
||||
/// example = extrude(exampleSketch, length = 5)
|
||||
/// ```
|
||||
#[stdlib {
|
||||
name = "segEndY",
|
||||
unlabeled_first = true,
|
||||
args = {
|
||||
tag = { docs = "The line segment being queried by its tag"},
|
||||
},
|
||||
tags = ["sketch"]
|
||||
}]
|
||||
fn inner_segment_end_y(tag: &TagIdentifier, exec_state: &mut ExecState, args: Args) -> Result<TyF64, KclError> {
|
||||
let line = args.get_tag_engine_info(exec_state, tag)?;
|
||||
let path = line.path.clone().ok_or_else(|| {
|
||||
KclError::Type(KclErrorDetails::new(
|
||||
KclError::new_type(KclErrorDetails::new(
|
||||
format!("Expected a line segment with a path, found `{:?}`", line),
|
||||
vec![args.source_range],
|
||||
))
|
||||
@ -155,49 +78,16 @@ fn inner_segment_end_y(tag: &TagIdentifier, exec_state: &mut ExecState, args: Ar
|
||||
|
||||
/// Returns the point at the start of the given segment.
|
||||
pub async fn segment_start(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
||||
let tag: TagIdentifier = args.get_unlabeled_kw_arg("tag")?;
|
||||
let tag: TagIdentifier = args.get_unlabeled_kw_arg_typed("tag", &RuntimeType::tag_identifier(), exec_state)?;
|
||||
let pt = inner_segment_start(&tag, exec_state, args.clone())?;
|
||||
|
||||
args.make_kcl_val_from_point([pt[0].n, pt[1].n], pt[0].ty.clone())
|
||||
}
|
||||
|
||||
/// Compute the starting point of the provided line segment.
|
||||
///
|
||||
/// ```no_run
|
||||
/// w = 15
|
||||
/// cube = startSketchOn(XY)
|
||||
/// |> startProfile(at = [0, 0])
|
||||
/// |> line(end = [w, 0], tag = $line1)
|
||||
/// |> line(end = [0, w], tag = $line2)
|
||||
/// |> line(end = [-w, 0], tag = $line3)
|
||||
/// |> line(end = [0, -w], tag = $line4)
|
||||
/// |> close()
|
||||
/// |> extrude(length = 5)
|
||||
///
|
||||
/// fn cylinder(radius, tag) {
|
||||
/// return startSketchOn(XY)
|
||||
/// |> startProfile(at = [0, 0])
|
||||
/// |> circle( radius = radius, center = segStart(tag) )
|
||||
/// |> extrude(length = radius)
|
||||
/// }
|
||||
///
|
||||
/// cylinder(radius = 1, tag = line1)
|
||||
/// cylinder(radius = 2, tag = line2)
|
||||
/// cylinder(radius = 3, tag = line3)
|
||||
/// cylinder(radius = 4, tag = line4)
|
||||
/// ```
|
||||
#[stdlib {
|
||||
name = "segStart",
|
||||
unlabeled_first = true,
|
||||
args = {
|
||||
tag = { docs = "The line segment being queried by its tag"},
|
||||
},
|
||||
tags = ["sketch"]
|
||||
}]
|
||||
fn inner_segment_start(tag: &TagIdentifier, exec_state: &mut ExecState, args: Args) -> Result<[TyF64; 2], KclError> {
|
||||
let line = args.get_tag_engine_info(exec_state, tag)?;
|
||||
let path = line.path.clone().ok_or_else(|| {
|
||||
KclError::Type(KclErrorDetails::new(
|
||||
KclError::new_type(KclErrorDetails::new(
|
||||
format!("Expected a line segment with a path, found `{:?}`", line),
|
||||
vec![args.source_range],
|
||||
))
|
||||
@ -211,37 +101,16 @@ fn inner_segment_start(tag: &TagIdentifier, exec_state: &mut ExecState, args: Ar
|
||||
|
||||
/// Returns the segment start of x.
|
||||
pub async fn segment_start_x(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
||||
let tag: TagIdentifier = args.get_unlabeled_kw_arg("tag")?;
|
||||
let tag: TagIdentifier = args.get_unlabeled_kw_arg_typed("tag", &RuntimeType::tag_identifier(), exec_state)?;
|
||||
let result = inner_segment_start_x(&tag, exec_state, args.clone())?;
|
||||
|
||||
Ok(args.make_user_val_from_f64_with_type(result))
|
||||
}
|
||||
|
||||
/// Compute the starting point of the provided line segment along the 'x' axis.
|
||||
///
|
||||
/// ```no_run
|
||||
/// exampleSketch = startSketchOn(XZ)
|
||||
/// |> startProfile(at = [0, 0])
|
||||
/// |> line(end = [20, 0], tag = $thing)
|
||||
/// |> line(end = [0, 5])
|
||||
/// |> line(end = [20 - segStartX(thing), 0])
|
||||
/// |> line(end = [-20, 10])
|
||||
/// |> close()
|
||||
///
|
||||
/// example = extrude(exampleSketch, length = 5)
|
||||
/// ```
|
||||
#[stdlib {
|
||||
name = "segStartX",
|
||||
unlabeled_first = true,
|
||||
args = {
|
||||
tag = { docs = "The line segment being queried by its tag"},
|
||||
},
|
||||
tags = ["sketch"]
|
||||
}]
|
||||
fn inner_segment_start_x(tag: &TagIdentifier, exec_state: &mut ExecState, args: Args) -> Result<TyF64, KclError> {
|
||||
let line = args.get_tag_engine_info(exec_state, tag)?;
|
||||
let path = line.path.clone().ok_or_else(|| {
|
||||
KclError::Type(KclErrorDetails::new(
|
||||
KclError::new_type(KclErrorDetails::new(
|
||||
format!("Expected a line segment with a path, found `{:?}`", line),
|
||||
vec![args.source_range],
|
||||
))
|
||||
@ -252,38 +121,16 @@ fn inner_segment_start_x(tag: &TagIdentifier, exec_state: &mut ExecState, args:
|
||||
|
||||
/// Returns the segment start of y.
|
||||
pub async fn segment_start_y(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
||||
let tag: TagIdentifier = args.get_unlabeled_kw_arg("tag")?;
|
||||
let tag: TagIdentifier = args.get_unlabeled_kw_arg_typed("tag", &RuntimeType::tag_identifier(), exec_state)?;
|
||||
let result = inner_segment_start_y(&tag, exec_state, args.clone())?;
|
||||
|
||||
Ok(args.make_user_val_from_f64_with_type(result))
|
||||
}
|
||||
|
||||
/// Compute the starting point of the provided line segment along the 'y' axis.
|
||||
///
|
||||
/// ```no_run
|
||||
/// exampleSketch = startSketchOn(XZ)
|
||||
/// |> startProfile(at = [0, 0])
|
||||
/// |> line(end = [20, 0])
|
||||
/// |> line(end = [0, 3], tag = $thing)
|
||||
/// |> line(end = [-10, 0])
|
||||
/// |> line(end = [0, 20-segStartY(thing)])
|
||||
/// |> line(end = [-10, 0])
|
||||
/// |> close()
|
||||
///
|
||||
/// example = extrude(exampleSketch, length = 5)
|
||||
/// ```
|
||||
#[stdlib {
|
||||
name = "segStartY",
|
||||
unlabeled_first = true,
|
||||
args = {
|
||||
tag = { docs = "The line segment being queried by its tag"},
|
||||
},
|
||||
tags = ["sketch"]
|
||||
}]
|
||||
fn inner_segment_start_y(tag: &TagIdentifier, exec_state: &mut ExecState, args: Args) -> Result<TyF64, KclError> {
|
||||
let line = args.get_tag_engine_info(exec_state, tag)?;
|
||||
let path = line.path.clone().ok_or_else(|| {
|
||||
KclError::Type(KclErrorDetails::new(
|
||||
KclError::new_type(KclErrorDetails::new(
|
||||
format!("Expected a line segment with a path, found `{:?}`", line),
|
||||
vec![args.source_range],
|
||||
))
|
||||
@ -300,34 +147,12 @@ pub async fn last_segment_x(exec_state: &mut ExecState, args: Args) -> Result<Kc
|
||||
Ok(args.make_user_val_from_f64_with_type(result))
|
||||
}
|
||||
|
||||
/// Extract the 'x' axis value of the last line segment in the provided 2-d
|
||||
/// sketch.
|
||||
///
|
||||
/// ```no_run
|
||||
/// exampleSketch = startSketchOn(XZ)
|
||||
/// |> startProfile(at = [0, 0])
|
||||
/// |> line(end = [5, 0])
|
||||
/// |> line(end = [20, 5])
|
||||
/// |> line(end = [lastSegX(%), 0])
|
||||
/// |> line(end = [-15, 0])
|
||||
/// |> close()
|
||||
///
|
||||
/// example = extrude(exampleSketch, length = 5)
|
||||
/// ```
|
||||
#[stdlib {
|
||||
name = "lastSegX",
|
||||
unlabeled_first = true,
|
||||
args = {
|
||||
sketch = { docs = "The sketch whose line segment is being queried"},
|
||||
},
|
||||
tags = ["sketch"]
|
||||
}]
|
||||
fn inner_last_segment_x(sketch: Sketch, args: Args) -> Result<TyF64, KclError> {
|
||||
let last_line = sketch
|
||||
.paths
|
||||
.last()
|
||||
.ok_or_else(|| {
|
||||
KclError::Type(KclErrorDetails::new(
|
||||
KclError::new_type(KclErrorDetails::new(
|
||||
format!("Expected a Sketch with at least one segment, found `{:?}`", sketch),
|
||||
vec![args.source_range],
|
||||
))
|
||||
@ -346,34 +171,12 @@ pub async fn last_segment_y(exec_state: &mut ExecState, args: Args) -> Result<Kc
|
||||
Ok(args.make_user_val_from_f64_with_type(result))
|
||||
}
|
||||
|
||||
/// Extract the 'y' axis value of the last line segment in the provided 2-d
|
||||
/// sketch.
|
||||
///
|
||||
/// ```no_run
|
||||
/// exampleSketch = startSketchOn(XZ)
|
||||
/// |> startProfile(at = [0, 0])
|
||||
/// |> line(end = [5, 0])
|
||||
/// |> line(end = [20, 5])
|
||||
/// |> line(end = [0, lastSegY(%)])
|
||||
/// |> line(end = [-15, 0])
|
||||
/// |> close()
|
||||
///
|
||||
/// example = extrude(exampleSketch, length = 5)
|
||||
/// ```
|
||||
#[stdlib {
|
||||
name = "lastSegY",
|
||||
unlabeled_first = true,
|
||||
args = {
|
||||
sketch = { docs = "The sketch whose line segment is being queried"},
|
||||
},
|
||||
tags = ["sketch"]
|
||||
}]
|
||||
fn inner_last_segment_y(sketch: Sketch, args: Args) -> Result<TyF64, KclError> {
|
||||
let last_line = sketch
|
||||
.paths
|
||||
.last()
|
||||
.ok_or_else(|| {
|
||||
KclError::Type(KclErrorDetails::new(
|
||||
KclError::new_type(KclErrorDetails::new(
|
||||
format!("Expected a Sketch with at least one segment, found `{:?}`", sketch),
|
||||
vec![args.source_range],
|
||||
))
|
||||
@ -385,42 +188,15 @@ fn inner_last_segment_y(sketch: Sketch, args: Args) -> Result<TyF64, KclError> {
|
||||
|
||||
/// Returns the length of the segment.
|
||||
pub async fn segment_length(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
||||
let tag: TagIdentifier = args.get_unlabeled_kw_arg("tag")?;
|
||||
let tag: TagIdentifier = args.get_unlabeled_kw_arg_typed("tag", &RuntimeType::tag_identifier(), exec_state)?;
|
||||
let result = inner_segment_length(&tag, exec_state, args.clone())?;
|
||||
Ok(args.make_user_val_from_f64_with_type(result))
|
||||
}
|
||||
|
||||
/// Compute the length of the provided line segment.
|
||||
///
|
||||
/// ```no_run
|
||||
/// exampleSketch = startSketchOn(XZ)
|
||||
/// |> startProfile(at = [0, 0])
|
||||
/// |> angledLine(
|
||||
/// angle = 60,
|
||||
/// length = 10,
|
||||
/// tag = $thing,
|
||||
/// )
|
||||
/// |> tangentialArc(angle = -120, radius = 5)
|
||||
/// |> angledLine(
|
||||
/// angle = -60,
|
||||
/// length = segLen(thing),
|
||||
/// )
|
||||
/// |> close()
|
||||
///
|
||||
/// example = extrude(exampleSketch, length = 5)
|
||||
/// ```
|
||||
#[stdlib {
|
||||
name = "segLen",
|
||||
unlabeled_first = true,
|
||||
args = {
|
||||
tag = { docs = "The line segment being queried by its tag"},
|
||||
},
|
||||
tags = ["sketch"]
|
||||
}]
|
||||
fn inner_segment_length(tag: &TagIdentifier, exec_state: &mut ExecState, args: Args) -> Result<TyF64, KclError> {
|
||||
let line = args.get_tag_engine_info(exec_state, tag)?;
|
||||
let path = line.path.clone().ok_or_else(|| {
|
||||
KclError::Type(KclErrorDetails::new(
|
||||
KclError::new_type(KclErrorDetails::new(
|
||||
format!("Expected a line segment with a path, found `{:?}`", line),
|
||||
vec![args.source_range],
|
||||
))
|
||||
@ -431,39 +207,16 @@ fn inner_segment_length(tag: &TagIdentifier, exec_state: &mut ExecState, args: A
|
||||
|
||||
/// Returns the angle of the segment.
|
||||
pub async fn segment_angle(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
||||
let tag: TagIdentifier = args.get_unlabeled_kw_arg("tag")?;
|
||||
let tag: TagIdentifier = args.get_unlabeled_kw_arg_typed("tag", &RuntimeType::tag_identifier(), exec_state)?;
|
||||
|
||||
let result = inner_segment_angle(&tag, exec_state, args.clone())?;
|
||||
Ok(args.make_user_val_from_f64_with_type(TyF64::new(result, NumericType::degrees())))
|
||||
}
|
||||
|
||||
/// Compute the angle (in degrees) of the provided line segment.
|
||||
///
|
||||
/// ```no_run
|
||||
/// exampleSketch = startSketchOn(XZ)
|
||||
/// |> startProfile(at = [0, 0])
|
||||
/// |> line(end = [10, 0])
|
||||
/// |> line(end = [5, 10], tag = $seg01)
|
||||
/// |> line(end = [-10, 0])
|
||||
/// |> angledLine(angle = segAng(seg01), length = 10)
|
||||
/// |> line(end = [-10, 0])
|
||||
/// |> angledLine(angle = segAng(seg01), length = -15)
|
||||
/// |> close()
|
||||
///
|
||||
/// example = extrude(exampleSketch, length = 4)
|
||||
/// ```
|
||||
#[stdlib {
|
||||
name = "segAng",
|
||||
unlabeled_first = true,
|
||||
args = {
|
||||
tag = { docs = "The line segment being queried by its tag"},
|
||||
},
|
||||
tags = ["sketch"]
|
||||
}]
|
||||
fn inner_segment_angle(tag: &TagIdentifier, exec_state: &mut ExecState, args: Args) -> Result<f64, KclError> {
|
||||
let line = args.get_tag_engine_info(exec_state, tag)?;
|
||||
let path = line.path.clone().ok_or_else(|| {
|
||||
KclError::Type(KclErrorDetails::new(
|
||||
KclError::new_type(KclErrorDetails::new(
|
||||
format!("Expected a line segment with a path, found `{:?}`", line),
|
||||
vec![args.source_range],
|
||||
))
|
||||
@ -476,95 +229,16 @@ fn inner_segment_angle(tag: &TagIdentifier, exec_state: &mut ExecState, args: Ar
|
||||
|
||||
/// Returns the angle coming out of the end of the segment in degrees.
|
||||
pub async fn tangent_to_end(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
||||
let tag: TagIdentifier = args.get_unlabeled_kw_arg("tag")?;
|
||||
let tag: TagIdentifier = args.get_unlabeled_kw_arg_typed("tag", &RuntimeType::tag_identifier(), exec_state)?;
|
||||
|
||||
let result = inner_tangent_to_end(&tag, exec_state, args.clone()).await?;
|
||||
Ok(args.make_user_val_from_f64_with_type(TyF64::new(result, NumericType::degrees())))
|
||||
}
|
||||
|
||||
/// Returns the angle coming out of the end of the segment in degrees.
|
||||
///
|
||||
/// ```no_run
|
||||
/// // Horizontal pill.
|
||||
/// pillSketch = startSketchOn(XZ)
|
||||
/// |> startProfile(at = [0, 0])
|
||||
/// |> line(end = [20, 0])
|
||||
/// |> tangentialArc(end = [0, 10], tag = $arc1)
|
||||
/// |> angledLine(
|
||||
/// angle = tangentToEnd(arc1),
|
||||
/// length = 20,
|
||||
/// )
|
||||
/// |> tangentialArc(end = [0, -10])
|
||||
/// |> close()
|
||||
///
|
||||
/// pillExtrude = extrude(pillSketch, length = 10)
|
||||
/// ```
|
||||
///
|
||||
/// ```no_run
|
||||
/// // Vertical pill. Use absolute coordinate for arc.
|
||||
/// pillSketch = startSketchOn(XZ)
|
||||
/// |> startProfile(at = [0, 0])
|
||||
/// |> line(end = [0, 20])
|
||||
/// |> tangentialArc(endAbsolute = [10, 20], tag = $arc1)
|
||||
/// |> angledLine(
|
||||
/// angle = tangentToEnd(arc1),
|
||||
/// length = 20,
|
||||
/// )
|
||||
/// |> tangentialArc(end = [-10, 0])
|
||||
/// |> close()
|
||||
///
|
||||
/// pillExtrude = extrude(pillSketch, length = 10)
|
||||
/// ```
|
||||
///
|
||||
/// ```no_run
|
||||
/// rectangleSketch = startSketchOn(XZ)
|
||||
/// |> startProfile(at = [0, 0])
|
||||
/// |> line(end = [10, 0], tag = $seg1)
|
||||
/// |> angledLine(
|
||||
/// angle = tangentToEnd(seg1),
|
||||
/// length = 10,
|
||||
/// )
|
||||
/// |> line(end = [0, 10])
|
||||
/// |> line(end = [-20, 0])
|
||||
/// |> close()
|
||||
///
|
||||
/// rectangleExtrude = extrude(rectangleSketch, length = 10)
|
||||
/// ```
|
||||
///
|
||||
/// ```no_run
|
||||
/// bottom = startSketchOn(XY)
|
||||
/// |> startProfile(at = [0, 0])
|
||||
/// |> arc(
|
||||
/// endAbsolute = [10, 10],
|
||||
/// interiorAbsolute = [5, 1],
|
||||
/// tag = $arc1,
|
||||
/// )
|
||||
/// |> angledLine(angle = tangentToEnd(arc1), length = 20)
|
||||
/// |> close()
|
||||
/// ```
|
||||
///
|
||||
/// ```no_run
|
||||
/// circSketch = startSketchOn(XY)
|
||||
/// |> circle( center= [0, 0], radius= 3 , tag= $circ)
|
||||
///
|
||||
/// triangleSketch = startSketchOn(XY)
|
||||
/// |> startProfile(at = [-5, 0])
|
||||
/// |> angledLine(angle = tangentToEnd(circ), length = 10)
|
||||
/// |> line(end = [-15, 0])
|
||||
/// |> close()
|
||||
/// ```
|
||||
#[stdlib {
|
||||
name = "tangentToEnd",
|
||||
unlabeled_first = true,
|
||||
args = {
|
||||
tag = { docs = "The line segment being queried by its tag"},
|
||||
},
|
||||
tags = ["sketch"]
|
||||
}]
|
||||
async fn inner_tangent_to_end(tag: &TagIdentifier, exec_state: &mut ExecState, args: Args) -> Result<f64, KclError> {
|
||||
let line = args.get_tag_engine_info(exec_state, tag)?;
|
||||
let path = line.path.clone().ok_or_else(|| {
|
||||
KclError::Type(KclErrorDetails::new(
|
||||
KclError::new_type(KclErrorDetails::new(
|
||||
format!("Expected a line segment with a path, found `{:?}`", line),
|
||||
vec![args.source_range],
|
||||
))
|
||||
|
@ -1,7 +1,6 @@
|
||||
//! Standard library shapes.
|
||||
|
||||
use anyhow::Result;
|
||||
use kcl_derive_docs::stdlib;
|
||||
use kcmc::{
|
||||
each_cmd as mcmd,
|
||||
length_unit::LengthUnit,
|
||||
@ -29,6 +28,7 @@ use crate::{
|
||||
utils::{calculate_circle_center, distance},
|
||||
Args,
|
||||
},
|
||||
SourceRange,
|
||||
};
|
||||
|
||||
/// A sketch surface or a sketch.
|
||||
@ -42,7 +42,8 @@ pub enum SketchOrSurface {
|
||||
|
||||
/// Sketch a circle.
|
||||
pub async fn circle(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
||||
let sketch_or_surface = args.get_unlabeled_kw_arg("sketchOrSurface")?;
|
||||
let sketch_or_surface =
|
||||
args.get_unlabeled_kw_arg_typed("sketchOrSurface", &RuntimeType::sketch_or_surface(), exec_state)?;
|
||||
let center = args.get_kw_arg_typed("center", &RuntimeType::point2d(), exec_state)?;
|
||||
let radius: Option<TyF64> = args.get_kw_arg_opt_typed("radius", &RuntimeType::length(), exec_state)?;
|
||||
let diameter: Option<TyF64> = args.get_kw_arg_opt_typed("diameter", &RuntimeType::length(), exec_state)?;
|
||||
@ -70,25 +71,7 @@ async fn inner_circle(
|
||||
let (center_u, ty) = untype_point(center.clone());
|
||||
let units = ty.expect_length();
|
||||
|
||||
let radius = match (radius, diameter) {
|
||||
(Some(radius), None) => radius,
|
||||
(None, Some(mut diameter)) => {
|
||||
diameter.n /= 2.0;
|
||||
diameter
|
||||
}
|
||||
(None, None) => {
|
||||
return Err(KclError::Type(KclErrorDetails::new(
|
||||
"This function needs either `diameter` or `radius`".to_string(),
|
||||
vec![args.source_range],
|
||||
)))
|
||||
}
|
||||
(Some(_), Some(_)) => {
|
||||
return Err(KclError::Type(KclErrorDetails::new(
|
||||
"You cannot specify both `diameter` and `radius`, please remove one".to_string(),
|
||||
vec![args.source_range],
|
||||
)))
|
||||
}
|
||||
};
|
||||
let radius = get_radius(radius, diameter, args.source_range)?;
|
||||
let from = [center_u[0] + radius.to_length_units(units), center_u[1]];
|
||||
let from_t = [TyF64::new(from[0], ty.clone()), TyF64::new(from[1], ty)];
|
||||
|
||||
@ -146,38 +129,19 @@ async fn inner_circle(
|
||||
|
||||
/// Sketch a 3-point circle.
|
||||
pub async fn circle_three_point(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
||||
let sketch_surface_or_group = args.get_unlabeled_kw_arg("sketch_surface_or_group")?;
|
||||
let sketch_or_surface =
|
||||
args.get_unlabeled_kw_arg_typed("sketchOrSurface", &RuntimeType::sketch_or_surface(), exec_state)?;
|
||||
let p1 = args.get_kw_arg_typed("p1", &RuntimeType::point2d(), exec_state)?;
|
||||
let p2 = args.get_kw_arg_typed("p2", &RuntimeType::point2d(), exec_state)?;
|
||||
let p3 = args.get_kw_arg_typed("p3", &RuntimeType::point2d(), exec_state)?;
|
||||
let tag = args.get_kw_arg_opt("tag")?;
|
||||
|
||||
let sketch = inner_circle_three_point(sketch_surface_or_group, p1, p2, p3, tag, exec_state, args).await?;
|
||||
let sketch = inner_circle_three_point(sketch_or_surface, p1, p2, p3, tag, exec_state, args).await?;
|
||||
Ok(KclValue::Sketch {
|
||||
value: Box::new(sketch),
|
||||
})
|
||||
}
|
||||
|
||||
/// Construct a circle derived from 3 points.
|
||||
///
|
||||
/// ```no_run
|
||||
/// exampleSketch = startSketchOn(XY)
|
||||
/// |> circleThreePoint(p1 = [10,10], p2 = [20,8], p3 = [15,5])
|
||||
/// |> extrude(length = 5)
|
||||
/// ```
|
||||
#[stdlib {
|
||||
name = "circleThreePoint",
|
||||
unlabeled_first = true,
|
||||
args = {
|
||||
sketch_surface_or_group = {docs = "Plane or surface to sketch on."},
|
||||
p1 = {docs = "1st point to derive the circle."},
|
||||
p2 = {docs = "2nd point to derive the circle."},
|
||||
p3 = {docs = "3rd point to derive the circle."},
|
||||
tag = {docs = "Identifier for the circle to reference elsewhere."},
|
||||
},
|
||||
tags = ["sketch"]
|
||||
}]
|
||||
|
||||
// Similar to inner_circle, but needs to retain 3-point information in the
|
||||
// path so it can be used for other features, otherwise it's lost.
|
||||
async fn inner_circle_three_point(
|
||||
@ -274,14 +238,15 @@ pub enum PolygonType {
|
||||
|
||||
/// Create a regular polygon with the specified number of sides and radius.
|
||||
pub async fn polygon(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
||||
let sketch_surface_or_group = args.get_unlabeled_kw_arg("sketchOrSurface")?;
|
||||
let sketch_or_surface =
|
||||
args.get_unlabeled_kw_arg_typed("sketchOrSurface", &RuntimeType::sketch_or_surface(), exec_state)?;
|
||||
let radius: TyF64 = args.get_kw_arg_typed("radius", &RuntimeType::length(), exec_state)?;
|
||||
let num_sides: TyF64 = args.get_kw_arg_typed("numSides", &RuntimeType::count(), exec_state)?;
|
||||
let center = args.get_kw_arg_typed("center", &RuntimeType::point2d(), exec_state)?;
|
||||
let inscribed = args.get_kw_arg_opt_typed("inscribed", &RuntimeType::bool(), exec_state)?;
|
||||
|
||||
let sketch = inner_polygon(
|
||||
sketch_surface_or_group,
|
||||
sketch_or_surface,
|
||||
radius,
|
||||
num_sides.n as u64,
|
||||
center,
|
||||
@ -295,44 +260,6 @@ pub async fn polygon(exec_state: &mut ExecState, args: Args) -> Result<KclValue,
|
||||
})
|
||||
}
|
||||
|
||||
/// Create a regular polygon with the specified number of sides that is either inscribed or circumscribed around a circle of the specified radius.
|
||||
///
|
||||
/// ```no_run
|
||||
/// // Create a regular hexagon inscribed in a circle of radius 10
|
||||
/// hex = startSketchOn(XY)
|
||||
/// |> polygon(
|
||||
/// radius = 10,
|
||||
/// numSides = 6,
|
||||
/// center = [0, 0],
|
||||
/// inscribed = true,
|
||||
/// )
|
||||
///
|
||||
/// example = extrude(hex, length = 5)
|
||||
/// ```
|
||||
///
|
||||
/// ```no_run
|
||||
/// // Create a square circumscribed around a circle of radius 5
|
||||
/// square = startSketchOn(XY)
|
||||
/// |> polygon(
|
||||
/// radius = 5.0,
|
||||
/// numSides = 4,
|
||||
/// center = [10, 10],
|
||||
/// inscribed = false,
|
||||
/// )
|
||||
/// example = extrude(square, length = 5)
|
||||
/// ```
|
||||
#[stdlib {
|
||||
name = "polygon",
|
||||
unlabeled_first = true,
|
||||
args = {
|
||||
sketch_surface_or_group = { docs = "Plane or surface to sketch on" },
|
||||
radius = { docs = "The radius of the polygon", include_in_snippet = true },
|
||||
num_sides = { docs = "The number of sides in the polygon", include_in_snippet = true },
|
||||
center = { docs = "The center point of the polygon", snippet_value_array = ["0", "0"] },
|
||||
inscribed = { docs = "Whether the polygon is inscribed (true, the default) or circumscribed (false) about a circle with the specified radius" },
|
||||
},
|
||||
tags = ["sketch"]
|
||||
}]
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
async fn inner_polygon(
|
||||
sketch_surface_or_group: SketchOrSurface,
|
||||
@ -344,14 +271,14 @@ async fn inner_polygon(
|
||||
args: Args,
|
||||
) -> Result<Sketch, KclError> {
|
||||
if num_sides < 3 {
|
||||
return Err(KclError::Type(KclErrorDetails::new(
|
||||
return Err(KclError::new_type(KclErrorDetails::new(
|
||||
"Polygon must have at least 3 sides".to_string(),
|
||||
vec![args.source_range],
|
||||
)));
|
||||
}
|
||||
|
||||
if radius.n <= 0.0 {
|
||||
return Err(KclError::Type(KclErrorDetails::new(
|
||||
return Err(KclError::new_type(KclErrorDetails::new(
|
||||
"Radius must be greater than 0".to_string(),
|
||||
vec![args.source_range],
|
||||
)));
|
||||
@ -565,4 +492,22 @@ async fn inner_ellipse(
|
||||
.await?;
|
||||
|
||||
Ok(new_sketch)
|
||||
|
||||
pub(crate) fn get_radius(
|
||||
radius: Option<TyF64>,
|
||||
diameter: Option<TyF64>,
|
||||
source_range: SourceRange,
|
||||
) -> Result<TyF64, KclError> {
|
||||
match (radius, diameter) {
|
||||
(Some(radius), None) => Ok(radius),
|
||||
(None, Some(diameter)) => Ok(TyF64::new(diameter.n / 2.0, diameter.ty)),
|
||||
(None, None) => Err(KclError::new_type(KclErrorDetails::new(
|
||||
"This function needs either `diameter` or `radius`".to_string(),
|
||||
vec![source_range],
|
||||
))),
|
||||
(Some(_), Some(_)) => Err(KclError::new_type(KclErrorDetails::new(
|
||||
"You cannot specify both `diameter` and `radius`, please remove one".to_string(),
|
||||
vec![source_range],
|
||||
))),
|
||||
}
|
||||
}
|
||||
|
@ -36,14 +36,14 @@ async fn inner_shell(
|
||||
args: Args,
|
||||
) -> Result<Vec<Solid>, KclError> {
|
||||
if faces.is_empty() {
|
||||
return Err(KclError::Type(KclErrorDetails::new(
|
||||
return Err(KclError::new_type(KclErrorDetails::new(
|
||||
"You must shell at least one face".to_owned(),
|
||||
vec![args.source_range],
|
||||
)));
|
||||
}
|
||||
|
||||
if solids.is_empty() {
|
||||
return Err(KclError::Type(KclErrorDetails::new(
|
||||
return Err(KclError::new_type(KclErrorDetails::new(
|
||||
"You must shell at least one solid".to_owned(),
|
||||
vec![args.source_range],
|
||||
)));
|
||||
@ -63,7 +63,7 @@ async fn inner_shell(
|
||||
}
|
||||
|
||||
if face_ids.is_empty() {
|
||||
return Err(KclError::Type(KclErrorDetails::new(
|
||||
return Err(KclError::new_type(KclErrorDetails::new(
|
||||
"Expected at least one valid face".to_owned(),
|
||||
vec![args.source_range],
|
||||
)));
|
||||
@ -72,7 +72,7 @@ async fn inner_shell(
|
||||
// Make sure all the solids have the same id, as we are going to shell them all at
|
||||
// once.
|
||||
if !solids.iter().all(|eg| eg.id == solids[0].id) {
|
||||
return Err(KclError::Type(KclErrorDetails::new(
|
||||
return Err(KclError::new_type(KclErrorDetails::new(
|
||||
"All solids stem from the same root object, like multiple sketch on face extrusions, etc.".to_owned(),
|
||||
vec![args.source_range],
|
||||
)));
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,7 +1,6 @@
|
||||
//! Standard library sweep.
|
||||
|
||||
use anyhow::Result;
|
||||
use kcl_derive_docs::stdlib;
|
||||
use kcmc::{each_cmd as mcmd, length_unit::LengthUnit, ModelingCmd};
|
||||
use kittycad_modeling_cmds::{self as kcmc, shared::RelativeTo};
|
||||
use schemars::JsonSchema;
|
||||
@ -22,6 +21,7 @@ use crate::{
|
||||
#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
||||
#[ts(export)]
|
||||
#[serde(untagged)]
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
pub enum SweepPath {
|
||||
Sketch(Sketch),
|
||||
Helix(Box<Helix>),
|
||||
@ -35,7 +35,7 @@ pub async fn sweep(exec_state: &mut ExecState, args: Args) -> Result<KclValue, K
|
||||
&RuntimeType::Union(vec![RuntimeType::sketch(), RuntimeType::helix()]),
|
||||
exec_state,
|
||||
)?;
|
||||
let sectional = args.get_kw_arg_opt("sectional")?;
|
||||
let sectional = args.get_kw_arg_opt_typed("sectional", &RuntimeType::bool(), exec_state)?;
|
||||
let tolerance: Option<TyF64> = args.get_kw_arg_opt_typed("tolerance", &RuntimeType::length(), exec_state)?;
|
||||
let relative_to: Option<String> = args.get_kw_arg_opt_typed("relativeTo", &RuntimeType::string(), exec_state)?;
|
||||
let tag_start = args.get_kw_arg_opt("tagStart")?;
|
||||
@ -56,122 +56,6 @@ pub async fn sweep(exec_state: &mut ExecState, args: Args) -> Result<KclValue, K
|
||||
Ok(value.into())
|
||||
}
|
||||
|
||||
/// Extrude a sketch along a path.
|
||||
///
|
||||
/// This, like extrude, is able to create a 3-dimensional solid from a
|
||||
/// 2-dimensional sketch. However, unlike extrude, this creates a solid
|
||||
/// by using the extent of the sketch as its path. This is useful for
|
||||
/// creating more complex shapes that can't be created with a simple
|
||||
/// extrusion.
|
||||
///
|
||||
/// You can provide more than one sketch to sweep, and they will all be
|
||||
/// swept along the same path.
|
||||
///
|
||||
/// ```no_run
|
||||
/// // Create a pipe using a sweep.
|
||||
///
|
||||
/// // Create a path for the sweep.
|
||||
/// sweepPath = startSketchOn(XZ)
|
||||
/// |> startProfile(at = [0.05, 0.05])
|
||||
/// |> line(end = [0, 7])
|
||||
/// |> tangentialArc(angle = 90, radius = 5)
|
||||
/// |> line(end = [-3, 0])
|
||||
/// |> tangentialArc(angle = -90, radius = 5)
|
||||
/// |> line(end = [0, 7])
|
||||
///
|
||||
/// // Create a hole for the pipe.
|
||||
/// pipeHole = startSketchOn(XY)
|
||||
/// |> circle(
|
||||
/// center = [0, 0],
|
||||
/// radius = 1.5,
|
||||
/// )
|
||||
///
|
||||
/// sweepSketch = startSketchOn(XY)
|
||||
/// |> circle(
|
||||
/// center = [0, 0],
|
||||
/// radius = 2,
|
||||
/// )
|
||||
/// |> subtract2d(tool = pipeHole)
|
||||
/// |> sweep(path = sweepPath)
|
||||
/// ```
|
||||
///
|
||||
/// ```no_run
|
||||
/// // Create a spring by sweeping around a helix path.
|
||||
///
|
||||
/// // Create a helix around the Z axis.
|
||||
/// helixPath = helix(
|
||||
/// angleStart = 0,
|
||||
/// ccw = true,
|
||||
/// revolutions = 4,
|
||||
/// length = 10,
|
||||
/// radius = 5,
|
||||
/// axis = Z,
|
||||
/// )
|
||||
///
|
||||
///
|
||||
/// // Create a spring by sweeping around the helix path.
|
||||
/// springSketch = startSketchOn(XZ)
|
||||
/// |> circle( center = [5, 0], radius = 1)
|
||||
/// |> sweep(path = helixPath)
|
||||
/// ```
|
||||
///
|
||||
/// ```no_run
|
||||
/// // Sweep two sketches along the same path.
|
||||
///
|
||||
/// sketch001 = startSketchOn(XY)
|
||||
/// rectangleSketch = startProfile(sketch001, at = [-200, 23.86])
|
||||
/// |> angledLine(angle = 0, length = 73.47, tag = $rectangleSegmentA001)
|
||||
/// |> angledLine(
|
||||
/// angle = segAng(rectangleSegmentA001) - 90,
|
||||
/// length = 50.61,
|
||||
/// )
|
||||
/// |> angledLine(
|
||||
/// angle = segAng(rectangleSegmentA001),
|
||||
/// length = -segLen(rectangleSegmentA001),
|
||||
/// )
|
||||
/// |> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||
/// |> close()
|
||||
///
|
||||
/// circleSketch = circle(sketch001, center = [200, -30.29], radius = 32.63)
|
||||
///
|
||||
/// sketch002 = startSketchOn(YZ)
|
||||
/// sweepPath = startProfile(sketch002, at = [0, 0])
|
||||
/// |> yLine(length = 231.81)
|
||||
/// |> tangentialArc(radius = 80, angle = -90)
|
||||
/// |> xLine(length = 384.93)
|
||||
///
|
||||
/// sweep([rectangleSketch, circleSketch], path = sweepPath)
|
||||
/// ```
|
||||
/// ```
|
||||
/// // Sectionally sweep one sketch along the path
|
||||
///
|
||||
/// sketch001 = startSketchOn(XY)
|
||||
/// circleSketch = circle(sketch001, center = [200, -30.29], radius = 32.63)
|
||||
///
|
||||
/// sketch002 = startSketchOn(YZ)
|
||||
/// sweepPath = startProfile(sketch002, at = [0, 0])
|
||||
/// |> yLine(length = 231.81)
|
||||
/// |> tangentialArc(radius = 80, angle = -90)
|
||||
/// |> xLine(length = 384.93)
|
||||
///
|
||||
/// sweep(circleSketch, path = sweepPath, sectional = true)
|
||||
/// ```
|
||||
|
||||
#[stdlib {
|
||||
name = "sweep",
|
||||
feature_tree_operation = true,
|
||||
unlabeled_first = true,
|
||||
args = {
|
||||
sketches = { docs = "The sketch or set of sketches that should be swept in space" },
|
||||
path = { docs = "The path to sweep the sketch along" },
|
||||
sectional = { docs = "If true, the sweep will be broken up into sub-sweeps (extrusions, revolves, sweeps) based on the trajectory path components." },
|
||||
tolerance = { docs = "Tolerance for this operation" },
|
||||
relative_to = { docs = "What is the sweep relative to? Can be either 'sketchPlane' or 'trajectoryCurve'. Defaults to trajectoryCurve."},
|
||||
tag_start = { docs = "A named tag for the face at the start of the sweep, i.e. the original sketch" },
|
||||
tag_end = { docs = "A named tag for the face at the end of the sweep" },
|
||||
},
|
||||
tags = ["sketch"]
|
||||
}]
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
async fn inner_sweep(
|
||||
sketches: Vec<Sketch>,
|
||||
@ -192,7 +76,7 @@ async fn inner_sweep(
|
||||
Some("sketchPlane") => RelativeTo::SketchPlane,
|
||||
Some("trajectoryCurve") | None => RelativeTo::TrajectoryCurve,
|
||||
Some(_) => {
|
||||
return Err(KclError::Syntax(crate::errors::KclErrorDetails::new(
|
||||
return Err(KclError::new_syntax(crate::errors::KclErrorDetails::new(
|
||||
"If you provide relativeTo, it must either be 'sketchPlane' or 'trajectoryCurve'".to_owned(),
|
||||
vec![args.source_range],
|
||||
)))
|
||||
|
@ -1,7 +1,6 @@
|
||||
//! Standard library transforms.
|
||||
|
||||
use anyhow::Result;
|
||||
use kcl_derive_docs::stdlib;
|
||||
use kcmc::{
|
||||
each_cmd as mcmd,
|
||||
length_unit::LengthUnit,
|
||||
@ -34,11 +33,11 @@ pub async fn scale(exec_state: &mut ExecState, args: Args) -> Result<KclValue, K
|
||||
let scale_x: Option<TyF64> = args.get_kw_arg_opt_typed("x", &RuntimeType::count(), exec_state)?;
|
||||
let scale_y: Option<TyF64> = args.get_kw_arg_opt_typed("y", &RuntimeType::count(), exec_state)?;
|
||||
let scale_z: Option<TyF64> = args.get_kw_arg_opt_typed("z", &RuntimeType::count(), exec_state)?;
|
||||
let global = args.get_kw_arg_opt("global")?;
|
||||
let global = args.get_kw_arg_opt_typed("global", &RuntimeType::bool(), exec_state)?;
|
||||
|
||||
// Ensure at least one scale value is provided.
|
||||
if scale_x.is_none() && scale_y.is_none() && scale_z.is_none() {
|
||||
return Err(KclError::Semantic(KclErrorDetails::new(
|
||||
return Err(KclError::new_semantic(KclErrorDetails::new(
|
||||
"Expected `x`, `y`, or `z` to be provided.".to_string(),
|
||||
vec![args.source_range],
|
||||
)));
|
||||
@ -57,106 +56,6 @@ pub async fn scale(exec_state: &mut ExecState, args: Args) -> Result<KclValue, K
|
||||
Ok(objects.into())
|
||||
}
|
||||
|
||||
/// Scale a solid or a sketch.
|
||||
///
|
||||
/// This is really useful for resizing parts. You can create a part and then scale it to the
|
||||
/// correct size.
|
||||
///
|
||||
/// For sketches, you can use this to scale a sketch and then loft it with another sketch.
|
||||
///
|
||||
/// By default the transform is applied in local sketch axis, therefore the origin will not move.
|
||||
///
|
||||
/// If you want to apply the transform in global space, set `global` to `true`. The origin of the
|
||||
/// model will move. If the model is not centered on origin and you scale globally it will
|
||||
/// look like the model moves and gets bigger at the same time. Say you have a square
|
||||
/// `(1,1) - (1,2) - (2,2) - (2,1)` and you scale by 2 globally it will become
|
||||
/// `(2,2) - (2,4)`...etc so the origin has moved from `(1.5, 1.5)` to `(2,2)`.
|
||||
///
|
||||
/// ```no_run
|
||||
/// // Scale a pipe.
|
||||
///
|
||||
/// // Create a path for the sweep.
|
||||
/// sweepPath = startSketchOn(XZ)
|
||||
/// |> startProfile(at = [0.05, 0.05])
|
||||
/// |> line(end = [0, 7])
|
||||
/// |> tangentialArc(angle = 90, radius = 5)
|
||||
/// |> line(end = [-3, 0])
|
||||
/// |> tangentialArc(angle = -90, radius = 5)
|
||||
/// |> line(end = [0, 7])
|
||||
///
|
||||
/// // Create a hole for the pipe.
|
||||
/// pipeHole = startSketchOn(XY)
|
||||
/// |> circle(
|
||||
/// center = [0, 0],
|
||||
/// radius = 1.5,
|
||||
/// )
|
||||
///
|
||||
/// sweepSketch = startSketchOn(XY)
|
||||
/// |> circle(
|
||||
/// center = [0, 0],
|
||||
/// radius = 2,
|
||||
/// )
|
||||
/// |> subtract2d(tool = pipeHole)
|
||||
/// |> sweep(path = sweepPath)
|
||||
/// |> scale(
|
||||
/// z = 2.5,
|
||||
/// )
|
||||
/// ```
|
||||
///
|
||||
/// ```no_run
|
||||
/// // Scale an imported model.
|
||||
///
|
||||
/// import "tests/inputs/cube.sldprt" as cube
|
||||
///
|
||||
/// cube
|
||||
/// |> scale(
|
||||
/// y = 2.5,
|
||||
/// )
|
||||
/// ```
|
||||
///
|
||||
/// ```
|
||||
/// // Sweep two sketches along the same path.
|
||||
///
|
||||
/// sketch001 = startSketchOn(XY)
|
||||
/// rectangleSketch = startProfile(sketch001, at = [-200, 23.86])
|
||||
/// |> angledLine(angle = 0, length = 73.47, tag = $rectangleSegmentA001)
|
||||
/// |> angledLine(
|
||||
/// angle = segAng(rectangleSegmentA001) - 90,
|
||||
/// length = 50.61,
|
||||
/// )
|
||||
/// |> angledLine(
|
||||
/// angle = segAng(rectangleSegmentA001),
|
||||
/// length = -segLen(rectangleSegmentA001),
|
||||
/// )
|
||||
/// |> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||
/// |> close()
|
||||
///
|
||||
/// circleSketch = circle(sketch001, center = [200, -30.29], radius = 32.63)
|
||||
///
|
||||
/// sketch002 = startSketchOn(YZ)
|
||||
/// sweepPath = startProfile(sketch002, at = [0, 0])
|
||||
/// |> yLine(length = 231.81)
|
||||
/// |> tangentialArc(radius = 80, angle = -90)
|
||||
/// |> xLine(length = 384.93)
|
||||
///
|
||||
/// parts = sweep([rectangleSketch, circleSketch], path = sweepPath)
|
||||
///
|
||||
/// // Scale the sweep.
|
||||
/// scale(parts, z = 0.5)
|
||||
/// ```
|
||||
#[stdlib {
|
||||
name = "scale",
|
||||
feature_tree_operation = false,
|
||||
unlabeled_first = true,
|
||||
args = {
|
||||
objects = {docs = "The solid, sketch, or set of solids or sketches to scale."},
|
||||
x = {docs = "The scale factor for the x axis. Default is 1 if not provided.", include_in_snippet = true},
|
||||
y = {docs = "The scale factor for the y axis. Default is 1 if not provided.", include_in_snippet = true},
|
||||
z = {docs = "The scale factor for the z axis. Default is 1 if not provided.", include_in_snippet = true},
|
||||
global = {docs = "If true, the transform is applied in global space. The origin of the model will move. By default, the transform is applied in local sketch axis, therefore the origin will not move."}
|
||||
},
|
||||
tags = ["transform"]
|
||||
}]
|
||||
async fn inner_scale(
|
||||
objects: SolidOrSketchOrImportedGeometry,
|
||||
x: Option<f64>,
|
||||
@ -216,11 +115,11 @@ pub async fn translate(exec_state: &mut ExecState, args: Args) -> Result<KclValu
|
||||
let translate_x: Option<TyF64> = args.get_kw_arg_opt_typed("x", &RuntimeType::length(), exec_state)?;
|
||||
let translate_y: Option<TyF64> = args.get_kw_arg_opt_typed("y", &RuntimeType::length(), exec_state)?;
|
||||
let translate_z: Option<TyF64> = args.get_kw_arg_opt_typed("z", &RuntimeType::length(), exec_state)?;
|
||||
let global = args.get_kw_arg_opt("global")?;
|
||||
let global = args.get_kw_arg_opt_typed("global", &RuntimeType::bool(), exec_state)?;
|
||||
|
||||
// Ensure at least one translation value is provided.
|
||||
if translate_x.is_none() && translate_y.is_none() && translate_z.is_none() {
|
||||
return Err(KclError::Semantic(KclErrorDetails::new(
|
||||
return Err(KclError::new_semantic(KclErrorDetails::new(
|
||||
"Expected `x`, `y`, or `z` to be provided.".to_string(),
|
||||
vec![args.source_range],
|
||||
)));
|
||||
@ -230,163 +129,6 @@ pub async fn translate(exec_state: &mut ExecState, args: Args) -> Result<KclValu
|
||||
Ok(objects.into())
|
||||
}
|
||||
|
||||
/// Move a solid or a sketch.
|
||||
///
|
||||
/// This is really useful for assembling parts together. You can create a part
|
||||
/// and then move it to the correct location.
|
||||
///
|
||||
/// Translate is really useful for sketches if you want to move a sketch
|
||||
/// and then rotate it using the `rotate` function to create a loft.
|
||||
///
|
||||
/// ```no_run
|
||||
/// // Move a pipe.
|
||||
///
|
||||
/// // Create a path for the sweep.
|
||||
/// sweepPath = startSketchOn(XZ)
|
||||
/// |> startProfile(at = [0.05, 0.05])
|
||||
/// |> line(end = [0, 7])
|
||||
/// |> tangentialArc(angle = 90, radius = 5)
|
||||
/// |> line(end = [-3, 0])
|
||||
/// |> tangentialArc(angle = -90, radius = 5)
|
||||
/// |> line(end = [0, 7])
|
||||
///
|
||||
/// // Create a hole for the pipe.
|
||||
/// pipeHole = startSketchOn(XY)
|
||||
/// |> circle(
|
||||
/// center = [0, 0],
|
||||
/// radius = 1.5,
|
||||
/// )
|
||||
///
|
||||
/// sweepSketch = startSketchOn(XY)
|
||||
/// |> circle(
|
||||
/// center = [0, 0],
|
||||
/// radius = 2,
|
||||
/// )
|
||||
/// |> subtract2d(tool = pipeHole)
|
||||
/// |> sweep(path = sweepPath)
|
||||
/// |> translate(
|
||||
/// x = 1.0,
|
||||
/// y = 1.0,
|
||||
/// z = 2.5,
|
||||
/// )
|
||||
/// ```
|
||||
///
|
||||
/// ```no_run
|
||||
/// // Move an imported model.
|
||||
///
|
||||
/// import "tests/inputs/cube.sldprt" as cube
|
||||
///
|
||||
/// // Circle so you actually see the move.
|
||||
/// startSketchOn(XY)
|
||||
/// |> circle(
|
||||
/// center = [-10, -10],
|
||||
/// radius = 10,
|
||||
/// )
|
||||
/// |> extrude(
|
||||
/// length = 10,
|
||||
/// )
|
||||
///
|
||||
/// cube
|
||||
/// |> translate(
|
||||
/// x = 10.0,
|
||||
/// y = 10.0,
|
||||
/// z = 2.5,
|
||||
/// )
|
||||
/// ```
|
||||
///
|
||||
/// ```
|
||||
/// // Sweep two sketches along the same path.
|
||||
///
|
||||
/// sketch001 = startSketchOn(XY)
|
||||
/// rectangleSketch = startProfile(sketch001, at = [-200, 23.86])
|
||||
/// |> angledLine(angle = 0, length = 73.47, tag = $rectangleSegmentA001)
|
||||
/// |> angledLine(
|
||||
/// angle = segAng(rectangleSegmentA001) - 90,
|
||||
/// length = 50.61,
|
||||
/// )
|
||||
/// |> angledLine(
|
||||
/// angle = segAng(rectangleSegmentA001),
|
||||
/// length = -segLen(rectangleSegmentA001),
|
||||
/// )
|
||||
/// |> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||
/// |> close()
|
||||
///
|
||||
/// circleSketch = circle(sketch001, center = [200, -30.29], radius = 32.63)
|
||||
///
|
||||
/// sketch002 = startSketchOn(YZ)
|
||||
/// sweepPath = startProfile(sketch002, at = [0, 0])
|
||||
/// |> yLine(length = 231.81)
|
||||
/// |> tangentialArc(radius = 80, angle = -90)
|
||||
/// |> xLine(length = 384.93)
|
||||
///
|
||||
/// parts = sweep([rectangleSketch, circleSketch], path = sweepPath)
|
||||
///
|
||||
/// // Move the sweeps.
|
||||
/// translate(parts, x = 1.0, y = 1.0, z = 2.5)
|
||||
/// ```
|
||||
///
|
||||
/// ```no_run
|
||||
/// // Move a sketch.
|
||||
///
|
||||
/// fn square(@length){
|
||||
/// l = length / 2
|
||||
/// p0 = [-l, -l]
|
||||
/// p1 = [-l, l]
|
||||
/// p2 = [l, l]
|
||||
/// p3 = [l, -l]
|
||||
///
|
||||
/// return startSketchOn(XY)
|
||||
/// |> startProfile(at = p0)
|
||||
/// |> line(endAbsolute = p1)
|
||||
/// |> line(endAbsolute = p2)
|
||||
/// |> line(endAbsolute = p3)
|
||||
/// |> close()
|
||||
/// }
|
||||
///
|
||||
/// square(10)
|
||||
/// |> translate(
|
||||
/// x = 5,
|
||||
/// y = 5,
|
||||
/// )
|
||||
/// |> extrude(
|
||||
/// length = 10,
|
||||
/// )
|
||||
/// ```
|
||||
///
|
||||
/// ```no_run
|
||||
/// // Translate and rotate a sketch to create a loft.
|
||||
/// sketch001 = startSketchOn(XY)
|
||||
///
|
||||
/// fn square() {
|
||||
/// return startProfile(sketch001, at = [-10, 10])
|
||||
/// |> xLine(length = 20)
|
||||
/// |> yLine(length = -20)
|
||||
/// |> xLine(length = -20)
|
||||
/// |> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||
/// |> close()
|
||||
/// }
|
||||
///
|
||||
/// profile001 = square()
|
||||
///
|
||||
/// profile002 = square()
|
||||
/// |> translate(z = 20)
|
||||
/// |> rotate(axis = [0, 0, 1.0], angle = 45)
|
||||
///
|
||||
/// loft([profile001, profile002])
|
||||
/// ```
|
||||
#[stdlib {
|
||||
name = "translate",
|
||||
feature_tree_operation = false,
|
||||
unlabeled_first = true,
|
||||
args = {
|
||||
objects = {docs = "The solid, sketch, or set of solids or sketches to move."},
|
||||
x = {docs = "The amount to move the solid or sketch along the x axis. Defaults to 0 if not provided.", include_in_snippet = true},
|
||||
y = {docs = "The amount to move the solid or sketch along the y axis. Defaults to 0 if not provided.", include_in_snippet = true},
|
||||
z = {docs = "The amount to move the solid or sketch along the z axis. Defaults to 0 if not provided.", include_in_snippet = true},
|
||||
global = {docs = "If true, the transform is applied in global space. The origin of the model will move. By default, the transform is applied in local sketch axis, therefore the origin will not move."}
|
||||
},
|
||||
tags = ["transform"]
|
||||
}]
|
||||
async fn inner_translate(
|
||||
objects: SolidOrSketchOrImportedGeometry,
|
||||
x: Option<TyF64>,
|
||||
@ -456,11 +198,11 @@ pub async fn rotate(exec_state: &mut ExecState, args: Args) -> Result<KclValue,
|
||||
)?;
|
||||
let axis = axis.map(|a| a.to_point3d());
|
||||
let angle: Option<TyF64> = args.get_kw_arg_opt_typed("angle", &RuntimeType::degrees(), exec_state)?;
|
||||
let global = args.get_kw_arg_opt("global")?;
|
||||
let global = args.get_kw_arg_opt_typed("global", &RuntimeType::bool(), exec_state)?;
|
||||
|
||||
// Check if no rotation values are provided.
|
||||
if roll.is_none() && pitch.is_none() && yaw.is_none() && axis.is_none() && angle.is_none() {
|
||||
return Err(KclError::Semantic(KclErrorDetails::new(
|
||||
return Err(KclError::new_semantic(KclErrorDetails::new(
|
||||
"Expected `roll`, `pitch`, and `yaw` or `axis` and `angle` to be provided.".to_string(),
|
||||
vec![args.source_range],
|
||||
)));
|
||||
@ -470,7 +212,7 @@ pub async fn rotate(exec_state: &mut ExecState, args: Args) -> Result<KclValue,
|
||||
if roll.is_some() || pitch.is_some() || yaw.is_some() {
|
||||
// Ensure they didn't also provide an axis or angle.
|
||||
if axis.is_some() || angle.is_some() {
|
||||
return Err(KclError::Semantic(KclErrorDetails::new(
|
||||
return Err(KclError::new_semantic(KclErrorDetails::new(
|
||||
"Expected `axis` and `angle` to not be provided when `roll`, `pitch`, and `yaw` are provided."
|
||||
.to_owned(),
|
||||
vec![args.source_range],
|
||||
@ -481,13 +223,13 @@ pub async fn rotate(exec_state: &mut ExecState, args: Args) -> Result<KclValue,
|
||||
// If they give us an axis or angle, they must give us both.
|
||||
if axis.is_some() || angle.is_some() {
|
||||
if axis.is_none() {
|
||||
return Err(KclError::Semantic(KclErrorDetails::new(
|
||||
return Err(KclError::new_semantic(KclErrorDetails::new(
|
||||
"Expected `axis` to be provided when `angle` is provided.".to_string(),
|
||||
vec![args.source_range],
|
||||
)));
|
||||
}
|
||||
if angle.is_none() {
|
||||
return Err(KclError::Semantic(KclErrorDetails::new(
|
||||
return Err(KclError::new_semantic(KclErrorDetails::new(
|
||||
"Expected `angle` to be provided when `axis` is provided.".to_string(),
|
||||
vec![args.source_range],
|
||||
)));
|
||||
@ -495,7 +237,7 @@ pub async fn rotate(exec_state: &mut ExecState, args: Args) -> Result<KclValue,
|
||||
|
||||
// Ensure they didn't also provide a roll, pitch, or yaw.
|
||||
if roll.is_some() || pitch.is_some() || yaw.is_some() {
|
||||
return Err(KclError::Semantic(KclErrorDetails::new(
|
||||
return Err(KclError::new_semantic(KclErrorDetails::new(
|
||||
"Expected `roll`, `pitch`, and `yaw` to not be provided when `axis` and `angle` are provided."
|
||||
.to_owned(),
|
||||
vec![args.source_range],
|
||||
@ -506,7 +248,7 @@ pub async fn rotate(exec_state: &mut ExecState, args: Args) -> Result<KclValue,
|
||||
// Validate the roll, pitch, and yaw values.
|
||||
if let Some(roll) = &roll {
|
||||
if !(-360.0..=360.0).contains(&roll.n) {
|
||||
return Err(KclError::Semantic(KclErrorDetails::new(
|
||||
return Err(KclError::new_semantic(KclErrorDetails::new(
|
||||
format!("Expected roll to be between -360 and 360, found `{}`", roll.n),
|
||||
vec![args.source_range],
|
||||
)));
|
||||
@ -514,7 +256,7 @@ pub async fn rotate(exec_state: &mut ExecState, args: Args) -> Result<KclValue,
|
||||
}
|
||||
if let Some(pitch) = &pitch {
|
||||
if !(-360.0..=360.0).contains(&pitch.n) {
|
||||
return Err(KclError::Semantic(KclErrorDetails::new(
|
||||
return Err(KclError::new_semantic(KclErrorDetails::new(
|
||||
format!("Expected pitch to be between -360 and 360, found `{}`", pitch.n),
|
||||
vec![args.source_range],
|
||||
)));
|
||||
@ -522,7 +264,7 @@ pub async fn rotate(exec_state: &mut ExecState, args: Args) -> Result<KclValue,
|
||||
}
|
||||
if let Some(yaw) = &yaw {
|
||||
if !(-360.0..=360.0).contains(&yaw.n) {
|
||||
return Err(KclError::Semantic(KclErrorDetails::new(
|
||||
return Err(KclError::new_semantic(KclErrorDetails::new(
|
||||
format!("Expected yaw to be between -360 and 360, found `{}`", yaw.n),
|
||||
vec![args.source_range],
|
||||
)));
|
||||
@ -532,7 +274,7 @@ pub async fn rotate(exec_state: &mut ExecState, args: Args) -> Result<KclValue,
|
||||
// Validate the axis and angle values.
|
||||
if let Some(angle) = &angle {
|
||||
if !(-360.0..=360.0).contains(&angle.n) {
|
||||
return Err(KclError::Semantic(KclErrorDetails::new(
|
||||
return Err(KclError::new_semantic(KclErrorDetails::new(
|
||||
format!("Expected angle to be between -360 and 360, found `{}`", angle.n),
|
||||
vec![args.source_range],
|
||||
)));
|
||||
@ -556,242 +298,6 @@ pub async fn rotate(exec_state: &mut ExecState, args: Args) -> Result<KclValue,
|
||||
Ok(objects.into())
|
||||
}
|
||||
|
||||
/// Rotate a solid or a sketch.
|
||||
///
|
||||
/// This is really useful for assembling parts together. You can create a part
|
||||
/// and then rotate it to the correct orientation.
|
||||
///
|
||||
/// For sketches, you can use this to rotate a sketch and then loft it with another sketch.
|
||||
///
|
||||
/// ### Using Roll, Pitch, and Yaw
|
||||
///
|
||||
/// When rotating a part in 3D space, "roll," "pitch," and "yaw" refer to the
|
||||
/// three rotational axes used to describe its orientation: roll is rotation
|
||||
/// around the longitudinal axis (front-to-back), pitch is rotation around the
|
||||
/// lateral axis (wing-to-wing), and yaw is rotation around the vertical axis
|
||||
/// (up-down); essentially, it's like tilting the part on its side (roll),
|
||||
/// tipping the nose up or down (pitch), and turning it left or right (yaw).
|
||||
///
|
||||
/// So, in the context of a 3D model:
|
||||
///
|
||||
/// - **Roll**: Imagine spinning a pencil on its tip - that's a roll movement.
|
||||
///
|
||||
/// - **Pitch**: Think of a seesaw motion, where the object tilts up or down along its side axis.
|
||||
///
|
||||
/// - **Yaw**: Like turning your head left or right, this is a rotation around the vertical axis
|
||||
///
|
||||
/// ### Using an Axis and Angle
|
||||
///
|
||||
/// When rotating a part around an axis, you specify the axis of rotation and the angle of
|
||||
/// rotation.
|
||||
///
|
||||
/// ```no_run
|
||||
/// // Rotate a pipe with roll, pitch, and yaw.
|
||||
///
|
||||
/// // Create a path for the sweep.
|
||||
/// sweepPath = startSketchOn(XZ)
|
||||
/// |> startProfile(at = [0.05, 0.05])
|
||||
/// |> line(end = [0, 7])
|
||||
/// |> tangentialArc(angle = 90, radius = 5)
|
||||
/// |> line(end = [-3, 0])
|
||||
/// |> tangentialArc(angle = -90, radius = 5)
|
||||
/// |> line(end = [0, 7])
|
||||
///
|
||||
/// // Create a hole for the pipe.
|
||||
/// pipeHole = startSketchOn(XY)
|
||||
/// |> circle(
|
||||
/// center = [0, 0],
|
||||
/// radius = 1.5,
|
||||
/// )
|
||||
///
|
||||
/// sweepSketch = startSketchOn(XY)
|
||||
/// |> circle(
|
||||
/// center = [0, 0],
|
||||
/// radius = 2,
|
||||
/// )
|
||||
/// |> subtract2d(tool = pipeHole)
|
||||
/// |> sweep(path = sweepPath)
|
||||
/// |> rotate(
|
||||
/// roll = 10,
|
||||
/// pitch = 10,
|
||||
/// yaw = 90,
|
||||
/// )
|
||||
/// ```
|
||||
///
|
||||
/// ```no_run
|
||||
/// // Rotate a pipe with just roll.
|
||||
///
|
||||
/// // Create a path for the sweep.
|
||||
/// sweepPath = startSketchOn(XZ)
|
||||
/// |> startProfile(at = [0.05, 0.05])
|
||||
/// |> line(end = [0, 7])
|
||||
/// |> tangentialArc(angle = 90, radius = 5)
|
||||
/// |> line(end = [-3, 0])
|
||||
/// |> tangentialArc(angle = -90, radius = 5)
|
||||
/// |> line(end = [0, 7])
|
||||
///
|
||||
/// // Create a hole for the pipe.
|
||||
/// pipeHole = startSketchOn(XY)
|
||||
/// |> circle(
|
||||
/// center = [0, 0],
|
||||
/// radius = 1.5,
|
||||
/// )
|
||||
///
|
||||
/// sweepSketch = startSketchOn(XY)
|
||||
/// |> circle(
|
||||
/// center = [0, 0],
|
||||
/// radius = 2,
|
||||
/// )
|
||||
/// |> subtract2d(tool = pipeHole)
|
||||
/// |> sweep(path = sweepPath)
|
||||
/// |> rotate(
|
||||
/// roll = 10,
|
||||
/// )
|
||||
/// ```
|
||||
///
|
||||
/// ```no_run
|
||||
/// // Rotate a pipe about a named axis with an angle.
|
||||
///
|
||||
/// // Create a path for the sweep.
|
||||
/// sweepPath = startSketchOn(XZ)
|
||||
/// |> startProfile(at = [0.05, 0.05])
|
||||
/// |> line(end = [0, 7])
|
||||
/// |> tangentialArc(angle = 90, radius = 5)
|
||||
/// |> line(end = [-3, 0])
|
||||
/// |> tangentialArc(angle = -90, radius = 5)
|
||||
/// |> line(end = [0, 7])
|
||||
///
|
||||
/// // Create a hole for the pipe.
|
||||
/// pipeHole = startSketchOn(XY)
|
||||
/// |> circle(
|
||||
/// center = [0, 0],
|
||||
/// radius = 1.5,
|
||||
/// )
|
||||
///
|
||||
/// sweepSketch = startSketchOn(XY)
|
||||
/// |> circle(
|
||||
/// center = [0, 0],
|
||||
/// radius = 2,
|
||||
/// )
|
||||
/// |> subtract2d(tool = pipeHole)
|
||||
/// |> sweep(path = sweepPath)
|
||||
/// |> rotate(
|
||||
/// axis = Z,
|
||||
/// angle = 90,
|
||||
/// )
|
||||
/// ```
|
||||
///
|
||||
/// ```no_run
|
||||
/// // Rotate an imported model.
|
||||
///
|
||||
/// import "tests/inputs/cube.sldprt" as cube
|
||||
///
|
||||
/// cube
|
||||
/// |> rotate(
|
||||
/// axis = [0, 0, 1.0],
|
||||
/// angle = 9,
|
||||
/// )
|
||||
/// ```
|
||||
///
|
||||
/// ```no_run
|
||||
/// // Rotate a pipe about a raw axis with an angle.
|
||||
///
|
||||
/// // Create a path for the sweep.
|
||||
/// sweepPath = startSketchOn(XZ)
|
||||
/// |> startProfile(at = [0.05, 0.05])
|
||||
/// |> line(end = [0, 7])
|
||||
/// |> tangentialArc(angle = 90, radius = 5)
|
||||
/// |> line(end = [-3, 0])
|
||||
/// |> tangentialArc(angle = -90, radius = 5)
|
||||
/// |> line(end = [0, 7])
|
||||
///
|
||||
/// // Create a hole for the pipe.
|
||||
/// pipeHole = startSketchOn(XY)
|
||||
/// |> circle(
|
||||
/// center = [0, 0],
|
||||
/// radius = 1.5,
|
||||
/// )
|
||||
///
|
||||
/// sweepSketch = startSketchOn(XY)
|
||||
/// |> circle(
|
||||
/// center = [0, 0],
|
||||
/// radius = 2,
|
||||
/// )
|
||||
/// |> subtract2d(tool = pipeHole)
|
||||
/// |> sweep(path = sweepPath)
|
||||
/// |> rotate(
|
||||
/// axis = [0, 0, 1.0],
|
||||
/// angle = 90,
|
||||
/// )
|
||||
/// ```
|
||||
///
|
||||
/// ```
|
||||
/// // Sweep two sketches along the same path.
|
||||
///
|
||||
/// sketch001 = startSketchOn(XY)
|
||||
/// rectangleSketch = startProfile(sketch001, at = [-200, 23.86])
|
||||
/// |> angledLine(angle = 0, length = 73.47, tag = $rectangleSegmentA001)
|
||||
/// |> angledLine(
|
||||
/// angle = segAng(rectangleSegmentA001) - 90,
|
||||
/// length = 50.61,
|
||||
/// )
|
||||
/// |> angledLine(
|
||||
/// angle = segAng(rectangleSegmentA001),
|
||||
/// length = -segLen(rectangleSegmentA001),
|
||||
/// )
|
||||
/// |> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||
/// |> close()
|
||||
///
|
||||
/// circleSketch = circle(sketch001, center = [200, -30.29], radius = 32.63)
|
||||
///
|
||||
/// sketch002 = startSketchOn(YZ)
|
||||
/// sweepPath = startProfile(sketch002, at = [0, 0])
|
||||
/// |> yLine(length = 231.81)
|
||||
/// |> tangentialArc(radius = 80, angle = -90)
|
||||
/// |> xLine(length = 384.93)
|
||||
///
|
||||
/// parts = sweep([rectangleSketch, circleSketch], path = sweepPath)
|
||||
///
|
||||
/// // Rotate the sweeps.
|
||||
/// rotate(parts, axis = [0, 0, 1.0], angle = 90)
|
||||
/// ```
|
||||
///
|
||||
/// ```no_run
|
||||
/// // Translate and rotate a sketch to create a loft.
|
||||
/// sketch001 = startSketchOn(XY)
|
||||
///
|
||||
/// fn square() {
|
||||
/// return startProfile(sketch001, at = [-10, 10])
|
||||
/// |> xLine(length = 20)
|
||||
/// |> yLine(length = -20)
|
||||
/// |> xLine(length = -20)
|
||||
/// |> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||
/// |> close()
|
||||
/// }
|
||||
///
|
||||
/// profile001 = square()
|
||||
///
|
||||
/// profile002 = square()
|
||||
/// |> translate(x = 0, y = 0, z = 20)
|
||||
/// |> rotate(axis = [0, 0, 1.0], angle = 45)
|
||||
///
|
||||
/// loft([profile001, profile002])
|
||||
/// ```
|
||||
#[stdlib {
|
||||
name = "rotate",
|
||||
feature_tree_operation = false,
|
||||
unlabeled_first = true,
|
||||
args = {
|
||||
objects = {docs = "The solid, sketch, or set of solids or sketches to rotate."},
|
||||
roll = {docs = "The roll angle in degrees. Must be between -360 and 360. Default is 0 if not given.", include_in_snippet = true},
|
||||
pitch = {docs = "The pitch angle in degrees. Must be between -360 and 360. Default is 0 if not given.", include_in_snippet = true},
|
||||
yaw = {docs = "The yaw angle in degrees. Must be between -360 and 360. Default is 0 if not given.", include_in_snippet = true},
|
||||
axis = {docs = "The axis to rotate around. Must be used with `angle`.", include_in_snippet = false},
|
||||
angle = {docs = "The angle to rotate in degrees. Must be used with `axis`. Must be between -360 and 360.", include_in_snippet = false},
|
||||
global = {docs = "If true, the transform is applied in global space. The origin of the model will move. By default, the transform is applied in local sketch axis, therefore the origin will not move."}
|
||||
},
|
||||
tags = ["transform"]
|
||||
}]
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
async fn inner_rotate(
|
||||
objects: SolidOrSketchOrImportedGeometry,
|
||||
|
@ -36,7 +36,16 @@ pub async fn execute_and_snapshot_ast(
|
||||
ast: Program,
|
||||
current_file: Option<PathBuf>,
|
||||
with_export_step: bool,
|
||||
) -> Result<(ExecState, EnvironmentRef, image::DynamicImage, Option<Vec<u8>>), ExecErrorWithState> {
|
||||
) -> Result<
|
||||
(
|
||||
ExecState,
|
||||
ExecutorContext,
|
||||
EnvironmentRef,
|
||||
image::DynamicImage,
|
||||
Option<Vec<u8>>,
|
||||
),
|
||||
ExecErrorWithState,
|
||||
> {
|
||||
let ctx = new_context(true, current_file).await?;
|
||||
let (exec_state, env, img) = match do_execute_and_snapshot(&ctx, ast).await {
|
||||
Ok((exec_state, env_ref, img)) => (exec_state, env_ref, img),
|
||||
@ -64,7 +73,7 @@ pub async fn execute_and_snapshot_ast(
|
||||
step = files.into_iter().next().map(|f| f.contents);
|
||||
}
|
||||
ctx.close().await;
|
||||
Ok((exec_state, env, img, step))
|
||||
Ok((exec_state, ctx, env, img, step))
|
||||
}
|
||||
|
||||
pub async fn execute_and_snapshot_no_auth(
|
||||
@ -93,7 +102,7 @@ async fn do_execute_and_snapshot(
|
||||
for e in exec_state.errors() {
|
||||
if e.severity.is_err() {
|
||||
return Err(ExecErrorWithState::new(
|
||||
KclErrorWithOutputs::no_outputs(KclError::Semantic(e.clone().into())).into(),
|
||||
KclErrorWithOutputs::no_outputs(KclError::new_semantic(e.clone().into())).into(),
|
||||
exec_state.clone(),
|
||||
));
|
||||
}
|
||||
@ -164,7 +173,7 @@ pub async fn execute_and_export_step(
|
||||
for e in exec_state.errors() {
|
||||
if e.severity.is_err() {
|
||||
return Err(ExecErrorWithState::new(
|
||||
KclErrorWithOutputs::no_outputs(KclError::Semantic(e.clone().into())).into(),
|
||||
KclErrorWithOutputs::no_outputs(KclError::new_semantic(e.clone().into())).into(),
|
||||
exec_state.clone(),
|
||||
));
|
||||
}
|
||||
|
@ -883,7 +883,7 @@ pub async fn walk_dir(dir: &std::path::PathBuf) -> Result<Vec<std::path::PathBuf
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub async fn recast_dir(dir: &std::path::Path, options: &crate::FormatOptions) -> Result<(), anyhow::Error> {
|
||||
let files = walk_dir(&dir.to_path_buf()).await.map_err(|err| {
|
||||
crate::KclError::Internal(crate::errors::KclErrorDetails::new(
|
||||
crate::KclError::new_internal(crate::errors::KclErrorDetails::new(
|
||||
format!("Failed to walk directory `{}`: {:?}", dir.display(), err),
|
||||
vec![crate::SourceRange::default()],
|
||||
))
|
||||
@ -912,7 +912,7 @@ pub async fn recast_dir(dir: &std::path::Path, options: &crate::FormatOptions) -
|
||||
if ce.severity != crate::errors::Severity::Warning {
|
||||
let report = crate::Report {
|
||||
kcl_source: contents.to_string(),
|
||||
error: crate::KclError::Semantic(ce.clone().into()),
|
||||
error: crate::KclError::new_semantic(ce.clone().into()),
|
||||
filename: file.to_string_lossy().to_string(),
|
||||
};
|
||||
let report = miette::Report::new(report);
|
||||
|
@ -3,10 +3,7 @@
|
||||
mod ast_node;
|
||||
mod ast_visitor;
|
||||
mod ast_walk;
|
||||
mod import_graph;
|
||||
|
||||
pub use ast_node::Node;
|
||||
pub use ast_visitor::Visitable;
|
||||
pub use ast_walk::walk;
|
||||
pub use import_graph::import_graph;
|
||||
pub(crate) use import_graph::{import_universe, Universe, UniverseMap};
|
||||
pub(crate) use ast_node::Node;
|
||||
pub(crate) use ast_visitor::Visitable;
|
||||
pub(crate) use ast_walk::walk;
|
||||
|
Reference in New Issue
Block a user