Skip to main content

kittycad_modeling_cmds/format/
mod.rs

1use bon::Builder;
2use schemars::JsonSchema;
3use serde::{Deserialize, Serialize};
4
5use crate::shared::{FileExportFormat, FileExportFormat2d, FileImportFormat};
6
7/// AutoCAD drawing interchange format.
8pub mod dxf;
9/// Autodesk Filmbox (FBX) format.
10pub mod fbx;
11/// glTF 2.0.
12/// We refer to this as glTF since that is how our customers refer to it, although by default
13/// it will be in binary format and thus technically (glb).
14/// If you prefer ASCII output, you can set that option for the export.
15pub mod gltf;
16/// Wavefront OBJ format.
17pub mod obj;
18/// The PLY Polygon File Format.
19pub mod ply;
20/// ISO 10303-21 (STEP) format.
21pub mod step;
22/// **ST**ereo**L**ithography format.
23pub mod stl;
24
25/// Output 2D format specifier.
26#[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    /// AutoCAD drawing interchange format.
34    Dxf(dxf::export::Options),
35}
36
37/// Alias for backward compatibility.
38#[deprecated(since = "0.2.96", note = "use `OutputFormat3d` instead")]
39pub type OutputFormat = OutputFormat3d;
40
41/// Output 3D format specifier.
42#[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    /// Autodesk Filmbox (FBX) format.
50    Fbx(fbx::export::Options),
51    /// glTF 2.0.
52    /// We refer to this as glTF since that is how our customers refer to it, although by default
53    /// it will be in binary format and thus technically (glb).
54    /// If you prefer ASCII output, you can set that option for the export.
55    Gltf(gltf::export::Options),
56    /// Wavefront OBJ format.
57    Obj(obj::export::Options),
58    /// The PLY Polygon File Format.
59    Ply(ply::export::Options),
60    /// ISO 10303-21 (STEP) format.
61    Step(step::export::Options),
62    /// **ST**ereo**L**ithography format.
63    Stl(stl::export::Options),
64}
65
66/// Alias for backward compatibility.
67#[deprecated(since = "0.2.96", note = "use `InputFormat3d` instead")]
68pub type InputFormat = InputFormat3d;
69
70/// Input format specifier.
71#[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 part format.
84    Acis(acis::import::Options),
85    /// CATIA part format.
86    Catia(catia::import::Options),
87    /// PTC Creo part format.
88    Creo(creo::import::Options),
89    /// Autodesk Filmbox (FBX) format.
90    Fbx(fbx::import::Options),
91    /// Binary glTF 2.0.
92    /// We refer to this as glTF since that is how our customers refer to it,
93    /// but this can also import binary glTF (glb).
94    Gltf(gltf::import::Options),
95    /// Autodesk Inventor part format.
96    Inventor(inventor::import::Options),
97    /// Siemens NX part format.
98    Nx(nx::import::Options),
99    /// Wavefront OBJ format.
100    Obj(obj::import::Options),
101    /// Parasolid part format.
102    Parasolid(parasolid::import::Options),
103    /// The PLY Polygon File Format.
104    Ply(ply::import::Options),
105    /// SolidWorks part (SLDPRT) format.
106    Sldprt(sldprt::import::Options),
107    /// ISO 10303-21 (STEP) format.
108    Step(step::import::Options),
109    /// **ST**ereo**L**ithography format.
110    Stl(stl::import::Options),
111}
112
113impl InputFormat3d {
114    /// Get the name of this format.
115    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/// Data item selection.
135#[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    /// Visit the default scene.
143    #[default]
144    DefaultScene,
145
146    /// Visit the indexed scene.
147    SceneByIndex {
148        /// The index.
149        index: usize,
150    },
151
152    /// Visit the first scene with the given name.
153    SceneByName {
154        /// The name.
155        name: String,
156    },
157
158    /// Visit the indexed mesh.
159    MeshByIndex {
160        /// The index.
161        index: usize,
162    },
163
164    /// Visit the first mesh with the given name.
165    MeshByName {
166        /// The name.
167        name: String,
168    },
169}
170
171/// Represents an in-memory file with an associated potentially foreign file path.
172#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, Builder)]
173#[cfg_attr(not(feature = "unstable_exhaustive"), non_exhaustive)]
174pub struct VirtualFile {
175    /// Original file path.
176    pub path: std::path::PathBuf,
177    /// File payload.
178    pub data: Vec<u8>,
179}
180
181impl VirtualFile {
182    /// Returns true if the file name has the given extension.
183    pub fn has_extension(&self, required_extension: &str) -> bool {
184        self.path
185            .extension()
186            .and_then(std::ffi::OsStr::to_str)
187            .map(|extension| extension.eq_ignore_ascii_case(required_extension))
188            .unwrap_or(false)
189    }
190
191    fn read_fs_impl(path: std::path::PathBuf) -> std::io::Result<Self> {
192        let data = std::fs::read(&path)?;
193        Ok(Self { path, data })
194    }
195
196    /// Read from file system.
197    pub fn read_fs<P>(path: P) -> std::io::Result<Self>
198    where
199        P: Into<std::path::PathBuf>,
200    {
201        Self::read_fs_impl(path.into())
202    }
203}
204
205impl From<OutputFormat3d> for FileExportFormat {
206    fn from(output_format: OutputFormat3d) -> Self {
207        match output_format {
208            OutputFormat3d::Fbx(_) => Self::Fbx,
209            OutputFormat3d::Gltf(_) => Self::Gltf,
210            OutputFormat3d::Obj(_) => Self::Obj,
211            OutputFormat3d::Ply(_) => Self::Ply,
212            OutputFormat3d::Step(_) => Self::Step,
213            OutputFormat3d::Stl(_) => Self::Stl,
214        }
215    }
216}
217
218impl From<OutputFormat2d> for FileExportFormat2d {
219    fn from(output_format: OutputFormat2d) -> Self {
220        match output_format {
221            OutputFormat2d::Dxf(_) => Self::Dxf,
222        }
223    }
224}
225
226impl From<FileExportFormat2d> for OutputFormat2d {
227    fn from(export_format: FileExportFormat2d) -> Self {
228        match export_format {
229            FileExportFormat2d::Dxf => OutputFormat2d::Dxf(Default::default()),
230        }
231    }
232}
233
234impl From<FileExportFormat> for OutputFormat3d {
235    fn from(export_format: FileExportFormat) -> Self {
236        match export_format {
237            FileExportFormat::Fbx => OutputFormat3d::Fbx(Default::default()),
238            FileExportFormat::Glb => OutputFormat3d::Gltf(gltf::export::Options {
239                storage: gltf::export::Storage::Binary,
240                ..Default::default()
241            }),
242            FileExportFormat::Gltf => OutputFormat3d::Gltf(gltf::export::Options {
243                storage: gltf::export::Storage::Embedded,
244                presentation: gltf::export::Presentation::Pretty,
245            }),
246            FileExportFormat::Obj => OutputFormat3d::Obj(Default::default()),
247            FileExportFormat::Ply => OutputFormat3d::Ply(Default::default()),
248            FileExportFormat::Step => OutputFormat3d::Step(Default::default()),
249            FileExportFormat::Stl => OutputFormat3d::Stl(stl::export::Options {
250                storage: stl::export::Storage::Ascii,
251                ..Default::default()
252            }),
253        }
254    }
255}
256
257impl From<InputFormat3d> for FileImportFormat {
258    fn from(input_format: InputFormat3d) -> Self {
259        match input_format {
260            InputFormat3d::Acis(_) => Self::Acis,
261            InputFormat3d::Catia(_) => Self::Catia,
262            InputFormat3d::Creo(_) => Self::Creo,
263            InputFormat3d::Fbx(_) => Self::Fbx,
264            InputFormat3d::Gltf(_) => Self::Gltf,
265            InputFormat3d::Inventor(_) => Self::Inventor,
266            InputFormat3d::Nx(_) => Self::Nx,
267            InputFormat3d::Obj(_) => Self::Obj,
268            InputFormat3d::Parasolid(_) => Self::Parasolid,
269            InputFormat3d::Ply(_) => Self::Ply,
270            InputFormat3d::Sldprt(_) => Self::Sldprt,
271            InputFormat3d::Step(_) => Self::Step,
272            InputFormat3d::Stl(_) => Self::Stl,
273        }
274    }
275}
276
277impl From<FileImportFormat> for InputFormat3d {
278    fn from(import_format: FileImportFormat) -> Self {
279        match import_format {
280            FileImportFormat::Acis => InputFormat3d::Acis(Default::default()),
281            FileImportFormat::Catia => InputFormat3d::Catia(Default::default()),
282            FileImportFormat::Creo => InputFormat3d::Creo(Default::default()),
283            FileImportFormat::Fbx => InputFormat3d::Fbx(Default::default()),
284            FileImportFormat::Gltf => InputFormat3d::Gltf(Default::default()),
285            FileImportFormat::Inventor => InputFormat3d::Inventor(Default::default()),
286            FileImportFormat::Nx => InputFormat3d::Nx(Default::default()),
287            FileImportFormat::Obj => InputFormat3d::Obj(Default::default()),
288            FileImportFormat::Parasolid => InputFormat3d::Parasolid(Default::default()),
289            FileImportFormat::Ply => InputFormat3d::Ply(Default::default()),
290            FileImportFormat::Sldprt => InputFormat3d::Sldprt(Default::default()),
291            FileImportFormat::Step => InputFormat3d::Step(Default::default()),
292            FileImportFormat::Stl => InputFormat3d::Stl(Default::default()),
293        }
294    }
295}
296
297/// Options for a 3D export.
298pub struct OutputFormat3dOptions {
299    src_unit: crate::units::UnitLength,
300}
301
302impl OutputFormat3dOptions {
303    /// Create the options, setting all optional fields to their defaults.
304    pub fn new(src_unit: crate::units::UnitLength) -> Self {
305        Self { src_unit }
306    }
307}
308
309impl OutputFormat3d {
310    /// Create the output format, setting the options as given.
311    pub fn new(format: &FileExportFormat, options: OutputFormat3dOptions) -> Self {
312        let OutputFormat3dOptions { src_unit } = options;
313        // Zoo co-ordinate system.
314        //
315        // * Forward: -Y
316        // * Up: +Z
317        // * Handedness: Right
318        let coords = crate::coord::System {
319            forward: crate::coord::AxisDirectionPair {
320                axis: crate::coord::Axis::Y,
321                direction: crate::coord::Direction::Negative,
322            },
323            up: crate::coord::AxisDirectionPair {
324                axis: crate::coord::Axis::Z,
325                direction: crate::coord::Direction::Positive,
326            },
327        };
328
329        match format {
330            FileExportFormat::Fbx => Self::Fbx(fbx::export::Options {
331                storage: fbx::export::Storage::Binary,
332                created: None,
333            }),
334            FileExportFormat::Glb => Self::Gltf(gltf::export::Options {
335                storage: gltf::export::Storage::Binary,
336                presentation: gltf::export::Presentation::Compact,
337            }),
338            FileExportFormat::Gltf => Self::Gltf(gltf::export::Options {
339                storage: gltf::export::Storage::Embedded,
340                presentation: gltf::export::Presentation::Pretty,
341            }),
342            FileExportFormat::Obj => Self::Obj(obj::export::Options {
343                coords,
344                units: src_unit,
345            }),
346            FileExportFormat::Ply => Self::Ply(ply::export::Options {
347                storage: ply::export::Storage::Ascii,
348                coords,
349                selection: Selection::DefaultScene,
350                units: src_unit,
351            }),
352            FileExportFormat::Step => Self::Step(step::export::Options {
353                coords,
354                created: None,
355                units: src_unit,
356                presentation: step::export::Presentation::Pretty,
357            }),
358            FileExportFormat::Stl => Self::Stl(stl::export::Options {
359                storage: stl::export::Storage::Ascii,
360                coords,
361                units: src_unit,
362                selection: Selection::DefaultScene,
363            }),
364        }
365    }
366}
367
368macro_rules! proprietary_brep_formats {
369    {
370        $(
371            (
372                $mod_name:ident,
373                $spec_name:literal,
374                $format_description:literal,
375                $coordinate_system:expr
376            )
377        )*
378    } => {
379        $(
380            #[doc = $format_description]
381            pub mod $mod_name {
382                /// Import functionality
383                pub mod import {
384                    use bon::Builder;
385                    use schemars::JsonSchema;
386                    use serde::{Deserialize, Serialize};
387                    use crate::coord;
388
389                    #[doc = std::concat!("Options for importing ", $format_description, ".")]
390                    #[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize, JsonSchema, Builder)]
391                    #[serde(default, rename = $spec_name)]
392                    #[cfg_attr(feature = "ts-rs", derive(ts_rs::TS))]
393                    #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
394                    #[cfg_attr(feature = "ts-rs", ts(export_to = "ModelingCmd.ts"))]
395                    #[cfg_attr(
396                        feature = "python",
397                        pyo3_stub_gen::derive::gen_stub_pyclass,
398                        pyo3::pyclass(name = $spec_name, from_py_object)
399                    )]
400                    pub struct Options {
401                        /// Co-ordinate system of input data.
402                        #[builder(default = *$coordinate_system)]
403                        pub coords: coord::System,
404
405                        /// Splits all closed faces into two open faces.
406                        ///
407                        /// Defaults to `false` but is implicitly `true` when importing into the engine.
408                        #[builder(default)]
409                        pub split_closed_faces: bool,
410                    }
411
412                    #[cfg(feature = "python")]
413                    #[pyo3_stub_gen::derive::gen_stub_pymethods]
414                    #[pyo3::pymethods]
415                    impl Options {
416                        #[new]
417                        /// Set the options to their defaults.
418                        pub fn new() -> Self {
419                            Default::default()
420                        }
421                    }
422
423                    impl Default for Options {
424                        fn default() -> Self {
425                            Self {
426                                coords: *$coordinate_system,
427                                split_closed_faces: false,
428                            }
429                        }
430                    }
431                }
432            }
433        )*
434    };
435}
436
437proprietary_brep_formats! {
438    (acis, "AcisImportOptions", "ACIS part format", coord::KITTYCAD)
439    (catia, "CatiaImportOptions", "CATIA part format", coord::KITTYCAD)
440    (creo, "CreoImportOptions", "PTC Creo part format", coord::OPENGL)
441    (inventor, "InventorImportOptions", "Autodesk Inventor part format", coord::KITTYCAD)
442    (nx, "NxImportOptions", "Siemens NX part format", coord::KITTYCAD)
443    (parasolid, "ParasolidImportOptions", "Parasolid part format", coord::KITTYCAD)
444    (sldprt, "SldprtImportOptions", "SolidWorks part format", coord::OPENGL)
445}