From 3801e52bff1842370899f1e36f1b2f60944a1c66 Mon Sep 17 00:00:00 2001 From: Thomas Krijnen Date: Fri, 6 Feb 2015 13:07:50 +0000 Subject: [PATCH] Add an XML serializer (NB: Not ifcXML / 10303-28) that describes property set data, decomposition relations and SPF header fields --- cmake/CMakeLists.txt | 5 +- src/ifcconvert/ColladaSerializer.h | 1 + src/ifcconvert/GeometrySerializer.h | 9 +- src/ifcconvert/IfcConvert.cpp | 50 +++- src/ifcconvert/OpenCascadeBasedSerializer.h | 1 + src/ifcconvert/Serializer.h | 35 +++ src/ifcconvert/WavefrontObjSerializer.h | 1 + src/ifcconvert/XmlSerializer.cpp | 252 ++++++++++++++++++++ src/ifcconvert/XmlSerializer.h | 41 ++++ win/IfcConvert.vcproj | 12 + 10 files changed, 389 insertions(+), 18 deletions(-) create mode 100644 src/ifcconvert/Serializer.h create mode 100644 src/ifcconvert/XmlSerializer.cpp create mode 100644 src/ifcconvert/XmlSerializer.h diff --git a/cmake/CMakeLists.txt b/cmake/CMakeLists.txt index ec1c1bb315..1696949bce 100644 --- a/cmake/CMakeLists.txt +++ b/cmake/CMakeLists.txt @@ -160,11 +160,12 @@ TARGET_LINK_LIBRARIES(IfcGeom IfcParse) LINK_DIRECTORIES (${LINK_DIRECTORIES} ${IfcOpenShell_BINARY_DIR} ${OCC_LIBRARY_DIR} ${OPENCOLLADA_LIBRARY_DIR} /usr/lib /usr/lib64 /usr/local/lib /usr/local/lib64 ${ICU_LIBRARY_DIR} ${Boost_LIBRARY_DIRS}) -ADD_EXECUTABLE(IfcConvert +ADD_EXECUTABLE(IfcConvert ../src/ifcconvert/ColladaSerializer.cpp ../src/ifcconvert/IfcConvert.cpp ../src/ifcconvert/OpenCascadeBasedSerializer.cpp - ../src/ifcconvert/WavefrontObjSerializer.cpp + ../src/ifcconvert/WavefrontObjSerializer.cpp + ../src/ifcconvert/XmlSerializer.cpp ) TARGET_LINK_LIBRARIES (IfcConvert IfcParse IfcGeom TKernel TKMath TKBRep TKGeomBase TKGeomAlgo TKG3d TKG2d TKShHealing TKTopAlgo TKMesh TKPrim TKBool TKBO TKFillet TKSTEP TKSTEPBase TKSTEPAttr TKXSBase TKSTEP209 TKIGES TKOffset ${Boost_LIBRARIES} ${OPENCOLLADA_LIBRARIES}) diff --git a/src/ifcconvert/ColladaSerializer.h b/src/ifcconvert/ColladaSerializer.h index 48db38a39f..6c3618fce6 100644 --- a/src/ifcconvert/ColladaSerializer.h +++ b/src/ifcconvert/ColladaSerializer.h @@ -157,6 +157,7 @@ public: unit_name = name; unit_magnitude = magnitude; } + void setFile(IfcParse::IfcFile*) {} }; #endif diff --git a/src/ifcconvert/GeometrySerializer.h b/src/ifcconvert/GeometrySerializer.h index 251678edfb..0a62defa18 100644 --- a/src/ifcconvert/GeometrySerializer.h +++ b/src/ifcconvert/GeometrySerializer.h @@ -20,15 +20,14 @@ #ifndef GEOMETRYSERIALIZER_H #define GEOMETRYSERIALIZER_H +#include "../ifcconvert/Serializer.h" #include "../ifcgeom/IfcGeomIterator.h" -class GeometrySerializer { +class GeometrySerializer : public Serializer { public: - virtual bool ready() = 0; - virtual void writeHeader() = 0; - virtual void finalize() = 0; - virtual bool isTesselated() const = 0; virtual ~GeometrySerializer() {} + + virtual bool isTesselated() const = 0; virtual void write(const IfcGeom::TriangulationElement* o) = 0; virtual void write(const IfcGeom::BRepElement* o) = 0; virtual void setUnitNameAndMagnitude(const std::string& name, float magnitude) = 0; diff --git a/src/ifcconvert/IfcConvert.cpp b/src/ifcconvert/IfcConvert.cpp index c2ff5b1872..d1101be7a5 100644 --- a/src/ifcconvert/IfcConvert.cpp +++ b/src/ifcconvert/IfcConvert.cpp @@ -39,6 +39,7 @@ #include "../ifcconvert/IgesSerializer.h" #include "../ifcconvert/StepSerializer.h" #include "../ifcconvert/WavefrontObjSerializer.h" +#include "../ifcconvert/XmlSerializer.h" void printVersion() { std::cerr << "IfcOpenShell IfcConvert " << IFCOPENSHELL_VERSION << std::endl; @@ -55,6 +56,7 @@ void printUsage(const boost::program_options::options_description& generic_optio #endif std::cerr << " .stp STEP Standard for the Exchange of Product Data" << std::endl << " .igs IGES Initial Graphics Exchange Specification" << std::endl + << " .xml XML Property definitions and decomposition tree" << std::endl << std::endl << "Command line options" << std::endl << generic_options << std::endl << "Advanced options" << std::endl << geom_options << std::endl; @@ -196,6 +198,31 @@ int main(int argc, char** argv) { return 1; } + std::string output_extension = output_filename.substr(output_filename.size()-4); + for (std::string::iterator c = output_extension.begin(); c != output_extension.end(); ++c) { + *c = tolower(*c); + } + + Logger::SetOutput(&std::cout, &log_stream); + Logger::Verbosity(verbose ? Logger::LOG_NOTICE : Logger::LOG_ERROR); + + if (output_extension == ".xml") { + int exit_code = 1; + try { + XmlSerializer s(output_filename); + IfcParse::IfcFile f; + if (!f.Init(input_filename)) { + Logger::Message(Logger::LOG_ERROR, "Unable to parse .ifc file"); + } else { + s.setFile(&f); + s.finalize(); + exit_code = 0; + } + } catch (...) {} + write_log(); + return exit_code; + } + IfcGeom::IteratorSettings settings; settings.set(IfcGeom::IteratorSettings::APPLY_DEFAULT_MATERIALS, true); @@ -207,14 +234,6 @@ int main(int argc, char** argv) { settings.set(IfcGeom::IteratorSettings::FORCE_CCW_FACE_ORIENTATION, force_ccw_face_orientation); settings.set(IfcGeom::IteratorSettings::DISABLE_OPENING_SUBTRACTIONS, disable_opening_subtractions); - std::string output_extension = output_filename.substr(output_filename.size()-4); - for (std::string::iterator c = output_extension.begin(); c != output_extension.end(); ++c) { - *c = tolower(*c); - } - - Logger::SetOutput(&std::cout, &log_stream); - Logger::Verbosity(verbose ? Logger::LOG_NOTICE : Logger::LOG_ERROR); - GeometrySerializer* serializer; if (output_extension == ".obj") { const std::string mtl_filename = output_filename.substr(0,output_filename.size()-3) + "mtl"; @@ -288,9 +307,16 @@ int main(int argc, char** argv) { int old_progress = -1; Logger::Status("Creating geometry..."); - // The functions IfcGeomObjects::Get() and IfcGeomObjects::Next() wrap an iterator of all geometrical entities in the Ifc file. - // IfcGeomObjects::Get() returns an IfcGeom::TriangulationElement (see IfcGeomIterator.h for definition) - // IfcGeomObjects::Next() is used to poll whether more geometrical entities are available + // The functions IfcGeom::Iterator::get() and IfcGeom::Iterator::next() + // wrap an iterator of all geometrical products in the Ifc file. + // IfcGeom::Iterator::get() returns an IfcGeom::TriangulationElement or + // -BRepElement pointer, based on current settings. (see IfcGeomIterator.h + // for definition) IfcGeom::Iterator::next() is used to poll whether more + // geometrical entities are available. None of these functions throw + // exceptions, neither for parsing errors or geometrical errors. Upon + // calling next() the entity to be returned has already been processed, a + // true return value guarantees that a successfully processed product is + // available. do { const IfcGeom::Element* geom_object = context_iterator.get(); @@ -316,6 +342,8 @@ int main(int argc, char** argv) { time(&end); int dif = (int) difftime (end,start); printf ("\nConversion took %d seconds\n", dif ); + + return 0; } void write_log() { diff --git a/src/ifcconvert/OpenCascadeBasedSerializer.h b/src/ifcconvert/OpenCascadeBasedSerializer.h index 8b707e40fb..74a76ca341 100644 --- a/src/ifcconvert/OpenCascadeBasedSerializer.h +++ b/src/ifcconvert/OpenCascadeBasedSerializer.h @@ -41,6 +41,7 @@ public: void write(const IfcGeom::TriangulationElement* o) {} void write(const IfcGeom::BRepElement* o); bool isTesselated() const { return false; } + void setFile(IfcParse::IfcFile*) {} }; #endif \ No newline at end of file diff --git a/src/ifcconvert/Serializer.h b/src/ifcconvert/Serializer.h new file mode 100644 index 0000000000..ba5d6fab69 --- /dev/null +++ b/src/ifcconvert/Serializer.h @@ -0,0 +1,35 @@ +/******************************************************************************** + * * + * This file is part of IfcOpenShell. * + * * + * IfcOpenShell is free software: you can redistribute it and/or modify * + * it under the terms of the Lesser GNU General Public License as published by * + * the Free Software Foundation, either version 3.0 of the License, or * + * (at your option) any later version. * + * * + * IfcOpenShell is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * Lesser GNU General Public License for more details. * + * * + * You should have received a copy of the Lesser GNU General Public License * + * along with this program. If not, see . * + * * + ********************************************************************************/ + +#ifndef SERIALIZER_H +#define SERIALIZER_H + +#include "../ifcparse/IfcFile.h" + +class Serializer { +public: + virtual ~Serializer() {} + + virtual bool ready() = 0; + virtual void writeHeader() = 0; + virtual void finalize() = 0; + virtual void setFile(IfcParse::IfcFile*) = 0; +}; + +#endif \ No newline at end of file diff --git a/src/ifcconvert/WavefrontObjSerializer.h b/src/ifcconvert/WavefrontObjSerializer.h index 3301577dd5..7cb67909d2 100644 --- a/src/ifcconvert/WavefrontObjSerializer.h +++ b/src/ifcconvert/WavefrontObjSerializer.h @@ -50,6 +50,7 @@ public: void finalize() {} bool isTesselated() const { return true; } void setUnitNameAndMagnitude(const std::string& name, float magnitude) {} + void setFile(IfcParse::IfcFile*) {} }; #endif \ No newline at end of file diff --git a/src/ifcconvert/XmlSerializer.cpp b/src/ifcconvert/XmlSerializer.cpp new file mode 100644 index 0000000000..81605a7ea3 --- /dev/null +++ b/src/ifcconvert/XmlSerializer.cpp @@ -0,0 +1,252 @@ +#include + +#include +#include +#include + +#include "XmlSerializer.h" + +using boost::property_tree::ptree; +using namespace IfcSchema; + +static std::map argument_name_map; + +// Format an IFC attribute and maybe returns as string. Only literal scalar +// values are converted. Things like entity instances and lists are omitted. +boost::optional format_attribute(const Argument* argument, IfcUtil::ArgumentType argument_type) { + boost::optional value; + switch(argument_type) { + case IfcUtil::Argument_BOOL: { + const bool b = *argument; + value = b ? "true" : "false"; + break; } + case IfcUtil::Argument_DOUBLE: { + const double d = *argument; + std::stringstream stream; + stream << d; + value = stream.str(); + break; } + case IfcUtil::Argument_STRING: + case IfcUtil::Argument_ENUMERATION: { + value = static_cast(*argument); + break; } + case IfcUtil::Argument_INT: { + const int v = *argument; + std::stringstream stream; + stream << v; + value = stream.str(); + break; } + case IfcUtil::Argument_ENTITY: { + IfcUtil::IfcBaseClass* e = *argument; + if (Type::IsSimple(e->type())) { + IfcUtil::IfcBaseType* f = (IfcUtil::IfcBaseType*) e; + value = format_attribute(f->getArgument(0), f->getArgumentType(0)); + } else if (e->is(IfcSchema::Type::IfcSIUnit) || e->is(IfcSchema::Type::IfcConversionBasedUnit)) { + // Some string concatenation to have a unit name as a XML attribute. + + std::string unit_name; + + if (e->is(IfcSchema::Type::IfcSIUnit)) { + IfcSchema::IfcSIUnit* unit = (IfcSchema::IfcSIUnit*) e; + unit_name = IfcSchema::IfcSIUnitName::ToString(unit->Name()); + if (unit->hasPrefix()) { + unit_name = IfcSchema::IfcSIPrefix::ToString(unit->Prefix()) + unit_name; + } + } else { + IfcSchema::IfcConversionBasedUnit* unit = (IfcSchema::IfcConversionBasedUnit*) e; + unit_name = unit->Name(); + } + + for (std::string::iterator c = unit_name.begin(); c != unit_name.end(); ++c) *c = tolower(*c); + + value = unit_name; + } + break; } + } + return value; +} + +// Formats an entity instances as a ptree node, and insert into the DOM. Recurses +// over the entity attributes and writes them as xml attributes of the node. +ptree& format_entity_instance(IfcUtil::IfcBaseEntity* instance, ptree& tree, bool as_link = false) { + ptree child; + const unsigned n = instance->getArgumentCount(); + for (unsigned i = 0; i < n; ++i) { + const Argument* argument = instance->getArgument(i); + if (argument->isNull()) continue; + + std::string argument_name = instance->getArgumentName(i); + std::map::const_iterator argument_name_it; + argument_name_it = argument_name_map.find(argument_name); + if (argument_name_it != argument_name_map.end()) { + argument_name = argument_name_it->second; + } + const IfcUtil::ArgumentType argument_type = instance->getArgumentType(i); + + boost::optional value; + try { + value = format_attribute(argument, argument_type); + } catch (...) {} + + if (value) { + if (as_link) { + if (argument_name == "id") { + child.put(".xlink:href", std::string("#") + *value); + } + } else { + std::stringstream stream; + stream << "." << argument_name; + child.put(stream.str(), *value); + } + } + } + return tree.add_child(Type::ToString(instance->type()), child); +} + +// A function to be called recursively. Template specialization is used +// to descend into decomposition, containment and property relationships. +template +void descend(A* instance, ptree& tree) { + format_entity_instance(instance, tree); +} + +// Returns related entity instances using IFC's objectified relationship +// model. The second and third argument require a member function pointer. +template +typename V::list::ptr get_related(T* t, F f, G g) { + typename U::list::ptr li = (*t.*f)()->as(); + typename V::list::ptr acc(new typename V::list); + for (typename U::list::it it = li->begin(); it != li->end(); ++it) { + U* u = *it; + acc->push((*u.*g)()); + } + return acc; +} + +// Descends into the tree by recursing into IfcRelContainedInSpatialStructure, +// IfcRelDecomposes and IfcRelDefinesByProperties relations. +template <> +void descend(IfcProduct* product, ptree& tree) { + ptree& child = format_entity_instance(product, tree); + + if (product->is(Type::IfcSpatialStructureElement)) { + IfcSpatialStructureElement* structure = (IfcSpatialStructureElement*) product; + + IfcProduct::list::ptr elements = get_related + + (structure, &IfcSpatialStructureElement::ContainsElements, &IfcRelContainedInSpatialStructure::RelatedElements); + + for (IfcProduct::list::it it = elements->begin(); it != elements->end(); ++it) { + descend(*it, child); + } + } + + IfcObjectDefinition::list::ptr structures = get_related + + (product, &IfcProduct::IsDecomposedBy, &IfcRelDecomposes::RelatedObjects); + + for (IfcObjectDefinition::list::it it = structures->begin(); it != structures->end(); ++it) { + IfcObjectDefinition* ob = *it; + if (ob->is(Type::IfcSpatialStructureElement)) { + descend((IfcProduct*)ob, child); + } else { + descend(ob, child); + } + } + + IfcPropertySetDefinition::list::ptr property_sets = get_related + + (product, &IfcProduct::IsDefinedBy, &IfcRelDefinesByProperties::RelatingPropertyDefinition); + + for (IfcPropertySetDefinition::list::it it = property_sets->begin(); it != property_sets->end(); ++it) { + IfcPropertySetDefinition* pset = *it; + if (pset->is(Type::IfcPropertySet)) { + format_entity_instance(pset, child, true); + } + } +} + +// Descends into the tree by recursing into IfcRelDecomposes relations. +template <> +void descend(IfcProject* project, ptree& tree) { + ptree& child = format_entity_instance(project, tree); + + IfcObjectDefinition::list::ptr structures = get_related + + (project, &IfcProject::IsDecomposedBy, &IfcRelDecomposes::RelatedObjects); + + for (IfcObjectDefinition::list::it it = structures->begin(); it != structures->end(); ++it) { + IfcObjectDefinition* ob = *it; + if (ob->is(Type::IfcSpatialStructureElement)) { + descend((IfcProduct*)ob, child); + } else { + descend(ob, child); + } + } +} + +// Format IfcProperty instances and insert into the DOM. IfcComplexProperties are flattened out. +void format_properties(IfcProperty::list::ptr properties, ptree& node) { + for (IfcProperty::list::it it = properties->begin(); it != properties->end(); ++it) { + IfcProperty* p = *it; + if (p->is(Type::IfcComplexProperty)) { + IfcComplexProperty* complex = (IfcComplexProperty*) p; + format_properties(complex->HasProperties(), node); + } else { + format_entity_instance(p, node); + } + } +} + +void XmlSerializer::finalize() { + argument_name_map.insert(std::make_pair("GlobalId", "id")); + + IfcProject::list::ptr projects = file->EntitiesByType(); + if (projects->size() != 1) { + Logger::Message(Logger::LOG_ERROR, "Expected a single IfcProject"); + return; + } + IfcProject* project = *projects->begin(); + + ptree root, header, decomposition, properties; + + // Write the SPF header as XML nodes. + BOOST_FOREACH(const std::string& s, file->header().file_description().description()) { + header.add_child("file_description.description", ptree(s)); + } + BOOST_FOREACH(const std::string& s, file->header().file_name().author()) { + header.add_child("file_name.author", ptree(s)); + } + BOOST_FOREACH(const std::string& s, file->header().file_name().organization()) { + header.add_child("file_name.organization", ptree(s)); + } + BOOST_FOREACH(const std::string& s, file->header().file_schema().schema_identifiers()) { + header.add_child("file_schema.schema_identifiers", ptree(s)); + } + header.put("file_description.implementation_level", file->header().file_description().implementation_level()); + header.put("file_name.name", file->header().file_name().name()); + header.put("file_name.time_stamp", file->header().file_name().time_stamp()); + header.put("file_name.preprocessor_version", file->header().file_name().preprocessor_version()); + header.put("file_name.originating_system", file->header().file_name().originating_system()); + header.put("file_name.authorization", file->header().file_name().authorization()); + + // Descend into the decomposition structure of the IFC file. + descend(project, decomposition); + + // Write all property sets and values as XML nodes. + IfcPropertySet::list::ptr psets = file->EntitiesByType(); + for (IfcPropertySet::list::it it = psets->begin(); it != psets->end(); ++it) { + IfcPropertySet* pset = *it; + ptree& node = format_entity_instance(pset, properties); + format_properties(pset->HasProperties(), node); + } + + root.add_child("ifc.header", header); + root.add_child("ifc.properties", properties); + root.add_child("ifc.decomposition", decomposition); + + root.put("ifc..xmlns:xlink", "http://www.w3.org/1999/xlink"); + + boost::property_tree::xml_writer_settings settings('\t', 1); + boost::property_tree::write_xml(xml_filename, root, std::locale(), settings); +} \ No newline at end of file diff --git a/src/ifcconvert/XmlSerializer.h b/src/ifcconvert/XmlSerializer.h new file mode 100644 index 0000000000..bd8b856f8f --- /dev/null +++ b/src/ifcconvert/XmlSerializer.h @@ -0,0 +1,41 @@ +/******************************************************************************** + * * + * This file is part of IfcOpenShell. * + * * + * IfcOpenShell is free software: you can redistribute it and/or modify * + * it under the terms of the Lesser GNU General Public License as published by * + * the Free Software Foundation, either version 3.0 of the License, or * + * (at your option) any later version. * + * * + * IfcOpenShell is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * Lesser GNU General Public License for more details. * + * * + * You should have received a copy of the Lesser GNU General Public License * + * along with this program. If not, see . * + * * + ********************************************************************************/ + +#ifndef XMLSERIALIZER_H +#define XMLSERIALIZER_H + +#include "../ifcconvert/Serializer.h" + +class XmlSerializer : public Serializer { +private: + IfcParse::IfcFile* file; + std::string xml_filename; +public: + XmlSerializer(const std::string& xml_filename) + : Serializer() + , xml_filename(xml_filename) + {} + + bool ready() { return true; } + void writeHeader() {} + void finalize(); + void setFile(IfcParse::IfcFile* f) { file = f; } +}; + +#endif \ No newline at end of file diff --git a/win/IfcConvert.vcproj b/win/IfcConvert.vcproj index fce94765fa..23cc883718 100644 --- a/win/IfcConvert.vcproj +++ b/win/IfcConvert.vcproj @@ -183,6 +183,10 @@ RelativePath="..\src\ifcconvert\WavefrontObjSerializer.cpp" > + + + + @@ -217,6 +225,10 @@ RelativePath="..\src\ifcconvert\WavefrontObjSerializer.h" > + +