Files
OneCAD-Draft/src/io/patterns.rs
T
Hakan SevenandClaude Opus 4.8 415dbe08d4 refactor: move resource/IO root modules under io/
Relocate three root-level modules into io/, where they belong by concern:

- update_check.rs -> io/update_check.rs (its own doc-comment notes it
  mirrors how crate::io wraps blocking calls in async).
- patterns.rs, linetypes.rs -> io/ (standard resource catalogs parsed
  from asset files and populated into the document — IO-shaped work).

Fix the moved files' include_str! paths (one level deeper), add the
mod declarations to io/mod.rs, drop them from lib.rs/main.rs, and
re-point all crate:: references to crate::io::. No behavior change.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-26 23:06:40 +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
}