merge: async mesh import with Apple Events and convert menu

This commit is contained in:
jess 2026-04-02 12:06:40 -07:00
commit 158719ef8b
1 changed files with 48 additions and 31 deletions

View File

@ -6,7 +6,7 @@ use iced::widget::{
use iced::{Background, Border, Color, Element, Fill, Length, Padding, Shadow, Subscription};
use iced::{mouse, keyboard, window, Point, Vector};
use std::path::PathBuf;
use std::sync::{Arc, Mutex, OnceLock};
use std::sync::{Arc, Mutex, OnceLock, mpsc};
use std::time::Duration;
static OPEN_QUEUE: OnceLock<Mutex<Vec<PathBuf>>> = OnceLock::new();
@ -45,6 +45,7 @@ pub struct App {
cursor_line: usize,
line_eval_text: Option<String>,
mesh_path: Option<PathBuf>,
mesh_import_rx: Option<mpsc::Receiver<Result<(String, PathBuf), String>>>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
@ -87,6 +88,7 @@ pub enum Message {
RenderAll,
DecomposeMesh,
ConvertToCordial,
MeshImportComplete(Result<(String, PathBuf), String>),
Tick,
}
@ -154,6 +156,7 @@ impl App {
cursor_line: 0,
line_eval_text: None,
mesh_path: None,
mesh_import_rx: None,
};
app.reparse();
@ -335,6 +338,26 @@ impl App {
}
Message::DecomposeMesh => self.decompose_mesh(),
Message::ConvertToCordial => self.convert_to_cordial(),
Message::MeshImportComplete(result) => {
match result {
Ok((source, path)) => {
self.source = text_editor::Content::with_text(&source);
self.undo_stack = vec![source];
self.redo_stack.clear();
self.mesh_path = Some(path.clone());
self.current_path = Some(path.clone());
self.dirty = false;
self.detect_mode();
self.reparse();
self.update_markdown();
self.push_recent(&path);
self.status = Some(format!("opened: {}", path.display()));
}
Err(e) => {
self.status = Some(format!("mesh import error: {e}"));
}
}
}
Message::Tick => {
if !self.menu_ready {
setup_native_menu();
@ -347,6 +370,13 @@ impl App {
self.open_path(&path);
}
}
if let Some(rx) = &self.mesh_import_rx {
if let Ok(result) = rx.try_recv() {
self.mesh_import_rx = None;
self.update(Message::MeshImportComplete(result));
return;
}
}
let new_line = self.source.cursor().position.line;
if new_line != self.cursor_line {
self.cursor_line = new_line;
@ -696,8 +726,18 @@ impl App {
.unwrap_or("")
.to_lowercase();
let is_mesh = matches!(ext.as_str(), "obj" | "stl" | "3mf");
let mut fallback_note: Option<&str> = None;
if matches!(ext.as_str(), "obj" | "stl" | "3mf") {
self.status = Some(format!("decomposing mesh: {}", path.display()));
let (tx, rx) = mpsc::channel();
let path_owned = path.to_path_buf();
std::thread::spawn(move || {
let result = import_mesh(&path_owned)
.map(|source| (source, path_owned));
let _ = tx.send(result);
});
self.mesh_import_rx = Some(rx);
return;
}
let result = match ext.as_str() {
"zcd" => load_zcd(path),
@ -705,17 +745,6 @@ impl App {
"cord" | "bin" => {
Err("binary format (.cord) — no source to edit".into())
}
"obj" | "stl" | "3mf" => {
match import_mesh(path) {
Ok(imp) => {
if imp.is_fallback {
fallback_note = Some(" (bounding box — full decompose failed)");
}
Ok(imp.source)
}
Err(e) => Err(e),
}
}
"step" | "stp" => {
Err(format!("import for .{ext} not yet implemented"))
}
@ -728,14 +757,13 @@ impl App {
self.undo_stack = vec![source];
self.redo_stack.clear();
self.current_path = Some(path.to_path_buf());
self.mesh_path = if is_mesh { Some(path.to_path_buf()) } else { None };
self.mesh_path = None;
self.dirty = false;
self.detect_mode();
self.reparse();
self.update_markdown();
self.push_recent(path);
let note = fallback_note.unwrap_or("");
self.status = Some(format!("opened: {}{note}", path.display()));
self.status = Some(format!("opened: {}", path.display()));
}
Err(e) => {
self.status = Some(format!("error: {e}"));
@ -1766,26 +1794,15 @@ fn load_zcd(path: &std::path::Path) -> Result<String, String> {
Err("no readable layers in .zcd".into())
}
struct MeshImport {
source: String,
is_fallback: bool,
}
fn import_mesh(path: &std::path::Path) -> Result<MeshImport, String> {
fn import_mesh(path: &std::path::Path) -> Result<String, String> {
use cord_decompile::mesh::TriangleMesh;
use cord_decompile::{decompile, DecompileConfig};
let mesh = TriangleMesh::load(path).map_err(|e| e.to_string())?;
let config = DecompileConfig::default();
match decompile(&mesh, &config) {
Ok(result) => Ok(MeshImport {
source: cord_sdf::sdf_to_cordial(&result.sdf),
is_fallback: false,
}),
Err(_) => Ok(MeshImport {
source: mesh_bounding_source(&mesh),
is_fallback: true,
}),
Ok(result) => Ok(cord_sdf::sdf_to_cordial(&result.sdf)),
Err(_) => Ok(mesh_bounding_source(&mesh)),
}
}