differt-core 0.10.0__tar.gz → 0.11.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. {differt_core-0.10.0 → differt_core-0.11.0}/Cargo.lock +1 -1
  2. {differt_core-0.10.0 → differt_core-0.11.0}/PKG-INFO +1 -1
  3. {differt_core-0.10.0 → differt_core-0.11.0}/differt-core/Cargo.toml +1 -1
  4. {differt_core-0.10.0 → differt_core-0.11.0}/differt-core/src/geometry/mesh.rs +41 -25
  5. {differt_core-0.10.0 → differt_core-0.11.0}/differt-core/src/geometry/scene.rs +14 -19
  6. {differt_core-0.10.0 → differt_core-0.11.0/differt-core}/src/geometry/sionna.rs +89 -4
  7. {differt_core-0.10.0 → differt_core-0.11.0}/differt-core/tests/geometry/test_graph.py +0 -27
  8. {differt_core-0.10.0 → differt_core-0.11.0}/python/differt_core/_differt_core/geometry/graph.pyi +0 -1
  9. {differt_core-0.10.0 → differt_core-0.11.0}/python/differt_core/_differt_core/geometry/mesh.pyi +4 -3
  10. {differt_core-0.10.0 → differt_core-0.11.0}/python/differt_core/_differt_core/geometry/scene.pyi +3 -1
  11. {differt_core-0.10.0 → differt_core-0.11.0}/python/differt_core/_differt_core/geometry/sionna.pyi +3 -1
  12. {differt_core-0.10.0 → differt_core-0.11.0}/src/geometry/mesh.rs +41 -25
  13. {differt_core-0.10.0 → differt_core-0.11.0}/src/geometry/scene.rs +14 -19
  14. {differt_core-0.10.0/differt-core → differt_core-0.11.0}/src/geometry/sionna.rs +89 -4
  15. {differt_core-0.10.0 → differt_core-0.11.0}/Cargo.toml +0 -0
  16. {differt_core-0.10.0 → differt_core-0.11.0}/LICENSE.md +0 -0
  17. {differt_core-0.10.0 → differt_core-0.11.0}/README.md +0 -0
  18. {differt_core-0.10.0 → differt_core-0.11.0}/differt-core/LICENSE.md +0 -0
  19. {differt_core-0.10.0 → differt_core-0.11.0}/differt-core/README.md +0 -0
  20. {differt_core-0.10.0 → differt_core-0.11.0}/differt-core/benches/bench_main.rs +0 -0
  21. {differt_core-0.10.0 → differt_core-0.11.0}/differt-core/benches/benchmarks/graph_iterators.rs +0 -0
  22. {differt_core-0.10.0 → differt_core-0.11.0}/differt-core/benches/benchmarks/mod.rs +0 -0
  23. {differt_core-0.10.0 → differt_core-0.11.0}/differt-core/src/geometry/graph.rs +0 -0
  24. {differt_core-0.10.0 → differt_core-0.11.0}/differt-core/src/geometry/mod.rs +0 -0
  25. {differt_core-0.10.0 → differt_core-0.11.0}/differt-core/src/lib.rs +0 -0
  26. {differt_core-0.10.0 → differt_core-0.11.0}/differt-core/tests/__init__.py +0 -0
  27. {differt_core-0.10.0 → differt_core-0.11.0}/differt-core/tests/geometry/__init__.py +0 -0
  28. {differt_core-0.10.0 → differt_core-0.11.0}/differt-core/tests/geometry/test_core_mesh.py +0 -0
  29. {differt_core-0.10.0 → differt_core-0.11.0}/differt-core/tests/geometry/test_core_scene.py +0 -0
  30. {differt_core-0.10.0 → differt_core-0.11.0}/differt-core/tests/test_version.py +0 -0
  31. {differt_core-0.10.0 → differt_core-0.11.0}/pyproject.toml +0 -0
  32. {differt_core-0.10.0 → differt_core-0.11.0}/python/differt_core/__init__.py +0 -0
  33. {differt_core-0.10.0 → differt_core-0.11.0}/python/differt_core/_differt_core/__init__.pyi +0 -0
  34. {differt_core-0.10.0 → differt_core-0.11.0}/python/differt_core/geometry/__init__.py +0 -0
  35. {differt_core-0.10.0 → differt_core-0.11.0}/python/differt_core/geometry/_graph.py +0 -0
  36. {differt_core-0.10.0 → differt_core-0.11.0}/python/differt_core/geometry/_mesh.py +0 -0
  37. {differt_core-0.10.0 → differt_core-0.11.0}/python/differt_core/geometry/_scene.py +0 -0
  38. {differt_core-0.10.0 → differt_core-0.11.0}/python/differt_core/geometry/_sionna.py +0 -0
  39. {differt_core-0.10.0 → differt_core-0.11.0}/python/differt_core/py.typed +0 -0
  40. {differt_core-0.10.0 → differt_core-0.11.0}/src/geometry/graph.rs +0 -0
  41. {differt_core-0.10.0 → differt_core-0.11.0}/src/geometry/mod.rs +0 -0
  42. {differt_core-0.10.0 → differt_core-0.11.0}/src/lib.rs +0 -0
