Rename the Home tab to "Draw" and add a Model tab beside it with two groups: - Model: BOX / CYLINDER / CONE / SPHERE / WEDGE / TORUS, placed with a footprint + height. Each is built as a real ACIS Solid3D (acadrust primitive builders) plus a truck B-rep, tessellated into the shaded mesh pipeline, with edge wires for picking + wireframe. - Design: UNION / SUBTRACT / INTERSECT via truck-shapeops. A session-only Scene.model_solids cache holds each solid's truck B-rep so the boolean tools can combine and chain them. Supersedes the old command-line BOX/SPHERE/CYLINDER (insert::solid3d_cmds keeps EXTRUDE/ REVOLVE/SWEEP/LOFT). Known limitations: booleans need solids created this session; curved ACIS primitives render in-session but not after reload (the SAT tessellator handles only planar caps); geometry assumes world_offset 0. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
91 lines
3.2 KiB
Rust
91 lines
3.2 KiB
Rust
// build.rs — auto-generates src/modules/registry.rs
|
|
//
|
|
// Scans src/modules/ for subdirectories containing a mod.rs file.
|
|
// Each subdirectory name like "home" maps to a struct "HomeModule"
|
|
// defined in src/modules/home/mod.rs.
|
|
//
|
|
// Rules for a directory to be picked up:
|
|
// • Located directly in src/modules/
|
|
// • Contains a mod.rs file
|
|
// • Defines a pub struct {PascalCase}Module implementing CadModule
|
|
// (unit struct — no ::new() needed)
|
|
//
|
|
// Tab display order: the PRIORITY list below controls order.
|
|
// Unlisted modules appear after priority modules, sorted alphabetically.
|
|
//
|
|
// Adding a new module:
|
|
// 1. Create src/modules/my_name/mod.rs (implement CadModule as MyNameModule)
|
|
// 2. Add `pub mod my_name;` to src/modules/mod.rs
|
|
// 3. cargo build → module appears in the ribbon
|
|
|
|
use std::fs;
|
|
use std::path::Path;
|
|
|
|
// Display order for known modules; unknown modules follow alphabetically.
|
|
const PRIORITY: &[&str] = &["home", "model", "insert", "annotate", "view", "manage"];
|
|
|
|
fn main() {
|
|
let mods_dir = Path::new("src/modules");
|
|
println!("cargo:rerun-if-changed=src/modules");
|
|
|
|
// Collect subdirectories that contain mod.rs.
|
|
let mut modules: Vec<String> = fs::read_dir(mods_dir)
|
|
.expect("src/modules/ not found")
|
|
.filter_map(|e| {
|
|
let entry = e.ok()?;
|
|
if !entry.file_type().ok()?.is_dir() {
|
|
return None;
|
|
}
|
|
let name = entry.file_name().into_string().ok()?;
|
|
let mod_file = mods_dir.join(&name).join("mod.rs");
|
|
if mod_file.exists() {
|
|
Some(name)
|
|
} else {
|
|
None
|
|
}
|
|
})
|
|
.collect();
|
|
|
|
// Sort: priority order first, then alphabetical for the rest.
|
|
modules.sort_by_key(|name| {
|
|
let pri = PRIORITY.iter().position(|p| *p == name.as_str());
|
|
(pri.map(|i| i as u32).unwrap_or(u32::MAX), name.clone())
|
|
});
|
|
|
|
// snake_case → PascalCase + "Module"
|
|
let to_struct = |s: &str| -> String {
|
|
s.split('_')
|
|
.map(|w| {
|
|
let mut c = w.chars();
|
|
match c.next() {
|
|
None => String::new(),
|
|
Some(f) => f.to_uppercase().collect::<String>() + c.as_str(),
|
|
}
|
|
})
|
|
.collect::<String>()
|
|
+ "Module"
|
|
};
|
|
|
|
let mut out = String::from(
|
|
"// AUTO-GENERATED by build.rs — do not edit by hand.\n\
|
|
// To add a module: create src/modules/my_name/mod.rs + add `pub mod my_name;` to modules/mod.rs.\n\n\
|
|
use crate::modules::CadModule;\n\n\
|
|
/// Returns one boxed instance of every registered CAD module.\n\
|
|
/// Called once at startup by `Ribbon::new()`.\n\
|
|
pub fn all_modules() -> Vec<Box<dyn CadModule>> {\n\
|
|
\tvec![\n",
|
|
);
|
|
|
|
for module in &modules {
|
|
let s = to_struct(module);
|
|
out.push_str(&format!("\t\tBox::new(super::{module}::{s}),\n"));
|
|
}
|
|
|
|
out.push_str("\t]\n}\n");
|
|
|
|
let out_path = mods_dir.join("registry.rs");
|
|
let current = fs::read_to_string(&out_path).unwrap_or_default();
|
|
if current != out {
|
|
fs::write(&out_path, &out).expect("failed to write registry.rs");
|
|
}
|
|
}
|