forked from 1b-agent/opencadstudio-upstream
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]>
125 lines
4.3 KiB
Rust
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
|
|
}
|