differt-core 0.0.29__tar.gz → 0.0.30__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 (61) hide show
  1. {differt_core-0.0.29 → differt_core-0.0.30}/Cargo.lock +1 -1
  2. {differt_core-0.0.29 → differt_core-0.0.30}/PKG-INFO +1 -1
  3. {differt_core-0.0.29 → differt_core-0.0.30}/differt-core/Cargo.toml +1 -1
  4. differt_core-0.0.30/differt-core/LICENSE.md +21 -0
  5. differt_core-0.0.30/differt-core/README.md +33 -0
  6. {differt_core-0.0.29 → differt_core-0.0.30}/pyproject.toml +6 -0
  7. differt_core-0.0.30/src/geometry/mod.rs +10 -0
  8. differt_core-0.0.30/src/geometry/triangle_mesh.rs +504 -0
  9. differt_core-0.0.30/src/lib.rs +20 -0
  10. differt_core-0.0.30/src/rt/graph.rs +1505 -0
  11. differt_core-0.0.30/src/rt/mod.rs +10 -0
  12. differt_core-0.0.30/src/scene/mod.rs +12 -0
  13. differt_core-0.0.30/src/scene/sionna.rs +233 -0
  14. differt_core-0.0.30/src/scene/triangle_scene.rs +79 -0
  15. {differt_core-0.0.29 → differt_core-0.0.30}/Cargo.toml +0 -0
  16. {differt_core-0.0.29/differt-core → differt_core-0.0.30}/LICENSE.md +0 -0
  17. {differt_core-0.0.29/differt-core → differt_core-0.0.30}/README.md +0 -0
  18. {differt_core-0.0.29 → differt_core-0.0.30}/differt-core/benches/bench_main.rs +0 -0
  19. {differt_core-0.0.29 → differt_core-0.0.30}/differt-core/benches/benchmarks/graph_iterators.rs +0 -0
  20. {differt_core-0.0.29 → differt_core-0.0.30}/differt-core/benches/benchmarks/mod.rs +0 -0
  21. {differt_core-0.0.29 → differt_core-0.0.30}/differt-core/python/differt_core/__init__.py +0 -0
  22. {differt_core-0.0.29 → differt_core-0.0.30}/differt-core/python/differt_core/_lowlevel/__init__.pyi +0 -0
  23. {differt_core-0.0.29 → differt_core-0.0.30}/differt-core/python/differt_core/_lowlevel/geometry/triangle_mesh.pyi +0 -0
  24. {differt_core-0.0.29 → differt_core-0.0.30}/differt-core/python/differt_core/_lowlevel/rt/__init__.pyi +0 -0
  25. {differt_core-0.0.29 → differt_core-0.0.30}/differt-core/python/differt_core/_lowlevel/rt/graph.pyi +0 -0
  26. {differt_core-0.0.29 → differt_core-0.0.30}/differt-core/python/differt_core/_lowlevel/scene/sionna.pyi +0 -0
  27. {differt_core-0.0.29 → differt_core-0.0.30}/differt-core/python/differt_core/_lowlevel/scene/triangle_scene.pyi +0 -0
  28. {differt_core-0.0.29 → differt_core-0.0.30}/differt-core/python/differt_core/geometry/__init__.py +0 -0
  29. {differt_core-0.0.29 → differt_core-0.0.30}/differt-core/python/differt_core/geometry/_triangle_mesh.py +0 -0
  30. {differt_core-0.0.29 → differt_core-0.0.30}/differt-core/python/differt_core/py.typed +0 -0
  31. {differt_core-0.0.29 → differt_core-0.0.30}/differt-core/python/differt_core/rt/__init__.py +0 -0
  32. {differt_core-0.0.29 → differt_core-0.0.30}/differt-core/python/differt_core/rt/_graph.py +0 -0
  33. {differt_core-0.0.29 → differt_core-0.0.30}/differt-core/python/differt_core/scene/__init__.py +0 -0
  34. {differt_core-0.0.29 → differt_core-0.0.30}/differt-core/python/differt_core/scene/_sionna.py +0 -0
  35. {differt_core-0.0.29 → differt_core-0.0.30}/differt-core/python/differt_core/scene/_triangle_scene.py +0 -0
  36. {differt_core-0.0.29 → differt_core-0.0.30}/differt-core/src/geometry/mod.rs +0 -0
  37. {differt_core-0.0.29 → differt_core-0.0.30}/differt-core/src/geometry/triangle_mesh.rs +0 -0
  38. {differt_core-0.0.29 → differt_core-0.0.30}/differt-core/src/lib.rs +0 -0
  39. {differt_core-0.0.29 → differt_core-0.0.30}/differt-core/src/rt/graph.rs +0 -0
  40. {differt_core-0.0.29 → differt_core-0.0.30}/differt-core/src/rt/mod.rs +0 -0
  41. {differt_core-0.0.29 → differt_core-0.0.30}/differt-core/src/scene/mod.rs +0 -0
  42. {differt_core-0.0.29 → differt_core-0.0.30}/differt-core/src/scene/sionna.rs +0 -0
  43. {differt_core-0.0.29 → differt_core-0.0.30}/differt-core/src/scene/triangle_scene.rs +0 -0
  44. {differt_core-0.0.29 → differt_core-0.0.30}/differt-core/tests/__init__.py +0 -0
  45. {differt_core-0.0.29 → differt_core-0.0.30}/differt-core/tests/rt/__init__.py +0 -0
  46. {differt_core-0.0.29 → differt_core-0.0.30}/differt-core/tests/rt/test_graph.py +0 -0
  47. {differt_core-0.0.29 → differt_core-0.0.30}/python/differt_core/__init__.py +0 -0
  48. {differt_core-0.0.29 → differt_core-0.0.30}/python/differt_core/_lowlevel/__init__.pyi +0 -0
  49. {differt_core-0.0.29 → differt_core-0.0.30}/python/differt_core/_lowlevel/geometry/triangle_mesh.pyi +0 -0
  50. {differt_core-0.0.29 → differt_core-0.0.30}/python/differt_core/_lowlevel/rt/__init__.pyi +0 -0
  51. {differt_core-0.0.29 → differt_core-0.0.30}/python/differt_core/_lowlevel/rt/graph.pyi +0 -0
  52. {differt_core-0.0.29 → differt_core-0.0.30}/python/differt_core/_lowlevel/scene/sionna.pyi +0 -0
  53. {differt_core-0.0.29 → differt_core-0.0.30}/python/differt_core/_lowlevel/scene/triangle_scene.pyi +0 -0
  54. {differt_core-0.0.29 → differt_core-0.0.30}/python/differt_core/geometry/__init__.py +0 -0
  55. {differt_core-0.0.29 → differt_core-0.0.30}/python/differt_core/geometry/_triangle_mesh.py +0 -0
  56. {differt_core-0.0.29 → differt_core-0.0.30}/python/differt_core/py.typed +0 -0
  57. {differt_core-0.0.29 → differt_core-0.0.30}/python/differt_core/rt/__init__.py +0 -0
  58. {differt_core-0.0.29 → differt_core-0.0.30}/python/differt_core/rt/_graph.py +0 -0
  59. {differt_core-0.0.29 → differt_core-0.0.30}/python/differt_core/scene/__init__.py +0 -0
  60. {differt_core-0.0.29 → differt_core-0.0.30}/python/differt_core/scene/_sionna.py +0 -0
  61. {differt_core-0.0.29 → differt_core-0.0.30}/python/differt_core/scene/_triangle_scene.py +0 -0
