Skip to content

Commit 33aa4e7

Browse files
authored
Merge pull request #1919 from alicevision/dev/gltfExport
Fix `convertMesh` software and add `.gltf` / `.glb` options for mesh IO
2 parents 48c8f8f + 8bcfff6 commit 33aa4e7

File tree

7 files changed

+274
-91
lines changed

7 files changed

+274
-91
lines changed
Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,44 @@
1-
__version__ = "1.0"
1+
__version__ = "2.0"
22

33
from meshroom.core import desc
44
from meshroom.core.utils import VERBOSE_LEVEL
55

66

77
class ConvertMesh(desc.AVCommandLineNode):
88
commandLine = "aliceVision_convertMesh {allParams}"
9-
109
category = "Utils"
11-
documentation = """This node allows to convert a mesh to another format."""
10+
documentation = """Convert a mesh to another mesh format."""
1211

1312
inputs = [
1413
desc.File(
1514
name="inputMesh",
1615
label="Input Mesh",
17-
description="Input mesh (*.obj, *.mesh, *.meshb, *.ply, *.off, *.stl).",
16+
description="Input mesh (*.obj, *.fbx, *.gltf, *.glb, *.stl, *.ply).",
1817
value="",
1918
),
2019
desc.ChoiceParam(
2120
name="outputMeshFileType",
2221
label="Output File Type",
23-
description="Output mesh format (*.obj, *.gltf, *.fbx, *.stl).",
22+
description="Output mesh format (*.obj, *.fbx, *.gltf, *.glb, *.stl, *.ply).",
2423
value="obj",
25-
values=["gltf", "obj", "fbx", "stl"],
24+
values=["obj", "fbx", "gltf", "glb", "stl", "ply"],
2625
group="",
2726
),
27+
desc.BoolParam(
28+
name="flipNormals",
29+
label="Flip Normals",
30+
description="Flip face normals. It can be needed as it depends on the vertices order "
31+
"in triangles and the convention changes from one software to another.",
32+
value=False,
33+
advanced=True,
34+
),
35+
desc.BoolParam(
36+
name="copyTextures",
37+
label="Copy Textures",
38+
description="Copy input mesh texture files to the output mesh folder.",
39+
value=True,
40+
advanced=True,
41+
),
2842
desc.ChoiceParam(
2943
name="verboseLevel",
3044
label="Verbose Level",
@@ -38,7 +52,7 @@ class ConvertMesh(desc.AVCommandLineNode):
3852
desc.File(
3953
name="output",
4054
label="Mesh",
41-
description="Output mesh (*.obj, *.mesh, *.meshb, *.ply, *.off, *.stl).",
55+
description="Output mesh (*.obj, *.fbx, *.gltf, *.glb, *.stl, *.ply).",
4256
value="{nodeCacheFolder}/mesh.{outputMeshFileTypeValue}",
4357
),
4458
]

src/aliceVision/mesh/Material.cpp

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -93,17 +93,17 @@ const StaticVector<std::string>& Material::getTextures(TextureType type) const
9393
}
9494
}
9595

96-
StaticVector<std::string> Material::getAllTextures() const
96+
std::vector<std::string> Material::getAllTextures() const
9797
{
98-
StaticVector<std::string> textures;
99-
textures.resize(_bumpTextures.size() + _diffuseTextures.size() + _displacementTextures.size() + _normalTextures.size());
98+
std::vector<std::string> texturePaths;
99+
texturePaths.resize(_bumpTextures.size() + _diffuseTextures.size() + _displacementTextures.size() + _normalTextures.size());
100100

101-
auto last = std::copy(_bumpTextures.begin(), _bumpTextures.end(), textures.begin());
101+
auto last = std::copy(_bumpTextures.begin(), _bumpTextures.end(), texturePaths.begin());
102102
last = std::copy(_diffuseTextures.begin(), _diffuseTextures.end(), last);
103103
last = std::copy(_displacementTextures.begin(), _displacementTextures.end(), last);
104104
std::copy(_normalTextures.begin(), _normalTextures.end(), last);
105105

106-
return textures;
106+
return texturePaths;
107107
}
108108

109109
bool Material::hasTextures(TextureType type) const

src/aliceVision/mesh/Material.hpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,8 @@ class Material
5454
/// Get textures by type
5555
const StaticVector<std::string>& getTextures(TextureType type) const;
5656

