diff --git a/src/ifcconvert/ColladaSerializer.cpp b/src/serializers/ColladaSerializer.cpp similarity index 100% rename from src/ifcconvert/ColladaSerializer.cpp rename to src/serializers/ColladaSerializer.cpp diff --git a/src/ifcconvert/ColladaSerializer.h b/src/serializers/ColladaSerializer.h similarity index 100% rename from src/ifcconvert/ColladaSerializer.h rename to src/serializers/ColladaSerializer.h diff --git a/src/ifcconvert/GeometrySerializer.h b/src/serializers/GeometrySerializer.h similarity index 100% rename from src/ifcconvert/GeometrySerializer.h rename to src/serializers/GeometrySerializer.h diff --git a/src/ifcconvert/IgesSerializer.h b/src/serializers/IgesSerializer.h similarity index 100% rename from src/ifcconvert/IgesSerializer.h rename to src/serializers/IgesSerializer.h diff --git a/src/ifcconvert/OpenCascadeBasedSerializer.cpp b/src/serializers/OpenCascadeBasedSerializer.cpp similarity index 100% rename from src/ifcconvert/OpenCascadeBasedSerializer.cpp rename to src/serializers/OpenCascadeBasedSerializer.cpp diff --git a/src/ifcconvert/OpenCascadeBasedSerializer.h b/src/serializers/OpenCascadeBasedSerializer.h similarity index 100% rename from src/ifcconvert/OpenCascadeBasedSerializer.h rename to src/serializers/OpenCascadeBasedSerializer.h diff --git a/src/ifcconvert/Serializer.h b/src/serializers/Serializer.h similarity index 100% rename from src/ifcconvert/Serializer.h rename to src/serializers/Serializer.h diff --git a/src/ifcconvert/StepSerializer.h b/src/serializers/StepSerializer.h similarity index 100% rename from src/ifcconvert/StepSerializer.h rename to src/serializers/StepSerializer.h diff --git a/src/ifcconvert/SvgSerializer.cpp b/src/serializers/SvgSerializer.cpp similarity index 100% rename from src/ifcconvert/SvgSerializer.cpp rename to src/serializers/SvgSerializer.cpp diff --git a/src/ifcconvert/SvgSerializer.h b/src/serializers/SvgSerializer.h similarity index 100% rename from src/ifcconvert/SvgSerializer.h rename to src/serializers/SvgSerializer.h diff --git a/src/ifcconvert/WavefrontObjSerializer.cpp b/src/serializers/WavefrontObjSerializer.cpp similarity index 100% rename from src/ifcconvert/WavefrontObjSerializer.cpp rename to src/serializers/WavefrontObjSerializer.cpp diff --git a/src/ifcconvert/WavefrontObjSerializer.h b/src/serializers/WavefrontObjSerializer.h similarity index 100% rename from src/ifcconvert/WavefrontObjSerializer.h rename to src/serializers/WavefrontObjSerializer.h diff --git a/src/serializers/XmlSerializer.h b/src/serializers/XmlSerializer.h new file mode 100644 index 0000000000..9c45a1a2c0 --- /dev/null +++ b/src/serializers/XmlSerializer.h @@ -0,0 +1,29 @@ +#define SCHEMA_METHOD + +#include "../ifcparse/IfcFile.h" + +#include + +#include + +template +class schema_delegate { + +}; + +class XmlSerializer : public schema_delegate<> { + +}; + +struct XmlSerializerFactory { + typedef boost::function1 fn; + + class Factory : public std::map { + public: + Factory(); + void bind(const std::string& schema_name, fn); + XmlSerializer* construct(const std::string& schema_name, IfcParse::IfcFile*); + }; + + Factory& implementations(); +}; diff --git a/src/serializers/schema_dependent/XmlSerializer.cpp b/src/serializers/schema_dependent/XmlSerializer.cpp new file mode 100644 index 0000000000..985bf4695c --- /dev/null +++ b/src/serializers/schema_dependent/XmlSerializer.cpp @@ -0,0 +1,475 @@ +/******************************************************************************** +* * +* 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 . * +* * +********************************************************************************/ + +#include + +#include +#include +#include + +#include "XmlSerializer.h" + +#include + +#include "../../ifcparse/IfcSIPrefix.h" +#include "../../ifcgeom/IfcGeom.h" + +using boost::property_tree::ptree; + +namespace { + +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, const std::string& argument_name) { + boost::optional value; + + // Hard-code lat-lon as it represents an array + // of integers best emitted as a single decimal + if (argument_name == "IfcSite.RefLatitude" || + argument_name == "IfcSite.RefLongitude") + { + std::vector angle = *argument; + double deg; + if (angle.size() >= 3) { + deg = angle[0] + angle[1] / 60. + angle[2] / 3600.; + int prec = 8; + if (angle.size() == 4) { + deg += angle[3] / (1000000. * 3600.); + prec = 14; + } + std::stringstream stream; + stream << std::setprecision(prec) << deg; + value = stream.str(); + } + return 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_INSTANCE: { + IfcUtil::IfcBaseClass* e = *argument; + if (!e->declaration().as_entity()) { + IfcUtil::IfcBaseType* f = (IfcUtil::IfcBaseType*) e; + value = format_attribute(f->data().getArgument(0), f->data().getArgument(0)->type(), argument_name); + } else if (e->declaration().is(IfcSchema::IfcSIUnit::Class()) || e->declaration().is(IfcSchema::IfcConversionBasedUnit::Class())) { + // Some string concatenation to have a unit name as a XML attribute. + + std::string unit_name; + + if (e->declaration().is(IfcSchema::IfcSIUnit::Class())) { + 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(); + } + + value = unit_name; + } else if (e->declaration().is(IfcSchema::IfcLocalPlacement::Class())) { + IfcSchema::IfcLocalPlacement* placement = e->as(); + gp_Trsf trsf; + IfcGeom::Kernel kernel; + + if (kernel.convert(placement, trsf)) { + std::stringstream stream; + for (int i = 1; i < 5; ++i) { + for (int j = 1; j < 4; ++j) { + const double trsf_value = trsf.Value(j, i); + stream << trsf_value << " "; + } + stream << ((i == 4) ? "1" : "0 "); + } + value = stream.str(); + } + } + break; } + default: + break; + } + return value; +} + +// Appends to a node with possibly existing attributes +ptree& format_entity_instance(IfcUtil::IfcBaseEntity* instance, ptree& child, ptree& tree, bool as_link = false) { + const unsigned n = instance->declaration().attribute_count(); + for (unsigned i = 0; i < n; ++i) { + const Argument* argument = instance->data().getArgument(i); + if (argument->isNull()) continue; + + std::string argument_name = instance->declaration().attribute_by_index(i)->name(); + 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->data().getArgument(i)->type(); + + const std::string qualified_name = instance->declaration().name() + "." + argument_name; + boost::optional value; + try { + value = format_attribute(argument, argument_type, qualified_name); + } catch (const std::exception& e) { + Logger::Error(e); + } + + 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(instance->declaration().name(), child); +} + +// 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; + return format_entity_instance(instance, child, tree, as_link); +} + +std::string qualify_unrooted_instance(IfcUtil::IfcBaseClass* inst) { + return inst->declaration().name() + "_" + boost::lexical_cast(inst->data().id()); +} + +// A function to be called recursively. Template specialization is used +// to descend into decomposition, containment and property relationships. +template +ptree& descend(A* instance, ptree& tree) { + if (instance->declaration().is(IfcSchema::IfcObjectDefinition::Class())) { + return descend(instance->template as(), tree); + } else { + return 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)()->template 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)()->template as()); + } + return acc; +} + +// Descends into the tree by recursing into IfcRelContainedInSpatialStructure, +// IfcRelDecomposes, IfcRelDefinesByType, IfcRelDefinesByProperties relations. +template <> +ptree& descend(IfcSchema::IfcObjectDefinition* product, ptree& tree) { + ptree& child = format_entity_instance(product, tree); + + if (product->declaration().is(IfcSchema::IfcSpatialStructureElement::Class())) { + IfcSchema::IfcSpatialStructureElement* structure = (IfcSchema::IfcSpatialStructureElement*) product; + + IfcSchema::IfcObjectDefinition::list::ptr elements = get_related + + (structure, &IfcSchema::IfcSpatialStructureElement::ContainsElements, &IfcSchema::IfcRelContainedInSpatialStructure::RelatedElements); + + for (IfcSchema::IfcObjectDefinition::list::it it = elements->begin(); it != elements->end(); ++it) { + descend(*it, child); + } + } + + if (product->declaration().is(IfcSchema::IfcElement::Class())) { + IfcSchema::IfcElement* element = static_cast(product); + IfcSchema::IfcOpeningElement::list::ptr openings = get_related( + element, &IfcSchema::IfcElement::HasOpenings, &IfcSchema::IfcRelVoidsElement::RelatedOpeningElement); + + for (IfcSchema::IfcOpeningElement::list::it it = openings->begin(); it != openings->end(); ++it) { + descend(*it, child); + } + } + +#ifndef USE_IFC4 + IfcSchema::IfcObjectDefinition::list::ptr structures = get_related + + (product, &IfcSchema::IfcObjectDefinition::IsDecomposedBy, &IfcSchema::IfcRelDecomposes::RelatedObjects); +#else + IfcSchema::IfcObjectDefinition::list::ptr structures = get_related + + (product, &IfcSchema::IfcProduct::IsDecomposedBy, &IfcSchema::IfcRelAggregates::RelatedObjects); +#endif + + for (IfcSchema::IfcObjectDefinition::list::it it = structures->begin(); it != structures->end(); ++it) { + IfcSchema::IfcObjectDefinition* ob = *it; + descend(ob, child); + } + + if (product->declaration().is(IfcSchema::IfcObject::Class())) { + IfcSchema::IfcObject* object = product->as(); + + IfcSchema::IfcPropertySetDefinition::list::ptr property_sets = get_related + + (object, &IfcSchema::IfcObject::IsDefinedBy, &IfcSchema::IfcRelDefinesByProperties::RelatingPropertyDefinition); + + for (IfcSchema::IfcPropertySetDefinition::list::it it = property_sets->begin(); it != property_sets->end(); ++it) { + IfcSchema::IfcPropertySetDefinition* pset = *it; + if (pset->declaration().is(IfcSchema::IfcPropertySet::Class())) { + format_entity_instance(pset, child, true); + } + if (pset->declaration().is(IfcSchema::IfcElementQuantity::Class())) { + format_entity_instance(pset, child, true); + } + } + +#ifdef USE_IFC4 + IfcSchema::IfcTypeObject::list::ptr types = get_related + + (object, &IfcSchema::IfcObject::IsTypedBy, &IfcSchema::IfcRelDefinesByType::RelatingType); +#else + IfcSchema::IfcTypeObject::list::ptr types = get_related + + (object, &IfcSchema::IfcObject::IsDefinedBy, &IfcSchema::IfcRelDefinesByType::RelatingType); +#endif + + for (IfcSchema::IfcTypeObject::list::it it = types->begin(); it != types->end(); ++it) { + IfcSchema::IfcTypeObject* type = *it; + format_entity_instance(type, child, true); + } + } + + if (product->declaration().is(IfcSchema::IfcProduct::Class())) { + std::map layers = IfcGeom::Kernel::get_layers(product->as()); + for (std::map::const_iterator it = layers.begin(); it != layers.end(); ++it) { + // IfcPresentationLayerAssignments don't have GUIDs (only optional Identifier) so use name as the ID. + // Note that the IfcPresentationLayerAssignment passed here doesn't really matter as as_link is true + // for the format_entity_instance() call. + ptree node; + node.put(".xlink:href", "#" + it->first); + format_entity_instance(it->second, node, child, true); + } + + IfcSchema::IfcRelAssociates::list::ptr associations = product->HasAssociations(); + for (IfcSchema::IfcRelAssociates::list::it it = associations->begin(); it != associations->end(); ++it) { + if ((*it)->as()) { + IfcSchema::IfcMaterialSelect* mat = (*it)->as()->RelatingMaterial(); + ptree node; + node.put(".xlink:href", "#" + qualify_unrooted_instance(mat)); + format_entity_instance((IfcUtil::IfcBaseEntity*) mat, node, child, true); + } + } + } + + return child; +} + +// Format IfcProperty instances and insert into the DOM. IfcComplexProperties are flattened out. +void format_properties(IfcSchema::IfcProperty::list::ptr properties, ptree& node) { + for (IfcSchema::IfcProperty::list::it it = properties->begin(); it != properties->end(); ++it) { + IfcSchema::IfcProperty* p = *it; + if (p->declaration().is(IfcSchema::IfcComplexProperty::Class())) { + IfcSchema::IfcComplexProperty* complex = (IfcSchema::IfcComplexProperty*) p; + format_properties(complex->HasProperties(), node); + } else { + format_entity_instance(p, node); + } + } +} + +// Format IfcElementQuantity instances and insert into the DOM. +void format_quantities(IfcSchema::IfcPhysicalQuantity::list::ptr quantities, ptree& node) { + for (IfcSchema::IfcPhysicalQuantity::list::it it = quantities->begin(); it != quantities->end(); ++it) { + IfcSchema::IfcPhysicalQuantity* p = *it; + format_entity_instance(p, node); + } +} + +} // ~unnamed namespace + +void XmlSerializer::finalize() { + argument_name_map.insert(std::make_pair("GlobalId", "id")); + + IfcSchema::IfcProject::list::ptr projects = file->instances_by_type(); + if (projects->size() != 1) { + Logger::Message(Logger::LOG_ERROR, "Expected a single IfcProject"); + return; + } + IfcSchema::IfcProject* project = *projects->begin(); + + ptree root, header, units, decomposition, properties, quantities, types, layers, materials; + + // Write the SPF header as XML nodes. + foreach(const std::string& s, file->header().file_description().description()) { + header.add_child("file_description.description", ptree(s)); + } + foreach(const std::string& s, file->header().file_name().author()) { + header.add_child("file_name.author", ptree(s)); + } + foreach(const std::string& s, file->header().file_name().organization()) { + header.add_child("file_name.organization", ptree(s)); + } + 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. + IfcSchema::IfcPropertySet::list::ptr psets = file->instances_by_type(); + for (IfcSchema::IfcPropertySet::list::it it = psets->begin(); it != psets->end(); ++it) { + IfcSchema::IfcPropertySet* pset = *it; + ptree& node = format_entity_instance(pset, properties); + format_properties(pset->HasProperties(), node); + } + + // Write all quantities and values as XML nodes. + IfcSchema::IfcElementQuantity::list::ptr qtosets = file->instances_by_type(); + for (IfcSchema::IfcElementQuantity::list::it it = qtosets->begin(); it != qtosets->end(); ++it) { + IfcSchema::IfcElementQuantity* qto = *it; + ptree& node = format_entity_instance(qto, quantities); + format_quantities(qto->Quantities(), node); + } + + + // Write all type objects as XML nodes. + IfcSchema::IfcTypeObject::list::ptr type_objects = file->instances_by_type(); + for (IfcSchema::IfcTypeObject::list::it it = type_objects->begin(); it != type_objects->end(); ++it) { + IfcSchema::IfcTypeObject* type_object = *it; + ptree& node = descend(type_object, types); + // ptree& node = format_entity_instance(type_object, types); + + if (type_object->hasHasPropertySets()) { + IfcSchema::IfcPropertySetDefinition::list::ptr property_sets = type_object->HasPropertySets(); + for (IfcSchema::IfcPropertySetDefinition::list::it jt = property_sets->begin(); jt != property_sets->end(); ++jt) { + IfcSchema::IfcPropertySetDefinition* pset = *jt; + if (pset->declaration().is(IfcSchema::IfcPropertySet::Class())) { + format_entity_instance(pset, node, true); + } + } + } + } + + // Write all assigned units as XML nodes. + IfcEntityList::ptr unit_assignments = project->UnitsInContext()->Units(); + for (IfcEntityList::it it = unit_assignments->begin(); it != unit_assignments->end(); ++it) { + if ((*it)->declaration().is(IfcSchema::IfcNamedUnit::Class())) { + IfcSchema::IfcNamedUnit* named_unit = (*it)->as(); + ptree& node = format_entity_instance(named_unit, units); + node.put(".SI_equivalent", IfcParse::get_SI_equivalent(named_unit)); + } else if ((*it)->declaration().is(IfcSchema::IfcMonetaryUnit::Class())) { + format_entity_instance((*it)->as(), units); + } + } + + // Layer assignments. IfcPresentationLayerAssignments don't have GUIDs (only optional Identifier) + // so use names as the IDs and only insert those with unique names. In case of possible duplicate names/IDs + // the first IfcPresentationLayerAssignment occurence takes precedence. + std::set layer_names; + IfcSchema::IfcPresentationLayerAssignment::list::ptr layer_assignments = file->instances_by_type(); + for (IfcSchema::IfcPresentationLayerAssignment::list::it it = layer_assignments->begin(); it != layer_assignments->end(); ++it) { + const std::string& name = (*it)->Name(); + if (layer_names.find(name) == layer_names.end()) { + layer_names.insert(name); + ptree node; + node.put(".id", name); + format_entity_instance(*it, node, layers); + } + } + + IfcSchema::IfcRelAssociatesMaterial::list::ptr materal_associations = file->instances_by_type(); + std::set emitted_materials; + for (IfcSchema::IfcRelAssociatesMaterial::list::it it = materal_associations->begin(); it != materal_associations->end(); ++it) { + IfcSchema::IfcMaterialSelect* mat = (**it).RelatingMaterial(); + if (emitted_materials.find(mat) == emitted_materials.end()) { + emitted_materials.insert(mat); + ptree node; + node.put(".id", qualify_unrooted_instance(mat)); + if (mat->as()) { + IfcSchema::IfcMaterialLayerSet* layerset = mat->as()->ForLayerSet(); + if (layerset->hasLayerSetName()) { + node.put(".LayerSetName", layerset->LayerSetName()); + } + IfcSchema::IfcMaterialLayer::list::ptr ls = layerset->MaterialLayers(); + for (IfcSchema::IfcMaterialLayer::list::it jt = ls->begin(); jt != ls->end(); ++jt) { + ptree subnode; + if ((*jt)->hasMaterial()) { + subnode.put(".Name", (*jt)->Material()->Name()); + } + format_entity_instance(*jt, subnode, node); + } + } else if (mat->as()) { + IfcSchema::IfcMaterial::list::ptr mats = mat->as()->Materials(); + for (IfcSchema::IfcMaterial::list::it jt = mats->begin(); jt != mats->end(); ++jt) { + ptree subnode; + format_entity_instance(*jt, subnode, node); + } + } + format_entity_instance((IfcUtil::IfcBaseEntity*) mat, node, materials); + } + } + + root.add_child("ifc.header", header); + root.add_child("ifc.units", units); + root.add_child("ifc.properties", properties); + root.add_child("ifc.quantities", quantities); + root.add_child("ifc.types", types); + root.add_child("ifc.layers", layers); + root.add_child("ifc.materials", materials); + root.add_child("ifc.decomposition", decomposition); + + root.put("ifc..xmlns:xlink", "http://www.w3.org/1999/xlink"); + +#if BOOST_VERSION >= 105600 + boost::property_tree::xml_writer_settings settings = boost::property_tree::xml_writer_make_settings('\t', 1); +#else + boost::property_tree::xml_writer_settings settings('\t', 1); +#endif + boost::property_tree::write_xml(xml_filename, root, std::locale(), settings); +} diff --git a/src/serializers/schema_dependent/XmlSerializer.h b/src/serializers/schema_dependent/XmlSerializer.h new file mode 100644 index 0000000000..b72de8cb5a --- /dev/null +++ b/src/serializers/schema_dependent/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 "../../serializers/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