diff --git a/.gitignore b/.gitignore index 4f822c617b..6fcb8d531f 100644 --- a/.gitignore +++ b/.gitignore @@ -97,4 +97,4 @@ src/ifcopenshell-python/ifcopenshell/ifcopenshell_wrapper.py .DS_Store # clangd cache -.cache \ No newline at end of file +.cache diff --git a/src/ifcconvert/IfcConvert.cpp b/src/ifcconvert/IfcConvert.cpp index bad908352f..aee5824106 100644 --- a/src/ifcconvert/IfcConvert.cpp +++ b/src/ifcconvert/IfcConvert.cpp @@ -637,7 +637,8 @@ int main(int argc, char** argv) { XML = IfcUtil::path::from_utf8(".xml"), CITY_JSON = IfcUtil::path::from_utf8(".cityjson"), IFC = IfcUtil::path::from_utf8(".ifc"), - USD = IfcUtil::path::from_utf8(".usd"); + USD = IfcUtil::path::from_utf8(".usd"), + USDA = IfcUtil::path::from_utf8(".usda"); // @todo clean up serializer selection // @todo detect program options that conflict with the chosen serializer @@ -830,8 +831,8 @@ int main(int argc, char** argv) { serializer = boost::make_shared(IfcUtil::path::to_utf8(output_temp_filename), geometry_settings, serializer_settings); #endif #ifdef WITH_USD - } else if (output_extension == USD) { - serializer = boost::make_shared(IfcUtil::path::to_utf8(output_temp_filename), settings); + } else if (output_extension == USD || output_extension == USDA) { + serializer = boost::make_shared(IfcUtil::path::to_utf8(output_filename), settings); #endif #ifdef IFOPSH_WITH_OPENCASCADE } else if (output_extension == STP) { @@ -1171,9 +1172,11 @@ int main(int argc, char** argv) { Logger::Message(Logger::LOG_PERF, "done file geometry conversion"); + // Renaming might fail (e.g. maybe the existing file was open in a viewer application) // Do not remove the temp file as user can salvage the conversion result from it. - bool successful = IfcUtil::path::rename_file(IfcUtil::path::to_utf8(output_temp_filename), IfcUtil::path::to_utf8(output_filename)); + bool successful = IfcUtil::path::rename_file(IfcUtil::path::to_utf8(output_temp_filename), IfcUtil::path::to_utf8(output_filename)) + || output_extension == USD || output_extension == USDA; if (!successful) { cerr_ << "Unable to write output file '" << output_filename << "', see '" << output_temp_filename << "' for the conversion result."; diff --git a/src/serializers/USDSerializer.cpp b/src/serializers/USDSerializer.cpp index 18667566a2..88c5e232cf 100644 --- a/src/serializers/USDSerializer.cpp +++ b/src/serializers/USDSerializer.cpp @@ -10,23 +10,18 @@ #include "pxr/usd/usdGeom/tokens.h" #include "pxr/usd/usdGeom/scope.h" #include "pxr/usd/usdLux/distantLight.h" -#include "pxr/usd/usdShade/material.h" #include "pxr/usd/usdShade/shader.h" #include "pxr/usd/usdShade/tokens.h" #include "pxr/usd/usdShade/materialBindingAPI.h" #include "pxr/usd/usdShade/connectableAPI.h" -#include -#include - -USDSerializer::USDSerializer(const std::string& filename, const SerializerSettings& settings) - : WriteOnlyGeometrySerializer(settings) - , filename_(filename) +USDSerializer::USDSerializer(const std::string& out_filename, const SerializerSettings& settings): + WriteOnlyGeometrySerializer(settings), + filename_(out_filename) { // create a new stage - stage_ = pxr::UsdStage::CreateNew(filename + ".usda"); - - // create root xform prim 'World' + stage_ = pxr::UsdStage::CreateNew(filename_ + ".usda"); + pxr::UsdGeomXform::Define(stage_, pxr::SdfPath("/World")); pxr::UsdGeomScope::Define(stage_, pxr::SdfPath("/Looks")); createLighting(); @@ -52,32 +47,32 @@ void USDSerializer::createLighting() { } void USDSerializer::writeMaterial(const pxr::UsdGeomMesh& mesh,const IfcGeom::Material& style) { - std::stringstream ss; - ss << "/Looks/" << style.original_name(); - pxr::UsdShadeMaterial::Define(stage_, pxr::SdfPath(ss.str())); - pxr::UsdShadeMaterial material(stage_->GetPrimAtPath(pxr::SdfPath(ss.str()))); - pxr::UsdShadeShader shader = pxr::UsdShadeShader::Define(stage_, pxr::SdfPath(ss.str() + "/Shader")); + std::string path("/Looks/" + sanitize(style.original_name())); + auto material = pxr::UsdShadeMaterial::Define(stage_, pxr::SdfPath(path)); + auto shader = pxr::UsdShadeShader::Define(stage_, pxr::SdfPath(path + "/Shader")); shader.CreateIdAttr().Set(pxr::TfToken("UsdPreviewSurface")); float rgba[4] { 0.18f, 0.18f, 0.18f, 1.0f }; if (style.hasDiffuse()) - for (int i = 0; i < 3; ++i) rgba[i] = static_cast(style.diffuse()[i]); + for (int i = 0; i < 3; ++i) + rgba[i] = static_cast(style.diffuse()[i]); shader.CreateInput(pxr::TfToken("diffuseColor"), pxr::SdfValueTypeNames->Color3f).Set(pxr::GfVec3f(rgba[0], rgba[1], rgba[2])); if(style.hasTransparency()) - rgba[3] = 1.0f - style.transparency(); + rgba[3] -= style.transparency(); shader.CreateInput(pxr::TfToken("opacity"), pxr::SdfValueTypeNames->Float).Set(rgba[3]); if(style.hasSpecular()) { - for (int i = 0; i < 3; ++i) rgba[i] = static_cast(style.specular()[i]); + for (int i = 0; i < 3; ++i) + rgba[i] = static_cast(style.specular()[i]); shader.CreateInput(pxr::TfToken("useSpecularWorkflow"), pxr::SdfValueTypeNames->Int).Set(1); } else { shader.CreateInput(pxr::TfToken("useSpecularWorkflow"), pxr::SdfValueTypeNames->Int).Set(0); } shader.CreateInput(pxr::TfToken("specularColor"), pxr::SdfValueTypeNames->Color3f).Set(pxr::GfVec3f(rgba[0], rgba[1], rgba[2])); - //material.CreateSurfaceOutput().ConnectToSource(shader, pxr::TfToken("surface")); - //pxr::UsdShadeMaterialBindingAPI(mesh).Bind(material); + material.CreateSurfaceOutput().ConnectToSource(shader.ConnectableAPI(), pxr::TfToken("surface")); + pxr::UsdShadeMaterialBindingAPI(mesh).Bind(material); } bool USDSerializer::ready() { @@ -85,36 +80,50 @@ bool USDSerializer::ready() { } void USDSerializer::writeHeader() { - std::stringstream ss; - ss << "File generated by IfcOpenShell " << IFCOPENSHELL_VERSION; - stage_->GetRootLayer()->SetComment(ss.str()); + stage_->GetRootLayer()->SetComment("File generated by IfcOpenShell " + std::string(IFCOPENSHELL_VERSION)); } void USDSerializer::write(const IfcGeom::TriangulationElement* o) { const IfcGeom::Representation::Triangulation& mesh = o->geometry(); - if ( mesh.material_ids().empty() || mesh.verts().empty() || mesh.faces().empty() ) - return; - - std::stringstream ss("/World/"); - ss << mesh.id(); - pxr::UsdGeomMesh usd_mesh = pxr::UsdGeomMesh::Define(stage_, pxr::SdfPath(ss.str())); + auto verts = mesh.verts(); + auto faces = mesh.faces(); const std::vector& m = o->transformation().matrix().data(); - const int vcount = (int) mesh.verts().size() / 3; - pxr::VtVec3dArray points; - for ( std::vector::const_iterator it = mesh.verts().begin(); it != mesh.verts().end(); ) { - points.push_back(pxr::GfVec3d(*(it++), *(it++), *(it++))); + if ( mesh.material_ids().empty() || verts.empty() || faces.empty() ) + return; + + std::string name = o->name() + std::to_string(o->id()); + if(name.empty()) { + name = "Unnamed_" + std::to_string(unnamed_count_); + unnamed_count_++; + } else { + name = sanitize(name); + } + pxr::UsdGeomMesh usd_mesh = pxr::UsdGeomMesh::Define(stage_, pxr::SdfPath("/World/" + name)); + + usd_mesh.AddTranslateOp().Set(pxr::GfVec3d(m[9], m[10], m[11])); + + pxr::VtVec3fArray points; + for(std::size_t i = 0; i < verts.size(); i+=3) { + points.push_back(pxr::GfVec3f(static_cast(verts[i] * m[0] + verts[i+1] * m[3] + verts[i+2] * m[6]), + static_cast(verts[i] * m[1] + verts[i+1] * m[4] + verts[i+2] * m[7]), + static_cast(verts[i] * m[2] + verts[i+1] * m[5] + verts[i+2] * m[8]))); } usd_mesh.CreatePointsAttr().Set(points); - usd_mesh.CreateFaceVertexIndicesAttr().Set(usd_utils::toVtArray(mesh.faces())); - const int fcount = (int) mesh.faces().size() / 3; - usd_mesh.CreateFaceVertexCountsAttr().Set(pxr::VtArray(fcount, 3)); - usd_mesh.CreateNormalsAttr().Set(usd_utils::toVtArray(mesh.normals())); + usd_mesh.CreateFaceVertexIndicesAttr().Set(usd_utils::toVtArray(faces)); + usd_mesh.CreateFaceVertexCountsAttr().Set(pxr::VtArray((int) faces.size() / 3, 3)); + + pxr::VtVec3fArray normals; + for (std::vector::const_iterator it = mesh.normals().begin(); it != mesh.normals().end();) + normals.push_back(pxr::GfVec3f(static_cast(*(it++)), static_cast(*(it++)), static_cast(*(it++)))); + usd_mesh.CreateNormalsAttr().Set(normals); + + writeMaterial(usd_mesh, mesh.materials()[0]); } void USDSerializer::finalize() { - stage_->GetRootLayer()->Save(); + stage_->Save(); } #endif // WITH_USD \ No newline at end of file diff --git a/src/serializers/USDSerializer.h b/src/serializers/USDSerializer.h index 39d9ef0aef..92e7501a21 100644 --- a/src/serializers/USDSerializer.h +++ b/src/serializers/USDSerializer.h @@ -33,17 +33,21 @@ #include "pxr/usd/usd/stage.h" #include "pxr/base/vt/array.h" #include "pxr/usd/usdGeom/mesh.h" +#include "pxr/usd/usdShade/material.h" #include +#include +#include +#include namespace usd_utils { template pxr::VtArray toVtArray(const std::vector& vec) { auto array = pxr::VtArray(vec.size()); - for (size_t i = 0; i < vec.size(); ++i) + for (std::size_t i = 0; i < vec.size(); ++i) array[i] = vec[i]; return array; - } + } } class SERIALIZERS_API USDSerializer : public WriteOnlyGeometrySerializer { @@ -51,6 +55,19 @@ private: std::string filename_; bool ready_ = false; pxr::UsdStageRefPtr stage_; + std::size_t unnamed_count_ = 0; + std::map meshes_; + std::map materials_; + + std::string sanitize(std::string s) { + std::replace_if(s.begin(), s.end(), + [](char c) { return c == ' ' || c == '.' || c == ',' + || c == ';' || c == ':' || c == '/' + || c == '-' || c == '+' || c == '"' + || c > 127; }, + '_'); + return s; + } void writeMaterial(const pxr::UsdGeomMesh&, const IfcGeom::Material&); void createLighting();