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.
- {differt_core-0.0.29 → differt_core-0.0.30}/Cargo.lock +1 -1
- {differt_core-0.0.29 → differt_core-0.0.30}/PKG-INFO +1 -1
- {differt_core-0.0.29 → differt_core-0.0.30}/differt-core/Cargo.toml +1 -1
- differt_core-0.0.30/differt-core/LICENSE.md +21 -0
- differt_core-0.0.30/differt-core/README.md +33 -0
- {differt_core-0.0.29 → differt_core-0.0.30}/pyproject.toml +6 -0
- differt_core-0.0.30/src/geometry/mod.rs +10 -0
- differt_core-0.0.30/src/geometry/triangle_mesh.rs +504 -0
- differt_core-0.0.30/src/lib.rs +20 -0
- differt_core-0.0.30/src/rt/graph.rs +1505 -0
- differt_core-0.0.30/src/rt/mod.rs +10 -0
- differt_core-0.0.30/src/scene/mod.rs +12 -0
- differt_core-0.0.30/src/scene/sionna.rs +233 -0
- differt_core-0.0.30/src/scene/triangle_scene.rs +79 -0
- {differt_core-0.0.29 → differt_core-0.0.30}/Cargo.toml +0 -0
- {differt_core-0.0.29/differt-core → differt_core-0.0.30}/LICENSE.md +0 -0
- {differt_core-0.0.29/differt-core → differt_core-0.0.30}/README.md +0 -0
- {differt_core-0.0.29 → differt_core-0.0.30}/differt-core/benches/bench_main.rs +0 -0
- {differt_core-0.0.29 → differt_core-0.0.30}/differt-core/benches/benchmarks/graph_iterators.rs +0 -0
- {differt_core-0.0.29 → differt_core-0.0.30}/differt-core/benches/benchmarks/mod.rs +0 -0
- {differt_core-0.0.29 → differt_core-0.0.30}/differt-core/python/differt_core/__init__.py +0 -0
- {differt_core-0.0.29 → differt_core-0.0.30}/differt-core/python/differt_core/_lowlevel/__init__.pyi +0 -0
- {differt_core-0.0.29 → differt_core-0.0.30}/differt-core/python/differt_core/_lowlevel/geometry/triangle_mesh.pyi +0 -0
- {differt_core-0.0.29 → differt_core-0.0.30}/differt-core/python/differt_core/_lowlevel/rt/__init__.pyi +0 -0
- {differt_core-0.0.29 → differt_core-0.0.30}/differt-core/python/differt_core/_lowlevel/rt/graph.pyi +0 -0
- {differt_core-0.0.29 → differt_core-0.0.30}/differt-core/python/differt_core/_lowlevel/scene/sionna.pyi +0 -0
- {differt_core-0.0.29 → differt_core-0.0.30}/differt-core/python/differt_core/_lowlevel/scene/triangle_scene.pyi +0 -0
- {differt_core-0.0.29 → differt_core-0.0.30}/differt-core/python/differt_core/geometry/__init__.py +0 -0
- {differt_core-0.0.29 → differt_core-0.0.30}/differt-core/python/differt_core/geometry/_triangle_mesh.py +0 -0
- {differt_core-0.0.29 → differt_core-0.0.30}/differt-core/python/differt_core/py.typed +0 -0
- {differt_core-0.0.29 → differt_core-0.0.30}/differt-core/python/differt_core/rt/__init__.py +0 -0
- {differt_core-0.0.29 → differt_core-0.0.30}/differt-core/python/differt_core/rt/_graph.py +0 -0
- {differt_core-0.0.29 → differt_core-0.0.30}/differt-core/python/differt_core/scene/__init__.py +0 -0
- {differt_core-0.0.29 → differt_core-0.0.30}/differt-core/python/differt_core/scene/_sionna.py +0 -0
- {differt_core-0.0.29 → differt_core-0.0.30}/differt-core/python/differt_core/scene/_triangle_scene.py +0 -0
- {differt_core-0.0.29 → differt_core-0.0.30}/differt-core/src/geometry/mod.rs +0 -0
- {differt_core-0.0.29 → differt_core-0.0.30}/differt-core/src/geometry/triangle_mesh.rs +0 -0
- {differt_core-0.0.29 → differt_core-0.0.30}/differt-core/src/lib.rs +0 -0
- {differt_core-0.0.29 → differt_core-0.0.30}/differt-core/src/rt/graph.rs +0 -0
- {differt_core-0.0.29 → differt_core-0.0.30}/differt-core/src/rt/mod.rs +0 -0
- {differt_core-0.0.29 → differt_core-0.0.30}/differt-core/src/scene/mod.rs +0 -0
- {differt_core-0.0.29 → differt_core-0.0.30}/differt-core/src/scene/sionna.rs +0 -0
- {differt_core-0.0.29 → differt_core-0.0.30}/differt-core/src/scene/triangle_scene.rs +0 -0
- {differt_core-0.0.29 → differt_core-0.0.30}/differt-core/tests/__init__.py +0 -0
- {differt_core-0.0.29 → differt_core-0.0.30}/differt-core/tests/rt/__init__.py +0 -0
- {differt_core-0.0.29 → differt_core-0.0.30}/differt-core/tests/rt/test_graph.py +0 -0
- {differt_core-0.0.29 → differt_core-0.0.30}/python/differt_core/__init__.py +0 -0
- {differt_core-0.0.29 → differt_core-0.0.30}/python/differt_core/_lowlevel/__init__.pyi +0 -0
- {differt_core-0.0.29 → differt_core-0.0.30}/python/differt_core/_lowlevel/geometry/triangle_mesh.pyi +0 -0
- {differt_core-0.0.29 → differt_core-0.0.30}/python/differt_core/_lowlevel/rt/__init__.pyi +0 -0
- {differt_core-0.0.29 → differt_core-0.0.30}/python/differt_core/_lowlevel/rt/graph.pyi +0 -0
- {differt_core-0.0.29 → differt_core-0.0.30}/python/differt_core/_lowlevel/scene/sionna.pyi +0 -0
- {differt_core-0.0.29 → differt_core-0.0.30}/python/differt_core/_lowlevel/scene/triangle_scene.pyi +0 -0
- {differt_core-0.0.29 → differt_core-0.0.30}/python/differt_core/geometry/__init__.py +0 -0
- {differt_core-0.0.29 → differt_core-0.0.30}/python/differt_core/geometry/_triangle_mesh.py +0 -0
- {differt_core-0.0.29 → differt_core-0.0.30}/python/differt_core/py.typed +0 -0
- {differt_core-0.0.29 → differt_core-0.0.30}/python/differt_core/rt/__init__.py +0 -0
- {differt_core-0.0.29 → differt_core-0.0.30}/python/differt_core/rt/_graph.py +0 -0
- {differt_core-0.0.29 → differt_core-0.0.30}/python/differt_core/scene/__init__.py +0 -0
- {differt_core-0.0.29 → differt_core-0.0.30}/python/differt_core/scene/_sionna.py +0 -0
- {differt_core-0.0.29 → differt_core-0.0.30}/python/differt_core/scene/_triangle_scene.py +0 -0
|
@@ -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,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
|
+
}
|