session_rust/
session.rs

1use crate::{
2    Arrow, BoundingBox, Cylinder, Graph, Line, Mesh, Objects, Plane, Point, PointCloud, Polyline,
3    Tree, TreeNode,
4};
5use serde::{Deserialize, Serialize};
6use std::collections::HashMap;
7use std::fmt;
8use std::fs;
9use uuid::Uuid;
10
11/// Enum representing all possible geometry types in a Session.
12/// This is equivalent to C++'s std::variant<...> for heterogeneous geometry storage.
13#[derive(Debug, Clone)]
14pub enum Geometry {
15    Arrow(Arrow),
16    BoundingBox(BoundingBox),
17    Cylinder(Cylinder),
18    Line(Line),
19    Mesh(Mesh),
20    Plane(Plane),
21    Point(Point),
22    PointCloud(PointCloud),
23    Polyline(Polyline),
24}
25
26impl Geometry {
27    /// Get the GUID of the geometry object
28    pub fn guid(&self) -> &str {
29        match self {
30            Geometry::Arrow(g) => &g.guid,
31            Geometry::BoundingBox(g) => &g.guid,
32            Geometry::Cylinder(g) => &g.guid,
33            Geometry::Line(g) => &g.guid,
34            Geometry::Mesh(g) => &g.guid,
35            Geometry::Plane(g) => &g.guid,
36            Geometry::Point(g) => &g.guid,
37            Geometry::PointCloud(g) => &g.guid,
38            Geometry::Polyline(g) => &g.guid,
39        }
40    }
41}
42
43/// A Session containing geometry objects with hierarchical and graph structures.
44///
45/// The Session serves as a container for managing geometry objects (currently Points)
46/// along with their relationships through tree and graph data structures. It provides
47/// JSON serialization capabilities for cross-language interoperability.
48#[derive(Debug, Clone, Serialize, Deserialize)]
49#[serde(tag = "type", rename = "Session")]
50pub struct Session {
51    /// Unique identifier for the session
52    pub guid: String,
53    /// Human-readable name for the session
54    pub name: String,
55    /// Collection of geometry objects (Points)
56    #[serde(rename = "objects")]
57    pub objects: Objects,
58    /// Lookup table mapping object GUIDs to geometry objects (fast heterogeneous lookup)
59    #[serde(skip)]
60    pub lookup: HashMap<String, Geometry>,
61    /// Hierarchical tree structure for organizing objects
62    #[serde(rename = "tree")]
63    pub tree: Tree,
64    /// Graph structure for representing object relationships
65    #[serde(rename = "graph")]
66    pub graph: Graph,
67}
68
69impl Default for Session {
70    /// Creates a default Session with the name "my_session".
71    fn default() -> Self {
72        Self::new("my_session")
73    }
74}
75
76impl Session {
77    /// Creates a new Session with the specified name.
78    ///
79    /// # Arguments
80    /// * `name` - The name for the session
81    ///
82    /// # Returns
83    /// A new Session instance with a unique GUID, empty objects collection,
84    /// and initialized tree and graph structures.
85    pub fn new(name: &str) -> Self {
86        let guid = Uuid::new_v4().to_string();
87        let objects = Objects::new();
88        let lookup = HashMap::new();
89        let mut tree = Tree::new(&format!("{name}_tree"));
90        let graph = Graph::new(&format!("{name}_graph"));
91
92        // Create empty root node with session name
93        let root_node = TreeNode::new(name);
94        tree.add(&root_node, None);
95
96        Self {
97            guid,
98            name: name.to_string(),
99            objects,
100            lookup,
101            tree,
102            graph,
103        }
104    }
105
106    ///////////////////////////////////////////////////////////////////////////////////////////
107    // JSON
108    ///////////////////////////////////////////////////////////////////////////////////////////
109
110    /// Serializes the Session to a JSON string.
111    ///
112    /// # Returns
113    /// A Result containing the JSON string representation of the Session,
114    /// or an error if serialization fails.
115    pub fn jsondump(&self) -> Result<String, Box<dyn std::error::Error>> {
116        // Use custom serialization to ensure consistent structure with C++/Python
117        // Convert graph to use array structure instead of nested objects
118        let graph_json: serde_json::Value = serde_json::from_str(&self.graph.jsondump()?)?;
119
120        let json_obj = serde_json::json!({
121            "type": "Session",
122            "guid": self.guid,
123            "name": self.name,
124            "objects": self.objects,
125            "tree": self.tree,
126            "graph": graph_json
127        });
128
129        Ok(serde_json::to_string_pretty(&json_obj)?)
130    }
131
132    /// Deserializes Session from a JSON string.
133    ///
134    /// # Arguments
135    /// * `json_data` - The JSON string to deserialize
136    ///
137    /// # Returns
138    /// A Result containing the deserialized Session, or an error if parsing fails.
139    pub fn jsonload(json_data: &str) -> Result<Self, Box<dyn std::error::Error>> {
140        let json_obj: serde_json::Value = serde_json::from_str(json_data)?;
141
142        // Deserialize components using their custom methods
143        let objects: Objects = serde_json::from_value(json_obj["objects"].clone())?;
144        let tree: Tree = serde_json::from_value(json_obj["tree"].clone())?;
145        // Convert graph JSON value to properly formatted string
146        let graph_json_str = serde_json::to_string(&json_obj["graph"])?;
147        let graph: Graph = Graph::jsonload(&graph_json_str)?;
148
149        // Rebuild lookup table from all objects
150        let mut lookup = HashMap::new();
151        for arrow in &objects.arrows {
152            lookup.insert(arrow.guid.clone(), Geometry::Arrow(arrow.clone()));
153        }
154        for bbox in &objects.bboxes {
155            lookup.insert(bbox.guid.clone(), Geometry::BoundingBox(bbox.clone()));
156        }
157        for cylinder in &objects.cylinders {
158            lookup.insert(cylinder.guid.clone(), Geometry::Cylinder(cylinder.clone()));
159        }
160        for line in &objects.lines {
161            lookup.insert(line.guid.clone(), Geometry::Line(line.clone()));
162        }
163        for mesh in &objects.meshes {
164            lookup.insert(mesh.guid.clone(), Geometry::Mesh(mesh.clone()));
165        }
166        for plane in &objects.planes {
167            lookup.insert(plane.guid.clone(), Geometry::Plane(plane.clone()));
168        }
169        for point in &objects.points {
170            lookup.insert(point.guid.clone(), Geometry::Point(point.clone()));
171        }
172        for pointcloud in &objects.pointclouds {
173            lookup.insert(
174                pointcloud.guid.clone(),
175                Geometry::PointCloud(pointcloud.clone()),
176            );
177        }
178        for polyline in &objects.polylines {
179            lookup.insert(polyline.guid.clone(), Geometry::Polyline(polyline.clone()));
180        }
181
182        let session = Session {
183            guid: json_obj["guid"].as_str().unwrap_or("").to_string(),
184            name: json_obj["name"]
185                .as_str()
186                .unwrap_or("my_session")
187                .to_string(),
188            objects,
189            lookup,
190            tree,
191            graph,
192        };
193
194        Ok(session)
195    }
196
197    /// Serializes the Session to a JSON file.
198    ///
199    /// # Arguments
200    /// * `filepath` - The path where the JSON file will be written
201    ///
202    /// # Returns
203    /// A Result indicating success or failure of the file write operation.
204    pub fn to_json(&self, filepath: &str) -> Result<(), Box<dyn std::error::Error>> {
205        let json = self.jsondump()?;
206        fs::write(filepath, json)?;
207        Ok(())
208    }
209
210    /// Deserializes Session from a JSON file.
211    ///
212    /// # Arguments
213    /// * `filepath` - The path to the JSON file to read
214    ///
215    /// # Returns
216    /// A Result containing the deserialized Session, or an error if file reading or parsing fails.
217    pub fn from_json(filepath: &str) -> Result<Self, Box<dyn std::error::Error>> {
218        let json = fs::read_to_string(filepath)?;
219        Self::jsonload(&json)
220    }
221
222    ///////////////////////////////////////////////////////////////////////////////////////////
223    // Details
224    ///////////////////////////////////////////////////////////////////////////////////////////
225
226    /// Adds a point to the Session.
227    ///
228    /// The point is added to the objects collection, lookup table, graph as a node,
229    /// and tree as a child of the root node.
230    ///
231    /// # Arguments
232    /// * `point` - The Point object to add to the session
233    ///
234    /// # Returns
235    /// The TreeNode created for this point
236    pub fn add_point(&mut self, point: Point) -> TreeNode {
237        let point_guid = point.guid.clone();
238        let point_name = point.name.clone();
239        self.objects.points.push(point.clone());
240        self.lookup
241            .insert(point_guid.clone(), Geometry::Point(point));
242        self.graph
243            .add_node(&point_guid, &format!("point_{point_name}"));
244        TreeNode::new(&point_guid)
245    }
246
247    pub fn add_line(&mut self, line: Line) -> TreeNode {
248        let guid = line.guid.clone();
249        let name = line.name.clone();
250        self.objects.lines.push(line.clone());
251        self.lookup.insert(guid.clone(), Geometry::Line(line));
252        self.graph.add_node(&guid, &format!("line_{name}"));
253        TreeNode::new(&guid)
254    }
255
256    pub fn add_plane(&mut self, plane: Plane) -> TreeNode {
257        let guid = plane.guid.clone();
258        let name = plane.name.clone();
259        self.objects.planes.push(plane.clone());
260        self.lookup.insert(guid.clone(), Geometry::Plane(plane));
261        self.graph.add_node(&guid, &format!("plane_{name}"));
262        TreeNode::new(&guid)
263    }
264
265    pub fn add_bbox(&mut self, bbox: BoundingBox) -> TreeNode {
266        let guid = bbox.guid.clone();
267        let name = bbox.name.clone();
268        self.objects.bboxes.push(bbox.clone());
269        self.lookup
270            .insert(guid.clone(), Geometry::BoundingBox(bbox));
271        self.graph.add_node(&guid, &format!("bbox_{name}"));
272        TreeNode::new(&guid)
273    }
274
275    pub fn add_polyline(&mut self, polyline: Polyline) -> TreeNode {
276        let guid = polyline.guid.clone();
277        let name = polyline.name.clone();
278        self.objects.polylines.push(polyline.clone());
279        self.lookup
280            .insert(guid.clone(), Geometry::Polyline(polyline));
281        self.graph.add_node(&guid, &format!("polyline_{name}"));
282        TreeNode::new(&guid)
283    }
284
285    pub fn add_pointcloud(&mut self, pointcloud: PointCloud) -> TreeNode {
286        let guid = pointcloud.guid.clone();
287        let name = pointcloud.name.clone();
288        self.objects.pointclouds.push(pointcloud.clone());
289        self.lookup
290            .insert(guid.clone(), Geometry::PointCloud(pointcloud));
291        self.graph.add_node(&guid, &format!("pointcloud_{name}"));
292        TreeNode::new(&guid)
293    }
294
295    pub fn add_mesh(&mut self, mesh: Mesh) -> TreeNode {
296        let guid = mesh.guid.clone();
297        let name = mesh.name.clone();
298        self.objects.meshes.push(mesh.clone());
299        self.lookup.insert(guid.clone(), Geometry::Mesh(mesh));
300        self.graph.add_node(&guid, &format!("mesh_{name}"));
301        TreeNode::new(&guid)
302    }
303
304    pub fn add_cylinder(&mut self, cylinder: Cylinder) -> TreeNode {
305        let guid = cylinder.guid.clone();
306        let name = cylinder.name.clone();
307        self.objects.cylinders.push(cylinder.clone());
308        self.lookup
309            .insert(guid.clone(), Geometry::Cylinder(cylinder));
310        self.graph.add_node(&guid, &format!("cylinder_{name}"));
311        TreeNode::new(&guid)
312    }
313
314    pub fn add_arrow(&mut self, arrow: Arrow) -> TreeNode {
315        let guid = arrow.guid.clone();
316        let name = arrow.name.clone();
317        self.objects.arrows.push(arrow.clone());
318        self.lookup.insert(guid.clone(), Geometry::Arrow(arrow));
319        self.graph.add_node(&guid, &format!("arrow_{name}"));
320        TreeNode::new(&guid)
321    }
322
323    /// Adds a TreeNode to the tree hierarchy.
324    ///
325    /// # Arguments
326    /// * `node` - The TreeNode to add
327    /// * `parent` - Optional parent TreeNode (defaults to root if None)
328    ///
329    /// # Examples
330    /// ```
331    /// use session_rust::{Session, TreeNode};
332    ///
333    /// let mut session = Session::new("my_session");
334    /// let node = TreeNode::new("my_node");
335    ///
336    /// // Add to root (pass None)
337    /// session.add(&node, None);
338    ///
339    /// // Add under parent (pass reference directly)
340    /// let parent = TreeNode::new("parent");
341    /// session.add(&parent, None);
342    /// session.add(&node, &parent);
343    /// ```
344    pub fn add<'a>(&mut self, node: &TreeNode, parent: impl Into<Option<&'a TreeNode>>)
345    where
346        TreeNode: 'a,
347    {
348        let parent_opt = parent.into();
349        if parent_opt.is_none() {
350            if let Some(root) = self.tree.root() {
351                self.tree.add(node, Some(&root));
352            }
353        } else {
354            self.tree.add(node, parent_opt);
355        }
356    }
357
358    /// Adds an edge between two geometry objects in the graph.
359    ///
360    /// # Arguments
361    /// * `from_guid` - The GUID of the source object
362    /// * `to_guid` - The GUID of the target object
363    /// * `attribute` - The attribute or label for the edge
364    pub fn add_edge(&mut self, from_guid: &str, to_guid: &str, attribute: &str) {
365        self.graph.add_edge(from_guid, to_guid, attribute);
366    }
367
368    ///////////////////////////////////////////////////////////////////////////////////////////
369    // Details - Lookup
370    ///////////////////////////////////////////////////////////////////////////////////////////
371
372    /// Gets a geometry object by its GUID.
373    ///
374    /// # Arguments
375    /// * `guid` - The GUID of the object to retrieve
376    ///
377    /// # Returns
378    /// An Option containing a reference to the Geometry enum if found, or None if not found.
379    pub fn get_object(&self, guid: &str) -> Option<&Geometry> {
380        self.lookup.get(guid)
381    }
382
383    /// Remove a geometry object by its GUID.
384    ///
385    /// # Arguments
386    /// * `guid` - The UUID of the geometry object to remove.
387    ///
388    /// # Returns
389    /// `true` if the object was removed, `false` if not found.
390    pub fn remove_object(&mut self, guid: &str) -> bool {
391        // Check if object exists in lookup table
392        if !self.lookup.contains_key(guid) {
393            return false;
394        }
395
396        // Remove from points collection
397        // Note: In Rust, the field is `vec` but serialized as "points" in JSON
398        self.objects.points.retain(|point| point.guid != guid);
399
400        // Remove from lookup table
401        self.lookup.remove(guid);
402
403        // Remove from tree - find node by GUID and remove it
404        if let Some(node) = self.tree.find_node_by_guid(&guid.to_string()) {
405            self.tree.remove(&node);
406        }
407
408        // Remove from graph using string GUID
409        if self.graph.has_node(guid) {
410            self.graph.remove_node(guid);
411        }
412
413        true
414    }
415
416    ///////////////////////////////////////////////////////////////////////////////////////////
417    // Details - Tree
418    ///////////////////////////////////////////////////////////////////////////////////////////
419
420    /// Add a parent-child relationship in the tree structure.
421    ///
422    /// # Arguments
423    /// * `parent_guid` - The GUID of the parent geometry object.
424    /// * `child_guid` - The GUID of the child geometry object.
425    ///
426    /// # Returns
427    /// `true` if the relationship was added successfully.
428    pub fn add_hierarchy(&mut self, parent_guid: &str, child_guid: &str) -> bool {
429        self.tree
430            .add_child_by_guid(&parent_guid.to_string(), &child_guid.to_string())
431    }
432
433    /// Get all children GUIDs of a geometry object in the tree.
434    ///
435    /// # Arguments
436    /// * `guid` - The GUID of the geometry object.
437    ///
438    /// # Returns
439    /// A vector containing the GUIDs of all children of the specified geometry object.
440    pub fn get_children(&self, guid: &str) -> Vec<String> {
441        self.tree.get_children(guid)
442    }
443
444    ///////////////////////////////////////////////////////////////////////////////////////////
445    // Details - Graph
446    ///////////////////////////////////////////////////////////////////////////////////////////
447
448    /// Add a relationship edge in the graph structure.
449    ///
450    /// # Arguments
451    /// * `from_guid` - The GUID of the source geometry object.
452    /// * `to_guid` - The GUID of the target geometry object.
453    /// * `relationship_type` - The type of relationship.
454    pub fn add_relationship(&mut self, from_guid: &str, to_guid: &str, relationship_type: &str) {
455        self.graph.add_edge(from_guid, to_guid, relationship_type);
456    }
457
458    /// Get all GUIDs connected to the given GUID in the graph.
459    ///
460    /// # Arguments
461    /// * `guid` - The GUID of the geometry object.
462    ///
463    /// # Returns
464    /// A vector containing the GUIDs of all connected geometry objects.
465    pub fn get_neighbours(&self, guid: &str) -> Vec<String> {
466        self.graph.get_neighbors(guid)
467    }
468}
469
470impl fmt::Display for Session {
471    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
472        write!(
473            f,
474            "Session({}, {}, points={}, vertices={}, edges={})",
475            self.name,
476            self.guid,
477            self.objects.points.len(),
478            self.graph.vertex_count,
479            self.graph.edge_count
480        )
481    }
482}
483
484#[cfg(test)]
485#[path = "session_test.rs"]
486mod session_test;