Loft uses kw arguments (#4757)

Part of #4600
This commit is contained in:
Adam Chalmers
2024-12-13 13:07:52 -06:00
committed by GitHub
parent 10cc5bce59
commit 05ed2a3367
27 changed files with 4123 additions and 168 deletions

File diff suppressed because one or more lines are too long

View File

@ -21,8 +21,8 @@ rem(num: i64, divisor: i64) -> i64
| Name | Type | Description | Required | | Name | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `num` | `i64` | | Yes | | `num` | `i64` | The number which will be divided by `divisor`. | Yes |
| `divisor` | `i64` | | Yes | | `divisor` | `i64` | The number which will divide `num`. | Yes |
### Returns ### Returns

File diff suppressed because it is too large Load Diff

View File

@ -121,9 +121,9 @@ dependencies = [
[[package]] [[package]]
name = "anyhow" name = "anyhow"
version = "1.0.93" version = "1.0.94"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c95c10ba0b00a02636238b814946408b1322d5ac4760326e6fb8ec956d85775" checksum = "c1fd03a028ef38ba2276dce7e33fcd6369c158a1bca17946c4b1b701891c1ff7"
dependencies = [ dependencies = [
"backtrace", "backtrace",
] ]
@ -401,9 +401,9 @@ dependencies = [
[[package]] [[package]]
name = "chrono" name = "chrono"
version = "0.4.38" version = "0.4.39"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" checksum = "7e36cc9d416881d2e24f9a963be5fb1cd90966419ac844274161d10488b3e825"
dependencies = [ dependencies = [
"android-tzdata", "android-tzdata",
"iana-time-zone", "iana-time-zone",
@ -1112,7 +1112,7 @@ dependencies = [
"fnv", "fnv",
"futures-core", "futures-core",
"futures-sink", "futures-sink",
"http 1.1.0", "http 1.2.0",
"indexmap 2.7.0", "indexmap 2.7.0",
"slab", "slab",
"tokio", "tokio",
@ -1215,9 +1215,9 @@ dependencies = [
[[package]] [[package]]
name = "http" name = "http"
version = "1.1.0" version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" checksum = "f16ca2af56261c99fba8bac40a10251ce8188205a4c448fbb745a2e4daa76fea"
dependencies = [ dependencies = [
"bytes", "bytes",
"fnv", "fnv",
@ -1242,7 +1242,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184"
dependencies = [ dependencies = [
"bytes", "bytes",
"http 1.1.0", "http 1.2.0",
] ]
[[package]] [[package]]
@ -1253,7 +1253,7 @@ checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f"
dependencies = [ dependencies = [
"bytes", "bytes",
"futures-util", "futures-util",
"http 1.1.0", "http 1.2.0",
"http-body 1.0.1", "http-body 1.0.1",
"pin-project-lite", "pin-project-lite",
] ]
@ -1303,7 +1303,7 @@ dependencies = [
"futures-channel", "futures-channel",
"futures-util", "futures-util",
"h2", "h2",
"http 1.1.0", "http 1.2.0",
"http-body 1.0.1", "http-body 1.0.1",
"httparse", "httparse",
"itoa", "itoa",
@ -1320,7 +1320,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08afdbb5c31130e3034af566421053ab03787c640246a446327f550d11bcb333" checksum = "08afdbb5c31130e3034af566421053ab03787c640246a446327f550d11bcb333"
dependencies = [ dependencies = [
"futures-util", "futures-util",
"http 1.1.0", "http 1.2.0",
"hyper 1.4.1", "hyper 1.4.1",
"hyper-util", "hyper-util",
"rustls", "rustls",
@ -1340,7 +1340,7 @@ dependencies = [
"bytes", "bytes",
"futures-channel", "futures-channel",
"futures-util", "futures-util",
"http 1.1.0", "http 1.2.0",
"http-body 1.0.1", "http-body 1.0.1",
"hyper 1.4.1", "hyper 1.4.1",
"pin-project-lite", "pin-project-lite",
@ -1706,7 +1706,7 @@ dependencies = [
"git_rev", "git_rev",
"gltf-json", "gltf-json",
"handlebars", "handlebars",
"http 1.1.0", "http 1.2.0",
"iai", "iai",
"image", "image",
"indexmap 2.7.0", "indexmap 2.7.0",
@ -1793,7 +1793,7 @@ dependencies = [
"data-encoding", "data-encoding",
"format_serde_error", "format_serde_error",
"futures", "futures",
"http 1.1.0", "http 1.2.0",
"itertools 0.13.0", "itertools 0.13.0",
"log", "log",
"mime_guess", "mime_guess",
@ -1819,9 +1819,9 @@ dependencies = [
[[package]] [[package]]
name = "kittycad-modeling-cmds" name = "kittycad-modeling-cmds"
version = "0.2.77" version = "0.2.79"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b77259b37acafa360d98af27431ac394bc8899eeed7037513832ddbee856811" checksum = "10a9cab4476455be70ea57643c31444068b056d091bd348cab6044c0d8ad7fcc"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"chrono", "chrono",
@ -1829,7 +1829,7 @@ dependencies = [
"enum-iterator", "enum-iterator",
"enum-iterator-derive", "enum-iterator-derive",
"euler", "euler",
"http 1.1.0", "http 1.2.0",
"kittycad-modeling-cmds-macros", "kittycad-modeling-cmds-macros",
"kittycad-unit-conversion-derive", "kittycad-unit-conversion-derive",
"measurements", "measurements",
@ -2863,7 +2863,7 @@ dependencies = [
"futures-core", "futures-core",
"futures-util", "futures-util",
"h2", "h2",
"http 1.1.0", "http 1.2.0",
"http-body 1.0.1", "http-body 1.0.1",
"http-body-util", "http-body-util",
"hyper 1.4.1", "hyper 1.4.1",
@ -2905,7 +2905,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f67ad7fdf5c0a015763fcd164bee294b13fb7b6f89f1b55961d40f00c3e32d6b" checksum = "f67ad7fdf5c0a015763fcd164bee294b13fb7b6f89f1b55961d40f00c3e32d6b"
dependencies = [ dependencies = [
"async-trait", "async-trait",
"http 1.1.0", "http 1.2.0",
"reqwest", "reqwest",
"reqwest-middleware", "reqwest-middleware",
] ]
@ -2918,7 +2918,7 @@ checksum = "d1ccd3b55e711f91a9885a2fa6fbbb2e39db1776420b062efc058c6410f7e5e3"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"async-trait", "async-trait",
"http 1.1.0", "http 1.2.0",
"reqwest", "reqwest",
"serde", "serde",
"thiserror 1.0.68", "thiserror 1.0.68",
@ -2935,7 +2935,7 @@ dependencies = [
"async-trait", "async-trait",
"futures", "futures",
"getrandom", "getrandom",
"http 1.1.0", "http 1.2.0",
"hyper 1.4.1", "hyper 1.4.1",
"parking_lot 0.11.2", "parking_lot 0.11.2",
"reqwest", "reqwest",
@ -2956,7 +2956,7 @@ dependencies = [
"anyhow", "anyhow",
"async-trait", "async-trait",
"getrandom", "getrandom",
"http 1.1.0", "http 1.2.0",
"matchit", "matchit",
"opentelemetry", "opentelemetry",
"reqwest", "reqwest",
@ -3200,9 +3200,9 @@ checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b"
[[package]] [[package]]
name = "serde" name = "serde"
version = "1.0.215" version = "1.0.216"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f" checksum = "0b9781016e935a97e8beecf0c933758c97a5520d32930e460142b4cd80c6338e"
dependencies = [ dependencies = [
"serde_derive", "serde_derive",
] ]
@ -3218,9 +3218,9 @@ dependencies = [
[[package]] [[package]]
name = "serde_derive" name = "serde_derive"
version = "1.0.215" version = "1.0.216"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" checksum = "46f859dbbf73865c6627ed570e78961cd3ac92407a2d117204c49232485da55e"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -4028,7 +4028,7 @@ dependencies = [
"byteorder", "byteorder",
"bytes", "bytes",
"data-encoding", "data-encoding",
"http 1.1.0", "http 1.2.0",
"httparse", "httparse",
"log", "log",
"rand 0.8.5", "rand 0.8.5",

View File

@ -76,7 +76,7 @@ members = [
[workspace.dependencies] [workspace.dependencies]
http = "1" http = "1"
kittycad = { version = "0.3.28", default-features = false, features = ["js", "requests"] } kittycad = { version = "0.3.28", default-features = false, features = ["js", "requests"] }
kittycad-modeling-cmds = { version = "0.2.77", features = ["websocket"] } kittycad-modeling-cmds = { version = "0.2.79", features = ["websocket"] }
[workspace.lints.clippy] [workspace.lints.clippy]
assertions_on_result_states = "warn" assertions_on_result_states = "warn"

View File

@ -6,6 +6,8 @@
mod tests; mod tests;
mod unbox; mod unbox;
use std::collections::HashMap;
use convert_case::Casing; use convert_case::Casing;
use inflector::Inflector; use inflector::Inflector;
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
@ -47,6 +49,10 @@ struct StdlibMetadata {
/// If false, all arguments require labels. /// If false, all arguments require labels.
#[serde(default)] #[serde(default)]
unlabeled_first: bool, unlabeled_first: bool,
/// Key = argument name, value = argument doc.
#[serde(default)]
arg_docs: HashMap<String, String>,
} }
#[proc_macro_attribute] #[proc_macro_attribute]
@ -282,6 +288,17 @@ fn do_stdlib_inner(
let ty_string = rust_type_to_openapi_type(&ty_string); let ty_string = rust_type_to_openapi_type(&ty_string);
let required = !ty_ident.to_string().starts_with("Option <"); let required = !ty_ident.to_string().starts_with("Option <");
let description = if let Some(s) = metadata.arg_docs.get(&arg_name) {
quote! { #s }
} else if metadata.keywords && ty_string != "Args" && ty_string != "ExecState" {
errors.push(Error::new_spanned(
&arg,
"Argument was not documented in the arg_docs block",
));
continue;
} else {
quote! { String::new() }
};
let label_required = !(i == 0 && metadata.unlabeled_first); let label_required = !(i == 0 && metadata.unlabeled_first);
if ty_string != "ExecState" && ty_string != "Args" { if ty_string != "ExecState" && ty_string != "Args" {
let schema = quote! { let schema = quote! {
@ -294,6 +311,7 @@ fn do_stdlib_inner(
schema: #schema, schema: #schema,
required: #required, required: #required,
label_required: #label_required, label_required: #label_required,
description: #description.to_string(),
} }
}); });
} }
@ -355,6 +373,7 @@ fn do_stdlib_inner(
schema, schema,
required: true, required: true,
label_required: true, label_required: true,
description: String::new(),
}) })
} }
} else { } else {

View File

@ -116,6 +116,9 @@ fn test_stdlib_line_to() {
let (item, errors) = do_stdlib( let (item, errors) = do_stdlib(
quote! { quote! {
name = "lineTo", name = "lineTo",
arg_docs = {
sketch = "the sketch you're adding the line to"
}
}, },
quote! { quote! {
/// This is some function. /// This is some function.

View File

@ -91,6 +91,7 @@ impl crate::docs::StdLibFn for SomeFn {
schema: generator.root_schema_for::<Foo>(), schema: generator.root_schema_for::<Foo>(),
required: true, required: true,
label_required: true, label_required: true,
description: String::new().to_string(),
}] }]
} }
@ -105,6 +106,7 @@ impl crate::docs::StdLibFn for SomeFn {
schema, schema,
required: true, required: true,
label_required: true, label_required: true,
description: String::new(),
}) })
} }

View File

@ -91,6 +91,7 @@ impl crate::docs::StdLibFn for SomeFn {
schema: generator.root_schema_for::<str>(), schema: generator.root_schema_for::<str>(),
required: true, required: true,
label_required: true, label_required: true,
description: String::new().to_string(),
}] }]
} }
@ -105,6 +106,7 @@ impl crate::docs::StdLibFn for SomeFn {
schema, schema,
required: true, required: true,
label_required: true, label_required: true,
description: String::new(),
}) })
} }

