1use l3d_rs::{from_buffer, L3d, Luminaire};
28use std::sync::Arc;
29
30uniffi::setup_scaffolding!();
31
32#[derive(Debug, thiserror::Error, uniffi::Error)]
34pub enum L3dError {
35 #[error("Failed to parse L3D data")]
36 ParseError,
37 #[error("Failed to read file: {0}")]
38 FileError(String),
39 #[error("JSON serialization error: {0}")]
40 JsonError(String),
41 #[error("Invalid data")]
42 InvalidData,
43}
44
45#[derive(Debug, Clone, uniffi::Record)]
47pub struct L3dVec3 {
48 pub x: f32,
49 pub y: f32,
50 pub z: f32,
51}
52
53#[derive(Debug, Clone, uniffi::Record)]
55pub struct L3dPart {
56 pub name: String,
58 pub path: String,
60 pub position: L3dVec3,
62 pub rotation: L3dVec3,
64 pub transform: Vec<f32>,
66}
67
68#[derive(Debug, Clone, uniffi::Record)]
70pub struct L3dLightEmitter {
71 pub name: String,
73 pub position: L3dVec3,
75 pub rotation: L3dVec3,
77 pub shape: String,
79 pub size_x: f64,
81 pub size_y: f64,
83}
84
85#[derive(Debug, Clone, uniffi::Record)]
87pub struct L3dAsset {
88 pub name: String,
90 pub content: Vec<u8>,
92}
93
94#[derive(uniffi::Object)]
96pub struct L3dFile {
97 inner: L3d,
98}
99
100#[uniffi::export]
101impl L3dFile {
102 #[uniffi::constructor]
104 pub fn new(data: Vec<u8>) -> Result<Arc<Self>, L3dError> {
105 let inner = from_buffer(&data);
106 if inner.file.structure.is_empty() {
107 return Err(L3dError::ParseError);
108 }
109 Ok(Arc::new(Self { inner }))
110 }
111
112 #[uniffi::constructor(name = "from_path")]
114 pub fn from_path(path: String) -> Result<Arc<Self>, L3dError> {
115 let data = std::fs::read(&path).map_err(|e| L3dError::FileError(e.to_string()))?;
116 Self::new(data)
117 }
118
119 pub fn get_structure_xml(&self) -> String {
121 self.inner.file.structure.clone()
122 }
123
124 pub fn to_json(&self) -> Result<String, L3dError> {
126 let luminaire =
127 Luminaire::from_xml(&self.inner.file.structure).map_err(|_| L3dError::ParseError)?;
128 luminaire
129 .to_json()
130 .map_err(|e| L3dError::JsonError(e.to_string()))
131 }
132
133 pub fn get_parts(&self) -> Vec<L3dPart> {
135 if let Ok(luminaire) = Luminaire::from_xml(&self.inner.file.structure) {
137 let mut parts = Vec::new();
138 extract_geometry_parts(
140 &luminaire.structure.geometry,
141 &mut parts,
142 &luminaire.geometry_definitions.geometry_file_definition,
143 &l3d_rs::MAT4_IDENTITY,
144 );
145 return parts;
146 }
147
148 self.inner
150 .model
151 .parts
152 .iter()
153 .map(|p| L3dPart {
154 name: String::new(),
155 path: p.path.clone(),
156 position: L3dVec3 {
157 x: p.mat[12],
158 y: p.mat[13],
159 z: p.mat[14],
160 },
161 rotation: L3dVec3 {
162 x: 0.0,
163 y: 0.0,
164 z: 0.0,
165 },
166 transform: p.mat.to_vec(),
167 })
168 .collect()
169 }
170
171 pub fn get_light_emitters(&self) -> Vec<L3dLightEmitter> {
173 let mut emitters = Vec::new();
174 if let Ok(luminaire) = Luminaire::from_xml(&self.inner.file.structure) {
175 extract_light_emitters(&luminaire.structure.geometry, &mut emitters);
176 }
177 emitters
178 }
179
180 pub fn get_assets(&self) -> Vec<L3dAsset> {
182 self.inner
183 .file
184 .assets
185 .iter()
186 .map(|a| L3dAsset {
187 name: a.name.clone(),
188 content: a.content.clone(),
189 })
190 .collect()
191 }
192
193 pub fn get_part_count(&self) -> u64 {
195 self.inner.model.parts.len() as u64
196 }
197
198 pub fn get_asset_count(&self) -> u64 {
200 self.inner.file.assets.len() as u64
201 }
202}
203
204fn extract_geometry_parts(
210 geometry: &l3d_rs::Geometry,
211 parts: &mut Vec<L3dPart>,
212 defs: &[l3d_rs::GeometryFileDefinition],
213 parent_transform: &[f32; 16],
214) {
215 let geom_id = &geometry.geometry_reference.geometry_id;
217 if let Some(def) = defs.iter().find(|d| &d.id == geom_id) {
218 let scale = l3d_rs::get_scale(&def.units);
219
220 let local_transform = l3d_rs::build_transform(&geometry.position, &geometry.rotation);
222
223 let accumulated = l3d_rs::mat4_mul(parent_transform, &local_transform);
225
226 let scale_mat = l3d_rs::mat4_scale(scale);
228 let final_transform = l3d_rs::mat4_mul(&accumulated, &scale_mat);
229
230 let world_position = L3dVec3 {
232 x: accumulated[12],
233 y: accumulated[13],
234 z: accumulated[14],
235 };
236
237 parts.push(L3dPart {
238 name: geometry.part_name.clone(),
239 path: format!("{}/{}", def.id, def.filename),
240 position: world_position,
241 rotation: L3dVec3 {
242 x: geometry.rotation.x,
243 y: geometry.rotation.y,
244 z: geometry.rotation.z,
245 },
246 transform: final_transform.to_vec(),
247 });
248
249 if let Some(joints) = &geometry.joints {
251 for joint in &joints.joint {
252 let joint_transform = l3d_rs::build_transform(&joint.position, &joint.rotation);
254 let joint_accumulated = l3d_rs::mat4_mul(&accumulated, &joint_transform);
255
256 for child_geom in &joint.geometries.geometry {
257 extract_geometry_parts(child_geom, parts, defs, &joint_accumulated);
258 }
259 }
260 }
261 } else {
262 let local_transform = l3d_rs::build_transform(&geometry.position, &geometry.rotation);
264 let accumulated = l3d_rs::mat4_mul(parent_transform, &local_transform);
265
266 if let Some(joints) = &geometry.joints {
267 for joint in &joints.joint {
268 let joint_transform = l3d_rs::build_transform(&joint.position, &joint.rotation);
269 let joint_accumulated = l3d_rs::mat4_mul(&accumulated, &joint_transform);
270
271 for child_geom in &joint.geometries.geometry {
272 extract_geometry_parts(child_geom, parts, defs, &joint_accumulated);
273 }
274 }
275 }
276 }
277}
278
279fn extract_light_emitters(geometry: &l3d_rs::Geometry, emitters: &mut Vec<L3dLightEmitter>) {
281 if let Some(leo) = &geometry.light_emitting_objects {
283 if let Ok(json) = serde_json::to_value(leo) {
285 if let Some(objects) = json.get("LightEmittingObject").and_then(|v| v.as_array()) {
286 for obj in objects {
287 let name = obj
288 .get("@partName")
289 .and_then(|v| v.as_str())
290 .unwrap_or("")
291 .to_string();
292
293 let position = obj
294 .get("Position")
295 .map(|p| L3dVec3 {
296 x: p.get("@x").and_then(|v| v.as_f64()).unwrap_or(0.0) as f32,
297 y: p.get("@y").and_then(|v| v.as_f64()).unwrap_or(0.0) as f32,
298 z: p.get("@z").and_then(|v| v.as_f64()).unwrap_or(0.0) as f32,
299 })
300 .unwrap_or(L3dVec3 {
301 x: 0.0,
302 y: 0.0,
303 z: 0.0,
304 });
305
306 let rotation = obj
307 .get("Rotation")
308 .map(|r| L3dVec3 {
309 x: r.get("@x").and_then(|v| v.as_f64()).unwrap_or(0.0) as f32,
310 y: r.get("@y").and_then(|v| v.as_f64()).unwrap_or(0.0) as f32,
311 z: r.get("@z").and_then(|v| v.as_f64()).unwrap_or(0.0) as f32,
312 })
313 .unwrap_or(L3dVec3 {
314 x: 0.0,
315 y: 0.0,
316 z: 0.0,
317 });
318
319 let (shape, size_x, size_y) = if let Some(rect) = obj.get("Rectangle") {
320 (
321 "rectangle".to_string(),
322 rect.get("@sizeX").and_then(|v| v.as_f64()).unwrap_or(0.0),
323 rect.get("@sizeY").and_then(|v| v.as_f64()).unwrap_or(0.0),
324 )
325 } else if let Some(circle) = obj.get("Circle") {
326 (
327 "circle".to_string(),
328 circle
329 .get("@diameter")
330 .and_then(|v| v.as_f64())
331 .unwrap_or(0.0),
332 0.0,
333 )
334 } else {
335 ("unknown".to_string(), 0.0, 0.0)
336 };
337
338 emitters.push(L3dLightEmitter {
339 name,
340 position,
341 rotation,
342 shape,
343 size_x,
344 size_y,
345 });
346 }
347 }
348 }
349 }
350
351 if let Some(joints) = &geometry.joints {
353 for joint in &joints.joint {
354 for child_geom in &joint.geometries.geometry {
355 extract_light_emitters(child_geom, emitters);
356 }
357 }
358 }
359}
360
361#[uniffi::export]
363pub fn version() -> String {
364 env!("CARGO_PKG_VERSION").to_string()
365}