convert SCAD to Cordial menu, Apple Event handler, status feedback

This commit is contained in:
jess 2026-04-02 11:56:46 -07:00
parent 5e5ab706b2
commit ef64a9996f
6 changed files with 119 additions and 251 deletions

View File

@ -41,5 +41,10 @@ else
echo "no icon svg or rsvg-convert not found, skipping icon" echo "no icon svg or rsvg-convert not found, skipping icon"
fi fi
LSREGISTER="/System/Library/Frameworks/CoreServices.framework/Frameworks/LaunchServices.framework/Support/lsregister"
if [ -x "${LSREGISTER}" ]; then
"${LSREGISTER}" -f "${BUNDLE}"
echo "registered file types with LaunchServices"
fi
echo "done: ${BUNDLE}" echo "done: ${BUNDLE}"
echo "to register file types, run: open ${BUNDLE}"

View File

@ -31,4 +31,5 @@ muda = "0.17"
[target.'cfg(target_os = "macos")'.dependencies] [target.'cfg(target_os = "macos")'.dependencies]
objc2 = "0.6" objc2 = "0.6"
objc2-foundation = { version = "0.3", features = ["NSAppleEventDescriptor", "NSAppleEventManager", "NSString"] } objc2-foundation = { version = "0.3", features = ["NSAppleEventDescriptor", "NSAppleEventManager", "NSString", "NSURL", "NSArray"] }
objc2-app-kit = { version = "0.3", features = ["NSApplication", "NSRunningApplication"] }

View File

@ -86,6 +86,7 @@ pub enum Message {
RenderPlots, RenderPlots,
RenderAll, RenderAll,
DecomposeMesh, DecomposeMesh,
ConvertToCordial,
Tick, Tick,
} }
@ -333,14 +334,16 @@ impl App {
} }
} }
Message::DecomposeMesh => self.decompose_mesh(), Message::DecomposeMesh => self.decompose_mesh(),
Message::ConvertToCordial => self.convert_to_cordial(),
Message::Tick => { Message::Tick => {
if !self.menu_ready { if !self.menu_ready {
setup_native_menu(); setup_native_menu();
self.menu_ready = true; self.menu_ready = true;
} }
self.poll_menu_events(); self.poll_menu_events();
if let Ok(mut q) = open_queue().lock() { if let Ok(mut queue) = open_queue().try_lock() {
for path in q.drain(..) { if let Some(path) = queue.pop() {
drop(queue);
self.open_path(&path); self.open_path(&path);
} }
} }
@ -400,6 +403,7 @@ impl App {
"export_scad" => Some(Message::ExportMesh(MeshFormat::Scad)), "export_scad" => Some(Message::ExportMesh(MeshFormat::Scad)),
"undo" => Some(Message::Undo), "undo" => Some(Message::Undo),
"redo" => Some(Message::Redo), "redo" => Some(Message::Redo),
"convert_cordial" => Some(Message::ConvertToCordial),
"decompose" => Some(Message::DecomposeMesh), "decompose" => Some(Message::DecomposeMesh),
"render_objects" => Some(Message::RenderObjects), "render_objects" => Some(Message::RenderObjects),
"render_plots" => Some(Message::RenderPlots), "render_plots" => Some(Message::RenderPlots),
@ -633,6 +637,46 @@ impl App {
self.status = Some("decomposed mesh to Cordial".into()); self.status = Some("decomposed mesh to Cordial".into());
} }
fn convert_to_cordial(&mut self) {
if self.mode != InputMode::Scad {
self.status = Some("already in Cordial mode".into());
return;
}
let src = self.source.text();
let src = src.trim();
use cord_parse::lexer::Lexer;
use cord_parse::parser::Parser;
use cord_sdf::lower::lower_program;
let tokens = match Lexer::new(src).tokenize() {
Ok(t) => t,
Err(e) => { self.status = Some(format!("parse error: {e}")); return; }
};
let program = match Parser::new(tokens).parse_program() {
Ok(p) => p,
Err(e) => { self.status = Some(format!("parse error: {e}")); return; }
};
let mut sdf = match lower_program(&program) {
Ok(s) => s,
Err(e) => { self.status = Some(format!("lower error: {e}")); return; }
};
cord_sdf::simplify(&mut sdf);
let cordial = cord_sdf::sdf_to_cordial(&sdf);
self.source = text_editor::Content::with_text(&cordial);
self.undo_stack.push(cordial);
if self.undo_stack.len() > UNDO_LIMIT {
self.undo_stack.remove(0);
}
self.redo_stack.clear();
self.dirty = true;
self.mode = InputMode::Expr;
self.reparse();
self.update_markdown();
self.status = Some("converted SCAD to Cordial".into());
}
fn open_dialog(&mut self) { fn open_dialog(&mut self) {
let path = rfd::FileDialog::new() let path = rfd::FileDialog::new()
.add_filter("All Supported", &["crd", "cord", "zcd", "scad", "stl", "obj", "3mf", "step", "stp"]) .add_filter("All Supported", &["crd", "cord", "zcd", "scad", "stl", "obj", "3mf", "step", "stp"])
@ -2201,6 +2245,7 @@ fn setup_native_menu() {
&MenuItem::with_id("export_step", "Export STEP\u{2026}", true, None), &MenuItem::with_id("export_step", "Export STEP\u{2026}", true, None),
&MenuItem::with_id("export_scad", "Export OpenSCAD\u{2026}", true, None), &MenuItem::with_id("export_scad", "Export OpenSCAD\u{2026}", true, None),
&PredefinedMenuItem::separator(), &PredefinedMenuItem::separator(),
&MenuItem::with_id("convert_cordial", "Convert SCAD to Cordial", true, m(cmd_shift, Code::KeyC)),
&MenuItem::with_id("decompose", "Decompose Mesh to Cordial", true, m(cmd_shift, Code::KeyD)), &MenuItem::with_id("decompose", "Decompose Mesh to Cordial", true, m(cmd_shift, Code::KeyD)),
]).unwrap(); ]).unwrap();

View File

@ -21,7 +21,6 @@ mod apple_events {
use objc2::runtime::{AnyObject, NSObject}; use objc2::runtime::{AnyObject, NSObject};
use objc2::{define_class, msg_send, sel, AnyThread}; use objc2::{define_class, msg_send, sel, AnyThread};
// kCoreEventClass = 'aevt', kAEOpenDocuments = 'odoc', keyDirectObject = '----'
const KAEVT: u32 = u32::from_be_bytes(*b"aevt"); const KAEVT: u32 = u32::from_be_bytes(*b"aevt");
const KODOC: u32 = u32::from_be_bytes(*b"odoc"); const KODOC: u32 = u32::from_be_bytes(*b"odoc");
const KEY_DIRECT_OBJECT: u32 = u32::from_be_bytes(*b"----"); const KEY_DIRECT_OBJECT: u32 = u32::from_be_bytes(*b"----");
@ -132,7 +131,6 @@ mod apple_events {
andEventID: KODOC andEventID: KODOC
]; ];
} }
// Leak the handler so it lives for the process lifetime
std::mem::forget(handler); std::mem::forget(handler);
} }
} }