View File

@ -129,6 +129,7 @@ impl crate::docs::StdLibFn for Show {
schema: generator.root_schema_for::<[f64; 2usize]>(), schema: generator.root_schema_for::<[f64; 2usize]>(),
required: true, required: true,
label_required: true, label_required: true,
description: String::new().to_string(),
}] }]
} }
@ -143,6 +144,7 @@ impl crate::docs::StdLibFn for Show {
schema, schema,
required: true, required: true,
label_required: true, label_required: true,
description: String::new(),
}) })
} }

View File

@ -92,6 +92,7 @@ impl crate::docs::StdLibFn for Show {
schema: generator.root_schema_for::<f64>(), schema: generator.root_schema_for::<f64>(),
required: true, required: true,
label_required: true, label_required: true,
description: String::new().to_string(),
}] }]
} }
@ -106,6 +107,7 @@ impl crate::docs::StdLibFn for Show {
schema, schema,
required: true, required: true,
label_required: true, label_required: true,
description: String::new(),
}) })
} }

View File

@ -130,6 +130,7 @@ impl crate::docs::StdLibFn for MyFunc {
schema: generator.root_schema_for::<Option<kittycad::types::InputFormat>>(), schema: generator.root_schema_for::<Option<kittycad::types::InputFormat>>(),
required: false, required: false,
label_required: true, label_required: true,
description: String::new().to_string(),
}] }]
} }
@ -144,6 +145,7 @@ impl crate::docs::StdLibFn for MyFunc {
schema, schema,
required: true, required: true,
label_required: true, label_required: true,
description: String::new(),
}) })
} }