@@ -238,7 +238,7 @@ checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7"
238
238
 
239
239
  [[package]]
240
240
  name = "differt-core"
241
- version = "0.0.29"
241
+ version = "0.0.30"
242
242
  dependencies = [
243
243
  "criterion",
244
244
  "indexmap",
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: differt-core
3
- Version: 0.0.29
3
+ Version: 0.0.30
4
4
  Classifier: Programming Language :: Python :: 3
5
5
  Classifier: Programming Language :: Python :: 3.9
6
6
  Classifier: Programming Language :: Python :: 3.10
@@ -37,4 +37,4 @@ 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.0.29"
40
+ version = "0.0.30"
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2023-2024 Jérome Eertmans
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,33 @@
1
+ <div align="center">
2
+ <img src="https://raw.githubusercontent.com/jeertmans/DiffeRT/main/static/logo_250px.png" alt="DiffeRT logo"></img>
3
+ </div>
4
+
5
+ <div align="center">
6
+
7
+ # DiffeRT-core
8
+
9
+ [![Latest Release][pypi-version-badge]][pypi-version-url]
10
+ [![Python version][pypi-python-version-badge]][pypi-version-url]
11
+ [![Documentation][documentation-badge]][documentation-url]
12
+
13
+ </div>
14
+
15
+ This package contains the core backend of
16
+ [DiffeRT](https://pypi.org/project/DiffeRT/),
17
+ implemented in Rust for performances.
18
+
19
+ As a result, both `differt` and `differt-core` will
20
+ share the same version, and `differt` directly depends on `differt-core`.
21
+
22
+ However, you can decide to only install `differt-core`
23
+ if you want to use features that are specific to this package.
24
+
25
+ The installation procedure, contributing guidelines, and documentation,
26
+ are shared with the
27
+ [main DiffeRT package](https://github.com/jeertmans/DiffeRT).
28
+
29
+ [pypi-version-badge]: https://img.shields.io/pypi/v/DiffeRT-core?label=DiffeRT-core&color=blueviolet
30
+ [pypi-version-url]: https://pypi.org/project/DiffeRT-core/
31
+ [pypi-python-version-badge]: https://img.shields.io/pypi/pyversions/DiffeRT-core?color=orange
32
+ [documentation-badge]: https://readthedocs.org/projects/differt/badge/?version=latest
33
+ [documentation-url]: https://differt.readthedocs.io/latest/?badge=latest
@@ -30,6 +30,12 @@ requires-python = ">= 3.9"
30
30
  [tool.maturin]
31
31
  bindings = "pyo3"
32
32
  features = ["pyo3/extension-module"]
33
+ include = [
34
+ {path = "src/**/*", format = "sdist"},
35
+ {path = "LICENSE.md", format = "sdist"},
36
+ {path = "README.md", format = "sdist"},
37
+ ]
33
38
  module-name = "differt_core._lowlevel"
34
39
  python-source = "python"
40
+ strip = true
35
41
  manifest-path = "differt-core/Cargo.toml"
@@ -0,0 +1,10 @@
1
+ use pyo3::{prelude::*, wrap_pymodule};
2
+
3
+ pub mod triangle_mesh;
4
+
5
+ #[cfg(not(tarpaulin_include))]
6
+ #[pymodule]
7
+ pub(crate) fn geometry(m: Bound<'_, PyModule>) -> PyResult<()> {
8
+ m.add_wrapped(wrap_pymodule!(triangle_mesh::triangle_mesh))?;
9
+ Ok(())
10
+ }
@@ -0,0 +1,504 @@
1
+ use std::{fs::File, io::BufReader, path::PathBuf};
2
+
3
+ use indexmap::IndexMap;
4
+ use numpy::{PyArray1, PyArray2, ndarray::arr2};
5
+ use obj::raw::object::{RawObj, parse_obj};
6
+ use ply_rs::{parser, ply};
7
+ use pyo3::{exceptions::PyValueError, prelude::*, types::PyType};
8
+
9
+ /// A lower-level mesh made of triangles.
10
+ ///
11
+ /// This class should not be used directly, prefer its higher-level version
12
+ /// :class:`differt.geometry.TriangleMesh` instead.
13
+ #[derive(Clone, Debug, Default)]
14
+ #[pyclass]
15
+ pub(crate) struct TriangleMesh {
16
+ vertices: Vec<[f32; 3]>,
17
+ triangles: Vec<[usize; 3]>,
18
+ face_colors: Option<Vec<[f32; 3]>>,
19
+ face_materials: Option<Vec<isize>>,
20
+ #[pyo3(get)]
21
+ /// list[str]: List of material names.
22
+ material_names: Vec<String>,
23
+ object_bounds: Option<Vec<[usize; 2]>>,
24
+ }
25
+
26
+ struct PlyVertex {
27
+ pub x: f32,
28
+ pub y: f32,
29
+ pub z: f32,
30
+ }
31
+
32
+ impl ply::PropertyAccess for PlyVertex {
33
+ fn new() -> Self {
34
+ Self {
35
+ x: 0.0,
36
+ y: 0.0,
37
+ z: 0.0,
38
+ }
39
+ }
40
+ fn set_property(&mut self, key: String, property: ply::Property) {
41
+ match (key.as_ref(), property) {
42
+ ("x", ply::Property::Float(v)) => self.x = v as _,
43
+ ("y", ply::Property::Float(v)) => self.y = v as _,
44
+ ("z", ply::Property::Float(v)) => self.z = v as _,
45
+ ("x", ply::Property::Double(v)) => self.x = v as _,
46
+ ("y", ply::Property::Double(v)) => self.y = v as _,
47
+ ("z", ply::Property::Double(v)) => self.z = v as _,
48
+ (k, property) => {
49
+ log::info!("vertex: unexpected key/value combination: {k}/{property:?}")
50
+ },
51
+ }
52
+ }
53
+ }
54
+
55
+ struct PlyFace {
56
+ pub vertex_indices: Vec<usize>,
57
+ }
58
+
59
+ impl ply::PropertyAccess for PlyFace {
60
+ fn new() -> Self {
61
+ Self {
62
+ vertex_indices: Vec::new(),
63
+ }
64
+ }
65
+ fn set_property(&mut self, key: String, property: ply::Property) {
66
+ match (key.as_ref(), property) {
67
+ ("vertex_indices" | "vertex_index", ply::Property::ListUInt(vec)) => {
68
+ self.vertex_indices = vec.iter().map(|&x| x as _).collect()
69
+ },
70
+ ("vertex_indices" | "vertex_index", ply::Property::ListInt(vec)) => {
71
+ self.vertex_indices = vec.iter().map(|&x| x as _).collect()
72
+ },
73
+ (k, property) => {
74
+ log::info!("face: unexpected key/value combination: {k}/{property:?}")
75
+ },
76
+ }
77
+ }
78
+ }
79
+
80
+ impl TriangleMesh {
81
+ pub fn get_material_index(&mut self, material_name: Option<String>) -> isize {
82
+ if let Some(material_name) = material_name {
83
+ return self
84
+ .material_names
85
+ .iter()
86
+ .position(|name| name == &material_name)
87
+ .unwrap_or_else(|| {
88
+ let index = self.material_names.len();
89
+ self.material_names.push(material_name);
90
+ index
91
+ }) as isize;
92
+ }
93
+ -1
94
+ }
95
+
96
+ pub(crate) fn set_face_color(&mut self, color: Option<&[f32; 3]>) {
97
+ let num_triangles = self.triangles.len();
98
+ let face_colors = self
99
+ .face_colors
100
+ .get_or_insert_with(|| Vec::with_capacity(num_triangles));
101
+ face_colors.clear();
102
+
103
+ let color = color.unwrap_or(&[-1.0, -1.0, -1.0]);
104
+
105
+ for _ in 0..num_triangles {
106
+ face_colors.push(*color);
107
+ }
108
+ }
109
+
110
+ pub(crate) fn set_face_material(&mut self, material_name: Option<String>) {
111
+ let material_index = self.get_material_index(material_name);
112
+ let num_triangles = self.triangles.len();
113
+ let face_materials = self
114
+ .face_materials
115
+ .get_or_insert_with(|| Vec::with_capacity(num_triangles));
116
+ face_materials.clear();
117
+
118
+ for _ in 0..num_triangles {
119
+ face_materials.push(material_index);
120
+ }
121
+ }
122
+ }
123
+
124
+ #[pymethods]
125
+ impl TriangleMesh {
126
+ /// :class:`Float[np.ndarray, 'num_vertices 3']<jaxtyping.Float>`: The array of triangle vertices.
127
+ #[getter]
128
+ fn vertices<'py>(&self, py: Python<'py>) -> Bound<'py, PyArray2<f32>> {
129
+ let array = arr2(&self.vertices);
130
+ PyArray2::from_owned_array(py, array)
131
+ }
132
+
133
+ /// :class:`Int[np.ndarray, 'num_triangles 3']<jaxtyping.Int>`: The array of triangle indices.
134
+ #[getter]
135
+ fn triangles<'py>(&self, py: Python<'py>) -> Bound<'py, PyArray2<usize>> {
136
+ let array = arr2(&self.triangles);
137
+ PyArray2::from_owned_array(py, array)
138
+ }
139
+
140
+ /// :class:`Float[np.ndarray, 'num_vertices 3']<jaxtyping.Float>` | :data:`None`: The array of face colors.
141
+ ///
142
+ /// The array contains the face colors, as RGB triplets,
143
+ /// with a black color used as defaults (if some faces have a color).
144
+ /// This attribute is :data:`None` if all face colors are unset.
145
+ #[getter]
146
+ fn face_colors<'py>(&self, py: Python<'py>) -> Option<Bound<'py, PyArray2<f32>>> {
147
+ if let Some(face_colors) = &self.face_colors {
148
+ let array = arr2(face_colors);
149
+ return Some(PyArray2::from_owned_array(py, array));
150
+ }
151
+ None
152
+ }
153
+
154
+ /// :class:`Int[np.ndarray, 'num_vertices']<jaxtyping.Int>` | :data:`None`: The array of face materials.
155
+ ///
156
+ /// The array contains the material indices,
157
+ /// with a special placeholder value of ``-1``.
158
+ /// The obtain the name of the material, see :attr:`material_names`.
159
+ /// This attribute is :data:`None` if all face materials are unset.
160
+ #[getter]
161
+ fn face_materials<'py>(&self, py: Python<'py>) -> Option<Bound<'py, PyArray1<isize>>> {
162
+ if let Some(face_materials) = &self.face_materials {
163
+ return Some(PyArray1::from_slice(py, face_materials.as_slice()));
164
+ }
165
+ None
166
+ }
167
+
168
+ /// :class:`Int[np.ndarray, 'num_objects 2']<jaxtyping.Int>` | :data:`None`: The array of object indices.
169
+ ///
170
+ /// If the present mesh contains multiple objects, usually as a result of
171
+ /// appending multiple meshes together, this array contain start end end
172
+ /// indices for each sub mesh.
173
+ #[getter]
174
+ fn object_bounds<'py>(&self, py: Python<'py>) -> Option<Bound<'py, PyArray2<usize>>> {
175
+ if let Some(object_bounds) = &self.object_bounds {
176
+ let array = arr2(object_bounds);
177
+ return Some(PyArray2::from_owned_array(py, array));
178
+ }
179
+ None
180
+ }
181
+
182
+ /// Move all the elements of ``other`` into ``self`` and update :attr:`object_bounds`.
183
+ ///
184
+ /// After calling this method, ``other`` will be empty.
185
+ ///
186
+ /// Args:
187
+ /// other(TriangleMesh): The mesh to be appended to ``self``.
188
+ pub(crate) fn append(&mut self, other: &mut Self) {
189
+ match (&self.face_colors, &other.face_colors) {
190
+ (None, None) => {},
191
+ (Some(_), None) => {
192
+ other.set_face_color(None);
193
+ },
194
+ (None, Some(_)) => {
195
+ self.set_face_color(None);
196
+ },
197
+ _ => {},
198
+ }
199
+
200
+ if let (Some(x), Some(y)) = (self.face_colors.as_mut(), other.face_colors.as_mut()) {
201
+ x.append(y);
202
+ }
203
+
204
+ match (&self.face_materials, &other.face_materials) {
205
+ (None, None) => {},
206
+ (Some(_), None) => {
207
+ other.set_face_material(None);
208
+ },
209
+ (None, Some(_)) => {
210
+ self.set_face_material(None);
211
+ },
212
+ (Some(_), Some(_)) => {
213
+ // We need to possibly renumber material indices.
214
+ let mut remap: Vec<isize> = vec![0; other.material_names.len()];
215
+
216
+ for (i, material_name) in other.material_names.drain(..).enumerate() {
217
+ remap[i] = match self
218
+ .material_names
219
+ .iter()
220
+ .position(|name| name == &material_name)
221
+ {
222
+ Some(material_index) => material_index,
223
+ None => {
224
+ let material_index = self.material_names.len();
225
+ self.material_names.push(material_name);
226
+ material_index
227
+ },
228
+ } as isize;
229
+ }
230
+
231
+ for material_index in other.face_materials.as_mut().unwrap() {
232
+ if *material_index >= 0 {
233
+ *material_index = remap[*material_index as usize];
234
+ }
235
+ }
236
+ },
237
+ }
238
+
239
+ if let (Some(x), Some(y)) = (self.face_materials.as_mut(), other.face_materials.as_mut()) {
240
+ x.append(y);
241
+ }
242
+
243
+ let index_offset = self.vertices.len();
244
+ let bound_offset = self.triangles.len();
245
+ self.vertices.append(&mut other.vertices);
246
+
247
+ self.triangles.reserve(other.triangles.len());
248
+
249
+ for [v0, v1, v2] in &other.triangles {
250
+ self.triangles
251
+ .push([v0 + index_offset, v1 + index_offset, v2 + index_offset]);
252
+ }
253
+
254
+ other.triangles.clear();
255
+
256
+ self.object_bounds
257
+ .get_or_insert_with(|| {
258
+ if bound_offset > 0 {
259
+ vec![[0, bound_offset]]
260
+ } else {
261
+ vec![]
262
+ }
263
+ })
264
+ .push([bound_offset, self.triangles.len()]);
265
+ }
266
+
267
+ /// Load a triangle mesh from a Wavefront .obj file.
268
+ ///
269
+ /// Currently, only vertices and triangles are loaded. Triangle normals
270
+ /// are ignored because they are computed with
271
+ /// :attr:`differt.geometry.TriangleMesh.normals` using
272
+ /// JAX so that they can be differentiated with respect to triangle
273
+ /// vertices.
274
+ ///
275
+ /// Args:
276
+ /// file (str): The path to the Wavefront .obj file.
277
+ ///
278
+ /// Returns:
279
+ /// TriangleMesh: The corresponding mesh containing only triangles.
280
+ #[classmethod]
281
+ pub(crate) fn load_obj(_: &Bound<'_, PyType>, filename: &str) -> PyResult<Self> {
282
+ let input = BufReader::new(File::open(filename)?);
283
+ let mut obj: RawObj = parse_obj(input).map_err(|err| {
284
+ PyValueError::new_err(format!("An error occurred while reading obj file: {}", err))
285
+ })?;
286
+
287
+ for material_file in obj.material_libraries.iter_mut() {
288
+ let mut path = PathBuf::from(filename);
289
+ path.set_file_name(&material_file);
290
+ if let Some(path_str) = path.to_str() {
291
+ *material_file = path_str.to_string();
292
+ }
293
+ }
294
+
295
+ Ok(obj.into())
296
+ }
297
+
298
+ /// Load a triangle mesh from a Stanford PLY .ply file.
299
+ ///
300
+ /// Currently, only vertices and triangles are loaded. Triangle normals
301
+ /// are ignored because they are computed with
302
+ /// :attr:`differt.geometry.TriangleMesh.normals` using
303
+ /// JAX so that they can be differentiated with respect to triangle
304
+ /// vertices.
305
+ ///
306
+ /// Args:
307
+ /// file (str): The path to the Stanford PLY .ply file.
308
+ ///
309
+ /// Returns:
310
+ /// TriangleMesh: The corresponding mesh containing only triangles.
311
+ #[classmethod]
312
+ pub(crate) fn load_ply(_: &Bound<'_, PyType>, filename: &str) -> PyResult<Self> {
313
+ let mut input = BufReader::new(File::open(filename)?);
314
+
315
+ let vertex_parser = parser::Parser::<PlyVertex>::new();
316
+ let face_parser = parser::Parser::<PlyFace>::new();
317
+
318
+ let header = vertex_parser.read_header(&mut input).map_err(|err| {
319
+ PyValueError::new_err(format!(
320
+ "An error occurred while reading the header of ply file: {}",
321
+ err
322
+ ))
323
+ })?;
324
+
325
+ let mut vertices = Vec::new();
326
+ let mut triangles = Vec::new();
327
+
328
+ for (_, element) in &header.elements {
329
+ match element.name.as_ref() {
330
+ "vertex" => {
331
+ vertices.extend(
332
+ vertex_parser
333
+ .read_payload_for_element(&mut input, element, &header)
334
+ .map_err(|err| {
335
+ PyValueError::new_err(format!(
336
+ "An error occurred while reading the vertex elements of ply \
337
+ file: {}",
338
+ err
339
+ ))
340
+ })?
341
+ .into_iter()
342
+ .map(|vertex| [vertex.x, vertex.y, vertex.z]),
343
+ );
344
+ },
345
+ "face" => {
346
+ triangles.extend(
347
+ face_parser
348
+ .read_payload_for_element(&mut input, element, &header)
349
+ .map_err(|err| {
350
+ PyValueError::new_err(format!(
351
+ "An error occurred while reading the face elements of PLY \
352
+ file: {}",
353
+ err
354
+ ))
355
+ })?
356
+ .into_iter()
357
+ .filter_map(|face| {
358
+ if face.vertex_indices.len() == 3 {
359
+ let indices = face.vertex_indices;
360
+ Some([indices[0], indices[1], indices[2]])
361
+ } else {
362
+ log::info!("Skipping a face because it is not a triangle.");
363
+ None
364
+ }
365
+ }),
366
+ );
367
+ },
368
+ name => log::info!("Unexpeced element: {name}, skipping."),
369
+ }
370
+ }
371
+
372
+ Ok(Self {
373
+ vertices,
374
+ triangles,
375
+ ..Default::default()
376
+ })
377
+ }
378
+ }
379
+
380
+ impl From<RawObj> for TriangleMesh {
381
+ fn from(mut raw_obj: RawObj) -> Self {
382
+ use obj::raw::object::Polygon::*;
383
+
384
+ let vertices = raw_obj
385
+ .positions
386
+ .into_iter()
387
+ .map(|(x, y, z, _)| [x, y, z])
388
+ .collect();
389
+
390
+ let mut triangles = Vec::with_capacity(raw_obj.polygons.len());
391
+
392
+ for polygon in raw_obj.polygons {
393
+ match polygon {
394
+ P(v) if v.len() == 3 => {
395
+ triangles.push([v[0], v[1], v[2]]);
396
+ },
397
+ PT(v) if v.len() == 3 => {
398
+ triangles.push([v[0].0, v[1].0, v[2].0]);
399
+ },
400
+ PN(v) if v.len() == 3 => {
401
+ triangles.push([v[0].0, v[1].0, v[2].0]);
402
+ },
403
+ PTN(v) if v.len() == 3 => {
404
+ triangles.push([v[0].0, v[1].0, v[2].0]);
405
+ },
406
+ _ => {
407
+ log::info!("Skipping a polygon because it is not a triangle.")
408
+ },
409
+ }
410
+ }
411
+
412
+ if !raw_obj.material_libraries.is_empty() && !raw_obj.meshes.is_empty() {
413
+ let mut materials = IndexMap::new();
414
+
415
+ for material_file in raw_obj.material_libraries {
416
+ match File::open(&material_file) {
417
+ Ok(file) => {
418
+ match obj::raw::material::parse_mtl(BufReader::new(file)) {
419
+ Ok(raw_mat) => {
420
+ for (material_name, material) in raw_mat.materials {
421
+ materials.insert(material_name, material);
422
+ }
423
+ },
424
+ Err(e) => {
425
+ log::warn!(
426
+ "An error occurred when parsing MTL file {material_file}: {e}."
427
+ );
428
+ },
429
+ }
430
+ },
431
+ Err(e) => {
432
+ log::warn!(
433
+ "An error occurred when trying to read MTL file {material_file}: {e}."
434
+ );
435
+ },
436
+ }
437
+ }
438
+
439
+ let mut face_colors = vec![[0.0, 0.0, 0.0]; triangles.len()];
440
+ let mut face_materials = vec![-1; triangles.len()];
441
+ let mut material_names = Vec::new();
442
+
443
+ for (material_index, (material_name, material)) in materials.into_iter().enumerate() {
444
+ if let Some(group) = raw_obj.meshes.remove(&material_name) {
445
+ use obj::raw::material::MtlColor;
446
+ let material_color = if let Some(MtlColor::Rgb(r, g, b)) = material.diffuse {
447
+ [r, g, b]
448
+ } else {
449
+ log::warn!(
450
+ "Currently, we only support diffuse color in RGB format, but it was \
451
+ not present."
452
+ );
453
+ [0.0, 0.0, 0.0]
454
+ };
455
+ for polygon_range in group.polygons {
456
+ face_colors[(polygon_range.start)..(polygon_range.end)]
457
+ .fill(material_color);
458
+ face_materials[(polygon_range.start)..(polygon_range.end)]
459
+ .fill(material_index as isize);
460
+ }
461
+ } else {
462
+ log::warn!(
463
+ "Material {material_name} was listed in OBJ material libraries, but is \
464
+ not present is mesh groups."
465
+ );
466
+ }
467
+ material_names.push(material_name);
468
+ }
469
+
470
+ if !raw_obj.meshes.is_empty() {
471
+ log::warn!(
472
+ "Some materials listed in mesh groups where not listed in material libraries: \
473
+ {}.",
474
+ raw_obj.meshes.into_keys().collect::<Vec<_>>().join(", ")
475
+ );
476
+ }
477
+
478
+ let face_colors = Some(face_colors);
479
+ let face_materials = Some(face_materials);
480
+
481
+ return Self {
482
+ vertices,
483
+ triangles,
484
+ face_colors,
485
+ face_materials,
486
+ material_names,
487
+ ..Default::default()
488
+ };
489
+ }
490
+
491
+ Self {
492
+ vertices,
493
+ triangles,
494
+ ..Default::default()
495
+ }
496
+ }
497
+ }
498
+
499
+ #[cfg(not(tarpaulin_include))]
500
+ #[pymodule]
501
+ pub(crate) fn triangle_mesh(m: Bound<'_, PyModule>) -> PyResult<()> {
502
+ m.add_class::<TriangleMesh>()?;
503
+ Ok(())
504
+ }
@@ -0,0 +1,20 @@
1
+ use pyo3::{prelude::*, wrap_pymodule};
2
+
3
+ pub mod geometry;
4
+ pub mod rt;
5
+ pub mod scene;
6
+
7
+ /// Core of DiffeRT module, implemented in Rust.
8
+ #[cfg(not(tarpaulin_include))]
9
+ #[pymodule]
10
+ fn _lowlevel(m: Bound<'_, PyModule>) -> PyResult<()> {
11
+ pyo3_log::init();
12
+
13
+ let mut version = env!("CARGO_PKG_VERSION").to_string();
14
+ version = version.replace("-alpha", "a").replace("-beta", "b");
15
+ m.add("__version__", version)?;
16
+ m.add_wrapped(wrap_pymodule!(geometry::geometry))?;
17
+ m.add_wrapped(wrap_pymodule!(rt::rt))?;
18
+ m.add_wrapped(wrap_pymodule!(scene::scene))?;
19
+ Ok(())
20
+ }