View File

@ -0,0 +1,63 @@
// SPE Press-Contact Jig
spe_length = 31.0;
spe_width = 12.0;
spe_thick = 0.35;
strip_count = 3;
strip_y = [3.1, 6.05, 9.0];
strip_length = 18.0;
wire_dia = 0.65;
wall = 1.8;
floor_t = 1.2;
rim = 1.5;
tol = 0.15;
nest = 0.8;
lid_coverage = strip_length - 1.0;
tray_inner_l = spe_length + 2*tol;
tray_inner_w = spe_width + 2*tol;
tray_outer_l = tray_inner_l + 2*wall;
tray_outer_w = tray_inner_w + 2*wall;
lid_inner_l = lid_coverage + 2*tol;
lid_outer_l = lid_inner_l + 2*wall;
lid_outer_w = tray_outer_w;
blade_w = 1.2;
blade_drop = rim + 0.3;
module bottom() {
difference() {
cube([tray_outer_l, tray_outer_w, floor_t + rim]);
translate([wall, wall, floor_t])
cube([tray_inner_l + wall + 1, tray_inner_w, rim + 0.1]);
for (i = [0:strip_count-1]) {
wy = wall + tol + strip_y[i];
translate([-0.1, wy - (wire_dia + tol)/2, floor_t])
cube([wall + 0.2, wire_dia + tol, rim + 0.1]);
}
}
}
module top() {
lip_inset = tol;
difference() {
cube([lid_outer_l, lid_outer_w, floor_t]);
for (i = [0:strip_count-1]) {
wy = wall + tol + strip_y[i];
translate([-0.1, wy - (wire_dia + tol)/2, -0.1])
cube([wall + 0.2, wire_dia + tol, floor_t + 0.2]);
}
}
translate([wall + lip_inset, wall + lip_inset, -nest])
difference() {
cube([lid_inner_l - 2*lip_inset, tray_inner_w - 2*lip_inset, nest]);
translate([tol, tol, -0.1])
cube([lid_inner_l - 2*lip_inset - 2*tol, tray_inner_w - 2*lip_inset - 2*tol, nest + 0.2]);
}
for (i = [0:strip_count-1]) {
wy = wall + tol + strip_y[i];
translate([wall, wy - blade_w/2, -blade_drop])
cube([lid_inner_l, blade_w, blade_drop]);
}
}
bottom();
translate([tray_outer_l + 8, 0, blade_drop])
top();

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 8.0 MiB

After

Width:  |  Height:  |  Size: 9.7 MiB