Skip to content
Open
28 changes: 21 additions & 7 deletions meshroom/aliceVision/ConvertMesh.py
Original file line number Diff line number Diff line change
@@ -1,30 +1,44 @@
__version__ = "1.0"
__version__ = "2.0"

from meshroom.core import desc
from meshroom.core.utils import VERBOSE_LEVEL


class ConvertMesh(desc.AVCommandLineNode):
commandLine = "aliceVision_convertMesh {allParams}"

category = "Utils"
documentation = """This node allows to convert a mesh to another format."""
documentation = """Convert a mesh to another mesh format."""

inputs = [
desc.File(
name="inputMesh",
label="Input Mesh",
description="Input mesh (*.obj, *.mesh, *.meshb, *.ply, *.off, *.stl).",
description="Input mesh (*.obj, *.fbx, *.gltf, *.glb, *.stl, *.ply).",
value="",
),
desc.ChoiceParam(
name="outputMeshFileType",
label="Output File Type",
description="Output mesh format (*.obj, *.gltf, *.fbx, *.stl).",
description="Output mesh format (*.obj, *.fbx, *.gltf, *.glb, *.stl, *.ply).",
value="obj",
values=["gltf", "obj", "fbx", "stl"],
values=["obj", "fbx", "gltf", "glb", "stl", "ply"],
group="",
),
desc.BoolParam(
name="flipNormals",
label="Flip Normals",
description="Flip face normals. It can be needed as it depends on the vertices order "
"in triangles and the convention changes from one software to another.",
value=False,
advanced=True,
),
desc.BoolParam(
name="copyTextures",
label="Copy Textures",
description="Copy input mesh texture files to the output mesh folder.",
value=True,
advanced=True,
),
desc.ChoiceParam(
name="verboseLevel",
label="Verbose Level",
Expand All @@ -38,7 +52,7 @@ class ConvertMesh(desc.AVCommandLineNode):
desc.File(
name="output",
label="Mesh",
description="Output mesh (*.obj, *.mesh, *.meshb, *.ply, *.off, *.stl).",
description="Output mesh (*.obj, *.fbx, *.gltf, *.glb, *.stl, *.ply).",
value="{nodeCacheFolder}/mesh.{outputMeshFileTypeValue}",
),
]
12 changes: 6 additions & 6 deletions src/aliceVision/mesh/Material.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -93,17 +93,17 @@ const StaticVector<std::string>& Material::getTextures(TextureType type) const
}
}

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

auto last = std::copy(_bumpTextures.begin(), _bumpTextures.end(), textures.begin());
auto last = std::copy(_bumpTextures.begin(), _bumpTextures.end(), texturePaths.begin());
last = std::copy(_diffuseTextures.begin(), _diffuseTextures.end(), last);
last = std::copy(_displacementTextures.begin(), _displacementTextures.end(), last);
std::copy(_normalTextures.begin(), _normalTextures.end(), last);

return textures;
return texturePaths;
}

bool Material::hasTextures(TextureType type) const
Expand Down
4 changes: 2 additions & 2 deletions src/aliceVision/mesh/Material.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,8 @@ class Material
/// Get textures by type
const StaticVector<std::string>& getTextures(TextureType type) const;

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

/// Check if material has textures of a given type
bool hasTextures(TextureType type) const;
Expand Down
154 changes: 113 additions & 41 deletions src/aliceVision/mesh/Mesh.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,14 @@ std::string EFileType_enumToString(const EFileType meshFileType)
return "obj";
case EFileType::FBX:
return "fbx";
case EFileType::STL:
return "stl";
case EFileType::GLTF:
return "gltf";
case EFileType::GLB:
return "glb";
case EFileType::STL:
return "stl";
case EFileType::PLY:
return "ply";
}
throw std::out_of_range("Unrecognized EMeshFileType");
}
Expand All @@ -61,10 +65,15 @@ EFileType EFileType_stringToEnum(const std::string& meshFileType)
return EFileType::OBJ;
if (m == "fbx")
return EFileType::FBX;
if (m == "stl")
return EFileType::STL;
if (m == "gltf")
return EFileType::GLTF;
if (m == "glb")
return EFileType::GLB;
if (m == "stl")
return EFileType::STL;
if (m == "ply")
return EFileType::PLY;

throw std::out_of_range("Invalid mesh file type " + meshFileType);
}

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

ALICEVISION_LOG_INFO("Save " << fileTypeStr << " mesh file");
ALICEVISION_LOG_INFO("Saving " << fileTypeStr << " mesh file using Assimp.");

// Assimp scene setup
// create scene and root node
aiScene scene;
scene.mRootNode = new aiNode();

scene.mRootNode = new aiNode;

// create default material
scene.mMaterials = new aiMaterial*[1];
scene.mMaterials[0] = new aiMaterial();
scene.mNumMaterials = 1;

// create mesh
scene.mMeshes = new aiMesh*[1];
scene.mMeshes[0] = new aiMesh();
scene.mNumMeshes = 1;

// link mesh to root node
scene.mRootNode->mMeshes = new unsigned int[1];
scene.mRootNode->mMeshes[0] = 0;
scene.mRootNode->mNumMeshes = 1;

// fill mesh data
aiMesh* aimesh = scene.mMeshes[0];

scene.mMaterials = new aiMaterial*[1];
scene.mNumMaterials = 1;
scene.mMaterials[0] = new aiMaterial;
// set default material index
aimesh->mMaterialIndex = 0;

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

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

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

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

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

