feat(resources): load the user's own hatch patterns and linetypes

*.pat files in <config>/patterns and *.lin files in <config>/linetypes
are read after the bundled catalogs, next to the fonts folder SHX
files already resolve from.

Fixes #1725

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
Hakan SevenandClaude Opus 5.5 committed 2026-10-13 10:04:25 +03:00
1 parent b5b505ee9d
commit 893dcee6a7
2 files changed
+44 -4

No files matched your search

+10 -2
View File
@@ -69,7 +69,7 @@ static COMPLEX_CATALOG: OnceLock<HashMap<String, ComplexLt>> = OnceLock::new();
/// Returns `None` for simple (dash-only) linetypes or unknown names.
pub fn complex_lt(name: &str) -> Option<&'static ComplexLt> {
COMPLEX_CATALOG
.get_or_init(|| parse_complex(LIN_SOURCE))
.get_or_init(|| parse_complex(lin_source()))
.get(&name.to_ascii_uppercase())
}
@@ -232,6 +232,14 @@ pub fn resolve_complex_lt(document: &CadDocument, name: &str) -> Option<ComplexL
const LIN_SOURCE: &str = include_str!("../../assets/linetypes/OpenCADStudio.lin");
/// The bundled linetypes followed by the user's own `.lin` files.
fn lin_source() -> &'static str {
static SOURCE: OnceLock<String> = OnceLock::new();
SOURCE.get_or_init(|| {
format!("{LIN_SOURCE}\n{}", crate::io::patterns::user_definitions("linetypes", "lin"))
})
}
// ── Pattern art extraction ────────────────────────────────────────────────
/// Extract the ASCII-art portion of a LIN description.
@@ -257,7 +265,7 @@ pub fn extract_pattern(desc: &str) -> String {
/// Add all standard OpenCADStudio linetypes to `doc`, skipping existing ones.
pub fn populate_document(doc: &mut CadDocument) {
populate_document_from_source(doc, LIN_SOURCE);
populate_document_from_source(doc, lin_source());
}
/// Add the linetypes of a caller-supplied `.lin` source, embedded shapes and
+34 -2
View File
@@ -39,9 +39,41 @@ pub struct PatternDef {
static CATALOG: OnceLock<Vec<PatternDef>> = OnceLock::new();
/// All parsed patterns, in file order.
/// All parsed patterns, in file order: the bundled catalog, then the user's
/// own `.pat` files.
pub fn catalog() -> &'static [PatternDef] {
CATALOG.get_or_init(|| parse(PAT_SRC))
CATALOG.get_or_init(|| parse(&format!("{PAT_SRC}\n{}", user_definitions("patterns", "pat"))))
}
/// The text of every `*.<ext>` file in `<config>/<folder>`, where users drop
/// their own hatch patterns and linetypes. Bundled names come first, so a
/// user file adds definitions rather than replacing the built-in ones.
#[cfg(not(target_arch = "wasm32"))]
pub(crate) fn user_definitions(folder: &str, ext: &str) -> String {
let Some(dir) = crate::config::config_dir().map(|dir| dir.join(folder)) else {
return String::new();
};
let Ok(entries) = std::fs::read_dir(dir) else {
return String::new();
};
let mut paths: Vec<_> = entries
.filter_map(Result::ok)
.map(|entry| entry.path())
.filter(|path| path.extension().is_some_and(|e| e.eq_ignore_ascii_case(ext)))
.collect();
paths.sort();
paths
.iter()
.filter_map(|path| std::fs::read(path).ok())
.map(|bytes| String::from_utf8_lossy(&bytes).into_owned())
.collect::<Vec<_>>()
.join("\n")
}
/// The web build has no user folder.
#[cfg(target_arch = "wasm32")]
pub(crate) fn user_definitions(_folder: &str, _ext: &str) -> String {
String::new()
}
// ── Geometry helper ───────────────────────────────────────────────────────