Move sketch functions to KCL; remove Rust decl dead code (#7335)

Signed-off-by: Nick Cameron <nrc@ncameron.org>
This commit is contained in:
Nick Cameron
2025-06-05 07:41:01 +12:00
committed by GitHub
parent 5ceb92d117
commit 5235a731ba
104 changed files with 1647 additions and 143019 deletions

View File

@ -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")]

View File

@ -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

View File

@ -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 {

View File

@ -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);

View File

@ -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,
};

View File

@ -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()

View File

@ -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
}

View File

@ -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)?;

View File

@ -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!(),

View File

@ -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")]

View File

@ -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)

View File

@ -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) {

View File

@ -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;

View File

@ -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>,