@@ -247,7 +247,7 @@ checksum = "43da5946c66ffcc7745f48db692ffbb10a83bfe0afd96235c5c2a4fb23994929"
247
247
 
248
248
  [[package]]
249
249
  name = "differt-core"
250
- version = "0.10.0"
250
+ version = "0.11.0"
251
251
  dependencies = [
252
252
  "criterion",
253
253
  "indexmap",
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: differt-core
3
- Version: 0.10.0
3
+ Version: 0.11.0
4
4
  Classifier: Programming Language :: Python :: 3
5
5
  Classifier: Programming Language :: Python :: 3.11
6
6
  Classifier: Programming Language :: Python :: 3.12
@@ -37,5 +37,5 @@ unexpected_cfgs = {level = "warn", check-cfg = ['cfg(tarpaulin_include)']}
37
37
  edition = "2021"
38
38
  name = "differt-core"
39
39
  rust-version = "1.78.0"
40
- version = "0.10.0"
40
+ version = "0.11.0"
41
41
  readme = "README.md"
@@ -1,4 +1,8 @@
1
- use std::{fs::File, io::BufReader, path::PathBuf};
1
+ use std::{
2
+ fs::File,
3
+ io::BufReader,
4
+ path::{Path, PathBuf},
5
+ };
2
6
 
3
7
  use indexmap::IndexMap;
4
8
  use numpy::{PyArray1, PyArray2, ndarray::arr2};
@@ -273,22 +277,48 @@ impl Mesh {
273
277
  /// vertices.
274
278
  ///
275
279
  /// Args:
276
- /// file (str): The path to the Wavefront .obj file.
280
+ /// file (str | os.PathLike[str]): The path to the Wavefront .obj file.
277
281
  ///
278
282
  /// Returns:
279
283
  /// Mesh: The corresponding mesh containing only triangles.
280
284
  #[classmethod]
281
- pub(crate) fn load_obj(_: &Bound<'_, PyType>, filename: &str) -> PyResult<Self> {
282
- let input = BufReader::new(File::open(filename)?);
285
+ #[pyo3(name = "load_obj")]
286
+ pub(crate) fn py_load_obj(_: &Bound<'_, PyType>, file: PathBuf) -> PyResult<Self> {
287
+ Self::load_obj(&file)
288
+ }
289
+
290
+ /// Load a triangle mesh from a Stanford PLY .ply file.
291
+ ///
292
+ /// Currently, only vertices and triangles are loaded. Triangle normals
293
+ /// are ignored because they are computed with
294
+ /// :attr:`differt.geometry.Mesh.normals` using
295
+ /// JAX so that they can be differentiated with respect to triangle
296
+ /// vertices.
297
+ ///
298
+ /// Args:
299
+ /// file (str | os.PathLike[str]): The path to the Stanford PLY .ply file.
300
+ ///
301
+ /// Returns:
302
+ /// Mesh: The corresponding mesh containing only triangles.
303
+ #[classmethod]
304
+ #[pyo3(name = "load_ply")]
305
+ pub(crate) fn py_load_ply(_: &Bound<'_, PyType>, file: PathBuf) -> PyResult<Self> {
306
+ Self::load_ply(&file)
307
+ }
308
+ }
309
+
310
+ impl Mesh {
311
+ pub(crate) fn load_obj(file: &Path) -> PyResult<Self> {
312
+ let input = BufReader::new(File::open(file)?);
283
313
  let mut obj: RawObj = parse_obj(input).map_err(|err| {
284
314
  PyValueError::new_err(format!(
285
- "An error occurred while reading obj file {filename:#?}: {}",
315
+ "An error occurred while reading obj file {file:#?}: {}",
286
316
  err
287
317
  ))
288
318
  })?;
289
319
 
290
320
  for material_file in obj.material_libraries.iter_mut() {
291
- let mut path = PathBuf::from(filename);
321
+ let mut path = file.to_path_buf();
292
322
  path.set_file_name(&material_file);
293
323
  if let Some(path_str) = path.to_str() {
294
324
  *material_file = path_str.to_string();
@@ -298,29 +328,15 @@ impl Mesh {
298
328
  Ok(obj.into())
299
329
  }
300
330
 
301
- /// Load a triangle mesh from a Stanford PLY .ply file.
302
- ///
303
- /// Currently, only vertices and triangles are loaded. Triangle normals
304
- /// are ignored because they are computed with
305
- /// :attr:`differt.geometry.Mesh.normals` using
306
- /// JAX so that they can be differentiated with respect to triangle
307
- /// vertices.
308
- ///
309
- /// Args:
310
- /// file (str): The path to the Stanford PLY .ply file.
311
- ///
312
- /// Returns:
313
- /// Mesh: The corresponding mesh containing only triangles.
314
- #[classmethod]
315
- pub(crate) fn load_ply(_: &Bound<'_, PyType>, filename: &str) -> PyResult<Self> {
316
- let mut input = BufReader::new(File::open(filename)?);
331
+ pub(crate) fn load_ply(file: &Path) -> PyResult<Self> {
332
+ let mut input = BufReader::new(File::open(file)?);
317
333
 
318
334
  let vertex_parser = parser::Parser::<PlyVertex>::new();
319
335
  let face_parser = parser::Parser::<PlyFace>::new();
320
336
 
321
337
  let header = vertex_parser.read_header(&mut input).map_err(|err| {
322
338
  PyValueError::new_err(format!(
323
- "An error occurred while reading the header of ply file {filename:#?}: {}",
339
+ "An error occurred while reading the header of ply file {file:#?}: {}",
324
340
  err
325
341
  ))
326
342
  })?;
@@ -337,7 +353,7 @@ impl Mesh {
337
353
  .map_err(|err| {
338
354
  PyValueError::new_err(format!(
339
355
  "An error occurred while reading the vertex elements of ply \
340
- file {filename:#?}: {}",
356
+ file {file:#?}: {}",
341
357
  err
342
358
  ))
343
359
  })?
@@ -352,7 +368,7 @@ impl Mesh {
352
368
  .map_err(|err| {
353
369
  PyValueError::new_err(format!(
354
370
  "An error occurred while reading the face elements of PLY \
355
- file {filename:#?}: {}",
371
+ file {file:#?}: {}",
356
372
  err
357
373
  ))
358
374
  })?
@@ -1,4 +1,4 @@
1
- use std::path::PathBuf;
1
+ use std::path::{Path, PathBuf};
2
2
 
3
3
  use pyo3::{exceptions::PyValueError, prelude::*, types::PyType};
4
4
 
@@ -22,38 +22,33 @@ impl Scene {
22
22
  /// Load a scene from a Sionna-compatible XML file.
23
23
  ///
24
24
  /// Args:
25
- /// file (str): The path to the XML file.
25
+ /// file (str | os.PathLike[str]): The path to the XML file.
26
26
  ///
27
27
  /// Returns:
28
28
  /// Scene: The corresponding scene.
29
29
  #[classmethod]
30
- fn load_xml(cls: &Bound<'_, PyType>, file: &str) -> PyResult<Self> {
31
- // TODO: create a Rust variant without PyType?
32
- let sionna_scene_py_type = PyType::new::<SionnaScene>(cls.py());
33
- let sionna = SionnaScene::load_xml(&sionna_scene_py_type, file)?;
30
+ #[pyo3(name = "load_xml")]
31
+ fn py_load_xml(_cls: &Bound<'_, PyType>, file: PathBuf) -> PyResult<Self> {
32
+ Self::load_xml(&file)
33
+ }
34
+ }
35
+ impl Scene {
36
+ fn load_xml(file: &Path) -> PyResult<Self> {
37
+ let sionna = SionnaScene::load_xml(&file)?;
34
38
 
35
- let path = PathBuf::from(file);
36
- let folder = path.parent().ok_or_else(|| {
39
+ let folder = file.parent().ok_or_else(|| {
37
40
  PyValueError::new_err(format!(
38
- "Could not determine parent folder of file: {}",
39
- file
41
+ "Could not determine parent folder of file: {file:#?}",
40
42
  ))
41
43
  })?;
42
44
 
43
45
  let mut mesh = Mesh::default();
44
46
 
45
- let mesh_py_type = PyType::new::<Mesh>(cls.py());
46
-
47
47
  for (_, shape) in sionna.shapes.into_iter() {
48
48
  let mesh_file_path = folder.join(shape.file);
49
- let mesh_file = mesh_file_path.to_str().ok_or_else(|| {
50
- PyValueError::new_err(format!(
51
- "Could not convert path {mesh_file_path:?} to valid unicode string"
52
- ))
53
- })?;
54
49
  let mut other_mesh = match shape.r#type.as_str() {
55
- "obj" => Mesh::load_obj(&mesh_py_type, mesh_file)?,
56
- "ply" => Mesh::load_ply(&mesh_py_type, mesh_file)?,
50
+ "obj" => Mesh::load_obj(&mesh_file_path)?,
51
+ "ply" => Mesh::load_ply(&mesh_file_path)?,
57
52
  ty => {
58
53
  log::warn!("Unsupported shape type {ty}, skipping.");
59
54
  continue;
@@ -1,4 +1,8 @@
1
- use std::{fs::File, io::BufReader};
1
+ use std::{
2
+ fs::File,
3
+ io::BufReader,
4
+ path::{Path, PathBuf},
5
+ };
2
6
 
3
7
  use indexmap::IndexMap;
4
8
  use pyo3::{exceptions::PyValueError, prelude::*, types::PyType};
@@ -248,7 +252,7 @@ impl<'de> Deserialize<'de> for Material {
248
252
  let color = match r#type.as_str() {
249
253
  // Copied from Sionna-RT's code, to match their colors:
250
254
  // https://github.com/NVlabs/sionna-rt/blob/main/src/sionna/rt/radio_materials/itu_material.py
251
- // v1.0.0
255
+ "vacuum" => [0.8, 0.8, 0.8],
252
256
  "marble" => [0.701, 0.644, 0.485],
253
257
  "concrete" => [0.539, 0.539, 0.539],
254
258
  "wood" => [0.266, 0.109, 0.060],
@@ -263,6 +267,10 @@ impl<'de> Deserialize<'de> for Material {
263
267
  "very_dry_ground" => [0.539, 0.319, 0.223],
264
268
  "medium_dry_ground" => [0.539, 0.181, 0.076],
265
269
  "wet_ground" => [0.539, 0.027, 0.147],
270
+ "clear_acrylic" => [0.8, 0.9, 0.95],
271
+ "vinyl_tile" => [0.75, 0.75, 0.72],
272
+ "carpet_tile" => [0.32, 0.35, 0.42],
273
+ "asphalt_concrete" => [0.18, 0.18, 0.18],
266
274
  _ => {
267
275
  log::warn!(
268
276
  "unknown material type: {type:#?}, using default color, i.e., black",
@@ -353,12 +361,19 @@ impl SionnaScene {
353
361
  /// Load a Sionna scene from a XML file.
354
362
  ///
355
363
  /// Args:
356
- /// file (str): The path to the XML file.
364
+ /// file (os.PathLike[str] | str): The path to the XML file.
357
365
  ///
358
366
  /// Returns:
359
367
  /// SionnaScene: The corresponding scene.
360
368
  #[classmethod]
361
- pub(crate) fn load_xml(_: &Bound<'_, PyType>, file: &str) -> PyResult<Self> {
369
+ #[pyo3(name = "load_xml")]
370
+ pub(crate) fn py_load_xml(_: &Bound<'_, PyType>, file: PathBuf) -> PyResult<Self> {
371
+ Self::load_xml(&file)
372
+ }
373
+ }
374
+
375
+ impl SionnaScene {
376
+ pub(crate) fn load_xml(file: &Path) -> PyResult<Self> {
362
377
  let input = BufReader::new(File::open(file)?);
363
378
  quick_xml::de::from_reader(input).map_err(|err| {
364
379
  PyValueError::new_err(format!(
@@ -662,6 +677,76 @@ mod tests {
662
677
  assert_eq!(material.color, [0.539, 0.027, 0.147]);
663
678
  }
664
679
 
680
+ #[test]
681
+ fn deserializes_itu_vacuum() {
682
+ let xml = r#"
683
+ <bsdf type="itu-radio-material" id="vacuum">
684
+ <string name="type" value="vacuum"/>
685
+ </bsdf>
686
+ "#;
687
+
688
+ let material: Material = quick_xml::de::from_str(xml).expect("material should parse");
689
+
690
+ assert_eq!(material.name, "itu_vacuum");
691
+ assert_eq!(material.color, [0.8, 0.8, 0.8]);
692
+ }
693
+
694
+ #[test]
695
+ fn deserializes_itu_clear_acrylic() {
696
+ let xml = r#"
697
+ <bsdf type="itu-radio-material" id="acrylic">
698
+ <string name="type" value="clear_acrylic"/>
699
+ </bsdf>
700
+ "#;
701
+
702
+ let material: Material = quick_xml::de::from_str(xml).expect("material should parse");
703
+
704
+ assert_eq!(material.name, "itu_clear_acrylic");
705
+ assert_eq!(material.color, [0.8, 0.9, 0.95]);
706
+ }
707
+
708
+ #[test]
709
+ fn deserializes_itu_vinyl_tile() {
710
+ let xml = r#"
711
+ <bsdf type="itu-radio-material" id="vinyl">
712
+ <string name="type" value="vinyl_tile"/>
713
+ </bsdf>
714
+ "#;
715
+
716
+ let material: Material = quick_xml::de::from_str(xml).expect("material should parse");
717
+
718
+ assert_eq!(material.name, "itu_vinyl_tile");
719
+ assert_eq!(material.color, [0.75, 0.75, 0.72]);
720
+ }
721
+
722
+ #[test]
723
+ fn deserializes_itu_carpet_tile() {
724
+ let xml = r#"
725
+ <bsdf type="itu-radio-material" id="carpet">
726
+ <string name="type" value="carpet_tile"/>
727
+ </bsdf>
728
+ "#;
729
+
730
+ let material: Material = quick_xml::de::from_str(xml).expect("material should parse");
731
+
732
+ assert_eq!(material.name, "itu_carpet_tile");
733
+ assert_eq!(material.color, [0.32, 0.35, 0.42]);
734
+ }
735
+
736
+ #[test]
737
+ fn deserializes_itu_asphalt_concrete() {
738
+ let xml = r#"
739
+ <bsdf type="itu-radio-material" id="asphalt">
740
+ <string name="type" value="asphalt_concrete"/>
741
+ </bsdf>
742
+ "#;
743
+
744
+ let material: Material = quick_xml::de::from_str(xml).expect("material should parse");
745
+
746
+ assert_eq!(material.name, "itu_asphalt_concrete");
747
+ assert_eq!(material.color, [0.18, 0.18, 0.18]);
748
+ }
749
+
665
750
  #[test]
666
751
  fn deserializes_itu_unknown_type() {
667
752
  let xml = r#"
@@ -1,4 +1,3 @@
1
- import logging
2
1
  import re
3
2
 
4
3
  import numpy as np
@@ -197,29 +196,3 @@ class TestDiGraph:
197
196
  assert num_paths == num_nodes * (num_nodes - 1) ** (depth - 1)
198
197
  array = graph.all_paths_array(from_, to, depth + 2, include_from_and_to=False)
199
198
  assert array.shape == (num_paths, depth)
200
-
201
- @pytest.mark.parametrize(
202
- ("num_nodes", "depth"),
203
- [
204
- (10, 100),
205
- (50_000, 10),
206
- ],
207
- )
208
- def test_all_paths_count_from_complete_graph_overflow(
209
- self,
210
- num_nodes: int,
211
- depth: int,
212
- caplog: pytest.LogCaptureFixture,
213
- ) -> None:
214
- graph = CompleteGraph(num_nodes)
215
- from_, to = num_nodes, num_nodes + 1
216
-
217
- caplog.clear()
218
-
219
- with caplog.at_level(logging.WARNING):
220
- _ = graph.all_paths(from_, to, depth + 2, include_from_and_to=False)
221
-
222
- assert (
223
- "OverflowError: overflow occurred when computing the total number of paths"
224
- in caplog.text
225
- )
@@ -1,4 +1,3 @@
1
- # pyright: reportMissingTypeArgument=false
2
1
  from collections.abc import Iterator, Sized
3
2
 
4
3
  import numpy as np
@@ -1,4 +1,5 @@
1
- # pyright: reportMissingTypeArgument=false
1
+ from os import PathLike
2
+
2
3
  import numpy as np
3
4
  from jaxtyping import Float, Int, UInt
4
5
 
@@ -12,6 +13,6 @@ class Mesh:
12
13
 
13
14
  def append(self, other: Mesh) -> None: ...
14
15
  @classmethod
15
- def load_obj(cls, file: str) -> Mesh: ...
16
+ def load_obj(cls, file: str | PathLike[str]) -> Mesh: ...
16
17
  @classmethod
17
- def load_ply(cls, file: str) -> Mesh: ...
18
+ def load_ply(cls, file: str | PathLike[str]) -> Mesh: ...
@@ -1,7 +1,9 @@
1
+ from os import PathLike
2
+
1
3
  from differt_core.geometry import TriangleMesh
2
4
 
3
5
  class Scene:
4
6
  mesh: list[TriangleMesh]
5
7
 
6
8
  @classmethod
7
- def load_xml(cls, file: str) -> Scene: ...
9
+ def load_xml(cls, file: str | PathLike[str]) -> Scene: ...
@@ -1,9 +1,11 @@
1
+ from os import PathLike
2
+
1
3
  class SionnaScene:
2
4
  shapes: dict[str, Shape]
3
5
  materials: dict[str, Material]
4
6
 
5
7
  @classmethod
6
- def load_xml(cls, file: str) -> SionnaScene: ...
8
+ def load_xml(cls, file: str | PathLike[str]) -> SionnaScene: ...
7
9
 
8
10
  class Material:
9
11
  name: str
@@ -1,4 +1,8 @@
1
- use std::{fs::File, io::BufReader, path::PathBuf};
1
+ use std::{
2
+ fs::File,
3
+ io::BufReader,
4
+ path::{Path, PathBuf},
5
+ };
2
6
 
3
7
  use indexmap::IndexMap;
4
8
  use numpy::{PyArray1, PyArray2, ndarray::arr2};
@@ -273,22 +277,48 @@ impl Mesh {
273
277
  /// vertices.
274
278
  ///
275
279
  /// Args:
276
- /// file (str): The path to the Wavefront .obj file.
280
+ /// file (str | os.PathLike[str]): The path to the Wavefront .obj file.
277
281
  ///
278
282
  /// Returns:
279
283
  /// Mesh: The corresponding mesh containing only triangles.
280
284
  #[classmethod]
281
- pub(crate) fn load_obj(_: &Bound<'_, PyType>, filename: &str) -> PyResult<Self> {
282
- let input = BufReader::new(File::open(filename)?);
285
+ #[pyo3(name = "load_obj")]
286
+ pub(crate) fn py_load_obj(_: &Bound<'_, PyType>, file: PathBuf) -> PyResult<Self> {
287
+ Self::load_obj(&file)
288
+ }
289
+
290
+ /// Load a triangle mesh from a Stanford PLY .ply file.
291
+ ///
292
+ /// Currently, only vertices and triangles are loaded. Triangle normals
293
+ /// are ignored because they are computed with
294
+ /// :attr:`differt.geometry.Mesh.normals` using
295
+ /// JAX so that they can be differentiated with respect to triangle
296
+ /// vertices.
297
+ ///
298
+ /// Args:
299
+ /// file (str | os.PathLike[str]): The path to the Stanford PLY .ply file.
300
+ ///
301
+ /// Returns:
302
+ /// Mesh: The corresponding mesh containing only triangles.
303
+ #[classmethod]
304
+ #[pyo3(name = "load_ply")]
305
+ pub(crate) fn py_load_ply(_: &Bound<'_, PyType>, file: PathBuf) -> PyResult<Self> {
306
+ Self::load_ply(&file)
307
+ }
308
+ }
309
+
310
+ impl Mesh {
311
+ pub(crate) fn load_obj(file: &Path) -> PyResult<Self> {
312
+ let input = BufReader::new(File::open(file)?);
283
313
  let mut obj: RawObj = parse_obj(input).map_err(|err| {
284
314
  PyValueError::new_err(format!(
285
- "An error occurred while reading obj file {filename:#?}: {}",
315
+ "An error occurred while reading obj file {file:#?}: {}",
286
316
  err
287
317
  ))
288
318
  })?;
289
319
 
290
320
  for material_file in obj.material_libraries.iter_mut() {
291
- let mut path = PathBuf::from(filename);
321
+ let mut path = file.to_path_buf();
292
322
  path.set_file_name(&material_file);
293
323
  if let Some(path_str) = path.to_str() {
294
324
  *material_file = path_str.to_string();
@@ -298,29 +328,15 @@ impl Mesh {
298
328
  Ok(obj.into())
299
329
  }
300
330
 
301
- /// Load a triangle mesh from a Stanford PLY .ply file.
302
- ///
303
- /// Currently, only vertices and triangles are loaded. Triangle normals
304
- /// are ignored because they are computed with
305
- /// :attr:`differt.geometry.Mesh.normals` using
306
- /// JAX so that they can be differentiated with respect to triangle
307
- /// vertices.
308
- ///
309
- /// Args:
310
- /// file (str): The path to the Stanford PLY .ply file.
311
- ///
312
- /// Returns:
313
- /// Mesh: The corresponding mesh containing only triangles.
314
- #[classmethod]
315
- pub(crate) fn load_ply(_: &Bound<'_, PyType>, filename: &str) -> PyResult<Self> {
316
- let mut input = BufReader::new(File::open(filename)?);
331
+ pub(crate) fn load_ply(file: &Path) -> PyResult<Self> {
332
+ let mut input = BufReader::new(File::open(file)?);
317
333
 
318
334
  let vertex_parser = parser::Parser::<PlyVertex>::new();
319
335
  let face_parser = parser::Parser::<PlyFace>::new();
320
336
 
321
337
  let header = vertex_parser.read_header(&mut input).map_err(|err| {
322
338
  PyValueError::new_err(format!(
323
- "An error occurred while reading the header of ply file {filename:#?}: {}",
339
+ "An error occurred while reading the header of ply file {file:#?}: {}",
324
340
  err
325
341
  ))
326
342
  })?;
@@ -337,7 +353,7 @@ impl Mesh {
337
353
  .map_err(|err| {
338
354
  PyValueError::new_err(format!(
339
355
  "An error occurred while reading the vertex elements of ply \
340
- file {filename:#?}: {}",
356
+ file {file:#?}: {}",
341
357
  err
342
358
  ))
343
359
  })?
@@ -352,7 +368,7 @@ impl Mesh {
352
368
  .map_err(|err| {
353
369
  PyValueError::new_err(format!(
354
370
  "An error occurred while reading the face elements of PLY \
355
- file {filename:#?}: {}",
371
+ file {file:#?}: {}",
356
372
  err
357
373
  ))
358
374
  })?
@@ -1,4 +1,4 @@
1
- use std::path::PathBuf;
1
+ use std::path::{Path, PathBuf};
2
2
 
3
3
  use pyo3::{exceptions::PyValueError, prelude::*, types::PyType};
4
4
 
@@ -22,38 +22,33 @@ impl Scene {
22
22
  /// Load a scene from a Sionna-compatible XML file.
23
23
  ///
24
24
  /// Args:
25
- /// file (str): The path to the XML file.
25
+ /// file (str | os.PathLike[str]): The path to the XML file.
26
26
  ///
27
27
  /// Returns:
28
28
  /// Scene: The corresponding scene.
29
29
  #[classmethod]
30
- fn load_xml(cls: &Bound<'_, PyType>, file: &str) -> PyResult<Self> {
31
- // TODO: create a Rust variant without PyType?
32
- let sionna_scene_py_type = PyType::new::<SionnaScene>(cls.py());
33
- let sionna = SionnaScene::load_xml(&sionna_scene_py_type, file)?;
30
+ #[pyo3(name = "load_xml")]
31
+ fn py_load_xml(_cls: &Bound<'_, PyType>, file: PathBuf) -> PyResult<Self> {
32
+ Self::load_xml(&file)
33
+ }
34
+ }
35
+ impl Scene {
36
+ fn load_xml(file: &Path) -> PyResult<Self> {
37
+ let sionna = SionnaScene::load_xml(&file)?;
34
38
 
35
- let path = PathBuf::from(file);
36
- let folder = path.parent().ok_or_else(|| {
39
+ let folder = file.parent().ok_or_else(|| {
37
40
  PyValueError::new_err(format!(
38
- "Could not determine parent folder of file: {}",
39
- file
41
+ "Could not determine parent folder of file: {file:#?}",
40
42
  ))
41
43
  })?;
42
44
 
43
45
  let mut mesh = Mesh::default();
44
46
 
45
- let mesh_py_type = PyType::new::<Mesh>(cls.py());
46
-
47
47
  for (_, shape) in sionna.shapes.into_iter() {
48
48
  let mesh_file_path = folder.join(shape.file);
49
- let mesh_file = mesh_file_path.to_str().ok_or_else(|| {
50
- PyValueError::new_err(format!(
51
- "Could not convert path {mesh_file_path:?} to valid unicode string"
52
- ))
53
- })?;
54
49
  let mut other_mesh = match shape.r#type.as_str() {
55
- "obj" => Mesh::load_obj(&mesh_py_type, mesh_file)?,
56
- "ply" => Mesh::load_ply(&mesh_py_type, mesh_file)?,
50
+ "obj" => Mesh::load_obj(&mesh_file_path)?,
51
+ "ply" => Mesh::load_ply(&mesh_file_path)?,
57
52
  ty => {
58
53
  log::warn!("Unsupported shape type {ty}, skipping.");
59
54
  continue;
@@ -1,4 +1,8 @@
1
- use std::{fs::File, io::BufReader};
1
+ use std::{
2
+ fs::File,
3
+ io::BufReader,
4
+ path::{Path, PathBuf},
5
+ };
2
6
 
3
7
  use indexmap::IndexMap;
4
8
  use pyo3::{exceptions::PyValueError, prelude::*, types::PyType};
@@ -248,7 +252,7 @@ impl<'de> Deserialize<'de> for Material {
248
252
  let color = match r#type.as_str() {
249
253
  // Copied from Sionna-RT's code, to match their colors:
250
254
  // https://github.com/NVlabs/sionna-rt/blob/main/src/sionna/rt/radio_materials/itu_material.py
251
- // v1.0.0
255
+ "vacuum" => [0.8, 0.8, 0.8],
252
256
  "marble" => [0.701, 0.644, 0.485],
253
257
  "concrete" => [0.539, 0.539, 0.539],
254
258
  "wood" => [0.266, 0.109, 0.060],
@@ -263,6 +267,10 @@ impl<'de> Deserialize<'de> for Material {
263
267
  "very_dry_ground" => [0.539, 0.319, 0.223],
264
268
  "medium_dry_ground" => [0.539, 0.181, 0.076],
265
269
  "wet_ground" => [0.539, 0.027, 0.147],
270
+ "clear_acrylic" => [0.8, 0.9, 0.95],
271
+ "vinyl_tile" => [0.75, 0.75, 0.72],
272
+ "carpet_tile" => [0.32, 0.35, 0.42],
273
+ "asphalt_concrete" => [0.18, 0.18, 0.18],
266
274
  _ => {
267
275
  log::warn!(
268
276
  "unknown material type: {type:#?}, using default color, i.e., black",
@@ -353,12 +361,19 @@ impl SionnaScene {
353
361
  /// Load a Sionna scene from a XML file.
354
362
  ///
355
363
  /// Args:
356
- /// file (str): The path to the XML file.
364
+ /// file (os.PathLike[str] | str): The path to the XML file.
357
365
  ///
358
366
  /// Returns:
359
367
  /// SionnaScene: The corresponding scene.
360
368
  #[classmethod]
361
- pub(crate) fn load_xml(_: &Bound<'_, PyType>, file: &str) -> PyResult<Self> {
369
+ #[pyo3(name = "load_xml")]
370
+ pub(crate) fn py_load_xml(_: &Bound<'_, PyType>, file: PathBuf) -> PyResult<Self> {
371
+ Self::load_xml(&file)
372
+ }
373
+ }
374
+
375
+ impl SionnaScene {
376
+ pub(crate) fn load_xml(file: &Path) -> PyResult<Self> {
362
377
  let input = BufReader::new(File::open(file)?);
363
378
  quick_xml::de::from_reader(input).map_err(|err| {
364
379
  PyValueError::new_err(format!(
@@ -662,6 +677,76 @@ mod tests {
662
677
  assert_eq!(material.color, [0.539, 0.027, 0.147]);
663
678
  }
664
679
 
680
+ #[test]
681
+ fn deserializes_itu_vacuum() {
682
+ let xml = r#"
683
+ <bsdf type="itu-radio-material" id="vacuum">
684
+ <string name="type" value="vacuum"/>
685
+ </bsdf>
686
+ "#;
687
+
688
+ let material: Material = quick_xml::de::from_str(xml).expect("material should parse");
689
+
690
+ assert_eq!(material.name, "itu_vacuum");
691
+ assert_eq!(material.color, [0.8, 0.8, 0.8]);
692
+ }
693
+
694
+ #[test]
695
+ fn deserializes_itu_clear_acrylic() {
696
+ let xml = r#"
697
+ <bsdf type="itu-radio-material" id="acrylic">
698
+ <string name="type" value="clear_acrylic"/>
699
+ </bsdf>
700
+ "#;
701
+
702
+ let material: Material = quick_xml::de::from_str(xml).expect("material should parse");
703
+
704
+ assert_eq!(material.name, "itu_clear_acrylic");
705
+ assert_eq!(material.color, [0.8, 0.9, 0.95]);
706
+ }
707
+
708
+ #[test]
709
+ fn deserializes_itu_vinyl_tile() {
710
+ let xml = r#"
711
+ <bsdf type="itu-radio-material" id="vinyl">
712
+ <string name="type" value="vinyl_tile"/>
713
+ </bsdf>
714
+ "#;
715
+
716
+ let material: Material = quick_xml::de::from_str(xml).expect("material should parse");
717
+
718
+ assert_eq!(material.name, "itu_vinyl_tile");
719
+ assert_eq!(material.color, [0.75, 0.75, 0.72]);
720
+ }
721
+
722
+ #[test]
723
+ fn deserializes_itu_carpet_tile() {
724
+ let xml = r#"
725
+ <bsdf type="itu-radio-material" id="carpet">
726
+ <string name="type" value="carpet_tile"/>
727
+ </bsdf>
728
+ "#;
729
+
730
+ let material: Material = quick_xml::de::from_str(xml).expect("material should parse");
731
+
732
+ assert_eq!(material.name, "itu_carpet_tile");
733
+ assert_eq!(material.color, [0.32, 0.35, 0.42]);
734
+ }
735
+
736
+ #[test]
737
+ fn deserializes_itu_asphalt_concrete() {
738
+ let xml = r#"
739
+ <bsdf type="itu-radio-material" id="asphalt">
740
+ <string name="type" value="asphalt_concrete"/>
741
+ </bsdf>
742
+ "#;
743
+
744
+ let material: Material = quick_xml::de::from_str(xml).expect("material should parse");
745
+
746
+ assert_eq!(material.name, "itu_asphalt_concrete");
747
+ assert_eq!(material.color, [0.18, 0.18, 0.18]);
748
+ }
749
+
665
750
  #[test]
666
751
  fn deserializes_itu_unknown_type() {
667
752
  let xml = r#"
File without changes
File without changes
File without changes
File without changes