KCL: [number; 3] to RGB hex string color function (#7184)

Closes https://github.com/KittyCAD/modeling-app/issues/6805. Enables users to programatically construct colors, which will be helpful for 

- Applying color to visualize program execution and help debugging
- Doing weird cool shit
This commit is contained in:
Adam Chalmers
2025-05-23 13:53:58 -05:00
committed by GitHub
parent 034366e65e
commit 1f53dd1357
17 changed files with 285 additions and 3 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -16,6 +16,8 @@ layout: manual
* [`helix`](/docs/kcl-std/functions/std-helix) * [`helix`](/docs/kcl-std/functions/std-helix)
* [`offsetPlane`](/docs/kcl-std/functions/std-offsetPlane) * [`offsetPlane`](/docs/kcl-std/functions/std-offsetPlane)
* [`patternLinear2d`](/docs/kcl-std/patternLinear2d) * [`patternLinear2d`](/docs/kcl-std/patternLinear2d)
* [**std::appearance**](/docs/kcl-std/modules/std-appearance)
* [`appearance::hexString`](/docs/kcl-std/functions/std-appearance-hexString)
* [**std::array**](/docs/kcl-std/modules/std-array) * [**std::array**](/docs/kcl-std/modules/std-array)
* [`map`](/docs/kcl-std/functions/std-array-map) * [`map`](/docs/kcl-std/functions/std-array-map)
* [`pop`](/docs/kcl-std/functions/std-array-pop) * [`pop`](/docs/kcl-std/functions/std-array-pop)

View File

@ -0,0 +1,16 @@
---
title: "appearance"
subtitle: "Module in std"
excerpt: ""
layout: manual
---
## Functions and constants
* [`appearance::hexString`](/docs/kcl-std/functions/std-appearance-hexString)

View File

@ -15,6 +15,7 @@ You might also want the [KCL language reference](/docs/kcl-lang) or the [KCL gui
## Modules ## Modules
* [`appearance::appearance`](/docs/kcl-std/modules/std-appearance)
* [`array`](/docs/kcl-std/modules/std-array) * [`array`](/docs/kcl-std/modules/std-array)
* [`math`](/docs/kcl-std/modules/std-math) * [`math`](/docs/kcl-std/modules/std-math)
* [`sketch`](/docs/kcl-std/modules/std-sketch) * [`sketch`](/docs/kcl-std/modules/std-sketch)

View File

@ -42,7 +42,10 @@ pub fn do_for_all_example_test(item: proc_macro2::TokenStream) -> proc_macro2::T
item.into_token_stream() item.into_token_stream()
} }
pub const TEST_NAMES: [&str; 93] = [ pub const TEST_NAMES: &[&str] = &[
"std-appearance-hexString-0",
"std-appearance-hexString-1",
"std-appearance-hexString-2",
"std-array-map-0", "std-array-map-0",
"std-array-map-1", "std-array-map-1",
"std-array-pop-0", "std-array-pop-0",

View File

@ -97,6 +97,7 @@ pub(crate) fn read_std(mod_name: &str) -> Option<&'static str> {
"units" => Some(include_str!("../std/units.kcl")), "units" => Some(include_str!("../std/units.kcl")),
"array" => Some(include_str!("../std/array.kcl")), "array" => Some(include_str!("../std/array.kcl")),
"sweep" => Some(include_str!("../std/sweep.kcl")), "sweep" => Some(include_str!("../std/sweep.kcl")),
"appearance" => Some(include_str!("../std/appearance.kcl")),
"transform" => Some(include_str!("../std/transform.kcl")), "transform" => Some(include_str!("../std/transform.kcl")),
_ => None, _ => None,
} }

View File

