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
13/// it may 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    /// Create a new virtual file.
183    pub fn new(path: std::path::PathBuf, data: Vec<u8>) -> Self {
184        Self { path, data }
185    }
186
187    /// Returns true if the file name has the given extension.
188    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    /// Read from file system.
202    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
302/// Options for a 3D export.
303pub struct OutputFormat3dOptions {
304    src_unit: crate::units::UnitLength,
305}
306
307impl OutputFormat3dOptions {
308    /// Create the options, setting all optional fields to their defaults.
309    pub fn new(src_unit: crate::units::UnitLength) -> Self {
310        Self { src_unit }
311    }
312}
313
314impl OutputFormat3d {
315    /// Create the output format, setting the options as given.
316    pub fn new(format: &FileExportFormat, options: OutputFormat3dOptions) -> Self {
317        let OutputFormat3dOptions { src_unit } = options;
318        // Zoo co-ordinate system.
319        //
320        // * Forward: -Y
321        // * Up: +Z
322        // * Handedness: Right
323        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                /// Import functionality
388                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                        /// Co-ordinate system of input data.
407                        #[builder(default = *$coordinate_system)]
408                        pub coords: coord::System,
409
410                        /// Splits all closed faces into two open faces.
411                        ///
412                        /// Defaults to `false` but is implicitly `true` when importing into the engine.
413                        #[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                        /// Set the options to their defaults.
423                        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}