223 lines
6.5 KiB
Rust
223 lines
6.5 KiB
Rust
use serde::Deserialize;
|
|
use std::collections::HashMap;
|
|
use std::sync::OnceLock;
|
|
|
|
#[derive(Debug, Clone, Deserialize)]
|
|
pub struct ParamDef {
|
|
pub default: String,
|
|
#[allow(dead_code)]
|
|
pub desc: String,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Deserialize)]
|
|
pub struct ComponentDef {
|
|
#[serde(rename = "type")]
|
|
pub ctype: String,
|
|
#[serde(default)]
|
|
pub pts: Vec<[String; 2]>,
|
|
#[serde(default)]
|
|
pub cx: String,
|
|
#[serde(default)]
|
|
pub cy: String,
|
|
#[serde(default)]
|
|
pub rx: String,
|
|
#[serde(default)]
|
|
pub ry: String,
|
|
#[serde(default)]
|
|
pub r: String,
|
|
#[serde(default)]
|
|
pub n: usize,
|
|
#[serde(default)]
|
|
pub fill: String,
|
|
#[serde(default)]
|
|
pub closed: bool,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Deserialize)]
|
|
pub struct ShapeTemplateDef {
|
|
pub name: String,
|
|
#[allow(dead_code)]
|
|
pub size_mm: u32,
|
|
pub description: String,
|
|
#[serde(default)]
|
|
pub parameters: HashMap<String, ParamDef>,
|
|
pub components: Vec<ComponentDef>,
|
|
}
|
|
|
|
pub struct TemplateComponent {
|
|
pub pts: Vec<[f32; 2]>,
|
|
pub closed: bool,
|
|
pub fill: bool,
|
|
pub fill_color: String,
|
|
}
|
|
|
|
static TEMPLATES: OnceLock<Vec<ShapeTemplateDef>> = OnceLock::new();
|
|
|
|
fn init_templates() -> Vec<ShapeTemplateDef> {
|
|
let json = include_str!("../../hcie-ai/templates_db.json");
|
|
serde_json::from_str(json).unwrap_or_default()
|
|
}
|
|
|
|
pub fn get_template_def(name: &str) -> Option<ShapeTemplateDef> {
|
|
TEMPLATES.get_or_init(init_templates).iter().find(|t| t.name == name).cloned()
|
|
}
|
|
|
|
pub fn all_names() -> Vec<String> {
|
|
TEMPLATES.get_or_init(init_templates).iter().map(|t| t.name.clone()).collect()
|
|
}
|
|
|
|
pub fn search_templates(query: &str) -> Vec<ShapeTemplateDef> {
|
|
let q_lower = query.to_lowercase();
|
|
TEMPLATES.get_or_init(init_templates)
|
|
.iter()
|
|
.filter(|t| t.name.to_lowercase().contains(&q_lower) || t.description.to_lowercase().contains(&q_lower))
|
|
.cloned()
|
|
.collect()
|
|
}
|
|
|
|
pub fn eval_template(def: &ShapeTemplateDef, provided_params: &HashMap<String, String>) -> Vec<TemplateComponent> {
|
|
let mut num_vars = HashMap::new();
|
|
let mut str_vars = HashMap::new();
|
|
|
|
for (k, v) in &def.parameters {
|
|
let val_str = provided_params.get(k).unwrap_or(&v.default);
|
|
if let Ok(num) = val_str.parse::<f32>() {
|
|
num_vars.insert(k.clone(), num);
|
|
} else {
|
|
str_vars.insert(k.clone(), val_str.clone());
|
|
}
|
|
}
|
|
|
|
let eval = |expr: &str| -> f32 {
|
|
if let Ok(num) = expr.parse::<f32>() { return num; }
|
|
math_eval(expr, &num_vars).unwrap_or(0.0)
|
|
};
|
|
|
|
let subst = |expr: &str| -> String {
|
|
let mut res = expr.to_string();
|
|
for (k, v) in &str_vars {
|
|
res = res.replace(&format!("{{{}}}", k), v);
|
|
}
|
|
res
|
|
};
|
|
|
|
let mut out = Vec::new();
|
|
for c in &def.components {
|
|
let fill_str = subst(&c.fill);
|
|
let fill = fill_str != "none" && !fill_str.is_empty();
|
|
|
|
let mut pts = Vec::new();
|
|
match c.ctype.as_str() {
|
|
"path" => {
|
|
for pt in &c.pts {
|
|
pts.push([eval(&pt[0]), eval(&pt[1])]);
|
|
}
|
|
}
|
|
"circle" => {
|
|
let cx = eval(&c.cx);
|
|
let cy = eval(&c.cy);
|
|
let rx_val = if !c.r.is_empty() { eval(&c.r) } else { eval(&c.rx) };
|
|
let ry_val = if !c.r.is_empty() { eval(&c.r) } else { eval(&c.ry) };
|
|
pts = circle_points(cx, cy, rx_val, ry_val, c.n.max(8));
|
|
}
|
|
"star" => {
|
|
let outer = eval(&c.rx);
|
|
let inner = eval(&c.ry);
|
|
pts = star_points(c.n.max(5), outer, inner);
|
|
}
|
|
_ => {}
|
|
}
|
|
|
|
if !pts.is_empty() {
|
|
out.push(TemplateComponent { pts, closed: c.closed, fill, fill_color: fill_str });
|
|
}
|
|
}
|
|
out
|
|
}
|
|
|
|
fn math_eval(expr: &str, vars: &HashMap<String, f32>) -> Option<f32> {
|
|
let tokens = tokenize(expr);
|
|
let mut pos = 0;
|
|
parse_expr(&tokens, &mut pos, vars)
|
|
}
|
|
|
|
fn tokenize(expr: &str) -> Vec<String> {
|
|
let mut tokens = Vec::new();
|
|
let mut current = String::new();
|
|
for c in expr.chars() {
|
|
if c.is_whitespace() { continue; }
|
|
if "+-*/()".contains(c) {
|
|
if !current.is_empty() {
|
|
tokens.push(current.clone());
|
|
current.clear();
|
|
}
|
|
tokens.push(c.to_string());
|
|
} else {
|
|
current.push(c);
|
|
}
|
|
}
|
|
if !current.is_empty() { tokens.push(current); }
|
|
tokens
|
|
}
|
|
|
|
fn parse_expr(tokens: &[String], pos: &mut usize, vars: &HashMap<String, f32>) -> Option<f32> {
|
|
let mut left = parse_term(tokens, pos, vars)?;
|
|
while *pos < tokens.len() {
|
|
let op = &tokens[*pos];
|
|
if op == "+" { *pos += 1; left += parse_term(tokens, pos, vars)?; }
|
|
else if op == "-" { *pos += 1; left -= parse_term(tokens, pos, vars)?; }
|
|
else { break; }
|
|
}
|
|
Some(left)
|
|
}
|
|
|
|
fn parse_term(tokens: &[String], pos: &mut usize, vars: &HashMap<String, f32>) -> Option<f32> {
|
|
let mut left = parse_factor(tokens, pos, vars)?;
|
|
while *pos < tokens.len() {
|
|
let op = &tokens[*pos];
|
|
if op == "*" { *pos += 1; left *= parse_factor(tokens, pos, vars)?; }
|
|
else if op == "/" { *pos += 1; left /= parse_factor(tokens, pos, vars)?; }
|
|
else { break; }
|
|
}
|
|
Some(left)
|
|
}
|
|
|
|
fn parse_factor(tokens: &[String], pos: &mut usize, vars: &HashMap<String, f32>) -> Option<f32> {
|
|
if *pos >= tokens.len() { return None; }
|
|
let t = &tokens[*pos];
|
|
if t == "(" {
|
|
*pos += 1;
|
|
let val = parse_expr(tokens, pos, vars)?;
|
|
if *pos < tokens.len() && tokens[*pos] == ")" { *pos += 1; }
|
|
return Some(val);
|
|
}
|
|
if let Ok(n) = t.parse::<f32>() {
|
|
*pos += 1;
|
|
return Some(n);
|
|
}
|
|
if let Some(v) = vars.get(t) {
|
|
*pos += 1;
|
|
return Some(*v);
|
|
}
|
|
None
|
|
}
|
|
|
|
fn circle_points(cx: f32, cy: f32, rx: f32, ry: f32, n: usize) -> Vec<[f32; 2]> {
|
|
let mut pts = Vec::with_capacity(n);
|
|
for i in 0..n {
|
|
let a = i as f32 / n as f32 * std::f32::consts::TAU;
|
|
pts.push([cx + a.cos() * rx, cy + a.sin() * ry]);
|
|
}
|
|
pts
|
|
}
|
|
|
|
fn star_points(n: usize, outer: f32, inner: f32) -> Vec<[f32; 2]> {
|
|
let mut pts = Vec::with_capacity(n * 2);
|
|
for i in 0..n {
|
|
let a1 = i as f32 / n as f32 * std::f32::consts::TAU - std::f32::consts::FRAC_PI_2;
|
|
let a2 = (i as f32 + 0.5) / n as f32 * std::f32::consts::TAU - std::f32::consts::FRAC_PI_2;
|
|
pts.push([a1.cos() * outer, a1.sin() * outer]);
|
|
pts.push([a2.cos() * inner, a2.sin() * inner]);
|
|
}
|
|
pts
|
|
} |