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.
- {differt_core-0.10.0 → differt_core-0.11.0}/Cargo.lock +1 -1
- {differt_core-0.10.0 → differt_core-0.11.0}/PKG-INFO +1 -1
- {differt_core-0.10.0 → differt_core-0.11.0}/differt-core/Cargo.toml +1 -1
- {differt_core-0.10.0 → differt_core-0.11.0}/differt-core/src/geometry/mesh.rs +41 -25
- {differt_core-0.10.0 → differt_core-0.11.0}/differt-core/src/geometry/scene.rs +14 -19
- {differt_core-0.10.0 → differt_core-0.11.0/differt-core}/src/geometry/sionna.rs +89 -4
- {differt_core-0.10.0 → differt_core-0.11.0}/differt-core/tests/geometry/test_graph.py +0 -27
- {differt_core-0.10.0 → differt_core-0.11.0}/python/differt_core/_differt_core/geometry/graph.pyi +0 -1
- {differt_core-0.10.0 → differt_core-0.11.0}/python/differt_core/_differt_core/geometry/mesh.pyi +4 -3
- {differt_core-0.10.0 → differt_core-0.11.0}/python/differt_core/_differt_core/geometry/scene.pyi +3 -1
- {differt_core-0.10.0 → differt_core-0.11.0}/python/differt_core/_differt_core/geometry/sionna.pyi +3 -1
- {differt_core-0.10.0 → differt_core-0.11.0}/src/geometry/mesh.rs +41 -25
- {differt_core-0.10.0 → differt_core-0.11.0}/src/geometry/scene.rs +14 -19
- {differt_core-0.10.0/differt-core → differt_core-0.11.0}/src/geometry/sionna.rs +89 -4
- {differt_core-0.10.0 → differt_core-0.11.0}/Cargo.toml +0 -0
- {differt_core-0.10.0 → differt_core-0.11.0}/LICENSE.md +0 -0
- {differt_core-0.10.0 → differt_core-0.11.0}/README.md +0 -0
- {differt_core-0.10.0 → differt_core-0.11.0}/differt-core/LICENSE.md +0 -0
- {differt_core-0.10.0 → differt_core-0.11.0}/differt-core/README.md +0 -0
- {differt_core-0.10.0 → differt_core-0.11.0}/differt-core/benches/bench_main.rs +0 -0
- {differt_core-0.10.0 → differt_core-0.11.0}/differt-core/benches/benchmarks/graph_iterators.rs +0 -0
- {differt_core-0.10.0 → differt_core-0.11.0}/differt-core/benches/benchmarks/mod.rs +0 -0
- {differt_core-0.10.0 → differt_core-0.11.0}/differt-core/src/geometry/graph.rs +0 -0
- {differt_core-0.10.0 → differt_core-0.11.0}/differt-core/src/geometry/mod.rs +0 -0
- {differt_core-0.10.0 → differt_core-0.11.0}/differt-core/src/lib.rs +0 -0
- {differt_core-0.10.0 → differt_core-0.11.0}/differt-core/tests/__init__.py +0 -0
- {differt_core-0.10.0 → differt_core-0.11.0}/differt-core/tests/geometry/__init__.py +0 -0
- {differt_core-0.10.0 → differt_core-0.11.0}/differt-core/tests/geometry/test_core_mesh.py +0 -0
- {differt_core-0.10.0 → differt_core-0.11.0}/differt-core/tests/geometry/test_core_scene.py +0 -0
- {differt_core-0.10.0 → differt_core-0.11.0}/differt-core/tests/test_version.py +0 -0
- {differt_core-0.10.0 → differt_core-0.11.0}/pyproject.toml +0 -0
- {differt_core-0.10.0 → differt_core-0.11.0}/python/differt_core/__init__.py +0 -0
- {differt_core-0.10.0 → differt_core-0.11.0}/python/differt_core/_differt_core/__init__.pyi +0 -0
- {differt_core-0.10.0 → differt_core-0.11.0}/python/differt_core/geometry/__init__.py +0 -0
- {differt_core-0.10.0 → differt_core-0.11.0}/python/differt_core/geometry/_graph.py +0 -0
- {differt_core-0.10.0 → differt_core-0.11.0}/python/differt_core/geometry/_mesh.py +0 -0
- {differt_core-0.10.0 → differt_core-0.11.0}/python/differt_core/geometry/_scene.py +0 -0
- {differt_core-0.10.0 → differt_core-0.11.0}/python/differt_core/geometry/_sionna.py +0 -0
- {differt_core-0.10.0 → differt_core-0.11.0}/python/differt_core/py.typed +0 -0
- {differt_core-0.10.0 → differt_core-0.11.0}/src/geometry/graph.rs +0 -0
- {differt_core-0.10.0 → differt_core-0.11.0}/src/geometry/mod.rs +0 -0
- {differt_core-0.10.0 → differt_core-0.11.0}/src/lib.rs +0 -0
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
use std::{
|
|
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
|
-
|
|
282
|
-
|
|
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 {
|
|
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 =
|
|
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
|
-
|
|
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 {
|
|
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 {
|
|
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 {
|
|
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
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
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
|
|
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(&
|
|
56
|
-
"ply" => Mesh::load_ply(&
|
|
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::{
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
)
|
{differt_core-0.10.0 → differt_core-0.11.0}/python/differt_core/_differt_core/geometry/mesh.pyi
RENAMED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
|
|
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: ...
|
{differt_core-0.10.0 → differt_core-0.11.0}/python/differt_core/_differt_core/geometry/sionna.pyi
RENAMED
|
@@ -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::{
|
|
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
|
-
|
|
282
|
-
|
|
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 {
|
|
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 =
|
|
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
|
-
|
|
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 {
|
|
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 {
|
|
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 {
|
|
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
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
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
|
|
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(&
|
|
56
|
-
"ply" => Mesh::load_ply(&
|
|
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::{
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
File without changes
|
|
File without changes
|
{differt_core-0.10.0 → differt_core-0.11.0}/differt-core/benches/benchmarks/graph_iterators.rs
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|