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 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 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
297pub struct OutputFormat3dOptions {
299 src_unit: crate::units::UnitLength,
300}
301
302impl OutputFormat3dOptions {
303 pub fn new(src_unit: crate::units::UnitLength) -> Self {
305 Self { src_unit }
306 }
307}
308
309impl OutputFormat3d {
310 pub fn new(format: &FileExportFormat, options: OutputFormat3dOptions) -> Self {
312 let OutputFormat3dOptions { src_unit } = options;
313 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 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 #[builder(default = *$coordinate_system)]
403 pub coords: coord::System,
404
405 #[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 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}