57-
/// Get all textures used in the material
58-
StaticVector<std::string> getAllTextures() const;
57+
/// Get all texture paths used in the material
58+
std::vector<std::string> getAllTextures() const;
5959

6060
/// Check if material has textures of a given type
6161
bool hasTextures(TextureType type) const;

src/aliceVision/mesh/Mesh.cpp

Lines changed: 113 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -44,10 +44,14 @@ std::string EFileType_enumToString(const EFileType meshFileType)
4444
return "obj";
4545
case EFileType::FBX:
4646
return "fbx";
47-
case EFileType::STL:
48-
return "stl";
4947
case EFileType::GLTF:
5048
return "gltf";
49+
case EFileType::GLB:
50+
return "glb";
51+
case EFileType::STL:
52+
return "stl";
53+
case EFileType::PLY:
54+
return "ply";
5155
}
5256
throw std::out_of_range("Unrecognized EMeshFileType");
5357
}
@@ -61,10 +65,15 @@ EFileType EFileType_stringToEnum(const std::string& meshFileType)
6165
return EFileType::OBJ;
6266
if (m == "fbx")
6367
return EFileType::FBX;
64-
if (m == "stl")
65-
return EFileType::STL;
6668
if (m == "gltf")
6769
return EFileType::GLTF;
70+
if (m == "glb")
71+
return EFileType::GLB;
72+
if (m == "stl")
73+
return EFileType::STL;
74+
if (m == "ply")
75+
return EFileType::PLY;
76+
6877
throw std::out_of_range("Invalid mesh file type " + meshFileType);
6978
}
7079

@@ -81,26 +90,42 @@ void Mesh::save(const std::string& filepath)
8190
const std::string fileTypeStr = std::filesystem::path(filepath).extension().string().substr(1);
8291
const EFileType fileType = mesh::EFileType_stringToEnum(fileTypeStr);
8392

84-
ALICEVISION_LOG_INFO("Save " << fileTypeStr << " mesh file");
93+
ALICEVISION_LOG_INFO("Saving " << fileTypeStr << " mesh file using Assimp.");
8594

95+
// Assimp scene setup
96+
// create scene and root node
8697
aiScene scene;
98+
scene.mRootNode = new aiNode();
8799

88-
scene.mRootNode = new aiNode;
89-
100+
// create default material
101+
scene.mMaterials = new aiMaterial*[1];
102+
scene.mMaterials[0] = new aiMaterial();
103+
scene.mNumMaterials = 1;
104+
105+
// create mesh
90106
scene.mMeshes = new aiMesh*[1];
107+
scene.mMeshes[0] = new aiMesh();
91108
scene.mNumMeshes = 1;
109+
110+
// link mesh to root node
92111
scene.mRootNode->mMeshes = new unsigned int[1];
112+
scene.mRootNode->mMeshes[0] = 0;
93113
scene.mRootNode->mNumMeshes = 1;
114+
115+
// fill mesh data
116+
aiMesh* aimesh = scene.mMeshes[0];
94117

95-
scene.mMaterials = new aiMaterial*[1];
96-
scene.mNumMaterials = 1;
97-
scene.mMaterials[0] = new aiMaterial;
118+
// set default material index
119+
aimesh->mMaterialIndex = 0;
98120

99-
scene.mRootNode->mMeshes[0] = 0;
100-
scene.mMeshes[0] = new aiMesh;
101-
aiMesh* aimesh = scene.mMeshes[0];
102-
aimesh->mMaterialIndex = 0;
121+
if (fileType == EFileType::GLTF || fileType == EFileType::GLB)
122+
{
123+
// set primitive types to triangles, required for gltf and glb export
124+
// for other file types, primitive types is unspecified to avoid normal generation
125+
aimesh->mPrimitiveTypes = aiPrimitiveType_TRIANGLE;
126+
}
103127

128+
// fill mesh vertices
104129
aimesh->mNumVertices = pts.size();
105130
aimesh->mVertices = new aiVector3D[pts.size()];
106131

@@ -114,6 +139,7 @@ void Mesh::save(const std::string& filepath)
114139
++index;
115140
}
116141

142+
// fill mesh faces
117143
aimesh->mNumFaces = tris.size();
118144
aimesh->mFaces = new aiFace[tris.size()];
119145