@ -10,7 +10,10 @@ use rgba_simple::Hex;
use super::args::TyF64; use super::args::TyF64;
use crate::{ use crate::{
errors::{KclError, KclErrorDetails}, errors::{KclError, KclErrorDetails},
execution::{types::RuntimeType, ExecState, KclValue, SolidOrImportedGeometry}, execution::{
types::{ArrayLen, RuntimeType},
ExecState, KclValue, SolidOrImportedGeometry,
},
std::Args, std::Args,
}; };
@ -18,6 +21,34 @@ lazy_static::lazy_static! {
static ref HEX_REGEX: Regex = Regex::new(r"^#[0-9a-fA-F]{6}$").unwrap(); static ref HEX_REGEX: Regex = Regex::new(r"^#[0-9a-fA-F]{6}$").unwrap();
} }
/// Construct a color from its red, blue and green components.
pub async fn hex_string(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let rgb: [TyF64; 3] = args.get_unlabeled_kw_arg_typed(
"rgb",
&RuntimeType::Array(Box::new(RuntimeType::count()), ArrayLen::Known(3)),
exec_state,
)?;
// Make sure the color if set is valid.
if let Some(component) = rgb.iter().find(|component| component.n < 0.0 || component.n > 255.0) {
return Err(KclError::Semantic(KclErrorDetails::new(
format!("Colors are given between 0 and 255, so {} is invalid", component.n),
vec![args.source_range],
)));
}
inner_hex_string(rgb, exec_state, args).await
}
async fn inner_hex_string(rgb: [TyF64; 3], _: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let [r, g, b] = rgb.map(|n| n.n.floor() as u32);
let s = format!("#{r:02x}{g:02x}{b:02x}");
Ok(KclValue::String {
value: s,
meta: args.into(),
})
}
/// Set the appearance of a solid. This only works on solids, not sketches or individual paths. /// 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> { pub async fn appearance(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let solids = args.get_unlabeled_kw_arg_typed( let solids = args.get_unlabeled_kw_arg_typed(

View File

@ -286,7 +286,13 @@ pub(crate) fn std_fn(path: &str, fn_name: &str) -> (crate::std::StdFn, StdFnProp
|e, a| Box::pin(crate::std::patterns::pattern_transform_2d(e, a)), |e, a| Box::pin(crate::std::patterns::pattern_transform_2d(e, a)),
StdFnProps::default("std::sketch::patternTransform2d"), StdFnProps::default("std::sketch::patternTransform2d"),
), ),
_ => unreachable!(), ("appearance", "hexString") => (
|e, a| Box::pin(crate::std::appearance::hex_string(e, a)),
StdFnProps::default("std::appearance::hexString"),
),
(module, fn_name) => {
panic!("No implementation found for {module}::{fn_name}, please add it to this big match statement")
}
} }
} }

View File

@ -0,0 +1,64 @@
/// Build a color from its red, green and blue components.
/// These must be between 0 and 255.
///
/// ```
/// startSketchOn(-XZ)
/// |> circle(center = [0, 0], radius = 10)
/// |> extrude(length = 4)
/// |> appearance(color = appearance::hexString([50, 160, 160]))
/// ```
/// ```
/// sideLen = 30
/// n = 10
///
/// // The cubes become more green and less blue with each instance.
/// fn cube(i, center) {
/// g = 255 / n * i
/// b = 255 / n * (n - i)
/// return startSketchOn(XY)
/// |> polygon(radius = sideLen / 2, numSides = 4, center = [center, 0])
/// |> extrude(length = sideLen)
/// |> appearance(color = appearance::hexString([0, g, b]), metalness = 80, roughness = 20)
/// }
///
/// // Create n cubes, shifting each one over in a line.
/// map(
/// [0..n],
/// f = fn(@i) {
/// return cube(i, center = sideLen * i * 1.5)
/// },
/// )
/// ```
/// ```
/// sideLen = 30
/// n = 6
///
/// fn cube(offset, i, red) {
/// x = floor(i / n)
/// y = rem(i, divisor = n)
/// g = 255 / n * x
/// b = 255 / n * y
/// return startSketchOn(offsetPlane(XZ, offset))
/// |> circle(diameter = sideLen, center = [sideLen * x * 1.5, sideLen * y * 1.5])
/// |> extrude(length = sideLen)
/// |> appearance(color = appearance::hexString([red, g, b]), metalness = 80, roughness = 40)
/// }
///
/// fn grid(offset, red) {
/// return map(
/// [0 ..< n * n],
/// f = fn(@i) {
/// return cube(offset, i, red)
/// },
/// )
/// }
///
/// grid(offset = 0, red = 0)
/// ```
///
@(impl = std_rust)
export fn hexString(
/// The red, blue and green components of the color.
/// Must be between 0 and 255.
@rgb: [number(_); 3],
): string {}

View File

@ -22,6 +22,7 @@ export import * from "std::solid"
export import * from "std::transform" export import * from "std::transform"
export import "std::turns" export import "std::turns"
export import "std::sweep" export import "std::sweep"
export import "std::appearance"
/// An abstract 3d plane aligned with the X and Y axes. Its normal is the positive Z axis. /// An abstract 3d plane aligned with the X and Y axes. Its normal is the positive Z axis.
export XY = { export XY = {

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 190 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 190 KiB