Declare appearance function in KCL (#7220)
Move appearance to KCL Signed-off-by: Nick Cameron <nrc@ncameron.org>
This commit is contained in:
@ -463,7 +463,11 @@ impl ModData {
|
||||
}
|
||||
|
||||
pub fn find_by_name(&self, name: &str) -> Option<&DocData> {
|
||||
if let Some(result) = self.children.values().find(|dd| dd.name() == name) {
|
||||
if let Some(result) = self
|
||||
.children
|
||||
.values()
|
||||
.find(|dd| dd.name() == name && !matches!(dd, DocData::Mod(_)))
|
||||
{
|
||||
return Some(result);
|
||||
}
|
||||
|
||||
@ -824,7 +828,13 @@ impl ArgData {
|
||||
Some("[Edge; 1+]") => Some((index, format!(r#"{label}[${{{index}:tag_or_edge_fn}}]"#))),
|
||||
Some("Plane") => Some((index, format!(r#"{label}${{{}:XY}}"#, index))),
|
||||
|
||||
Some("string") => Some((index, format!(r#"{label}${{{}:"string"}}"#, index))),
|
||||
Some("string") => {
|
||||
if self.name == "color" {
|
||||
Some((index, format!(r#"{label}${{{}:"ff0000"}}"#, index)))
|
||||
} else {
|
||||
Some((index, format!(r#"{label}${{{}:"string"}}"#, index)))
|
||||
}
|
||||
}
|
||||
Some("bool") => Some((index, format!(r#"{label}${{{}:false}}"#, index))),
|
||||
_ => None,
|
||||
}
|
||||
|
@ -1074,12 +1074,12 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn get_autocomplete_snippet_appearance() {
|
||||
let appearance_fn: Box<dyn StdLibFn> = Box::new(crate::std::appearance::Appearance);
|
||||
let snippet = appearance_fn.to_autocomplete_snippet().unwrap();
|
||||
assert_eq!(
|
||||
snippet,
|
||||
r#"appearance(${0:%}, color = ${1:"#.to_owned() + "\"#" + r#"ff0000"})"#
|
||||
);
|
||||
let data = kcl_doc::walk_prelude();
|
||||
let DocData::Fn(helix_fn) = data.find_by_name("appearance").unwrap() else {
|
||||
panic!();
|
||||
};
|
||||
let snippet = helix_fn.to_autocomplete_snippet();
|
||||
assert_eq!(snippet, r#"appearance(color = ${0:"ff0000"})"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -160,8 +160,10 @@ impl ExecutorContext {
|
||||
.cloned();
|
||||
let ty_name = format!("{}{}", memory::TYPE_PREFIX, import_item.name.name);
|
||||
let mut ty = mem.get_from(&ty_name, env_ref, import_item.into(), 0).cloned();
|
||||
let mod_name = format!("{}{}", memory::MODULE_PREFIX, import_item.name.name);
|
||||
let mut mod_value = mem.get_from(&mod_name, env_ref, import_item.into(), 0).cloned();
|
||||
|
||||
if value.is_err() && ty.is_err() {
|
||||
if value.is_err() && ty.is_err() && mod_value.is_err() {
|
||||
return Err(KclError::UndefinedValue(KclErrorDetails::new(
|
||||
format!("{} is not defined in module", import_item.name.name),
|
||||
vec![SourceRange::from(&import_item.name)],
|
||||
@ -180,10 +182,22 @@ impl ExecutorContext {
|
||||
}
|
||||
|
||||
if ty.is_ok() && !module_exports.contains(&ty_name) {
|
||||
ty = Err(KclError::Semantic(KclErrorDetails::new(String::new(), vec![])));
|
||||
ty = Err(KclError::Semantic(KclErrorDetails::new(format!(
|
||||
"Cannot import \"{}\" from module because it is not exported. Add \"export\" before the definition to export it.",
|
||||
import_item.name.name
|
||||
),
|
||||
vec![SourceRange::from(&import_item.name)],)));
|
||||
}
|
||||
|
||||
if value.is_err() && ty.is_err() {
|
||||
if mod_value.is_ok() && !module_exports.contains(&mod_name) {
|
||||
mod_value = Err(KclError::Semantic(KclErrorDetails::new(format!(
|
||||
"Cannot import \"{}\" from module because it is not exported. Add \"export\" before the definition to export it.",
|
||||
import_item.name.name
|
||||
),
|
||||
vec![SourceRange::from(&import_item.name)],)));
|
||||
}
|
||||
|
||||
if value.is_err() && ty.is_err() && mod_value.is_err() {
|
||||
return value.map(Option::Some);
|
||||
}
|
||||
|
||||
@ -205,7 +219,6 @@ impl ExecutorContext {
|
||||
|
||||
if let Ok(ty) = ty {
|
||||
let ty_name = format!("{}{}", memory::TYPE_PREFIX, import_item.identifier());
|
||||
// Add the item to the current module.
|
||||
exec_state.mut_stack().add(
|
||||
ty_name.clone(),
|
||||
ty,
|
||||
@ -216,6 +229,19 @@ impl ExecutorContext {
|
||||
exec_state.mod_local.module_exports.push(ty_name);
|
||||
}
|
||||
}
|
||||
|
||||
if let Ok(mod_value) = mod_value {
|
||||
let mod_name = format!("{}{}", memory::MODULE_PREFIX, import_item.identifier());
|
||||
exec_state.mut_stack().add(
|
||||
mod_name.clone(),
|
||||
mod_value,
|
||||
SourceRange::from(&import_item.name),
|
||||
)?;
|
||||
|
||||
if let ItemVisibility::Export = import_stmt.visibility {
|
||||
exec_state.mod_local.module_exports.push(mod_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ImportSelector::Glob(_) => {
|
||||
@ -246,7 +272,11 @@ impl ExecutorContext {
|
||||
value: module_id,
|
||||
meta: vec![source_range.into()],
|
||||
};
|
||||
exec_state.mut_stack().add(name, item, source_range)?;
|
||||
exec_state.mut_stack().add(
|
||||
format!("{}{}", memory::MODULE_PREFIX, name),
|
||||
item,
|
||||
source_range,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
last_expr = None;
|
||||
@ -780,8 +810,14 @@ impl Node<Name> {
|
||||
)));
|
||||
}
|
||||
|
||||
let mod_name = format!("{}{}", memory::MODULE_PREFIX, self.name.name);
|
||||
|
||||
if self.path.is_empty() {
|
||||
return exec_state.stack().get(&self.name.name, self.into());
|
||||
let item_value = exec_state.stack().get(&self.name.name, self.into());
|
||||
if item_value.is_ok() {
|
||||
return item_value;
|
||||
}
|
||||
return exec_state.stack().get(&mod_name, self.into());
|
||||
}
|
||||
|
||||
let mut mem_spec: Option<(EnvironmentRef, Vec<String>)> = None;
|
||||
@ -800,7 +836,9 @@ impl Node<Name> {
|
||||
.memory
|
||||
.get_from(&p.name, env, p.as_source_range(), 0)?
|
||||
}
|
||||
None => exec_state.stack().get(&p.name, self.into())?,
|
||||
None => exec_state
|
||||
.stack()
|
||||
.get(&format!("{}{}", memory::MODULE_PREFIX, p.name), self.into())?,
|
||||
};
|
||||
|
||||
let KclValue::Module { value: module_id, .. } = value else {
|
||||
@ -820,17 +858,40 @@ impl Node<Name> {
|
||||
}
|
||||
|
||||
let (env, exports) = mem_spec.unwrap();
|
||||
if !exports.contains(&self.name.name) {
|
||||
return Err(KclError::Semantic(KclErrorDetails::new(
|
||||
format!("Item {} not found in module's exported items", self.name.name),
|
||||
self.name.as_source_ranges(),
|
||||
)));
|
||||
}
|
||||
|
||||
exec_state
|
||||
let item_exported = exports.contains(&self.name.name);
|
||||
let item_value = exec_state
|
||||
.stack()
|
||||
.memory
|
||||
.get_from(&self.name.name, env, self.name.as_source_range(), 0)
|
||||
.get_from(&self.name.name, env, self.name.as_source_range(), 0);
|
||||
|
||||
// Item is defined and exported.
|
||||
if item_exported && item_value.is_ok() {
|
||||
return item_value;
|
||||
}
|
||||
|
||||
let mod_exported = exports.contains(&mod_name);
|
||||
let mod_value = exec_state
|
||||
.stack()
|
||||
.memory
|
||||
.get_from(&mod_name, env, self.name.as_source_range(), 0);
|
||||
|
||||
// Module is defined and exported.
|
||||
if mod_exported && mod_value.is_ok() {
|
||||
return mod_value;
|
||||
}
|
||||
|
||||
// Neither item or module is defined.
|
||||
if item_value.is_err() && mod_value.is_err() {
|
||||
return item_value;
|
||||
}
|
||||
|
||||
// Either item or module is defined, but not exported.
|
||||
debug_assert!((item_value.is_ok() && !item_exported) || (mod_value.is_ok() && !mod_exported));
|
||||
Err(KclError::Semantic(KclErrorDetails::new(
|
||||
format!("Item {} not found in module's exported items", self.name.name),
|
||||
self.name.as_source_ranges(),
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -226,8 +226,9 @@ use crate::{
|
||||
|
||||
/// The distinguished name of the return value of a function.
|
||||
pub(crate) const RETURN_NAME: &str = "__return";
|
||||
/// Low-budget namespacing for types.
|
||||
/// Low-budget namespacing for types and modules.
|
||||
pub(crate) const TYPE_PREFIX: &str = "__ty_";
|
||||
pub(crate) const MODULE_PREFIX: &str = "__mod_";
|
||||
|
||||
/// KCL memory. There should be only one ProgramMemory for the interpretation of a program (
|
||||
/// including other modules). Multiple interpretation runs should have fresh instances.
|
||||
@ -364,8 +365,10 @@ impl ProgramMemory {
|
||||
};
|
||||
}
|
||||
|
||||
let name = var.trim_start_matches(TYPE_PREFIX).trim_start_matches(MODULE_PREFIX);
|
||||
|
||||
Err(KclError::UndefinedValue(KclErrorDetails::new(
|
||||
format!("`{}` is not defined", var),
|
||||
format!("`{name}` is not defined"),
|
||||
vec![source_range],
|
||||
)))
|
||||
}
|
||||
|
@ -194,7 +194,10 @@ async fn execute_test(test: &Test, render_to_png: bool, export_step: bool) {
|
||||
".**[].x[]" => rounded_redaction(3),
|
||||
".**[].y[]" => rounded_redaction(3),
|
||||
".**[].z[]" => rounded_redaction(3),
|
||||
".**.sourceRange" => Vec::new(),
|
||||
".**.x" => rounded_redaction(3),
|
||||
".**.y" => rounded_redaction(3),
|
||||
".**.z" => rounded_redaction(3),
|
||||
".**.sourceRange" => Vec::new(),
|
||||
})
|
||||
})
|
||||
}));
|
||||
|
@ -1,7 +1,6 @@
|
||||
//! Standard library appearance.
|
||||
|
||||
use anyhow::Result;
|
||||
use kcl_derive_docs::stdlib;
|
||||
use kcmc::{each_cmd as mcmd, ModelingCmd};
|
||||
use kittycad_modeling_cmds::{self as kcmc, shared::Color};
|
||||
use regex::Regex;
|
||||
@ -57,7 +56,7 @@ pub async fn appearance(exec_state: &mut ExecState, args: Args) -> Result<KclVal
|
||||
exec_state,
|
||||
)?;
|
||||
|
||||
let color: String = args.get_kw_arg("color")?;
|
||||
let color: String = args.get_kw_arg_typed("color", &RuntimeType::string(), exec_state)?;
|
||||
let metalness: Option<TyF64> = args.get_kw_arg_opt_typed("metalness", &RuntimeType::count(), exec_state)?;
|
||||
let roughness: Option<TyF64> = args.get_kw_arg_opt_typed("roughness", &RuntimeType::count(), exec_state)?;
|
||||
|
||||
@ -81,224 +80,6 @@ pub async fn appearance(exec_state: &mut ExecState, args: Args) -> Result<KclVal
|
||||
Ok(result.into())
|
||||
}
|
||||
|
||||
/// Set the appearance of a solid. This only works on solids, not sketches or individual paths.
|
||||
///
|
||||
/// This will work on any solid, including extruded solids, revolved solids, and shelled solids.
|
||||
/// ```no_run
|
||||
/// // Add color to an extruded solid.
|
||||
/// exampleSketch = startSketchOn(XZ)
|
||||
/// |> startProfile(at = [0, 0])
|
||||
/// |> line(endAbsolute = [10, 0])
|
||||
/// |> line(endAbsolute = [0, 10])
|
||||
/// |> line(endAbsolute = [-10, 0])
|
||||
/// |> close()
|
||||
///
|
||||
/// example = extrude(exampleSketch, length = 5)
|
||||
/// // There are other options besides 'color', but they're optional.
|
||||
/// |> appearance(color='#ff0000')
|
||||
/// ```
|
||||
///
|
||||
/// ```no_run
|
||||
/// // Add color to a revolved solid.
|
||||
/// sketch001 = startSketchOn(XY)
|
||||
/// |> circle( center = [15, 0], radius = 5 )
|
||||
/// |> revolve( angle = 360, axis = Y)
|
||||
/// |> appearance(
|
||||
/// color = '#ff0000',
|
||||
/// metalness = 90,
|
||||
/// roughness = 90
|
||||
/// )
|
||||
/// ```
|
||||
///
|
||||
/// ```no_run
|
||||
/// // Add color to different solids.
|
||||
/// fn cube(center) {
|
||||
/// return startSketchOn(XY)
|
||||
/// |> startProfile(at = [center[0] - 10, center[1] - 10])
|
||||
/// |> line(endAbsolute = [center[0] + 10, center[1] - 10])
|
||||
/// |> line(endAbsolute = [center[0] + 10, center[1] + 10])
|
||||
/// |> line(endAbsolute = [center[0] - 10, center[1] + 10])
|
||||
/// |> close()
|
||||
/// |> extrude(length = 10)
|
||||
/// }
|
||||
///
|
||||
/// example0 = cube(center = [0, 0])
|
||||
/// example1 = cube(center = [20, 0])
|
||||
/// example2 = cube(center = [40, 0])
|
||||
///
|
||||
/// appearance([example0, example1], color='#ff0000', metalness=50, roughness=50)
|
||||
/// appearance(example2, color='#00ff00', metalness=50, roughness=50)
|
||||
/// ```
|
||||
///
|
||||
/// ```no_run
|
||||
/// // You can set the appearance before or after you shell it will yield the same result.
|
||||
/// // This example shows setting the appearance _after_ the shell.
|
||||
/// firstSketch = startSketchOn(XY)
|
||||
/// |> startProfile(at = [-12, 12])
|
||||
/// |> line(end = [24, 0])
|
||||
/// |> line(end = [0, -24])
|
||||
/// |> line(end = [-24, 0])
|
||||
/// |> close()
|
||||
/// |> extrude(length = 6)
|
||||
///
|
||||
/// shell(
|
||||
/// firstSketch,
|
||||
/// faces = [END],
|
||||
/// thickness = 0.25,
|
||||
/// )
|
||||
/// |> appearance(
|
||||
/// color = '#ff0000',
|
||||
/// metalness = 90,
|
||||
/// roughness = 90
|
||||
/// )
|
||||
/// ```
|
||||
///
|
||||
/// ```no_run
|
||||
/// // You can set the appearance before or after you shell it will yield the same result.
|
||||
/// // This example shows setting the appearance _before_ the shell.
|
||||
/// firstSketch = startSketchOn(XY)
|
||||
/// |> startProfile(at = [-12, 12])
|
||||
/// |> line(end = [24, 0])
|
||||
/// |> line(end = [0, -24])
|
||||
/// |> line(end = [-24, 0])
|
||||
/// |> close()
|
||||
/// |> extrude(length = 6)
|
||||
/// |> appearance(
|
||||
/// color = '#ff0000',
|
||||
/// metalness = 90,
|
||||
/// roughness = 90
|
||||
/// )
|
||||
///
|
||||
/// shell(
|
||||
/// firstSketch,
|
||||
/// faces = [END],
|
||||
/// thickness = 0.25,
|
||||
/// )
|
||||
/// ```
|
||||
///
|
||||
/// ```no_run
|
||||
/// // Setting the appearance of a 3D pattern can be done _before_ or _after_ the pattern.
|
||||
/// // This example shows _before_ the pattern.
|
||||
/// exampleSketch = startSketchOn(XZ)
|
||||
/// |> startProfile(at = [0, 0])
|
||||
/// |> line(end = [0, 2])
|
||||
/// |> line(end = [3, 1])
|
||||
/// |> line(end = [0, -4])
|
||||
/// |> close()
|
||||
///
|
||||
/// example = extrude(exampleSketch, length = 1)
|
||||
/// |> appearance(
|
||||
/// color = '#ff0000',
|
||||
/// metalness = 90,
|
||||
/// roughness = 90
|
||||
/// )
|
||||
/// |> patternLinear3d(
|
||||
/// axis = [1, 0, 1],
|
||||
/// instances = 7,
|
||||
/// distance = 6
|
||||
/// )
|
||||
/// ```
|
||||
///
|
||||
/// ```no_run
|
||||
/// // Setting the appearance of a 3D pattern can be done _before_ or _after_ the pattern.
|
||||
/// // This example shows _after_ the pattern.
|
||||
/// exampleSketch = startSketchOn(XZ)
|
||||
/// |> startProfile(at = [0, 0])
|
||||
/// |> line(end = [0, 2])
|
||||
/// |> line(end = [3, 1])
|
||||
/// |> line(end = [0, -4])
|
||||
/// |> close()
|
||||
///
|
||||
/// example = extrude(exampleSketch, length = 1)
|
||||
/// |> patternLinear3d(
|
||||
/// axis = [1, 0, 1],
|
||||
/// instances = 7,
|
||||
/// distance = 6
|
||||
/// )
|
||||
/// |> appearance(
|
||||
/// color = '#ff0000',
|
||||
/// metalness = 90,
|
||||
/// roughness = 90
|
||||
/// )
|
||||
/// ```
|
||||
///
|
||||
/// ```no_run
|
||||
/// // Color the result of a 2D pattern that was extruded.
|
||||
/// exampleSketch = startSketchOn(XZ)
|
||||
/// |> startProfile(at = [.5, 25])
|
||||
/// |> line(end = [0, 5])
|
||||
/// |> line(end = [-1, 0])
|
||||
/// |> line(end = [0, -5])
|
||||
/// |> close()
|
||||
/// |> patternCircular2d(
|
||||
/// center = [0, 0],
|
||||
/// instances = 13,
|
||||
/// arcDegrees = 360,
|
||||
/// rotateDuplicates = true
|
||||
/// )
|
||||
///
|
||||
/// example = extrude(exampleSketch, length = 1)
|
||||
/// |> appearance(
|
||||
/// color = '#ff0000',
|
||||
/// metalness = 90,
|
||||
/// roughness = 90
|
||||
/// )
|
||||
/// ```
|
||||
///
|
||||
/// ```no_run
|
||||
/// // Color the result of a sweep.
|
||||
///
|
||||
/// // Create a path for the sweep.
|
||||
/// sweepPath = startSketchOn(XZ)
|
||||
/// |> startProfile(at = [0.05, 0.05])
|
||||
/// |> line(end = [0, 7])
|
||||
/// |> tangentialArc(angle = 90, radius = 5)
|
||||
/// |> line(end = [-3, 0])
|
||||
/// |> tangentialArc(angle = -90, radius = 5)
|
||||
/// |> line(end = [0, 7])
|
||||
///
|
||||
/// pipeHole = startSketchOn(XY)
|
||||
/// |> circle(
|
||||
/// center = [0, 0],
|
||||
/// radius = 1.5,
|
||||
/// )
|
||||
///
|
||||
/// sweepSketch = startSketchOn(XY)
|
||||
/// |> circle(
|
||||
/// center = [0, 0],
|
||||
/// radius = 2,
|
||||
/// )
|
||||
/// |> subtract2d(tool = pipeHole)
|
||||
/// |> sweep(path = sweepPath)
|
||||
/// |> appearance(
|
||||
/// color = "#ff0000",
|
||||
/// metalness = 50,
|
||||
/// roughness = 50
|
||||
/// )
|
||||
/// ```
|
||||
///
|
||||
/// ```no_run
|
||||
/// // Change the appearance of an imported model.
|
||||
///
|
||||
/// import "tests/inputs/cube.sldprt" as cube
|
||||
///
|
||||
/// cube
|
||||
/// |> appearance(
|
||||
/// color = "#ff0000",
|
||||
/// metalness = 50,
|
||||
/// roughness = 50
|
||||
/// )
|
||||
/// ```
|
||||
#[stdlib {
|
||||
name = "appearance",
|
||||
unlabeled_first = true,
|
||||
args = {
|
||||
solids = { 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(
|
||||
solids: SolidOrImportedGeometry,
|
||||
color: String,
|
||||
|
@ -45,7 +45,6 @@ pub type StdFn = fn(
|
||||
|
||||
lazy_static! {
|
||||
static ref CORE_FNS: Vec<Box<dyn StdLibFn>> = vec![
|
||||
Box::new(crate::std::appearance::Appearance),
|
||||
Box::new(crate::std::extrude::Extrude),
|
||||
Box::new(crate::std::segment::SegEnd),
|
||||
Box::new(crate::std::segment::SegEndX),
|
||||
@ -277,6 +276,10 @@ pub(crate) fn std_fn(path: &str, fn_name: &str) -> (crate::std::StdFn, StdFnProp
|
||||
|e, a| Box::pin(crate::std::patterns::pattern_circular_3d(e, a)),
|
||||
StdFnProps::default("std::solid::patternCircular3d").include_in_feature_tree(),
|
||||
),
|
||||
("solid", "appearance") => (
|
||||
|e, a| Box::pin(crate::std::appearance::appearance(e, a)),
|
||||
StdFnProps::default("std::solid::appearance"),
|
||||
),
|
||||
("array", "map") => (
|
||||
|e, a| Box::pin(crate::std::array::map(e, a)),
|
||||
StdFnProps::default("std::array::map"),
|
||||
|
Reference in New Issue
Block a user