View File

@ -131,6 +131,7 @@ impl crate::docs::StdLibFn for LineTo {
schema: generator.root_schema_for::<LineToData>(), schema: generator.root_schema_for::<LineToData>(),
required: true, required: true,
label_required: true, label_required: true,
description: String::new().to_string(),
}, },
crate::docs::StdLibFnArg { crate::docs::StdLibFnArg {
name: "sketch".to_string(), name: "sketch".to_string(),
@ -138,6 +139,7 @@ impl crate::docs::StdLibFn for LineTo {
schema: generator.root_schema_for::<Sketch>(), schema: generator.root_schema_for::<Sketch>(),
required: true, required: true,
label_required: true, label_required: true,
description: "the sketch you're adding the line to".to_string(),
}, },
] ]
} }
@ -153,6 +155,7 @@ impl crate::docs::StdLibFn for LineTo {
schema, schema,
required: true, required: true,
label_required: true, label_required: true,
description: String::new(),
}) })
} }

View File

@ -129,6 +129,7 @@ impl crate::docs::StdLibFn for Min {
schema: generator.root_schema_for::<Vec<f64>>(), schema: generator.root_schema_for::<Vec<f64>>(),
required: true, required: true,
label_required: true, label_required: true,
description: String::new().to_string(),
}] }]
} }
@ -143,6 +144,7 @@ impl crate::docs::StdLibFn for Min {
schema, schema,
required: true, required: true,
label_required: true, label_required: true,
description: String::new(),
}) })
} }