@@ -128,31 +154,57 @@ void Mesh::save(const std::string& filepath)
128154
}
129155
}
130156

131-
std::string formatId = fileTypeStr;
157+
// exporter setup
158+
std::string pFormatId = fileTypeStr;
132159
unsigned int pPreprocessing = 0u;
133-
// If gltf, use gltf 2.0
134-
if (fileType == EFileType::GLTF)
160+
161+
if (fileType == EFileType::GLTF || fileType == EFileType::GLB)
135162
{
136-
formatId = "gltf2";
163+
if (fileType == EFileType::GLTF)
164+
{
165+
// gltf file, use gltf 2.0
166+
pFormatId = "gltf2";
167+
}
168+
else
169+
{
170+
// glb file, use glb 2.0
171+
pFormatId = "glb2";
172+
}
173+
137174
// gen normals in order to have correct shading in Qt 3D Scene
138175
// but cause problems with assimp importer
139176
pPreprocessing |= aiProcess_GenNormals;
140177
}
141-
// If obj, do not use material
142178
else if (fileType == EFileType::OBJ)
143179
{
144-
formatId = "objnomtl";
180+
// obj file, do not use material
181+
pFormatId = "objnomtl";
145182
}
146183

184+
// export mesh
147185
Assimp::Exporter exporter;
148-
exporter.Export(&scene, formatId, filepath, pPreprocessing);
186+
const aiReturn ret = exporter.Export(&scene, pFormatId, filepath, pPreprocessing);
187+
188+
// log mesh information
189+
ALICEVISION_LOG_DEBUG("Mesh information:" << std::endl
190+
<< "\t- # vertices: " << pts.size() << std::endl
191+
<< "\t- # triangles: " << tris.size() << std::endl
192+
<< "\t- # UVs: " << uvCoords.size() << std::endl
193+
<< "\t- # normals: " << normals.size());
149194

150-
ALICEVISION_LOG_INFO("Save mesh to " << fileTypeStr << " done.");
195+
// check for errors
196+
if (ret != AI_SUCCESS)
197+
{
198+
if (ret == AI_OUTOFMEMORY)
199+
{
200+
ALICEVISION_LOG_ERROR("Assimp exporter ran out of memory while exporting mesh to " << filepath);
201+
}
151202

152-
ALICEVISION_LOG_DEBUG("Vertices: " << pts.size());
153-
ALICEVISION_LOG_DEBUG("Triangles: " << tris.size());
154-
ALICEVISION_LOG_DEBUG("UVs: " << uvCoords.size());
155-
ALICEVISION_LOG_DEBUG("Normals: " << normals.size());
203+
ALICEVISION_THROW_ERROR("Assimp exporter failed to export mesh to " << filepath << ", error: " << exporter.GetErrorString());
204+
return;
205+
}
206+
207+
ALICEVISION_LOG_INFO("Mesh saved.");
156208
}
157209

