KCL: Appearance stdlib fn is now kwargs (#5308)

This commit is contained in:
Adam Chalmers
2025-02-07 16:36:51 -06:00
committed by GitHub
parent 67e60cb832
commit 9a3ac64603
11 changed files with 4521 additions and 174 deletions

View File

@ -32,7 +32,7 @@ child_process.spawnSync('git', [
'clone',
'--single-branch',
'--branch',
'achalmers/kw-shell',
'achalmers/kw-appearance',
URL_GIT_KCL_SAMPLES,
DIR_KCL_SAMPLES,
])

View File

@ -118,7 +118,7 @@ impl StdLibFnArg {
} else if self.type_ == "KclValue" && self.required {
return Ok(Some((index, format!("{label}${{{}:{}}}", index, "3"))));
}
self.get_autocomplete_snippet_from_schema(&self.schema.schema.clone().into(), index, in_keyword_fn)
self.get_autocomplete_snippet_from_schema(&self.schema.schema.clone().into(), index, in_keyword_fn, &self.name)
.map(|maybe| maybe.map(|(index, snippet)| (index, format!("{label}{snippet}"))))
}
@ -136,6 +136,7 @@ impl StdLibFnArg {
schema: &schemars::schema::Schema,
index: usize,
in_keyword_fn: bool,
name: &str,
) -> Result<Option<(usize, String)>> {
match schema {
schemars::schema::Schema::Object(o) => {
@ -149,6 +150,10 @@ impl StdLibFnArg {
return Ok(Some((index, format!("${{{}:sketch{}}}", index, "000"))));
}
if name == "color" {
let snippet = format!("${{{}:\"#ff0000\"}}", index);
return Ok(Some((index, snippet)));
}
if let Some(serde_json::Value::Bool(nullable)) = o.extensions.get("nullable") {
if (!in_keyword_fn && *nullable) || (in_keyword_fn && !self.include_in_snippet) {
return Ok(None);
@ -192,13 +197,9 @@ impl StdLibFnArg {
continue;
}
if prop_name == "color" {
fn_docs.push_str(&format!("\t{} = ${{{}:\"#ff0000\"}},\n", prop_name, i));
i += 1;
continue;
}
if let Some((new_index, snippet)) = self.get_autocomplete_snippet_from_schema(prop, i, false)? {
if let Some((new_index, snippet)) =
self.get_autocomplete_snippet_from_schema(prop, i, false, name)?
{
fn_docs.push_str(&format!("\t{} = {},\n", prop_name, snippet));
i = new_index + 1;
}
@ -223,7 +224,8 @@ impl StdLibFnArg {
.get_autocomplete_snippet_from_schema(
items,
index + (v as usize),
in_keyword_fn
in_keyword_fn,
name
)
.unwrap()
.unwrap()
@ -238,7 +240,7 @@ impl StdLibFnArg {
index,
format!(
"[{}]",
self.get_autocomplete_snippet_from_schema(items, index, in_keyword_fn)?
self.get_autocomplete_snippet_from_schema(items, index, in_keyword_fn, name)?
.ok_or_else(|| anyhow::anyhow!("expected snippet"))?
.1
),
@ -250,7 +252,7 @@ impl StdLibFnArg {
index,
format!(
"[{}]",
self.get_autocomplete_snippet_from_schema(items, index, in_keyword_fn)?
self.get_autocomplete_snippet_from_schema(items, index, in_keyword_fn, name)?
.ok_or_else(|| anyhow::anyhow!("expected snippet"))?
.1
),
@ -293,7 +295,7 @@ impl StdLibFnArg {
return Ok(Some((index, parsed_enum_values[0].to_string())));
} else if let Some(item) = items.iter().next() {
if let Some((new_index, snippet)) =
self.get_autocomplete_snippet_from_schema(item, index, in_keyword_fn)?
self.get_autocomplete_snippet_from_schema(item, index, in_keyword_fn, name)?
{
i = new_index + 1;
fn_docs.push_str(&snippet);
@ -302,7 +304,7 @@ impl StdLibFnArg {
} else if let Some(items) = &subschemas.any_of {
if let Some(item) = items.iter().next() {
if let Some((new_index, snippet)) =
self.get_autocomplete_snippet_from_schema(item, index, in_keyword_fn)?
self.get_autocomplete_snippet_from_schema(item, index, in_keyword_fn, name)?
{
i = new_index + 1;
fn_docs.push_str(&snippet);
@ -1018,12 +1020,7 @@ mod tests {
let snippet = appearance_fn.to_autocomplete_snippet().unwrap();
assert_eq!(
snippet,
r#"appearance({
color = ${0:"#
.to_owned()
+ "\"#"
+ r#"ff0000"},
}, ${1:%})${}"#
r#"appearance(${0:%}, color = ${1:"#.to_owned() + "\"#" + r#"ff0000"})${}"#
);
}

View File

@ -24,7 +24,7 @@ lazy_static::lazy_static! {
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema, Validate)]
#[ts(export)]
#[serde(rename_all = "camelCase")]
pub struct AppearanceData {
struct AppearanceData {
/// Color of the new material, a hex string like "#ff0000".
#[schemars(regex(pattern = "#[0-9a-fA-F]{6}"))]
pub color: String,
@ -39,7 +39,16 @@ pub struct AppearanceData {
/// Set the appearance of a solid. This only works on solids, not sketches or individual paths.
pub async fn appearance(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let (data, solid_set): (AppearanceData, SolidSet) = args.get_data_and_solid_set()?;
let solid_set: SolidSet = args.get_unlabeled_kw_arg("solidSet")?;
let color: String = args.get_kw_arg("color")?;
let metalness: Option<f64> = args.get_kw_arg_opt("metalness")?;
let roughness: Option<f64> = args.get_kw_arg_opt("roughness")?;
let data = AppearanceData {
color,
metalness,
roughness,
};
// Validate the data.
data.validate().map_err(|err| {
@ -57,7 +66,7 @@ pub async fn appearance(_exec_state: &mut ExecState, args: Args) -> Result<KclVa
}));
}
let result = inner_appearance(data, solid_set, args).await?;
let result = inner_appearance(solid_set, data.color, data.metalness, data.roughness, args).await?;
Ok(result.into())
}
@ -74,7 +83,8 @@ pub async fn appearance(_exec_state: &mut ExecState, args: Args) -> Result<KclVa
/// |> close()
///
/// example = extrude(exampleSketch, length = 5)
/// |> appearance({color= '#ff0000', metalness= 50, roughness= 50}, %)
/// // There are other options besides 'color', but they're optional.
/// |> appearance(color='#ff0000')
/// ```
///
/// ```no_run
@ -82,11 +92,11 @@ pub async fn appearance(_exec_state: &mut ExecState, args: Args) -> Result<KclVa
/// sketch001 = startSketchOn('XY')
/// |> circle({ center = [15, 0], radius = 5 }, %)
/// |> revolve({ angle = 360, axis = 'y' }, %)
/// |> appearance({
/// |> appearance(
/// color = '#ff0000',
/// metalness = 90,
/// roughness = 90
/// }, %)
/// )
/// ```
///
/// ```no_run
@ -105,8 +115,8 @@ pub async fn appearance(_exec_state: &mut ExecState, args: Args) -> Result<KclVa
/// example1 = cube([20, 0])
/// example2 = cube([40, 0])
///
/// appearance({color= '#ff0000', metalness= 50, roughness= 50}, [example0, example1])
/// appearance({color= '#00ff00', metalness= 50, roughness= 50}, example2)
/// appearance([example0, example1], color='#ff0000', metalness=50, roughness=50)
/// appearance(example2, color='#00ff00', metalness=50, roughness=50)
/// ```
///
/// ```no_run
@ -125,11 +135,11 @@ pub async fn appearance(_exec_state: &mut ExecState, args: Args) -> Result<KclVa
/// faces = ['end'],
/// thickness = 0.25,
/// )
/// |> appearance({
/// |> appearance(
/// color = '#ff0000',
/// metalness = 90,
/// roughness = 90
/// }, %)
/// )
/// ```
///
/// ```no_run
@ -142,11 +152,11 @@ pub async fn appearance(_exec_state: &mut ExecState, args: Args) -> Result<KclVa
/// |> line(end = [-24, 0])
/// |> close()
/// |> extrude(length = 6)
/// |> appearance({
/// |> appearance(
/// color = '#ff0000',
/// metalness = 90,
/// roughness = 90
/// }, %)
/// )
///
/// shell(
/// firstSketch,
@ -166,11 +176,11 @@ pub async fn appearance(_exec_state: &mut ExecState, args: Args) -> Result<KclVa
/// |> close()
///
/// example = extrude(exampleSketch, length = 1)
/// |> appearance({
/// |> appearance(
/// color = '#ff0000',
/// metalness = 90,
/// roughness = 90
/// }, %)
/// )
/// |> patternLinear3d({
/// axis = [1, 0, 1],
/// instances = 7,
@ -194,11 +204,11 @@ pub async fn appearance(_exec_state: &mut ExecState, args: Args) -> Result<KclVa
/// instances = 7,
/// distance = 6
/// }, %)
/// |> appearance({
/// |> appearance(
/// color = '#ff0000',
/// metalness = 90,
/// roughness = 90
/// }, %)
/// )
/// ```
///
/// ```no_run
@ -217,11 +227,11 @@ pub async fn appearance(_exec_state: &mut ExecState, args: Args) -> Result<KclVa
/// }, %)
///
/// example = extrude(exampleSketch, length = 1)
/// |> appearance({
/// |> appearance(
/// color = '#ff0000',
/// metalness = 90,
/// roughness = 90
/// }, %)
/// )
/// ```
///
/// ```no_run
@ -255,23 +265,37 @@ pub async fn appearance(_exec_state: &mut ExecState, args: Args) -> Result<KclVa
/// }, %)
/// |> hole(pipeHole, %)
/// |> sweep(path = sweepPath)
/// |> appearance({
/// color: "#ff0000",
/// metalness: 50,
/// roughness: 50
/// }, %)
/// |> appearance(
/// color = "#ff0000",
/// metalness = 50,
/// roughness = 50
/// )
/// ```
#[stdlib {
name = "appearance",
keywords = true,
unlabeled_first = true,
args = {
solid_set = { docs = "The solid(s) whose appearance is being set" },
color = { docs = "Color of the new material, a hex string like '#ff0000'"},
metalness = { docs = "Metalness of the new material, a percentage like 95.7." },
roughness = { docs = "Roughness of the new material, a percentage like 95.7." },
}
}]
async fn inner_appearance(data: AppearanceData, solid_set: SolidSet, args: Args) -> Result<SolidSet, KclError> {
async fn inner_appearance(
solid_set: SolidSet,
color: String,
metalness: Option<f64>,
roughness: Option<f64>,
args: Args,
) -> Result<SolidSet, KclError> {
let solids: Vec<Box<Solid>> = solid_set.into();
for solid in &solids {
// Set the material properties.
let rgb = rgba_simple::RGB::<f32>::from_hex(&data.color).map_err(|err| {
let rgb = rgba_simple::RGB::<f32>::from_hex(&color).map_err(|err| {
KclError::Semantic(KclErrorDetails {
message: format!("Invalid hex color (`{}`): {}", data.color, err),
message: format!("Invalid hex color (`{color}`): {err}"),
source_ranges: vec![args.source_range],
})
})?;
@ -288,8 +312,8 @@ async fn inner_appearance(data: AppearanceData, solid_set: SolidSet, args: Args)
ModelingCmd::from(mcmd::ObjectSetMaterialParamsPbr {
object_id: solid.id,
color,
metalness: data.metalness.unwrap_or_default() as f32 / 100.0,
roughness: data.roughness.unwrap_or_default() as f32 / 100.0,
metalness: metalness.unwrap_or_default() as f32 / 100.0,
roughness: roughness.unwrap_or_default() as f32 / 100.0,
ambient_occlusion: 0.0,
}),
)

View File

@ -1042,20 +1042,6 @@ impl<'a> FromKclValue<'a> for super::fillet::FilletData {
}
}
impl<'a> FromKclValue<'a> for super::appearance::AppearanceData {
fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
let obj = arg.as_object()?;
let_field_of!(obj, color);
let_field_of!(obj, metalness?);
let_field_of!(obj, roughness?);
Some(Self {
color,
metalness,
roughness,
})
}
}
impl<'a> FromKclValue<'a> for super::helix::HelixRevolutionsData {
fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
let obj = arg.as_object()?;