diff --git a/bundle.sh b/bundle.sh
index 94d4d2e..95cb004 100755
--- a/bundle.sh
+++ b/bundle.sh
@@ -41,5 +41,10 @@ else
echo "no icon svg or rsvg-convert not found, skipping icon"
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 "to register file types, run: open ${BUNDLE}"
diff --git a/crates/cord-gui/Cargo.toml b/crates/cord-gui/Cargo.toml
index cdb59f9..fd9e713 100644
--- a/crates/cord-gui/Cargo.toml
+++ b/crates/cord-gui/Cargo.toml
@@ -31,4 +31,5 @@ muda = "0.17"
[target.'cfg(target_os = "macos")'.dependencies]
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"] }
diff --git a/crates/cord-gui/src/app.rs b/crates/cord-gui/src/app.rs
index 75c73ad..8e98086 100644
--- a/crates/cord-gui/src/app.rs
+++ b/crates/cord-gui/src/app.rs
@@ -86,6 +86,7 @@ pub enum Message {
RenderPlots,
RenderAll,
DecomposeMesh,
+ ConvertToCordial,
Tick,
}
@@ -333,14 +334,16 @@ impl App {
}
}
Message::DecomposeMesh => self.decompose_mesh(),
+ Message::ConvertToCordial => self.convert_to_cordial(),
Message::Tick => {
if !self.menu_ready {
setup_native_menu();
self.menu_ready = true;
}
self.poll_menu_events();
- if let Ok(mut q) = open_queue().lock() {
- for path in q.drain(..) {
+ if let Ok(mut queue) = open_queue().try_lock() {
+ if let Some(path) = queue.pop() {
+ drop(queue);
self.open_path(&path);
}
}
@@ -400,6 +403,7 @@ impl App {
"export_scad" => Some(Message::ExportMesh(MeshFormat::Scad)),
"undo" => Some(Message::Undo),
"redo" => Some(Message::Redo),
+ "convert_cordial" => Some(Message::ConvertToCordial),
"decompose" => Some(Message::DecomposeMesh),
"render_objects" => Some(Message::RenderObjects),
"render_plots" => Some(Message::RenderPlots),
@@ -633,6 +637,46 @@ impl App {
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) {
let path = rfd::FileDialog::new()
.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_scad", "Export OpenSCAD\u{2026}", true, None),
&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)),
]).unwrap();
diff --git a/crates/cord-gui/src/main.rs b/crates/cord-gui/src/main.rs
index c79dcb6..67dcdfb 100644
--- a/crates/cord-gui/src/main.rs
+++ b/crates/cord-gui/src/main.rs
@@ -21,7 +21,6 @@ mod apple_events {
use objc2::runtime::{AnyObject, NSObject};
use objc2::{define_class, msg_send, sel, AnyThread};
- // kCoreEventClass = 'aevt', kAEOpenDocuments = 'odoc', keyDirectObject = '----'
const KAEVT: u32 = u32::from_be_bytes(*b"aevt");
const KODOC: u32 = u32::from_be_bytes(*b"odoc");
const KEY_DIRECT_OBJECT: u32 = u32::from_be_bytes(*b"----");
@@ -132,7 +131,6 @@ mod apple_events {
andEventID: KODOC
];
}
- // Leak the handler so it lives for the process lifetime
std::mem::forget(handler);
}
}
diff --git a/examples/spe_contact_jig.scad b/examples/spe_contact_jig.scad
new file mode 100644
index 0000000..ddc4f04
--- /dev/null
+++ b/examples/spe_contact_jig.scad
@@ -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();
diff --git a/static/vectors/cord.svg b/static/vectors/cord.svg
index e228f3b..9a2f1fa 100644
--- a/static/vectors/cord.svg
+++ b/static/vectors/cord.svg
@@ -1,245 +1 @@
-
-
\ No newline at end of file
+