1use bon::Builder;
2use schemars::JsonSchema;
3use serde::{Deserialize, Serialize};
4
5use crate::shared::{FileExportFormat, FileExportFormat2d, FileImportFormat};
6
7pub mod dxf;
9pub mod fbx;
11pub mod gltf;
16pub mod obj;
18pub mod ply;
20pub mod step;
22pub mod stl;
24
25#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize, JsonSchema)]
27#[serde(tag = "type", rename_all = "snake_case")]
28#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS))]
29#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
30#[cfg_attr(feature = "ts-rs", ts(export_to = "ModelingCmd.ts"))]
31#[cfg_attr(not(feature = "unstable_exhaustive"), non_exhaustive)]
32pub enum OutputFormat2d {
33 Dxf(dxf::export::Options),
35}
36
37#[deprecated(since = "0.2.96", note = "use `OutputFormat3d` instead")]
39pub type OutputFormat = OutputFormat3d;
40
41#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize, JsonSchema)]
43#[serde(tag = "type", rename_all = "snake_case")]
44#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS))]
45#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
46#[cfg_attr(feature = "ts-rs", ts(export_to = "ModelingCmd.ts"))]
47#[cfg_attr(not(feature = "unstable_exhaustive"), non_exhaustive)]
48pub enum OutputFormat3d {
49 Fbx(fbx::export::Options),
51 Gltf(gltf::export::Options),
56 Obj(obj::export::Options),
58 Ply(ply::export::Options),
60 Step(step::export::Options),
62 Stl(stl::export::Options),
64}
65
66#[deprecated(since = "0.2.96", note = "use `InputFormat3d` instead")]
68pub type InputFormat = InputFormat3d;
69
70#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize, JsonSchema)]
72#[serde(tag = "type", rename_all = "snake_case")]
73#[cfg_attr(
74 feature = "python",
75 pyo3::pyclass(from_py_object),
76 pyo3_stub_gen::derive::gen_stub_pyclass_complex_enum
77)]
78#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS))]
79#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
80#[cfg_attr(feature = "ts-rs", ts(export_to = "ModelingCmd.ts"))]
81#[cfg_attr(not(feature = "unstable_exhaustive"), non_exhaustive)]
82pub enum InputFormat3d {
83 Acis(acis::import::Options),
85 Catia(catia::import::Options),
87 Creo(creo::import::Options),
89 Fbx(fbx::import::Options),
91 Gltf(gltf::import::Options),
95 Inventor(inventor::import::Options),
97 Nx(nx::import::Options),
99 Obj(obj::import::Options),
101 Parasolid(parasolid::import::Options),
103 Ply(ply::import::Options),
105 Sldprt(sldprt::import::Options),
107 Step(step::import::Options),
109 Stl(stl::import::Options),
111}
112
113impl InputFormat3d {
114 pub fn name(&self) -> &'static str {
116 match self {
117 InputFormat3d::Acis(_) => "acis",
118 InputFormat3d::Catia(_) => "catia",
119 InputFormat3d::Creo(_) => "creo",
120 InputFormat3d::Fbx(_) => "fbx",
121 InputFormat3d::Gltf(_) => "gltf",
122 InputFormat3d::Inventor(_) => "inventor",
123 InputFormat3d::Nx(_) => "nx",
124 InputFormat3d::Parasolid(_) => "parasolid",
125 InputFormat3d::Obj(_) => "obj",
126 InputFormat3d::Ply(_) => "ply",
127 InputFormat3d::Sldprt(_) => "sldprt",
128 InputFormat3d::Step(_) => "step",
129 InputFormat3d::Stl(_) => "stl",
130 }
131 }
132}
133
134#[derive(Clone, Debug, Default, Eq, Hash, PartialEq, JsonSchema, Deserialize, Serialize)]
136#[serde(rename_all = "snake_case", tag = "type")]
137#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS))]
138#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
139#[cfg_attr(feature = "ts-rs", ts(export_to = "ModelingCmd.ts"))]
140#[cfg_attr(not(feature = "unstable_exhaustive"), non_exhaustive)]
141pub enum Selection {
142 #[default]
144 DefaultScene,
145
146 SceneByIndex {
148 index: usize,
150 },
151
152 SceneByName {
154 name: String,
156 },
157
158 MeshByIndex {
160 index: usize,
162 },
163
164 MeshByName {
166 name: String,
168 },
169}
170
171#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, Builder)]
173#[cfg_attr(not(feature = "unstable_exhaustive"), non_exhaustive)]
174pub struct VirtualFile {
175 pub path: std::path::PathBuf,
177 pub data: Vec<u8>,
179}
180
181impl VirtualFile {
182 pub fn new(path: std::path::PathBuf, data: Vec<u8>) -> Self {
184 Self { path, data }
185 }
186
187 pub fn has_extension(&self, required_extension: &str) -> bool {
189 self.path
190 .extension()
191 .and_then(std::ffi::OsStr::to_str)
192 .map(|extension| extension.eq_ignore_ascii_case(required_extension))
193 .unwrap_or(false)
194 }
195
196 fn read_fs_impl(path: std::path::PathBuf) -> std::io::Result<Self> {
197 let data = std::fs::read(&path)?;
198 Ok(Self { path, data })
199 }
200
201 pub fn read_fs<P>(path: P) -> std::io::Result<Self>
203 where
204 P: Into<std::path::PathBuf>,
205 {
206 Self::read_fs_impl(path.into())
207 }
208}
209
210impl From<OutputFormat3d> for FileExportFormat {
211 fn from(output_format: OutputFormat3d) -> Self {
212 match output_format {
213 OutputFormat3d::Fbx(_) => Self::Fbx,
214 OutputFormat3d::Gltf(_) => Self::Gltf,
215 OutputFormat3d::Obj(_) => Self::Obj,
216 OutputFormat3d::Ply(_) => Self::Ply,
217 OutputFormat3d::Step(_) => Self::Step,
218 OutputFormat3d::Stl(_) => Self::Stl,
219 }
220 }
221}
222
223impl From<OutputFormat2d> for FileExportFormat2d {
224 fn from(output_format: OutputFormat2d) -> Self {
225 match output_format {
226 OutputFormat2d::Dxf(_) => Self::Dxf,
227 }
228 }
229}
230
231impl From<FileExportFormat2d> for OutputFormat2d {
232 fn from(export_format: FileExportFormat2d) -> Self {
233 match export_format {
234 FileExportFormat2d::Dxf => OutputFormat2d::Dxf(Default::default()),
235 }
236 }
237}
238
239impl From<FileExportFormat> for OutputFormat3d {
240 fn from(export_format: FileExportFormat) -> Self {
241 match export_format {
242 FileExportFormat::Fbx => OutputFormat3d::Fbx(Default::default()),
243 FileExportFormat::Glb => OutputFormat3d::Gltf(gltf::export::Options {
244 storage: gltf::export::Storage::Binary,
245 ..Default::default()
246 }),
247 FileExportFormat::Gltf => OutputFormat3d::Gltf(gltf::export::Options {
248 storage: gltf::export::Storage::Embedded,
249 presentation: gltf::export::Presentation::Pretty,
250 }),
251 FileExportFormat::Obj => OutputFormat3d::Obj(Default::default()),
252 FileExportFormat::Ply => OutputFormat3d::Ply(Default::default()),
253 FileExportFormat::Step => OutputFormat3d::Step(Default::default()),
254 FileExportFormat::Stl => OutputFormat3d::Stl(stl::export::Options {
255 storage: stl::export::Storage::Ascii,
256 ..Default::default()
257 }),
258 }
259 }
260}
261
262impl From<InputFormat3d> for FileImportFormat {
263 fn from(input_format: InputFormat3d) -> Self {
264 match input_format {
265 InputFormat3d::Acis(_) => Self::Acis,
266 InputFormat3d::Catia(_) => Self::Catia,
267 InputFormat3d::Creo(_) => Self::Creo,
268 InputFormat3d::Fbx(_) => Self::Fbx,
269 InputFormat3d::Gltf(_) => Self::Gltf,
270 InputFormat3d::Inventor(_) => Self::Inventor,
271 InputFormat3d::Nx(_) => Self::Nx,
272 InputFormat3d::Obj(_) => Self::Obj,
273 InputFormat3d::Parasolid(_) => Self::Parasolid,
274 InputFormat3d::Ply(_) => Self::Ply,
275 InputFormat3d::Sldprt(_) => Self::Sldprt,
276 InputFormat3d::Step(_) => Self::Step,
277 InputFormat3d::Stl(_) => Self::Stl,
278 }
279 }
280}
281
282impl From<FileImportFormat> for InputFormat3d {
283 fn from(import_format: FileImportFormat) -> Self {
284 match import_format {
285 FileImportFormat::Acis => InputFormat3d::Acis(Default::default()),
286 FileImportFormat::Catia => InputFormat3d::Catia(Default::default()),
287 FileImportFormat::Creo => InputFormat3d::Creo(Default::default()),
288 FileImportFormat::Fbx => InputFormat3d::Fbx(Default::default()),
289 FileImportFormat::Gltf => InputFormat3d::Gltf(Default::default()),
290 FileImportFormat::Inventor => InputFormat3d::Inventor(Default::default()),
291 FileImportFormat::Nx => InputFormat3d::Nx(Default::default()),
292 FileImportFormat::Obj => InputFormat3d::Obj(Default::default()),
293 FileImportFormat::Parasolid => InputFormat3d::Parasolid(Default::default()),
294 FileImportFormat::Ply => InputFormat3d::Ply(Default::default()),
295 FileImportFormat::Sldprt => InputFormat3d::Sldprt(Default::default()),
296 FileImportFormat::Step => InputFormat3d::Step(Default::default()),
297 FileImportFormat::Stl => InputFormat3d::Stl(Default::default()),
298 }
299 }
300}
301
302pub struct OutputFormat3dOptions {
304 src_unit: crate::units::UnitLength,
305}
306
307impl OutputFormat3dOptions {
308 pub fn new(src_unit: crate::units::UnitLength) -> Self {
310 Self { src_unit }
311 }
312}
313
314impl OutputFormat3d {
315 pub fn new(format: &FileExportFormat, options: OutputFormat3dOptions) -> Self {
317 let OutputFormat3dOptions { src_unit } = options;
318 let coords = crate::coord::System {
324 forward: crate::coord::AxisDirectionPair {
325 axis: crate::coord::Axis::Y,
326 direction: crate::coord::Direction::Negative,
327 },
328 up: crate::coord::AxisDirectionPair {
329 axis: crate::coord::Axis::Z,
330 direction: crate::coord::Direction::Positive,
331 },
332 };
333
334 match format {
335 FileExportFormat::Fbx => Self::Fbx(fbx::export::Options {
336 storage: fbx::export::Storage::Binary,
337 created: None,
338 }),
339 FileExportFormat::Glb => Self::Gltf(gltf::export::Options {
340 storage: gltf::export::Storage::Binary,
341 presentation: gltf::export::Presentation::Compact,
342 }),
343 FileExportFormat::Gltf => Self::Gltf(gltf::export::Options {
344 storage: gltf::export::Storage::Embedded,
345 presentation: gltf::export::Presentation::Pretty,
346 }),
347 FileExportFormat::Obj => Self::Obj(obj::export::Options {
348 coords,
349 units: src_unit,
350 }),
351 FileExportFormat::Ply => Self::Ply(ply::export::Options {
352 storage: ply::export::Storage::Ascii,
353 coords,
354 selection: Selection::DefaultScene,
355 units: src_unit,
356 }),
357 FileExportFormat::Step => Self::Step(step::export::Options {
358 coords,
359 created: None,
360 units: src_unit,
361 presentation: step::export::Presentation::Pretty,
362 }),
363 FileExportFormat::Stl => Self::Stl(stl::export::Options {
364 storage: stl::export::Storage::Ascii,
365 coords,
366 units: src_unit,
367 selection: Selection::DefaultScene,
368 }),
369 }
370 }
371}
372
373macro_rules! proprietary_brep_formats {
374 {
375 $(
376 (
377 $mod_name:ident,
378 $spec_name:literal,
379 $format_description:literal,
380 $coordinate_system:expr
381 )
382 )*
383 } => {
384 $(
385 #[doc = $format_description]
386 pub mod $mod_name {
387 pub mod import {
389 use bon::Builder;
390 use schemars::JsonSchema;
391 use serde::{Deserialize, Serialize};
392 use crate::coord;
393
394 #[doc = std::concat!("Options for importing ", $format_description, ".")]
395 #[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize, JsonSchema, Builder)]
396 #[serde(default, rename = $spec_name)]
397 #[cfg_attr(feature = "ts-rs", derive(ts_rs::TS))]
398 #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
399 #[cfg_attr(feature = "ts-rs", ts(export_to = "ModelingCmd.ts"))]
400 #[cfg_attr(
401 feature = "python",
402 pyo3_stub_gen::derive::gen_stub_pyclass,
403 pyo3::pyclass(name = $spec_name, from_py_object)
404 )]
405 pub struct Options {
406 #[builder(default = *$coordinate_system)]
408 pub coords: coord::System,
409
410 #[builder(default)]
414 pub split_closed_faces: bool,
415 }
416
417 #[cfg(feature = "python")]
418 #[pyo3_stub_gen::derive::gen_stub_pymethods]
419 #[pyo3::pymethods]
420 impl Options {
421 #[new]
422 pub fn new() -> Self {
424 Default::default()
425 }
426 }
427
428 impl Default for Options {
429 fn default() -> Self {
430 Self {
431 coords: *$coordinate_system,
432 split_closed_faces: false,
433 }
434 }
435 }
436 }
437 }
438 )*
439 };
440}
441
442proprietary_brep_formats! {
443 (acis, "AcisImportOptions", "ACIS part format", coord::KITTYCAD)
444 (catia, "CatiaImportOptions", "CATIA part format", coord::KITTYCAD)
445 (creo, "CreoImportOptions", "PTC Creo part format", coord::OPENGL)
446 (inventor, "InventorImportOptions", "Autodesk Inventor part format", coord::KITTYCAD)
447 (nx, "NxImportOptions", "Siemens NX part format", coord::KITTYCAD)
448 (parasolid, "ParasolidImportOptions", "Parasolid part format", coord::KITTYCAD)
449 (sldprt, "SldprtImportOptions", "SolidWorks part format", coord::OPENGL)
450}