Skip to content

Commit 5c982c7

Browse files
authored
Convert triangle mesh model to tmesh map (#6758)
- ToString() for Material and string representation in Python. - MaterialRecord to Material conversion - allows converting triangle mesh models to a map of tmeshes, including materials. Limitation: Only one material per tmesh (same as TriangleMeshModel) - Support for emissive color reading in FileASSIMP (model) and writing for tmesh (tio FIleASSIMP)
1 parent 785878f commit 5c982c7

File tree

10 files changed

+190
-17
lines changed

10 files changed

+190
-17
lines changed

cpp/open3d/io/file_format/FileASSIMP.cpp

+3
Original file line numberDiff line numberDiff line change
@@ -422,6 +422,9 @@ bool ReadModelUsingAssimp(const std::string& filename,
422422
mat->Get(AI_MATKEY_CLEARCOAT_ROUGHNESS_FACTOR,
423423
o3d_mat.base_clearcoat_roughness);
424424
mat->Get(AI_MATKEY_ANISOTROPY, o3d_mat.base_anisotropy);
425+
mat->Get(AI_MATKEY_COLOR_EMISSIVE, color);
426+
o3d_mat.emissive_color =
427+
Eigen::Vector4f(color.r, color.g, color.b, 1.f);
425428
aiString alpha_mode;
426429
mat->Get(AI_MATKEY_GLTF_ALPHAMODE, alpha_mode);
427430
std::string alpha_mode_str(alpha_mode.C_Str());

cpp/open3d/t/geometry/TriangleMesh.cpp

+21-4
Original file line numberDiff line numberDiff line change
@@ -377,6 +377,7 @@ geometry::TriangleMesh TriangleMesh::FromLegacy(
377377
tmat.SetAnisotropy(mat.baseAnisotropy);
378378
tmat.SetBaseClearcoat(mat.baseClearCoat);
379379
tmat.SetBaseClearcoatRoughness(mat.baseClearCoatRoughness);
380+
// no emissive_color in legacy mesh material
380381
if (mat.albedo) tmat.SetAlbedoMap(Image::FromLegacy(*mat.albedo));
381382
if (mat.normalMap) tmat.SetNormalMap(Image::FromLegacy(*mat.normalMap));
382383
if (mat.roughness)
@@ -453,10 +454,6 @@ open3d::geometry::TriangleMesh TriangleMesh::ToLegacy() const {
453454
legacy_mat.baseColor.f4[1] = tmat.GetBaseColor().y();
454455
legacy_mat.baseColor.f4[2] = tmat.GetBaseColor().z();
455456
legacy_mat.baseColor.f4[3] = tmat.GetBaseColor().w();
456-
utility::LogWarning("{},{},{},{}", legacy_mat.baseColor.f4[0],
457-
legacy_mat.baseColor.f4[1],
458-
legacy_mat.baseColor.f4[2],
459-
legacy_mat.baseColor.f4[3]);
460457
}
461458
if (tmat.HasBaseRoughness()) {
462459
legacy_mat.baseRoughness = tmat.GetBaseRoughness();
@@ -523,6 +520,26 @@ open3d::geometry::TriangleMesh TriangleMesh::ToLegacy() const {
523520
return mesh_legacy;
524521
}
525522

523+
std::unordered_map<std::string, geometry::TriangleMesh>
524+
TriangleMesh::FromTriangleMeshModel(
525+
const open3d::visualization::rendering::TriangleMeshModel &model,
526+
core::Dtype float_dtype,
527+
core::Dtype int_dtype,
528+
const core::Device &device) {
529+
std::unordered_map<std::string, TriangleMesh> tmeshes;
530+
for (const auto &mobj : model.meshes_) {
531+
auto tmesh = TriangleMesh::FromLegacy(*mobj.mesh, float_dtype,
532+
int_dtype, device);
533+
// material textures will be on the CPU. GPU resident texture images is
534+
// not yet supported. See comment in Material.cpp
535+
tmesh.SetMaterial(
536+
visualization::rendering::Material::FromMaterialRecord(
537+
model.materials_[mobj.material_idx]));
538+
tmeshes.emplace(mobj.mesh_name, tmesh);
539+
}
540+
return tmeshes;
541+
}
542+
526543
TriangleMesh TriangleMesh::To(const core::Device &device, bool copy) const {
527544
if (!copy && GetDevice() == device) {
528545
return *this;

cpp/open3d/t/geometry/TriangleMesh.h

+26-1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
#pragma once
99

1010
#include <list>
11+
#include <unordered_map>
1112

1213
#include "open3d/core/Tensor.h"
1314
#include "open3d/core/TensorCheck.h"
@@ -16,6 +17,7 @@
1617
#include "open3d/t/geometry/DrawableGeometry.h"
1718
#include "open3d/t/geometry/Geometry.h"
1819
#include "open3d/t/geometry/TensorMap.h"
20+
#include "open3d/visualization/rendering/Model.h"
1921

2022
namespace open3d {
2123
namespace t {
@@ -701,7 +703,8 @@ class TriangleMesh : public Geometry, public DrawableGeometry {
701703
/// values, e.g. vertices, normals, colors.
702704
/// \param int_dtype Int32 or Int64, used to store index values, e.g.
703705
/// triangles.
704-
/// \param device The device where the resulting TriangleMesh resides in.
706+
/// \param device The device where the resulting TriangleMesh resides in
707+
/// (default CPU:0).
705708
static geometry::TriangleMesh FromLegacy(
706709
const open3d::geometry::TriangleMesh &mesh_legacy,
707710
core::Dtype float_dtype = core::Float32,
@@ -711,6 +714,28 @@ class TriangleMesh : public Geometry, public DrawableGeometry {
711714
/// Convert to a legacy Open3D TriangleMesh.
712715
open3d::geometry::TriangleMesh ToLegacy() const;
713716

717+
/// Convert a TriangleMeshModel (e.g. as read from a file with
718+
/// open3d::io::ReadTriangleMeshModel) to an unordered map of mesh names to
719+
/// TriangleMeshes. Only one material is supported per mesh. Materials
720+
/// common to multiple meshes will be dupicated. Textures (as
721+
/// t::geometry::Image) will use shared storage.
722+
/// \param model TriangleMeshModel to convert.
723+
/// \param float_dtype Float32 or Float64, used to store floating point
724+
/// values, e.g. vertices, normals, colors.
725+
/// \param int_dtype Int32 or Int64, used to store index values, e.g.
726+
/// triangles.
727+
/// \param device The device where the resulting TriangleMesh resides in
728+
/// (default CPU:0). Material textures use CPU storage - GPU resident
729+
/// texture images are not yet supported.
730+
/// \return unordered map of constituent mesh names to TriangleMeshes, with
731+
/// materials.
732+
static std::unordered_map<std::string, geometry::TriangleMesh>
733+
FromTriangleMeshModel(
734+
const open3d::visualization::rendering::TriangleMeshModel &model,
735+
core::Dtype float_dtype = core::Float32,
736+
core::Dtype int_dtype = core::Int64,
737+
const core::Device &device = core::Device("CPU:0"));
738+
714739
/// Compute the convex hull of the triangle mesh using qhull.
715740
///
716741
/// This runs on the CPU.

cpp/open3d/t/io/file_format/FileASSIMP.cpp

+8-2
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
// ----------------------------------------------------------------------------
77

88
#include <assimp/GltfMaterial.h>
9+
#include <assimp/material.h>
910
#include <assimp/postprocess.h>
1011
#include <assimp/scene.h>
1112

@@ -357,12 +358,17 @@ bool WriteTriangleMeshUsingASSIMP(const std::string& filename,
357358
auto r = mesh.GetMaterial().GetBaseClearcoatRoughness();
358359
ai_mat->AddProperty(&r, 1, AI_MATKEY_CLEARCOAT_ROUGHNESS_FACTOR);
359360
}
361+
if (mesh.GetMaterial().HasEmissiveColor()) {
362+
auto c = mesh.GetMaterial().GetEmissiveColor();
363+
auto ac = aiColor4D(c.x(), c.y(), c.z(), c.w());
364+
ai_mat->AddProperty(&ac, 1, AI_MATKEY_COLOR_EMISSIVE);
365+
}
360366

361367
// Count texture maps...
362368
// NOTE: GLTF2 expects a single combined roughness/metal map. If the
363369
// model has one we just export it, otherwise if both roughness and
364-
// metal maps are avaialbe we combine them, otherwise if only one or the
365-
// other is available we just export the one map.
370+
// metal maps are available we combine them, otherwise if only one or
371+
// the other is available we just export the one map.
366372
int n_textures = 0;
367373
if (mesh.GetMaterial().HasAlbedoMap()) ++n_textures;
368374
if (mesh.GetMaterial().HasNormalMap()) ++n_textures;

cpp/open3d/visualization/rendering/Material.cpp

+78
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ void Material::SetDefaultProperties() {
2626
SetTransmission(1.f);
2727
SetAbsorptionColor(Eigen::Vector4f(1.f, 1.f, 1.f, 1.f));
2828
SetAbsorptionDistance(1.f);
29+
SetEmissiveColor(Eigen::Vector4f(1.f, 1.f, 1.f, 1.f));
2930
SetPointSize(3.f);
3031
SetLineWidth(1.f);
3132
}
@@ -39,6 +40,24 @@ void Material::SetTextureMap(const std::string &key,
3940
texture_maps_[key] = image.To(core::Device("CPU:0"), true);
4041
}
4142

43+
std::string Material::ToString() const {
44+
if (!IsValid()) {
45+
return "Invalid Material\n";
46+
}
47+
std::ostringstream os;
48+
os << "Material " << material_name_ << '\n';
49+
for (const auto &kv : scalar_properties_) {
50+
os << '\t' << kv.first << ": " << kv.second << '\n';
51+
}
52+
for (const auto &kv : vector_properties_) {
53+
os << '\t' << kv.first << ": " << kv.second.transpose() << '\n';
54+
}
55+
for (const auto &kv : texture_maps_) {
56+
os << '\t' << kv.first << ": " << kv.second.ToString() << '\n';
57+
}
58+
return os.str();
59+
}
60+
4261
void Material::ToMaterialRecord(MaterialRecord &record) const {
4362
record.shader = GetMaterialName();
4463
// Convert base material properties
@@ -63,6 +82,9 @@ void Material::ToMaterialRecord(MaterialRecord &record) const {
6382
if (HasAnisotropy()) {
6483
record.base_anisotropy = GetAnisotropy();
6584
}
85+
if (HasEmissiveColor()) {
86+
record.emissive_color = GetEmissiveColor();
87+
}
6688
if (HasThickness()) {
6789
record.thickness = GetThickness();
6890
}
@@ -124,6 +146,62 @@ void Material::ToMaterialRecord(MaterialRecord &record) const {
124146
}
125147
}
126148

149+
Material Material::FromMaterialRecord(const MaterialRecord &record) {
150+
using t::geometry::Image;
151+
Material tmat(record.shader);
152+
// scalar and vector properties
153+
tmat.SetBaseColor(record.base_color);
154+
tmat.SetBaseMetallic(record.base_metallic);
155+
tmat.SetBaseRoughness(record.base_roughness);
156+
tmat.SetBaseReflectance(record.base_reflectance);
157+
tmat.SetBaseClearcoat(record.base_clearcoat);
158+
tmat.SetBaseClearcoatRoughness(record.base_clearcoat_roughness);
159+
tmat.SetAnisotropy(record.base_anisotropy);
160+
tmat.SetEmissiveColor(record.emissive_color);
161+
// refractive materials
162+
tmat.SetThickness(record.thickness);
163+
tmat.SetTransmission(record.transmission);
164+
tmat.SetAbsorptionDistance(record.absorption_distance);
165+
// points and lines
166+
tmat.SetPointSize(record.point_size);
167+
tmat.SetLineWidth(record.line_width);
168+
// maps
169+
if (record.albedo_img) {
170+
tmat.SetAlbedoMap(Image::FromLegacy(*record.albedo_img));
171+
}
172+
if (record.normal_img) {
173+
tmat.SetNormalMap(Image::FromLegacy(*record.normal_img));
174+
}
175+
if (record.ao_img) {
176+
tmat.SetAOMap(Image::FromLegacy(*record.ao_img));
177+
}
178+
if (record.metallic_img) {
179+
tmat.SetMetallicMap(Image::FromLegacy(*record.metallic_img));
180+
}
181+
if (record.roughness_img) {
182+
tmat.SetRoughnessMap(Image::FromLegacy(*record.roughness_img));
183+
}
184+
if (record.reflectance_img) {
185+
tmat.SetReflectanceMap(Image::FromLegacy(*record.reflectance_img));
186+
}
187+
if (record.clearcoat_img) {
188+
tmat.SetClearcoatMap(Image::FromLegacy(*record.clearcoat_img));
189+
}
190+
if (record.clearcoat_roughness_img) {
191+
tmat.SetClearcoatRoughnessMap(
192+
Image::FromLegacy(*record.clearcoat_roughness_img));
193+
}
194+
if (record.anisotropy_img) {
195+
tmat.SetAnisotropyMap(Image::FromLegacy(*record.anisotropy_img));
196+
}
197+
if (record.ao_rough_metal_img) {
198+
tmat.SetAORoughnessMetalMap(
199+
Image::FromLegacy(*record.ao_rough_metal_img));
200+
}
201+
202+
return tmat;
203+
}
204+
127205
} // namespace rendering
128206
} // namespace visualization
129207
} // namespace open3d

cpp/open3d/visualization/rendering/Material.h

+16
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
#pragma once
99

10+
#include <sstream>
1011
#include <string>
1112

1213
#include "open3d/t/geometry/Image.h"
@@ -34,6 +35,9 @@ class Material {
3435

3536
Material(const Material &mat) = default;
3637

38+
/// Convert from MaterialRecord
39+
static Material FromMaterialRecord(const MaterialRecord &mat);
40+
3741
Material &operator=(const Material &other) = default;
3842

3943
/// Create an empty but valid material for the specified material name
@@ -51,6 +55,9 @@ class Material {
5155
/// Get the name of the material.
5256
const std::string &GetMaterialName() const { return material_name_; }
5357

58+
/// String reprentation for printing.
59+
std::string ToString() const;
60+
5461
/// Returns the texture map map
5562
const TextureMaps &GetTextureMaps() const { return texture_maps_; }
5663

@@ -249,6 +256,9 @@ class Material {
249256
float GetAbsorptionDistance() const {
250257
return GetScalarProperty("absorption_distance");
251258
}
259+
Eigen::Vector4f GetEmissiveColor() const {
260+
return GetVectorProperty("emissive_color");
261+
}
252262

253263
bool HasBaseColor() const { return HasVectorProperty("base_color"); }
254264
bool HasBaseMetallic() const { return HasScalarProperty("metallic"); }
@@ -267,6 +277,9 @@ class Material {
267277
bool HasAbsorptionDistance() const {
268278
return HasScalarProperty("absorption_distance");
269279
}
280+
bool HasEmissiveColor() const {
281+
return HasVectorProperty("emissive_color");
282+
}
270283

271284
void SetBaseColor(const Eigen::Vector4f &value) {
272285
SetVectorProperty("base_color", value);
@@ -295,6 +308,9 @@ class Material {
295308
void SetAbsorptionDistance(float value) {
296309
SetScalarProperty("absorption_distance", value);
297310
}
311+
void SetEmissiveColor(const Eigen::Vector4f &value) {
312+
SetVectorProperty("emissive_color", value);
313+
}
298314

299315
////////////////////////////////////////////////////////////////////////////
300316
///

cpp/pybind/t/geometry/trianglemesh.cpp

+28-6
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,28 @@ The attributes of the triangle mesh have different levels::
238238
"vertex_dtype"_a = core::Float32, "triangle_dtype"_a = core::Int64,
239239
"device"_a = core::Device("CPU:0"),
240240
"Create a TriangleMesh from a legacy Open3D TriangleMesh.");
241+
triangle_mesh.def_static(
242+
"from_triangle_mesh_model", &TriangleMesh::FromTriangleMeshModel,
243+
"model"_a, "vertex_dtype"_a = core::Float32,
244+
"triangle_dtype"_a = core::Int64,
245+
"device"_a = core::Device("CPU:0"),
246+
R"(Convert a TriangleMeshModel (e.g. as read from a file with
247+
`open3d.io.read_triangle_mesh_model()`) to a dictionary of mesh names to
248+
triangle meshes with the specified vertex and triangle dtypes and moved to the
249+
specified device. Only a single material per mesh is supported. Materials common
250+
to multiple meshes will be duplicated. Textures (as t.geometry.Image) will use
251+
shared storage on the CPU (GPU resident images for textures is not yet supported).
252+
253+
Returns:
254+
Dictionary of names to triangle meshes.
255+
256+
Example:
257+
flight_helmet = o3d.data.FlightHelmetModel()
258+
model = o3d.io.read_triangle_model(flight_helmet.path)
259+
mesh_dict = o3d.t.geometry.TriangleMesh.from_triangle_mesh_model(model)
260+
o3d.visualization.draw(list({"name": name, "geometry": tmesh} for
261+
(name, tmesh) in mesh_dict.items()))
262+
)");
241263
// conversion
242264
triangle_mesh.def("to_legacy", &TriangleMesh::ToLegacy,
243265
"Convert to a legacy Open3D TriangleMesh.");
@@ -706,7 +728,7 @@ This function always uses the CPU device.
706728
707729
Returns:
708730
This function creates a face attribute "texture_uvs" and returns a tuple
709-
with (max stretch, num_charts, num_partitions) storing the
731+
with (max stretch, num_charts, num_partitions) storing the
710732
actual amount of stretch, the number of created charts, and the number of
711733
parallel partitions created.
712734
@@ -883,7 +905,7 @@ This function always uses the CPU device.
883905
"max_faces"_a,
884906
R"(Partition the mesh by recursively doing PCA.
885907
886-
This function creates a new face attribute with the name "partition_ids" storing
908+
This function creates a new face attribute with the name "partition_ids" storing
887909
the partition id for each face.
888910
889911
Args:
@@ -892,7 +914,7 @@ the partition id for each face.
892914
893915
Example:
894916
895-
This code partitions a mesh such that each partition contains at most 20k
917+
This code partitions a mesh such that each partition contains at most 20k
896918
faces::
897919
898920
import open3d as o3d
@@ -911,15 +933,15 @@ the partition id for each face.
911933
R"(Returns a new mesh with the faces selected by a boolean mask.
912934
913935
Args:
914-
mask (open3d.core.Tensor): A boolean mask with the shape (N) with N as the
936+
mask (open3d.core.Tensor): A boolean mask with the shape (N) with N as the
915937
number of faces in the mesh.
916-
938+
917939
Returns:
918940
A new mesh with the selected faces. If the original mesh is empty, return an empty mesh.
919941
920942
Example:
921943
922-
This code partitions the mesh using PCA and then visualized the individual
944+
This code partitions the mesh using PCA and then visualized the individual
923945
parts::
924946
925947
import open3d as o3d

cpp/pybind/visualization/rendering/material.cpp

+4
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
#include "open3d/visualization/rendering/Material.h"
1414

15+
#include "open3d/visualization/rendering/MaterialRecord.h"
1516
#include "pybind/open3d_pybind.h"
1617

1718
PYBIND11_MAKE_OPAQUE(
@@ -41,6 +42,9 @@ void pybind_material(py::module& m) {
4142
mat.def(py::init<>())
4243
.def(py::init<Material>(), "", "mat"_a)
4344
.def(py::init<const std::string&>(), "", "material_name"_a)
45+
.def(py::init(&Material::FromMaterialRecord), "material_record"_a,
46+
"Convert from MaterialRecord.")
47+
.def("__repr__", &Material::ToString)
4448
.def("set_default_properties", &Material::SetDefaultProperties,
4549
"Fills material with defaults for common PBR material "
4650
"properties used by Open3D")

0 commit comments

Comments
 (0)