View File

@ -92,6 +92,7 @@ impl crate::docs::StdLibFn for Show {
schema: generator.root_schema_for::<Option<f64>>(), schema: generator.root_schema_for::<Option<f64>>(),
required: false, required: false,
label_required: true, label_required: true,
description: String::new().to_string(),
}] }]
} }
@ -106,6 +107,7 @@ impl crate::docs::StdLibFn for Show {
schema, schema,
required: true, required: true,
label_required: true, label_required: true,
description: String::new(),
}) })
} }

View File

@ -92,6 +92,7 @@ impl crate::docs::StdLibFn for Import {
schema: generator.root_schema_for::<Option<kittycad::types::InputFormat>>(), schema: generator.root_schema_for::<Option<kittycad::types::InputFormat>>(),
required: false, required: false,
label_required: true, label_required: true,
description: String::new().to_string(),
}] }]
} }
@ -106,6 +107,7 @@ impl crate::docs::StdLibFn for Import {
schema, schema,
required: true, required: true,
label_required: true, label_required: true,
description: String::new(),
}) })
} }

View File

@ -92,6 +92,7 @@ impl crate::docs::StdLibFn for Import {
schema: generator.root_schema_for::<Option<kittycad::types::InputFormat>>(), schema: generator.root_schema_for::<Option<kittycad::types::InputFormat>>(),
required: false, required: false,
label_required: true, label_required: true,
description: String::new().to_string(),
}] }]
} }
@ -106,6 +107,7 @@ impl crate::docs::StdLibFn for Import {
schema, schema,
required: true, required: true,
label_required: true, label_required: true,
description: String::new(),
}) })
} }

