1use crate::Result;
6use crate::{memory::Memory, Destination, ExecutionError};
7use kittycad_execution_plan_traits::{InMemory, Primitive, ReadMemory, Value};
8use kittycad_modeling_cmds::{coord, format, shared, units};
9use serde::{Deserialize, Serialize};
10use std::fs;
11use std::path::Path;
12use std::str::FromStr;
13
14#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
16pub struct ImportFiles {
17 pub files_destination: Option<Destination>,
20 pub format_destination: Option<Destination>,
23 pub arguments: Vec<InMemory>,
27}
28
29impl ImportFiles {
30 pub async fn execute(self, mem: &mut Memory) -> Result<()> {
32 let Self {
33 files_destination,
34 format_destination,
35 arguments,
36 } = self;
37
38 let file_path_prim = match arguments[0] {
39 InMemory::Address(addr) => mem.get_ok(&addr),
40 InMemory::StackPop => mem.stack_pop(),
41 InMemory::StackPeek => mem.stack_peek(),
42 };
43
44 let Ok([Primitive::String(file_path_str)]) = file_path_prim.as_deref() else {
45 return Err(ExecutionError::BadArg {
46 reason: "first arg must be a string".to_string(),
47 });
48 };
49
50 let file_path = Path::new(&file_path_str);
51 let Ok(file_contents) = fs::read(file_path) else {
52 return Err(ExecutionError::General {
53 reason: "Can't read file".to_string(),
54 });
55 };
56
57 let ext_format = get_import_format_from_extension(file_path_str.split('.').last().ok_or_else(|| {
58 ExecutionError::General {
59 reason: format!("No file extension found for `{}`", file_path_str),
60 }
61 })?)
62 .map_err(|e| ExecutionError::General { reason: e.to_string() })?;
63
64 let options_prim = match arguments.get(1) {
65 Some(InMemory::Address(addr)) => mem.get_ok(addr),
66 Some(InMemory::StackPop) => mem.stack_pop(),
67 Some(InMemory::StackPeek) => mem.stack_peek(),
68 None => Ok(vec![]),
69 };
70
71 let maybe_opt_format = match options_prim.as_deref() {
72 Ok(format_values) => from_vec_prim_to_res_opt_input_format(format_values.to_vec()),
73 _ => {
74 return Err(ExecutionError::General {
75 reason: "invalid format option passed".to_string(),
76 });
77 }
78 };
79
80 let format = if let Ok(Some(opt_format)) = maybe_opt_format {
82 validate_extension_format(ext_format, opt_format.clone())?;
84 opt_format
85 } else {
86 ext_format
87 };
88
89 let file_name = file_path
91 .file_name()
92 .map(|p| p.to_string_lossy().to_string())
93 .ok_or_else(|| ExecutionError::General {
94 reason: "couldn't extract file name from file path".to_string(),
95 })?;
96
97 let mut import_files = vec![kittycad_modeling_cmds::ImportFile {
100 path: file_name,
101 data: file_contents.clone(),
102 }];
103
104 if let format::InputFormat::Gltf(format::gltf::import::Options {}) = format {
107 if !file_contents.starts_with(b"glTF") {
110 let json = gltf_json::Root::from_slice(&file_contents)
111 .map_err(|e| ExecutionError::General { reason: e.to_string() })?;
112
113 for buffer in json.buffers {
115 if let Some(uri) = &buffer.uri {
116 if !uri.starts_with("data:") {
117 let bin_path = std::path::Path::new(&file_path)
119 .parent()
120 .map(|p| p.join(uri))
121 .map(|p| p.to_string_lossy().to_string())
122 .ok_or_else(|| ExecutionError::General {
123 reason: format!("Could not get the parent path of the file `{}`", file_path_str),
124 })?;
125
126 let bin_contents =
127 fs::read(&bin_path).map_err(|e| ExecutionError::General { reason: e.to_string() })?;
128
129 import_files.push(kittycad_modeling_cmds::ImportFile {
130 path: uri.to_string(),
131 data: bin_contents,
132 });
133 }
134 }
135 }
136 }
137 }
138
139 if let Some(memory_area) = format_destination {
141 match memory_area {
142 Destination::Address(addr) => {
143 mem.set_composite(addr, format);
144 }
145 Destination::StackPush => {
146 mem.stack.push(format.into_parts());
147 }
148 Destination::StackExtend => {
149 mem.stack.extend(format.into_parts())?;
150 }
151 }
152 }
153 if let Some(memory_area) = files_destination {
154 match memory_area {
155 Destination::Address(addr) => {
156 mem.set_composite(addr, import_files);
157 }
158 Destination::StackPush => {
159 mem.stack.push(import_files.into_parts());
160 }
161 Destination::StackExtend => {
162 mem.stack.extend(import_files.into_parts())?;
163 }
164 }
165 }
166
167 Ok(())
168 }
169}
170
171pub const ZOO_COORD_SYSTEM: coord::System = coord::System {
177 forward: coord::AxisDirectionPair {
178 axis: coord::Axis::Y,
179 direction: coord::Direction::Negative,
180 },
181 up: coord::AxisDirectionPair {
182 axis: coord::Axis::Z,
183 direction: coord::Direction::Positive,
184 },
185};
186
187fn from_vec_prim_to_res_opt_input_format(values: Vec<Primitive>) -> Result<Option<format::InputFormat>> {
188 let mut iter = values.iter();
189
190 let str_type = match iter.next() {
191 None => {
192 return Ok(None);
193 }
194 Some(Primitive::Nil) => {
195 return Ok(None);
196 }
197 Some(Primitive::String(str)) => str,
198 _ => {
199 return Err(ExecutionError::General {
200 reason: "missing type".to_string(),
201 });
202 }
203 };
204
205 return match str_type.as_str() {
206 "stl" => {
207 let Some(Primitive::String(str_units)) = iter.next() else {
208 return Err(ExecutionError::General {
209 reason: "missing units".to_string(),
210 });
211 };
212 let Some(Primitive::String(str_coords_forward_axis)) = iter.next() else {
213 return Err(ExecutionError::General {
214 reason: "missing coords.forward.axis".to_string(),
215 });
216 };
217 let Some(Primitive::String(str_coords_forward_direction)) = iter.next() else {
218 return Err(ExecutionError::General {
219 reason: "missing coords.forward.direction".to_string(),
220 });
221 };
222 let Some(Primitive::String(str_coords_up_axis)) = iter.next() else {
223 return Err(ExecutionError::General {
224 reason: "missing coords.up.axis".to_string(),
225 });
226 };
227 let Some(Primitive::String(str_coords_up_direction)) = iter.next() else {
228 return Err(ExecutionError::General {
229 reason: "missing coords.up.direction".to_string(),
230 });
231 };
232 Ok(Some(format::InputFormat::Stl(format::stl::import::Options {
233 coords: coord::System {
234 forward: coord::AxisDirectionPair {
235 axis: coord::Axis::from_str(str_coords_forward_axis).unwrap(),
236 direction: coord::Direction::from_str(str_coords_forward_direction).unwrap(),
237 },
238 up: coord::AxisDirectionPair {
239 axis: coord::Axis::from_str(str_coords_up_axis).unwrap(),
240 direction: coord::Direction::from_str(str_coords_up_direction).unwrap(),
241 },
242 },
243 units: units::UnitLength::from_str(str_units).unwrap(),
244 })))
245 }
246 _ => Err(ExecutionError::General {
247 reason: "unknown type".to_string(),
248 }),
249 };
250}
251
252fn from_input_format(type_: format::InputFormat) -> String {
254 match type_ {
255 format::InputFormat::Fbx(_) => "fbx".to_string(),
256 format::InputFormat::Gltf(_) => "gltf".to_string(),
257 format::InputFormat::Obj(_) => "obj".to_string(),
258 format::InputFormat::Ply(_) => "ply".to_string(),
259 format::InputFormat::Sldprt(_) => "sldprt".to_string(),
260 format::InputFormat::Step(_) => "step".to_string(),
261 format::InputFormat::Stl(_) => "stl".to_string(),
262 }
263}
264
265fn validate_extension_format(ext: format::InputFormat, given: format::InputFormat) -> Result<()> {
266 if let format::InputFormat::Stl(format::stl::import::Options { coords: _, units: _ }) = ext {
267 if let format::InputFormat::Stl(format::stl::import::Options { coords: _, units: _ }) = given {
268 return Ok(());
269 }
270 }
271
272 if let format::InputFormat::Obj(format::obj::import::Options { coords: _, units: _ }) = ext {
273 if let format::InputFormat::Obj(format::obj::import::Options { coords: _, units: _ }) = given {
274 return Ok(());
275 }
276 }
277
278 if let format::InputFormat::Ply(format::ply::import::Options { coords: _, units: _ }) = ext {
279 if let format::InputFormat::Ply(format::ply::import::Options { coords: _, units: _ }) = given {
280 return Ok(());
281 }
282 }
283
284 if ext == given {
285 return Ok(());
286 }
287
288 Err(ExecutionError::General {
289 reason: format!(
290 "The given format does not match the file extension. Expected: `{}`, Given: `{}`",
291 from_input_format(ext),
292 from_input_format(given)
293 ),
294 })
295}
296
297fn get_import_format_from_extension(ext: &str) -> Result<format::InputFormat> {
299 let format = match shared::FileImportFormat::from_str(ext) {
300 Ok(format) => format,
301 Err(_) => {
302 if ext == "stp" {
303 shared::FileImportFormat::Step
304 } else if ext == "glb" {
305 shared::FileImportFormat::Gltf
306 } else {
307 return Err(ExecutionError::General {
308 reason: format!(
309 "unknown source format for file extension: {}. Try setting the `--src-format` flag explicitly or use a valid format.",
310 ext
311 )
312 });
313 }
314 }
315 };
316
317 let ul = units::UnitLength::Millimeters;
319
320 match format {
326 shared::FileImportFormat::Step => Ok(format::InputFormat::Step(format::step::ImportOptions {})),
327 shared::FileImportFormat::Stl => Ok(format::InputFormat::Stl(format::stl::import::Options {
328 coords: ZOO_COORD_SYSTEM,
329 units: ul,
330 })),
331 shared::FileImportFormat::Obj => Ok(format::InputFormat::Obj(format::obj::import::Options {
332 coords: ZOO_COORD_SYSTEM,
333 units: ul,
334 })),
335 shared::FileImportFormat::Gltf => Ok(format::InputFormat::Gltf(format::gltf::import::Options::default())),
336 shared::FileImportFormat::Ply => Ok(format::InputFormat::Ply(format::ply::import::Options {
337 coords: ZOO_COORD_SYSTEM,
338 units: ul,
339 })),
340 shared::FileImportFormat::Fbx => Ok(format::InputFormat::Fbx(format::fbx::import::Options::default())),
341 shared::FileImportFormat::Sldprt => Ok(format::InputFormat::Sldprt(format::sldprt::import::Options::default())),
342 }
343}