forked from 1b-agent/opencadstudio-upstream
feat(io): FBXEXPORT writes the drawing's 3D meshes as binary FBX
FBXEXPORT (also EXPORTFBX) saves every 3D mesh of the drawing (solids, surfaces and meshes, at full display resolution) as a binary FBX 7.4 file for visualisation tools: one model per object with its geometry, face normals and a material in its display colour. Axes stay Z-up and the unit scale follows the drawing's insertion units. Refs #1502 Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
12ffc3961f
commit
f18386c100
30 files changed
+678
No files matched your search
@@ -940,6 +940,11 @@ impl OpenCADStudio {
|
||||
return Some(Task::done(Message::StlExport));
|
||||
}
|
||||
|
||||
// FBXEXPORT — export 3D meshes to FBX for visualisation tools
|
||||
"FBXEXPORT" | "EXPORTFBX" => {
|
||||
return Some(Task::done(Message::FbxExport));
|
||||
}
|
||||
|
||||
// STEPOUT — export 3D meshes to STEP AP203 format
|
||||
"STEPOUT" | "EXPORTSTEP" | "STPOUT" => {
|
||||
return Some(Task::done(Message::StepExport));
|
||||
|
||||
@@ -918,6 +918,7 @@ inventory::submit!(crate::command::CommandRegistration {
|
||||
"SHORTCUTS",
|
||||
"SSM",
|
||||
"STEPOUT",
|
||||
"FBXEXPORT",
|
||||
"STLOUT",
|
||||
"STPOUT",
|
||||
"STYLE",
|
||||
|
||||
@@ -4411,6 +4411,12 @@ pub enum Message {
|
||||
/// Callback after the user picks (or cancels) the STEP save path.
|
||||
StepExportPath(Option<std::path::PathBuf>),
|
||||
StepExportFinished(std::path::PathBuf, Result<(), String>),
|
||||
// ── FBX export ────────────────────────────────────────────────────────
|
||||
/// Trigger FBX export: show save dialog.
|
||||
FbxExport,
|
||||
/// Callback after the user picks (or cancels) the FBX save path.
|
||||
FbxExportPath(Option<std::path::PathBuf>),
|
||||
FbxExportFinished(std::path::PathBuf, Result<(), String>),
|
||||
// ── OBJ import ────────────────────────────────────────────────────────
|
||||
/// Trigger OBJ import: show open-file dialog.
|
||||
ObjImport,
|
||||
|
||||
@@ -2573,6 +2573,41 @@ impl OpenCADStudio {
|
||||
)
|
||||
}
|
||||
|
||||
pub(super) fn on_fbx_export_path_some(&mut self, path: std::path::PathBuf) -> Task<Message> {
|
||||
let i = self.active_tab;
|
||||
// Export uses LOD 0 (full resolution); see StlExportPath above.
|
||||
let meshes: Vec<crate::scene::model::mesh_model::MeshModel> = self.tabs[i]
|
||||
.scene
|
||||
.meshes
|
||||
.values()
|
||||
.filter_map(|s| s.lods.first().cloned())
|
||||
.collect();
|
||||
let header = &self.tabs[i].scene.document.header;
|
||||
// Centimetres per drawing unit (INSUNITS); unitless metric drawings
|
||||
// are taken as millimetres, imperial ones as inches.
|
||||
let unit_cm = match header.insertion_units {
|
||||
1 => 2.54,
|
||||
2 => 30.48,
|
||||
4 => 0.1,
|
||||
5 => 1.0,
|
||||
6 => 100.0,
|
||||
14 => 10.0,
|
||||
_ if header.measurement == 1 => 0.1,
|
||||
_ => 2.54,
|
||||
};
|
||||
let worker_path = path.clone();
|
||||
background_task(
|
||||
crate::t!("FBX export"),
|
||||
move || {
|
||||
let mesh_refs: Vec<_> = meshes.iter().collect();
|
||||
let bytes = crate::io::fbx::build_fbx(&mesh_refs, unit_cm)
|
||||
.ok_or_else(|| "no mesh data to export".to_string())?;
|
||||
write_export(&worker_path, &bytes)
|
||||
},
|
||||
move |result| Message::FbxExportFinished(path, result),
|
||||
)
|
||||
}
|
||||
|
||||
pub(super) fn on_obj_import_path_some(&mut self, path: std::path::PathBuf) -> Task<Message> {
|
||||
let tab_id = self.tabs[self.active_tab].id;
|
||||
let worker_path = path.clone();
|
||||
|
||||
@@ -1994,6 +1994,62 @@ impl OpenCADStudio {
|
||||
}
|
||||
}
|
||||
|
||||
// ── FBX export ────────────────────────────────────────────────
|
||||
Message::FbxExport => {
|
||||
let i = self.active_tab;
|
||||
if self.tabs[i].scene.meshes.is_empty() {
|
||||
self.command_line.push_error(
|
||||
crate::t!("FBXEXPORT: no 3D mesh data in this drawing.").as_ref(),
|
||||
);
|
||||
return Task::none();
|
||||
}
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
{
|
||||
Task::perform(
|
||||
async {
|
||||
crate::sys::file_dialog()
|
||||
.set_title(crate::t!("Export FBX").as_ref())
|
||||
.set_file_name("export.fbx")
|
||||
.add_filter(crate::t!("FBX Files").as_ref(), &["fbx"])
|
||||
.add_filter(crate::t!("All Files").as_ref(), &["*"])
|
||||
.save_file()
|
||||
.await
|
||||
.map(|h| crate::sys::handle_path(&h))
|
||||
},
|
||||
Message::FbxExportPath,
|
||||
)
|
||||
}
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
{
|
||||
let stem = self.tabs[i]
|
||||
.current_path
|
||||
.as_deref()
|
||||
.and_then(|p: &std::path::Path| p.file_stem())
|
||||
.map(|s: &std::ffi::OsStr| s.to_string_lossy().into_owned())
|
||||
.unwrap_or_else(|| "drawing".into());
|
||||
Task::perform(
|
||||
crate::io::fbx::pick_fbx_path_owned(stem),
|
||||
Message::FbxExportPath,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Message::FbxExportPath(Some(path)) => self.on_fbx_export_path_some(path),
|
||||
|
||||
Message::FbxExportPath(None) => Task::none(),
|
||||
|
||||
Message::FbxExportFinished(path, result) => {
|
||||
match result {
|
||||
Ok(()) => self.command_line.push_output(
|
||||
crate::tf!("FBXEXPORT: exported to \"{}\"", path.display()).as_ref(),
|
||||
),
|
||||
Err(error) => self
|
||||
.command_line
|
||||
.push_error(crate::tf!("FBXEXPORT: {error}").as_ref()),
|
||||
}
|
||||
Task::none()
|
||||
}
|
||||
|
||||
Message::StepExportPath(Some(path)) => self.on_step_export_path_some(path),
|
||||
|
||||
Message::StepExportPath(None) => Task::none(),
|
||||
|
||||
Reference in new issue
Block a user