Move sketch functions to KCL; remove Rust decl dead code (#7335)
Signed-off-by: Nick Cameron <nrc@ncameron.org>
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
|
||||
@ -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((
|
||||
@ -827,7 +830,7 @@ impl ArgData {
|
||||
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),
|
||||
@ -989,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| {
|
||||
@ -999,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 {
|
||||
@ -1050,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());
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -565,30 +565,6 @@ 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 { 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
|
@ -1901,7 +1901,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,62 +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::new_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::new_undefined_value(
|
||||
KclErrorDetails::new(
|
||||
format!("Result of user-defined function {} is undefined", fn_name),
|
||||
source_ranges,
|
||||
),
|
||||
None,
|
||||
)
|
||||
})?;
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
@ -603,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::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],
|
||||
))
|
||||
})?;
|
||||
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 => {
|
||||
@ -706,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()),
|
||||
@ -940,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,
|
||||
};
|
||||
|
@ -41,7 +41,6 @@ use crate::{
|
||||
modules::{ModuleId, ModulePath, ModuleRepr},
|
||||
parsing::ast::types::{Expr, ImportPath, NodeRef},
|
||||
source_range::SourceRange,
|
||||
std::StdLib,
|
||||
walk::{Universe, UniverseMap},
|
||||
CompilationError, ExecError, KclErrorWithOutputs,
|
||||
};
|
||||
@ -273,7 +272,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 +410,6 @@ impl ExecutorContext {
|
||||
Ok(Self {
|
||||
engine,
|
||||
fs: Arc::new(FileManager::new()),
|
||||
stdlib: Arc::new(StdLib::new()),
|
||||
settings,
|
||||
context_type: ContextType::Live,
|
||||
})
|
||||
@ -423,7 +420,6 @@ impl ExecutorContext {
|
||||
ExecutorContext {
|
||||
engine,
|
||||
fs,
|
||||
stdlib: Arc::new(StdLib::new()),
|
||||
settings,
|
||||
context_type: ContextType::Live,
|
||||
}
|
||||
@ -436,7 +432,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 +442,6 @@ impl ExecutorContext {
|
||||
ExecutorContext {
|
||||
engine,
|
||||
fs,
|
||||
stdlib: Arc::new(StdLib::new()),
|
||||
settings,
|
||||
context_type: ContextType::Mock,
|
||||
}
|
||||
@ -458,7 +452,6 @@ impl ExecutorContext {
|
||||
ExecutorContext {
|
||||
engine,
|
||||
fs: Arc::new(FileManager::new()),
|
||||
stdlib: Arc::new(StdLib::new()),
|
||||
settings: Default::default(),
|
||||
context_type: ContextType::MockCustomForwarded,
|
||||
}
|
||||
@ -1390,7 +1383,6 @@ pub(crate) async fn parse_execute_with_project_dir(
|
||||
})?,
|
||||
)),
|
||||
fs: Arc::new(crate::fs::FileManager::new()),
|
||||
stdlib: Arc::new(crate::std::StdLib::new()),
|
||||
settings: ExecutorSettings {
|
||||
project_directory,
|
||||
..Default::default()
|
||||
|
@ -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,
|
||||
@ -1634,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() {
|
||||
@ -1660,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() {
|
||||
@ -1678,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");
|
||||
}
|
||||
@ -3884,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!(),
|
||||
|
@ -25,7 +25,6 @@ pub use crate::parsing::ast::types::{
|
||||
none::KclNone,
|
||||
};
|
||||
use crate::{
|
||||
docs::StdLibFn,
|
||||
errors::KclError,
|
||||
execution::{
|
||||
annotations,
|
||||
@ -1973,31 +1972,6 @@ impl CallExpressionKw {
|
||||
}
|
||||
}
|
||||
|
||||
/// 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")]
|
||||
|
@ -2480,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.
|
||||
@ -4898,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)
|
||||
|
@ -143,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)
|
||||
}
|
||||
@ -174,7 +179,7 @@ impl Args {
|
||||
{
|
||||
let Some(arg) = self.kw_args.labeled.get(label) else {
|
||||
return Err(KclError::new_semantic(KclErrorDetails::new(
|
||||
format!("This function requires a keyword argument '{label}'"),
|
||||
format!("This function requires a keyword argument `{label}`"),
|
||||
vec![self.source_range],
|
||||
)));
|
||||
};
|
||||
@ -186,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) {
|
||||
|
@ -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,35 +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::sketch::InvoluteCircular),
|
||||
Box::new(crate::std::sketch::Line),
|
||||
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),
|
||||
];
|
||||
}
|
||||
|
||||
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,
|
||||
@ -385,6 +352,70 @@ pub(crate) fn std_fn(path: &str, fn_name: &str) -> (crate::std::StdFn, StdFnProp
|
||||
|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"),
|
||||
@ -409,56 +440,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;
|
||||
|
@ -2,7 +2,6 @@
|
||||
|
||||
use anyhow::Result;
|
||||
use indexmap::IndexMap;
|
||||
use kcl_derive_docs::stdlib;
|
||||
use kcmc::shared::Point2d as KPoint2d; // Point2d is already defined in this pkg, to impl ts_rs traits.
|
||||
use kcmc::shared::Point3d as KPoint3d; // Point3d is already defined in this pkg, to impl ts_rs traits.
|
||||
use kcmc::{each_cmd as mcmd, length_unit::LengthUnit, shared::Angle, websocket::ModelingCmdReq, ModelingCmd};
|
||||
@ -100,8 +99,7 @@ pub enum StartOrEnd {
|
||||
pub const NEW_TAG_KW: &str = "tag";
|
||||
|
||||
pub async fn involute_circular(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
||||
let sketch =
|
||||
args.get_unlabeled_kw_arg_typed("sketch", &RuntimeType::Primitive(PrimitiveType::Sketch), exec_state)?;
|
||||
let sketch = args.get_unlabeled_kw_arg_typed("sketch", &RuntimeType::sketch(), exec_state)?;
|
||||
|
||||
let start_radius: TyF64 = args.get_kw_arg_typed("startRadius", &RuntimeType::length(), exec_state)?;
|
||||
let end_radius: TyF64 = args.get_kw_arg_typed("endRadius", &RuntimeType::length(), exec_state)?;
|
||||
@ -122,29 +120,6 @@ fn involute_curve(radius: f64, angle: f64) -> (f64, f64) {
|
||||
)
|
||||
}
|
||||
|
||||
/// Extend the current sketch with a new involute circular curve.
|
||||
///
|
||||
/// ```no_run
|
||||
/// a = 10
|
||||
/// b = 14
|
||||
/// startSketchOn(XZ)
|
||||
/// |> startProfile(at = [0, 0])
|
||||
/// |> involuteCircular(startRadius = a, endRadius = b, angle = 60)
|
||||
/// |> involuteCircular(startRadius = a, endRadius = b, angle = 60, reverse = true)
|
||||
/// ```
|
||||
#[stdlib {
|
||||
name = "involuteCircular",
|
||||
unlabeled_first = true,
|
||||
args = {
|
||||
sketch = { docs = "Which sketch should this path be added to?"},
|
||||
start_radius = { docs = "The involute is described between two circles, start_radius is the radius of the inner circle."},
|
||||
end_radius = { docs = "The involute is described between two circles, end_radius is the radius of the outer circle."},
|
||||
angle = { docs = "The angle to rotate the involute by. A value of zero will produce a curve with a tangent along the x-axis at the start point of the curve."},
|
||||
reverse = { docs = "If reverse is true, the segment will start from the end of the involute, otherwise it will start from that start. Defaults to false."},
|
||||
tag = { docs = "Create a new tag which refers to this line"},
|
||||
},
|
||||
tags = ["sketch"]
|
||||
}]
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
async fn inner_involute_circular(
|
||||
sketch: Sketch,
|
||||
@ -228,41 +203,6 @@ pub async fn line(exec_state: &mut ExecState, args: Args) -> Result<KclValue, Kc
|
||||
})
|
||||
}
|
||||
|
||||
/// Extend the current sketch with a new straight line.
|
||||
///
|
||||
/// ```no_run
|
||||
/// triangle = startSketchOn(XZ)
|
||||
/// |> startProfile(at = [0, 0])
|
||||
/// // The END argument means it ends at exactly [10, 0].
|
||||
/// // This is an absolute measurement, it is NOT relative to
|
||||
/// // the start of the sketch.
|
||||
/// |> line(endAbsolute = [10, 0])
|
||||
/// |> line(endAbsolute = [0, 10])
|
||||
/// |> line(endAbsolute = [-10, 0], tag = $thirdLineOfTriangle)
|
||||
/// |> close()
|
||||
/// |> extrude(length = 5)
|
||||
///
|
||||
/// box = startSketchOn(XZ)
|
||||
/// |> startProfile(at = [10, 10])
|
||||
/// // The 'to' argument means move the pen this much.
|
||||
/// // So, [10, 0] is a relative distance away from the current point.
|
||||
/// |> line(end = [10, 0])
|
||||
/// |> line(end = [0, 10])
|
||||
/// |> line(end = [-10, 0], tag = $thirdLineOfBox)
|
||||
/// |> close()
|
||||
/// |> extrude(length = 5)
|
||||
/// ```
|
||||
#[stdlib {
|
||||
name = "line",
|
||||
unlabeled_first = true,
|
||||
args = {
|
||||
sketch = { docs = "Which sketch should this path be added to?"},
|
||||
end_absolute = { docs = "Which absolute point should this line go to? Incompatible with `end`."},
|
||||
end = { docs = "How far away (along the X and Y axes) should this line go? Incompatible with `endAbsolute`.", include_in_snippet = true},
|
||||
tag = { docs = "Create a new tag which refers to this line"},
|
||||
},
|
||||
tags = ["sketch"]
|
||||
}]
|
||||
async fn inner_line(
|
||||
sketch: Sketch,
|
||||
end_absolute: Option<[TyF64; 2]>,
|
||||
@ -401,39 +341,6 @@ pub async fn x_line(exec_state: &mut ExecState, args: Args) -> Result<KclValue,
|
||||
})
|
||||
}
|
||||
|
||||
/// Draw a line relative to the current origin to a specified distance away
|
||||
/// from the current position along the 'x' axis.
|
||||
///
|
||||
/// ```no_run
|
||||
/// exampleSketch = startSketchOn(XZ)
|
||||
/// |> startProfile(at = [0, 0])
|
||||
/// |> xLine(length = 15)
|
||||
/// |> angledLine(
|
||||
/// angle = 80,
|
||||
/// length = 15,
|
||||
/// )
|
||||
/// |> line(end = [8, -10])
|
||||
/// |> xLine(length = 10)
|
||||
/// |> angledLine(
|
||||
/// angle = 120,
|
||||
/// length = 30,
|
||||
/// )
|
||||
/// |> xLine(length = -15)
|
||||
/// |> close()
|
||||
///
|
||||
/// example = extrude(exampleSketch, length = 10)
|
||||
/// ```
|
||||
#[stdlib {
|
||||
name = "xLine",
|
||||
unlabeled_first = true,
|
||||
args = {
|
||||
sketch = { docs = "Which sketch should this path be added to?"},
|
||||
length = { docs = "How far away along the X axis should this line go? Incompatible with `endAbsolute`.", include_in_snippet = true},
|
||||
end_absolute = { docs = "Which absolute X value should this line go to? Incompatible with `length`."},
|
||||
tag = { docs = "Create a new tag which refers to this line"},
|
||||
},
|
||||
tags = ["sketch"]
|
||||
}]
|
||||
async fn inner_x_line(
|
||||
sketch: Sketch,
|
||||
length: Option<TyF64>,
|
||||
@ -471,34 +378,6 @@ pub async fn y_line(exec_state: &mut ExecState, args: Args) -> Result<KclValue,
|
||||
})
|
||||
}
|
||||
|
||||
/// Draw a line relative to the current origin to a specified distance away
|
||||
/// from the current position along the 'y' axis.
|
||||
///
|
||||
/// ```no_run
|
||||
/// exampleSketch = startSketchOn(XZ)
|
||||
/// |> startProfile(at = [0, 0])
|
||||
/// |> yLine(length = 15)
|
||||
/// |> angledLine(
|
||||
/// angle = 30,
|
||||
/// length = 15,
|
||||
/// )
|
||||
/// |> line(end = [8, -10])
|
||||
/// |> yLine(length = -5)
|
||||
/// |> close()
|
||||
///
|
||||
/// example = extrude(exampleSketch, length = 10)
|
||||
/// ```
|
||||
#[stdlib {
|
||||
name = "yLine",
|
||||
unlabeled_first = true,
|
||||
args = {
|
||||
sketch = { docs = "Which sketch should this path be added to?"},
|
||||
length = { docs = "How far away along the Y axis should this line go? Incompatible with `endAbsolute`.", include_in_snippet = true},
|
||||
end_absolute = { docs = "Which absolute Y value should this line go to? Incompatible with `length`."},
|
||||
tag = { docs = "Create a new tag which refers to this line"},
|
||||
},
|
||||
tags = ["sketch"]
|
||||
}]
|
||||
async fn inner_y_line(
|
||||
sketch: Sketch,
|
||||
length: Option<TyF64>,
|
||||
@ -553,38 +432,6 @@ pub async fn angled_line(exec_state: &mut ExecState, args: Args) -> Result<KclVa
|
||||
})
|
||||
}
|
||||
|
||||
/// Draw a line segment relative to the current origin using the polar
|
||||
/// measure of some angle and distance.
|
||||
///
|
||||
/// ```no_run
|
||||
/// exampleSketch = startSketchOn(XZ)
|
||||
/// |> startProfile(at = [0, 0])
|
||||
/// |> yLine(endAbsolute = 15)
|
||||
/// |> angledLine(
|
||||
/// angle = 30,
|
||||
/// length = 15,
|
||||
/// )
|
||||
/// |> line(end = [8, -10])
|
||||
/// |> yLine(endAbsolute = 0)
|
||||
/// |> close()
|
||||
///
|
||||
/// example = extrude(exampleSketch, length = 10)
|
||||
/// ```
|
||||
#[stdlib {
|
||||
name = "angledLine",
|
||||
unlabeled_first = true,
|
||||
args = {
|
||||
sketch = { docs = "Which sketch should this path be added to?"},
|
||||
angle = { docs = "Which angle should the line be drawn at?" },
|
||||
length = { docs = "Draw the line this distance along the given angle. Only one of `length`, `lengthX`, `lengthY`, `endAbsoluteX`, `endAbsoluteY` can be given."},
|
||||
length_x = { docs = "Draw the line this distance along the X axis. Only one of `length`, `lengthX`, `lengthY`, `endAbsoluteX`, `endAbsoluteY` can be given."},
|
||||
length_y = { docs = "Draw the line this distance along the Y axis. Only one of `length`, `lengthX`, `lengthY`, `endAbsoluteX`, `endAbsoluteY` can be given."},
|
||||
end_absolute_x = { docs = "Draw the line along the given angle until it reaches this point along the X axis. Only one of `length`, `lengthX`, `lengthY`, `endAbsoluteX`, `endAbsoluteY` can be given."},
|
||||
end_absolute_y = { docs = "Draw the line along the given angle until it reaches this point along the Y axis. Only one of `length`, `lengthX`, `lengthY`, `endAbsoluteX`, `endAbsoluteY` can be given."},
|
||||
tag = { docs = "Create a new tag which refers to this line"},
|
||||
},
|
||||
tags = ["sketch"]
|
||||
}]
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
async fn inner_angled_line(
|
||||
sketch: Sketch,
|
||||
@ -850,37 +697,6 @@ pub async fn angled_line_that_intersects(exec_state: &mut ExecState, args: Args)
|
||||
})
|
||||
}
|
||||
|
||||
/// Draw an angled line from the current origin, constructing a line segment
|
||||
/// such that the newly created line intersects the desired target line
|
||||
/// segment.
|
||||
///
|
||||
/// ```no_run
|
||||
/// exampleSketch = startSketchOn(XZ)
|
||||
/// |> startProfile(at = [0, 0])
|
||||
/// |> line(endAbsolute = [5, 10])
|
||||
/// |> line(endAbsolute = [-10, 10], tag = $lineToIntersect)
|
||||
/// |> line(endAbsolute = [0, 20])
|
||||
/// |> angledLineThatIntersects(
|
||||
/// angle = 80,
|
||||
/// intersectTag = lineToIntersect,
|
||||
/// offset = 10,
|
||||
/// )
|
||||
/// |> close()
|
||||
///
|
||||
/// example = extrude(exampleSketch, length = 10)
|
||||
/// ```
|
||||
#[stdlib {
|
||||
name = "angledLineThatIntersects",
|
||||
unlabeled_first = true,
|
||||
args = {
|
||||
sketch = { docs = "Which sketch should this path be added to?"},
|
||||
angle = { docs = "Which angle should the line be drawn at?" },
|
||||
intersect_tag = { docs = "The tag of the line to intersect with" },
|
||||
offset = { docs = "The offset from the intersecting line. Defaults to 0." },
|
||||
tag = { docs = "Create a new tag which refers to this line"},
|
||||
},
|
||||
tags = ["sketch"]
|
||||
}]
|
||||
pub async fn inner_angled_line_that_intersects(
|
||||
sketch: Sketch,
|
||||
angle: TyF64,
|
||||
@ -971,190 +787,6 @@ pub async fn start_sketch_on(exec_state: &mut ExecState, args: Args) -> Result<K
|
||||
}
|
||||
}
|
||||
|
||||
/// Start a new 2-dimensional sketch on a specific plane or face.
|
||||
///
|
||||
/// ### Sketch on Face Behavior
|
||||
///
|
||||
/// There are some important behaviors to understand when sketching on a face:
|
||||
///
|
||||
/// The resulting sketch will _include_ the face and thus Solid
|
||||
/// that was sketched on. So say you were to export the resulting Sketch / Solid
|
||||
/// from a sketch on a face, you would get both the artifact of the sketch
|
||||
/// on the face and the parent face / Solid itself.
|
||||
///
|
||||
/// This is important to understand because if you were to then sketch on the
|
||||
/// resulting Solid, it would again include the face and parent Solid that was
|
||||
/// sketched on. This could go on indefinitely.
|
||||
///
|
||||
/// The point is if you want to export the result of a sketch on a face, you
|
||||
/// only need to export the final Solid that was created from the sketch on the
|
||||
/// face, since it will include all the parent faces and Solids.
|
||||
///
|
||||
///
|
||||
/// ```no_run
|
||||
/// exampleSketch = startSketchOn(XY)
|
||||
/// |> startProfile(at = [0, 0])
|
||||
/// |> line(end = [10, 0])
|
||||
/// |> line(end = [0, 10])
|
||||
/// |> line(end = [-10, 0])
|
||||
/// |> close()
|
||||
///
|
||||
/// example = extrude(exampleSketch, length = 5)
|
||||
///
|
||||
/// exampleSketch002 = startSketchOn(example, face = END)
|
||||
/// |> startProfile(at = [1, 1])
|
||||
/// |> line(end = [8, 0])
|
||||
/// |> line(end = [0, 8])
|
||||
/// |> line(end = [-8, 0])
|
||||
/// |> close()
|
||||
///
|
||||
/// example002 = extrude(exampleSketch002, length = 5)
|
||||
///
|
||||
/// exampleSketch003 = startSketchOn(example002, face = END)
|
||||
/// |> startProfile(at = [2, 2])
|
||||
/// |> line(end = [6, 0])
|
||||
/// |> line(end = [0, 6])
|
||||
/// |> line(end = [-6, 0])
|
||||
/// |> close()
|
||||
///
|
||||
/// example003 = extrude(exampleSketch003, length = 5)
|
||||
/// ```
|
||||
///
|
||||
/// ```no_run
|
||||
/// // Sketch on the end of an extruded face by tagging the end face.
|
||||
///
|
||||
/// exampleSketch = startSketchOn(XY)
|
||||
/// |> startProfile(at = [0, 0])
|
||||
/// |> line(end = [10, 0])
|
||||
/// |> line(end = [0, 10])
|
||||
/// |> line(end = [-10, 0])
|
||||
/// |> close()
|
||||
///
|
||||
/// example = extrude(exampleSketch, length = 5, tagEnd = $end01)
|
||||
///
|
||||
/// exampleSketch002 = startSketchOn(example, face = end01)
|
||||
/// |> startProfile(at = [1, 1])
|
||||
/// |> line(end = [8, 0])
|
||||
/// |> line(end = [0, 8])
|
||||
/// |> line(end = [-8, 0])
|
||||
/// |> close()
|
||||
///
|
||||
/// example002 = extrude(exampleSketch002, length = 5, tagEnd = $end02)
|
||||
///
|
||||
/// exampleSketch003 = startSketchOn(example002, face = end02)
|
||||
/// |> startProfile(at = [2, 2])
|
||||
/// |> line(end = [6, 0])
|
||||
/// |> line(end = [0, 6])
|
||||
/// |> line(end = [-6, 0])
|
||||
/// |> close()
|
||||
///
|
||||
/// example003 = extrude(exampleSketch003, length = 5)
|
||||
/// ```
|
||||
///
|
||||
/// ```no_run
|
||||
/// exampleSketch = startSketchOn(XY)
|
||||
/// |> startProfile(at = [0, 0])
|
||||
/// |> line(end = [10, 0])
|
||||
/// |> line(end = [0, 10], tag = $sketchingFace)
|
||||
/// |> line(end = [-10, 0])
|
||||
/// |> close()
|
||||
///
|
||||
/// example = extrude(exampleSketch, length = 10)
|
||||
///
|
||||
/// exampleSketch002 = startSketchOn(example, face = sketchingFace)
|
||||
/// |> startProfile(at = [1, 1])
|
||||
/// |> line(end = [8, 0])
|
||||
/// |> line(end = [0, 8])
|
||||
/// |> line(end = [-8, 0])
|
||||
/// |> close(tag = $sketchingFace002)
|
||||
///
|
||||
/// example002 = extrude(exampleSketch002, length = 10)
|
||||
///
|
||||
/// exampleSketch003 = startSketchOn(example002, face = sketchingFace002)
|
||||
/// |> startProfile(at = [-8, 12])
|
||||
/// |> line(end = [0, 6])
|
||||
/// |> line(end = [6, 0])
|
||||
/// |> line(end = [0, -6])
|
||||
/// |> close()
|
||||
///
|
||||
/// example003 = extrude(exampleSketch003, length = 5)
|
||||
/// ```
|
||||
///
|
||||
/// ```no_run
|
||||
/// exampleSketch = startSketchOn(XY)
|
||||
/// |> startProfile(at = [4, 12])
|
||||
/// |> line(end = [2, 0])
|
||||
/// |> line(end = [0, -6])
|
||||
/// |> line(end = [4, -6])
|
||||
/// |> line(end = [0, -6])
|
||||
/// |> line(end = [-3.75, -4.5])
|
||||
/// |> line(end = [0, -5.5])
|
||||
/// |> line(end = [-2, 0])
|
||||
/// |> close()
|
||||
///
|
||||
/// example = revolve(exampleSketch, axis = Y, angle = 180)
|
||||
///
|
||||
/// exampleSketch002 = startSketchOn(example, face = END)
|
||||
/// |> startProfile(at = [4.5, -5])
|
||||
/// |> line(end = [0, 5])
|
||||
/// |> line(end = [5, 0])
|
||||
/// |> line(end = [0, -5])
|
||||
/// |> close()
|
||||
///
|
||||
/// example002 = extrude(exampleSketch002, length = 5)
|
||||
/// ```
|
||||
///
|
||||
/// ```no_run
|
||||
/// // Sketch on the end of a revolved face by tagging the end face.
|
||||
///
|
||||
/// exampleSketch = startSketchOn(XY)
|
||||
/// |> startProfile(at = [4, 12])
|
||||
/// |> line(end = [2, 0])
|
||||
/// |> line(end = [0, -6])
|
||||
/// |> line(end = [4, -6])
|
||||
/// |> line(end = [0, -6])
|
||||
/// |> line(end = [-3.75, -4.5])
|
||||
/// |> line(end = [0, -5.5])
|
||||
/// |> line(end = [-2, 0])
|
||||
/// |> close()
|
||||
///
|
||||
/// example = revolve(exampleSketch, axis = Y, angle = 180, tagEnd = $end01)
|
||||
///
|
||||
/// exampleSketch002 = startSketchOn(example, face = end01)
|
||||
/// |> startProfile(at = [4.5, -5])
|
||||
/// |> line(end = [0, 5])
|
||||
/// |> line(end = [5, 0])
|
||||
/// |> line(end = [0, -5])
|
||||
/// |> close()
|
||||
///
|
||||
/// example002 = extrude(exampleSketch002, length = 5)
|
||||
/// ```
|
||||
///
|
||||
/// ```no_run
|
||||
/// a1 = startSketchOn({
|
||||
/// origin = { x = 0, y = 0, z = 0 },
|
||||
/// xAxis = { x = 1, y = 0, z = 0 },
|
||||
/// yAxis = { x = 0, y = 1, z = 0 },
|
||||
/// zAxis = { x = 0, y = 0, z = 1 }
|
||||
/// })
|
||||
/// |> startProfile(at = [0, 0])
|
||||
/// |> line(end = [100.0, 0])
|
||||
/// |> yLine(length = -100.0)
|
||||
/// |> xLine(length = -100.0)
|
||||
/// |> yLine(length = 100.0)
|
||||
/// |> close()
|
||||
/// |> extrude(length = 3.14)
|
||||
/// ```
|
||||
#[stdlib {
|
||||
name = "startSketchOn",
|
||||
feature_tree_operation = true,
|
||||
unlabeled_first = true,
|
||||
args = {
|
||||
plane_or_solid = { docs = "The plane or solid to sketch on"},
|
||||
face = { docs = "Identify a face of a solid if a solid is specified as the input argument (`plane_or_solid`)"},
|
||||
},
|
||||
tags = ["sketch"]
|
||||
}]
|
||||
async fn inner_start_sketch_on(
|
||||
plane_or_solid: SketchData,
|
||||
face: Option<FaceTag>,
|
||||
@ -1280,50 +912,6 @@ pub async fn start_profile(exec_state: &mut ExecState, args: Args) -> Result<Kcl
|
||||
})
|
||||
}
|
||||
|
||||
/// Start a new profile at a given point.
|
||||
///
|
||||
/// ```no_run
|
||||
/// exampleSketch = startSketchOn(XZ)
|
||||
/// |> startProfile(at = [0, 0])
|
||||
/// |> line(end = [10, 0])
|
||||
/// |> line(end = [0, 10])
|
||||
/// |> line(end = [-10, 0])
|
||||
/// |> close()
|
||||
///
|
||||
/// example = extrude(exampleSketch, length = 5)
|
||||
/// ```
|
||||
///
|
||||
/// ```no_run
|
||||
/// exampleSketch = startSketchOn(-XZ)
|
||||
/// |> startProfile(at = [10, 10])
|
||||
/// |> line(end = [10, 0])
|
||||
/// |> line(end = [0, 10])
|
||||
/// |> line(end = [-10, 0])
|
||||
/// |> close()
|
||||
///
|
||||
/// example = extrude(exampleSketch, length = 5)
|
||||
/// ```
|
||||
///
|
||||
/// ```no_run
|
||||
/// exampleSketch = startSketchOn(-XZ)
|
||||
/// |> startProfile(at = [-10, 23])
|
||||
/// |> line(end = [10, 0])
|
||||
/// |> line(end = [0, 10])
|
||||
/// |> line(end = [-10, 0])
|
||||
/// |> close()
|
||||
///
|
||||
/// example = extrude(exampleSketch, length = 5)
|
||||
/// ```
|
||||
#[stdlib {
|
||||
name = "startProfile",
|
||||
unlabeled_first = true,
|
||||
args = {
|
||||
sketch_surface = { docs = "What to start the profile on" },
|
||||
at = { docs = "Where to start the profile. An absolute point.", snippet_value_array = ["0", "0"] },
|
||||
tag = { docs = "Tag this first starting point" },
|
||||
},
|
||||
tags = ["sketch"]
|
||||
}]
|
||||
pub(crate) async fn inner_start_profile(
|
||||
sketch_surface: SketchSurface,
|
||||
at: [TyF64; 2],
|
||||
@ -1440,91 +1028,36 @@ pub(crate) async fn inner_start_profile(
|
||||
|
||||
/// Returns the X component of the sketch profile start point.
|
||||
pub async fn profile_start_x(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
||||
let sketch: Sketch = args.get_unlabeled_kw_arg_typed("sketch", &RuntimeType::sketch(), exec_state)?;
|
||||
let sketch: Sketch = args.get_unlabeled_kw_arg_typed("profile", &RuntimeType::sketch(), exec_state)?;
|
||||
let ty = sketch.units.into();
|
||||
let x = inner_profile_start_x(sketch)?;
|
||||
Ok(args.make_user_val_from_f64_with_type(TyF64::new(x, ty)))
|
||||
}
|
||||
|
||||
/// Extract the provided 2-dimensional sketch's profile's origin's 'x'
|
||||
/// value.
|
||||
///
|
||||
/// ```no_run
|
||||
/// sketch001 = startSketchOn(XY)
|
||||
/// |> startProfile(at = [5, 2])
|
||||
/// |> angledLine(angle = -26.6, length = 50)
|
||||
/// |> angledLine(angle = 90, length = 50)
|
||||
/// |> angledLine(angle = 30, endAbsoluteX = profileStartX(%))
|
||||
/// ```
|
||||
#[stdlib {
|
||||
name = "profileStartX",
|
||||
unlabeled_first = true,
|
||||
args = {
|
||||
profile = {docs = "Profile whose start is being used"},
|
||||
},
|
||||
tags = ["sketch"]
|
||||
}]
|
||||
pub(crate) fn inner_profile_start_x(profile: Sketch) -> Result<f64, KclError> {
|
||||
Ok(profile.start.to[0])
|
||||
}
|
||||
|
||||
/// Returns the Y component of the sketch profile start point.
|
||||
pub async fn profile_start_y(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
||||
let sketch: Sketch = args.get_unlabeled_kw_arg_typed("sketch", &RuntimeType::sketch(), exec_state)?;
|
||||
let sketch: Sketch = args.get_unlabeled_kw_arg_typed("profile", &RuntimeType::sketch(), exec_state)?;
|
||||
let ty = sketch.units.into();
|
||||
let x = inner_profile_start_y(sketch)?;
|
||||
Ok(args.make_user_val_from_f64_with_type(TyF64::new(x, ty)))
|
||||
}
|
||||
|
||||
/// Extract the provided 2-dimensional sketch's profile's origin's 'y'
|
||||
/// value.
|
||||
///
|
||||
/// ```no_run
|
||||
/// sketch001 = startSketchOn(XY)
|
||||
/// |> startProfile(at = [5, 2])
|
||||
/// |> angledLine(angle = -60, length = 14 )
|
||||
/// |> angledLine(angle = 30, endAbsoluteY = profileStartY(%))
|
||||
/// ```
|
||||
#[stdlib {
|
||||
name = "profileStartY",
|
||||
unlabeled_first = true,
|
||||
args = {
|
||||
profile = {docs = "Profile whose start is being used"},
|
||||
},
|
||||
tags = ["sketch"]
|
||||
}]
|
||||
pub(crate) fn inner_profile_start_y(profile: Sketch) -> Result<f64, KclError> {
|
||||
Ok(profile.start.to[1])
|
||||
}
|
||||
|
||||
/// Returns the sketch profile start point.
|
||||
pub async fn profile_start(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
||||
let sketch: Sketch = args.get_unlabeled_kw_arg_typed("sketch", &RuntimeType::sketch(), exec_state)?;
|
||||
let sketch: Sketch = args.get_unlabeled_kw_arg_typed("profile", &RuntimeType::sketch(), exec_state)?;
|
||||
let ty = sketch.units.into();
|
||||
let point = inner_profile_start(sketch)?;
|
||||
Ok(KclValue::from_point2d(point, ty, args.into()))
|
||||
}
|
||||
|
||||
/// Extract the provided 2-dimensional sketch's profile's origin
|
||||
/// value.
|
||||
///
|
||||
/// ```no_run
|
||||
/// sketch001 = startSketchOn(XY)
|
||||
/// |> startProfile(at = [5, 2])
|
||||
/// |> angledLine(angle = 120, length = 50 , tag = $seg01)
|
||||
/// |> angledLine(angle = segAng(seg01) + 120, length = 50 )
|
||||
/// |> line(end = profileStart(%))
|
||||
/// |> close()
|
||||
/// |> extrude(length = 20)
|
||||
/// ```
|
||||
#[stdlib {
|
||||
name = "profileStart",
|
||||
unlabeled_first = true,
|
||||
args = {
|
||||
profile = {docs = "Profile whose start is being used"},
|
||||
},
|
||||
tags = ["sketch"]
|
||||
}]
|
||||
pub(crate) fn inner_profile_start(profile: Sketch) -> Result<[f64; 2], KclError> {
|
||||
Ok(profile.start.to)
|
||||
}
|
||||
@ -1540,41 +1073,6 @@ pub async fn close(exec_state: &mut ExecState, args: Args) -> Result<KclValue, K
|
||||
})
|
||||
}
|
||||
|
||||
/// Construct a line segment from the current origin back to the profile's
|
||||
/// origin, ensuring the resulting 2-dimensional sketch is not open-ended.
|
||||
///
|
||||
/// If you want to perform some 3-dimensional operation on a sketch, like
|
||||
/// extrude or sweep, you must `close` it first. `close` must be called even
|
||||
/// if the end point of the last segment is coincident with the sketch
|
||||
/// starting point.
|
||||
///
|
||||
/// ```no_run
|
||||
/// startSketchOn(XZ)
|
||||
/// |> startProfile(at = [0, 0])
|
||||
/// |> line(end = [10, 10])
|
||||
/// |> line(end = [10, 0])
|
||||
/// |> close()
|
||||
/// |> extrude(length = 10)
|
||||
/// ```
|
||||
///
|
||||
/// ```no_run
|
||||
/// exampleSketch = startSketchOn(-XZ)
|
||||
/// |> startProfile(at = [0, 0])
|
||||
/// |> line(end = [10, 0])
|
||||
/// |> line(end = [0, 10])
|
||||
/// |> close()
|
||||
///
|
||||
/// example = extrude(exampleSketch, length = 10)
|
||||
/// ```
|
||||
#[stdlib {
|
||||
name = "close",
|
||||
unlabeled_first = true,
|
||||
args = {
|
||||
sketch = { docs = "The sketch you want to close"},
|
||||
tag = { docs = "Create a new tag which refers to this line"},
|
||||
},
|
||||
tags = ["sketch"]
|
||||
}]
|
||||
pub(crate) async fn inner_close(
|
||||
sketch: Sketch,
|
||||
tag: Option<TagNode>,
|
||||
@ -1644,54 +1142,6 @@ pub async fn arc(exec_state: &mut ExecState, args: Args) -> Result<KclValue, Kcl
|
||||
})
|
||||
}
|
||||
|
||||
/// Draw a curved line segment along an imaginary circle.
|
||||
///
|
||||
/// The arc is constructed such that the current position of the sketch is
|
||||
/// placed along an imaginary circle of the specified radius, at angleStart
|
||||
/// degrees. The resulting arc is the segment of the imaginary circle from
|
||||
/// that origin point to angleEnd, radius away from the center of the imaginary
|
||||
/// circle.
|
||||
///
|
||||
/// Unless this makes a lot of sense and feels like what you're looking
|
||||
/// for to construct your shape, you're likely looking for tangentialArc.
|
||||
///
|
||||
/// ```no_run
|
||||
/// exampleSketch = startSketchOn(XZ)
|
||||
/// |> startProfile(at = [0, 0])
|
||||
/// |> line(end = [10, 0])
|
||||
/// |> arc(
|
||||
/// angleStart = 0,
|
||||
/// angleEnd = 280,
|
||||
/// radius = 16
|
||||
/// )
|
||||
/// |> close()
|
||||
/// example = extrude(exampleSketch, length = 10)
|
||||
/// ```
|
||||
/// ```no_run
|
||||
/// exampleSketch = startSketchOn(XZ)
|
||||
/// |> startProfile(at = [0, 0])
|
||||
/// |> arc(
|
||||
/// endAbsolute = [10,0],
|
||||
/// interiorAbsolute = [5,5]
|
||||
/// )
|
||||
/// |> close()
|
||||
/// example = extrude(exampleSketch, length = 10)
|
||||
/// ```
|
||||
#[stdlib {
|
||||
name = "arc",
|
||||
unlabeled_first = true,
|
||||
args = {
|
||||
sketch = { docs = "Which sketch should this path be added to?" },
|
||||
angle_start = { docs = "Where along the circle should this arc start?", include_in_snippet = true },
|
||||
angle_end = { docs = "Where along the circle should this arc end?", include_in_snippet = true },
|
||||
radius = { docs = "How large should the circle be? Incompatible with `diameter`." },
|
||||
diameter = { docs = "How large should the circle be? Incompatible with `radius`.", include_in_snippet = true },
|
||||
interior_absolute = { docs = "Any point between the arc's start and end? Requires `endAbsolute`. Incompatible with `angleStart` or `angleEnd`" },
|
||||
end_absolute = { docs = "Where should this arc end? Requires `interiorAbsolute`. Incompatible with `angleStart` or `angleEnd`" },
|
||||
tag = { docs = "Create a new tag which refers to this line"},
|
||||
},
|
||||
tags = ["sketch"]
|
||||
}]
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub(crate) async fn inner_arc(
|
||||
sketch: Sketch,
|
||||
@ -1880,74 +1330,6 @@ pub async fn tangential_arc(exec_state: &mut ExecState, args: Args) -> Result<Kc
|
||||
})
|
||||
}
|
||||
|
||||
/// Starting at the current sketch's origin, draw a curved line segment along
|
||||
/// some part of an imaginary circle until it reaches the desired (x, y)
|
||||
/// coordinates.
|
||||
///
|
||||
/// When using radius and angle, draw a curved line segment along part of an
|
||||
/// imaginary circle. The arc is constructed such that the last line segment is
|
||||
/// placed tangent to the imaginary circle of the specified radius. The
|
||||
/// resulting arc is the segment of the imaginary circle from that tangent point
|
||||
/// for 'angle' degrees along the imaginary circle.
|
||||
///
|
||||
/// ```no_run
|
||||
/// exampleSketch = startSketchOn(XZ)
|
||||
/// |> startProfile(at = [0, 0])
|
||||
/// |> angledLine(
|
||||
/// angle = 45,
|
||||
/// length = 10,
|
||||
/// )
|
||||
/// |> tangentialArc(end = [0, -10])
|
||||
/// |> line(end = [-10, 0])
|
||||
/// |> close()
|
||||
///
|
||||
/// example = extrude(exampleSketch, length = 10)
|
||||
/// ```
|
||||
///
|
||||
/// ```no_run
|
||||
/// exampleSketch = startSketchOn(XZ)
|
||||
/// |> startProfile(at = [0, 0])
|
||||
/// |> angledLine(
|
||||
/// angle = 60,
|
||||
/// length = 10,
|
||||
/// )
|
||||
/// |> tangentialArc(endAbsolute = [15, 15])
|
||||
/// |> line(end = [10, -15])
|
||||
/// |> close()
|
||||
///
|
||||
/// example = extrude(exampleSketch, length = 10)
|
||||
/// ```
|
||||
///
|
||||
/// ```no_run
|
||||
/// exampleSketch = startSketchOn(XZ)
|
||||
/// |> startProfile(at = [0, 0])
|
||||
/// |> angledLine(
|
||||
/// angle = 60,
|
||||
/// length = 10,
|
||||
/// )
|
||||
/// |> tangentialArc(radius = 10, angle = -120)
|
||||
/// |> angledLine(
|
||||
/// angle = -60,
|
||||
/// length = 10,
|
||||
/// )
|
||||
/// |> close()
|
||||
///
|
||||
/// example = extrude(exampleSketch, length = 10)
|
||||
/// ```
|
||||
#[stdlib {
|
||||
name = "tangentialArc",
|
||||
unlabeled_first = true,
|
||||
args = {
|
||||
sketch = { docs = "Which sketch should this path be added to?"},
|
||||
end_absolute = { docs = "Which absolute point should this arc go to? Incompatible with `end`, `radius`, and `offset`."},
|
||||
end = { docs = "How far away (along the X and Y axes) should this arc go? Incompatible with `endAbsolute`, `radius`, and `offset`.", include_in_snippet = true },
|
||||
radius = { docs = "Radius of the imaginary circle. `angle` must be given. Incompatible with `end` and `endAbsolute` and `diameter`."},
|
||||
diameter = { docs = "Diameter of the imaginary circle. `angle` must be given. Incompatible with `end` and `endAbsolute` and `radius`."},
|
||||
angle = { docs = "Offset of the arc in degrees. `radius` must be given. Incompatible with `end` and `endAbsolute`."},
|
||||
tag = { docs = "Create a new tag which refers to this arc"},
|
||||
},
|
||||
tags = ["sketch"]
|
||||
}]
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
async fn inner_tangential_arc(
|
||||
sketch: Sketch,
|
||||
@ -2206,48 +1588,6 @@ pub async fn bezier_curve(exec_state: &mut ExecState, args: Args) -> Result<KclV
|
||||
})
|
||||
}
|
||||
|
||||
/// Draw a smooth, continuous, curved line segment from the current origin to
|
||||
/// the desired (x, y), using a number of control points to shape the curve's
|
||||
/// shape.
|
||||
///
|
||||
/// ```no_run
|
||||
/// // Example using relative control points.
|
||||
/// exampleSketch = startSketchOn(XZ)
|
||||
/// |> startProfile(at = [0, 0])
|
||||
/// |> line(end = [0, 10])
|
||||
/// |> bezierCurve(
|
||||
/// control1 = [5, 0],
|
||||
/// control2 = [5, 10],
|
||||
/// end = [10, 10],
|
||||
/// )
|
||||
/// |> line(endAbsolute = [10, 0])
|
||||
/// |> close()
|
||||
///
|
||||
/// example = extrude(exampleSketch, length = 10)
|
||||
/// ```
|
||||
/// ```no_run
|
||||
/// // Example using absolute control points.
|
||||
/// startSketchOn(XY)
|
||||
/// |> startProfile(at = [300, 300])
|
||||
/// |> bezierCurve(control1Absolute = [600, 300], control2Absolute = [-300, -100], endAbsolute = [600, 600])
|
||||
/// |> close()
|
||||
/// |> extrude(length = 10)
|
||||
/// ```
|
||||
#[stdlib {
|
||||
name = "bezierCurve",
|
||||
unlabeled_first = true,
|
||||
args = {
|
||||
sketch = { docs = "Which sketch should this path be added to?"},
|
||||
control1 = { docs = "First control point for the cubic" },
|
||||
control2 = { docs = "Second control point for the cubic" },
|
||||
end = { docs = "How far away (along the X and Y axes) should this line go?" },
|
||||
control1_absolute = { docs = "First control point for the cubic. Absolute point." },
|
||||
control2_absolute = { docs = "Second control point for the cubic. Absolute point." },
|
||||
end_absolute = { docs = "Coordinate on the plane at which this line should end." },
|
||||
tag = { docs = "Create a new tag which refers to this line"},
|
||||
},
|
||||
tags = ["sketch"]
|
||||
}]
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
async fn inner_bezier_curve(
|
||||
sketch: Sketch,
|
||||
@ -2364,47 +1704,6 @@ pub async fn subtract_2d(exec_state: &mut ExecState, args: Args) -> Result<KclVa
|
||||
})
|
||||
}
|
||||
|
||||
/// Use a 2-dimensional sketch to cut a hole in another 2-dimensional sketch.
|
||||
///
|
||||
/// ```no_run
|
||||
/// exampleSketch = startSketchOn(XY)
|
||||
/// |> startProfile(at = [0, 0])
|
||||
/// |> line(end = [0, 5])
|
||||
/// |> line(end = [5, 0])
|
||||
/// |> line(end = [0, -5])
|
||||
/// |> close()
|
||||
/// |> subtract2d(tool =circle( center = [1, 1], radius = .25 ))
|
||||
/// |> subtract2d(tool =circle( center = [1, 4], radius = .25 ))
|
||||
///
|
||||
/// example = extrude(exampleSketch, length = 1)
|
||||
/// ```
|
||||
///
|
||||
/// ```no_run
|
||||
/// fn squareHoleSketch() {
|
||||
/// squareSketch = startSketchOn(-XZ)
|
||||
/// |> startProfile(at = [-1, -1])
|
||||
/// |> line(end = [2, 0])
|
||||
/// |> line(end = [0, 2])
|
||||
/// |> line(end = [-2, 0])
|
||||
/// |> close()
|
||||
/// return squareSketch
|
||||
/// }
|
||||
///
|
||||
/// exampleSketch = startSketchOn(-XZ)
|
||||
/// |> circle( center = [0, 0], radius = 3 )
|
||||
/// |> subtract2d(tool = squareHoleSketch())
|
||||
/// example = extrude(exampleSketch, length = 1)
|
||||
/// ```
|
||||
#[stdlib {
|
||||
name = "subtract2d",
|
||||
feature_tree_operation = true,
|
||||
unlabeled_first = true,
|
||||
args = {
|
||||
sketch = { docs = "Which sketch should this path be added to?" },
|
||||
tool = { docs = "The shape(s) which should be cut out of the sketch." },
|
||||
},
|
||||
tags = ["sketch"]
|
||||
}]
|
||||
async fn inner_subtract_2d(
|
||||
sketch: Sketch,
|
||||
tool: Vec<Sketch>,
|
||||
|
Reference in New Issue
Block a user