std::string formatId = fileTypeStr;
// exporter setup
std::string pFormatId = fileTypeStr;
unsigned int pPreprocessing = 0u;
// If gltf, use gltf 2.0
if (fileType == EFileType::GLTF)

if (fileType == EFileType::GLTF || fileType == EFileType::GLB)
{
formatId = "gltf2";
if (fileType == EFileType::GLTF)
{
// gltf file, use gltf 2.0
pFormatId = "gltf2";
}
else
{
// glb file, use glb 2.0
pFormatId = "glb2";
}

// gen normals in order to have correct shading in Qt 3D Scene
// but cause problems with assimp importer
pPreprocessing |= aiProcess_GenNormals;
}
// If obj, do not use material
else if (fileType == EFileType::OBJ)
{
formatId = "objnomtl";
// obj file, do not use material
pFormatId = "objnomtl";
}

// export mesh
Assimp::Exporter exporter;
exporter.Export(&scene, formatId, filepath, pPreprocessing);
const aiReturn ret = exporter.Export(&scene, pFormatId, filepath, pPreprocessing);

// log mesh information
ALICEVISION_LOG_DEBUG("Mesh information:" << std::endl
<< "\t- # vertices: " << pts.size() << std::endl
<< "\t- # triangles: " << tris.size() << std::endl
<< "\t- # UVs: " << uvCoords.size() << std::endl
<< "\t- # normals: " << normals.size());

ALICEVISION_LOG_INFO("Save mesh to " << fileTypeStr << " done.");
// check for errors
if (ret != AI_SUCCESS)
{
if (ret == AI_OUTOFMEMORY)
{
ALICEVISION_LOG_ERROR("Assimp exporter ran out of memory while exporting mesh to " << filepath);
}

ALICEVISION_LOG_DEBUG("Vertices: " << pts.size());
ALICEVISION_LOG_DEBUG("Triangles: " << tris.size());
ALICEVISION_LOG_DEBUG("UVs: " << uvCoords.size());
ALICEVISION_LOG_DEBUG("Normals: " << normals.size());
ALICEVISION_THROW_ERROR("Assimp exporter failed to export mesh to " << filepath << ", error: " << exporter.GetErrorString());
return;
}

ALICEVISION_LOG_INFO("Mesh saved.");
}

bool Mesh::loadFromBin(const std::string& binFilepath)
Expand Down Expand Up @@ -2560,52 +2612,72 @@ void Mesh::load(const std::string& filepath, bool mergeCoincidentVerts, Material
f.v[2] = oldToNewMap[f.v[2]];
}
}

// get number of materials used and materials properties
if (material== nullptr || scene->mNumMaterials <= 1)
{
std::unordered_set<int> materialIds = std::unordered_set<int>(_trisMtlIds.begin(), _trisMtlIds.end());

// set number of materials used
const std::unordered_set<int> materialIds = std::unordered_set<int>(_trisMtlIds.begin(), _trisMtlIds.end());
nmtls = static_cast<int>(materialIds.size());

// store textures per atlas
if (material != nullptr)
// set number of materials used
nmtls = static_cast<int>(materialIds.size());
}
else
{
std::unordered_set<int> materialIds; // does not preserve insertion order
std::vector<int> materialIdsWithTriangleOrder;

// build materialIds and materialIdsWithTriangleOrder
for(int id : _trisMtlIds)
{
if(materialIds.insert(id).second)
materialIdsWithTriangleOrder.push_back(id);
}

// set number of materials used
nmtls = static_cast<int>(materialIds.size());

// get material properties from the first material as they are shared across all others
scene->mMaterials[1]->Get(AI_MATKEY_COLOR_AMBIENT, material->ambient);
scene->mMaterials[1]->Get(AI_MATKEY_COLOR_DIFFUSE, material->diffuse);
scene->mMaterials[1]->Get(AI_MATKEY_COLOR_SPECULAR, material->specular);
scene->mMaterials[1]->Get(AI_MATKEY_SHININESS, material->shininess);

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

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

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

aiString height;
if (scene->mMaterials[id + 1]->Get(AI_MATKEY_TEXTURE_HEIGHT(0), height) == aiReturn_SUCCESS)
if (scene->mMaterials[id + materialIdOffset]->Get(AI_MATKEY_TEXTURE_HEIGHT(0), height) == aiReturn_SUCCESS)
{
material->addTexture(Material::TextureType::BUMP, std::string(height.C_Str()));
}
}
}

ALICEVISION_LOG_DEBUG("Vertices: " << pts.size());
ALICEVISION_LOG_DEBUG("Triangles: " << tris.size());
ALICEVISION_LOG_DEBUG("UVs: " << uvCoords.size());
ALICEVISION_LOG_DEBUG("Num Materials: " + std::to_string(nmtls));
// log mesh information
ALICEVISION_LOG_DEBUG("Mesh information:" << std::endl
<< "\t- # vertices: " << pts.size() << std::endl
<< "\t- # triangles: " << tris.size() << std::endl
<< "\t- # UVs: " << uvCoords.size() << std::endl
<< "\t- # normals: " << normals.size() << std::endl
<< "\t- # materials: " << nmtls);
}

bool Mesh::getEdgeNeighTrisInterval(Pixel& itr, Pixel& edge, StaticVector<Voxel>& edgesXStat, StaticVector<Voxel>& edgesXYStat)
Expand Down
4 changes: 3 additions & 1 deletion src/aliceVision/mesh/Mesh.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,9 @@ enum class EFileType
OBJ = 0,
FBX,
GLTF,
STL
GLB,
STL,
PLY
};

EFileType EFileType_stringToEnum(const std::string& filetype);
Expand Down
Loading
Loading