Files
OneCAD-Draft/src/patterns.rs
T
Hakan SevenandClaude Opus 4.7 ef1f9a08d6 chore: rename H7CAD to Open CAD Studio
Joining the OpenAEC Foundation (open-aec.com). Crate/struct become
OpenCADStudio, user-facing strings use the spaced "Open CAD Studio"
form, repo URLs point to HakanSeven12/open-cad-studio. Logo (window
icon + assets/logo.svg) redrawn with OCS initials. Drop examples/
binaries — unused.

Co-Authored-By: Claude Opus 4.7 <[email protected]>
2026-05-23 17:16:15 +03:00

125 lines
4.3 KiB
Rust

//! OpenCADStudio hatch pattern catalog — loaded from `assets/patterns/OpenCADStudio.pat`.
//!
//! PAT line format: `angle, x0, y0, dx, dy [, dash1, dash2, ...]`
//! - `angle` line direction in degrees
//! - `x0, y0` origin of the first line in the family
//! - `dx, dy` step vector to the next parallel line
//! - `dash…` positive = dash length, negative = gap length (omit = solid)
use std::sync::OnceLock;
const PAT_SRC: &str = include_str!("../assets/patterns/OpenCADStudio.pat");
// ── Public types ──────────────────────────────────────────────────────────
/// One line family inside a hatch pattern.
#[derive(Clone, Debug)]
pub struct PatLineDef {
/// Line direction in degrees.
pub angle_deg: f32,
/// Origin of the first line in the family.
pub x0: f32,
pub y0: f32,
/// Step vector to the next parallel line.
pub dx: f32,
pub dy: f32,
/// Dash/gap sequence (positive = dash, negative = gap, empty = solid).
pub dashes: Vec<f32>,
}
/// One parsed hatch pattern.
#[derive(Clone, Debug)]
pub struct PatternDef {
pub name: String,
pub description: String,
pub lines: Vec<PatLineDef>,
}
// ── Static catalog ────────────────────────────────────────────────────────
static CATALOG: OnceLock<Vec<PatternDef>> = OnceLock::new();
/// All parsed patterns, in file order.
pub fn catalog() -> &'static [PatternDef] {
CATALOG.get_or_init(|| parse(PAT_SRC))
}
// ── Geometry helper ───────────────────────────────────────────────────────
// ── Parser ────────────────────────────────────────────────────────────────
fn parse(src: &str) -> Vec<PatternDef> {
let mut result = Vec::new();
let mut cur_name = String::new();
let mut cur_desc = String::new();
let mut cur_lines: Vec<PatLineDef> = Vec::new();
let mut in_pattern = false;
let flush = |name: &mut String,
desc: &mut String,
lines: &mut Vec<PatLineDef>,
result: &mut Vec<PatternDef>| {
if !lines.is_empty() {
result.push(PatternDef {
name: std::mem::take(name),
description: std::mem::take(desc),
lines: std::mem::take(lines),
});
}
*name = String::new();
*desc = String::new();
};
for raw in src.lines() {
let line = raw.trim();
// Skip blanks and comments.
if line.is_empty() || line.starts_with(';') {
continue;
}
if let Some(rest) = line.strip_prefix('*') {
// Flush previous pattern.
flush(&mut cur_name, &mut cur_desc, &mut cur_lines, &mut result);
in_pattern = true;
// Header: *NAME[,Description]
if let Some((name, desc)) = rest.split_once(',') {
cur_name = name.trim().to_string();
cur_desc = desc.trim().to_string();
} else {
cur_name = rest.trim().to_string();
cur_desc = String::new();
}
} else if in_pattern {
// Pattern line: angle, x0, y0, dx, dy [, d1, d2, ...]
// Strip inline comments.
let data = line.split(';').next().unwrap_or("").trim();
if data.is_empty() {
continue;
}
let parts: Vec<f32> = data
.split(',')
.filter_map(|s| s.trim().parse().ok())
.collect();
if parts.len() >= 5 {
cur_lines.push(PatLineDef {
angle_deg: parts[0],
x0: parts[1],
y0: parts[2],
dx: parts[3],
dy: parts[4],
dashes: parts[5..].to_vec(),
});
}
}
}
// Flush last pattern.
flush(&mut cur_name, &mut cur_desc, &mut cur_lines, &mut result);
result
}