diff --git a/src/ifcconvert/IfcConvert.cpp b/src/ifcconvert/IfcConvert.cpp index 44f5affb14..81a7b37a79 100644 --- a/src/ifcconvert/IfcConvert.cpp +++ b/src/ifcconvert/IfcConvert.cpp @@ -41,6 +41,7 @@ #include "../serializers/USDSerializer.h" #include "../serializers/TtlWktSerializer.h" #include "../serializers/RocksDbSerializer.h" +#include "../serializers/JsonSerializer.h" #include "../ifcgeom/IfcGeomFilter.h" #include "../ifcgeom/Iterator.h" @@ -128,7 +129,10 @@ void print_usage(bool suggest_help = true) << " .stp STEP Standard for the Exchange of Product Data\n" << " .igs IGES Initial Graphics Exchange Specification\n" << " .xml XML Property definitions and decomposition tree\n" - << " .rdb RocksDB RocksDB Key-Value store serialization of IFC data\n" +#ifdef WITH_GLTF + << " .json JSON Property definitions and decomposition tree in xeokit json format\n" +#endif + << " .rdb RocksDB RocksDB Key-Value store serialization of IFC data\n" << " .svg SVG Scalable Vector Graphics (2D floor plan)\n" #ifdef WITH_HDF5 << " .h5 HDF Hierarchical Data Format storing positions, normals and indices\n" @@ -654,7 +658,8 @@ int main(int argc, char** argv) { CACHE = IfcUtil::path::from_utf8(".cache"), HDF = IfcUtil::path::from_utf8(".h5"), XML = IfcUtil::path::from_utf8(".xml"), - // @todo this is just temporary as it doesn't make sense to require an extension for a DB + JSON = IfcUtil::path::from_utf8(".json"), + // @todo this is just temporary as it doesn't make sense to require an extension for a DB RDB = IfcUtil::path::from_utf8(".rdb"), CITY_JSON = IfcUtil::path::from_utf8(".cityjson"), IFC = IfcUtil::path::from_utf8(".ifc"), @@ -665,15 +670,23 @@ int main(int argc, char** argv) { // @todo clean up serializer selection // @todo detect program options that conflict with the chosen serializer - if (output_extension == XML) { + if (output_extension == XML || output_extension == JSON) { int exit_code = EXIT_FAILURE; try { if (init_input_file(IfcUtil::path::to_utf8(input_filename), ifc_file, no_progress || quiet, mmap)) { time_t start, end; time(&start); - XmlSerializer s(ifc_file, IfcUtil::path::to_utf8(output_temp_filename)); - Logger::Status("Writing XML output..."); - s.finalize(); + if (output_extension == XML) { + XmlSerializer s(ifc_file, IfcUtil::path::to_utf8(output_temp_filename)); + Logger::Status("Writing XML output..."); + s.finalize(); + } else { +#ifdef WITH_GLTF + JsonSerializer s(ifc_file, IfcUtil::path::to_utf8(output_temp_filename), JsonSerializer::JSON_DIALECT_CREOOX); + Logger::Status("Writing JSON output..."); + s.finalize(); +#endif + } time(&end); Logger::Status("Done! Conversion took " + format_duration(start, end)); diff --git a/src/ifcopenshell-python/ifcopenshell/geom/main.py b/src/ifcopenshell-python/ifcopenshell/geom/main.py index a1f00bb137..0800ac7763 100644 --- a/src/ifcopenshell-python/ifcopenshell/geom/main.py +++ b/src/ifcopenshell-python/ifcopenshell/geom/main.py @@ -672,7 +672,7 @@ class serializers: # so no wrap_buffer_creation() for these serializers xml = ifcopenshell_wrapper.XmlSerializer buffer = ifcopenshell_wrapper.buffer - # gltf and hdf5 availability depend on IfcOpenShell configuration settings + # gltf, hdf5, collada and json availability depend on IfcOpenShell configuration settings try: gltf = ifcopenshell_wrapper.GltfSerializer except: @@ -685,6 +685,10 @@ class serializers: collada = ifcopenshell_wrapper.ColladaSerializer except: pass + try: + json = ifcopenshell_wrapper.JsonSerializer + except: + pass # ttl is always available since it doesn't depend on any C++ libraries, # just people might be using an outdated binary if hasattr(ifcopenshell_wrapper, "TtlWktSerializer"): diff --git a/src/ifcwrap/IfcGeomWrapper.i b/src/ifcwrap/IfcGeomWrapper.i index 6991abbdaf..e155c2837e 100644 --- a/src/ifcwrap/IfcGeomWrapper.i +++ b/src/ifcwrap/IfcGeomWrapper.i @@ -275,6 +275,7 @@ namespace { %include "../serializers/XmlSerializer.h" %include "../serializers/GltfSerializer.h" %include "../serializers/TtlWktSerializer.h" +%include "../serializers/JsonSerializer.h" %extend ifcopenshell::geometry::taxonomy::style { size_t instance_id() const { diff --git a/src/ifcwrap/IfcPython.i b/src/ifcwrap/IfcPython.i index 8595e264e2..689f9acfbf 100644 --- a/src/ifcwrap/IfcPython.i +++ b/src/ifcwrap/IfcPython.i @@ -308,6 +308,7 @@ constexpr bool is_std_vector_vector_v = is_std_vector_vector::value; #include "../serializers/GltfSerializer.h" #include "../serializers/TtlWktSerializer.h" #include "../serializers/RocksDbSerializer.h" + #include "../serializers/JsonSerializer.h" #ifdef HAS_SCHEMA_2x3 #include "../ifcparse/Ifc2x3.h" diff --git a/src/serializers/JsonSerializer.cpp b/src/serializers/JsonSerializer.cpp new file mode 100644 index 0000000000..ed3c4ac3d5 --- /dev/null +++ b/src/serializers/JsonSerializer.cpp @@ -0,0 +1,42 @@ +#include "JsonSerializer.h" + +#include +#include +#include + +#define EXTERNAL_DEFS(r, data, elem) \ + extern void BOOST_PP_CAT(init_JsonSerializer_Ifc, elem)(JsonSerializerFactory::Factory*); + +#define CALL_DEFS(r, data, elem) \ + BOOST_PP_CAT(init_JsonSerializer_Ifc, elem)(this); + +BOOST_PP_SEQ_FOR_EACH(EXTERNAL_DEFS, , SCHEMA_SEQ) + +JsonSerializerFactory::Factory::Factory() { + BOOST_PP_SEQ_FOR_EACH(CALL_DEFS, , SCHEMA_SEQ) +} + +void JsonSerializerFactory::Factory::bind(const std::string& schema_name, fn f) { + const std::string schema_name_lower = boost::to_lower_copy(schema_name); + this->insert(std::make_pair(schema_name_lower, f)); +} + +JsonSerializer* JsonSerializerFactory::Factory::construct(const std::string& schema_name, IfcParse::IfcFile* file, std::string json_filename, JsonSerializer::Dialect dialect) { + const std::string schema_name_lower = boost::to_lower_copy(schema_name); + auto it = this->find(schema_name_lower); + if (it == this->end()) { + throw IfcParse::IfcException("No Json serializer registered for " + schema_name); + } + return it->second(file, json_filename, dialect); +} + +JsonSerializer::JsonSerializer(IfcParse::IfcFile* file, const std::string& json_filename, JsonSerializer::Dialect dialect) { + if (file) { + implementation_ = JsonSerializerFactory::implementations().construct(file->schema()->name(), file, json_filename, dialect); + } +} + +JsonSerializerFactory::Factory& JsonSerializerFactory::implementations() { + static JsonSerializerFactory::Factory impl; + return impl; +} diff --git a/src/serializers/JsonSerializer.h b/src/serializers/JsonSerializer.h new file mode 100644 index 0000000000..fddade17fe --- /dev/null +++ b/src/serializers/JsonSerializer.h @@ -0,0 +1,52 @@ +#ifndef JSONSERIALIZER_H +#define JSONSERIALIZER_H + +#ifdef WITH_GLTF + +#include "../ifcgeom/Serializer.h" +#include "../ifcparse/IfcFile.h" +#include "../serializers/serializers_api.h" + +#include +#include + +class SERIALIZERS_API JsonSerializer : public Serializer { + public: + enum Dialect { + JSON_DIALECT_CREOOX + }; + private: + JsonSerializer* implementation_; + + protected: + std::string json_filename; + Dialect dialect_; + + public: + JsonSerializer(IfcParse::IfcFile* file, const std::string& json_filename, Dialect dialect = Dialect::JSON_DIALECT_CREOOX); + + virtual ~JsonSerializer() {} + + bool ready() { return true; } + void writeHeader() {} + + void finalize() { implementation_->finalize(); } + void setFile(IfcParse::IfcFile*) { throw IfcParse::IfcException("Should be supplied on construction"); } +}; + +struct SERIALIZERS_API JsonSerializerFactory { + typedef boost::function2 fn; + + class Factory : public std::map { + public: + Factory(); + void bind(const std::string& schema_name, fn); + JsonSerializer* construct(const std::string& schema_name, IfcParse::IfcFile*, std::string, JsonSerializer::Dialect); + }; + + static Factory& implementations(); +}; + +#endif + +#endif diff --git a/src/serializers/schema_dependent/JsonSerializer.cpp b/src/serializers/schema_dependent/JsonSerializer.cpp new file mode 100644 index 0000000000..10628afb67 --- /dev/null +++ b/src/serializers/schema_dependent/JsonSerializer.cpp @@ -0,0 +1,857 @@ +/******************************************************************************** +* * +* 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 . * +* * +********************************************************************************/ + +#ifdef WITH_GLTF + +#include "JsonSerializer.h" + +#include +#include + +#include + +#include "../../ifcparse/IfcSIPrefix.h" +#include "../../ifcparse/utils.h" +#include "../../ifcparse/IfcLogger.h" + +using json = nlohmann::json; + +namespace { + struct POSTFIX_SCHEMA(factory_t) { + JsonSerializer* operator()(IfcParse::IfcFile* file, const std::string& json_filename, JsonSerializer::Dialect dialect) const { + POSTFIX_SCHEMA(JsonSerializer)* s = new POSTFIX_SCHEMA(JsonSerializer)(file, json_filename, dialect); + s->setFile(file); + return s; + } + }; +} + +void MAKE_INIT_FN(JsonSerializer)(JsonSerializerFactory::Factory* mapping) { + static const std::string schema_name = STRINGIFY(IfcSchema); + POSTFIX_SCHEMA(factory_t) factory; + mapping->bind(schema_name, factory); +} + +class format_value_visitor : public boost::static_visitor { + public: + template + json operator()(const T& t) const { + if constexpr (std::is_same_v, Derived> || std::is_same_v, boost::dynamic_bitset<>> || std::is_same_v, IfcUtil::IfcBaseClass*> || std::is_same_v, std::vector> || std::is_same_v, std::vector> || std::is_same_v, std::vector> || std::is_same_v, std::vector>> || std::is_same_v, aggregate_of_instance::ptr> || std::is_same_v, aggregate_of_aggregate_of_instance::ptr> || std::is_same_v, std::vector>> || std::is_same_v, std::vector>> || std::is_same_v, empty_aggregate_t> || std::is_same_v, empty_aggregate_of_aggregate_t> || std::is_same_v, Blank>) { + return ""; + } else if constexpr (std::is_same_v, boost::logic::tribool>) { + // @todo handle indeterminate + return ""; + } else if constexpr (std::is_same_v, std::string>) { + return t; + } else if constexpr (std::is_same_v, EnumerationReference>) { + return t.value(); + } else { + return t; + } + } +}; + +class get_type_visitor : public boost::static_visitor { + public: + template + std::string operator()(const T& t) const { + // @todo more types + return "number"; + } +}; + +// A function to be called recursively. Template specialization is used +// to descend into decomposition, containment and property relationships. +template +void descend(A* instance, json& tree, IfcUtil::IfcBaseEntity* parent = nullptr) { + if (instance->declaration().is(IfcSchema::IfcObjectDefinition::Class())) { + return descend(instance->template as(), tree, parent); + } 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 +auto get_related(T* t, F f, G g) { + typename U::list::ptr li = (*t.*f)()->template as(); + typename aggregate_of::ptr acc(new aggregate_of); + for (typename U::list::it it = li->begin(); it != li->end(); ++it) { + U* u = *it; + try { + acc->push((*u.*g)()->template as()); + } catch (IfcParse::IfcException& e) { + Logger::Error(e); + } + } + return acc; +} + +void format_entity_instance(IfcUtil::IfcBaseEntity* instance, json& tree, IfcUtil::IfcBaseEntity* parent = nullptr) { + /* + { + "id" : string, // Element GUID (IFC GloballyUniqueId) + "name" : string, // Element name + "longName" ?: string, // Long name (for spatial elements) + "type" : string, // IFC entity type + "parent" : string | null, // Parent element GUID (null for root) + "groups" ?: string[], // Array of group GUIDs + "ObjectType" ?: string, // ObjectType attribute (for IfcObject) + "tag" ?: string, // Tag attribute (for IfcElement) + "attributes" ?: { + // Special attributes + "elevation" ?: number // Elevation for IfcBuildingStorey + }, + "propertySetIds" ?: string[] // Array of property set GUIDs + } + */ + + json child; + + auto write_to_json = [&](const std::string& keyJson, const std::string& keyIfc) { + AttributeValue val; + try { + val = instance->get(keyIfc); + } catch (const IfcParse::IfcException&) { + // simply laziness like no attribute Tag on IfcProject + return; + } + if (!val.isNull()) { + child[keyJson] = val.apply_visitor(format_value_visitor{}); + } + }; + + write_to_json("id", "GlobalId"); + write_to_json("name", "Name"); + write_to_json("longName", "LongName"); + child["type"] = instance->declaration().name(); + if (parent) { + if (auto* rt = parent->as()) { + child["parent"] = rt->GlobalId(); + } + } + // @todo groups + write_to_json("ObjectType", "ObjectType"); + write_to_json("tag", "Tag"); + if (auto* storey = instance->as()) { + auto elevation = storey->Elevation(); + if (elevation) { + child["attributes"] = json::object({{"elevation", *elevation}}); + } + } + + if (auto* obj = instance->as()) { + IfcSchema::IfcPropertySetDefinition::list::ptr property_sets = get_related(obj, &IfcSchema::IfcObject::IsDefinedBy, &IfcSchema::IfcRelDefinesByProperties::RelatingPropertyDefinition); + if (!property_sets && property_sets->size()) { + child["propertySetIds"] = json::array(); + for (IfcSchema::IfcPropertySetDefinition::list::it it = property_sets->begin(); it != property_sets->end(); ++it) { + IfcSchema::IfcPropertySetDefinition* pset = *it; + child["propertySetIds"].push_back(pset->GlobalId()); + } + } + } + + tree.push_back(child); +} + +// @todo would be nice to generalize this with the XML version +// +// Descends into the tree by recursing into IfcRelContainedInSpatialStructure, +// IfcRelDecomposes, IfcRelDefinesByType, IfcRelDefinesByProperties relations. +template <> +void descend(IfcSchema::IfcObjectDefinition* product, json& tree, IfcUtil::IfcBaseEntity* parent) { + if (product->declaration().is(IfcSchema::IfcElement::Class())) { + auto voids = product->as()->FillsVoids(); + if (voids && voids->size() == 1 && (*voids->begin())->RelatingOpeningElement() != parent) { + // Fills are placed under their corresponding opening, return early to avoid duplication. + return; + } + } + + format_entity_instance(product, tree, parent); + + if (product->declaration().is(IfcSchema::IfcOpeningElement::Class())) { + IfcSchema::IfcOpeningElement* opening = product->as(); + IfcSchema::IfcElement::list::ptr fills = get_related( + opening, &IfcSchema::IfcOpeningElement::HasFillings, &IfcSchema::IfcRelFillsElement::RelatedBuildingElement); + + for (IfcSchema::IfcElement::list::it it = fills->begin(); it != fills->end(); ++it) { + descend(*it, tree, product); + } + } + + if (product->declaration().is(IfcSchema::IfcSpatialStructureElement::Class())) { + IfcSchema::IfcSpatialStructureElement* structure = product->as(); + + 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, tree, product); + } + } + + 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, tree, product); + } + } + +#ifdef SCHEMA_IfcRelDecomposes_HAS_RelatedObjects + IfcSchema::IfcObjectDefinition::list::ptr structures = get_related(product, &IfcSchema::IfcObjectDefinition::IsDecomposedBy, &IfcSchema::IfcRelDecomposes::RelatedObjects); +#else + IfcSchema::IfcObjectDefinition::list::ptr structures = get_related(product, &IfcSchema::IfcObjectDefinition::IsDecomposedBy, &IfcSchema::IfcRelAggregates::RelatedObjects); + + structures->push(get_related(product, &IfcSchema::IfcObjectDefinition::IsNestedBy, &IfcSchema::IfcRelNests::RelatedObjects)); +#endif + + for (IfcSchema::IfcObjectDefinition::list::it it = structures->begin(); it != structures->end(); ++it) { + IfcSchema::IfcObjectDefinition* ob = *it; + descend(ob, tree, product); + } + + // psets are handled as part of format_entity_instance() + // all other relationships are not needed in JSON output +} + +IfcSchema::IfcValue* get_value_from_prop(const IfcSchema::IfcProperty* prop) { + if (auto* psv = prop->as()) { + if (auto* nv = psv->NominalValue()) { + return nv; + } + } + // @todo other unit typs + return nullptr; +} + +IfcSchema::IfcUnit* get_unit_from_prop(const IfcSchema::IfcProperty* prop) { + if (auto* psv = prop->as()) { + if (auto* un = psv->Unit()) { + return un; + } + } + // @todo other unit typs + return nullptr; +} + +void POSTFIX_SCHEMA(JsonSerializer)::finalize() { + json output; + + 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(); + + auto catch_exceptions = [this](const auto& fn) { + try { + return fn(); + } catch (const std::exception& e) { + Logger::Error(e); + static std::invoke_result_t v; + return v; + } + }; + + output["id"] = catch_exceptions([&]() { return file->header().file_name()->name(); }); + output["projectId"] = catch_exceptions([&]() { return project->GlobalId(); }); + output["author"] = catch_exceptions([&]() { return file->header().file_name()->author().empty() ? "unknown" : file->header().file_name()->author().front(); }); + output["createdAt"] = catch_exceptions([&]() { return file->header().file_name()->time_stamp(); }); + output["schema"] = catch_exceptions([&]() { return file->header().file_schema()->schema_identifiers().front(); }); // without schema we would not be here + output["creatingApplication"] = catch_exceptions([&]() { return file->header().file_name()->originating_system(); }); + output["properties"] = json::array(); + output["propertySets"] = json::array(); + output["units"] = json::array(); + output["projectUnits"] = json::object(); + output["metaObjects"] = json::array(); + output["groups"] = json::array(); + + // Maps for deduplication of properties and quantities + std::map property_to_index; + std::unordered_map json_to_index; + + // Obtain sequence of units because properties, quantities reference them by index. + // IfcUnit is a select of IfcDerivedUnit, IfcMonetaryUnit and IfcNamedUnit. + // Unfortunately, instances_by_type() does not support select types directly (even though there isn't a real reason for that). + IfcSchema::IfcUnit::list::ptr units(new IfcSchema::IfcUnit::list); + units->push(file->instances_by_type()->as()); + units->push(file->instances_by_type()->as()); + units->push(file->instances_by_type()->as()); + + auto format_property = [&](const IfcUtil::IfcBaseEntity* prop_) { + json jprop; + /* + { + "name": "LoadBearing", + "ifcPropertyType": "IfcPropertySingleValue", + "ifcValueType": "IfcBoolean", + "value": "True", + "valueType": "boolean" + }, + */ + if (auto* prop = prop_->as()) { + jprop["name"] = prop->Name(); + jprop["ifcPropertyType"] = prop->declaration().name(); + if (auto* val = get_value_from_prop(prop)) { + jprop["ifcValueType"] = val->declaration().name(); + jprop["value"] = val->data().get_attribute_value(nullptr, nullptr, 0, 0).apply_visitor(format_value_visitor{}); + jprop["valueType"] = val->data().get_attribute_value(nullptr, nullptr, 0, 0).apply_visitor(get_type_visitor{}); + } + if (auto* unit = get_unit_from_prop(prop)) { + jprop["unit"] = std::distance(units->begin(), std::find(units->begin(), units->end(), unit)); + } + } + + return jprop; + }; + + auto format_quantity = [&](const IfcUtil::IfcBaseEntity* qto_) { + json jprop; + /* + { + "name": "GrossVolume", + "ifcPropertyType": "IfcQuantityVolume", + "value": 12.5, + "valueType": "ElementQuantity", + "unit": 3 + } + */ + if (auto* qto = qto_->as()) { + jprop["name"] = qto->Name(); + jprop["ifcPropertyType"] = qto->declaration().name(); + if (auto* prop = qto->as()) { + jprop["ifcValueType"] = prop->declaration().attributes()[0]->name(); + jprop["value"] = prop->data().get_attribute_value(nullptr, nullptr, 0, 3).apply_visitor(format_value_visitor{}); + jprop["valueType"] = "number"; + if (auto* unit = prop->Unit()) { + jprop["unit"] = std::distance(units->begin(), std::find(units->begin(), units->end(), unit)); + } + } + } + return jprop; + }; + + auto deduplicate = [&](auto base_formatter) { + return [&, base_formatter](const IfcUtil::IfcBaseEntity* prop) mutable -> std::size_t { + if (auto it = property_to_index.find(prop); it != property_to_index.end()) { + return it->second; + } + + // Build JSON for this property + json j = base_formatter(prop); + + // Check if an identical JSON object is already in the global list + auto [it2, inserted] = json_to_index.try_emplace(j, output["properties"].size()); + if (inserted) { + // First time we've seen this JSON -> append to output + output["properties"].push_back(j); + } + + std::size_t idx = it2->second; + property_to_index.emplace(prop, idx); // remember for this pointer too + return idx; + }; + }; + auto property_index_for = deduplicate(format_property); + auto quantity_index_for = deduplicate(format_quantity); + + auto pset_predef_or_qsets = file->instances_by_type(); + for (auto& inst : *pset_predef_or_qsets) { + std::vector property_indices; + if (auto* pset = inst->as()) { + auto props = pset->HasProperties(); + for (auto& prop : *props) { + std::size_t index = property_index_for(prop); + property_indices.push_back(index); + } + } else if (auto* qset = inst->as()) { + auto qtos = qset->Quantities(); + for (auto& qto : *qtos) { + std::size_t index = quantity_index_for(qto); + property_indices.push_back(index); + } +#ifdef SCHEMA_HAS_IfcPreDefinedPropertySet + // ifc2x3 does not have this type yet, just inherits from IfcPropertySetDefinition + } else if (auto* pset = inst->as()) { +#else + } else { +#endif + /* + // not all_attributes() only the attributes defined on this particular concrete type + // @todo actually I don't know how to map PreDefinedPropertySet yet + auto attributes = inst->declaration().attributes(); + for (auto* a : attributes) { + auto val = inst->get(a->name()); + if (val.isNull()) { + continue; + } + val.apply_visitor(format_value_visitor{}); + } + */ + } + + /* + { + "id" : "3fG7k$Hj2_9Pxd8vD_xg7", + "name" : "Pset_WallCommon", + "type" : "IfcPropertySet", + "properties" : [ 0, 1, 2 ] + }, + */ + output["propertySets"].push_back(json::object({{"id", inst->GlobalId()}, + {"name", *inst->Name()}, // @todo optional + {"type", inst->declaration().name()}, + {"properties", property_indices}})); + } + + for (auto& unit : *units) { + /* + { + "name": string, // Unit symbol/name + "className": string, // IFC unit class + "unitEnum"?: string, // Unit type enum + "unitType"?: string, // Unit type (alternative) + "prefix"?: string, // SI prefix (for IfcSIUnit) + "userDefinedType"?: string, // User-defined type + "conversionFactor"?: { // Conversion factor (for IfcConversionBasedUnit) + "valueComponent": { + "value": number, + "valueType": string, + "ifcValueType": string + }, + "unitComponent": number // Reference to base unit index + }, + "elements"?: [ // For IfcDerivedUnit + { + "unit": number, // Reference to unit index + "exponent": number // Exponent value + } + ], + "dimensions"?: { // IfcDimensionalExponents + "LengthExponent": number, + "MassExponent": number, + "TimeExponent": number, + "ElectricCurrentExponent": number, + "ThermodynamicTemperatureExponent": number, + "AmountOfSubstanceExponent": number, + "LuminousIntensityExponent": number + } + } + */ + json junit; + junit["className"] = unit->declaration().name(); + if (auto* siunit = unit->as()) { + // @todo figure out how to encode name for si units + std::string unit_name = ""; + junit["unitEnum"] = IfcSchema::IfcUnitEnum::ToString(siunit->UnitType()); + if (siunit->Prefix()) { + junit["prefix"] = IfcSchema::IfcSIPrefix::ToString(*siunit->Prefix()); + unit_name.push_back(IfcSchema::IfcSIPrefix::ToString(*siunit->Prefix())[0]); + } + unit_name.push_back(IfcSchema::IfcSIUnitName::ToString(siunit->Name())[0]); + boost::to_lower(unit_name); + junit["name"] = unit_name; + } else if (auto* convunit = unit->as()) { + junit["name"] = convunit->Name(); + junit["unitEnum"] = IfcSchema::IfcUnitEnum::ToString(convunit->UnitType()); + if (convunit->ConversionFactor()) { + json jconv; + auto val = convunit->ConversionFactor()->ValueComponent(); + jconv["valueComponent"] = { + {"value", val->data().get_attribute_value(nullptr, nullptr, 0, 0).apply_visitor(format_value_visitor{})}, + {"valueType", val->data().get_attribute_value(nullptr, nullptr, 0, 0).apply_visitor(get_type_visitor{})} + }; + jconv["unitComponent"] = std::distance(units->begin(), std::find(units->begin(), units->end(), convunit->ConversionFactor()->UnitComponent())); + junit["conversionFactor"] = jconv; + } + } else if (auto* derunit = unit->as()) { +#ifdef SCHEMA_IfcDerivedUnit_HAS_Name + // 4.3 onwards + if (derunit->Name()) { + junit["name"] = *derunit->Name(); + } +#endif + json jelements = json::array(); + auto elements = derunit->Elements(); + for (auto& elem : *elements) { + jelements.push_back({ + {"unit", std::distance(units->begin(), std::find(units->begin(), units->end(), elem->Unit()))}, + {"exponent", elem->Exponent()} + }); + } + junit["elements"] = jelements; + } + if (auto* namedunit = unit->as()) { + // support for derived attributes is only available in python + if (namedunit->as() == nullptr) { + if (auto* dimexp = namedunit->Dimensions()) { + junit["dimensions"] = { + {"LengthExponent", dimexp->LengthExponent()}, + {"MassExponent", dimexp->MassExponent()}, + {"TimeExponent", dimexp->TimeExponent()}, + {"ElectricCurrentExponent", dimexp->ElectricCurrentExponent()}, + {"ThermodynamicTemperatureExponent", dimexp->ThermodynamicTemperatureExponent()}, + {"AmountOfSubstanceExponent", dimexp->AmountOfSubstanceExponent()}, + {"LuminousIntensityExponent", dimexp->LuminousIntensityExponent()}}; + } + } + } + output["units"].push_back(junit); + } + + auto project_units = project->UnitsInContext()->Units(); + /* + { + "LENGTHUNIT": number, + "AREAUNIT": number, + "VOLUMEUNIT": number, + "PLANEANGLEUNIT": number, + "MASSUNIT": number, + "TIMEUNIT": number, + // ... other unit types + }*/ + for (auto* pu : *project_units) { + auto it = std::find(units->begin(), units->end(), pu); + if (auto* nu = pu->as()) { + if (it != units->end()) { + output["projectUnits"][IfcSchema::IfcUnitEnum::ToString(nu->UnitType())] = std::distance(units->begin(), it); + } + } + } + + /* + // meta objects + { + "id": string, // Element GUID (IFC GloballyUniqueId) + "name": string, // Element name + "longName"?: string, // Long name (for spatial elements) + "type": string, // IFC entity type + "parent": string | null, // Parent element GUID (null for root) + "groups"?: string[], // Array of group GUIDs + "ObjectType"?: string, // ObjectType attribute (for IfcObject) + "tag"?: string, // Tag attribute (for IfcElement) + "attributes"?: { // Special attributes + "elevation"?: number // Elevation for IfcBuildingStorey + }, + "propertySetIds"?: string[] // Array of property set GUIDs + } + */ + + descend(project, output["metaObjects"]); + + std::ofstream f(IfcUtil::path::from_utf8(json_filename).c_str()); + f << output.dump(4); +} + + /* + + ptree root, header, units, decomposition, properties, quantities, types, layers, materials, work, calendars, connections, groups; + + + // Write the SPF header as XML nodes. + BOOST_FOREACH (const std::string& s, catch_exceptions([this]() { return file->header().file_description()->description(); })) { + header.add_child("file_description.description", ptree(s)); + } + BOOST_FOREACH (const std::string& s, catch_exceptions([this]() { return file->header().file_name()->author(); })) { + header.add_child("file_name.author", ptree(s)); + } + BOOST_FOREACH (const std::string& s, catch_exceptions([this]() { return file->header().file_name()->organization(); })) { + header.add_child("file_name.organization", ptree(s)); + } + BOOST_FOREACH (const std::string& s, catch_exceptions([this]() { return file->header().file_schema()->schema_identifiers(); })) { + header.add_child("file_schema.schema_identifiers", ptree(s)); + } + try { + header.put("file_description.implementation_level", file->header().file_description()->implementation_level()); + } catch (const IfcParse::IfcException& ex) { + std::stringstream ss; + ss << "Failed to get ifc file header file_description implementation_level, error: '" << ex.what() << "'"; + Logger::Message(Logger::LOG_ERROR, ss.str()); + } + try { + header.put("file_name.name", file->header().file_name()->name()); + } catch (const IfcParse::IfcException& ex) { + std::stringstream ss; + ss << "Failed to get ifc file header file_name name, error: '" << ex.what() << "'"; + Logger::Message(Logger::LOG_ERROR, ss.str()); + } + try { + header.put("file_name.time_stamp", file->header().file_name()->time_stamp()); + } catch (const IfcParse::IfcException& ex) { + std::stringstream ss; + ss << "Failed to get ifc file header file_name time_stamp, error: '" << ex.what() << "'"; + Logger::Message(Logger::LOG_ERROR, ss.str()); + } + try { + header.put("file_name.preprocessor_version", file->header().file_name()->preprocessor_version()); + } catch (const IfcParse::IfcException& ex) { + std::stringstream ss; + ss << "Failed to get ifc file header file_name preprocessor_version, error: '" << ex.what() << "'"; + Logger::Message(Logger::LOG_ERROR, ss.str()); + } + try { + header.put("file_name.originating_system", file->header().file_name()->originating_system()); + } catch (const IfcParse::IfcException& ex) { + std::stringstream ss; + ss << "Failed to get ifc file header file_name originating_system, error: '" << ex.what() << "'"; + Logger::Message(Logger::LOG_ERROR, ss.str()); + } + try { + // @nb inconsistent spelling + header.put("file_name.authorization", file->header().file_name()->authorization()); + } catch (const IfcParse::IfcException& ex) { + std::stringstream ss; + ss << "Failed to get ifc file header file_name authorization, error: '" << ex.what() << "'"; + Logger::Message(Logger::LOG_ERROR, ss.str()); + } + + // Descend into the decomposition structure of the IFC file. + descend(mapping_, 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(mapping_, pset, properties); + if (node) { + format_properties(mapping_, pset->HasProperties(), *node); + } + } + + // Write all group sets and values as XML nodes. + IfcSchema::IfcGroup::list::ptr gsets = file->instances_by_type(); + std::set notRootGroups; //selfname, fathername + for (IfcSchema::IfcGroup::list::it it = gsets->begin(); it != gsets->end(); ++it) { + writeGroupToNode(mapping_, *it, groups, notRootGroups); + } + for (auto it = groups.begin(); it != groups.end();) { + if (notRootGroups.find(it->second.get(".Name")) != notRootGroups.end()) { + it = groups.erase(it); + } else { + it++; + } + } + + // 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(mapping_, qto, quantities); + if (node) { + format_quantities(mapping_, qto->Quantities(), *node); + } + } + + // Write all work schedules and values as XML nodes. + ptree pwork_schedules; + IfcSchema::IfcWorkSchedule::list::ptr pschedules = file->instances_by_type(); + for (IfcSchema::IfcWorkSchedule::list::it it = pschedules->begin(); it != pschedules->end(); ++it) { + IfcSchema::IfcWorkSchedule* schedule = *it; + ptree* nschedule = format_entity_instance(mapping_, schedule, pwork_schedules); + + if (nschedule) { + IfcSchema::IfcRelAssignsToControl::list::ptr controls = schedule->Controls(); + for (IfcSchema::IfcRelAssignsToControl::list::it it2 = controls->begin(); it2 != controls->end(); ++it2) { + IfcSchema::IfcRelAssignsToControl* control = *it2; + + IfcSchema::IfcObjectDefinition::list::ptr objects = control->RelatedObjects(); + for (IfcSchema::IfcObjectDefinition::list::it it3 = objects->begin(); it3 != objects->end(); ++it3) { + IfcSchema::IfcObjectDefinition* object = *it3; + + if (object && object->declaration().is(IfcSchema::IfcTask::Class())) { + IfcSchema::IfcTask* task = object->as(); + format_tasks(mapping_, task, *nschedule); + } + } + } + } + } + work.add_child("schedules", pwork_schedules); + + // Write all work plans and values as XML nodes. + ptree pwork_plans; + IfcSchema::IfcWorkPlan::list::ptr pplans = file->instances_by_type(); + for (IfcSchema::IfcWorkPlan::list::it it = pplans->begin(); it != pplans->end(); ++it) { + IfcSchema::IfcWorkPlan* plan = *it; + ptree* nschedule = format_entity_instance(mapping_, plan, pwork_plans); + + if (nschedule) { +#ifdef SCHEMA_IfcObjectDefinition_HAS_IsDecomposedBy + auto decomposed_by = plan->IsDecomposedBy(); + for (auto it2 = decomposed_by->begin(); it2 != decomposed_by->end(); ++it2) { + IfcSchema::IfcObjectDefinition::list::ptr related_objects = (*it2)->RelatedObjects(); + for (IfcSchema::IfcObjectDefinition::list::it it3 = related_objects->begin(); it3 != related_objects->end(); ++it3) { + IfcSchema::IfcObjectDefinition* work_schedule = *it3; + ptree pwork_schedule; + pwork_schedule.put(".id", work_schedule->GlobalId()); + nschedule->add_child("IfcWorkSchedule", pwork_schedule); + } + } +#endif + } + } + work.add_child("plans", pwork_plans); + + // Write all work calendars and values as XML nodes. +#ifdef SCHEMA_HAS_IfcWorkCalendar + IfcSchema::IfcWorkCalendar::list::ptr pcalendars = file->instances_by_type(); + for (IfcSchema::IfcWorkCalendar::list::it it = pcalendars->begin(); it != pcalendars->end(); ++it) { + IfcSchema::IfcWorkCalendar* calendar = *it; + ptree* ncalendar = format_entity_instance(mapping_, calendar, calendars); + + if (ncalendar) { + IfcSchema::IfcWorkTime::list::ptr working_times = calendar->WorkingTimes().value_or(nullptr); + if (working_times != nullptr) { + for (IfcSchema::IfcWorkTime::list::it it2 = working_times->begin(); it2 != working_times->end(); ++it2) { + IfcSchema::IfcWorkTime* working_time = *it2; + format_entity_instance(mapping_, working_time, *ncalendar); + } + } + } + } +#endif + + IfcSchema::IfcRelConnectsElements::list::ptr pconnections = file->instances_by_type(); + for (IfcSchema::IfcRelConnectsElements::list::it it = pconnections->begin(); it != pconnections->end(); ++it) { + IfcSchema::IfcRelConnectsElements* connection = *it; + + ptree* nconnection = format_entity_instance(mapping_, connection, connections); + + ptree nrelatedElement; + ptree nrelatingElement; + + format_entity_instance(mapping_, connection->RelatedElement(), nrelatedElement, true); + format_entity_instance(mapping_, connection->RelatingElement(), nrelatingElement, true); + + nconnection->add_child("RelatedElement", nrelatedElement); + nconnection->add_child("RelatingElement", nrelatingElement); + } + + // 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(mapping_, type_object, types); + + if (node && type_object->HasPropertySets()) { + 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(mapping_, pset, *node, true); + } + } + } + } + + // Write all assigned units as XML nodes. + auto unit_assignments = project->UnitsInContext()->Units(); + for (auto 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(mapping_, named_unit, units); + if (node) { + node->put(".SI_equivalent", IfcParse::get_SI_equivalent(named_unit)); + } + } else if ((*it)->declaration().is(IfcSchema::IfcMonetaryUnit::Class())) { + format_entity_instance(mapping_, (*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 occurrence 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(mapping_, *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() || mat->as()) { + IfcSchema::IfcMaterialLayerSet* layerset = mat->as(); + if (!layerset) { + layerset = mat->as()->ForLayerSet(); + } + if (layerset->LayerSetName()) { + 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)->Material()) { + subnode.put(".Name", (*jt)->Material()->Name()); + } + format_entity_instance(mapping_, *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(mapping_, *jt, subnode, node); + } + } + format_entity_instance(mapping_, mat->as(), node, materials); + } + } + + root.add_child("ifc.header", header); + root.add_child("ifc.units", units); + root.add_child("ifc.connections", connections); + root.add_child("ifc.properties", properties); + root.add_child("ifc.quantities", quantities); + root.add_child("ifc.work", work); + root.add_child("ifc.calendars", calendars); + root.add_child("ifc.types", types); + root.add_child("ifc.layers", layers); + root.add_child("ifc.groups", groups); + 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 + + std::ofstream f(IfcUtil::path::from_utf8(xml_filename).c_str()); + boost::property_tree::write_xml(f, root, settings); + */ + +#endif \ No newline at end of file diff --git a/src/serializers/schema_dependent/JsonSerializer.h b/src/serializers/schema_dependent/JsonSerializer.h new file mode 100644 index 0000000000..0fd7c57040 --- /dev/null +++ b/src/serializers/schema_dependent/JsonSerializer.h @@ -0,0 +1,58 @@ +/******************************************************************************** + * * + * 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 JSONSERIALIZERIMPL_H +#define JSONSERIALIZERIMPL_H + +#ifdef WITH_GLTF + +#include "../../ifcgeom/abstract_mapping.h" +#include "../../ifcparse/macros.h" +#include "../../serializers/JsonSerializer.h" + +#define INCLUDE_PARENT_PARENT_DIR(x) STRINGIFY(../../ifcparse/x.h) +#include INCLUDE_PARENT_PARENT_DIR(IfcSchema) +#undef INCLUDE_PARENT_PARENT_DIR +#define INCLUDE_PARENT_PARENT_DIR(x) STRINGIFY(../../ifcparse/x-definitions.h) +#include INCLUDE_PARENT_PARENT_DIR(IfcSchema) + +class POSTFIX_SCHEMA(JsonSerializer) : public JsonSerializer { + private: + IfcParse::IfcFile* file; + + // @todo + ifcopenshell::geometry::Settings settings_; + ifcopenshell::geometry::abstract_mapping* mapping_; + + public: + POSTFIX_SCHEMA(JsonSerializer)(IfcParse::IfcFile* file, const std::string& json_filename, JsonSerializer::Dialect dialect) + : JsonSerializer(0, "", dialect), mapping_(ifcopenshell::geometry::impl::mapping_implementations().construct(file, settings_)) + { + this->file = file; + this->json_filename = json_filename; + this->dialect_ = dialect; + } + + void finalize(); + void setFile(IfcParse::IfcFile*) {} +}; + +#endif + +#endif