diff --git a/modeling-cmds/openapi/api.json b/modeling-cmds/openapi/api.json index 2a9e2e23..52ce0100 100644 --- a/modeling-cmds/openapi/api.json +++ b/modeling-cmds/openapi/api.json @@ -4134,6 +4134,13 @@ } ] }, + "imported_files": { + "description": "Original imported files to embed in glTF source metadata. Paths are relative to the source project. Other export formats ignore these files.", + "type": "array", + "items": { + "$ref": "#/components/schemas/ImportFile" + } + }, "kcl_source": { "nullable": true, "description": "KCL source to embed in formats that support source metadata.", @@ -4176,6 +4183,13 @@ } ] }, + "imported_files": { + "description": "Original imported files to embed in glTF source metadata. Paths are relative to the source project. Other export formats ignore these files.", + "type": "array", + "items": { + "$ref": "#/components/schemas/ImportFile" + } + }, "kcl_source": { "nullable": true, "description": "KCL source to embed in formats that support source metadata.", diff --git a/modeling-cmds/src/def_enum.rs b/modeling-cmds/src/def_enum.rs index 6720e6d8..0b5eb6cc 100644 --- a/modeling-cmds/src/def_enum.rs +++ b/modeling-cmds/src/def_enum.rs @@ -696,6 +696,11 @@ define_modeling_cmd_enum! { /// KCL source to embed in formats that support source metadata. #[serde(default, skip_serializing_if = "Option::is_none")] pub kcl_source: Option, + /// Original imported files to embed in glTF source metadata. + /// Paths are relative to the source project. Other export formats ignore these files. + #[builder(default)] + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub imported_files: Vec, } /// Export the scene to a file. @@ -715,6 +720,11 @@ define_modeling_cmd_enum! { /// KCL source to embed in formats that support source metadata. #[serde(default, skip_serializing_if = "Option::is_none")] pub kcl_source: Option, + /// Original imported files to embed in glTF source metadata. + /// Paths are relative to the source project. Other export formats ignore these files. + #[builder(default)] + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub imported_files: Vec, } /// What is this entity's parent? diff --git a/modeling-cmds/tests/export_imported_files.rs b/modeling-cmds/tests/export_imported_files.rs new file mode 100644 index 00000000..75be97d5 --- /dev/null +++ b/modeling-cmds/tests/export_imported_files.rs @@ -0,0 +1,63 @@ +//! Compatibility and byte-preservation tests for original files supplied with exports. + +use kittycad_modeling_cmds::{ + each_cmd::{Export, Export3d}, + format::{gltf::export::Options, OutputFormat3d}, + ImportFile, ModelingCmd, +}; +use serde_json::json; + +#[test] +fn old_export_requests_and_builders_omit_imported_files() { + let format = OutputFormat3d::Gltf(Options::default()); + for command in [ + ModelingCmd::from(Export3d::builder().format(format.clone()).build()), + ModelingCmd::from(Export::builder().format(format.clone()).build()), + ] { + let value = serde_json::to_value(&command).unwrap(); + assert!(value.get("imported_files").is_none()); + assert_eq!(serde_json::from_value::(value).unwrap(), command); + } +} + +#[test] +fn both_export_commands_round_trip_original_binary_files() { + let files = vec![ + ImportFile::builder() + .path("parts/reference.step".to_owned()) + .data(vec![0, 255, 128, 13, 10]) + .build(), + ImportFile::builder() + .path("parts/empty.bin".to_owned()) + .data(Vec::new()) + .build(), + ]; + let format = OutputFormat3d::Gltf(Options::default()); + for command in [ + ModelingCmd::from( + Export3d::builder() + .format(format.clone()) + .imported_files(files.clone()) + .build(), + ), + ModelingCmd::from( + Export::builder() + .format(format.clone()) + .imported_files(files.clone()) + .build(), + ), + ] { + let value = serde_json::to_value(&command).unwrap(); + assert_eq!( + value["imported_files"], + json!([ + {"path": "parts/reference.step", "data": [0, 255, 128, 13, 10]}, + {"path": "parts/empty.bin", "data": []} + ]) + ); + assert_eq!(serde_json::from_value::(value).unwrap(), command); + let bson = bson::to_vec(&command).unwrap(); + assert_eq!(bson::from_slice::(&bson).unwrap(), command); + assert!(format!("{command:?}").contains("")); + } +}