View File

@ -92,6 +92,7 @@ impl crate::docs::StdLibFn for Import {
schema: generator.root_schema_for::<Option<kittycad::types::InputFormat>>(), schema: generator.root_schema_for::<Option<kittycad::types::InputFormat>>(),
required: false, required: false,
label_required: true, label_required: true,
description: String::new().to_string(),
}] }]
} }
@ -106,6 +107,7 @@ impl crate::docs::StdLibFn for Import {
schema, schema,
required: true, required: true,
label_required: true, label_required: true,
description: String::new(),
}) })
} }

View File

@ -92,6 +92,7 @@ impl crate::docs::StdLibFn for Show {
schema: generator.root_schema_for::<Vec<f64>>(), schema: generator.root_schema_for::<Vec<f64>>(),
required: true, required: true,
label_required: true, label_required: true,
description: String::new().to_string(),
}] }]
} }
@ -106,6 +107,7 @@ impl crate::docs::StdLibFn for Show {
schema, schema,
required: true, required: true,
label_required: true, label_required: true,
description: String::new(),
}) })
} }

View File

@ -99,6 +99,7 @@ impl crate::docs::StdLibFn for SomeFunction {
schema, schema,
required: true, required: true,
label_required: true, label_required: true,
description: String::new(),
}) })
} }

View File