158210
bool Mesh::loadFromBin(const std::string& binFilepath)
@@ -2560,52 +2612,72 @@ void Mesh::load(const std::string& filepath, bool mergeCoincidentVerts, Material
25602612
f.v[2] = oldToNewMap[f.v[2]];
25612613
}
25622614
}
2615+
2616+
// get number of materials used and materials properties
2617+
if (material== nullptr || scene->mNumMaterials <= 1)
2618+
{
2619+
std::unordered_set<int> materialIds = std::unordered_set<int>(_trisMtlIds.begin(), _trisMtlIds.end());
25632620

2564-
// set number of materials used
2565-
const std::unordered_set<int> materialIds = std::unordered_set<int>(_trisMtlIds.begin(), _trisMtlIds.end());
2566-
nmtls = static_cast<int>(materialIds.size());
2567-
2568-
// store textures per atlas
2569-
if (material != nullptr)
2621+
// set number of materials used
2622+
nmtls = static_cast<int>(materialIds.size());
2623+
}
2624+
else
25702625
{
2626+
std::unordered_set<int> materialIds; // does not preserve insertion order
2627+
std::vector<int> materialIdsWithTriangleOrder;
2628+
2629+
// build materialIds and materialIdsWithTriangleOrder
2630+
for(int id : _trisMtlIds)
2631+
{
2632+
if(materialIds.insert(id).second)
2633+
materialIdsWithTriangleOrder.push_back(id);
2634+
}
2635+
2636+
// set number of materials used
2637+
nmtls = static_cast<int>(materialIds.size());
2638+
25712639
// get material properties from the first material as they are shared across all others
25722640
scene->mMaterials[1]->Get(AI_MATKEY_COLOR_AMBIENT, material->ambient);
25732641
scene->mMaterials[1]->Get(AI_MATKEY_COLOR_DIFFUSE, material->diffuse);
25742642
scene->mMaterials[1]->Get(AI_MATKEY_COLOR_SPECULAR, material->specular);
25752643
scene->mMaterials[1]->Get(AI_MATKEY_SHININESS, material->shininess);
25762644

2577-
for (int id : materialIds)
2645+
// get textures from the next materials
2646+
for (int id : materialIdsWithTriangleOrder)
25782647
{
25792648
aiString diffuse;
2580-
if (scene->mMaterials[id + 1]->Get(AI_MATKEY_TEXTURE_DIFFUSE(0), diffuse) == aiReturn_SUCCESS)
2649+
if (scene->mMaterials[id + materialIdOffset]->Get(AI_MATKEY_TEXTURE_DIFFUSE(0), diffuse) == aiReturn_SUCCESS)
25812650
{
25822651
material->addTexture(Material::TextureType::DIFFUSE, std::string(diffuse.C_Str()));
25832652
}
25842653

25852654
aiString displacement;
2586-
if (scene->mMaterials[id + 1]->Get(AI_MATKEY_TEXTURE_DISPLACEMENT(0), displacement) == aiReturn_SUCCESS)
2655+
if (scene->mMaterials[id + materialIdOffset]->Get(AI_MATKEY_TEXTURE_DISPLACEMENT(0), displacement) == aiReturn_SUCCESS)
25872656
{
25882657
material->addTexture(Material::TextureType::DISPLACEMENT, std::string(displacement.C_Str()));
25892658
}
25902659

25912660
aiString normal;
2592-
if (scene->mMaterials[id + 1]->Get(AI_MATKEY_TEXTURE_NORMALS(0), normal) == aiReturn_SUCCESS)
2661+
if (scene->mMaterials[id + materialIdOffset]->Get(AI_MATKEY_TEXTURE_NORMALS(0), normal) == aiReturn_SUCCESS)
25932662
{
25942663
material->addTexture(Material::TextureType::NORMAL, std::string(normal.C_Str()));
25952664
}
25962665

25972666
aiString height;
2598-
if (scene->mMaterials[id + 1]->Get(AI_MATKEY_TEXTURE_HEIGHT(0), height) == aiReturn_SUCCESS)
2667+
if (scene->mMaterials[id + materialIdOffset]->Get(AI_MATKEY_TEXTURE_HEIGHT(0), height) == aiReturn_SUCCESS)
25992668
{
26002669
material->addTexture(Material::TextureType::BUMP, std::string(height.C_Str()));
26012670
}
26022671
}
26032672
}
26042673

2605-
ALICEVISION_LOG_DEBUG("Vertices: " << pts.size());
2606-
ALICEVISION_LOG_DEBUG("Triangles: " << tris.size());
2607-
ALICEVISION_LOG_DEBUG("UVs: " << uvCoords.size());
2608-
ALICEVISION_LOG_DEBUG("Num Materials: " + std::to_string(nmtls));
2674+
// log mesh information
2675+
ALICEVISION_LOG_DEBUG("Mesh information:" << std::endl
2676+
<< "\t- # vertices: " << pts.size() << std::endl
2677+
<< "\t- # triangles: " << tris.size() << std::endl
2678+
<< "\t- # UVs: " << uvCoords.size() << std::endl
2679+
<< "\t- # normals: " << normals.size() << std::endl
2680+
<< "\t- # materials: " << nmtls);
26092681
}
26102682

26112683
bool Mesh::getEdgeNeighTrisInterval(Pixel& itr, Pixel& edge, StaticVector<Voxel>& edgesXStat, StaticVector<Voxel>& edgesXYStat)

src/aliceVision/mesh/Mesh.hpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,9 @@ enum class EFileType
5050
OBJ = 0,
5151
FBX,
5252
GLTF,
53-
STL
53+
GLB,
54+
STL,
55+
PLY
5456
};
5557

5658
EFileType EFileType_stringToEnum(const std::string& filetype);

0 commit comments

Comments
 (0)