Files
hcie-rust-v3.05/hcie-engine-api/src/ai_templates.rs
T

223 lines
6.5 KiB
Rust
Raw Normal View History

2026-07-09 02:59:53 +03:00
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
}