@ -189,7 +189,11 @@ impl EngineConnection {
uuid_to_cpp(path_id) uuid_to_cpp(path_id)
) )
} }
kcmc::ModelingCmd::Extrude(kcmc::Extrude { distance, target }) => { kcmc::ModelingCmd::Extrude(kcmc::Extrude {
distance,
target,
faces: _, // Engine team: start using this once the frontend and engine both use it.
}) => {
format!( format!(
r#" r#"
scene->getSceneObject(Utils::UUID("{target}"))->extrudeToSolid3D({} * scaleFactor, true); scene->getSceneObject(Utils::UUID("{target}"))->extrudeToSolid3D({} * scaleFactor, true);

View File

@ -59,6 +59,12 @@ pub struct StdLibFnArg {
pub schema: schemars::schema::RootSchema, pub schema: schemars::schema::RootSchema,
/// If the argument is required. /// If the argument is required.
pub required: bool, pub required: bool,
/// Additional information that could be used instead of the type's description.
/// This is helpful if the type is really basic, like "u32" -- that won't tell the user much about
/// how this argument is meant to be used.
/// Empty string means this has no docs.
#[serde(default, skip_serializing_if = "String::is_empty")]
pub description: String,
/// Even in functions that use keyword arguments, not every parameter requires a label (most do though). /// Even in functions that use keyword arguments, not every parameter requires a label (most do though).
/// Some functions allow one unlabeled parameter, which has to be first in the /// Some functions allow one unlabeled parameter, which has to be first in the
/// argument list. /// argument list.
@ -106,6 +112,11 @@ impl StdLibFnArg {
} }
pub fn description(&self) -> Option<String> { pub fn description(&self) -> Option<String> {
// Check if we explicitly gave this stdlib arg a description.
if !self.description.is_empty() {
return Some(self.description.clone());
}
// If not, then try to get something meaningful from the schema.
get_description_string_from_schema(&self.schema.clone()) get_description_string_from_schema(&self.schema.clone())
} }
} }

View File

@ -2539,6 +2539,7 @@ fn fn_call_kw(i: &mut TokenSlice) -> PResult<Node<CallExpressionKw>> {
let initial_unlabeled_arg = opt((expression, comma, opt(whitespace)).map(|(arg, _, _)| arg)).parse_next(i)?; let initial_unlabeled_arg = opt((expression, comma, opt(whitespace)).map(|(arg, _, _)| arg)).parse_next(i)?;
let args = labeled_arguments(i)?; let args = labeled_arguments(i)?;
ignore_whitespace(i); ignore_whitespace(i);
opt(comma_sep).parse_next(i)?;
let end = close_paren.parse_next(i)?.end; let end = close_paren.parse_next(i)?.end;
Ok(Node { Ok(Node {

View File

@ -133,12 +133,16 @@ impl Args {
where where
T: FromKclValue<'a>, T: FromKclValue<'a>,
{ {
let Some(ref arg) = self.kw_args.unlabeled else { let arg = self
return Err(KclError::Semantic(KclErrorDetails { .kw_args
.unlabeled
.as_ref()
.or(self.args.first())
.ok_or(KclError::Semantic(KclErrorDetails {
source_ranges: vec![self.source_range], source_ranges: vec![self.source_range],
message: format!("This function requires a value for the special unlabeled first parameter, '{label}'"), message: format!("This function requires a value for the special unlabeled first parameter, '{label}'"),
})); }))?;
};
T::from_kcl_val(&arg.value).ok_or_else(|| { T::from_kcl_val(&arg.value).ok_or_else(|| {
KclError::Semantic(KclErrorDetails { KclError::Semantic(KclErrorDetails {
source_ranges: arg.source_ranges(), source_ranges: arg.source_ranges(),
@ -411,13 +415,6 @@ impl Args {
FromArgs::from_args(self, 0) FromArgs::from_args(self, 0)
} }
pub(crate) fn get_sketches_and_data<'a, T>(&'a self) -> Result<(Vec<Sketch>, Option<T>), KclError>
where
T: FromArgs<'a> + serde::de::DeserializeOwned + FromKclValue<'a> + Sized,
{
FromArgs::from_args(self, 0)
}
pub(crate) fn get_data_and_optional_tag<'a, T>(&'a self) -> Result<(T, Option<FaceTag>), KclError> pub(crate) fn get_data_and_optional_tag<'a, T>(&'a self) -> Result<(T, Option<FaceTag>), KclError>
where where
T: serde::de::DeserializeOwned + FromKclValue<'a> + Sized, T: serde::de::DeserializeOwned + FromKclValue<'a> + Sized,
@ -865,22 +862,6 @@ impl<'a> FromKclValue<'a> for crate::std::polar::PolarCoordsData {
} }
} }
impl<'a> FromKclValue<'a> for crate::std::loft::LoftData {
fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
let obj = arg.as_object()?;
let_field_of!(obj, v_degree?);
let_field_of!(obj, bez_approximate_rational?);
let_field_of!(obj, base_curve_index?);
let_field_of!(obj, tolerance?);
Some(Self {
v_degree,
bez_approximate_rational,
base_curve_index,
tolerance,
})
}
}
impl<'a> FromKclValue<'a> for crate::std::planes::StandardPlane { impl<'a> FromKclValue<'a> for crate::std::planes::StandardPlane {
fn from_kcl_val(arg: &'a KclValue) -> Option<Self> { fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
let s = arg.as_str()?; let s = arg.as_str()?;

View File

@ -113,6 +113,7 @@ async fn inner_extrude(
ModelingCmd::from(mcmd::Extrude { ModelingCmd::from(mcmd::Extrude {
target: sketch.id.into(), target: sketch.id.into(),
distance: LengthUnit(length), distance: LengthUnit(length),
faces: Default::default(),
}), }),
) )
.await?; .await?;

View File

@ -1,11 +1,11 @@
//! Standard library lofts. //! Standard library lofts.
use std::num::NonZeroU32;
use anyhow::Result; use anyhow::Result;
use derive_docs::stdlib; use derive_docs::stdlib;
use kcmc::{each_cmd as mcmd, length_unit::LengthUnit, ModelingCmd}; use kcmc::{each_cmd as mcmd, length_unit::LengthUnit, ModelingCmd};
use kittycad_modeling_cmds as kcmc; use kittycad_modeling_cmds as kcmc;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use crate::{ use crate::{
errors::{KclError, KclErrorDetails}, errors::{KclError, KclErrorDetails},
@ -15,45 +15,31 @@ use crate::{
const DEFAULT_V_DEGREE: u32 = 2; const DEFAULT_V_DEGREE: u32 = 2;
/// Data for a loft.
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)]
#[serde(rename_all = "camelCase")]
pub struct LoftData {
/// Degree of the interpolation. Must be greater than zero.
/// For example, use 2 for quadratic, or 3 for cubic interpolation in the V direction.
/// This defaults to 2, if not specified.
pub v_degree: Option<std::num::NonZeroU32>,
/// Attempt to approximate rational curves (such as arcs) using a bezier.
/// This will remove banding around interpolations between arcs and non-arcs. It may produce errors in other scenarios
/// Over time, this field won't be necessary.
#[serde(default)]
pub bez_approximate_rational: Option<bool>,
/// This can be set to override the automatically determined topological base curve, which is usually the first section encountered.
#[serde(default)]
pub base_curve_index: Option<u32>,
/// Tolerance for the loft operation.
#[serde(default)]
pub tolerance: Option<f64>,
}
impl Default for LoftData {
fn default() -> Self {
Self {
// This unwrap is safe because the default value is always greater than zero.
v_degree: Some(std::num::NonZeroU32::new(DEFAULT_V_DEGREE).unwrap()),
bez_approximate_rational: None,
base_curve_index: None,
tolerance: None,
}
}
}
/// Create a 3D surface or solid by interpolating between two or more sketches. /// Create a 3D surface or solid by interpolating between two or more sketches.
pub async fn loft(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> { pub async fn loft(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let (sketches, data): (Vec<Sketch>, Option<LoftData>) = args.get_sketches_and_data()?; let sketches = args.get_unlabeled_kw_arg("sketches")?;
let v_degree: NonZeroU32 = args
.get_kw_arg_opt("vDegree")
.unwrap_or(NonZeroU32::new(DEFAULT_V_DEGREE).unwrap());
// Attempt to approximate rational curves (such as arcs) using a bezier.
// This will remove banding around interpolations between arcs and non-arcs. It may produce errors in other scenarios
// Over time, this field won't be necessary.
let bez_approximate_rational = args.get_kw_arg_opt("bezApproximateRational").unwrap_or(false);
// This can be set to override the automatically determined topological base curve, which is usually the first section encountered.
let base_curve_index: Option<u32> = args.get_kw_arg_opt("baseCurveIndex");
// Tolerance for the loft operation.
let tolerance: Option<f64> = args.get_kw_arg_opt("tolerance");
let solid = inner_loft(sketches, data, exec_state, args).await?; let solid = inner_loft(
sketches,
v_degree,
bez_approximate_rational,
base_curve_index,
tolerance,
exec_state,
args,
)
.await?;
Ok(KclValue::Solid(solid)) Ok(KclValue::Solid(solid))
} }
@ -116,28 +102,31 @@ pub async fn loft(exec_state: &mut ExecState, args: Args) -> Result<KclValue, Kc
/// circleSketch1 = startSketchOn(offsetPlane('XY', 150)) /// circleSketch1 = startSketchOn(offsetPlane('XY', 150))
/// |> circle({ center = [0, 100], radius = 20 }, %) /// |> circle({ center = [0, 100], radius = 20 }, %)
/// ///
/// loft([squareSketch, circleSketch0, circleSketch1], { /// loft([squareSketch, circleSketch0, circleSketch1],
/// // This can be set to override the automatically determined
/// // topological base curve, which is usually the first section encountered.
/// baseCurveIndex = 0, /// baseCurveIndex = 0,
/// // Attempt to approximate rational curves (such as arcs) using a bezier.
/// // This will remove banding around interpolations between arcs and non-arcs.
/// // It may produce errors in other scenarios Over time, this field won't be necessary.
/// bezApproximateRational = false, /// bezApproximateRational = false,
/// // Tolerance for the loft operation.
/// tolerance = 0.000001, /// tolerance = 0.000001,
/// // Degree of the interpolation. Must be greater than zero.
/// // For example, use 2 for quadratic, or 3 for cubic interpolation in
/// // the V direction. This defaults to 2, if not specified.
/// vDegree = 2, /// vDegree = 2,
/// }) /// )
/// ``` /// ```
#[stdlib { #[stdlib {
name = "loft", name = "loft",
keywords = true,
unlabeled_first = true,
arg_docs = {
sketches = "Which sketches to loft. Must include at least 2 sketches.",
v_degree = "Degree of the interpolation. Must be greater than zero. For example, use 2 for quadratic, or 3 for cubic interpolation in the V direction. This defaults to 2, if not specified.",
bez_approximate_rational = "Attempt to approximate rational curves (such as arcs) using a bezier. This will remove banding around interpolations between arcs and non-arcs. It may produce errors in other scenarios Over time, this field won't be necessary.",
base_curve_index = "This can be set to override the automatically determined topological base curve, which is usually the first section encountered.",
tolerance = "Tolerance for the loft operation.",
}
}] }]
async fn inner_loft( async fn inner_loft(
sketches: Vec<Sketch>, sketches: Vec<Sketch>,
data: Option<LoftData>, v_degree: NonZeroU32,
bez_approximate_rational: bool,
base_curve_index: Option<u32>,
tolerance: Option<f64>,
exec_state: &mut ExecState, exec_state: &mut ExecState,
args: Args, args: Args,
) -> Result<Box<Solid>, KclError> { ) -> Result<Box<Solid>, KclError> {
@ -152,20 +141,15 @@ async fn inner_loft(
})); }));
} }
// Get the loft data.
let data = data.unwrap_or_default();
let id = exec_state.id_generator.next_uuid(); let id = exec_state.id_generator.next_uuid();
args.batch_modeling_cmd( args.batch_modeling_cmd(
id, id,
ModelingCmd::from(mcmd::Loft { ModelingCmd::from(mcmd::Loft {
section_ids: sketches.iter().map(|group| group.id).collect(), section_ids: sketches.iter().map(|group| group.id).collect(),
base_curve_index: data.base_curve_index, base_curve_index,
bez_approximate_rational: data.bez_approximate_rational.unwrap_or(false), bez_approximate_rational,
tolerance: LengthUnit(data.tolerance.unwrap_or(default_tolerance(&args.ctx.settings.units))), tolerance: LengthUnit(tolerance.unwrap_or(default_tolerance(&args.ctx.settings.units))),
v_degree: data v_degree,
.v_degree
.unwrap_or_else(|| std::num::NonZeroU32::new(DEFAULT_V_DEGREE).unwrap()),
}), }),
) )
.await?; .await?;

View File

@ -34,6 +34,10 @@ pub async fn rem(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, Kc
tags = ["math"], tags = ["math"],
keywords = true, keywords = true,
unlabeled_first = true, unlabeled_first = true,
arg_docs = {
num = "The number which will be divided by `divisor`.",
divisor = "The number which will divide `num`.",
}
}] }]
fn inner_rem(num: i64, divisor: i64) -> Result<i64, KclError> { fn inner_rem(num: i64, divisor: i64) -> Result<i64, KclError> {
Ok(num % divisor) Ok(num % divisor)