Support loading .obj and .mtl files

This commit is contained in:
Connor Slade
2024-07-02 10:39:20 -04:00
parent 5cff6c16d7
commit bf59ed8f25
7 changed files with 126 additions and 88 deletions

11
Cargo.lock generated
View File

@@ -2581,6 +2581,16 @@ dependencies = [
"syn 2.0.66",
]
[[package]]
name = "obj-rs"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a502182538a65adc7e3843f820eb5a4bd3ff395017e0ba6e3ccad404e9ea1da3"
dependencies = [
"num-traits",
"serde",
]
[[package]]
name = "objc"
version = "0.2.7"
@@ -3449,6 +3459,7 @@ dependencies = [
"common",
"goo_format",
"nalgebra",
"obj-rs",
"ordered-float",
"rayon",
"stl_io",

View File

@@ -26,4 +26,6 @@
- [ ] Fix slicing rotated objects
- [ ] 0-pad slice preview layer display so its width doesn't change as you move the slider
- [ ] Preload layer 1 in slice preview
- [ ] Better camera movement (left click to orbit, right click to change orbit point)
- [x] Better camera movement (left click to orbit, right click to change orbit point)
- [ ] Don't crash when object is outside bounds
- [ ] Change centerpoint of model to be at z=0

View File

@@ -10,88 +10,86 @@ use crate::{
pub fn ui(app: &mut App, ctx: &Context, _frame: &mut Frame) {
if let Some(result) = app.slice_result.lock().unwrap().as_mut() {
Window::new("Slice Preview")
.resizable([true, true])
.show(ctx, move |ui| {
ui.horizontal(|ui| {
ui.add(
Slider::new(&mut result.slice_preview_layer, 1..=result.goo.layers.len())
.vertical()
.show_value(false),
Window::new("Slice Preview").show(ctx, move |ui| {
ui.horizontal(|ui| {
ui.add(
Slider::new(&mut result.slice_preview_layer, 1..=result.goo.layers.len())
.vertical()
.show_value(false),
);
result.slice_preview_layer =
result.slice_preview_layer.clamp(1, result.goo.layers.len());
let new_preview = if result.last_preview_layer != result.slice_preview_layer {
result.last_preview_layer = result.slice_preview_layer;
let (width, height) = (
result.goo.header.x_resolution as u32,
result.goo.header.y_resolution as u32,
);
result.slice_preview_layer =
result.slice_preview_layer.clamp(1, result.goo.layers.len());
let new_preview = if result.last_preview_layer != result.slice_preview_layer {
result.last_preview_layer = result.slice_preview_layer;
let (width, height) = (
result.goo.header.x_resolution as u32,
result.goo.header.y_resolution as u32,
);
let layer_data = &result.goo.layers[result.slice_preview_layer - 1].data;
let decoder = LayerDecoder::new(layer_data);
let layer_data = &result.goo.layers[result.slice_preview_layer - 1].data;
let decoder = LayerDecoder::new(layer_data);
let mut image = vec![0; (width * height) as usize];
let mut pixel = 0;
for run in decoder {
for _ in 0..run.length {
image[pixel] = run.value;
pixel += 1;
}
let mut image = vec![0; (width * height) as usize];
let mut pixel = 0;
for run in decoder {
for _ in 0..run.length {
image[pixel] = run.value;
pixel += 1;
}
}
Some(image)
} else {
None
};
Some(image)
} else {
None
};
result.preview_scale = result.preview_scale.max(0.1);
egui::Frame::canvas(ui.style()).show(ui, |ui| {
let available_size = ui.available_size();
let (rect, _response) = ui.allocate_exact_size(
Vec2::new(
available_size.x,
available_size.x / result.goo.header.x_resolution as f32
* result.goo.header.y_resolution as f32,
),
Sense::drag(),
);
let callback = Callback::new_paint_callback(
rect,
SlicePreviewRenderCallback {
dimensions: Vector2::new(
result.goo.header.x_resolution as u32,
result.goo.header.y_resolution as u32,
),
offset: result.preview_offset,
scale: result.preview_scale,
new_preview,
},
);
ui.painter().add(callback);
});
});
ui.horizontal(|ui| {
ui.add(
DragValue::new(&mut result.slice_preview_layer)
.clamp_range(1..=result.goo.layers.len())
.custom_formatter(|n, _| format!("{}/{}", n, result.goo.layers.len())),
result.preview_scale = result.preview_scale.max(0.1);
egui::Frame::canvas(ui.style()).show(ui, |ui| {
let available_size = ui.available_size();
let (rect, _response) = ui.allocate_exact_size(
Vec2::new(
available_size.x,
available_size.x / result.goo.header.x_resolution as f32
* result.goo.header.y_resolution as f32,
),
Sense::drag(),
);
result.slice_preview_layer +=
ui.button(RichText::new("+").monospace()).clicked() as usize;
result.slice_preview_layer -=
ui.button(RichText::new("-").monospace()).clicked() as usize;
ui.separator();
ui.label("Offset");
vec2_dragger(ui, result.preview_offset.as_mut(), |x| x);
ui.separator();
ui.label("Scale");
ui.add(DragValue::new(&mut result.preview_scale));
let callback = Callback::new_paint_callback(
rect,
SlicePreviewRenderCallback {
dimensions: Vector2::new(
result.goo.header.x_resolution as u32,
result.goo.header.y_resolution as u32,
),
offset: result.preview_offset,
scale: result.preview_scale,
new_preview,
},
);
ui.painter().add(callback);
});
});
ui.horizontal(|ui| {
ui.add(
DragValue::new(&mut result.slice_preview_layer)
.clamp_range(1..=result.goo.layers.len())
.custom_formatter(|n, _| format!("{}/{}", n, result.goo.layers.len())),
);
result.slice_preview_layer +=
ui.button(RichText::new("+").monospace()).clicked() as usize;
result.slice_preview_layer -=
ui.button(RichText::new("-").monospace()).clicked() as usize;
ui.separator();
ui.label("Offset");
vec2_dragger(ui, result.preview_offset.as_mut(), |x| x);
ui.separator();
ui.label("Scale");
ui.add(DragValue::new(&mut result.preview_scale));
});
});
}
}

View File

@@ -1,4 +1,4 @@
use std::thread;
use std::{fs::File, io::BufReader, thread};
use clone_macro::clone;
use eframe::Frame;
@@ -21,11 +21,19 @@ pub fn ui(app: &mut App, ctx: &Context, _frame: &mut Frame) {
ui.menu_button("🖹 File", |ui| {
if ui.button("Import Model").clicked() {
// TODO: async
if let Some(path) = FileDialog::new().add_filter("STL", &["stl"]).pick_file() {
if let Some(path) = FileDialog::new()
.add_filter("Mesh", &["stl", "obj"])
.pick_file()
{
let name = path.file_name().unwrap().to_str().unwrap().to_string();
let ext = path.extension();
let format = ext
.expect("Selected file has no extension")
.to_string_lossy();
let mut file = std::fs::File::open(path).unwrap();
let model = slicer::mesh::load_mesh(&mut file, "stl").unwrap();
let file = File::open(&path).unwrap();
let mut buf = BufReader::new(file);
let model = slicer::mesh::load_mesh(&mut buf, &format).unwrap();
app.meshes
.write()

View File

@@ -14,3 +14,4 @@ stl_io = "0.7.0"
common = { path = "../common" }
goo_format = { path = "../goo_format" }
obj-rs = "0.7.1"

View File

@@ -1,6 +1,6 @@
use std::{
fs::{self, File},
io::{stdout, Write},
io::{stdout, BufReader, Write},
thread,
time::Instant,
};
@@ -36,8 +36,9 @@ fn main() -> Result<()> {
first_layers: 10,
};
let mut file = File::open(FILE_PATH)?;
let mut mesh = load_mesh(&mut file, "stl")?;
let file = File::open(FILE_PATH)?;
let mut buf = BufReader::new(file);
let mut mesh = load_mesh(&mut buf, "stl")?;
let (min, max) = mesh.minmax_point();
let real_scale = 1.0;

View File

@@ -1,7 +1,8 @@
use std::io::{Read, Seek};
use std::io::{BufRead, Seek};
use anyhow::Result;
use nalgebra::Matrix4;
use obj::{Obj, Position};
use crate::Pos;
@@ -137,11 +138,11 @@ impl Mesh {
}
}
pub fn load_mesh<T: Read + Seek>(reader: &mut T, format: &str) -> Result<Mesh> {
match format {
pub fn load_mesh<T: BufRead + Seek>(reader: &mut T, format: &str) -> Result<Mesh> {
Ok(match format {
"stl" => {
let model = stl_io::read_stl(reader)?;
Ok(Mesh {
Mesh {
vertices: model
.vertices
.iter()
@@ -160,10 +161,26 @@ pub fn load_mesh<T: Read + Seek>(reader: &mut T, format: &str) -> Result<Mesh> {
.collect(),
..Default::default()
}
.center_vertices())
}
_ => Err(anyhow::anyhow!("Unsupported format: {}", format)),
"obj" | "mtl" => {
let model: Obj<Position> = obj::load_obj(reader)?;
Mesh {
vertices: model
.vertices
.iter()
.map(|v| Pos::new(v.position[0], v.position[1], v.position[2]))
.collect(),
faces: model
.indices
.chunks_exact(3)
.map(|i| [i[0] as u32, i[1] as u32, i[2] as u32])
.collect(),
..Default::default()
}
}
_ => return Err(anyhow::anyhow!("Unsupported format: {}", format)),
}
.center_vertices())
}
impl Default for Mesh {