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/// SolidWorks part (SLDPRT) format.
26pub mod sldprt;
27
28/// Output 2D format specifier.
29#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize, JsonSchema)]
30#[serde(tag = "type", rename_all = "snake_case")]
31#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS))]
32#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
33#[cfg_attr(feature = "ts-rs", ts(export_to = "ModelingCmd.ts"))]
34#[cfg_attr(not(feature = "unstable_exhaustive"), non_exhaustive)]
35pub enum OutputFormat2d {
36    /// AutoCAD drawing interchange format.
37    Dxf(dxf::export::Options),
38}
39
40/// Alias for backward compatibility.
41#[deprecated(since = "0.2.96", note = "use `OutputFormat3d` instead")]
42pub type OutputFormat = OutputFormat3d;
43
44/// Output 3D format specifier.
45#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize, JsonSchema)]
46#[serde(tag = "type", rename_all = "snake_case")]
47#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS))]
48#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
49#[cfg_attr(feature = "ts-rs", ts(export_to = "ModelingCmd.ts"))]
50#[cfg_attr(not(feature = "unstable_exhaustive"), non_exhaustive)]
51pub enum OutputFormat3d {
52    /// Autodesk Filmbox (FBX) format.
53    Fbx(fbx::export::Options),
54    /// glTF 2.0.
55    /// We refer to this as glTF since that is how our customers refer to it, although by default
56    /// it will be in binary format and thus technically (glb).
57    /// If you prefer ASCII output, you can set that option for the export.
58    Gltf(gltf::export::Options),
59    /// Wavefront OBJ format.
60    Obj(obj::export::Options),
61    /// The PLY Polygon File Format.
62    Ply(ply::export::Options),
63    /// ISO 10303-21 (STEP) format.
64    Step(step::export::Options),
65    /// **ST**ereo**L**ithography format.
66    Stl(stl::export::Options),
67}
68
69/// Alias for backward compatibility.
70#[deprecated(since = "0.2.96", note = "use `InputFormat3d` instead")]
71pub type InputFormat = InputFormat3d;
72
73/// Input format specifier.
74#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize, JsonSchema)]
75#[serde(tag = "type", rename_all = "snake_case")]
76#[cfg_attr(
77    feature = "python",
78    pyo3::pyclass,
79    pyo3_stub_gen::derive::gen_stub_pyclass_complex_enum
80)]
81#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS))]
82#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
83#[cfg_attr(feature = "ts-rs", ts(export_to = "ModelingCmd.ts"))]
84#[cfg_attr(not(feature = "unstable_exhaustive"), non_exhaustive)]
85pub enum InputFormat3d {
86    /// Autodesk Filmbox (FBX) format.
87    Fbx(fbx::import::Options),
88    /// Binary glTF 2.0.
89    /// We refer to this as glTF since that is how our customers refer to it,
90    /// but this can also import binary glTF (glb).
91    Gltf(gltf::import::Options),
92    /// Wavefront OBJ format.
93    Obj(obj::import::Options),
94    /// The PLY Polygon File Format.
95    Ply(ply::import::Options),
96    /// SolidWorks part (SLDPRT) format.
97    Sldprt(sldprt::import::Options),
98    /// ISO 10303-21 (STEP) format.
99    Step(step::import::Options),
100    /// **ST**ereo**L**ithography format.
101    Stl(stl::import::Options),
102}
103
104impl InputFormat3d {
105    /// Get the name of this format.
106    pub fn name(&self) -> &'static str {
107        match self {
108            InputFormat3d::Fbx(_) => "fbx",
109            InputFormat3d::Gltf(_) => "gltf",
110            InputFormat3d::Obj(_) => "obj",
111            InputFormat3d::Ply(_) => "ply",
112            InputFormat3d::Sldprt(_) => "sldprt",
113            InputFormat3d::Step(_) => "step",
114            InputFormat3d::Stl(_) => "stl",
115        }
116    }
117}
118
119/// Data item selection.
120#[derive(Clone, Debug, Default, Eq, Hash, PartialEq, JsonSchema, Deserialize, Serialize)]
121#[serde(rename_all = "snake_case", tag = "type")]
122#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS))]
123#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
124#[cfg_attr(feature = "ts-rs", ts(export_to = "ModelingCmd.ts"))]
125#[cfg_attr(not(feature = "unstable_exhaustive"), non_exhaustive)]
126pub enum Selection {
127    /// Visit the default scene.
128    #[default]
129    DefaultScene,
130
131    /// Visit the indexed scene.
132    SceneByIndex {
133        /// The index.
134        index: usize,
135    },
136
137    /// Visit the first scene with the given name.
138    SceneByName {
139        /// The name.
140        name: String,
141    },
142
143    /// Visit the indexed mesh.
144    MeshByIndex {
145        /// The index.
146        index: usize,
147    },
148
149    /// Visit the first mesh with the given name.
150    MeshByName {
151        /// The name.
152        name: String,
153    },
154}
155
156/// Represents an in-memory file with an associated potentially foreign file path.
157#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, Builder)]
158#[cfg_attr(not(feature = "unstable_exhaustive"), non_exhaustive)]
159pub struct VirtualFile {
160    /// Original file path.
161    pub path: std::path::PathBuf,
162    /// File payload.
163    pub data: Vec<u8>,
164}
165
166impl VirtualFile {
167    /// Returns true if the file name has the given extension.
168    pub fn has_extension(&self, required_extension: &str) -> bool {
169        self.path
170            .extension()
171            .and_then(std::ffi::OsStr::to_str)
172            .map(|extension| extension.eq_ignore_ascii_case(required_extension))
173            .unwrap_or(false)
174    }
175
176    fn read_fs_impl(path: std::path::PathBuf) -> std::io::Result<Self> {
177        let data = std::fs::read(&path)?;
178        Ok(Self { path, data })
179    }
180
181    /// Read from file system.
182    pub fn read_fs<P>(path: P) -> std::io::Result<Self>
183    where
184        P: Into<std::path::PathBuf>,
185    {
186        Self::read_fs_impl(path.into())
187    }
188}
189
190impl From<OutputFormat3d> for FileExportFormat {
191    fn from(output_format: OutputFormat3d) -> Self {
192        match output_format {
193            OutputFormat3d::Fbx(_) => Self::Fbx,
194            OutputFormat3d::Gltf(_) => Self::Gltf,
195            OutputFormat3d::Obj(_) => Self::Obj,
196            OutputFormat3d::Ply(_) => Self::Ply,
197            OutputFormat3d::Step(_) => Self::Step,
198            OutputFormat3d::Stl(_) => Self::Stl,
199        }
200    }
201}
202
203impl From<OutputFormat2d> for FileExportFormat2d {
204    fn from(output_format: OutputFormat2d) -> Self {
205        match output_format {
206            OutputFormat2d::Dxf(_) => Self::Dxf,
207        }
208    }
209}
210
211impl From<FileExportFormat2d> for OutputFormat2d {
212    fn from(export_format: FileExportFormat2d) -> Self {
213        match export_format {
214            FileExportFormat2d::Dxf => OutputFormat2d::Dxf(Default::default()),
215        }
216    }
217}
218
219impl From<FileExportFormat> for OutputFormat3d {
220    fn from(export_format: FileExportFormat) -> Self {
221        match export_format {
222            FileExportFormat::Fbx => OutputFormat3d::Fbx(Default::default()),
223            FileExportFormat::Glb => OutputFormat3d::Gltf(gltf::export::Options {
224                storage: gltf::export::Storage::Binary,
225                ..Default::default()
226            }),
227            FileExportFormat::Gltf => OutputFormat3d::Gltf(gltf::export::Options {
228                storage: gltf::export::Storage::Embedded,
229                presentation: gltf::export::Presentation::Pretty,
230            }),
231            FileExportFormat::Obj => OutputFormat3d::Obj(Default::default()),
232            FileExportFormat::Ply => OutputFormat3d::Ply(Default::default()),
233            FileExportFormat::Step => OutputFormat3d::Step(Default::default()),
234            FileExportFormat::Stl => OutputFormat3d::Stl(stl::export::Options {
235                storage: stl::export::Storage::Ascii,
236                ..Default::default()
237            }),
238        }
239    }
240}
241
242impl From<InputFormat3d> for FileImportFormat {
243    fn from(input_format: InputFormat3d) -> Self {
244        match input_format {
245            InputFormat3d::Fbx(_) => Self::Fbx,
246            InputFormat3d::Gltf(_) => Self::Gltf,
247            InputFormat3d::Obj(_) => Self::Obj,
248            InputFormat3d::Ply(_) => Self::Ply,
249            InputFormat3d::Sldprt(_) => Self::Sldprt,
250            InputFormat3d::Step(_) => Self::Step,
251            InputFormat3d::Stl(_) => Self::Stl,
252        }
253    }
254}
255
256impl From<FileImportFormat> for InputFormat3d {
257    fn from(import_format: FileImportFormat) -> Self {
258        match import_format {
259            FileImportFormat::Fbx => InputFormat3d::Fbx(Default::default()),
260            FileImportFormat::Gltf => InputFormat3d::Gltf(Default::default()),
261            FileImportFormat::Obj => InputFormat3d::Obj(Default::default()),
262            FileImportFormat::Ply => InputFormat3d::Ply(Default::default()),
263            FileImportFormat::Sldprt => InputFormat3d::Sldprt(Default::default()),
264            FileImportFormat::Step => InputFormat3d::Step(Default::default()),
265            FileImportFormat::Stl => InputFormat3d::Stl(Default::default()),
266        }
267    }
268}
269
270/// Options for a 3D export.
271pub struct OutputFormat3dOptions {
272    src_unit: crate::units::UnitLength,
273}
274
275impl OutputFormat3dOptions {
276    /// Create the options, setting all optional fields to their defaults.
277    pub fn new(src_unit: crate::units::UnitLength) -> Self {
278        Self { src_unit }
279    }
280}
281
282impl OutputFormat3d {
283    /// Create the output format, setting the options as given.
284    pub fn new(format: &FileExportFormat, options: OutputFormat3dOptions) -> Self {
285        let OutputFormat3dOptions { src_unit } = options;
286        // Zoo co-ordinate system.
287        //
288        // * Forward: -Y
289        // * Up: +Z
290        // * Handedness: Right
291        let coords = crate::coord::System {
292            forward: crate::coord::AxisDirectionPair {
293                axis: crate::coord::Axis::Y,
294                direction: crate::coord::Direction::Negative,
295            },
296            up: crate::coord::AxisDirectionPair {
297                axis: crate::coord::Axis::Z,
298                direction: crate::coord::Direction::Positive,
299            },
300        };
301
302        match format {
303            FileExportFormat::Fbx => Self::Fbx(fbx::export::Options {
304                storage: fbx::export::Storage::Binary,
305                created: None,
306            }),
307            FileExportFormat::Glb => Self::Gltf(gltf::export::Options {
308                storage: gltf::export::Storage::Binary,
309                presentation: gltf::export::Presentation::Compact,
310            }),
311            FileExportFormat::Gltf => Self::Gltf(gltf::export::Options {
312                storage: gltf::export::Storage::Embedded,
313                presentation: gltf::export::Presentation::Pretty,
314            }),
315            FileExportFormat::Obj => Self::Obj(obj::export::Options {
316                coords,
317                units: src_unit,
318            }),
319            FileExportFormat::Ply => Self::Ply(ply::export::Options {
320                storage: ply::export::Storage::Ascii,
321                coords,
322                selection: Selection::DefaultScene,
323                units: src_unit,
324            }),
325            FileExportFormat::Step => Self::Step(step::export::Options {
326                coords,
327                created: None,
328                units: src_unit,
329                presentation: step::export::Presentation::Pretty,
330            }),
331            FileExportFormat::Stl => Self::Stl(stl::export::Options {
332                storage: stl::export::Storage::Ascii,
333                coords,
334                units: src_unit,
335                selection: Selection::DefaultScene,
336            }),
337        }
338    }
339}