diff --git a/IfcOpenShell-fork.zip b/IfcOpenShell-fork.zip new file mode 100644 index 0000000000..6ecc329e75 Binary files /dev/null and b/IfcOpenShell-fork.zip differ diff --git a/XmlSerializer(20210109).cpp b/XmlSerializer(20210109).cpp new file mode 100644 index 0000000000..1fa828938c --- /dev/null +++ b/XmlSerializer(20210109).cpp @@ -0,0 +1,632 @@ +/******************************************************************************** +* * +* 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" +#include "../../ifcparse/utils.h" + +using boost::property_tree::ptree; + +#include "XmlSerializer.h" + +namespace { + struct MAKE_TYPE_NAME(factory_t) { + XmlSerializer* operator()(IfcParse::IfcFile* file, const std::string& xml_filename) const { + MAKE_TYPE_NAME(XmlSerializer)* s = new MAKE_TYPE_NAME(XmlSerializer)(file, xml_filename); + s->setFile(file); + return s; + } + }; +} + +void MAKE_INIT_FN(XmlSerializer)(XmlSerializerFactory::Factory* mapping) { + static const std::string schema_name = STRINGIFY(IfcSchema); + MAKE_TYPE_NAME(factory_t) factory; + mapping->bind(schema_name, factory); +} + +namespace { + + // TODO: Make this a member of XmlSerializer? + std::map MAKE_TYPE_NAME(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::MAKE_TYPE_NAME(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) { + try { + instance->data().getArgument(i); + } + catch (const std::exception&) { + Logger::Error("Expected " + boost::lexical_cast(n) + " attributes for:", instance); + break; + } + 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 = MAKE_TYPE_NAME(argument_name_map).find(argument_name); + if (argument_name_it != MAKE_TYPE_NAME(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); + } + catch (const Standard_ConstructionError& e) { + Logger::Error(e.GetMessageString(), instance); + } + + 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, IfcUtil::IfcBaseClass* 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 + 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, IfcUtil::IfcBaseClass* 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 nullptr; + } + } + + ptree& child = *format_entity_instance(product, tree); + + if (product->declaration().is(IfcSchema::IfcOpeningElement::Class())) { + IfcSchema::IfcOpeningElement* opening = static_cast(product); + 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, child, product); + } + } + + 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, 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, child, 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, child, product); + } + + 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); + } + else if (pset->declaration().is(IfcSchema::IfcElementQuantity::Class())) { + format_entity_instance(pset, child, true); + } + } + +#ifdef SCHEMA_IfcObject_HAS_IsTypedBy + 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); + 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; + } + + void writeGroupToNode(IfcSchema::IfcGroup* group, ptree& node, std::setnotRootGroups) + { + if (notRootGroups.find(group->Name()) != notRootGroups.end()) { + return; + } + ptree* node2 = descend(group, node);//write one group to root + auto father = group->IsGroupedBy(); + for (auto iter = father->begin(); iter != father->end(); iter++) + { + IfcSchema::IfcRelAssigns* ii = *iter; + auto objs = ii->RelatedObjects(); + for (auto objit = objs->begin(); objit != objs->end(); objit++) { + auto entity = *objit; + if (entity->declaration().is(IfcSchema::IfcGroup::Class())) { + writeGroupToNode(entity->as(), *node2, notRootGroups); + notRootGroups.emplace(entity->Name()); + } + else { + descend(entity, *node2);//write son to father group + } + } + } + } + + + // 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; + ptree* node2 = format_entity_instance(p, node); + if (node2 && p->declaration().is(IfcSchema::IfcPhysicalComplexQuantity::Class())) { + IfcSchema::IfcPhysicalComplexQuantity* complex = (IfcSchema::IfcPhysicalComplexQuantity*)p; + format_quantities(complex->HasQuantities(), *node2); + } + } + } + +} // ~unnamed namespace + +void MAKE_TYPE_NAME(XmlSerializer)::finalize() { + MAKE_TYPE_NAME(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, groups; + + // 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)); + } + 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 { + 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(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); + if (node) { + format_properties(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(*it, groups, notRootGroups); + } + for (auto it = groups.begin(); it != groups.end();)//root + { + 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(qto, quantities); + if (node) { + 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); + + if (node && 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); + if (node) { + 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 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(*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->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.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); +} diff --git a/XmlSerializer(20210111).cpp b/XmlSerializer(20210111).cpp new file mode 100644 index 0000000000..055af37c0a --- /dev/null +++ b/XmlSerializer(20210111).cpp @@ -0,0 +1,635 @@ +/******************************************************************************** +* * +* 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" +#include "../../ifcparse/utils.h" + +using boost::property_tree::ptree; + +#include "XmlSerializer.h" + +namespace { + struct MAKE_TYPE_NAME(factory_t) { + XmlSerializer* operator()(IfcParse::IfcFile* file, const std::string& xml_filename) const { + MAKE_TYPE_NAME(XmlSerializer)* s = new MAKE_TYPE_NAME(XmlSerializer)(file, xml_filename); + s->setFile(file); + return s; + } + }; +} + +void MAKE_INIT_FN(XmlSerializer)(XmlSerializerFactory::Factory* mapping) { + static const std::string schema_name = STRINGIFY(IfcSchema); + MAKE_TYPE_NAME(factory_t) factory; + mapping->bind(schema_name, factory); +} + +namespace { + + // TODO: Make this a member of XmlSerializer? + std::map MAKE_TYPE_NAME(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::MAKE_TYPE_NAME(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) { + try { + instance->data().getArgument(i); + } + catch (const std::exception&) { + Logger::Error("Expected " + boost::lexical_cast(n) + " attributes for:", instance); + break; + } + 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 = MAKE_TYPE_NAME(argument_name_map).find(argument_name); + if (argument_name_it != MAKE_TYPE_NAME(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); + } + catch (const Standard_ConstructionError& e) { + Logger::Error(e.GetMessageString(), instance); + } + + 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, IfcUtil::IfcBaseClass* 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 + 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, IfcUtil::IfcBaseClass* 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 nullptr; + } + } + + ptree& child = *format_entity_instance(product, tree); + + if (product->declaration().is(IfcSchema::IfcOpeningElement::Class())) { + IfcSchema::IfcOpeningElement* opening = static_cast(product); + 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, child, product); + } + } + + 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, 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, child, 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, child, product); + } + + 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); + } + else if (pset->declaration().is(IfcSchema::IfcElementQuantity::Class())) { + format_entity_instance(pset, child, true); + } + } + +#ifdef SCHEMA_IfcObject_HAS_IsTypedBy + 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); + 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; + ptree* node2 = format_entity_instance(p, node); + if (node2 && p->declaration().is(IfcSchema::IfcPhysicalComplexQuantity::Class())) { + IfcSchema::IfcPhysicalComplexQuantity* complex = (IfcSchema::IfcPhysicalComplexQuantity*)p; + format_quantities(complex->HasQuantities(), *node2); + } + } + } + +} // ~unnamed namespace + +void MAKE_TYPE_NAME(XmlSerializer)::finalize() { + MAKE_TYPE_NAME(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, groups; + + // 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)); + } + 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 { + 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(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); + if (node) { + format_properties(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 + std::function writeGroupToNode = + [&](IfcSchema::IfcGroup* group, ptree& node)->void + { + if (notRootGroups.find(group->Name()) != notRootGroups.end()) { + return; + } + ptree* node2 = descend(group, node);//write one group to root + auto father = group->IsGroupedBy(); + for (auto iter = father->begin(); iter != father->end(); iter++) + { + IfcSchema::IfcRelAssigns* ii = *iter; + auto objs = ii->RelatedObjects(); + for (auto objit = objs->begin(); objit != objs->end(); objit++) { + auto entity = *objit; + if (entity->declaration().is(IfcSchema::IfcGroup::Class())) { + writeGroupToNode(entity->as(), *node2); + notRootGroups.emplace(entity->Name()); + } + else { + descend(entity, *node2);//write son to father group + } + } + } + return; + }; + for (IfcSchema::IfcGroup::list::it it = gsets->begin(); it != gsets->end(); ++it) { + writeGroupToNode(*it, groups); + } + for (auto it = groups.begin(); it != groups.end();)//root + { + 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(qto, quantities); + if (node) { + 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); + + if (node && 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); + if (node) { + 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 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(*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->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.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); +} diff --git a/XmlSerializer(20210114).cpp b/XmlSerializer(20210114).cpp new file mode 100644 index 0000000000..0d5ced7c8b --- /dev/null +++ b/XmlSerializer(20210114).cpp @@ -0,0 +1,647 @@ +/******************************************************************************** +* * +* 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" +#include "../../ifcparse/utils.h" + +using boost::property_tree::ptree; + +#include "XmlSerializer.h" + +namespace { + struct MAKE_TYPE_NAME(factory_t) { + XmlSerializer* operator()(IfcParse::IfcFile* file, const std::string& xml_filename) const { + MAKE_TYPE_NAME(XmlSerializer)* s = new MAKE_TYPE_NAME(XmlSerializer)(file, xml_filename); + s->setFile(file); + return s; + } + }; +} + +void MAKE_INIT_FN(XmlSerializer)(XmlSerializerFactory::Factory* mapping) { + static const std::string schema_name = STRINGIFY(IfcSchema); + MAKE_TYPE_NAME(factory_t) factory; + mapping->bind(schema_name, factory); +} + +namespace { + + // TODO: Make this a member of XmlSerializer? + std::map MAKE_TYPE_NAME(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::MAKE_TYPE_NAME(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) { + try { + instance->data().getArgument(i); + } + catch (const std::exception&) { + Logger::Error("Expected " + boost::lexical_cast(n) + " attributes for:", instance); + break; + } + 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 = MAKE_TYPE_NAME(argument_name_map).find(argument_name); + if (argument_name_it != MAKE_TYPE_NAME(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); + } + catch (const Standard_ConstructionError& e) { + Logger::Error(e.GetMessageString(), instance); + } + + 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, IfcUtil::IfcBaseClass* 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 + 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, IfcUtil::IfcBaseClass* 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 nullptr; + } + } + + ptree& child = *format_entity_instance(product, tree); + + if (product->declaration().is(IfcSchema::IfcOpeningElement::Class())) { + IfcSchema::IfcOpeningElement* opening = static_cast(product); + 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, child, product); + } + } + + 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, 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, child, 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, child, product); + } + + 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); + } + else if (pset->declaration().is(IfcSchema::IfcElementQuantity::Class())) { + format_entity_instance(pset, child, true); + } + } + +#ifdef SCHEMA_IfcObject_HAS_IsTypedBy + 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); + 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; + ptree* node2 = format_entity_instance(p, node); + if (node2 && p->declaration().is(IfcSchema::IfcPhysicalComplexQuantity::Class())) { + IfcSchema::IfcPhysicalComplexQuantity* complex = (IfcSchema::IfcPhysicalComplexQuantity*)p; + format_quantities(complex->HasQuantities(), *node2); + } + } + } + +} // ~unnamed namespace + +void MAKE_TYPE_NAME(XmlSerializer)::finalize() { + MAKE_TYPE_NAME(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, groups; + + // 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)); + } + 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 { + 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(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); + if (node) { + format_properties(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 + std::map rootGroups; + std::function writeGroupToNode = + [&](IfcSchema::IfcGroup* group, ptree& node)->void + { + ptree* node2 = descend(group, node);//write one group to root + auto father = group->IsGroupedBy(); + for (auto iter = father->begin(); iter != father->end(); iter++) + { + IfcSchema::IfcRelAssigns* ii = *iter; + auto objs = ii->RelatedObjects(); + for (auto objit = objs->begin(); objit != objs->end(); objit++) { + auto entity = *objit; + if (entity->declaration().is(IfcSchema::IfcGroup::Class())) { + writeGroupToNode(entity->as(), *node2); + notRootGroups.emplace(entity->data().id()); + } + else { + descend(entity, *node2);//write son to father group + } + } + } + return; + }; + int idx = 0; + for (IfcSchema::IfcGroup::list::it it = gsets->begin(); it != gsets->end(); ++it) { + if (notRootGroups.find((*it)->data().id()) != notRootGroups.end()) { + continue; + } + writeGroupToNode(*it, groups); + rootGroups.emplace((*it)->data().id(), idx++);//map record the index of each group id in xml + } + std::set idxNeedToDelete; + for (auto it = notRootGroups.begin(); it != notRootGroups.end(); it++) {//delete double writes in root + auto mapIt = rootGroups.find(*it); + if (mapIt != rootGroups.end())//need to delete somebody + { + idxNeedToDelete.emplace(mapIt->second); + } + } + idx = 0; + for (auto it = groups.begin(); it != groups.end(); idx++)//root + { + if (idxNeedToDelete.find(idx) != idxNeedToDelete.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(qto, quantities); + if (node) { + 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); + + if (node && 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); + if (node) { + 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 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(*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->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.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); +} diff --git a/cmake/CMakeLists.txt b/cmake/CMakeLists.txt index 732105c7e8..f1bfa7a917 100644 --- a/cmake/CMakeLists.txt +++ b/cmake/CMakeLists.txt @@ -143,6 +143,14 @@ IF(WIN32 AND ("$ENV{CONDA_BUILD}" STREQUAL "")) SET(Boost_USE_STATIC_LIBS ON) SET(Boost_USE_STATIC_RUNTIME ON) SET(Boost_USE_MULTITHREADED ON) + # Disable Boost's autolinking as the libraries to be linked to are supplied + # already by CMake, and wrong libraries would be asked for when code is + # compiled with a toolset different from default. + if(MSVC) + ADD_DEFINITIONS(-DBOOST_ALL_NO_LIB) + # Necessary for boost version >= 1.67 + SET(BCRYPT_LIBRARIES "bcrypt.lib") + ENDIF() ELSE() # Disable Boost's autolinking as the libraries to be linked to are supplied # already by CMake, and it's going to conflict if there are multiple, as is @@ -209,6 +217,10 @@ endfunction() if(BUILD_IFCGEOM) +IF(MSVC) + add_debug_variants(LIBXML2_LIBRARIES "${LIBXML2_LIBRARIES}" d) +ENDIF() + # Find Open CASCADE IF("${OCC_INCLUDE_DIR}" STREQUAL "") SET(OCC_INCLUDE_DIR "/usr/include/oce/" CACHE FILEPATH "Open CASCADE header files") diff --git a/nix/build-all.py b/nix/build-all.py index 536b7dba76..38f8a63c9e 100644 --- a/nix/build-all.py +++ b/nix/build-all.py @@ -79,14 +79,14 @@ ch.setLevel(logging.INFO) logger.addHandler(ch) PROJECT_NAME="IfcOpenShell" -PYTHON_VERSIONS=["2.7.16", "3.2.6", "3.3.6", "3.4.6", "3.5.3", "3.6.2", "3.7.3", "3.8.6", "3.9.1"] +PYTHON_VERSIONS=["3.9.1"] JSON_VERSION="v3.6.1" OCE_VERSION="0.18" -# OCCT_VERSION="7.1.0" +OCCT_VERSION="master" # OCCT_HASH="89aebde" # OCCT_VERSION="7.2.0" # OCCT_HASH="88af392" -OCCT_VERSION="7.3.0p3" +#OCCT_VERSION="7.5.0" BOOST_VERSION="1.71.0" #PCRE_VERSION="8.39" PCRE_VERSION="8.41" @@ -494,7 +494,7 @@ os.environ["LDFLAGS"] = LDFLAGS # build_dependency(name="cmake-%s" % (CMAKE_VERSION,), mode="autoconf", build_tool_args=[], download_url="https://cmake.org/files/v%s" % (CMAKE_VERSION_2,), download_name="cmake-%s.tar.gz" % (CMAKE_VERSION,)) if "json" in targets: - json_url = "https://github.com/nlohmann/json/releases/download/{JSON_VERSION}/json.hpp".format(**locals()) + json_url = "http://121.36.151.68:9008/download/json/v3.6.1/json.hpp".format(**locals()) json_install_path = "{DEPS_DIR}/install/json/nlohmann/json.hpp".format(**locals()) if not os.path.exists(os.path.dirname(json_install_path)): os.makedirs(os.path.dirname(json_install_path)) @@ -506,7 +506,7 @@ if "pcre" in targets: name="pcre-{PCRE_VERSION}".format(**locals()), mode="autoconf", build_tool_args=[DISABLE_FLAG], - download_url="https://downloads.sourceforge.net/project/pcre/pcre/{PCRE_VERSION}/".format(**locals()), + download_url="http://121.36.151.68:9008/download/pcre/8.41/".format(**locals()), download_name="pcre-{PCRE_VERSION}.tar.bz2".format(**locals()) ) @@ -533,7 +533,7 @@ if USE_OCCT and "occ" in targets: "-DBUILD_MODULE_Draw=0", "-DBUILD_RELEASE_DISABLE_EXCEPTIONS=Off" ], - download_url = "https://git.dev.opencascade.org/repos/occt.git", + download_url = "http://47.92.33.33:8080/gitserver/r/occt.git", download_name = "occt", download_tool=download_tool_git, patch=None if OCCT_VERSION >= "7.4" else "./patches/occt/enable-exception-handling.patch", @@ -620,7 +620,7 @@ if "python" in targets: "python-{PYTHON_VERSION}{abi_tag}".format(**locals()), "autoconf", PYTHON_CONFIGURE_ARGS + [unicode_conf], - "http://www.python.org/ftp/python/{PYTHON_VERSION}/".format(**locals()), + "http://121.36.151.68:9008/download/python/{PYTHON_VERSION}/".format(**locals()), "Python-{PYTHON_VERSION}.tgz".format(**locals()) ) except Exception as e: @@ -681,7 +681,8 @@ cmake_args=[ "-DCMAKE_INSTALL_PREFIX=" "{DEPS_DIR}/install/ifcopenshell".format(**locals()), "-DBOOST_ROOT=" "{DEPS_DIR}/install/boost-{BOOST_VERSION}".format(**locals()), "-DGLTF_SUPPORT=" "ON", - "-DJSON_INCLUDE_DIR=" "{DEPS_DIR}/install/json".format(**locals()) + "-DJSON_INCLUDE_DIR=" "{DEPS_DIR}/install/json".format(**locals()), + "-DBoost_NO_BOOST_CMAKE=" "On" ] if "occ" in targets and USE_OCCT: diff --git a/src/bcf/bcf/bcfxml.py b/src/bcf/bcf/bcfxml.py index bc4eaadee0..c0791f98fd 100644 --- a/src/bcf/bcf/bcfxml.py +++ b/src/bcf/bcf/bcfxml.py @@ -476,8 +476,8 @@ class BcfXml: if viewpoint.snapshot: topic_filepath = os.path.join(self.filepath, topic.guid) filepath = os.path.join(topic_filepath, viewpoint.snapshot) - if not os.path.exists(filepath) or topic_filepath not in filepath: - filename = viewpoint.guid + "." + viewpoint.snapshot[-3:] + if not os.path.exists(filepath): + filename = viewpoint.guid + os.path.splitext(viewpoint.snapshot)[-1] copyfile(viewpoint.snapshot, os.path.join(topic_filepath, filename)) viewpoint.snapshot = filename topic.viewpoints[viewpoint.guid] = viewpoint diff --git a/src/ifcblenderexport/Makefile b/src/ifcblenderexport/Makefile index 431bf7960e..6847dc334f 100644 --- a/src/ifcblenderexport/Makefile +++ b/src/ifcblenderexport/Makefile @@ -33,23 +33,6 @@ endif cd dist/working && wget https://raw.githubusercontent.com/IfcOpenShell/IfcOpenShell/v0.6.0/src/ifcblenderexport/occ_utils.py cd dist/working && mv occ_utils.py ../blenderbim/libs/site/packages/ifcopenshell/geom/occ_utils.py rm -rf dist/working - # IfcOpenBot sometimes lags behind, so we hotfix the Python utilities - cd dist/blenderbim/libs/site/packages/ifcopenshell/util && rm -f attribute_4_to_2x3.json - cd dist/blenderbim/libs/site/packages/ifcopenshell/util && rm -f class_4_to_2x3.json - cd dist/blenderbim/libs/site/packages/ifcopenshell/util && rm -f element.py - cd dist/blenderbim/libs/site/packages/ifcopenshell/util && rm -f geolocation.py - cd dist/blenderbim/libs/site/packages/ifcopenshell/util && rm -f selector.py - cd dist/blenderbim/libs/site/packages/ifcopenshell/util && rm -f unit.py - cd dist/blenderbim/libs/site/packages/ifcopenshell/util && rm -f pset.py - cd dist/blenderbim/libs/site/packages/ifcopenshell/util && rm -f schema.py - cd dist/blenderbim/libs/site/packages/ifcopenshell/util && wget https://raw.githubusercontent.com/IfcOpenShell/IfcOpenShell/v0.6.0/src/ifcopenshell-python/ifcopenshell/util/attribute_4_to_2x3.json - cd dist/blenderbim/libs/site/packages/ifcopenshell/util && wget https://raw.githubusercontent.com/IfcOpenShell/IfcOpenShell/v0.6.0/src/ifcopenshell-python/ifcopenshell/util/class_4_to_2x3.json - cd dist/blenderbim/libs/site/packages/ifcopenshell/util && wget https://raw.githubusercontent.com/IfcOpenShell/IfcOpenShell/v0.6.0/src/ifcopenshell-python/ifcopenshell/util/element.py - cd dist/blenderbim/libs/site/packages/ifcopenshell/util && wget https://raw.githubusercontent.com/IfcOpenShell/IfcOpenShell/v0.6.0/src/ifcopenshell-python/ifcopenshell/util/geolocation.py - cd dist/blenderbim/libs/site/packages/ifcopenshell/util && wget https://raw.githubusercontent.com/IfcOpenShell/IfcOpenShell/v0.6.0/src/ifcopenshell-python/ifcopenshell/util/selector.py - cd dist/blenderbim/libs/site/packages/ifcopenshell/util && wget https://raw.githubusercontent.com/IfcOpenShell/IfcOpenShell/v0.6.0/src/ifcopenshell-python/ifcopenshell/util/unit.py - cd dist/blenderbim/libs/site/packages/ifcopenshell/util && wget https://raw.githubusercontent.com/IfcOpenShell/IfcOpenShell/v0.6.0/src/ifcopenshell-python/ifcopenshell/util/pset.py - cd dist/blenderbim/libs/site/packages/ifcopenshell/util && wget https://raw.githubusercontent.com/IfcOpenShell/IfcOpenShell/v0.6.0/src/ifcopenshell-python/ifcopenshell/util/schema.py # Provides Python OCC functionality for cutting IFC geometry for construction documentation mkdir dist/working @@ -96,6 +79,28 @@ ifeq ($(PLATFORM), macos) rm -rf dist/working endif + # Provides dependencies that are part of IfcOpenShell + mkdir dist/working + cd dist/working && wget https://github.com/IfcOpenShell/IfcOpenShell/archive/v0.6.0.zip + cd dist/working && unzip v0.6.0.zip + # IfcOpenBot sometimes lags behind, so we hotfix the Python utilities + cp -r dist/working/IfcOpenShell-0.6.0/src/ifcopenshell-python/ifcopenshell/util/* dist/blenderbim/libs/site/packages/ifcopenshell/util/ + # Provides bcf functionality + cp -r dist/working/IfcOpenShell-0.6.0/src/bcf/bcf dist/blenderbim/libs/site/packages/ + # Provides IFCClash functionality + cp -r dist/working/IfcOpenShell-0.6.0/src/ifcclash/* dist/blenderbim/libs/site/packages/ + # Provides BIMTester functionality + cp -r dist/working/IfcOpenShell-0.6.0/src/ifcbimtester/bimtester dist/blenderbim/libs/site/packages/ + # Provides IFCCOBie functionality + cp -r dist/working/IfcOpenShell-0.6.0/src/ifccobie/* dist/blenderbim/libs/site/packages/ + # Provides IFCDiff functionality + cp -r dist/working/IfcOpenShell-0.6.0/src/ifcdiff/* dist/blenderbim/libs/site/packages/ + # Provides IFCCSV functionality + cp -r dist/working/IfcOpenShell-0.6.0/src/ifccsv/* dist/blenderbim/libs/site/packages/ + # Provides IFCPatch functionality + cp -r dist/working/IfcOpenShell-0.6.0/src/ifcpatch dist/blenderbim/libs/site/packages/ + rm -rf dist/working + # Provides Mustache templating in construction documentation mkdir dist/working cd dist/working && wget https://files.pythonhosted.org/packages/d6/fd/eb8c212053addd941cc90baac307c00ac246ac3fce7166b86434c6eae963/pystache-0.5.4.tar.gz @@ -145,15 +150,18 @@ endif cp -r dist/working/xmlschema-1.1.1/xmlschema dist/blenderbim/libs/site/packages/ rm -rf dist/working - # Provides bcf functionality - mkdir dist/blenderbim/libs/site/packages/bcf - mkdir dist/blenderbim/libs/site/packages/bcf/xsd - cd dist/blenderbim/libs/site/packages/bcf && wget https://raw.githubusercontent.com/IfcOpenShell/IfcOpenShell/v0.6.0/src/bcf/bcf/bcfxml.py - cd dist/blenderbim/libs/site/packages/bcf && wget https://raw.githubusercontent.com/IfcOpenShell/IfcOpenShell/v0.6.0/src/bcf/bcf/data.py - cd dist/blenderbim/libs/site/packages/bcf/xsd && wget https://raw.githubusercontent.com/IfcOpenShell/IfcOpenShell/v0.6.0/src/bcf/bcf/xsd/markup.xsd - cd dist/blenderbim/libs/site/packages/bcf/xsd && wget https://raw.githubusercontent.com/IfcOpenShell/IfcOpenShell/v0.6.0/src/bcf/bcf/xsd/project.xsd - cd dist/blenderbim/libs/site/packages/bcf/xsd && wget https://raw.githubusercontent.com/IfcOpenShell/IfcOpenShell/v0.6.0/src/bcf/bcf/xsd/version.xsd - cd dist/blenderbim/libs/site/packages/bcf/xsd && wget https://raw.githubusercontent.com/IfcOpenShell/IfcOpenShell/v0.6.0/src/bcf/bcf/xsd/visinfo.xsd + # Required by bcf + mkdir dist/working + cd dist/working && wget https://files.pythonhosted.org/packages/12/f9/f9960222d5274944b01391749e55e4dcdf28d8f0c108b64ac931ceff6fdb/elementpath-1.4.3.tar.gz + cd dist/working && tar -xzvf elementpath* + cp -r dist/working/elementpath-1.4.3/elementpath dist/blenderbim/libs/site/packages/ + rm -rf dist/working + + # Required by bcf + mkdir dist/working + cd dist/working && wget https://files.pythonhosted.org/packages/21/9f/b251f7f8a76dec1d6651be194dfba8fb8d7781d10ab3987190de8391d08e/six-1.14.0.tar.gz + cd dist/working && tar -xzvf six* + cp -r dist/working/six-1.14.0/six.py dist/blenderbim/libs/site/packages/ rm -rf dist/working # Required by IFCCSV and ifcopenshell.util.selector @@ -181,10 +189,6 @@ ifeq ($(PLATFORM), win) rm -rf dist/working endif - # Provides IFCClash functionality - cd dist/blenderbim/libs/site/packages/ && wget https://raw.githubusercontent.com/IfcOpenShell/IfcOpenShell/v0.6.0/src/ifcclash/collision.py - cd dist/blenderbim/libs/site/packages/ && wget https://raw.githubusercontent.com/IfcOpenShell/IfcOpenShell/v0.6.0/src/ifcclash/ifcclash.py - # Required by BIMTester mkdir dist/working cd dist/working && wget https://files.pythonhosted.org/packages/c8/4b/d0a8c23b6c8985e5544ea96d27105a273ea22051317f850c2cdbf2029fe4/behave-1.2.6.tar.gz @@ -206,28 +210,6 @@ endif cd dist/working/parse_type-0.5.2/ && cp -r parse_type ../../blenderbim/libs/site/packages/ rm -rf dist/working - # Provides BIMTester functionality - mkdir dist/blenderbim/libs/site/packages/bimtester/ - cd dist/blenderbim/libs/site/packages/bimtester && wget https://raw.githubusercontent.com/IfcOpenShell/IfcOpenShell/v0.6.0/src/ifcbimtester/bimtester/__init__.py - cd dist/blenderbim/libs/site/packages/bimtester && wget https://raw.githubusercontent.com/IfcOpenShell/IfcOpenShell/v0.6.0/src/ifcbimtester/bimtester/clean.py - cd dist/blenderbim/libs/site/packages/bimtester && wget https://raw.githubusercontent.com/IfcOpenShell/IfcOpenShell/v0.6.0/src/ifcbimtester/bimtester/guiwidget.py - cd dist/blenderbim/libs/site/packages/bimtester && wget https://raw.githubusercontent.com/IfcOpenShell/IfcOpenShell/v0.6.0/src/ifcbimtester/bimtester/reports.py - cd dist/blenderbim/libs/site/packages/bimtester && wget https://raw.githubusercontent.com/IfcOpenShell/IfcOpenShell/v0.6.0/src/ifcbimtester/bimtester/run.py - mkdir dist/blenderbim/libs/site/packages/bimtester/features/ - mkdir dist/blenderbim/libs/site/packages/bimtester/features/steps/ - cd dist/blenderbim/libs/site/packages/bimtester/features/ && wget https://raw.githubusercontent.com/IfcOpenShell/IfcOpenShell/v0.6.0/src/ifcbimtester/bimtester/features/environment.py - cd dist/blenderbim/libs/site/packages/bimtester/features/ && wget https://raw.githubusercontent.com/IfcOpenShell/IfcOpenShell/v0.6.0/src/ifcbimtester/bimtester/features/template.html - cd dist/blenderbim/libs/site/packages/bimtester/features/steps/ && wget https://raw.githubusercontent.com/IfcOpenShell/IfcOpenShell/v0.6.0/src/ifcbimtester/bimtester/features/steps/classification.py - cd dist/blenderbim/libs/site/packages/bimtester/features/steps/ && wget https://raw.githubusercontent.com/IfcOpenShell/IfcOpenShell/v0.6.0/src/ifcbimtester/bimtester/features/steps/element_classes.py - cd dist/blenderbim/libs/site/packages/bimtester/features/steps/ && wget https://raw.githubusercontent.com/IfcOpenShell/IfcOpenShell/v0.6.0/src/ifcbimtester/bimtester/features/steps/geocoding.py - cd dist/blenderbim/libs/site/packages/bimtester/features/steps/ && wget https://raw.githubusercontent.com/IfcOpenShell/IfcOpenShell/v0.6.0/src/ifcbimtester/bimtester/features/steps/geolocation.py - cd dist/blenderbim/libs/site/packages/bimtester/features/steps/ && wget https://raw.githubusercontent.com/IfcOpenShell/IfcOpenShell/v0.6.0/src/ifcbimtester/bimtester/features/steps/geometric_detail.py - cd dist/blenderbim/libs/site/packages/bimtester/features/steps/ && wget https://raw.githubusercontent.com/IfcOpenShell/IfcOpenShell/v0.6.0/src/ifcbimtester/bimtester/features/steps/ifcdata.py - cd dist/blenderbim/libs/site/packages/bimtester/features/steps/ && wget https://raw.githubusercontent.com/IfcOpenShell/IfcOpenShell/v0.6.0/src/ifcbimtester/bimtester/features/steps/model_federation.py - cd dist/blenderbim/libs/site/packages/bimtester/features/steps/ && wget https://raw.githubusercontent.com/IfcOpenShell/IfcOpenShell/v0.6.0/src/ifcbimtester/bimtester/features/steps/project_setup.py - cd dist/blenderbim/libs/site/packages/bimtester/features/steps/ && wget https://raw.githubusercontent.com/IfcOpenShell/IfcOpenShell/v0.6.0/src/ifcbimtester/bimtester/features/steps/steps.py - cd dist/blenderbim/libs/site/packages/bimtester/features/steps/ && wget https://raw.githubusercontent.com/IfcOpenShell/IfcOpenShell/v0.6.0/src/ifcbimtester/bimtester/features/steps/utils.py - # Required by IFCCOBie for XLSX support mkdir dist/working cd dist/working && wget https://files.pythonhosted.org/packages/0c/bc/82d6783f83f65f56d8b77d052773c4a2f952fa86385f0cd54e1e006658d7/XlsxWriter-1.2.9.tar.gz @@ -249,15 +231,6 @@ endif cd dist/working/defusedxml-0.6.0/ && cp -r defusedxml ../../blenderbim/libs/site/packages/ rm -rf dist/working - # Provides IFCCOBie functionality - cd dist/blenderbim/libs/site/packages/ && wget https://raw.githubusercontent.com/IfcOpenShell/IfcOpenShell/v0.6.0/src/ifccobie/cobie.py - - # Provides IFCDiff functionality - cd dist/blenderbim/libs/site/packages/ && wget https://raw.githubusercontent.com/IfcOpenShell/IfcOpenShell/v0.6.0/src/ifcdiff/ifcdiff.py - - # Provides IFCCSV functionality - cd dist/blenderbim/libs/site/packages/ && wget https://raw.githubusercontent.com/IfcOpenShell/IfcOpenShell/v0.6.0/src/ifccsv/ifccsv.py - # Provides IFCJSON functionality mkdir dist/working cd dist/working && wget https://github.com/IFCJSON-Team/IFC2JSON_python/archive/master.zip @@ -265,9 +238,6 @@ endif cp -r dist/working/IFC2JSON_python-master/file_converters/ifcjson dist/blenderbim/libs/site/packages/ rm -rf dist/working - # Provides IFCPatch functionality - cd dist/blenderbim/libs/site/packages/ && svn export https://github.com/IfcOpenShell/IfcOpenShell/trunk/src/ifcpatch - cd dist/blenderbim && sed -i "s/999999/$(VERSION)/" __init__.py cd dist && zip -r blender28-bim-$(VERSION)-$(PLATFORM).zip ./* rm -rf dist/blenderbim diff --git a/src/ifcblenderexport/blenderbim/bim/__init__.py b/src/ifcblenderexport/blenderbim/bim/__init__.py index eb28ff5d73..83c476b160 100644 --- a/src/ifcblenderexport/blenderbim/bim/__init__.py +++ b/src/ifcblenderexport/blenderbim/bim/__init__.py @@ -9,45 +9,50 @@ if bpy is not None: from . import ui, prop, operator modules = { - "root": None, - "aggregate": None, - "attribute": None, + "project": None, + "search": None, "bcf": None, - "cobie": None, - "context": None, - "covetool": None, - "csv": None, - "diff": None, - "bimtester": None, - "debug": None, - "geometry": None, + "root": None, + "unit": None, "georeference": None, + "context": None, + "attribute": None, + "type": None, + "spatial": None, + "void": None, + "aggregate": None, + "geometry": None, + "cobie": None, "material": None, + "style": None, + "layer": None, "model": None, "owner": None, - "project": None, "pset": None, - "spatial": None, - "style": None, - "type": None, - "unit": None, - "void": None, + "qto": None, + "classification": None, + "constraint": None, + "document": None, + "pset_template": None, + "clash": None, + "csv": None, + "bimtester": None, + "diff": None, + "patch": None, + "covetool": None, + "debug": None, } for name in modules.keys(): modules[name] = importlib.import_module(f"blenderbim.bim.module.{name}") classes = [ - operator.SelectClass, - operator.SelectType, operator.OpenUri, operator.SelectDataDir, operator.SelectSchemaDir, operator.SelectIfcFile, operator.ExportIFC, operator.ImportIFC, - operator.ColourByAttribute, - operator.ColourByPset, operator.SelectExternalMaterialDir, operator.AddSweptSolid, operator.RemoveSweptSolid, @@ -57,43 +62,6 @@ if bpy is not None: operator.SelectSweptSolidInnerCurves, operator.AssignSweptSolidExtrusion, operator.SelectSweptSolidExtrusion, - operator.AddMaterialPset, - operator.RemoveMaterialPset, - operator.AddMaterialLayer, - operator.RemoveMaterialLayer, - operator.MoveMaterialLayer, - operator.AddMaterialConstituent, - operator.RemoveMaterialConstituent, - operator.MoveMaterialConstituent, - operator.AddMaterialProfile, - operator.RemoveMaterialProfile, - operator.MoveMaterialProfile, - operator.AddConstraint, - operator.RemoveConstraint, - operator.AssignConstraint, - operator.UnassignConstraint, - operator.RemoveObjectConstraint, - operator.AddDocumentInformation, - operator.RemoveDocumentInformation, - operator.AssignDocumentInformation, - operator.AddDocumentReference, - operator.RemoveDocumentReference, - operator.AssignDocumentReference, - operator.UnassignDocumentReference, - operator.RemoveObjectDocumentReference, - operator.GenerateGlobalId, - operator.AddMaterialAttribute, - operator.RemoveMaterialAttribute, - operator.SelectGlobalId, - operator.SelectAttribute, - operator.SelectPset, - operator.LoadClassification, - operator.AddClassification, - operator.RemoveClassification, - operator.AssignClassification, - operator.UnassignClassification, - operator.RemoveClassificationReference, - operator.FetchLibraryInformation, operator.FetchExternalMaterial, operator.FetchObjectPassport, operator.CutSection, @@ -104,28 +72,7 @@ if bpy is not None: operator.OpenView, operator.OpenViewCamera, operator.ActivateView, - operator.ExportClashSets, - operator.ImportClashSets, - operator.AddClashSet, - operator.RemoveClashSet, - operator.AddClashSource, - operator.RemoveClashSource, - operator.SelectClashSource, - operator.ExecuteIfcClash, - operator.SelectIfcClashResults, - operator.SelectClashResults, - operator.SelectSmartGroupedClashesPath, - operator.SmartClashGroup, - operator.SelectSmartGroup, - operator.LoadSmartGroupsForActiveClashSet, operator.OpenUpstream, - operator.BIM_OT_ChangeClassificationLevel, - operator.AddPropertySetTemplate, - operator.RemovePropertySetTemplate, - operator.EditPropertySetTemplate, - operator.SavePropertySetTemplate, - operator.AddPropertyTemplate, - operator.RemovePropertyTemplate, operator.AddSectionPlane, operator.RemoveSectionPlane, operator.ReloadIfcFile, @@ -138,13 +85,6 @@ if bpy is not None: operator.AddVariable, operator.RemoveVariable, operator.PropagateTextData, - operator.SelectIfcPatchInput, - operator.SelectIfcPatchOutput, - operator.ExecuteIfcPatch, - operator.CalculateEdgeLengths, - operator.CalculateFaceAreas, - operator.CalculateObjectVolumes, - operator.AddOpening, operator.SetOverrideColour, operator.AddDrawing, operator.RemoveDrawing, @@ -160,19 +100,11 @@ if bpy is not None: operator.BuildSchedule, operator.AddScheduleToSheet, operator.SetViewportShadowFromSun, - operator.AddPresentationLayer, - operator.AssignPresentationLayer, - operator.UnassignPresentationLayer, - operator.RemovePresentationLayer, - operator.UpdatePresentationLayer, operator.AddDrawingStyleAttribute, operator.RemoveDrawingStyleAttribute, operator.CopyPropertyToSelection, operator.CopyAttributeToSelection, operator.RefreshDrawingList, - operator.SetBlenderClashSetA, - operator.SetBlenderClashSetB, - operator.ExecuteBlenderClash, operator.CleanWireframes, operator.LinkIfc, operator.SnapSpacesTogether, @@ -181,25 +113,12 @@ if bpy is not None: prop.StrProperty, prop.Attribute, prop.Variable, - prop.Classification, - prop.ClassificationReference, - prop.ClassificationView, - prop.PropertySetTemplate, - prop.PropertyTemplate, - prop.DocumentInformation, - prop.DocumentReference, - prop.ClashSource, - prop.ClashSet, - prop.SmartClashGroup, - prop.Constraint, prop.Drawing, prop.Schedule, prop.DrawingStyle, prop.Sheet, - prop.PresentationLayer, prop.BIMProperties, prop.DocProperties, - prop.BIMLibrary, prop.IfcParameter, prop.BoundaryCondition, prop.PsetQto, @@ -216,37 +135,13 @@ if bpy is not None: ui.BIM_PT_drawings, ui.BIM_PT_schedules, ui.BIM_PT_sheets, - ui.BIM_PT_psets, - ui.BIM_PT_classifications, - ui.BIM_PT_document_information, - ui.BIM_PT_constraints, - ui.BIM_PT_search, - ui.BIM_PT_ifcclash, - ui.BIM_PT_library, - ui.BIM_PT_presentation_layers, - ui.BIM_PT_patch, - ui.BIM_PT_mvd, - ui.BIM_PT_presentation_layer_data, - ui.BIM_PT_classification_references, - ui.BIM_PT_documents, - ui.BIM_PT_constraint_relations, - ui.BIM_PT_object_structural, ui.BIM_PT_camera, ui.BIM_PT_text, - ui.BIM_PT_modeling_utilities, ui.BIM_PT_annotation_utilities, - ui.BIM_PT_qto_utilities, - ui.BIM_PT_clash_manager, ui.BIM_PT_misc_utilities, ui.BIM_UL_generic, ui.BIM_UL_drawinglist, - ui.BIM_UL_clash_sets, - ui.BIM_UL_smart_groups, - ui.BIM_UL_constraints, - ui.BIM_UL_document_information, - ui.BIM_UL_document_references, ui.BIM_UL_topics, - ui.BIM_UL_classifications, ui.BIM_ADDON_preferences, ] @@ -268,11 +163,11 @@ if bpy is not None: bpy.utils.register_class(cls) bpy.app.handlers.depsgraph_update_post.append(on_register) bpy.app.handlers.load_post.append(prop.setDefaultProperties) + bpy.app.handlers.load_post.append(prop.clearIfcStore) bpy.types.TOPBAR_MT_file_export.append(menu_func_export) bpy.types.TOPBAR_MT_file_import.append(menu_func_import) bpy.types.Scene.BIMProperties = bpy.props.PointerProperty(type=prop.BIMProperties) bpy.types.Scene.DocProperties = bpy.props.PointerProperty(type=prop.DocProperties) - bpy.types.Scene.BIMLibrary = bpy.props.PointerProperty(type=prop.BIMLibrary) bpy.types.Object.BIMObjectProperties = bpy.props.PointerProperty(type=prop.BIMObjectProperties) bpy.types.Material.BIMObjectProperties = bpy.props.PointerProperty(type=prop.BIMObjectProperties) bpy.types.Collection.BIMObjectProperties = bpy.props.PointerProperty(type=prop.BIMObjectProperties) # Check if we need this @@ -292,6 +187,7 @@ if bpy is not None: for cls in reversed(classes): bpy.utils.unregister_class(cls) bpy.app.handlers.load_post.remove(prop.setDefaultProperties) + bpy.app.handlers.load_post.remove(prop.clearIfcStore) bpy.types.TOPBAR_MT_file_export.remove(menu_func_export) bpy.types.TOPBAR_MT_file_import.remove(menu_func_import) del bpy.types.Scene.BIMProperties diff --git a/src/ifcblenderexport/blenderbim/bim/export_ifc.py b/src/ifcblenderexport/blenderbim/bim/export_ifc.py index c4e4349888..379f50d27b 100644 --- a/src/ifcblenderexport/blenderbim/bim/export_ifc.py +++ b/src/ifcblenderexport/blenderbim/bim/export_ifc.py @@ -1,3453 +1,20 @@ -import bpy -import csv -import bmesh -import json -import time -import datetime import os +import bpy +import json +import datetime import zipfile import tempfile import ifcopenshell -import ifcopenshell.util.schema -from pathlib import Path -from mathutils import Vector, Matrix -from .helper import SIUnitHelper -from . import schema -from . import ifc +from blenderbim.bim.ifc import IfcStore import addon_utils -class ArrayModifier: - count: int - offset: Vector - - -class IfcParser: - def __init__(self, ifc_export_settings, qto_calculator): - self.data_dir = ifc_export_settings.data_dir - self.qto_calculator = qto_calculator - - self.ifc_export_settings = ifc_export_settings - - self.selected_products = [] - self.selected_types = [] - self.selected_grid_axes = [] - self.selected_spatial_structure_elements = [] - self.selected_groups = [] - self.global_ids = [] - - self.product_index = 0 - self.product_name_index_map = {} - - self.units = {} - self.people = [] - self.organisations = [] - self.psets = {} - self.material_psets = {} - self.document_references = {} - self.classifications = [] - self.classification_references = {} - self.constraints = {} - self.qtos = {} - self.aggregates = {} - self.materials = {} - self.styled_items = [] - self.surface_styles = {} - self.spatial_structure_elements = [] - self.spatial_structure_elements_tree = [] - self.groups = [] - self.rel_contained_in_spatial_structure = {} - self.rel_nests = {} - self.rel_space_boundaries = {} - self.rel_defines_by_qto = {} - self.rel_defines_by_pset = {} - self.rel_associates_document_object = {} - self.rel_associates_document_type = {} - self.rel_associates_classification_object = {} - self.rel_associates_classification_type = {} - self.rel_associates_material = {} - self.rel_associates_material_layer_set = [] - self.rel_associates_material_constituent_set = [] - self.rel_associates_material_profile_set = [] - self.rel_associates_constraint_object = {} - self.rel_associates_constraint_type = {} - self.rel_aggregates = {} - self.rel_voids_elements = {} - self.rel_fills_elements = {} - self.rel_projects_elements = {} - self.rel_connects_structural_member = {} - self.rel_assigns_to_group = {} - self.presentation_layer_assignments = {} - self.representations = {} - self.grid_axes = {} - self.type_products = [] - self.door_attributes = {} - self.window_attributes = {} - self.project = {} - self.libraries = [] - self.products = [] - - def parse(self, selected_objects): - self.projects = self.get_projects() - if not self.projects: - self.setup_project() - self.projects = self.get_projects() - self.project = self.projects[0] - if not selected_objects: - selected_objects = self.get_all_objects_in_project(self.project["raw"]) - self.units = self.get_units() - self.unit_scale = self.get_unit_scale() - self.people = self.get_people() - self.organisations = self.get_organisations() - selected_objects = self.add_spatial_elements_if_unselected(selected_objects) - self.add_type_elements_if_unselected(selected_objects) - self.categorise_selected_objects(selected_objects) - self.document_information = self.get_document_information() - self.document_references = self.get_document_references() - self.classifications = self.get_classifications() - self.classification_reference_maps = self.get_classification_reference_maps() - self.classification_references = self.get_classification_references() - self.constraints = self.get_constraints() - self.load_representations() - self.load_presentation_layer_assignments() - # TODO: migrate this into the product / type / spatial element loop - self.get_materials_and_surface_styles() - self.spatial_structure_elements = self.get_spatial_structure_elements() - self.groups = self.get_groups() - self.libraries = self.get_libraries() - self.door_attributes = self.get_door_attributes() - self.window_attributes = self.get_window_attributes() - self.grid_axes = self.get_grid_axes() - self.type_products = self.get_type_products() - self.get_products() - self.resolve_product_relationships() - self.map_conversion = self.get_map_conversion() - self.target_crs = self.get_target_crs() - self.library_information = self.get_library_information() - - self.spatial_structure_elements_tree = [] - for project in self.projects: - self.spatial_structure_elements_tree.extend(self.get_spatial_structure_elements_tree(project)) - - def get_units(self): - units = { - "length": { - "ifc": None, - "is_metric": bpy.context.scene.unit_settings.system != "IMPERIAL", - "raw": bpy.context.scene.unit_settings.length_unit, - }, - "area": { - "ifc": None, - "is_metric": bpy.context.scene.unit_settings.system != "IMPERIAL", - "raw": bpy.context.scene.unit_settings.length_unit, - }, - "volume": { - "ifc": None, - "is_metric": bpy.context.scene.unit_settings.system != "IMPERIAL", - "raw": bpy.context.scene.unit_settings.length_unit, - }, - } - for data in units.values(): - if data["raw"] == "ADAPTIVE": - if data["is_metric"]: - data["raw"] = "METERS" - else: - data["raw"] = "FEET" - return units - - def get_unit_scale(self): - conversions = { - "KILOMETERS": 1e3, - "CENTIMETERS": 1e-2, - "MILLIMETERS": 1e-3, - "MICROMETERS": 1e-6, - "FEET": 0.3048, - "INCHES": 0.0254, - } - if bpy.context.scene.unit_settings.system in {"METRIC", "IMPERIAL"}: - scale = bpy.context.scene.unit_settings.scale_length - else: - scale = 1 - if self.units["length"]["raw"] in conversions.keys(): - scale *= conversions[self.units["length"]["raw"]] - return scale - - def get_object_attributes(self, obj): - attributes = {"Name": self.get_ifc_name(obj.name)} - global_id_index = obj.BIMObjectProperties.attributes.find("GlobalId") - if global_id_index == -1: - global_id = obj.BIMObjectProperties.attributes.add() - global_id.name = "GlobalId" - global_id.string_value = ifcopenshell.guid.new() - elif obj.BIMObjectProperties.attributes[global_id_index].string_value in self.global_ids: - obj.BIMObjectProperties.attributes[global_id_index].string_value = ifcopenshell.guid.new() - attributes.update({a.name: a.string_value for a in obj.BIMObjectProperties.attributes}) - self.global_ids.append(attributes["GlobalId"]) - return attributes - - def get_products(self): - for product in self.selected_products: - self.add_product(self.get_product(product)) - self.resolve_modifiers(product) - - def resolve_modifiers(self, product): - obj = product["raw"] - if obj.data and hasattr(obj.data, "BIMMeshProperties") and not obj.data.BIMMeshProperties.is_parametric: - return - instance_objects = [ - (obj, {"location": obj.matrix_world.translation, "array_offset": Vector((0, 0, 0)), "scale": obj.scale}) - ] - - for modifier in obj.modifiers: - created_instances = [] - if modifier.type == "ARRAY": - instance_objects.extend(self.resolve_array_modifier(product, modifier, instance_objects)) - elif modifier.type == "MIRROR": - instance_objects.extend(self.resolve_mirror_modifier(product, modifier, instance_objects)) - - def get_array_modifier(self, product, modifier): - obj = product["raw"] - array = ArrayModifier() - world_rotation = obj.matrix_world.decompose()[1] - array.offset = world_rotation @ Vector( - ( - modifier.constant_offset_displace[0], - modifier.constant_offset_displace[1], - modifier.constant_offset_displace[2], - ) - ) - if modifier.fit_type == "FIXED_COUNT": - array.count = modifier.count - elif modifier.fit_type == "FIT_LENGTH": - array.count = int(modifier.fit_length / array.offset.length) - return array - - def resolve_array_modifier(self, product, modifier, instance_objects): - modifier = self.get_array_modifier(product, modifier) - created_instances = [] - for obj in instance_objects: - for n in range(modifier.count - 1): - override = obj[1].copy() - override["array_offset"] = (n + 1) * modifier.offset - override["location"] = obj[1]["location"].copy() - location = override["location"] + ((n + 1) * modifier.offset) - override["location"] = location - self.add_product( - self.get_product( - {"raw": obj[0], "metadata": product["metadata"]}, - metadata_override=override, - attribute_override={ - "GlobalId": self.get_parametric_global_id( - product["raw"], len(instance_objects) + len(created_instances) - 1 - ) - }, - ) - ) - created_instances.append((obj[0], override)) - return created_instances - - def resolve_mirror_modifier(self, product, modifier, instance_objects): - created_instances = [] - mirrors = [] - for axis in [0, 1, 2]: - if modifier.use_axis[axis]: - mirrors.append(axis) - for mirror in mirrors: - axis_instances = [] - for obj in instance_objects: - override = obj[1].copy() - override["has_scale"] = True - override["has_mirror"] = True - override["scale"] = obj[1]["scale"].copy() - override["scale"][mirror] *= -1 - mirror_axis = Vector((0, 0, 0)) - mirror_axis[mirror] = 1 - world_rotation = obj[0].matrix_world.decompose()[1].to_matrix().to_4x4() - unrotated_offset = world_rotation.inverted() @ override["array_offset"] - mirrored_offset = unrotated_offset @ Matrix.Scale(-1, 4, mirror_axis) - rotated_offset = world_rotation @ mirrored_offset - override["location"] = override["location"] - override["array_offset"] + rotated_offset - self.add_product( - self.get_product( - {"raw": obj[0], "metadata": product["metadata"]}, - metadata_override=override, - attribute_override={ - "GlobalId": self.get_parametric_global_id( - product["raw"], len(instance_objects) + len(created_instances) - 1 - ) - }, - ) - ) - created_instances.append((obj[0], override)) - axis_instances.append((obj[0], override)) - instance_objects.extend(axis_instances) - return created_instances - - def resolve_product_relationships(self): - for i, product in enumerate(self.products): - obj = product["raw"] - self.resolve_voids_and_fills(i, obj) - self.resolve_structural_connections(i, obj) - - def resolve_structural_connections(self, i, obj): - if not obj.BIMObjectProperties.structural_member_connection: - return - self.rel_connects_structural_member[i] = self.get_product_index_from_raw_name( - obj.BIMObjectProperties.structural_member_connection.name - ) - - def resolve_voids_and_fills(self, i, obj): - for m in obj.modifiers: - if m.type != "BOOLEAN" or m.object is None: - continue - void_or_projection = self.get_product_index_from_raw_name(m.object.name) - if void_or_projection is None: - continue - if m.operation == "DIFFERENCE" and self.get_ifc_class(m.object.name) == "IfcOpeningElement": - self.rel_voids_elements.setdefault(i, []).append(void_or_projection) - if not m.object.parent: - continue - fill = self.get_product_index_from_raw_name(m.object.parent.name) - if fill: - self.rel_fills_elements.setdefault(void_or_projection, []).append(fill) - elif m.operation == "UNION" and self.get_ifc_class(m.object.name) == "IfcProjectionElement": - self.rel_projects_elements.setdefault(i, []).append(void_or_projection) - - def get_axis(self, matrix, axis): - return matrix.col[axis].to_3d().normalized() - - def get_parametric_global_id(self, obj, index): - global_ids = obj.BIMObjectProperties.global_ids - total_global_ids = len(global_ids) - if index < total_global_ids: - return global_ids[index].name - global_id = obj.BIMObjectProperties.global_ids.add() - global_id.name = ifcopenshell.guid.new() - return global_id.name - - def add_product(self, product): - self.products.append(product) - self.product_name_index_map[product["raw"].name] = self.product_index - self.product_index += 1 - - def get_product_index_from_raw_name(self, name): - for index, product in enumerate(self.products): - if product["raw"].name == name: - return index - - def append_product_attributes(self, product, obj): - product.update( - { - "location": obj.matrix_world.translation, - "up_axis": self.get_axis(obj.matrix_world, 2), - "forward_axis": self.get_axis(obj.matrix_world, 0), - "right_axis": self.get_axis(obj.matrix_world, 1), - "has_scale": (obj.scale - Vector((1, 1, 1))).length > 0.01, - "has_mirror": False, - "array_offset": Vector((0, 0, 0)), - "scale": obj.scale, - "representations": self.get_object_representation_names(obj), - } - ) - - def get_product(self, selected_product, metadata_override={}, attribute_override={}): - obj = selected_product["raw"] - product = { - "ifc": None, - "raw": obj, - "class": self.get_ifc_class(obj.name), - "attributes": self.get_object_attributes(obj), - "relating_structure": None, - "relating_host": None, - "relating_qtos_key": None, - "has_boundary_condition": obj.BIMObjectProperties.has_boundary_condition, - "boundary_condition_class": None, - "boundary_condition_attributes": {}, - "structural_member_connection": None, - } - self.append_product_attributes(product, obj) - product["attributes"].update(attribute_override) - product.update(metadata_override) - - type_product = obj.BIMObjectProperties.relating_type - - if product["has_boundary_condition"]: - product["boundary_condition_class"] = obj.BIMObjectProperties.boundary_condition.name - product["boundary_condition_attributes"] = { - a.name: a.string_value for a in obj.BIMObjectProperties.boundary_condition.attributes - } - - self.get_product_relating_structure(product, obj) - - if "IfcRelNests" in obj.constraints: - # TODO: I think get_product_index_from_raw_name should not be used - parent_product_index = self.get_product_index_from_raw_name(obj.constraints["IfcRelNests"].target.name) - self.rel_nests.setdefault(parent_product_index, []).append(product) - product["relating_host"] = parent_product_index - - for name, constraint in obj.constraints.items(): - if "IfcRelSpaceBoundary" not in name: - continue - self.rel_space_boundaries.setdefault(self.product_index, []).append( - { - "ifc": None, - "class": self.get_ifc_class(name), - "related_building_element_raw_name": constraint.target.name, - "connection_geometry_face_index": name.split("/")[1], - "attributes": { - "PhysicalOrVirtualBoundary": name.split("/")[2], - "InternalOrExternalBoundary": name.split("/")[3], - }, - } - ) - - if obj.instance_type == "COLLECTION" and self.is_a_rel_aggregates( - self.get_ifc_class(obj.instance_collection.name) - ): - self.rel_aggregates[self.product_index] = obj.name - - if "rel_aggregates_relating_object" in selected_product["metadata"]: - relating_object = selected_product["metadata"]["rel_aggregates_relating_object"] - self.aggregates.setdefault(relating_object.name, []).append(self.product_index) - - if obj.name in self.qtos: - self.rel_defines_by_qto.setdefault(obj.name, []).append(product) - - self.get_product_psets_qtos(product, obj, is_pset=True) - self.get_product_psets_qtos(product, obj, is_qto=True) - self.get_styled_items_and_surface_styles(product, obj) - - for reference in obj.BIMObjectProperties.document_references: - self.rel_associates_document_object.setdefault(reference.name, []).append(product) - - for classification in obj.BIMObjectProperties.classifications: - self.rel_associates_classification_object.setdefault(classification.name, []).append(product) - - for constraint in obj.BIMObjectProperties.constraints: - self.rel_associates_constraint_object.setdefault(constraint.name, []).append(product) - - if obj.BIMObjectProperties.material_type == "IfcMaterial" and obj.BIMObjectProperties.material: - self.rel_associates_material.setdefault(obj.BIMObjectProperties.material.name, []).append(product) - elif obj.BIMObjectProperties.material_type == "IfcMaterialConstituentSet": - self.rel_associates_material_constituent_set.append((obj.BIMObjectProperties.material_set, product)) - elif obj.BIMObjectProperties.material_type == "IfcMaterialLayerSet": - self.rel_associates_material_layer_set.append((obj.BIMObjectProperties.material_set, product)) - elif obj.BIMObjectProperties.material_type == "IfcMaterialProfileSet": - self.rel_associates_material_profile_set.append((obj.BIMObjectProperties.material_set, product)) - - return product - - def get_product_psets_qtos(self, product, obj, is_pset=False, is_qto=False): - if is_pset: - psets_qtos = obj.BIMObjectProperties.psets - results = self.psets - relationships = self.rel_defines_by_pset - if is_qto: - psets_qtos = obj.BIMObjectProperties.qtos - if not psets_qtos and self.ifc_export_settings.should_guess_quantities: - self.add_automatic_qtos(product["class"], obj) - psets_qtos = obj.BIMObjectProperties.qtos - results = self.qtos - relationships = self.rel_defines_by_qto - for item in psets_qtos: - item_key = "{}/{}".format(item.name, obj.name) - raw = {p.name: p.string_value for p in item.properties if p.string_value} - if not raw: - continue - results[item_key] = {"ifc": None, "raw": raw, "attributes": {"Name": item.name}} - relationships.setdefault(item_key, []).append(product) - - def get_material_psets(self, material, obj): - psets = obj.BIMMaterialProperties.psets - results = self.material_psets - for item in psets: - item_key = "{}/{}".format(item.name, obj.name) - raw = {p.name: p.string_value for p in item.properties if p.string_value} - if not raw: - continue - results[item_key] = {"ifc": None, "raw": raw, "material": material, "attributes": {"Name": item.name}} - - def add_automatic_qtos(self, ifc_class: str, obj): - if not obj.data: - return - applicable_qtos = schema.ifc.psetqto.get_applicable(ifc_class, qto_only=True) - for applicable_qto in applicable_qtos: - has_automatic_value = False - guessed_values = {} - prop_names = [p.Name for p in applicable_qto.HasPropertyTemplates] - for prop_name in prop_names: - value = self.qto_calculator.guess_quantity(prop_name, prop_names, obj) - if value: - guessed_values[prop_name] = value - has_automatic_value = True - if has_automatic_value: - qto = obj.BIMObjectProperties.qtos.add() - qto.name = applicable_qto.Name - for prop_name in prop_names: - prop = qto.properties.add() - prop.name = prop_name - if prop_name in guessed_values: - prop.string_value = str(guessed_values[prop_name]) - - def get_product_relating_structure(self, product, obj): - relating_structure = obj.BIMObjectProperties.relating_structure - if relating_structure: - reference = self.get_spatial_structure_element_reference(relating_structure.name) - self.rel_contained_in_spatial_structure.setdefault(reference, []).append(self.product_index) - product["relating_structure"] = reference - return - for collection in product["raw"].users_collection: - self.parse_product_collection(product, collection) - - def parse_product_collection(self, product, collection): - if collection is None: - return - class_name = self.get_ifc_class(collection.name) - if self.is_a_spatial_structure_element(class_name): - reference = self.get_spatial_structure_element_reference(collection.name) - self.rel_contained_in_spatial_structure.setdefault(reference, []).append(self.product_index) - product["relating_structure"] = reference - elif self.is_a_group(class_name): - reference = self.get_group_reference(collection.name) - self.rel_assigns_to_group.setdefault(reference, []).append(self.product_index) - elif self.is_a_rel_aggregates(class_name): - # Aggregates are not handled here, since we don't know the order in - # which products are parsed. - pass - else: - self.parse_product_collection(product, self.get_parent_collection(collection)) - - def get_parent_collection(self, child_collection): - for parent_collection in bpy.data.collections: - for child in parent_collection.children: - if child.name == child_collection.name: - return parent_collection - - def add_spatial_elements_if_unselected(self, selected_objects): - results = set(selected_objects) - base_collections = set() - added_objs = [] - for obj in selected_objects: - for collection in obj.users_collection: - base_collections.add(collection) - for collection in base_collections: - spatial_obj = bpy.data.objects.get(collection.name) - if not spatial_obj or spatial_obj in added_objs: - continue - added_objs.append(spatial_obj) - parent_collection = self.get_parent_collection(collection) - while parent_collection: - spatial_obj = bpy.data.objects.get(parent_collection.name) - parent_collection = self.get_parent_collection(parent_collection) - if not spatial_obj or spatial_obj in added_objs: - continue - added_objs.append(spatial_obj) - results.update(added_objs) - return results - - def add_type_elements_if_unselected(self, selected_objects): - added_objs = [] - for obj in selected_objects: - if obj.BIMObjectProperties.relating_type: - added_objs.append(obj.BIMObjectProperties.relating_type) - if obj.instance_type == "COLLECTION": - for obj2 in obj.instance_collection.objects: - if obj2.BIMObjectProperties.relating_type: - added_objs.append(obj2.BIMObjectProperties.relating_type) - selected_objects.update(added_objs) - selected_objects = set(selected_objects) - - def categorise_selected_objects(self, objects_to_sort, metadata=None): - if not metadata: - metadata = {} - for obj in objects_to_sort: - if obj.name[0:3] != "Ifc": - continue - elif self.is_a_grid_axis(self.get_ifc_class(obj.name)): - self.selected_grid_axes.append({"raw": obj, "metadata": metadata}) - elif self.is_a_spatial_structure_element(self.get_ifc_class(obj.name)): - self.selected_spatial_structure_elements.append({"raw": obj, "metadata": metadata}) - elif self.is_a_type(self.get_ifc_class(obj.name)): - self.selected_types.append({"raw": obj, "metadata": metadata}) - elif self.is_a_group(self.get_ifc_class(obj.name)): - self.selected_groups.append({"raw": obj, "metadata": metadata}) - elif obj.instance_type == "COLLECTION": - self.categorise_selected_objects( - obj.instance_collection.objects, {"rel_aggregates_relating_object": obj} - ) - self.selected_products.append({"raw": obj, "metadata": metadata}) - elif self.is_a_project(self.get_ifc_class(obj.name)) or self.is_a_library(self.get_ifc_class(obj.name)): - pass - elif not self.is_a_library(self.get_ifc_class(obj.users_collection[0].name)): - self.selected_products.append({"raw": obj, "metadata": metadata}) - - def get_door_attributes(self): - return self.get_predefined_attributes("door") - - def get_window_attributes(self): - return self.get_predefined_attributes("window") - - def get_predefined_attributes(self, attr): - results = {} - for filename in Path(self.data_dir + attr + "/").glob("**/*.csv"): - with open(filename, "r") as f: - type_name = filename.parts[-2] - pset_name = filename.stem - results.setdefault(type_name, []).append( - { - "ifc": None, - "raw": {x[0]: x[1] for x in list(csv.reader(f))}, - "pset_name": pset_name.split(".")[0], - } - ) - return results - - def get_classifications(self): - results = {} - for classification in bpy.context.scene.BIMProperties.classifications: - if classification.name not in schema.ifc.classification_files: - schema.ifc.classification_files[classification.name] = ifcopenshell.file.from_string( - classification.data - ) - results[classification.name] = { - "ifc": None, - "raw": classification, - "raw_element": schema.ifc.classification_files[classification.name].by_type("IfcClassification")[0], - } - return results - - def get_classification_reference_maps(self): - results = {} - for name, classification in self.classifications.items(): - ifc_file = schema.ifc.classification_files[name] - if ifc_file.schema == "IFC2X3": - results[name] = {e.ItemReference: e for e in ifc_file.by_type("IfcClassificationReference")} - else: - results[name] = {e.Identification: e for e in ifc_file.by_type("IfcClassificationReference")} - return results - - def get_classification_references(self): - results = {} - for product in self.selected_products + self.selected_types + self.selected_spatial_structure_elements: - for reference in product["raw"].BIMObjectProperties.classifications: - results[reference.name] = { - "ifc": None, - "raw": reference, - "raw_element": self.classification_reference_maps[reference.referenced_source][reference.name], - } - return results - - def get_constraints(self): - results = {} - data_map = { - "name": "Name", - "description": "Description", - "constraint_grade": "ConstraintGrade", - "constraint_source": "ConstraintSource", - "user_defined_grade": "UserDefinedGrade", - "objective_qualifier": "ObjectiveQualifier", - "user_defined_qualifier": "UserDefinedQualifier", - } - for constraint in bpy.context.scene.BIMProperties.constraints: - attributes = {} - for key, value in data_map.items(): - if getattr(constraint, key): - attributes[value] = getattr(constraint, key) - results[constraint.name] = {"ifc": None, "raw": constraint, "attributes": attributes} - return results - - def get_people(self): - data_map = { - "name": "Identification", - "family_name": "FamilyName", - "given_name": "GivenName", - } - list_data_map = { - "middle_names": "MiddleNames", - "prefix_titles": "PrefixTitles", - "suffix_titles": "SuffixTitles", - } - results = [] - - if self.ifc_export_settings.schema == "IFC2X3" and not bpy.context.scene.BIMProperties.people: - bpy.ops.bim.add_person() - - for person in bpy.context.scene.BIMProperties.people: - attributes = {} - for key, value in data_map.items(): - if getattr(person, key): - attributes[value] = getattr(person, key) - for key, value in list_data_map.items(): - if getattr(person, key): - attributes[value] = getattr(person, key).split(",") - results.append( - { - "ifc": None, - "raw": person, - "attributes": attributes, - "roles": self.get_roles(person.roles), - "addresses": self.get_addresses(person.addresses), - } - ) - return results - - def get_organisations(self): - data_map = { - "name": "Name", - "description": "Description", - } - results = [] - - if self.ifc_export_settings.schema == "IFC2X3" and not bpy.context.scene.BIMProperties.organisations: - bpy.ops.bim.add_organisation() - - for organisation in bpy.context.scene.BIMProperties.organisations: - attributes = {} - for key, value in data_map.items(): - if getattr(organisation, key): - attributes[value] = getattr(organisation, key) - results.append( - { - "ifc": None, - "raw": organisation, - "attributes": attributes, - "roles": self.get_roles(organisation.roles), - "addresses": self.get_addresses(organisation.addresses), - } - ) - return results - - def get_roles(self, roles): - data_map = { - "name": "Role", - "user_defined_role": "UserDefinedRole", - "description": "Description", - } - results = [] - for role in roles: - attributes = {} - for key, value in data_map.items(): - if getattr(role, key): - attributes[value] = getattr(role, key) - results.append({"ifc": None, "raw": role, "attributes": attributes}) - return results - - def get_addresses(self, addresses): - results = [] - for address in addresses: - results.append(self.get_address(address)) - return results - - def get_address(self, address): - address_data_map = { - "purpose": "Purpose", - "description": "Description", - "user_defined_purpose": "UserDefinedPurpose", - } - postal_data_map = { - "internal_location": "InternalLocation", - "postal_box": "PostalBox", - "town": "Town", - "region": "Region", - "postal_code": "PostalCode", - "country": "Country", - } - telecom_data_map = { - "pager_number": "PagerNumber", - "www_home_page_url": "WWWHomePageURL", - } - telecom_list_data_map = { - "telephone_numbers": "TelephoneNumbers", - "fascimile_numbers": "FascimileNumbers", - "electronic_mail_addresses": "ElectronicMailAddresses", - "messaging_ids": "MessagingIDs", - } - attributes = {} - if "IfcPostalAddress" in address.name: - merged_data_map = {**address_data_map, **postal_data_map} - if address.address_lines: - attributes["AddressLines"] = address.address_lines.split("/") - elif "IfcTelecomAddress" in address.name: - merged_data_map = {**address_data_map, **telecom_data_map} - for key, value in telecom_list_data_map.items(): - if getattr(address, key): - attributes[value] = getattr(address, key).split(",") - for key, value in merged_data_map.items(): - if getattr(address, key): - attributes[value] = getattr(address, key) - return { - "ifc": None, - "raw": address, - "is_postal": "IfcPostalAddress" in address.name, - "is_telecom": "IfcTelecomAddress" in address.name, - "attributes": attributes, - } - - def get_document_references(self): - results = {} - for reference in bpy.context.scene.BIMProperties.document_references: - data_map = { - "name": "Identification", - "human_name": "Name", - "description": "Description", - "location": "Location", - } - attributes = {} - for key, value in data_map.items(): - if getattr(reference, key): - attributes[value] = getattr(reference, key) - results[reference.name] = { - "ifc": None, - "raw": reference, - "referenced_document": reference.referenced_document, - "attributes": attributes, - } - return results - - def get_document_information(self): - results = {} - for information in bpy.context.scene.BIMProperties.document_information: - data_map = { - "name": "Identification", - "human_name": "Name", - "description": "Description", - "location": "Location", - "purpose": "Purpose", - "intended_use": "IntendedUse", - "scope": "Scope", - "revision": "Revision", - "creation_time": "CreationTime", - "last_revision_time": "LastRevisionTime", - "electronic_format": "ElectronicFormat", - "valid_from": "ValidFrom", - "valid_until": "ValidUntil", - "confidentiality": "Confidentiality", - "status": "Status", - } - attributes = {} - for key, value in data_map.items(): - if getattr(information, key): - attributes[value] = getattr(information, key) - results[information.name] = {"ifc": None, "raw": information, "attributes": attributes} - return results - - def get_projects(self): - results = [] - for collection in bpy.data.collections: - if self.is_a_project(self.get_ifc_class(collection.name)): - obj = bpy.data.objects.get(collection.name) - results.append( - { - "ifc": None, - "raw": collection, - "class": self.get_ifc_class(collection.name), - "attributes": self.get_object_attributes(obj), - } - ) - return results - - def get_all_objects_in_project(self, collection): - results = [] - results.extend(list(collection.objects)) - for child in collection.children: - results.extend(self.get_all_objects_in_project(child)) - return results - - def setup_project(self): - bpy.ops.bim.quick_project_setup() - for collection in bpy.data.collections: - if collection.name == "IfcBuildingStorey/Ground Floor": - break - for obj in bpy.context.selected_objects: - if hasattr(obj, "data") and isinstance(obj.data, bpy.types.Mesh) and "/" not in obj.name: - obj.name = "IfcBuildingElementProxy/{}".format(obj.name) - for user_collection in obj.users_collection: - user_collection.objects.unlink(obj) - collection.objects.link(obj) - - def get_libraries(self): - results = [] - for collection in self.project["raw"].children: - if not self.is_a_library(self.get_ifc_class(collection.name)): - continue - results.append( - { - "ifc": None, - "raw": collection, - "class": self.get_ifc_class(collection.name), - "rel_declares_type_products": [], - "attributes": self.get_object_attributes(collection), - } - ) - return results - - def get_map_conversion(self): - scene = bpy.context.scene - if not scene.BIMProperties.has_georeferencing: - return {} - return { - "ifc": None, - "attributes": { - "Eastings": float(scene.MapConversion.eastings), - "Northings": float(scene.MapConversion.northings), - "OrthogonalHeight": float(scene.MapConversion.orthogonal_height), - "XAxisAbscissa": float(scene.MapConversion.x_axis_abscissa), - "XAxisOrdinate": float(scene.MapConversion.x_axis_ordinate), - "Scale": float(scene.MapConversion.scale), - }, - } - - def get_target_crs(self): - scene = bpy.context.scene - if not scene.BIMProperties.has_georeferencing: - return {} - return { - "ifc": None, - "attributes": { - "Name": scene.TargetCRS.name, - "Description": scene.TargetCRS.description, - "GeodeticDatum": scene.TargetCRS.geodetic_datum, - "VerticalDatum": scene.TargetCRS.vertical_datum, - "MapProjection": scene.TargetCRS.map_projection, - "MapZone": str(scene.TargetCRS.map_zone), - "MapUnit": scene.TargetCRS.map_unit, - }, - } - - def get_library_information(self): - scene = bpy.context.scene - if not scene.BIMProperties.has_library: - return {} - return { - "ifc": None, - "attributes": { - "Name": scene.BIMLibrary.name, - "Version": scene.BIMLibrary.version, - "VersionDate": scene.BIMLibrary.version_date, - "Location": scene.BIMLibrary.location, - "Description": scene.BIMLibrary.description, - }, - } - - def get_spatial_structure_elements(self): - elements = [] - for selected_element in self.selected_spatial_structure_elements: - obj = selected_element["raw"] - element = { - "ifc": None, - "raw": obj, - "class": self.get_ifc_class(obj.name), - "attributes": self.get_object_attributes(obj), - "address": self.get_address(obj.BIMObjectProperties.address), - } - self.append_product_attributes(element, obj) - self.get_product_psets_qtos(element, obj, is_pset=True) - self.get_product_psets_qtos(element, obj, is_qto=True) - self.get_styled_items_and_surface_styles(element, obj) - elements.append(element) - return elements - - def get_groups(self): - elements = [] - for selected_element in self.selected_groups: - obj = selected_element["raw"] - elements.append( - { - "ifc": None, - "raw": obj, - "class": self.get_ifc_class(obj.name), - "attributes": self.get_object_attributes(obj), - } - ) - return elements - - def load_presentation_layer_assignments(self): - for representation in self.representations.values(): - if representation["presentation_layer"] is False: - continue - self.presentation_layer_assignments.setdefault(representation["presentation_layer"], []).append( - representation - ) - - def load_representations(self): - if not self.ifc_export_settings.has_representations: - return - self.generated_subcontexts = [] - for context in self.ifc_export_settings.context_tree: - for subcontext in context["subcontexts"]: - for target_view in subcontext["target_views"]: - if context["name"] == "Model" and subcontext["name"] == "Box" and target_view == "MODEL_VIEW": - self.generated_subcontexts = "/".join([context["name"], subcontext["name"], target_view]) - for product in self.selected_products + self.selected_types + self.selected_spatial_structure_elements: - self.prevent_data_name_duplicates(product) - self.load_product_representations(product) - - def prevent_data_name_duplicates(self, product): - if ( - product["raw"].data - and bpy.data.meshes.get(product["raw"].data.name) - and bpy.data.curves.get(product["raw"].data.name) - ): - product["raw"].data.name += "~" - - def load_product_representations(self, product): - obj = product["raw"] - if obj.data and obj.data.name in self.representations: - return - if isinstance(obj.data, bpy.types.Camera): - return - self.append_representation_per_context(obj) - - def is_point_cloud(self, obj): - return hasattr(obj, "point_cloud_visualizer") and obj.point_cloud_visualizer.uuid - - def is_structural(self, obj): - return "IfcStructural" in obj.name - - def append_default_representation(self, obj): - self.representations["Model/Body/MODEL_VIEW/{}".format(obj.data.name)] = self.get_representation( - obj.data, obj, "Model", "Body", "MODEL_VIEW" - ) - if "Model/Box/MODEL_VIEW" in self.generated_subcontexts: - self.representations["Model/Box/MODEL_VIEW/{}".format(obj.data.name)] = self.get_representation( - obj.data, obj, "Model", "Box", "MODEL_VIEW" - ) - - def append_point_cloud_representation(self, obj): - self.representations["Model/Body/MODEL_VIEW/{}".format(obj.name)] = self.get_representation( - obj.point_cloud_visualizer, obj, "Model", "Body", "MODEL_VIEW" - ) - - def append_curve_axis_representation(self, obj): - self.representations["Model/Axis/GRAPH_VIEW/{}".format(obj.data.name)] = self.get_representation( - obj.data, obj, "Model", "Axis", "GRAPH_VIEW" - ) - - def append_structural_reference_representation(self, obj): - if obj.type == "EMPTY": - self.representations["Model/Reference/GRAPH_VIEW/{}".format(obj.name)] = self.get_representation( - obj, obj, "Model", "Reference", "GRAPH_VIEW" - ) - else: - self.representations["Model/Reference/GRAPH_VIEW/{}".format(obj.data.name)] = self.get_representation( - obj.data, obj, "Model", "Reference", "GRAPH_VIEW" - ) - - def append_representation_per_context(self, obj): - if obj.data: - name = self.get_ifc_representation_name(obj.data.name) - else: - name = obj.name - for context in self.ifc_export_settings.context_tree: - for subcontext in context["subcontexts"]: - for target_view in subcontext["target_views"]: - representation = self.get_shape_representation(obj, context["name"], subcontext["name"], target_view) - if representation: - self.append_representation_in_context(obj, representation, name) - - def get_shape_representation(self, obj, context, subcontext, target_view): - for representation in obj.BIMObjectProperties.representations: - c = self.stored_file.by_id(representation.ifc_definition_id) - if c.ContextType == context and c.ContextIdentifier == subcontext and c.TargetView == target_view: - return representation - if obj.BIMObjectProperties.representations: - return - # TODO: reimplement - see bug #1222 - #if context == "Model" and subcontext == "Body" and target_view == "MODEL_VIEW": - # representation_context = obj.BIMObjectProperties.representation_contexts.add() - # representation_context.context = "Model" - # representation_context.name = "Body" - # representation_context.target_view = "MODEL_VIEW" - # return representation_context - - def append_representation_in_context(self, obj, shape_representation, name): - context_of_items = self.stored_file.by_id(shape_representation.ifc_definition_id).ContextOfItems - context = context_of_items.ContextType - subcontext = context_of_items.ContextIdentifier - target_view = context_of_items.TargetView - - if self.ifc_export_settings.should_roundtrip_native and shape_representation.ifc_definition_id: - self.representations[ - "{}/{}/{}/{}".format(context, subcontext, target_view, name) - ] = self.get_representation(obj.data, obj, context, subcontext, target_view) - return - - context_prefix = "/".join([context, subcontext, target_view]) - mesh_name = "/".join([context_prefix, name]) - mesh = self.search_for_mesh_or_curve_data(mesh_name) - if mesh: - self.representations[mesh_name] = self.get_representation(mesh, obj, context, subcontext, target_view) - if "Model/Box/MODEL_VIEW" in self.generated_subcontexts and context_prefix == "Model/Body/MODEL_VIEW": - self.representations[ - "Model/Box/MODEL_VIEW/{}".format(mesh_name.split("/")[3]) - ] = self.get_representation(obj.data, obj, "Model", "Box", "MODEL_VIEW") - elif ( - context_prefix == "Model/Body/MODEL_VIEW" and obj.data and not self.is_mesh_context_sensitive(obj.data.name) - ): - self.append_default_representation(obj) - elif context_prefix == "Model/Body/MODEL_VIEW" and self.is_point_cloud(obj): - self.append_point_cloud_representation(obj) - elif context_prefix == "Model/Reference/GRAPH_VIEW" and self.is_structural(obj): - self.append_structural_reference_representation(obj) - elif context_prefix == "Model/Axis/GRAPH_VIEW" and obj.type == "CURVE": - self.append_curve_axis_representation(obj) - - def search_for_mesh_or_curve_data(self, name): - data = bpy.data.meshes.get(name) - if not data: - data = bpy.data.curves.get(name) - return data - - def get_representation(self, mesh, obj, context, subcontext, target_view): - representation = self.get_shape_representation(obj, context, subcontext, target_view) - return { - "ifc": None, - "raw": mesh, - "raw_object": obj, - "context": context, - "subcontext": subcontext, - "target_view": target_view, - "has_ifc_definition": representation and representation.ifc_definition_id, - "ifc_definition": mesh.BIMMeshProperties.ifc_definition if hasattr(mesh, "BIMMeshProperties") else None, - "ifc_definition_id": representation.ifc_definition_id if representation else 0 - if hasattr(mesh, "BIMMeshProperties") - else None, - "is_parametric": mesh.BIMMeshProperties.is_parametric if hasattr(mesh, "BIMMeshProperties") else False, - "is_curve": isinstance(mesh, bpy.types.Curve), - "is_point_cloud": self.is_point_cloud(obj), - "is_structural": self.is_structural(obj), - "is_text": isinstance(mesh, bpy.types.TextCurve), - "is_wireframe": self.is_wireframe_mesh(mesh, obj), - "is_native": mesh.BIMMeshProperties.is_native if hasattr(mesh, "BIMMeshProperties") else False, - "is_swept_solid": mesh.BIMMeshProperties.is_swept_solid if hasattr(mesh, "BIMMeshProperties") else False, - "is_generated": False, - "presentation_layer": mesh.BIMMeshProperties.presentation_layer_index - if hasattr(mesh, "BIMMeshProperties") and mesh.BIMMeshProperties.presentation_layer_index != -1 - else False, - "attributes": {"Name": mesh.name if mesh else ""}, - } - - def is_wireframe_mesh(self, mesh, obj): - if isinstance(mesh, bpy.types.Mesh) and not mesh.polygons: - modifiers = [m.type for m in obj.modifiers] - # SCREW and SKIN can create faces, so it is not a wireframe mesh - if "SCREW" not in modifiers and "SKIN" not in modifiers: - return True - if isinstance(mesh, bpy.types.Curve) and not mesh.bevel_object and not mesh.bevel_depth: - return True - return False - - def is_mesh_context_sensitive(self, name): - return "/" in name and (name[0:6] == "Model/" or name[0:5] == "Plan/") - - def get_ifc_representation_name(self, name): - if self.is_mesh_context_sensitive(name): - return name.split("/")[3] - return name - - def get_materials_and_surface_styles(self): - if not self.ifc_export_settings.has_representations: - return - for product in self.selected_products + self.selected_types + self.selected_spatial_structure_elements: - obj = product["raw"] - if obj.BIMObjectProperties.material_type == "IfcMaterial" and obj.BIMObjectProperties.material: - self.get_material(obj.BIMObjectProperties.material) - elif obj.BIMObjectProperties.material_type == "IfcMaterialConstituentSet": - for constituent in obj.BIMObjectProperties.material_set.material_constituents: - self.get_material(constituent.material) - elif obj.BIMObjectProperties.material_type == "IfcMaterialLayerSet": - for layer in obj.BIMObjectProperties.material_set.material_layers: - self.get_material(layer.material) - elif obj.BIMObjectProperties.material_type == "IfcMaterialProfileSet": - for profile in obj.BIMObjectProperties.material_set.material_profiles: - self.get_material(profile.material) - - def get_material(self, material): - if material.name in self.materials: - return - data = { - "ifc": None, - "raw": material, - "attributes": self.get_material_attributes(material), - } - self.surface_styles[material.name] = {"ifc": None, "raw": material} - self.materials[material.name] = data - self.get_material_psets(data, material) - - def get_material_attributes(self, material): - attributes = {"Name": material.name} - attributes.update({a.name: a.string_value for a in material.BIMMaterialProperties.attributes}) - return attributes - - def get_styled_items_and_surface_styles(self, element, obj): - if not self.ifc_export_settings.has_representations: - return - if obj.data is None: - return - for slot in obj.material_slots: - if slot.material is None: - continue - self.surface_styles[slot.material.name] = {"ifc": None, "raw": slot.material} - self.styled_items.append( - { - "ifc": None, - "raw": slot.material, - "related_element": element, - "attributes": {"Name": slot.material.name}, - } - ) - - def get_grid_axes(self): - results = {} - for selected_axis in self.selected_grid_axes: - obj = selected_axis["raw"] - grid_raw = bpy.data.objects.get(self.get_parent_collection(obj.users_collection[0]).name) - if grid_raw.name not in results: - results[grid_raw.name] = {"UAxes": [], "VAxes": [], "WAxes": []} - if "UAxes" in obj.users_collection[0].name: - axis_type = "UAxes" - elif "VAxes" in obj.users_collection[0].name: - axis_type = "VAxes" - else: - axis_type = "WAxes" - results[grid_raw.name][axis_type].append( - { - "ifc": None, - "raw": obj, - "grid_raw": grid_raw, - "class": "IfcGridAxis", - "attributes": {a.name: a.string_value for a in obj.BIMObjectProperties.attributes}, - } - ) - return results - - def get_type_products(self): - results = [] - for product in self.selected_types: - results.append(self.get_product(product)) - return results - - def get_object_representation_names(self, obj): - names = [] - if self.is_point_cloud(obj): - names.append("Model/Body/MODEL_VIEW/{}".format(obj.name)) - return names - elif self.is_structural(obj) and obj.type == "EMPTY": - names.append("Model/Reference/GRAPH_VIEW/{}".format(obj.name)) - return names - if not obj.data: - return names - name = self.get_ifc_representation_name(obj.data.name) - for context in self.ifc_export_settings.context_tree: - for subcontext in context["subcontexts"]: - for target_view in subcontext["target_views"]: - mesh_name = "/".join([context["name"], subcontext["name"], target_view, name]) - if mesh_name in self.representations: - names.append(mesh_name) - return names - - def get_spatial_structure_elements_tree(self, parent): - children = [] - if parent["raw"].name not in bpy.data.collections: - return children - for reference, element in enumerate(self.spatial_structure_elements): - if ( # A convention is established that spatial elements may be - # an object placed in a collection of the same name - element["raw"].name == element["raw"].users_collection[0].name - and element["raw"].users_collection[0].name - in [c.name for c in bpy.data.collections[parent["raw"].name].children] - ) or ( # We allow finer grain spatial elements such as IfcSpace to - # break the convention to prevent collection overload in Blender - element["raw"].name != element["raw"].users_collection[0].name - and element["raw"].users_collection[0].name - in [o.name for o in bpy.data.collections[parent["raw"].name].objects] - ): - children.append({"reference": reference, "children": self.get_spatial_structure_elements_tree(element)}) - return children - - def get_spatial_structure_element_reference(self, name): - return [e["raw"].name for e in self.spatial_structure_elements].index(name) - - def get_group_reference(self, name): - return ["{}/{}".format(e["class"], e["attributes"]["Name"]) for e in self.groups].index(name) - - def get_type_product_reference(self, name): - return [p["raw"].name for p in self.type_products].index(name) - - def get_ifc_class(self, name): - return name.split("/")[0] - - def get_ifc_name(self, name): - try: - return name.split("/")[1] - except IndexError: - self.ifc_export_settings.logger.error( - 'Name "{}" does not follow the format of "IfcClass/Name"'.format(name) - ) - - def get_name_attribute(self, obj): - name = obj.BIMObjectProperties.attributes.get("Name") - if name: - return name.string_value - return self.get_ifc_name(obj.name) - - def is_a_grid_axis(self, class_name): - return class_name == "IfcGridAxis" - - def is_a_spatial_structure_element(self, class_name): - return class_name in [ - "IfcBuilding", - "IfcBuildingStorey", - "IfcExternalSpatialElement", - "IfcSite", - "IfcSpace", - "IfcSpatialZone", - ] - - def is_a_rel_aggregates(self, class_name): - return class_name == "IfcRelAggregates" - - def is_a_project(self, class_name): - return class_name == "IfcProject" - - def is_a_library(self, class_name): - return class_name == "IfcProjectLibrary" - - def is_a_group(self, class_name): - return class_name in [g for g in schema.ifc.IfcGroup.keys()] - - def is_a_type(self, class_name): - return (class_name[0:3] == "Ifc" and class_name[-4:] == "Type") or ( - class_name[0:3] == "Ifc" and class_name[-5:] == "Style" - ) - - class IfcExporter: - def __init__(self, ifc_export_settings, ifc_parser): - self.template_file = "{}template.ifc".format(ifc_export_settings.schema_dir) + def __init__(self, ifc_export_settings): self.ifc_export_settings = ifc_export_settings - self.ifc_parser = ifc_parser - self.migrator = ifcopenshell.util.schema.Migrator() - self.roundtrip_id_new_to_old = {} - def export(self, selected_objects): - self.stored_file = ifc.IfcStore.get_file() # See bug #1222 - if self.stored_file and self.ifc_export_settings.should_export_from_memory: - self.file = self.stored_file - return self.write_ifc_file() - self.schema_version = self.ifc_export_settings.schema - self.schema = ifcopenshell.ifcopenshell_wrapper.schema_by_name(self.schema_version) - self.file = ifcopenshell.file(schema=self.schema_version) - self.ifc_parser.parse(selected_objects) - self.create_units() - self.create_people() - self.create_organisations() - self.create_origin() - self.create_owner_history() - self.set_header() - self.create_rep_context() - self.create_project() - self.create_library_information() - self.create_document_information() - self.create_document_references() - self.create_classifications() - self.create_classification_references() - self.create_constraints() - self.create_psets() - self.create_libraries() - self.create_map_conversion() - self.create_representations() - self.create_materials() - self.create_type_products() - self.create_spatial_structure_elements(self.ifc_parser.spatial_structure_elements_tree) - self.create_groups() - self.create_qtos() - self.create_grid_axes() - self.create_products() - self.create_styled_items() - self.create_presentation_layer_assignments() - self.relate_definitions_to_contexts() - self.relate_objects_to_objects() - self.relate_elements_to_spatial_structures() - self.relate_nested_elements_to_hosted_elements() - self.relate_objects_to_qtos() - self.relate_objects_to_psets() - self.relate_objects_to_opening_elements() - self.relate_opening_elements_to_fillings() - self.relate_objects_to_projection_elements() - self.relate_objects_to_materials() - for set_type in ["constituent", "layer", "profile"]: - self.relate_objects_to_material_sets(set_type) - self.relate_spaces_to_boundary_elements() - self.relate_to_documents(self.ifc_parser.rel_associates_document_object) - self.relate_to_documents(self.ifc_parser.rel_associates_document_type) - self.relate_to_classifications(self.ifc_parser.rel_associates_classification_object) - self.relate_to_classifications(self.ifc_parser.rel_associates_classification_type) - self.relate_to_constraints(self.ifc_parser.rel_associates_constraint_object) - self.relate_structural_members_to_connections() - self.relate_objects_to_groups() - self.write_ifc_file() - - def create_origin(self): - self.origin = self.file.createIfcAxis2Placement3D( - self.file.createIfcCartesianPoint((0.0, 0.0, 0.0)), - self.file.createIfcDirection((0.0, 0.0, 1.0)), - self.file.createIfcDirection((1.0, 0.0, 0.0)), - ) - - def set_header(self): - # TODO: add all metadata, pending bug #747 - self.file.wrapped_data.header.file_name.name = os.path.basename(self.ifc_export_settings.output_file) - self.file.wrapped_data.header.file_name.time_stamp = ( - datetime.datetime.utcnow() - .replace(tzinfo=datetime.timezone.utc) - .astimezone() - .replace(microsecond=0) - .isoformat() - ) - self.file.wrapped_data.header.file_name.preprocessor_version = "IfcOpenShell {}".format(ifcopenshell.version) - self.file.wrapped_data.header.file_name.originating_system = "{} {}".format( - self.get_application_name(), self.get_application_version() - ) - # TODO: reimplement. See #1222. - #if self.owner_history: - # if self.schema_version == "IFC2X3": - # self.file.wrapped_data.header.file_name.authorization = self.owner_history.OwningUser.ThePerson.Id - # else: - # self.file.wrapped_data.header.file_name.authorization = ( - # self.owner_history.OwningUser.ThePerson.Identification - # ) - #else: - # self.file.wrapped_data.header.file_name.authorization = "Nobody" - - def get_application_name(self): - return "BlenderBIM" - - def get_application_version(self): - return ".".join( - [ - str(x) - for x in [ - addon.bl_info.get("version", (-1, -1, -1)) - for addon in addon_utils.modules() - if addon.bl_info["name"] == "BlenderBIM" - ][0] - ] - ) - - def get_application_organisation(self): - self.application_organisation = self.file.create_entity( - "IfcOrganization", - **{ - "Name": "IfcOpenShell", - "Description": "IfcOpenShell is an open source (LGPL) software library that helps users and software developers to work with the IFC file format.", - "Roles": [ - self.file.create_entity("IfcActorRole", **{"Role": "USERDEFINED", "UserDefinedRole": "CONTRIBUTOR"}) - ], - "Addresses": [ - self.file.create_entity( - "IfcTelecomAddress", - **{ - "Purpose": "USERDEFINED", - "UserDefinedPurpose": "WEBPAGE", - "Description": "The main webpage of the software collection.", - "WWWHomePageURL": "https://ifcopenshell.org", - }, - ), - self.file.create_entity( - "IfcTelecomAddress", - **{ - "Purpose": "USERDEFINED", - "UserDefinedPurpose": "WEBPAGE", - "Description": "The BlenderBIM Add-on webpage of the software collection.", - "WWWHomePageURL": "https://blenderbim.org", - }, - ), - self.file.create_entity( - "IfcTelecomAddress", - **{ - "Purpose": "USERDEFINED", - "UserDefinedPurpose": "REPOSITORY", - "Description": "The source code repository of the software collection.", - "WWWHomePageURL": "https://github.com/IfcOpenShell/IfcOpenShell.git", - }, - ), - ], - }, - ) - return self.application_organisation - - def create_owner_history(self): - person = None - organisation = None - for person in self.ifc_parser.people: - if self.schema_version == "IFC2X3" and person["ifc"].Id == bpy.context.scene.BIMProperties.person: - break - elif person["ifc"].Identification == bpy.context.scene.BIMProperties.person: - break - for organisation in self.ifc_parser.organisations: - if organisation["ifc"].Name == bpy.context.scene.BIMProperties.organisation: - break - if not person or not organisation: - self.owner_history = None - return - person_and_organisation = self.file.create_entity( - "IfcPersonAndOrganization", - **{"ThePerson": person["ifc"], "TheOrganization": organisation["ifc"], "Roles": None}, # TODO - ) - developer_organisation = self.get_application_organisation() - application = self.file.create_entity( - "IfcApplication", - **{ - "ApplicationDeveloper": developer_organisation, - "Version": self.get_application_version(), - "ApplicationFullName": self.get_application_name(), - "ApplicationIdentifier": self.get_application_name(), - }, - ) - self.owner_history = self.file.create_entity( - "IfcOwnerHistory", - **{ - "OwningUser": person_and_organisation, - "OwningApplication": application, - "State": "READWRITE", - "ChangeAction": "NOCHANGE", - "LastModifiedDate": int(time.time()), - "LastModifyingUser": person_and_organisation, - "LastModifyingApplication": application, - "CreationDate": int(time.time()), # illegal, but better than nothing ... - }, - ) - - def create_units(self): - for unit_type, data in self.ifc_parser.units.items(): - if data["is_metric"]: - data["ifc"] = self.create_metric_unit(unit_type, data) - else: - data["ifc"] = self.create_imperial_unit(unit_type, data) - self.file.createIfcUnitAssignment([u["ifc"] for u in self.ifc_parser.units.values()]) - - def create_metric_unit(self, unit_type, data): - type_prefix = "" - if unit_type == "area": - type_prefix = "SQUARE_" - elif unit_type == "volume": - type_prefix = "CUBIC_" - return self.file.createIfcSIUnit( - None, - "{}UNIT".format(unit_type.upper()), - SIUnitHelper.get_prefix(data["raw"]), - type_prefix + SIUnitHelper.get_unit_name(data["raw"]), - ) - - def create_imperial_unit(self, unit_type, data): - if unit_type == "length": - dimensional_exponents = self.file.createIfcDimensionalExponents(1, 0, 0, 0, 0, 0, 0) - name_prefix = "" - elif unit_type == "area": - dimensional_exponents = self.file.createIfcDimensionalExponents(2, 0, 0, 0, 0, 0, 0) - name_prefix = "square" - elif unit_type == "volume": - dimensional_exponents = self.file.createIfcDimensionalExponents(3, 0, 0, 0, 0, 0, 0) - name_prefix = "cubic" - si_unit = self.file.createIfcSIUnit( - None, - "{}UNIT".format(unit_type.upper()), - None, - "{}METRE".format(name_prefix.upper() + "_" if name_prefix else ""), - ) - if data["raw"] == "INCHES": - name = "{}inch".format(name_prefix + " " if name_prefix else "") - elif data["raw"] == "FEET": - name = "{}foot".format(name_prefix + " " if name_prefix else "") - value_component = self.file.create_entity("IfcReal", **{"wrappedValue": SIUnitHelper.si_conversions[name]}) - conversion_factor = self.file.createIfcMeasureWithUnit(value_component, si_unit) - return self.file.createIfcConversionBasedUnit( - dimensional_exponents, "{}UNIT".format(unit_type.upper()), name, conversion_factor - ) - - def create_people(self): - for person in self.ifc_parser.people: - if person["roles"]: - person["attributes"]["Roles"] = self.create_roles(person["roles"]) - if person["addresses"]: - person["attributes"]["Addresses"] = self.create_addresses(person["addresses"]) - if self.schema_version == "IFC2X3" and "Identification" in person["attributes"]: - person["attributes"]["Id"] = person["attributes"]["Identification"] - del person["attributes"]["Identification"] - person["ifc"] = self.file.create_entity("IfcPerson", **person["attributes"]) - - def create_organisations(self): - for organisation in self.ifc_parser.organisations: - if organisation["roles"]: - organisation["attributes"]["Roles"] = self.create_roles(organisation["roles"]) - if organisation["addresses"]: - organisation["attributes"]["Addresses"] = self.create_addresses(organisation["addresses"]) - organisation["ifc"] = self.file.create_entity("IfcOrganization", **organisation["attributes"]) - - def create_roles(self, roles): - results = [] - for role in roles: - results.append(self.file.create_entity("IfcActorRole", **role["attributes"])) - return results - - def create_addresses(self, addresses): - results = [] - for address in addresses: - results.append(self.create_address(address)) - return results - - def create_address(self, address): - if self.schema_version == "IFC2X3" and "MessagingIDs" in address["attributes"]: - del address["attributes"]["MessagingIDs"] - return self.file.create_entity( - "IfcPostalAddress" if address["is_postal"] else "IfcTelecomAddress", **address["attributes"] - ) - - def create_library_information(self): - information = self.ifc_parser.library_information - if not information: - return - information["attributes"]["Publisher"] = self.owner_history.OwningUser - information["ifc"] = self.file.create_entity("IfcLibraryInformation", **information["attributes"]) - self.file.createIfcRelAssociatesLibrary( - ifcopenshell.guid.new(), - self.owner_history, - information["attributes"]["Name"], - information["attributes"]["Description"], - [self.ifc_parser.project["ifc"]], - information["ifc"], - ) - - def create_document_information(self): - for information in self.ifc_parser.document_information.values(): - information["ifc"] = self.file.create_entity("IfcDocumentInformation", **information["attributes"]) - - def create_document_references(self): - for reference in self.ifc_parser.document_references.values(): - if ( - reference["referenced_document"] - and reference["referenced_document"] in self.ifc_parser.document_information - ): - reference["attributes"]["ReferencedDocument"] = self.ifc_parser.document_information[ - reference["referenced_document"] - ]["ifc"] - reference["ifc"] = self.file.create_entity("IfcDocumentReference", **reference["attributes"]) - self.file.createIfcRelAssociatesDocument( - ifcopenshell.guid.new(), None, None, None, [self.ifc_parser.project["ifc"]], reference["ifc"] - ) - - def create_classifications(self): - for classification in self.ifc_parser.classifications.values(): - if self.file.schema == "IFC4": - classification["ifc"] = self.file.add(classification["raw_element"]) - else: - # TODO: Check if we can use self.migrator instead - migrator = ifcopenshell.util.schema.Migrator() - classification["ifc"] = migrator.migrate(classification["raw_element"], self.file) - self.file.createIfcRelAssociatesClassification( - ifcopenshell.guid.new(), - self.owner_history, - None, - None, - [self.ifc_parser.project["ifc"]], - classification["ifc"], - ) - - def create_classification_references(self): - for reference in self.ifc_parser.classification_references.values(): - if self.file.schema == "IFC4": - reference["ifc"] = self.file.add(reference["raw_element"]) - else: - # TODO: Check if we can use self.migrator instead - migrator = ifcopenshell.util.schema.Migrator() - reference["ifc"] = migrator.migrate(reference["raw_element"], self.file) - - def create_constraints(self): - for constraint in self.ifc_parser.constraints.values(): - constraint["ifc"] = self.file.create_entity("IfcObjective", **constraint["attributes"]) - - def create_psets(self): - for pset in self.ifc_parser.psets.values(): - properties = self.create_pset_properties(pset) - if not properties: - continue - pset["attributes"].update( - {"GlobalId": ifcopenshell.guid.new(), "OwnerHistory": self.owner_history, "HasProperties": properties} - ) - pset["ifc"] = self.file.create_entity("IfcPropertySet", **pset["attributes"]) - - def create_material_psets(self, material): - for pset in self.ifc_parser.material_psets.values(): - properties = self.create_pset_properties(pset) - if not properties: - continue - pset["attributes"].update({"Properties": properties, "Material": pset["material"]["ifc"]}) - pset["ifc"] = self.file.create_entity("IfcMaterialProperties", **pset["attributes"]) - - def create_qto_properties(self, qto): - qto_template = schema.ifc.psetqto.get_by_name(qto["attributes"]["Name"]) - if qto_template: - return self.create_templated_qto_properties(qto, qto_template) - return self.create_custom_qto_properties(qto) - - def create_pset_properties(self, pset): - pset_template = schema.ifc.psetqto.get_by_name(pset["attributes"]["Name"]) - if pset_template: - return self.create_templated_pset_properties(pset, pset_template) - return self.create_custom_pset_properties(pset) - - def create_custom_pset_properties(self, pset): - properties = [] - for key, value in pset["raw"].items(): - properties.append( - self.file.create_entity( - "IfcPropertySingleValue", - **{"Name": key, "NominalValue": self.file.create_entity("IfcLabel", value)}, - ) - ) - return properties - - def create_custom_qto_properties(self, qto): - properties = [] - for key, value in qto["raw"].items(): - if "Area" in key: - quantity_type = "Area" - elif "Volume" in key: - quantity_type = "Volume" - else: - quantity_type = "Length" - properties.append( - self.file.create_entity( - f"IfcQuantity{quantity_type}", **{"Name": key, f"{quantity_type}Value": float(value)} - ) - ) - return properties - - def create_templated_pset_properties(self, pset, pset_template): - properties = [] - for prop in pset_template.HasPropertyTemplates: - name = prop.Name - if name not in pset["raw"]: - continue - if prop.TemplateType == "P_SINGLEVALUE" or prop.TemplateType == "P_ENUMERATEDVALUE": - if prop.PrimaryMeasureType: - value_type = prop.PrimaryMeasureType - else: - # The IFC spec is missing some, so we provide a fallback - value_type = "IfcLabel" - nominal_value = self.file.create_entity( - value_type, self.cast_to_base_type(value_type, pset["raw"][name]) - ) - properties.append( - self.file.create_entity("IfcPropertySingleValue", **{"Name": name, "NominalValue": nominal_value}) - ) - templates_names = [prop.Name for prop in qto_template.HasPropertyTemplates] - invalid_pset_keys = [k for k in pset["raw"].keys() if k not in templates_names] - if invalid_pset_keys: - self.ifc_export_settings.logger.error( - "One or more properties were invalid in the pset {}: {}".format( - pset["attributes"]["Name"], invalid_pset_keys - ) - ) - return properties - - def create_templated_qto_properties(self, qto, qto_template): - properties = [] - for prop in qto_template.HasPropertyTemplates: - name = prop.Name - if name not in qto["raw"]: - continue - if prop.TemplateType[0:2] == "Q_": - value_basename = prop.TemplateType[2:].title() - value_name = f"{value_basename}Value" - class_name = f"IfcQuantity{value_basename}" - properties.append( - self.file.create_entity(class_name, **{"Name": name, value_name: float(qto["raw"][name])}) - ) - templates_names = [prop.Name for prop in qto_template.HasPropertyTemplates] - invalid_qto_keys = [k for k in qto["raw"].keys() if k not in templates_names] - if invalid_qto_keys: - self.ifc_export_settings.logger.error( - "One or more properties were invalid in the qto {}/{}: {}".format( - qto["attributes"]["Name"], qto["attributes"]["Description"], invalid_qto_keys - ) - ) - return properties - - def cast_to_base_type(self, var_type, value): - if var_type not in schema.ifc.type_map: - return value - elif schema.ifc.type_map[var_type] == "float": - return float(value) - elif schema.ifc.type_map[var_type] == "integer": - return int(value) - elif schema.ifc.type_map[var_type] == "bool": - return True if value.lower() in ["1", "t", "true", "yes", "y", "uh-huh"] else False - return str(value) - - def create_rep_context(self): - self.ifc_rep_context = {} - for context in self.ifc_export_settings.context_tree: - if context["name"] == "Model": - self.ifc_rep_context["Model"] = { - "ifc": self.file.createIfcGeometricRepresentationContext(None, "Model", 3, 1.0e-05, self.origin) - } - elif context["name"] == "Plan": - self.ifc_rep_context["Plan"] = { - "ifc": self.file.createIfcGeometricRepresentationContext(None, "Plan", 2, 1.0e-05, self.origin) - } - for subcontext in context["subcontexts"]: - self.ifc_rep_context[context["name"]][subcontext["name"]] = {} - for target_view in subcontext["target_views"]: - self.ifc_rep_context[context["name"]][subcontext["name"]][target_view] = { - "ifc": self.file.createIfcGeometricRepresentationSubContext( - subcontext["name"], - context["name"], - None, - None, - None, - None, - self.ifc_rep_context[context["name"]]["ifc"], - None, - target_view, - None, - ) - } - - def create_project(self): - self.ifc_parser.project["attributes"].update( - { - "RepresentationContexts": [c["ifc"] for c in self.ifc_rep_context.values()], - "UnitsInContext": self.file.by_type("IfcUnitAssignment")[0], - } - ) - self.ifc_parser.project["ifc"] = self.file.create_entity( - self.ifc_parser.project["class"], **self.ifc_parser.project["attributes"] - ) - - def create_libraries(self): - for library in self.ifc_parser.libraries: - library["ifc"] = self.file.create_entity(library["class"], **library["attributes"]) - libraries = [l["ifc"] for l in self.ifc_parser.libraries] - if libraries: - self.file.createIfcRelDeclares( - ifcopenshell.guid.new(), self.owner_history, None, None, self.ifc_parser.project["ifc"], libraries - ) - - def create_map_conversion(self): - if not self.ifc_parser.map_conversion: - return - self.create_target_crs() - # TODO should this be hardcoded? - self.ifc_parser.map_conversion["attributes"]["SourceCRS"] = self.ifc_rep_context["Model"]["ifc"] - self.ifc_parser.map_conversion["attributes"]["TargetCRS"] = self.ifc_parser.target_crs["ifc"] - self.ifc_parser.map_conversion["ifc"] = self.file.create_entity( - "IfcMapConversion", **self.ifc_parser.map_conversion["attributes"] - ) - - def create_target_crs(self): - for key, value in self.ifc_parser.target_crs["attributes"].items(): - if not self.ifc_parser.target_crs["attributes"][key]: - self.ifc_parser.target_crs["attributes"][key] = None - if self.ifc_parser.target_crs["attributes"]["MapUnit"]: - self.ifc_parser.target_crs["attributes"]["MapUnit"] = self.file.createIfcSIUnit( - None, - "LENGTHUNIT", - SIUnitHelper.get_prefix(self.ifc_parser.target_crs["attributes"]["MapUnit"]), - SIUnitHelper.get_unit_name(self.ifc_parser.target_crs["attributes"]["MapUnit"]), - ) - self.ifc_parser.target_crs["ifc"] = self.file.create_entity( - "IfcProjectedCRS", **self.ifc_parser.target_crs["attributes"] - ) - - def create_type_products(self): - for product in self.ifc_parser.type_products: - self.cast_attributes(product["class"], product["attributes"]) - - product["attributes"].update( - { - "OwnerHistory": self.owner_history, # TODO: unhardcode - "RepresentationMaps": self.get_product_shape(product), - } - ) - - # TODO: re-implement psets, relationships, door/window properties - - try: - product["ifc"] = self.file.create_entity(product["class"], **product["attributes"]) - except RuntimeError as e: - product["ifc"] = self.create_ifc_entity(product) - - def add_predefined_attributes_to_type_product(self, product, attributes): - self.create_predefined_attributes(attributes) - product["attributes"].setdefault("HasPropertySets", []) - for attribute in attributes: - product["attributes"]["HasPropertySets"].append(attribute["ifc"]) - - def create_predefined_attributes(self, attributes): - for attribute in attributes: - attribute["ifc"] = self.file.create_entity( - attribute["pset_name"], - **{k: float(v) if v.replace(".", "", 1).isdigit() else v for k, v in attribute["raw"].items()}, - ) - - def relate_definitions_to_contexts(self): - for library in self.ifc_parser.libraries: - self.file.createIfcRelDeclares( - ifcopenshell.guid.new(), - self.owner_history, - None, - None, - library["ifc"], - [self.ifc_parser.type_products[t]["ifc"] for t in library["rel_declares_type_products"]], - ) - - def relate_objects_to_objects(self): - for relating_object, related_objects_reference in self.ifc_parser.rel_aggregates.items(): - relating_object = self.ifc_parser.products[relating_object] - if related_objects_reference not in self.ifc_parser.aggregates: - continue - related_objects = [ - self.ifc_parser.products[o]["ifc"] for o in self.ifc_parser.aggregates[related_objects_reference] - ] - self.file.createIfcRelAggregates( - ifcopenshell.guid.new(), - self.owner_history, - relating_object["attributes"]["Name"], - None, - relating_object["ifc"], - related_objects, - ) - for obj in related_objects: - obj.ObjectPlacement.PlacementRelTo = relating_object["ifc"].ObjectPlacement - - def create_spatial_structure_elements(self, element_tree, relating_object=None): - if relating_object == None: - relating_object = self.ifc_parser.project["ifc"] - placement_rel_to = None - else: - placement_rel_to = relating_object.ObjectPlacement - - related_objects = [] - for node in element_tree: - element = self.ifc_parser.spatial_structure_elements[node["reference"]] - - if element["has_scale"]: - # Omission of the relative placement here is not as per implementer agreements - placement = self.file.createIfcLocalPlacement(None, self.origin) - else: - placement = self.file.createIfcLocalPlacement( - placement_rel_to, self.get_relative_placement(element, placement_rel_to) - ) - - self.cast_attributes(element["class"], element["attributes"]) - element["attributes"].update( - { - "OwnerHistory": self.owner_history, # TODO: unhardcode - "ObjectPlacement": placement, - "Representation": self.get_product_shape(element), - } - ) - - if element["class"] == "IfcSite": - element["attributes"].update({"SiteAddress": self.create_address(element["address"])}) - elif element["class"] == "IfcBuilding": - element["attributes"].update({"BuildingAddress": self.create_address(element["address"])}) - - element["ifc"] = self.file.create_entity(element["class"], **element["attributes"]) - related_objects.append(element["ifc"]) - self.create_spatial_structure_elements(node["children"], element["ifc"]) - if related_objects: - self.file.createIfcRelAggregates( - ifcopenshell.guid.new(), self.owner_history, None, None, relating_object, related_objects - ) - - def get_relative_placement(self, element, placement_rel_to): - if placement_rel_to: - relating_object_matrix = self.get_local_placement(placement_rel_to) - relating_object_matrix[0][3] = self.convert_unit_to_si(relating_object_matrix[0][3]) - relating_object_matrix[1][3] = self.convert_unit_to_si(relating_object_matrix[1][3]) - relating_object_matrix[2][3] = self.convert_unit_to_si(relating_object_matrix[2][3]) - else: - relating_object_matrix = Matrix() - z = Vector(element["up_axis"]) - x = Vector(element["forward_axis"]) - o = Vector(element["location"]) - object_matrix = self.a2p(o, z, x) - relative_placement_matrix = relating_object_matrix.inverted() @ object_matrix - return self.create_ifc_axis_2_placement_3d( - relative_placement_matrix.translation, - self.get_axis(relative_placement_matrix, 2), - self.get_axis(relative_placement_matrix, 0), - ) - - def get_axis(self, matrix, axis): - return matrix.col[axis].to_3d().normalized() - - def get_local_placement(self, plc): - if plc.PlacementRelTo is None: - parent = Matrix() - else: - parent = self.get_local_placement(plc.PlacementRelTo) - return parent @ self.get_axis2placement(plc.RelativePlacement) - - def a2p(self, o, z, x): - y = z.cross(x) - r = Matrix((x, y, z, o)) - r.resize_4x4() - r.transpose() - return r - - def get_axis2placement(self, plc): - z = Vector(plc.Axis.DirectionRatios if plc.Axis else (0, 0, 1)) - x = Vector(plc.RefDirection.DirectionRatios if plc.RefDirection else (1, 0, 0)) - o = plc.Location.Coordinates - return self.a2p(o, z, x) - - def create_groups(self): - for group in self.ifc_parser.groups: - group["ifc"] = self.file.create_entity(group["class"], **group["attributes"]) - self.file.createIfcRelDeclares( - ifcopenshell.guid.new(), self.owner_history, None, None, self.ifc_parser.project["ifc"], [group["ifc"]] - ) - - def create_styled_items(self): - for styled_item in self.ifc_parser.styled_items: - self.process_styled_item(styled_item) - - def process_styled_item(self, styled_item): - product = styled_item["related_element"] - - if not product["ifc"].Representation: - return - - material_slots = [] - # This is a simplification, which works since we are currently in a controlled environment where the - # BlenderBIM Add-on controls how data is structured during export. When we implement full IFC - # round-tripping, this simplification can no longer apply. - for representation in product["ifc"].Representation.Representations: - # At the moment, we assume that styled items only apply to the body context. - if representation.RepresentationIdentifier != "Body": - continue - rep = self.ifc_parser.get_shape_representation(product["raw"], "Model", "Body", "MODEL_VIEW") - if self.ifc_export_settings.should_roundtrip_native and rep and rep.ifc_definition_id: - # For native roundtripping, each slot could be a one to many relationship to items - for item in self.get_geometric_representation_items(representation): - original_id = self.roundtrip_id_new_to_old[item.id()] - i = product["raw"].data.BIMMeshProperties.ifc_item_ids.get(str(original_id)).slot_index - material_slots.append((product["raw"].material_slots[i].name, item)) - else: - # For Blender, each slot represents a geometric representation item - for i, item in enumerate(self.get_geometric_representation_items(representation)): - if i >= len(product["raw"].material_slots): - i = 0 - material_slots.append((product["raw"].material_slots[i].name, item)) - for styled_item_name, representation_item in material_slots: - if styled_item_name == styled_item["attributes"]["Name"]: - styled_item["ifc"] = self.create_styled_item(styled_item, representation_item) - - def get_geometric_representation_items(self, representation): - results = [] - for item in representation.Items: - if item.is_a("IfcGeometricRepresentationItem"): - results.append(item) - elif item.is_a("IfcMappedItem"): - results.extend(self.get_geometric_representation_items(item.MappingSource.MappedRepresentation)) - return results - - def create_styled_item(self, styled_item, representation_item=None): - surface_style = self.ifc_parser.surface_styles[styled_item["raw"].name] - if not surface_style["ifc"]: - styles = [] - styles.append(self.create_surface_style_rendering(styled_item)) - if styled_item["raw"].BIMMaterialProperties.is_external: - styles.append( - self.file.create_entity( - "IfcExternallyDefinedSurfaceStyle", **self.get_material_external_definition(styled_item["raw"]) - ) - ) - # Name is filled out because Revit treats this incorrectly as the material name - surface_style["ifc"] = self.file.createIfcSurfaceStyle(styled_item["attributes"]["Name"], "BOTH", styles) - if self.schema_version == "IFC2X3" or self.ifc_export_settings.should_use_presentation_style_assignment: - surface_style["ifc"] = self.file.createIfcPresentationStyleAssignment([surface_style["ifc"]]) - return self.file.createIfcStyledItem( - representation_item, [surface_style["ifc"]], styled_item["attributes"]["Name"] - ) - - def create_presentation_layer_assignments(self): - for layer_index, representations in self.ifc_parser.presentation_layer_assignments.items(): - layer = bpy.context.scene.BIMProperties.presentation_layers[int(layer_index)] - assigned_items = [] - for representation in representations: - assigned_items.append(representation["ifc"]) - if layer.layer_on: - self.file.createIfcPresentationLayerAssignment( - layer.name, layer.description or None, assigned_items, layer.identifier or None, - ) - else: - self.file.createIfcPresentationLayerWithStyle( - layer.name, - layer.description or None, - assigned_items, - layer.identifier or None, - layer.layer_on, - layer.layer_frozen, - layer.layer_blocked, - None, - ) - - def create_materials(self): - for material in self.ifc_parser.materials.values(): - styled_item = self.create_styled_item(material) - styled_representation = self.file.createIfcStyledRepresentation( - self.ifc_rep_context["Model"]["Body"]["MODEL_VIEW"]["ifc"], None, None, [styled_item] - ) - if self.schema_version == "IFC2X3": - material["ifc"] = self.file.createIfcMaterial(material["attributes"]["Name"]) - else: - material["ifc"] = self.file.create_entity("IfcMaterial", **material["attributes"]) - self.create_material_psets(material) - self.file.createIfcMaterialDefinitionRepresentation( - material["attributes"]["Name"], None, [styled_representation], material["ifc"] - ) - - def create_material_profile_def(self, profile): - ifc_class = profile.profile - attributes = {a.name: a.string_value for a in profile.profile_attributes} - self.cast_attributes(ifc_class, attributes) - return self.file.create_entity(ifc_class, **attributes) - - def cast_attributes(self, ifc_class, attributes): - for key, value in attributes.items(): - edge_case_attribute = self.cast_edge_case(ifc_class, key, value) - if edge_case_attribute: - attributes[key] = edge_case_attribute - continue - - complex_attribute = self.cast_complex_attribute(ifc_class, key, value) - if complex_attribute: - attributes[key] = complex_attribute - continue - - var_type = self.get_product_attribute_type(ifc_class, key) - if var_type is None: - continue - attributes[key] = self.cast_to_base_type(var_type, value) - - def cast_edge_case(self, ifc_class, key, value): - if key == "RefLatitude" or key == "RefLongitude": - return self.dd2dms(value) - - # TODO: migrate to ifcopenshell.util - def dd2dms(self, dd): - dd = float(dd) - sign = 1 if dd >= 0 else -1 - dd = abs(dd) - minutes, seconds = divmod(dd * 3600, 60) - degrees, minutes = divmod(minutes, 60) - if dd < 0: - degrees = -degrees - return (int(degrees) * sign, int(minutes) * sign, int(seconds) * sign) - - def create_surface_style_rendering(self, styled_item): - surface_colour = self.create_colour_rgb(styled_item["raw"].diffuse_color) - rendering_attributes = { - "SurfaceColour": surface_colour, - "Transparency": (styled_item["raw"].diffuse_color[3] - 1) * -1, - "ReflectanceMethod": "NOTDEFINED", - } - rendering_attributes.update(self.get_rendering_attributes(styled_item["raw"])) - return self.file.create_entity("IfcSurfaceStyleRendering", **rendering_attributes) - - def get_rendering_attributes(self, material): - if ( - not material.use_nodes - or not hasattr(material.node_tree, "nodes") - or "Principled BSDF" not in material.node_tree.nodes - ): - return {} - bsdf = material.node_tree.nodes["Principled BSDF"] - return { - "Transparency": (bsdf.inputs["Alpha"].default_value - 1) * -1, - "DiffuseColour": self.create_colour_rgb(bsdf.inputs["Base Color"].default_value), - } - - def get_material_external_definition(self, material): - return { - "Location": material.BIMMaterialProperties.location, - "Identification": material.BIMMaterialProperties.identification - if material.BIMMaterialProperties.identification - else material.name, - "Name": material.BIMMaterialProperties.name if material.BIMMaterialProperties.name else material.name, - } - - def create_colour_rgb(self, colour): - return self.file.createIfcColourRgb(None, colour[0], colour[1], colour[2]) - - def create_representations(self): - for representation in self.ifc_parser.representations.values(): - self.create_representation(representation) - - def create_grid_axes(self): - for uvw in self.ifc_parser.grid_axes.values(): - for axes in uvw.values(): - for axis in axes: - self.create_grid_axis(axis) - - def create_grid_axis(self, axis): - points = [ - axis["grid_raw"].matrix_world.inverted() @ (axis["raw"].matrix_world @ v.co) - for v in axis["raw"].data.vertices[0:2] - ] - self.cast_attributes("IfcGridAxis", axis["attributes"]) - axis["attributes"]["AxisCurve"] = self.file.createIfcPolyline( - [ - self.create_cartesian_point(points[0][0], points[0][1], points[0][2]), - self.create_cartesian_point(points[1][0], points[1][1], points[1][2]), - ] - ) - axis["ifc"] = self.file.create_entity("IfcGridAxis", **axis["attributes"]) - - def create_products(self): - for product in self.ifc_parser.products: - self.create_product(product) - - def create_qtos(self): - # TODO: re-introduce calculated quantities - for qto in self.ifc_parser.qtos.values(): - properties = self.create_qto_properties(qto) - if not properties: - continue - qto["attributes"].update( - {"GlobalId": ifcopenshell.guid.new(), "OwnerHistory": self.owner_history, "Quantities": properties} - ) - qto["ifc"] = self.file.create_entity("IfcElementQuantity", **qto["attributes"]) - - def create_product(self, product): - if self.schema.declaration_by_name(product["class"]).is_abstract(): - self.ifc_export_settings.logger.error( - 'The product "{}/{}" class is abstract and could not be created'.format( - product["class"], product["attributes"]["Name"] - ) - ) - return - - if product["relating_structure"] is not None: - placement_rel_to = self.ifc_parser.spatial_structure_elements[product["relating_structure"]][ - "ifc" - ].ObjectPlacement - elif product["relating_host"] is not None: - # TODO: this could be unsafe if the host is not yet created, so we - # should consider migrating it such that the placement rel to is set - # as the relationship creation stage, like how IfcRelAggregates for - # object aggregates work. - placement_rel_to = self.ifc_parser.products[product["relating_host"]]["ifc"].ObjectPlacement - else: - placement_rel_to = None - - if product["has_scale"]: - # Omission of the relative placement here is not as per implementer agreements - placement = self.file.createIfcLocalPlacement(None, self.origin) - else: - placement = self.file.createIfcLocalPlacement( - placement_rel_to, self.get_relative_placement(product, placement_rel_to) - ) - - self.cast_attributes(product["class"], product["attributes"]) - - product["attributes"].update( - { - "OwnerHistory": self.owner_history, # TODO: unhardcode - "ObjectPlacement": placement, - "Representation": self.get_product_shape(product), - } - ) - - if product["has_boundary_condition"]: - ifc_class = product["boundary_condition_class"] - attributes = product["boundary_condition_attributes"] - for key, value in attributes.items(): - if value == "True" or value == "False": - attributes[key] = bool(value) - else: - attributes[key] = float(value) - self.cast_attributes(ifc_class, attributes) - boundary_condition = self.file.create_entity(ifc_class, **attributes) - product["attributes"]["AppliedCondition"] = boundary_condition - - if product["class"] == "IfcGrid": - name = "IfcGrid/" + product["attributes"]["Name"] - product["attributes"]["UAxes"] = [a["ifc"] for a in self.ifc_parser.grid_axes[name]["UAxes"]] - product["attributes"]["VAxes"] = [a["ifc"] for a in self.ifc_parser.grid_axes[name]["VAxes"]] - if self.ifc_parser.grid_axes[name]["WAxes"]: - product["attributes"]["WAxes"] = [a["ifc"] for a in self.ifc_parser.grid_axes[name]["WAxes"]] - - try: - product["ifc"] = self.file.create_entity(product["class"], **product["attributes"]) - except RuntimeError as e: - product["ifc"] = self.create_ifc_entity(product) - - def create_ifc_entity(self, data): - result = self.file.create_entity(data["class"]) - for key, value in data["attributes"].items(): - try: - setattr(result, key, value) - except RuntimeError as e: - self.ifc_export_settings.logger.error( - 'The entity "{}/{}" attribute {} with value {} could not be created: {}'.format( - data["class"], data["attributes"]["Name"], key, value, e.args - ) - ) - return result - - def get_product_attribute_type(self, product_class, attribute_name): - element_schema = schema.ifc.elements[product_class] - for a in element_schema["attributes"]: - if a["name"] == attribute_name: - return a["type"] - if element_schema["parent"] in schema.ifc.elements: - return self.get_product_attribute_type(element_schema["parent"], attribute_name) - - def cast_complex_attribute(self, product_class, attribute_name, attribute_value): - element_schema = schema.ifc.elements[product_class] - for a in element_schema["complex_attributes"]: - if a["name"] == attribute_name: - if not a["is_select"]: - return a["type"] - for select_type in a["select_types"]: - try: - return self.file.create_entity(select_type, attribute_value) - except: - pass - - def get_product_shape(self, product): - try: - representations = self.get_product_shape_representations(product) - if representations: - return self.file.createIfcProductDefinitionShape(None, None, representations) - except: - pass - return None - - def get_product_shape_representations(self, product): - results = [] - for representation_name in product["representations"]: - representation = self.ifc_parser.representations[representation_name] - if self.ifc_export_settings.should_roundtrip_native and representation["has_ifc_definition"]: - pass - else: - self.get_product_mapped_geometry(product, representation) - results.append(representation["ifc"]) - return results - - def get_product_mapped_geometry(self, product, representation): - mapping_source = representation["ifc_map"] - shape_representation = mapping_source.MappedRepresentation - if product["has_scale"]: - if not product["has_mirror"]: - product["scale"] = Vector((abs(product["scale"].x), abs(product["scale"].y), abs(product["scale"].z))) - mapping_target = self.file.createIfcCartesianTransformationOperator3DnonUniform( - self.create_direction(product["forward_axis"]), - self.create_direction(product["right_axis"]), - self.create_cartesian_point(product["location"].x, product["location"].y, product["location"].z), - product["scale"].x, - self.create_direction(product["up_axis"]), - product["scale"].y, - product["scale"].z, - ) - else: - mapping_target = self.file.createIfcCartesianTransformationOperator3D( - self.create_direction(Vector((1, 0, 0))), - self.create_direction(Vector((0, 1, 0))), - self.create_cartesian_point(0, 0, 0), - 1, - self.create_direction(Vector((0, 0, 1))), - ) - mapped_item = self.file.createIfcMappedItem(mapping_source, mapping_target) - representation["ifc"] = self.file.createIfcShapeRepresentation( - shape_representation.ContextOfItems, - shape_representation.RepresentationIdentifier, - "MappedRepresentation", - [mapped_item], - ) - - def create_ifc_axis_2_placement_2d(self, point, forward): - return self.file.createIfcAxis2Placement2D( - self.create_cartesian_point(point.x, point.y), self.file.createIfcDirection((forward.x, forward.y)) - ) - - def create_ifc_axis_2_placement_3d(self, point, up, forward): - return self.file.createIfcAxis2Placement3D( - self.create_cartesian_point(point.x, point.y, point.z), - self.file.createIfcDirection((up.x, up.y, up.z)), - self.file.createIfcDirection((forward.x, forward.y, forward.z)), - ) - - def create_representation(self, representation): - if self.ifc_export_settings.should_roundtrip_native and representation["has_ifc_definition"]: - representation["ifc"] = self.create_representation_from_definition(representation) - return - self.ifc_vertices = [] - self.ifc_edges = [] - if representation["context"] == "Model": - representation["ifc_map"] = self.create_model_representation(representation) - elif representation["context"] == "Plan": - representation["ifc_map"] = self.create_plan_representation(representation) - elif representation["context"] == "NotDefined": - representation["ifc_map"] = self.create_variable_representation(representation) - - def create_representation_from_definition(self, representation): - if representation["ifc_definition"]: - print("Authoring an IFC definition directly is not yet implemented") - return - if representation["ifc_definition_id"]: - return self.create_representation_from_definition_id(representation) - - def create_representation_from_definition_id(self, representation): - if self.file.schema == ifc.IfcStore.get_file().schema: - entry = self.file.add(ifc.IfcStore.get_file().by_id(representation["ifc_definition_id"])) - else: - entry = self.migrator.migrate(ifc.IfcStore.get_file().by_id(representation["ifc_definition_id"]), self.file) - - substitutions = {"contexts": []} - - representation_elements = ifc.IfcStore.get_file().traverse( - ifc.IfcStore.get_file().by_id(representation["ifc_definition_id"]) - ) - - for element in representation_elements: - if self.file.schema == ifc.IfcStore.get_file().schema: - added_element = self.file.add(element) - else: - added_element = self.migrator.migrate(element, self.file) - if added_element.is_a("IfcGeometricRepresentationContext"): - substitutions["contexts"].append(added_element) - elif added_element.is_a("IfcGeometricRepresentationItem"): - self.roundtrip_id_new_to_old[added_element.id()] = element.id() - - for element in substitutions["contexts"]: - new_element = self.ifc_rep_context[representation["context"]][representation["subcontext"]][ - representation["target_view"] - ]["ifc"] - for inverse in self.file.get_inverse(element): - ifcopenshell.util.element.replace_attribute(inverse, element, new_element) - # TODO: Work out how and when to purge this - # self.file.remove(element) - return entry - - def create_model_representation(self, representation): - if representation["subcontext"] == "Annotation": - return self.file.createIfcRepresentationMap( - self.origin, self.create_geometric_set_representation(representation) - ) - elif representation["subcontext"] == "Axis": - return self.file.createIfcRepresentationMap(self.origin, self.create_curve3d_representation(representation)) - elif representation["subcontext"] == "Body": - return self.create_variable_representation(representation) - elif representation["subcontext"] == "Box": - return self.file.createIfcRepresentationMap(self.origin, self.create_box_representation(representation)) - elif representation["subcontext"] == "Clearance": - return self.create_variable_representation(representation) - elif representation["subcontext"] == "CoG": - return self.file.createIfcRepresentationMap(self.origin, self.create_cog_representation(representation)) - elif representation["subcontext"] == "FootPrint": - return self.create_variable_representation(representation) - elif representation["subcontext"] == "Reference": - if representation["target_view"] == "GRAPH_VIEW": - return self.file.createIfcRepresentationMap( - self.origin, self.create_structural_reference_representation(representation) - ) - elif representation["subcontext"] == "Profile": - return self.file.createIfcRepresentationMap(self.origin, self.create_curve3d_representation(representation)) - elif representation["subcontext"] == "SurveyPoints": - return self.file.createIfcRepresentationMap( - self.origin, self.create_geometric_curve_set_representation(representation) - ) - - def create_plan_representation(self, representation): - if representation["subcontext"] == "Annotation": - if representation["is_text"]: - shape_representation = self.create_text_representation(representation) - else: - shape_representation = self.create_geometric_curve_set_representation(representation, is_2d=True) - shape_representation.RepresentationType = "Annotation2D" - return self.file.createIfcRepresentationMap(self.origin, shape_representation) - elif representation["subcontext"] == "Axis": - return self.file.createIfcRepresentationMap(self.origin, self.create_curve2d_representation(representation)) - elif representation["subcontext"] == "Body": - pass - elif representation["subcontext"] == "Box": - pass - elif representation["subcontext"] == "Clearance": - pass - elif representation["subcontext"] == "CoG": - pass - elif representation["subcontext"] == "FootPrint": - if representation["target_view"] in ["PLAN_VIEW", "REFLECTED_PLAN_VIEW"]: - return self.file.createIfcRepresentationMap( - self.origin, self.create_geometric_curve_set_representation(representation, is_2d=True) - ) - elif representation["subcontext"] == "Reference": - pass - elif representation["subcontext"] == "Profile": - pass - elif representation["subcontext"] == "SurveyPoints": - pass - - def create_variable_representation(self, representation): - if representation["is_wireframe"]: - return self.file.createIfcRepresentationMap( - self.origin, self.create_wireframe_representation(representation) - ) - elif representation["is_curve"]: - return self.file.createIfcRepresentationMap(self.origin, self.create_curve_representation(representation)) - elif representation["is_native"]: - return self.file.createIfcRepresentationMap(self.origin, self.create_native_representation(representation)) - elif representation["is_swept_solid"]: - return self.file.createIfcRepresentationMap( - self.origin, self.create_swept_solid_representation(representation) - ) - elif representation["is_point_cloud"]: - return self.file.createIfcRepresentationMap( - self.origin, self.create_point_cloud_representation(representation) - ) - return self.file.createIfcRepresentationMap(self.origin, self.create_solid_representation(representation)) - - def create_box_representation(self, representation): - obj = representation["raw_object"] - bounding_box = self.file.createIfcBoundingBox( - self.create_cartesian_point(obj.bound_box[0][0], obj.bound_box[0][1], obj.bound_box[0][2]), - self.convert_si_to_unit(obj.dimensions[0]), - self.convert_si_to_unit(obj.dimensions[1]), - self.convert_si_to_unit(obj.dimensions[2]), - ) - return self.file.createIfcShapeRepresentation( - self.ifc_rep_context[representation["context"]][representation["subcontext"]][ - representation["target_view"] - ]["ifc"], - representation["subcontext"], - "BoundingBox", - [bounding_box], - ) - - def create_cog_representation(self, representation): - mesh = representation["raw"] - cog = self.create_cartesian_point(mesh.vertices[0].co.x, mesh.vertices[0].co.y, mesh.vertices[0].co.z) - return self.file.createIfcShapeRepresentation( - self.ifc_rep_context[representation["context"]][representation["subcontext"]][ - representation["target_view"] - ]["ifc"], - representation["subcontext"], - "BoundingBox", - [cog], - ) - - def create_text_representation(self, representation): - return self.file.createIfcShapeRepresentation( - self.ifc_rep_context[representation["context"]][representation["subcontext"]][ - representation["target_view"] - ]["ifc"], - representation["subcontext"], - "Annotation2D", - [self.create_text(representation["raw"])], - ) - - def create_wireframe_representation(self, representation): - return self.file.createIfcShapeRepresentation( - self.ifc_rep_context[representation["context"]][representation["subcontext"]][ - representation["target_view"] - ]["ifc"], - representation["subcontext"], - "Curve", - self.create_curves(representation["raw"]), - ) - - def create_geometric_set_representation(self, representation, is_2d=False): - geometric_curve_set = self.file.createIfcGeometricSet(self.create_curves(representation["raw"], is_2d=is_2d)) - return self.file.createIfcShapeRepresentation( - self.ifc_rep_context[representation["context"]][representation["subcontext"]][ - representation["target_view"] - ]["ifc"], - representation["subcontext"], - "GeometricSet", - [geometric_curve_set], - ) - - def create_geometric_curve_set_representation(self, representation, is_2d=False): - geometric_curve_set = self.file.createIfcGeometricCurveSet( - self.create_curves(representation["raw"], is_2d=is_2d) - ) - return self.file.createIfcShapeRepresentation( - self.ifc_rep_context[representation["context"]][representation["subcontext"]][ - representation["target_view"] - ]["ifc"], - representation["subcontext"], - "GeometricCurveSet", - [geometric_curve_set], - ) - - # https://medium.com/@behreajj/scripting-curves-in-blender-with-python-c487097efd13 - # https://blender.stackexchange.com/questions/30597/python-up-vector-math-for-curve - def bezier_tangent(self, pt0=Vector(), pt1=Vector(), pt2=Vector(), pt3=Vector(), step=0.5): - # Return early if step is out of bounds [0, 1]. - if step <= 0.0: - return pt1 - pt0 - if step >= 1.0: - return pt3 - pt2 - - # Find coefficients. - u = 1.0 - step - ut6 = u * step * 6.0 - tsq3 = step * step * 3.0 - usq3 = u * u * 3.0 - - # Find tangent and return. - return (pt1 - pt0) * usq3 + (pt2 - pt1) * ut6 + (pt3 - pt2) * tsq3 - - def create_curve3d_representation(self, representation): - return self.file.createIfcShapeRepresentation( - self.ifc_rep_context[representation["context"]][representation["subcontext"]][ - representation["target_view"] - ]["ifc"], - representation["subcontext"], - "Curve3D", - self.create_curves(representation["raw"]), - ) - - def create_curve2d_representation(self, representation): - return self.file.createIfcShapeRepresentation( - self.ifc_rep_context[representation["context"]][representation["subcontext"]][ - representation["target_view"] - ]["ifc"], - representation["subcontext"], - "Curve2D", - self.create_curves(representation["raw"], is_2d=True), - ) - - def create_structural_reference_representation(self, representation): - if representation["raw_object"].type == "EMPTY": - return self.file.createIfcTopologyRepresentation( - self.ifc_rep_context[representation["context"]][representation["subcontext"]][ - representation["target_view"] - ]["ifc"], - representation["subcontext"], - "Vertex", - [self.create_vertex_point(Vector((0, 0, 0)))], - ) - return self.file.createIfcTopologyRepresentation( - self.ifc_rep_context[representation["context"]][representation["subcontext"]][ - representation["target_view"] - ]["ifc"], - representation["subcontext"], - "Edge", - [self.create_edge(representation["raw"])], - ) - - def create_curve_representation(self, representation): - if representation["raw"].bevel_object: - swept_area_solids = self.create_extruded_area_solids(representation) - else: - swept_area_solids = self.create_swept_disk_solids(representation) - return self.file.createIfcShapeRepresentation( - self.ifc_rep_context[representation["context"]][representation["subcontext"]][ - representation["target_view"] - ]["ifc"], - representation["subcontext"], - "AdvancedSweptSolid", - swept_area_solids, - ) - - def create_swept_disk_solids(self, representation): - results = [] - radius = self.convert_si_to_unit(representation["raw"].bevel_depth) - start_param = representation["raw"].bevel_factor_start - end_param = representation["raw"].bevel_factor_end - directrixes = self.create_curves(representation["raw"]) - for directrix in directrixes: - results.append(self.file.createIfcSweptDiskSolid(directrix, radius, None, start_param, end_param)) - return results - - def create_extruded_area_solids(self, representation): - # TODO: support unclosed surfaces - swept_area = self.file.createIfcArbitraryClosedProfileDef( - "AREA", None, self.create_curves(representation["raw"].bevel_object.data)[0] - ) - if (representation["raw"].bevel_object.scale - Vector((1, 1, 1))).length > 0.01: - self.scale_ifc_representation(swept_area, representation["raw"].bevel_object.scale) - swept_area_solids = [] - for spline in representation["raw"].splines: - points = self.get_spline_points(spline) - if not points: - continue - # Intuitively, the direction below is reversed, but apparently - # Blender likes to extrude down (opposite of IFC) natively. - direction = (points[0].co - points[1].co).xyz - unit_direction = direction.normalized() - - # This can be used in the future when dealing with non vector curves - # curr_point = points[0] - # next_point = points[1] - # j_percent = 0 - # direction = self.bezier_tangent( - # pt0=curr_point.co, - # pt1=curr_point.handle_right, - # pt2=next_point.handle_left, - # pt3=next_point.co, - # step=j_percent) - tilt_matrix = Matrix.Rotation(points[0].tilt, 4, "Z") - x_axis = unit_direction.to_track_quat("-Y", "Z") @ Vector((1, 0, 0)) @ tilt_matrix - position = self.create_ifc_axis_2_placement_3d(points[1].co, unit_direction, x_axis) - swept_area_solids.append( - self.file.createIfcExtrudedAreaSolid( - swept_area, - position, - self.file.createIfcDirection((0.0, 0.0, 1.0)), - self.convert_si_to_unit(direction.length), - ) - ) - # TODO: support other types of swept areas - # swept_area_solid = self.file.createIfcFixedReferenceSweptAreaSolid( - # swept_area, self.origin, # self.create_curves(representation['raw'])[0], - # 0., 1., self.file.createIfcDirection((0.0, -1.0, 0.0))) - return swept_area_solids - - def scale_ifc_representation(self, rep, scale): - for element in self.file.traverse(rep): - if not element.is_a("IfcCartesianPoint"): - continue - element.Coordinates = tuple( - Vector(element.Coordinates) @ Matrix(((scale[0], 0, 0), (0, scale[1], 0), (0, 0, scale[2]))) - ) - - def create_vertex_point(self, point): - return self.file.createIfcVertexPoint(self.create_cartesian_point(point.x, point.y, point.z)) - - def get_spline_points(self, spline): - return spline.bezier_points if spline.bezier_points else spline.points - - def create_edge(self, curve): - if hasattr(curve, "splines"): - points = self.get_spline_points(curve.splines[0]) - else: - points = curve.vertices - if not points: - return - return self.file.createIfcEdge(self.create_vertex_point(points[0].co), self.create_vertex_point(points[1].co)) - - def create_text(self, text): - if text.align_y in ["TOP_BASELINE", "BOTTOM_BASELINE", "BOTTOM"]: - y = "bottom" - elif text.align_y == "CENTER": - y = "middle" - elif text.align_y == "TOP": - y = "top" - - if text.align_x == "LEFT": - x = "left" - elif text.align_x == "CENTER": - x = "middle" - elif text.align_x == "RIGHT": - x = "right" - - # TODO: Planar extent right now is wrong ... - return self.file.createIfcTextLiteralWithExtent( - text.body, self.origin, "RIGHT", self.file.createIfcPlanarExtent(1000, 1000), f"{y}-{x}" - ) - - def create_curves(self, curve, is_2d=False): - if isinstance(curve, bpy.types.Mesh): - return self.create_curves_from_mesh(curve, is_2d=is_2d) - elif isinstance(curve, bpy.types.Curve): - return self.create_curves_from_curve(curve, is_2d=is_2d) - - def create_curves_from_mesh(self, mesh, is_2d=False): - curves = [] - points = self.create_cartesian_point_list_from_vertices(mesh.vertices, is_2d=is_2d) - edge_loops = [] - previous_edge = None - edge_loop = [] - for edge in mesh.edges: - if (Vector(points.CoordList[edge.vertices[0]]) - Vector(points.CoordList[edge.vertices[1]])).length < 0.001: - # Maybe we should warn the user to weld vertices in this scenario? - continue - elif previous_edge is None: - edge_loop = [self.file.createIfcLineIndex((edge.vertices[0] + 1, edge.vertices[1] + 1))] - elif edge.vertices[0] == previous_edge.vertices[1]: - edge_loop.append(self.file.createIfcLineIndex((edge.vertices[0] + 1, edge.vertices[1] + 1))) - else: - edge_loops.append(edge_loop) - edge_loop = [self.file.createIfcLineIndex((edge.vertices[0] + 1, edge.vertices[1] + 1))] - previous_edge = edge - edge_loops.append(edge_loop) - for edge_loop in edge_loops: - curves.append(self.file.createIfcIndexedPolyCurve(points, edge_loop)) - return curves - - def create_curves_from_curve(self, curve, is_2d=False): - results = [] - for spline in curve.splines: - # TODO: support interpolated curves, not just polylines - points = [] - for point in spline.bezier_points: - if is_2d: - points.append(self.create_cartesian_point(point.co.x, point.co.y)) - else: - points.append(self.create_cartesian_point(point.co.x, point.co.y, point.co.z)) - for point in spline.points: - if is_2d: - points.append(self.create_cartesian_point(point.co.x, point.co.y)) - else: - points.append(self.create_cartesian_point(point.co.x, point.co.y, point.co.z)) - if spline.use_cyclic_u: - points.append(points[0]) - results.append(self.file.createIfcPolyline(points)) - return results - - def create_native_representation(self, representation): - obj = representation["raw_object"] - items = {} - for index, vg in enumerate(obj.vertex_groups): - components = vg.name.split("/") - key = components[1] - if components[0] == "Item": - items[key] = {"name": components[2], "subitems": {}} - elif components[0] == "Subitem": - items[key]["subitems"][components[2]] = self.get_vertices_in_vertex_group(obj, index) - ifc_items = [] - for item in items.values(): - if item["name"] == "IfcExtrudedAreaSolid": - ifc_items.append(self.create_native_extruded_area_solid(obj, item)) - elif item["name"] == "IfcFacetedBrep": - # TODO: check if we allow representation item type mixing - return self.create_solid_representation(representation) - return self.file.createIfcShapeRepresentation( - self.ifc_rep_context[representation["context"]][representation["subcontext"]][ - representation["target_view"] - ]["ifc"], - representation["subcontext"], - "SweptSolid", - ifc_items, - ) - - def get_vertices_in_vertex_group(self, obj, vg_index): - return [v.index for v in obj.data.vertices if vg_index in [g.group for g in v.groups]] - - def create_native_extruded_area_solid(self, obj, item): - extrusion_edge = self.get_edges_in_v_indices(obj, item["subitems"]["ExtrudedDirection"])[0] - - if "IfcArbitraryClosedProfileDef" in item["subitems"]: - outer_curve_loop = self.get_loop_from_v_indices(obj, item["subitems"]["IfcArbitraryClosedProfileDef"]) - curve_ucs = self.get_curve_profile_coordinate_system(obj, outer_curve_loop) - outer_curve = self.create_polyline_from_loop(obj, outer_curve_loop, curve_ucs) - curve = self.file.createIfcArbitraryClosedProfileDef("AREA", None, outer_curve) - elif "IfcRectangleProfileDef" in item["subitems"]: - outer_curve_loop = self.get_loop_from_v_indices(obj, item["subitems"]["IfcRectangleProfileDef"]) - curve_ucs = self.get_curve_profile_coordinate_system(obj, outer_curve_loop) - xdim = self.convert_si_to_unit( - (obj.data.vertices[outer_curve_loop[0]].co - obj.data.vertices[outer_curve_loop[1]].co).length - ) - ydim = self.convert_si_to_unit( - (obj.data.vertices[outer_curve_loop[1]].co - obj.data.vertices[outer_curve_loop[2]].co).length - ) - curve = self.file.createIfcRectangleProfileDef("AREA", None, None, xdim, ydim) - elif "IfcCircleProfileDef" in item["subitems"]: - indices = item["subitems"]["IfcCircleProfileDef"] - outer_curve_loop = self.get_loop_from_v_indices(obj, indices) - curve_ucs = self.get_curve_profile_coordinate_system(obj, outer_curve_loop) - radius = self.convert_si_to_unit( - abs((obj.data.vertices[indices[0]].co - obj.data.vertices[indices[int(len(indices) / 2)]].co).length) - / 2 - ) - center = Vector((0, 0)) - position = self.create_ifc_axis_2_placement_2d(center, Vector((1, 0))) - curve = self.file.createIfcCircleProfileDef("AREA", None, position, radius) - - position = self.create_ifc_axis_2_placement_3d(curve_ucs["center"], curve_ucs["z_axis"], curve_ucs["x_axis"]) - direction = self.get_extrusion_direction(obj, outer_curve_loop, extrusion_edge, curve_ucs) - unit_direction = direction.normalized() - - return self.file.createIfcExtrudedAreaSolid( - curve, - position, - self.file.createIfcDirection((unit_direction.x, unit_direction.y, unit_direction.z)), - self.convert_si_to_unit(direction.length), - ) - - def create_swept_solid_representation(self, representation): - # TODO: deprecate this in favour of native representations - obj = representation["raw_object"] - mesh = representation["raw"] - items = [] - for swept_solid in mesh.BIMMeshProperties.swept_solids: - extrusion_edge = self.get_edges_in_v_indices(obj, json.loads(swept_solid.extrusion))[0] - - inner_curves = [] - if swept_solid.inner_curves: - for indices in json.loads(swept_solid.inner_curves): - loop = self.get_loop_from_v_indices(obj, indices) - curve_ucs = self.get_curve_profile_coordinate_system(obj, loop) - inner_curves.append(self.create_polyline_from_loop(obj, loop, curve_ucs)) - - outer_curve_loop = self.get_loop_from_v_indices(obj, json.loads(swept_solid.outer_curve)) - curve_ucs = self.get_curve_profile_coordinate_system(obj, outer_curve_loop) - outer_curve = self.create_polyline_from_loop(obj, outer_curve_loop, curve_ucs) - - if inner_curves: - curve = self.file.createIfcArbitraryProfileDefWithVoids("AREA", None, outer_curve, inner_curves) - else: - curve = self.file.createIfcArbitraryClosedProfileDef("AREA", None, outer_curve) - - direction = self.get_extrusion_direction(obj, outer_curve_loop, extrusion_edge, curve_ucs) - unit_direction = direction.normalized() - position = self.create_ifc_axis_2_placement_3d( - curve_ucs["center"], curve_ucs["z_axis"], curve_ucs["x_axis"] - ) - - items.append( - self.file.createIfcExtrudedAreaSolid( - curve, - position, - self.file.createIfcDirection((unit_direction.x, unit_direction.y, unit_direction.z)), - self.convert_si_to_unit(direction.length), - ) - ) - return self.file.createIfcShapeRepresentation( - self.ifc_rep_context[representation["context"]][representation["subcontext"]][ - representation["target_view"] - ]["ifc"], - representation["subcontext"], - "SweptSolid", - items, - ) - - def get_start_and_end_of_extrusion(self, profile_points, extrusion_edge): - if extrusion_edge.vertices[0] in profile_points: - return (extrusion_edge.vertices[0], extrusion_edge.vertices[1]) - return (extrusion_edge.vertices[1], extrusion_edge.vertices[0]) - - def get_curve_profile_coordinate_system(self, obj, loop): - profile_face = bpy.data.meshes.new("profile_face") - profile_verts = [ - (obj.data.vertices[p].co.x, obj.data.vertices[p].co.y, obj.data.vertices[p].co.z) for p in loop - ] - profile_faces = [tuple(range(0, len(profile_verts)))] - profile_face.from_pydata(profile_verts, [], profile_faces) - center = profile_face.polygons[0].center - if (obj.data.vertices[loop[1]].co - obj.data.vertices[loop[0]].co).length < 0.01: - x_axis = (obj.data.vertices[loop[0]].co - center).normalized() - else: - x_axis = (obj.data.vertices[loop[1]].co - obj.data.vertices[loop[0]].co).normalized() - z_axis = profile_face.polygons[0].normal.normalized() - y_axis = z_axis.cross(x_axis).normalized() - matrix = Matrix((x_axis, y_axis, z_axis)) - matrix.normalize() - return { - "center": center, - "x_axis": x_axis, - "y_axis": y_axis, - "z_axis": z_axis, - "matrix": matrix.to_4x4() @ Matrix.Translation(-center), - } - - def create_polyline_from_loop(self, obj, loop, curve_ucs): - points = [] - for point in loop: - transformed_point = curve_ucs["matrix"] @ obj.data.vertices[point].co - points.append(self.create_cartesian_point(transformed_point.x, transformed_point.y)) - points.append(points[0]) - return self.file.createIfcPolyline(points) - - def get_extrusion_direction(self, obj, outer_curve_loop, extrusion_edge, curve_ucs): - start, end = self.get_start_and_end_of_extrusion(outer_curve_loop, extrusion_edge) - return curve_ucs["matrix"] @ (curve_ucs["center"] + (obj.data.vertices[end].co - obj.data.vertices[start].co)) - - def get_loop_from_v_indices(self, obj, indices): - edges = self.get_edges_in_v_indices(obj, indices) - loop = self.get_loop_from_edges(edges) - loop.pop(-1) - return loop - - def get_edges_in_v_indices(self, obj, indices): - return [e for e in obj.data.edges if (e.vertices[0] in indices and e.vertices[1] in indices)] - - def get_loop_from_edges(self, edges): - while edges: - currentEdge = edges.pop() - startVert = currentEdge.vertices[0] - endVert = currentEdge.vertices[1] - polyLine = [startVert, endVert] - ok = 1 - while ok: - ok = 0 - i = len(edges) - while i: - i -= 1 - ed = edges[i] - if ed.vertices[0] == endVert: - polyLine.append(ed.vertices[1]) - endVert = polyLine[-1] - ok = 1 - del edges[i] - elif ed.vertices[1] == endVert: - polyLine.append(ed.vertices[0]) - endVert = polyLine[-1] - ok = 1 - del edges[i] - elif ed.vertices[0] == startVert: - polyLine.insert(0, ed.vertices[1]) - startVert = polyLine[0] - ok = 1 - del edges[i] - elif ed.vertices[1] == startVert: - polyLine.insert(0, ed.vertices[0]) - startVert = polyLine[0] - ok = 1 - del edges[i] - return polyLine - - def create_point_cloud_representation(self, representation): - import space_view3d_point_cloud_visualizer as pcv - - if representation["raw"].uuid not in pcv.PCVManager.cache: - return - return self.file.createIfcShapeRepresentation( - self.ifc_rep_context[representation["context"]][representation["subcontext"]][ - representation["target_view"] - ]["ifc"], - representation["subcontext"], - "PointCloud", - [ - self.file.createIfcCartesianPointList3D( - pcv.PCVManager.cache[representation["raw"].uuid]["points"].tolist() - ) - ], - ) - - def create_solid_representation(self, representation): - mesh = representation["raw"] - if not representation["is_parametric"]: - mesh = representation["raw_object"].evaluated_get(bpy.context.evaluated_depsgraph_get()).to_mesh() - if self.ifc_export_settings.should_force_triangulation: - mesh = representation["raw_object"].evaluated_get(bpy.context.evaluated_depsgraph_get()).to_mesh() - bm = bmesh.new() - bm.from_mesh(mesh) - bmesh.ops.triangulate(bm, faces=bm.faces) - bm.to_mesh(mesh) - bm.free() - del bm - if self.schema_version == "IFC2X3" or self.ifc_export_settings.should_force_faceted_brep: - return self.create_faceted_brep(representation, mesh) - return self.create_polygonal_face_set(representation, mesh) - - def create_polygonal_face_set(self, representation, mesh): - n_slots = max(1, len(representation["raw_object"].material_slots)) - ifc_raw_items = [None] * n_slots - for i, value in enumerate(ifc_raw_items): - ifc_raw_items[i] = [] - for polygon in mesh.polygons: - ifc_raw_items[polygon.material_index % n_slots].append( - self.file.createIfcIndexedPolygonalFace([v + 1 for v in polygon.vertices]) - ) - coordinates = self.file.createIfcCartesianPointList3D([self.convert_si_to_unit(v.co) for v in mesh.vertices]) - items = [self.file.createIfcPolygonalFaceSet(coordinates, None, i) for i in ifc_raw_items if i] - return self.file.createIfcShapeRepresentation( - self.ifc_rep_context[representation["context"]][representation["subcontext"]][ - representation["target_view"] - ]["ifc"], - representation["subcontext"], - "Tessellation", - items, - ) - - def create_faceted_brep(self, representation, mesh): - self.create_vertices(mesh.vertices) - n_slots = max(1, len(representation["raw_object"].material_slots)) - ifc_raw_items = [None] * n_slots - for i, value in enumerate(ifc_raw_items): - ifc_raw_items[i] = [] - for polygon in mesh.polygons: - ifc_raw_items[polygon.material_index % n_slots].append( - self.file.createIfcFace( - [ - self.file.createIfcFaceOuterBound( - self.file.createIfcPolyLoop([self.ifc_vertices[vertice] for vertice in polygon.vertices]), - True, - ) - ] - ) - ) - # TODO: May not actually be a closed shell, but who checks anyway? - items = [self.file.createIfcFacetedBrep(self.file.createIfcClosedShell(i)) for i in ifc_raw_items if i] - return self.file.createIfcShapeRepresentation( - self.ifc_rep_context[representation["context"]][representation["subcontext"]][ - representation["target_view"] - ]["ifc"], - representation["subcontext"], - "Brep", - items, - ) - - def create_cartesian_point_list_from_vertices(self, vertices, is_2d=False): - if is_2d: - return self.file.createIfcCartesianPointList2D([self.convert_si_to_unit(v.co.xy) for v in vertices]) - return self.file.createIfcCartesianPointList3D([self.convert_si_to_unit(v.co) for v in vertices]) - - def create_vertices(self, vertices, is_2d=False): - if is_2d: - for v in vertices: - co = self.convert_si_to_unit(v.co) - self.ifc_vertices.append(self.file.createIfcCartesianPoint((co[0], co[1]))) - else: - self.ifc_vertices.extend( - [self.file.createIfcCartesianPoint(self.convert_si_to_unit(v.co)) for v in vertices] - ) - - def create_cartesian_point(self, x, y, z=None): - x = self.convert_si_to_unit(x) - y = self.convert_si_to_unit(y) - if z is None: - return self.file.createIfcCartesianPoint((x, y)) - z = self.convert_si_to_unit(z) - return self.file.createIfcCartesianPoint((x, y, z)) - - def create_direction(self, vector): - return self.file.createIfcDirection((vector.x, vector.y, vector.z)) - - def relate_objects_to_opening_elements(self): - for relating_building_element, related_opening_elements in self.ifc_parser.rel_voids_elements.items(): - for related_opening_element in related_opening_elements: - self.file.createIfcRelVoidsElement( - ifcopenshell.guid.new(), - self.owner_history, - None, - None, - self.ifc_parser.products[relating_building_element]["ifc"], - self.ifc_parser.products[related_opening_element]["ifc"], - ) - - def relate_opening_elements_to_fillings(self): - for relating_opening_element, related_building_elements in self.ifc_parser.rel_fills_elements.items(): - for related_building_element in related_building_elements: - self.file.createIfcRelFillsElement( - ifcopenshell.guid.new(), - self.owner_history, - None, - None, - self.ifc_parser.products[relating_opening_element]["ifc"], - self.ifc_parser.products[related_building_element]["ifc"], - ) - - def relate_objects_to_projection_elements(self): - for relating_building_element, related_projection_elements in self.ifc_parser.rel_projects_elements.items(): - for related_projection_element in related_projection_elements: - self.file.createIfcRelProjectsElement( - ifcopenshell.guid.new(), - self.owner_history, - None, - None, - self.ifc_parser.products[relating_building_element]["ifc"], - self.ifc_parser.products[related_projection_element]["ifc"], - ) - - def relate_elements_to_spatial_structures(self): - for relating_structure, related_elements in self.ifc_parser.rel_contained_in_spatial_structure.items(): - self.file.createIfcRelContainedInSpatialStructure( - ifcopenshell.guid.new(), - self.owner_history, - None, - None, - [self.ifc_parser.products[e]["ifc"] for e in related_elements], - self.ifc_parser.spatial_structure_elements[relating_structure]["ifc"], - ) - - def relate_nested_elements_to_hosted_elements(self): - for relating_object, related_objects in self.ifc_parser.rel_nests.items(): - self.file.createIfcRelNests( - ifcopenshell.guid.new(), - self.owner_history, - None, - None, - self.ifc_parser.products[relating_object]["ifc"], - [o["ifc"] for o in related_objects], - ) - - def relate_objects_to_qtos(self): - for relating_property_key, related_objects in self.ifc_parser.rel_defines_by_qto.items(): - self.file.createIfcRelDefinesByProperties( - ifcopenshell.guid.new(), - self.owner_history, - None, - None, - [o["ifc"] for o in related_objects], - self.ifc_parser.qtos[relating_property_key]["ifc"], - ) - - def relate_objects_to_psets(self): - for relating_property_key, related_objects in self.ifc_parser.rel_defines_by_pset.items(): - if self.ifc_parser.psets[relating_property_key]["ifc"]: - self.file.createIfcRelDefinesByProperties( - ifcopenshell.guid.new(), - self.owner_history, - None, - None, - [o["ifc"] for o in related_objects], - self.ifc_parser.psets[relating_property_key]["ifc"], - ) - - def relate_objects_to_materials(self): - if not self.ifc_export_settings.has_representations: - return - for relating_material_key, related_objects in self.ifc_parser.rel_associates_material.items(): - self.file.createIfcRelAssociatesMaterial( - ifcopenshell.guid.new(), - self.owner_history, - None, - None, - [o["ifc"] for o in related_objects], - self.ifc_parser.materials[relating_material_key]["ifc"], - ) - - def relate_objects_to_material_sets(self, set_type): - if not self.ifc_export_settings.has_representations: - return - if self.file.schema == "IFC2X3": - return self.relate_objects_to_material_sets_ifc2x3(set_type) - for material_set, product in getattr(self.ifc_parser, f"rel_associates_material_{set_type}_set"): - if set_type == "constituent": - materials = self.create_material_constituents(material_set.material_constituents) - elif set_type == "layer": - materials = self.create_material_layers(material_set.material_layers) - elif set_type == "profile": - materials = self.create_material_profiles(material_set.material_profiles) - if not materials: - continue - - attributes = { - f"Material{set_type.capitalize()}s": materials, - "Description": material_set.description or None, - } - - if set_type == "layer": - attributes["LayerSetName"] = material_set.name or None - else: - attributes["Name"] = material_set.name or None - - material_set = self.file.create_entity(f"IfcMaterial{set_type.capitalize()}Set", **attributes) - self.file.createIfcRelAssociatesMaterial( - ifcopenshell.guid.new(), - self.owner_history, - None, - None, - [product["ifc"]], - material_set, - ) - - def relate_objects_to_material_sets_ifc2x3(self, set_type): - # IFC2X3 has a very different way of handling materials, so we have a dedicated function - for material_set, product in getattr(self.ifc_parser, f"rel_associates_material_{set_type}_set"): - if set_type == "constituent": - # IFC2X3 only supports lists, so we gracefully downgrade - material_select = self.file.create_entity( - "IfcMaterialList", **{"Materials": self.create_material_list(material_set.material_constituents)} - ) - elif set_type == "layer": - material_select = self.file.create_entity( - "IfcMaterialLayerSet", - **{ - "MaterialLayers": self.create_material_layers(material_set.material_layers), - "LayerSetName": material_set.name or None, - }, - ) - elif set_type == "profile": - material_select = None # Not supported in IFC2X3 - if not material_select: - continue - self.file.createIfcRelAssociatesMaterial( - ifcopenshell.guid.new(), - self.owner_history, - None, - None, - [product["ifc"]], - material_select, - ) - - def create_material_layers(self, layers): - results = [] - for layer in layers: - if layer.category == "None": - category = None - elif layer.category == "Custom": - category = layer.custom_category or None - else: - category = layer.category - is_ventilated = layer.is_ventilated == "TRUE" if layer.is_ventilated != "UNKNOWN" else None - - attributes = { - "Material": self.ifc_parser.materials[layer.material.name]["ifc"] or None, - "LayerThickness": layer.layer_thickness, - "IsVentilated": is_ventilated, - "Name": layer.name or None, - "Description": layer.description or None, - "Category": category, - "Priority": layer.priority, - } - - if self.file.schema == "IFC2X3": - del attributes["Name"] - del attributes["Description"] - del attributes["Category"] - del attributes["Priority"] - - results.append(self.file.create_entity("IfcMaterialLayer", **attributes)) - return results - - def create_material_constituents(self, constituents): - results = [] - # TODO: the correlation for IfcShapeAspect is not yet implemented - for constituent in constituents: - results.append( - self.file.create_entity( - "IfcMaterialConstituent", - **{ - "Name": constituent.name or None, - "Description": constituent.description or None, - "Material": self.ifc_parser.materials[constituent.material.name]["ifc"], - "Fraction": constituent.fraction or None, - "Category": constituent.category or None, - }, - ) - ) - return results - - def create_material_list(self, materials): - return [self.ifc_parser.materials[m.material.name]["ifc"] for m in materials] - - def create_material_profiles(self, profiles): - results = [] - for profile in profiles: - results.append( - self.file.create_entity( - "IfcMaterialProfile", - **{ - "Name": profile.name or None, - "Description": profile.description or None, - "Material": self.ifc_parser.materials[profile.material.name]["ifc"], - "Profile": self.create_material_profile_def(profile), - "Priority": profile.priority, - "Category": profile.category or None, - }, - ) - ) - return results - - def relate_spaces_to_boundary_elements(self): - for (relating_space_index, relationships,) in self.ifc_parser.rel_space_boundaries.items(): - for relationship in relationships: - relationship["attributes"]["GlobalId"] = ifcopenshell.guid.new() - relationship["attributes"]["RelatedBuildingElement"] = self.ifc_parser.products[ - self.ifc_parser.get_product_index_from_raw_name(relationship["related_building_element_raw_name"]) - ]["ifc"] - relationship["attributes"]["RelatingSpace"] = self.ifc_parser.products[relating_space_index]["ifc"] - relationship["attributes"]["ConnectionGeometry"] = self.create_connection_geometry( - self.ifc_parser.products[relating_space_index], relationship["connection_geometry_face_index"] - ) - self.file.create_entity(relationship["class"], **relationship["attributes"]) - - def create_connection_geometry(self, product, face_index): - mesh = product["raw"].data - polygon = mesh.polygons[int(face_index)] - vertex_on_polygon = mesh.vertices[polygon.vertices[0]].co - center = polygon.center - normal = polygon.normal - forward = center - vertex_on_polygon - return self.file.createIfcFaceSurface( - [ - self.file.createIfcFaceOuterBound( - self.file.createIfcPolyLoop( - [ - self.create_cartesian_point( - mesh.vertices[vertice].co.x, mesh.vertices[vertice].co.y, mesh.vertices[vertice].co.z - ) - for vertice in polygon.vertices - ] - ), - True, - ) - ], - self.file.createIfcPlane( - self.file.createIfcAxis2Placement3D( - self.create_cartesian_point(center.x, center.y, center.z), - self.file.createIfcDirection((normal.x, normal.y, normal.z)), - self.file.createIfcDirection((forward.x, forward.y, forward.z)), - ) - ), - True, - ) - - def relate_to_documents(self, relationships): - for relating_document_key, related_objects in relationships.items(): - self.file.createIfcRelAssociatesDocument( - ifcopenshell.guid.new(), - self.owner_history, - None, - None, - [o["ifc"] for o in related_objects], - self.ifc_parser.document_references[relating_document_key]["ifc"], - ) - - def relate_to_classifications(self, relationships): - for relating_key, related_objects in relationships.items(): - self.file.createIfcRelAssociatesClassification( - ifcopenshell.guid.new(), - self.owner_history, - None, - None, - [o["ifc"] for o in related_objects], - self.ifc_parser.classification_references[relating_key]["ifc"], - ) - - def relate_to_constraints(self, relationships): - for relating_key, related_objects in relationships.items(): - self.file.createIfcRelAssociatesConstraint( - ifcopenshell.guid.new(), - self.owner_history, - None, - None, - [o["ifc"] for o in related_objects], - None, - self.ifc_parser.constraints[relating_key]["ifc"], - ) - - def relate_structural_members_to_connections(self): - for relating_member, relating_connection in self.ifc_parser.rel_connects_structural_member.items(): - self.file.create_entity( - "IfcRelConnectsStructuralMember", - **{ - "RelatingStructuralMember": self.ifc_parser.products[relating_member]["ifc"], - "RelatedStructuralConnection": self.ifc_parser.products[relating_connection]["ifc"], - }, - ) - - def relate_objects_to_groups(self): - for relating_group, related_objects in self.ifc_parser.rel_assigns_to_group.items(): - self.file.createIfcRelAssignsToGroup( - ifcopenshell.guid.new(), - self.owner_history, - None, - None, - [self.ifc_parser.products[o]["ifc"] for o in related_objects], - None, - self.ifc_parser.groups[relating_group]["ifc"], - ) - - def convert_si_to_unit(self, co): - return co / self.ifc_parser.unit_scale - - def convert_unit_to_si(self, co): - return co * self.ifc_parser.unit_scale - - def write_ifc_file(self): + def export(self): + self.file = IfcStore.get_file() self.set_header() extension = self.ifc_export_settings.output_file.split(".")[-1] if extension == "ifczip": @@ -3474,44 +41,51 @@ class IfcExporter: with open(self.ifc_export_settings.output_file, "w") as outfile: json.dump(jsonData, outfile, indent=None if self.ifc_export_settings.json_compact else 4) + def set_header(self): + # TODO: add all metadata, pending bug #747 + self.file.wrapped_data.header.file_name.name = os.path.basename(self.ifc_export_settings.output_file) + self.file.wrapped_data.header.file_name.time_stamp = ( + datetime.datetime.utcnow() + .replace(tzinfo=datetime.timezone.utc) + .astimezone() + .replace(microsecond=0) + .isoformat() + ) + self.file.wrapped_data.header.file_name.preprocessor_version = "IfcOpenShell {}".format(ifcopenshell.version) + self.file.wrapped_data.header.file_name.originating_system = "{} {}".format( + self.get_application_name(), self.get_application_version() + ) + # TODO: reimplement. See #1222. + # if self.owner_history: + # if self.schema_version == "IFC2X3": + # self.file.wrapped_data.header.file_name.authorization = self.owner_history.OwningUser.ThePerson.Id + # else: + # self.file.wrapped_data.header.file_name.authorization = ( + # self.owner_history.OwningUser.ThePerson.Identification + # ) + # else: + # self.file.wrapped_data.header.file_name.authorization = "Nobody" + + def get_application_name(self): + return "BlenderBIM" + + def get_application_version(self): + return ".".join( + [ + str(x) + for x in [ + addon.bl_info.get("version", (-1, -1, -1)) + for addon in addon_utils.modules() + if addon.bl_info["name"] == "BlenderBIM" + ][0] + ] + ) + class IfcExportSettings: def __init__(self): self.logger = None - self.schema_dir = None - self.data_dir = None self.output_file = None - self.has_representations = True - self.has_quantities = True - self.contexts = ["Model", "Plan"] - self.subcontexts = [ - "Annotation", - "Axis", - "Box", - "FootPrint", - "Reference", - "Body", - "Clearance", - "CoG", - "Profile", - "SurveyPoints", - ] - self.schema_version = "IFC4" - self.target_views = [ - "GRAPH_VIEW", - "SKETCH_VIEW", - "MODEL_VIEW", - "PLAN_VIEW", - "REFLECTED_PLAN_VIEW", - "SECTION_VIEW", - "ELEVATION_VIEW", - "USERDEFINED", - "NOTDEFINED", - ] - self.should_use_presentation_style_assignment = False - self.should_guess_quantities = False - self.should_export_from_memory = False - self.context_tree = [] @staticmethod def factory(context, output_file, logger): @@ -3519,17 +93,4 @@ class IfcExportSettings: settings = IfcExportSettings() settings.output_file = output_file settings.logger = logger - settings.data_dir = scene_bim.data_dir - settings.schema_dir = scene_bim.schema_dir - settings.has_representations = scene_bim.export_has_representations - settings.json_version = scene_bim.export_json_version - settings.json_compact = scene_bim.export_json_compact - settings.schema = scene_bim.export_schema - settings.should_use_presentation_style_assignment = scene_bim.export_should_use_presentation_style_assignment - settings.should_guess_quantities = scene_bim.export_should_guess_quantities - settings.should_force_faceted_brep = scene_bim.export_should_force_faceted_brep - settings.should_force_triangulation = scene_bim.export_should_force_triangulation - settings.should_roundtrip_native = scene_bim.import_export_should_roundtrip_native - settings.should_export_from_memory = scene_bim.export_should_export_from_memory - settings.context_tree = [] return settings diff --git a/src/ifcblenderexport/blenderbim/bim/import_ifc.py b/src/ifcblenderexport/blenderbim/bim/import_ifc.py index 605cebe288..f30847f97b 100644 --- a/src/ifcblenderexport/blenderbim/bim/import_ifc.py +++ b/src/ifcblenderexport/blenderbim/bim/import_ifc.py @@ -51,9 +51,7 @@ class MaterialCreator: hasattr(element, "RepresentationMaps") and not element.RepresentationMaps ): return - if not self.mesh: - return - if self.mesh.name in self.parsed_meshes: + if not self.mesh or self.mesh.name in self.parsed_meshes: return self.parsed_meshes.add(self.mesh.name) if self.parse_representations(element): @@ -97,10 +95,9 @@ class MaterialCreator: return True style = bpy.data.materials.get(style_name) - if not style: style = bpy.data.materials.new(style_name) - self.parse_styled_item(styled_item, style) + self.parse_styled_item(styled_item, style) self.assign_style_to_mesh(style) item_id.slot_index = len(self.mesh.materials) - 1 @@ -281,26 +278,19 @@ class IfcImporter: self.diff = None self.file = None self.settings = ifcopenshell.geom.settings() + self.settings.set(self.settings.DISABLE_OPENING_SUBTRACTIONS, True) self.settings.set_deflection_tolerance(self.ifc_import_settings.deflection_tolerance) - # Uncomment this when the latest IfcOpenBot build is ready - # self.settings.set_angular_tolerance(self.ifc_import_settings.angular_tolerance) + self.settings.set_angular_tolerance(self.ifc_import_settings.angular_tolerance) if self.ifc_import_settings.should_import_curves: self.settings.set(self.settings.INCLUDE_CURVES, True) self.settings_native = ifcopenshell.geom.settings() self.settings_native.set(self.settings_native.INCLUDE_CURVES, True) - if self.ifc_import_settings.should_import_native: - self.settings.set(self.settings.DISABLE_OPENING_SUBTRACTIONS, True) - self.ifc_import_settings.should_import_opening_elements = True - if self.ifc_import_settings.should_roundtrip_native: - self.settings.set(self.settings.DISABLE_OPENING_SUBTRACTIONS, True) - self.ifc_import_settings.should_import_opening_elements = True self.settings_2d = ifcopenshell.geom.settings() self.settings_2d.set(self.settings_2d.INCLUDE_CURVES, True) self.existing_elements = {} self.include_elements = [] self.exclude_elements = [] self.project = None - self.classifications = {} self.spatial_structure_elements = {} self.elements = {} self.type_collection = None @@ -313,15 +303,12 @@ class IfcImporter: self.added_data = {} self.native_elements = {} self.native_data = {} - self.groups = {} self.aggregates = {} self.aggregate_collections = {} self.material_creator = MaterialCreator(ifc_import_settings, self) def profile_code(self, message): - if not self.ifc_import_settings.should_import_with_profiling: - return if not self.time: self.time = time.time() print("{} :: {:.2f}".format(message, time.time() - self.time)) @@ -350,44 +337,30 @@ class IfcImporter: self.profile_code("Set units") self.create_project() self.profile_code("Create project") - self.create_classifications() - self.profile_code("Create classifications") - self.create_constraints() - self.profile_code("Create constraints") - self.create_document_information() - self.profile_code("Create doc info") - self.create_document_references() - self.profile_code("Create doc refs") self.create_spatial_hierarchy() self.profile_code("Create spatial hierarchy") self.create_type_products() self.profile_code("Create type products") - if self.ifc_import_settings.should_import_aggregates: - self.create_aggregates() - self.profile_code("Create aggregates") + self.create_aggregates() + self.profile_code("Create aggregates") self.create_openings_collection() self.profile_code("Create opening collection") self.process_element_filter() self.profile_code("Process element filter") - if self.ifc_import_settings.should_import_native: - self.parse_native_elements() - self.profile_code("Parsing native elements") - self.create_groups() - self.profile_code("Creating groups") + # TODO: Deprecate + #self.parse_native_elements() + #self.profile_code("Parsing native elements") self.create_grids() self.profile_code("Creating grids") - if self.ifc_import_settings.should_import_native: - self.create_native_products() - self.profile_code("Creating native products") + # TODO: Deprecate + #self.create_native_products() + #self.profile_code("Creating native products") self.create_products() self.profile_code("Creating meshified products") self.relate_openings() self.profile_code("Relating openings") self.place_objects_in_spatial_tree() self.profile_code("Placing objects in spatial tree") - if self.ifc_import_settings.should_merge_aggregates: - self.merge_aggregates() - self.profile_code("Merging aggregates") if self.ifc_import_settings.should_merge_by_class: self.merge_by_class() self.profile_code("Merging by class") @@ -399,8 +372,6 @@ class IfcImporter: ): self.merge_materials_by_colour() self.profile_code("Merging by colour") - self.create_presentation_layers() - self.profile_code("Create presentation layers") self.add_project_to_scene() self.profile_code("Add project to scene") if self.ifc_import_settings.should_clean_mesh and len(self.file.by_type("IfcElement")) < 1000: @@ -609,27 +580,6 @@ class IfcImporter: results.extend(self.find_decomposed_ifc_class(part, ifc_class)) return results - def create_groups(self): - group_collection = None - for collection in self.project["blender"].children: - if collection.name == "Groups": - group_collection = collection - break - if group_collection is None: - group_collection = bpy.data.collections.new("Groups") - self.project["blender"].children.link(group_collection) - for element in self.file.by_type("IfcGroup"): - self.create_group(element, group_collection) - - def create_group(self, element, group_collection): - if element.GlobalId in self.existing_elements: - obj = self.existing_elements[element.GlobalId] - else: - obj = bpy.data.objects.new(f"{element.is_a()}/{element.Name}", None) - obj.BIMObjectProperties.ifc_definition_id = element.id() - group_collection.objects.link(obj) - self.groups[element.GlobalId] = {"ifc": element, "blender": obj} - def create_grids(self): grids = self.file.by_type("IfcGrid") for grid in grids: @@ -662,7 +612,7 @@ class IfcImporter: shape = ifcopenshell.geom.create_shape(self.settings_2d, axis.AxisCurve) mesh = self.create_mesh(axis, shape) obj = bpy.data.objects.new(f"IfcGridAxis/{axis.AxisTag}", mesh) - obj.BIMObjectProperties.ifc_definition_id = element.id() + obj.BIMObjectProperties.ifc_definition_id = axis.id() obj.matrix_world = matrix_world grid.objects.link(obj) @@ -698,8 +648,6 @@ class IfcImporter: obj = bpy.data.objects.new(self.get_name(element), mesh) obj.BIMObjectProperties.ifc_definition_id = element.id() self.material_creator.create(element, obj, mesh) - self.add_element_classifications(element, obj) - self.add_element_document_relations(element, obj) self.type_collection.objects.link(obj) self.type_products[element.GlobalId] = obj @@ -776,9 +724,6 @@ class IfcImporter: if element is None: return - if not self.ifc_import_settings.should_import_opening_elements and element.is_a("IfcOpeningElement"): - return - if not self.ifc_import_settings.should_import_spaces and element.is_a("IfcSpace"): return @@ -818,9 +763,6 @@ class IfcImporter: elif hasattr(element, "ObjectPlacement"): obj.matrix_world = self.apply_blender_offset_to_matrix(self.get_element_matrix(element)) - self.add_element_representation_items(element, obj) - self.add_element_classifications(element, obj) - self.add_element_document_relations(element, obj) self.add_opening_relation(element, obj) self.added_data[element.GlobalId] = obj @@ -828,27 +770,6 @@ class IfcImporter: obj.display_type = "WIRE" return obj - def add_element_representation_items(self, element, obj): - if not obj.data or "ios_items" not in obj.data: - return - cumulative_vertex_index = 0 - for i, item in enumerate(obj.data["ios_items"]): - vg = obj.vertex_groups.new(name=f"Item/{i}/" + item["name"]) - vg.add( - [ - v.index - for v in obj.data.vertices[ - cumulative_vertex_index : cumulative_vertex_index + item["total_vertices"] - ] - ], - 1, - "ADD", - ) - for subitem in item["subitems"]: - vg = obj.vertex_groups.new(name=f"Subitem/{i}/" + subitem["name"]) - vg.add([v + cumulative_vertex_index for v in subitem["vertices"]], 1, "ADD") - cumulative_vertex_index += item["total_vertices"] - def create_native_mesh(self, element, shape): # TODO This should be split off into its own module for run-time native mesh conversion data = self.native_elements[element.GlobalId] @@ -1071,37 +992,6 @@ class IfcImporter: bm.faces.ensure_lookup_table() return bm - def merge_aggregates(self): - self.merge_objects_inside_aggregates() - self.convert_aggregate_instances_to_object() - - def merge_objects_inside_aggregates(self): - global_ids_to_delete = [] - for collection in self.aggregate_collections.values(): - obs = [] - for i, ob in enumerate(collection.objects): - if ob.type == "MESH": - if i > 0: - global_ids_to_delete.append(ob.BIMObjectProperties.attributes.get("GlobalId").string_value) - obs.append(ob) - ctx = {} - ctx["active_object"] = obs[0] - ctx["selected_editable_objects"] = obs - if obs[0].data.users > 1: - obs[0].data = obs[0].data.copy() - bpy.ops.object.join(ctx) - - for global_id in global_ids_to_delete: - del self.added_data[global_id] - - def convert_aggregate_instances_to_object(self): - for obj in self.aggregates.values(): - aggregate = obj.instance_collection.objects[0] - obj.users_collection[0].objects.link(aggregate) - aggregate.name = obj.name - bpy.data.collections.remove(obj.instance_collection) - bpy.data.objects.remove(obj) - def merge_by_class(self): merge_set = {} for obj in self.added_data.values(): @@ -1159,31 +1049,6 @@ class IfcImporter: project_collection.children[self.opening_collection.name].hide_viewport = True project_collection.children[self.type_collection.name].hide_viewport = True - def create_presentation_layers(self): - for assignment in self.file.by_type("IfcPresentationLayerAssignment"): - layer = bpy.context.scene.BIMProperties.presentation_layers.add() - layer_index = len(bpy.context.scene.BIMProperties.presentation_layers) - 1 - layer.name = assignment.Name - layer.description = assignment.Description or "" - layer.identifier = assignment.Identifier or "" - if assignment.is_a() == "IfcPresentationLayerWithStyle": - layer.layer_on = assignment.LayerOn if assignment.LayerOn is not None else True - layer.layer_frozen = assignment.LayerFrozen if assignment.LayerFrozen is not None else False - layer.layer_blocked = assignment.LayerBlocked if assignment.LayerBlocked is not None else False - - for item in assignment.AssignedItems: - # TODO: This is a simplified implementation of assigning presentation layers that ignores assigned - # representation items, does not consider mapped representations, and assumes a Body context. See #1109. - if not hasattr(item, "OfProductRepresentation") or item.RepresentationIdentifier != "Body": - continue - for product_representation in item.OfProductRepresentation: - for product in product_representation.ShapeOfProduct: - try: - obj = self.added_data[product.GlobalId] - obj.data.BIMMeshProperties.presentation_layer_index = layer_index - except: - pass # Occurs for example in opening elements or exclusions - def clean_mesh(self): obj = None last_obj = None @@ -1207,9 +1072,10 @@ class IfcImporter: self.openings[element.GlobalId] = obj def load_existing_rooted_elements(self): + # TODO: consider how this impacts file reloading, for now we assume you only ever load the same file again for obj in bpy.data.objects: - if hasattr(obj, "BIMObjectProperties") and obj.BIMObjectProperties.attributes.get("GlobalId"): - self.existing_elements[obj.BIMObjectProperties.attributes.get("GlobalId").string_value] = obj + if hasattr(obj, "BIMObjectProperties") and obj.BIMObjectProperties.ifc_definition_id: + self.existing_elements[self.file.by_id(obj.BIMObjectProperties.ifc_definition_id).GlobalId] = obj def load_diff(self): if not self.ifc_import_settings.diff_file: @@ -1290,146 +1156,6 @@ class IfcImporter: self.project["blender"].objects.link(obj) del self.added_data[self.project["ifc"].GlobalId] - def create_classifications(self): - for element in self.file.by_type("IfcClassification"): - classification = bpy.context.scene.BIMProperties.classifications.add() - data_map = { - "name": "Name", - "source": "Source", - "edition": "Edition", - "edition_date": "EditionDate", - "description": "Description", - "location": "Location", - "reference_tokens": "ReferenceTokens", - } - for key, value in data_map.items(): - if hasattr(element, value) and getattr(element, value): - setattr(classification, key, str(getattr(element, value))) - classification_file = ifcopenshell.file() - - # IFC2X3 has no references, so let's manually add them - if self.file.wrapped_data.schema == "IFC2X3": - if element.EditionDate: - edition_date = "{}-{}-{}".format( - element.EditionDate.YearComponent, - element.EditionDate.MonthComponent, - element.EditionDate.DayComponent, - ) - else: - edition_date = None - classification_element = classification_file.createIfcClassification( - element.Source, element.Edition, edition_date, element.Name - ) - for reference in self.file.by_type("IfcClassificationReference"): - classification_file.createIfcClassificationReference( - reference.Location, reference.ItemReference, reference.Name, classification_element - ) - else: - references = [element] - while references: - entities_to_add = references - references = self.get_classification_references(references) - for entity in entities_to_add: - classification_file.add(entity) - - classification.data = classification_file.to_string() - self.classifications[classification.name] = classification - - self.schema_dir = bpy.context.scene.BIMProperties.schema_dir - from . import prop - - prop.classification_enum.clear() - prop.getClassifications(self, bpy.context) - - def get_classification_references(self, references): - results = [] - for reference in references: - results.extend(self.file.get_inverse(reference)) - return results - - def create_constraints(self): - for element in self.file.by_type("IfcObjective"): - constraint = bpy.context.scene.BIMProperties.constraints.add() - data_map = { - "name": "Name", - "description": "Description", - "constraint_grade": "ConstraintGrade", - "constraint_source": "ConstraintSource", - "user_defined_grade": "UserDefinedGrade", - "objective_qualifier": "ObjectiveQualifier", - "user_defined_qualifier": "UserDefinedQualifier", - } - for key, value in data_map.items(): - if hasattr(element, value) and getattr(element, value): - setattr(constraint, key, getattr(element, value)) - - def create_document_information(self): - for element in self.file.by_type("IfcDocumentInformation"): - info = bpy.context.scene.BIMProperties.document_information.add() - data_map = { - "name": "Identification", - "human_name": "Name", - "description": "Description", - "location": "Location", - "purpose": "Purpose", - "intended_use": "IntendedUse", - "scope": "Scope", - "revision": "Revision", - "creation_time": "CreationTime", - "last_revision_time": "LastRevisionTime", - "electronic_format": "ElectronicFormat", - "valid_from": "ValidFrom", - "valid_until": "ValidUntil", - "confidentiality": "Confidentiality", - "status": "Status", - } - if self.file.schema == "IFC2X3": - data_map["name"] = "DocumentId" - for key, value in data_map.items(): - if hasattr(element, value) and getattr(element, value): - element_value = getattr(element, value) - if self.file.schema == "IFC2X3" and isinstance(element_value, ifcopenshell.entity_instance): - if element_value.is_a("IfcDateAndTime"): - element_value = self.convert_ifc_date_and_time_to_string(element_value) - elif element_value.is_a("IfcDocumentElectronicFormat"): - element_value = self.convert_ifc_document_electronic_format(element_value) - setattr(info, key, element_value) - - # TODO Maybe a candidate for ifcopenshell.util? - def convert_ifc_date_and_time_to_string(self, element): - return datetime( - element.DateComponent.YearComponent, - element.DateComponent.MonthComponent, - element.DateComponent.DayComponent, - element.TimeComponent.HourComponent, - element.TimeComponent.MinuteComponent if element.TimeComponent.MinuteComponent else 0, - int(element.TimeComponent.SecondComponent) if element.TimeComponent.SecondComponent else 0, - ).isoformat() - - def convert_ifc_document_electronic_format(self, element): - if not element.MimeContentType or not element.MimeSubtype: - return "" - return "{}/{}".format(element.MimeContentType, element.MimeSubtype) - - def create_document_references(self): - for element in self.file.by_type("IfcDocumentReference"): - reference = bpy.context.scene.BIMProperties.document_references.add() - data_map = { - "name": "Identification", - "human_name": "Name", - "location": "Location", - "description": "Description", - } - for key, value in data_map.items(): - if hasattr(element, value) and getattr(element, value): - setattr(reference, key, getattr(element, value)) - if self.file.schema == "IFC2X3": - if element.ReferenceToDocument: - reference.referenced_document = element.ReferenceToDocument[0].DocumentId - else: - if element.ReferencedDocument: - reference.referenced_document = element.ReferencedDocument.Identification - def create_spatial_hierarchy(self): if self.project["ifc"].IsDecomposedBy: for rel_aggregate in self.project["ifc"].IsDecomposedBy: @@ -1485,8 +1211,6 @@ class IfcImporter: obj.users_collection[0].objects.unlink(obj) collection.objects.link(obj) - self.add_element_classifications(element, obj) - self.add_element_document_relations(element, obj) self.aggregates[element.GlobalId] = obj self.aggregate_collections[rel_aggregate.id()] = collection @@ -1509,21 +1233,6 @@ class IfcImporter: objects_to_purge.append(obj) bpy.ops.object.delete({"selected_objects": objects_to_purge}) - def add_element_document_relations(self, element, obj): - for association in element.HasAssociations: - if association.is_a("IfcRelAssociatesDocument"): - reference = obj.BIMObjectProperties.document_references.add() - data_map = { - "name": "Identification", - "human_name": "Name", - "description": "Description", - "location": "Location", - } - attributes = {} - for key, value in data_map.items(): - if hasattr(association.RelatingDocument, value) and getattr(association.RelatingDocument, value): - setattr(reference, key, getattr(association.RelatingDocument, value)) - def relate_openings(self): for global_id, opening in self.openings.items(): building_element_global_id = self.file.by_guid(global_id).VoidsElements[0].RelatingBuildingElement.GlobalId @@ -1575,10 +1284,8 @@ class IfcImporter: # since it does not have a collection within an IfcSpace if not collection: return self.place_object_in_spatial_tree(element.Decomposes[0].RelatingObject, obj) - elif self.ifc_import_settings.should_import_aggregates: - collection = self.aggregate_collections[element.Decomposes[0].id()] else: - return self.place_object_in_spatial_tree(element.Decomposes[0].RelatingObject, obj) + collection = self.aggregate_collections[element.Decomposes[0].id()] if collection: collection.objects.link(obj) else: @@ -1594,37 +1301,6 @@ class IfcImporter: return ifcopenshell.util.geolocation.dms2dd(*value) return value - def add_element_classifications(self, element, obj): - if not element.HasAssociations: - return - for association in element.HasAssociations: - if not association.is_a("IfcRelAssociatesClassification"): - continue - data = association.RelatingClassification - reference = obj.BIMObjectProperties.classifications.add() - data_map = { - "name": "Identification", - "location": "Location", - "human_name": "Name", - "description": "Description", - "sort": "Sort", - } - if self.file.schema == "IFC2X3": - data_map["name"] = "ItemReference" - for key, value in data_map.items(): - if hasattr(data, value) and getattr(data, value): - setattr(reference, key, getattr(data, value)) - if hasattr(data, "ReferencedSource") and data.ReferencedSource: - reference.referenced_source = self.get_referenced_source_name(data.ReferencedSource) - - def get_referenced_source_name(self, element): - if not hasattr(element, "ReferencedSource") or not element.ReferencedSource: - if element.is_a("IfcClassification"): - return element.Name - else: - return element.Identification - return self.get_referenced_source_name(element.ReferencedSource) - def get_element_matrix(self, element, mesh_name=None): result = ifcopenshell.util.placement.get_local_placement(element.ObjectPlacement) result[0][3] *= self.unit_scale @@ -1700,8 +1376,10 @@ class IfcImporter: representation_id = int(re.sub(r"\D", "", representation_id.split("-")[0])) else: representation_id = int(re.sub(r"\D", "", representation_id)) + representation = self.file.by_id(representation_id) + context_id = representation.ContextOfItems.id() if hasattr(representation, "ContextOfItems") else 0 mesh = bpy.data.meshes.new( - "{}/{}".format(self.file.by_id(representation_id).ContextOfItems.id(), geometry.id) + "{}/{}".format(context_id, geometry.id) ) props = bpy.context.scene.BIMGeoreferenceProperties @@ -1866,55 +1544,31 @@ class IfcImportSettings: def __init__(self): self.logger = None self.input_file = None - self.should_auto_set_workarounds = True - self.should_ignore_site_coordinates = False - self.should_ignore_building_coordinates = False - self.should_merge_materials_by_colour = False + self.diff_file = None self.should_import_type_representations = False self.should_import_curves = False - self.should_import_opening_elements = False self.should_import_spaces = False - self.should_use_cpu_multiprocessing = False - self.should_import_with_profiling = False - self.should_import_native = False - self.should_merge_aggregates = False + self.should_auto_set_workarounds = True + self.should_use_cpu_multiprocessing = True self.should_merge_by_class = False self.should_merge_by_material = False - self.should_import_aggregates = True + self.should_merge_materials_by_colour = False self.should_clean_mesh = True - self.diff_file = None + self.deflection_tolerance = 0.001 + self.angular_tolerance = 0.5 + self.should_allow_non_element_aggregates = False + self.should_offset_model = False + self.model_offset_coordinates = (0, 0, 0) + self.ifc_import_filter = "NONE" + self.ifc_selector = "" + @staticmethod def factory(context, input_file, logger): scene_bim = context.scene.BIMProperties + scene_diff = context.scene.DiffProperties settings = IfcImportSettings() settings.input_file = input_file settings.logger = logger - settings.diff_file = scene_bim.diff_json_file - settings.ifc_import_filter = scene_bim.ifc_import_filter - settings.ifc_selector = scene_bim.ifc_selector - settings.should_import_type_representations = scene_bim.import_should_import_type_representations - settings.should_import_curves = scene_bim.import_should_import_curves - settings.should_import_opening_elements = scene_bim.import_should_import_opening_elements - settings.should_import_spaces = scene_bim.import_should_import_spaces - settings.should_auto_set_workarounds = scene_bim.import_should_auto_set_workarounds - settings.should_use_cpu_multiprocessing = scene_bim.import_should_use_cpu_multiprocessing - settings.should_import_with_profiling = scene_bim.import_should_import_with_profiling - settings.should_import_native = scene_bim.import_should_import_native - settings.should_roundtrip_native = scene_bim.import_export_should_roundtrip_native - settings.should_import_aggregates = scene_bim.import_should_import_aggregates - settings.should_merge_aggregates = scene_bim.import_should_merge_aggregates - settings.should_merge_by_class = scene_bim.import_should_merge_by_class - settings.should_merge_by_material = scene_bim.import_should_merge_by_material - settings.should_merge_materials_by_colour = scene_bim.import_should_merge_materials_by_colour - settings.should_clean_mesh = scene_bim.import_should_clean_mesh - settings.should_allow_non_element_aggregates = scene_bim.import_should_allow_non_element_aggregates - settings.should_offset_model = scene_bim.import_should_offset_model - settings.model_offset_coordinates = ( - [float(o) for o in scene_bim.import_model_offset_coordinates.split(",")] - if scene_bim.import_model_offset_coordinates - else (0, 0, 0) - ) - settings.deflection_tolerance = scene_bim.import_deflection_tolerance - settings.angular_tolerance = scene_bim.import_angular_tolerance + settings.diff_file = scene_diff.diff_json_file return settings diff --git a/src/ifcblenderexport/blenderbim/bim/module/aggregate/ui.py b/src/ifcblenderexport/blenderbim/bim/module/aggregate/ui.py index c6b5c3475d..6749a6b567 100644 --- a/src/ifcblenderexport/blenderbim/bim/module/aggregate/ui.py +++ b/src/ifcblenderexport/blenderbim/bim/module/aggregate/ui.py @@ -1,10 +1,12 @@ from bpy.types import Panel from blenderbim.bim.module.aggregate.data import Data +from blenderbim.bim.ifc import IfcStore class BIM_PT_aggregate(Panel): - bl_label = "IFC Aggregation" + bl_label = "IFC Aggregates" bl_idname = "BIM_PT_aggregate" + bl_options = {"DEFAULT_CLOSED"} bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" bl_context = "object" @@ -14,6 +16,8 @@ class BIM_PT_aggregate(Panel): props = context.active_object.BIMObjectProperties if not props.ifc_definition_id: return False + if not IfcStore.get_file().by_id(props.ifc_definition_id).is_a("IfcObjectDefinition"): + return False if props.ifc_definition_id not in Data.products: Data.load(props.ifc_definition_id) if not Data.products[props.ifc_definition_id]: @@ -38,7 +42,7 @@ class BIM_PT_aggregate(Panel): Data.products[props.ifc_definition_id]["type"], Data.products[props.ifc_definition_id]["Name"] ) if name == "None/None": - name = "This object is not part of an aggregation" + name = "No Aggregate Found" row.label(text=name) row.operator("bim.enable_editing_aggregate", icon="GREASEPENCIL", text="") row.operator("bim.add_aggregate", icon="ADD", text="") diff --git a/src/ifcblenderexport/blenderbim/bim/module/attribute/__init__.py b/src/ifcblenderexport/blenderbim/bim/module/attribute/__init__.py index 1801a76bfa..f7c565111c 100644 --- a/src/ifcblenderexport/blenderbim/bim/module/attribute/__init__.py +++ b/src/ifcblenderexport/blenderbim/bim/module/attribute/__init__.py @@ -5,6 +5,7 @@ classes = ( operator.EnableEditingAttributes, operator.DisableEditingAttributes, operator.EditAttributes, + operator.GenerateGlobalId, prop.BIMAttributeProperties, ui.BIM_PT_object_attributes, ui.BIM_PT_material_attributes, diff --git a/src/ifcblenderexport/blenderbim/bim/module/attribute/operator.py b/src/ifcblenderexport/blenderbim/bim/module/attribute/operator.py index 93665b6565..ddd03797b7 100644 --- a/src/ifcblenderexport/blenderbim/bim/module/attribute/operator.py +++ b/src/ifcblenderexport/blenderbim/bim/module/attribute/operator.py @@ -1,5 +1,6 @@ import bpy import json +import ifcopenshell import blenderbim.bim.module.attribute.edit_attributes as edit_attributes from blenderbim.bim.ifc import IfcStore from blenderbim.bim.module.attribute.data import Data @@ -100,3 +101,19 @@ class EditAttributes(bpy.types.Operator): Data.load(oprops.ifc_definition_id) bpy.ops.bim.disable_editing_attributes(obj=self.obj, obj_type=self.obj_type) return {"FINISHED"} + + +class GenerateGlobalId(bpy.types.Operator): + bl_idname = "bim.generate_global_id" + bl_label = "Regenerate GlobalId" + + def execute(self, context): + index = bpy.context.active_object.BIMAttributeProperties.attributes.find("GlobalId") + if index >= 0: + global_id = bpy.context.active_object.BIMAttributeProperties.attributes[index] + else: + global_id = bpy.context.active_object.BIMAttributeProperties.attributes.add() + global_id.name = "GlobalId" + global_id.data_type = "string" + global_id.string_value = ifcopenshell.guid.new() + return {"FINISHED"} diff --git a/src/ifcblenderexport/blenderbim/bim/module/bcf/operator.py b/src/ifcblenderexport/blenderbim/bim/module/bcf/operator.py index 26af41b9f6..ec7b1969ea 100644 --- a/src/ifcblenderexport/blenderbim/bim/module/bcf/operator.py +++ b/src/ifcblenderexport/blenderbim/bim/module/bcf/operator.py @@ -2,6 +2,7 @@ import os import bpy import bcf from . import bcfstore +from blenderbim.bim.ifc import IfcStore from math import radians, degrees, atan, tan, cos, sin from mathutils import Vector, Matrix, Euler, geometry @@ -624,6 +625,7 @@ class ActivateBcfViewpoint(bpy.types.Operator): bl_label = "Activate BCF Viewpoint" def execute(self, context): + self.file = IfcStore.get_file() bcfxml = bcfstore.BcfStore.get_bcfxml() props = bpy.context.scene.BCFProperties blender_topic = props.topics[props.active_topic_index] @@ -713,10 +715,9 @@ class ActivateBcfViewpoint(bpy.types.Operator): global_id_colours.setdefault(component.ifc_guid, coloring.color) for obj in bpy.data.objects: - global_id = obj.BIMObjectProperties.attributes.get("GlobalId") - if not global_id: + if not obj.BIMObjectProperties.ifc_definition_id: continue - global_id = global_id.string_value + global_id = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id).GlobalId is_visible = viewpoint.components.visibility.default_visibility if global_id in exception_global_ids: is_visible = not is_visible @@ -724,9 +725,15 @@ class ActivateBcfViewpoint(bpy.types.Operator): obj.hide_set(True) continue if "IfcSpace" in obj.name: - is_visible = viewpoint.components.viewSetuphints.spacesVisible + if viewpoint.components.view_setup_hints: + is_visible = viewpoint.components.view_setup_hints.spaces_visible + else: + is_visible = False elif "IfcOpeningElement" in obj.name: - is_visible = viewpoint.components.viewSetuphints.openingsVisible + if viewpoint.components.view_setup_hints: + is_visible = viewpoint.components.view_setup_hints.openings_visible + else: + is_visible = False obj.hide_set(not is_visible) if not is_visible: continue diff --git a/src/ifcblenderexport/blenderbim/bim/module/bcf/ui.py b/src/ifcblenderexport/blenderbim/bim/module/bcf/ui.py index f2d22c08ee..f03032d4ef 100644 --- a/src/ifcblenderexport/blenderbim/bim/module/bcf/ui.py +++ b/src/ifcblenderexport/blenderbim/bim/module/bcf/ui.py @@ -4,7 +4,7 @@ from . import bcfstore from bpy.types import Panel class BIM_PT_bcf(Panel): - bl_label = "BIM Collaboration Format (BCF)" + bl_label = "BCF Project" bl_idname = "BIM_PT_bcf" bl_options = {"DEFAULT_CLOSED"} bl_space_type = "PROPERTIES" diff --git a/src/ifcblenderexport/blenderbim/bim/module/bimtester/__init__.py b/src/ifcblenderexport/blenderbim/bim/module/bimtester/__init__.py index cfe212cf63..a42cce8b97 100644 --- a/src/ifcblenderexport/blenderbim/bim/module/bimtester/__init__.py +++ b/src/ifcblenderexport/blenderbim/bim/module/bimtester/__init__.py @@ -4,10 +4,8 @@ from . import ui, prop, operator classes = ( operator.ExecuteBIMTester, operator.BIMTesterPurge, - operator.SelectFeaturesDir, - operator.RejectElement, - operator.ColourByClass, - operator.ResetObjectColours, + operator.SelectFeaturesDir, + operator.RejectElement, operator.ApproveClass, operator.RejectClass, operator.SelectAudited, @@ -22,4 +20,3 @@ def register(): def unregister(): del bpy.types.Scene.BimTesterProperties - diff --git a/src/ifcblenderexport/blenderbim/bim/module/bimtester/operator.py b/src/ifcblenderexport/blenderbim/bim/module/bimtester/operator.py index 41df7fa30e..d3c4df2b4d 100644 --- a/src/ifcblenderexport/blenderbim/bim/module/bimtester/operator.py +++ b/src/ifcblenderexport/blenderbim/bim/module/bimtester/operator.py @@ -1,10 +1,13 @@ import bpy import ifcopenshell import bimtester +import bimtester.run +import bimtester.reports import os import webbrowser from pathlib import Path from itertools import cycle +from blenderbim.bim.ifc import IfcStore class ExecuteBIMTester(bpy.types.Operator): @@ -14,11 +17,21 @@ class ExecuteBIMTester(bpy.types.Operator): def execute(self, context): filename = os.path.join( - bpy.context.scene.BimTesterProperties.features_dir, bpy.context.scene.BimTesterProperties.features_file + ".feature" + bpy.context.scene.BimTesterProperties.features_dir, + bpy.context.scene.BimTesterProperties.features_file + ".feature", ) cwd = os.getcwd() os.chdir(bpy.context.scene.BimTesterProperties.features_dir) - bimtester.run.run_tests({"feature": filename, "advanced_arguments": None, "console": False}) + bimtester.run.run_tests({ + "advanced_arguments": "", + "console": False, + "featuresdir": "", + "feature": filename, + "gui": False, + "ifcfile": "", + "purge": False, + "path": "", + }) bimtester.reports.generate_report() webbrowser.open( "file://" @@ -31,6 +44,7 @@ class ExecuteBIMTester(bpy.types.Operator): os.chdir(cwd) return {"FINISHED"} + class BIMTesterPurge(bpy.types.Operator): bl_idname = "bim.bim_tester_purge" bl_label = "Purge Tests" @@ -38,7 +52,8 @@ class BIMTesterPurge(bpy.types.Operator): def execute(self, context): filename = os.path.join( - bpy.context.scene.BimTesterProperties.features_dir, bpy.context.scene.BimTesterProperties.features_file + ".feature" + bpy.context.scene.BimTesterProperties.features_dir, + bpy.context.scene.BimTesterProperties.features_file + ".feature", ) cwd = os.getcwd() os.chdir(bpy.context.scene.BimTesterProperties.features_dir) @@ -46,6 +61,7 @@ class BIMTesterPurge(bpy.types.Operator): os.chdir(cwd) return {"FINISHED"} + class SelectFeaturesDir(bpy.types.Operator): bl_idname = "bim.select_features_dir" bl_label = "Select Features Directory" @@ -61,50 +77,24 @@ class SelectFeaturesDir(bpy.types.Operator): context.window_manager.fileselect_add(self) return {"RUNNING_MODAL"} + class RejectElement(bpy.types.Operator): bl_idname = "bim.reject_element" bl_label = "Reject Element" def execute(self, context): lines = [] - for object in bpy.context.selected_objects: + self.file = IfcStore.get_file() + for obj in bpy.context.selected_objects: lines.append( " * The element {} should not exist because {}".format( - object.BIMObjectProperties.attributes[ - object.BIMObjectProperties.attributes.find("GlobalId") - ].string_value, + self.file.by_id(obj.BIMObjectProperties.ifc_definition_id).GlobalId, bpy.context.scene.BimTesterProperties.qa_reject_element_reason, ) ) QAHelper.append_to_scenario(lines) return {"FINISHED"} -class ColourByClass(bpy.types.Operator): - bl_idname = "bim.colour_by_class" - bl_label = "Colour by Class" - - def execute(self, context): - colours = cycle(colour_list) - ifc_classes = {} - for obj in bpy.context.visible_objects: - if "/" not in obj.name: - continue - ifc_class = obj.name.split("/")[0] - if ifc_class not in ifc_classes: - ifc_classes[ifc_class] = next(colours) - obj.color = ifc_classes[ifc_class] - area = next(area for area in bpy.context.screen.areas if area.type == "VIEW_3D") - area.spaces[0].shading.color_type = "OBJECT" - return {"FINISHED"} - -class ResetObjectColours(bpy.types.Operator): - bl_idname = "bim.reset_object_colours" - bl_label = "Reset Colours" - - def execute(self, context): - for object in bpy.context.selected_objects: - object.color = (1, 1, 1, 1) - return {"FINISHED"} class ApproveClass(bpy.types.Operator): bl_idname = "bim.approve_class" @@ -112,41 +102,43 @@ class ApproveClass(bpy.types.Operator): def execute(self, context): lines = [] - for object in bpy.context.selected_objects: - index = object.BIMObjectProperties.attributes.find("GlobalId") - if index != -1: - lines.append( - " * The element {} is an {}".format( - object.BIMObjectProperties.attributes[index].string_value, object.name.split("/")[0] - ) - ) + self.file = IfcStore.get_file() + for obj in bpy.context.selected_objects: + if not obj.BIMObjectProperties.ifc_definition_id: + continue + element = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id) + lines.append(" * The element {} is an {}".format(element.GlobalId, element.is_a())) QAHelper.append_to_scenario(lines) return {"FINISHED"} + class RejectClass(bpy.types.Operator): bl_idname = "bim.reject_class" bl_label = "Reject Class" def execute(self, context): lines = [] - for object in bpy.context.selected_objects: + self.file = IfcStore.get_file() + for obj in bpy.context.selected_objects: + if not obj.BIMObjectProperties.ifc_definition_id: + continue lines.append( " * The element {} is an {}".format( - object.BIMObjectProperties.attributes[ - object.BIMObjectProperties.attributes.find("GlobalId") - ].string_value, + self.file.by_id(obj.BIMObjectProperties.ifc_definition_id).GlobalId, bpy.context.scene.BimTesterProperties.audit_ifc_class, ) ) QAHelper.append_to_scenario(lines) return {"FINISHED"} + class SelectAudited(bpy.types.Operator): bl_idname = "bim.select_audited" bl_label = "Select Audited" def execute(self, context): audited_global_ids = [] + self.file = IfcStore.get_file() for filename in Path(bpy.context.scene.BimTesterProperties.features_dir).glob("*.feature"): with open(filename, "r") as feature_file: lines = feature_file.readlines() @@ -155,20 +147,23 @@ class SelectAudited(bpy.types.Operator): for word in words: if self.is_a_global_id(word): audited_global_ids.append(word) - for object in bpy.context.visible_objects: - index = object.BIMObjectProperties.attributes.find("GlobalId") - if index != -1 and object.BIMObjectProperties.attributes[index].string_value in audited_global_ids: - object.select_set(True) + for obj in bpy.context.visible_objects: + if not obj.BIMObjectProperties.ifc_definition_id: + continue + if self.file.by_id(obj.BIMObjectProperties.ifc_definition_id).GlobalId in audited_global_ids: + obj.select_set(True) return {"FINISHED"} def is_a_global_id(self, word): return word[0] in ["0", "1", "2", "3"] and len(word) == 22 + class QAHelper: @classmethod def append_to_scenario(cls, lines): filename = os.path.join( - bpy.context.scene.BimTesterProperties.features_dir, bpy.context.scene.BimTesterProperties.features_file + ".feature" + bpy.context.scene.BimTesterProperties.features_dir, + bpy.context.scene.BimTesterProperties.features_file + ".feature", ) if os.path.exists(filename + "~"): os.remove(filename + "~") @@ -189,6 +184,7 @@ class QAHelper: destination.write(source_line) os.remove(filename + "~") + colour_list = [ (0.651, 0.81, 0.892, 1), (0.121, 0.471, 0.706, 1), diff --git a/src/ifcblenderexport/blenderbim/bim/module/bimtester/ui.py b/src/ifcblenderexport/blenderbim/bim/module/bimtester/ui.py index 1e6ffddf79..7026417f16 100644 --- a/src/ifcblenderexport/blenderbim/bim/module/bimtester/ui.py +++ b/src/ifcblenderexport/blenderbim/bim/module/bimtester/ui.py @@ -3,7 +3,7 @@ from bpy.types import Panel class BIM_PT_qa(Panel): - bl_label = "BIMTester Quality Auditing" + bl_label = "BIMTester" bl_idname = "BIM_PT_qa" bl_options = {"DEFAULT_CLOSED"} bl_space_type = "PROPERTIES" @@ -46,10 +46,6 @@ class BIM_PT_qa(Panel): row = layout.row() row.operator("bim.reject_element") - row = layout.row(align=True) - row.operator("bim.colour_by_class") - row.operator("bim.reset_object_colours") - row = layout.row() row.prop(bimtester_properties, "audit_ifc_class") diff --git a/src/ifcblenderexport/blenderbim/bim/module/clash/__init__.py b/src/ifcblenderexport/blenderbim/bim/module/clash/__init__.py new file mode 100644 index 0000000000..63448c96e3 --- /dev/null +++ b/src/ifcblenderexport/blenderbim/bim/module/clash/__init__.py @@ -0,0 +1,38 @@ +import bpy +from . import ui, prop, operator + +classes = ( + operator.ExportClashSets, + operator.ImportClashSets, + operator.AddClashSet, + operator.RemoveClashSet, + operator.AddClashSource, + operator.RemoveClashSource, + operator.SelectClashSource, + operator.ExecuteIfcClash, + operator.SelectIfcClashResults, + operator.SelectClashResults, + operator.SelectSmartGroupedClashesPath, + operator.SmartClashGroup, + operator.SelectSmartGroup, + operator.LoadSmartGroupsForActiveClashSet, + operator.SetBlenderClashSetA, + operator.SetBlenderClashSetB, + operator.ExecuteBlenderClash, + prop.ClashSource, + prop.ClashSet, + prop.SmartClashGroup, + prop.BIMClashProperties, + ui.BIM_PT_ifcclash, + ui.BIM_PT_clash_manager, + ui.BIM_UL_clash_sets, + ui.BIM_UL_smart_groups, +) + + +def register(): + bpy.types.Scene.BIMClashProperties = bpy.props.PointerProperty(type=prop.BIMClashProperties) + + +def unregister(): + del bpy.types.Scene.BIMClashProperties diff --git a/src/ifcblenderexport/blenderbim/bim/module/clash/operator.py b/src/ifcblenderexport/blenderbim/bim/module/clash/operator.py new file mode 100644 index 0000000000..b6824ace7f --- /dev/null +++ b/src/ifcblenderexport/blenderbim/bim/module/clash/operator.py @@ -0,0 +1,456 @@ +import os +import bpy +import json +import bmesh +import logging +import numpy as np +from mathutils import Matrix +from math import radians + + +class ExportClashSets(bpy.types.Operator): + bl_idname = "bim.export_clash_sets" + bl_label = "Export Clash Sets" + filename_ext = ".json" + filepath: bpy.props.StringProperty(subtype="FILE_PATH") + + def invoke(self, context, event): + self.filepath = bpy.path.ensure_ext(bpy.data.filepath, ".json") + WindowManager = context.window_manager + WindowManager.fileselect_add(self) + return {"RUNNING_MODAL"} + + def execute(self, context): + self.filepath = bpy.path.ensure_ext(self.filepath, ".json") + clash_sets = [] + for clash_set in bpy.context.scene.BIMClashProperties.clash_sets: + self.a = [] + self.b = [] + for ab in ["a", "b"]: + for data in getattr(clash_set, ab): + clash_source = {"file": data.name} + if data.selector: + clash_source["selector"] = data.selector + clash_source["mode"] = data.mode + getattr(self, ab).append(clash_source) + clash_sets.append({"name": clash_set.name, "tolerance": clash_set.tolerance, "a": self.a, "b": self.b}) + with open(self.filepath, "w") as destination: + destination.write(json.dumps(clash_sets, indent=4)) + return {"FINISHED"} + + +class ImportClashSets(bpy.types.Operator): + bl_idname = "bim.import_clash_sets" + bl_label = "Import Clash Sets" + filename_ext = ".json" + filepath: bpy.props.StringProperty(subtype="FILE_PATH") + + def invoke(self, context, event): + self.filepath = bpy.path.ensure_ext(bpy.data.filepath, ".json") + WindowManager = context.window_manager + WindowManager.fileselect_add(self) + return {"RUNNING_MODAL"} + + def execute(self, context): + with open(self.filepath) as f: + clash_sets = json.load(f) + for clash_set in clash_sets: + new = bpy.context.scene.BIMClashProperties.clash_sets.add() + new.name = clash_set["name"] + new.tolerance = clash_set["tolerance"] + for clash_source in clash_set["a"]: + new_source = new.a.add() + new_source.name = clash_source["file"] + if "selector" in clash_source: + new_source.selector = clash_source["selector"] + new_source.mode = clash_source["mode"] + if clash_set["b"]: + for clash_source in clash_set["b"]: + new_source = new.b.add() + new_source.name = clash_source["file"] + if "selector" in clash_source: + new_source.selector = clash_source["selector"] + new_source.mode = clash_source["mode"] + return {"FINISHED"} + + +class AddClashSet(bpy.types.Operator): + bl_idname = "bim.add_clash_set" + bl_label = "Add Clash Set" + + def execute(self, context): + new = bpy.context.scene.BIMClashProperties.clash_sets.add() + new.name = "New Clash Set" + new.tolerance = 0.01 + return {"FINISHED"} + + +class RemoveClashSet(bpy.types.Operator): + bl_idname = "bim.remove_clash_set" + bl_label = "Remove Clash Set" + index: bpy.props.IntProperty() + + def execute(self, context): + bpy.context.scene.BIMClashProperties.clash_sets.remove(self.index) + return {"FINISHED"} + + +class AddClashSource(bpy.types.Operator): + bl_idname = "bim.add_clash_source" + bl_label = "Add Clash Source" + group: bpy.props.StringProperty() + + def execute(self, context): + clash_set = bpy.context.scene.BIMClashProperties.clash_sets[bpy.context.scene.BIMClashProperties.active_clash_set_index] + source = getattr(clash_set, self.group).add() + return {"FINISHED"} + + +class RemoveClashSource(bpy.types.Operator): + bl_idname = "bim.remove_clash_source" + bl_label = "Remove Clash Source" + index: bpy.props.IntProperty() + group: bpy.props.StringProperty() + + def execute(self, context): + clash_set = bpy.context.scene.BIMClashProperties.clash_sets[bpy.context.scene.BIMClashProperties.active_clash_set_index] + getattr(clash_set, self.group).remove(self.index) + return {"FINISHED"} + + +class SelectClashSource(bpy.types.Operator): + bl_idname = "bim.select_clash_source" + bl_label = "Select Clash Source" + filepath: bpy.props.StringProperty(subtype="FILE_PATH") + index: bpy.props.IntProperty() + group: bpy.props.StringProperty() + + def execute(self, context): + clash_set = bpy.context.scene.BIMClashProperties.clash_sets[bpy.context.scene.BIMClashProperties.active_clash_set_index] + getattr(clash_set, self.group)[self.index].name = self.filepath + return {"FINISHED"} + + def invoke(self, context, event): + context.window_manager.fileselect_add(self) + return {"RUNNING_MODAL"} + + +class SelectClashResults(bpy.types.Operator): + bl_idname = "bim.select_clash_results" + bl_label = "Select Clash Results" + filepath: bpy.props.StringProperty(subtype="FILE_PATH") + + def execute(self, context): + bpy.context.scene.BIMClashProperties.clash_results_path = self.filepath + return {"FINISHED"} + + def invoke(self, context, event): + context.window_manager.fileselect_add(self) + return {"RUNNING_MODAL"} + + +class SelectSmartGroupedClashesPath(bpy.types.Operator): + bl_idname = "bim.select_smart_grouped_clashes_path" + bl_label = "Select Smart-Grouped Clashes Path" + filepath: bpy.props.StringProperty(subtype="FILE_PATH") + + def execute(self, context): + bpy.context.scene.BIMClashProperties.smart_grouped_clashes_path = self.filepath + return {"FINISHED"} + + def invoke(self, context, event): + context.window_manager.fileselect_add(self) + return {"RUNNING_MODAL"} + + +class ExecuteIfcClash(bpy.types.Operator): + bl_idname = "bim.execute_ifc_clash" + bl_label = "Execute IFC Clash" + filename_ext = ".bcf" + filepath: bpy.props.StringProperty(subtype="FILE_PATH") + + def invoke(self, context, event): + if ".json" not in bpy.data.filepath: + self.filepath = bpy.path.ensure_ext(bpy.data.filepath, ".bcf") + WindowManager = context.window_manager + WindowManager.fileselect_add(self) + return {"RUNNING_MODAL"} + + def execute(self, context): + import ifcclash + + settings = ifcclash.IfcClashSettings() + if ".json" not in self.filepath: + self.filepath = bpy.path.ensure_ext(self.filepath, ".bcf") + settings.output = self.filepath + settings.logger = logging.getLogger("Clash") + settings.logger.setLevel(logging.DEBUG) + ifc_clasher = ifcclash.IfcClasher(settings) + + if bpy.context.scene.BIMClashProperties.should_create_clash_snapshots: + + def get_viewpoint_snapshot(self, viewpoint, mat): + camera = bpy.data.objects.get("IFC Clash Camera") + if not camera: + camera = bpy.data.objects.new("IFC Clash Camera", bpy.data.cameras.new("IFC Clash Camera")) + bpy.context.scene.collection.objects.link(camera) + camera.matrix_world = Matrix(mat) + bpy.context.scene.camera = camera + camera.data.angle = radians(60) + area = next(area for area in bpy.context.screen.areas if area.type == "VIEW_3D") + area.spaces[0].region_3d.view_perspective = "CAMERA" + area.spaces[0].shading.show_xray = True + bpy.context.scene.render.resolution_x = 480 + bpy.context.scene.render.resolution_y = 270 + bpy.context.scene.render.image_settings.file_format = "PNG" + bpy.context.scene.render.filepath = os.path.join( + bpy.context.scene.BIMProperties.data_dir, "snapshot.png" + ) + bpy.ops.render.opengl(write_still=True) + return bpy.context.scene.render.filepath + + ifcclash.IfcClasher.get_viewpoint_snapshot = get_viewpoint_snapshot + + ifc_clasher.clash_sets = [] + for clash_set in bpy.context.scene.BIMClashProperties.clash_sets: + self.a = [] + self.b = [] + for ab in ["a", "b"]: + for data in getattr(clash_set, ab): + clash_source = {"file": data.name} + if data.selector: + clash_source["selector"] = data.selector + clash_source["mode"] = data.mode + getattr(self, ab).append(clash_source) + ifc_clasher.clash_sets.append( + {"name": clash_set.name, "tolerance": clash_set.tolerance, "a": self.a, "b": self.b} + ) + ifc_clasher.clash() + ifc_clasher.export() + return {"FINISHED"} + + +class SelectIfcClashResults(bpy.types.Operator): + bl_idname = "bim.select_ifc_clash_results" + bl_label = "Select IFC Clash Results" + filename_ext = ".json" + filepath: bpy.props.StringProperty(subtype="FILE_PATH") + + def invoke(self, context, event): + self.filepath = bpy.path.ensure_ext(bpy.data.filepath, ".json") + WindowManager = context.window_manager + WindowManager.fileselect_add(self) + return {"RUNNING_MODAL"} + + def execute(self, context): + self.filepath = bpy.path.ensure_ext(self.filepath, ".json") + with open(self.filepath) as f: + clash_sets = json.load(f) + clash_set_name = bpy.context.scene.BIMClashProperties.clash_sets[ + bpy.context.scene.BIMClashProperties.active_clash_set_index + ].name + global_ids = [] + for clash_set in clash_sets: + if clash_set["name"] != clash_set_name: + continue + if not "clashes" in clash_set.keys(): + self.report({"WARNING"}, "No clashes found for the selected Clash Set.") + return {"CANCELLED"} + for clash in clash_set["clashes"].values(): + global_ids.extend([clash["a_global_id"], clash["b_global_id"]]) + for obj in bpy.context.visible_objects: + if not obj.BIMObjectProperties.ifc_definition_id: + continue + element = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id) + if element.GlobalId in global_ids: + obj.select_set(True) + return {"FINISHED"} + + +class SmartClashGroup(bpy.types.Operator): + bl_idname = "bim.smart_clash_group" + bl_label = "Smart Group Clashes" + filepath: bpy.props.StringProperty(subtype="FILE_PATH") + + def execute(self, context): + import ifcclash + + settings = ifcclash.IfcClashSettings() + self.filepath = bpy.path.ensure_ext(bpy.context.scene.BIMClashProperties.clash_results_path, ".json") + settings.output = self.filepath + settings.logger = logging.getLogger("Clash") + settings.logger.setLevel(logging.DEBUG) + ifc_clasher = ifcclash.IfcClasher(settings) + + with open(self.filepath) as f: + clash_sets = json.load(f) + + # execute the smart grouping + save_path = bpy.path.ensure_ext(bpy.context.scene.BIMClashProperties.smart_grouped_clashes_path, ".json") + smart_grouped_clashes = ifc_clasher.smart_group_clashes( + clash_sets, bpy.context.scene.BIMClashProperties.smart_clash_grouping_max_distance + ) + + # save smart_groups to json + with open(save_path, "w") as f: + f.write(json.dumps(smart_grouped_clashes)) + + clash_set_name = bpy.context.scene.BIMClashProperties.clash_sets[ + bpy.context.scene.BIMClashProperties.active_clash_set_index + ].name + + # Reset the list of smart_clash_groups for the UI + bpy.context.scene.BIMClashProperties.smart_clash_groups.clear() + + for clash_set, smart_groups in smart_grouped_clashes.items(): + # Only select the clashes that correspond to the actively selected IFC Clash Set + if clash_set != clash_set_name: + continue + else: + for smart_group, global_id_pairs in smart_groups[0].items(): + new_group = bpy.context.scene.BIMClashProperties.smart_clash_groups.add() + new_group.number = f"{smart_group}" + + for pair in global_id_pairs: + for id in pair: + new_global_id = new_group.global_ids.add() + new_global_id.name = id + + return {"FINISHED"} + + +class LoadSmartGroupsForActiveClashSet(bpy.types.Operator): + bl_idname = "bim.load_smart_groups_for_active_clash_set" + bl_label = "Load Smart Groups for Active Clash Set" + + def execute(self, context): + smart_groups_path = bpy.path.ensure_ext(bpy.context.scene.BIMClashProperties.smart_grouped_clashes_path, ".json") + + clash_set_name = bpy.context.scene.BIMClashProperties.clash_sets[ + bpy.context.scene.BIMClashProperties.active_clash_set_index + ].name + + with open(smart_groups_path) as f: + smart_grouped_clashes = json.load(f) + + # Reset the list of smart_clash_groups for the UI + bpy.context.scene.BIMClashProperties.smart_clash_groups.clear() + + for clash_set, smart_groups in smart_grouped_clashes.items(): + # Only select the clashes that correspond to the actively selected IFC Clash Set + if clash_set != clash_set_name: + continue + else: + for smart_group, global_id_pairs in smart_groups[0].items(): + new_group = bpy.context.scene.BIMClashProperties.smart_clash_groups.add() + new_group.number = f"{smart_group}" + for pair in global_id_pairs: + for id in pair: + new_global_id = new_group.global_ids.add() + new_global_id.name = id + + return {"FINISHED"} + + +class SelectSmartGroup(bpy.types.Operator): + bl_idname = "bim.select_smart_group" + bl_label = "Select Smart Group" + + def execute(self, context): + # Select smart group in view + selected_smart_group = bpy.context.scene.BIMClashProperties.smart_clash_groups[ + bpy.context.scene.BIMCLashProperties.active_smart_group_index + ] + # print(selected_smart_group.number) + + for obj in bpy.context.visible_objects: + if not obj.BIMObjectProperties.ifc_definition_id: + continue + element = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id) + for id in selected_smart_group.global_ids: + # print("Id: ", id) + # print("Global id: ", element.GlobalId) + if element.GlobalId in id.name: + # print("object match: ", global_id) + obj.select_set(True) + + return {"FINISHED"} + + +class BlenderClasher: + def process_clash_set(self): + import collision + + a_cm = collision.CollisionManager() + b_cm = collision.CollisionManager() + self.add_to_cm(a_cm, bpy.context.scene.BIMClashProperties.blender_clash_set_a) + self.add_to_cm(b_cm, bpy.context.scene.BIMClashProperties.blender_clash_set_b) + results = a_cm.in_collision_other(b_cm, return_data=True) + if not results[0]: + print("No clashes") + return + for contact in results[1]: + if contact.raw.penetration_depth < 0.01: + continue + print("-----") + print(contact.names) + print(contact.raw.normal) + print(contact.raw.pos) + + def add_to_cm(self, cm, object_names): + import ifcclash + + for object_name in object_names: + name = object_name.name + obj = bpy.data.objects[name] + triangulated_mesh = self.triangulate_mesh(obj) + mesh = ifcclash.Mesh() + mesh.vertices = np.array([tuple(obj.matrix_world @ v.co) for v in triangulated_mesh.vertices]) + mesh.faces = np.array([tuple(p.vertices) for p in triangulated_mesh.polygons]) + cm.add_object(name, mesh) + + def triangulate_mesh(self, obj): + mesh = obj.evaluated_get(bpy.context.evaluated_depsgraph_get()).to_mesh() + bm = bmesh.new() + bm.from_mesh(mesh) + bmesh.ops.triangulate(bm, faces=bm.faces) + bm.to_mesh(mesh) + bm.free() + del bm + return mesh + + +class SetBlenderClashSetA(bpy.types.Operator): + bl_idname = "bim.set_blender_clash_set_a" + bl_label = "Set Blender Clash Set A" + + def execute(self, context): + while len(bpy.context.scene.BIMClashProperties.blender_clash_set_a) > 0: + bpy.context.scene.BIMClashProperties.blender_clash_set_a.remove(0) + for obj in bpy.context.selected_objects: + new = bpy.context.scene.BIMClashProperties.blender_clash_set_a.add() + new.name = obj.name + return {"FINISHED"} + + +class SetBlenderClashSetB(bpy.types.Operator): + bl_idname = "bim.set_blender_clash_set_b" + bl_label = "Set Blender Clash Set B" + + def execute(self, context): + while len(bpy.context.scene.BIMClashProperties.blender_clash_set_b) > 0: + bpy.context.scene.BIMClashProperties.blender_clash_set_b.remove(0) + for obj in bpy.context.selected_objects: + new = bpy.context.scene.BIMClashProperties.blender_clash_set_b.add() + new.name = obj.name + return {"FINISHED"} + + +class ExecuteBlenderClash(bpy.types.Operator): + bl_idname = "bim.execute_blender_clash" + bl_label = "Execute Blender Clash" + + def execute(self, context): + blender_clasher = BlenderClasher() + blender_clasher.process_clash_set() + return {"FINISHED"} diff --git a/src/ifcblenderexport/blenderbim/bim/module/clash/prop.py b/src/ifcblenderexport/blenderbim/bim/module/clash/prop.py new file mode 100644 index 0000000000..185e9fe163 --- /dev/null +++ b/src/ifcblenderexport/blenderbim/bim/module/clash/prop.py @@ -0,0 +1,53 @@ +import bpy +from blenderbim.bim.prop import StrProperty, Attribute +from blenderbim.bim.module.owner.data import Data +from bpy.types import PropertyGroup +from bpy.props import ( + PointerProperty, + StringProperty, + EnumProperty, + BoolProperty, + IntProperty, + FloatProperty, + FloatVectorProperty, + CollectionProperty, +) + + +class ClashSource(PropertyGroup): + name: StringProperty(name="File") + selector: StringProperty(name="Selector") + mode: EnumProperty( + items=[ + ("i", "Include", "Only the selected objects are included for clashing"), + ("e", "Exclude", "All objects except the selected objects are included for clashing"), + ], + name="Mode", + ) + + +class ClashSet(PropertyGroup): + name: StringProperty(name="Name") + tolerance: FloatProperty(name="Tolerance") + a: CollectionProperty(name="Group A", type=ClashSource) + b: CollectionProperty(name="Group B", type=ClashSource) + + +class SmartClashGroup(PropertyGroup): + number: StringProperty(name="Number") + global_ids: CollectionProperty(name="GlobalIDs", type=StrProperty) + + +class BIMClashProperties(PropertyGroup): + blender_clash_set_a: CollectionProperty(name="Blender Clash Set A", type=StrProperty) + blender_clash_set_b: CollectionProperty(name="Blender Clash Set B", type=StrProperty) + clash_sets: CollectionProperty(name="Clash Sets", type=ClashSet) + should_create_clash_snapshots: BoolProperty(name="Create Snapshots", default=True) + clash_results_path: StringProperty(name="Clash Results Path") + smart_grouped_clashes_path: StringProperty(name="Smart Grouped Clashes Path") + active_clash_set_index: IntProperty(name="Active Clash Set Index") + smart_clash_groups: CollectionProperty(name="Smart Clash Groups", type=SmartClashGroup) + active_smart_group_index: IntProperty(name="Active Smart Group Index") + smart_clash_grouping_max_distance: IntProperty( + name="Smart Clash Grouping Max Distance", default=3, soft_min=1, soft_max=10 + ) diff --git a/src/ifcblenderexport/blenderbim/bim/module/clash/ui.py b/src/ifcblenderexport/blenderbim/bim/module/clash/ui.py new file mode 100644 index 0000000000..d7f3416b65 --- /dev/null +++ b/src/ifcblenderexport/blenderbim/bim/module/clash/ui.py @@ -0,0 +1,148 @@ +import bpy +from bpy.types import Panel + + +class BIM_PT_ifcclash(Panel): + bl_label = "IFC Clash Sets" + bl_idname = "BIM_PT_ifcclash" + bl_options = {"DEFAULT_CLOSED"} + bl_space_type = "PROPERTIES" + bl_region_type = "WINDOW" + bl_context = "scene" + + def draw(self, context): + layout = self.layout + + scene = context.scene + props = scene.BIMClashProperties + + layout.label(text="Blender Clash:") + + row = layout.row(align=True) + row.operator("bim.set_blender_clash_set_a") + row.operator("bim.set_blender_clash_set_b") + + row = layout.row(align=True) + row.operator("bim.execute_blender_clash") + + layout.label(text="IFC Clash:") + + row = layout.row(align=True) + row.operator("bim.add_clash_set") + row.operator("bim.import_clash_sets", text="", icon="IMPORT") + row.operator("bim.export_clash_sets", text="", icon="EXPORT") + + if not props.clash_sets: + return + + layout.template_list("BIM_UL_clash_sets", "", props, "clash_sets", props, "active_clash_set_index") + + if props.active_clash_set_index < len(props.clash_sets): + clash_set = props.clash_sets[props.active_clash_set_index] + + row = layout.row(align=True) + row.prop(clash_set, "name") + row.operator("bim.remove_clash_set", icon="X", text="").index = props.active_clash_set_index + + row = layout.row(align=True) + row.prop(clash_set, "tolerance") + + layout.label(text="Group A:") + row = layout.row() + row.operator("bim.add_clash_source").group = "a" + + for index, source in enumerate(clash_set.a): + row = layout.row(align=True) + row.prop(source, "name", text="") + op = row.operator("bim.select_clash_source", icon="FILE_FOLDER", text="") + op.index = index + op.group = "a" + op = row.operator("bim.remove_clash_source", icon="X", text="") + op.index = index + op.group = "a" + + row = layout.row(align=True) + row.prop(source, "mode", text="") + row.prop(source, "selector", text="") + + layout.label(text="Group B:") + row = layout.row() + row.operator("bim.add_clash_source").group = "b" + + for index, source in enumerate(clash_set.b): + row = layout.row(align=True) + row.prop(source, "name", text="") + op = row.operator("bim.select_clash_source", icon="FILE_FOLDER", text="") + op.index = index + op.group = "b" + op = row.operator("bim.remove_clash_source", icon="X", text="") + op.index = index + op.group = "b" + + row = layout.row(align=True) + row.prop(source, "mode", text="") + row.prop(source, "selector", text="") + + row = layout.row() + row.prop(props, "should_create_clash_snapshots") + row = layout.row(align=True) + row.operator("bim.execute_ifc_clash") + row.operator("bim.select_ifc_clash_results") + + +class BIM_PT_clash_manager(Panel): + bl_idname = "BIM_PT_clash_manager" + bl_label = "Clash Manager" + bl_space_type = "VIEW_3D" + bl_region_type = "UI" + bl_category = "BlenderBIM" + + def draw(self, context): + layout = self.layout + props = context.scene.BIMClashProperties + + row = layout.row() + layout.label(text="Select clash results to group:") + + row = layout.row(align=True) + row.prop(props, "clash_results_path", text="") + op = row.operator("bim.select_clash_results", icon="FILE_FOLDER", text="") + + row = layout.row() + layout.label(text="Select output path for smart-grouped clashes:") + + row = layout.row(align=True) + row.prop(props, "smart_grouped_clashes_path", text="") + op = row.operator("bim.select_smart_grouped_clashes_path", icon="FILE_FOLDER", text="") + + row = layout.row(align=True) + row.prop(props, "smart_clash_grouping_max_distance") + + row = layout.row(align=True) + row.operator("bim.smart_clash_group") + + row = layout.row(align=True) + row.operator("bim.load_smart_groups_for_active_clash_set") + + layout.template_list("BIM_UL_smart_groups", "", props, "smart_clash_groups", props, "active_smart_group_index") + + row = layout.row(align=True) + row.operator("bim.select_smart_group") + + +class BIM_UL_clash_sets(bpy.types.UIList): + def draw_item(self, context, layout, data, item, icon, active_data, active_propname): + ob = data + if item: + layout.prop(item, "name", text="", emboss=False) + else: + layout.label(text="", translate=False) + + +class BIM_UL_smart_groups(bpy.types.UIList): + def draw_item(self, context, layout, data, item, icon, active_data, active_propname): + ob = data + if item: + layout.label(text=str(item.number), translate=False, icon="NONE", icon_value=0) + else: + layout.label(text="", translate=False) diff --git a/src/ifcblenderexport/blenderbim/bim/module/classification/__init__.py b/src/ifcblenderexport/blenderbim/bim/module/classification/__init__.py new file mode 100644 index 0000000000..f486b4e3fa --- /dev/null +++ b/src/ifcblenderexport/blenderbim/bim/module/classification/__init__.py @@ -0,0 +1,35 @@ +import bpy +from . import ui, prop, operator + +classes = ( + operator.LoadClassificationLibrary, + operator.AddClassification, + operator.RemoveClassification, + operator.EnableEditingClassification, + operator.DisableEditingClassification, + operator.EditClassification, + operator.RemoveClassificationReference, + operator.EnableEditingClassificationReference, + operator.DisableEditingClassificationReference, + operator.EditClassificationReference, + operator.AddClassificationReference, + operator.ChangeClassificationLevel, + prop.ClassificationReference, + prop.BIMClassificationProperties, + prop.BIMClassificationReferenceProperties, + ui.BIM_PT_classifications, + ui.BIM_PT_classification_references, + ui.BIM_UL_classifications, +) + + +def register(): + bpy.types.Scene.BIMClassificationProperties = bpy.props.PointerProperty(type=prop.BIMClassificationProperties) + bpy.types.Object.BIMClassificationReferenceProperties = bpy.props.PointerProperty( + type=prop.BIMClassificationReferenceProperties + ) + + +def unregister(): + del bpy.types.Scene.BIMClassificationProperties + del bpy.types.Object.BIMClassificationReferenceProperties diff --git a/src/ifcblenderexport/blenderbim/bim/module/classification/add_classification.py b/src/ifcblenderexport/blenderbim/bim/module/classification/add_classification.py new file mode 100644 index 0000000000..7c16292e9f --- /dev/null +++ b/src/ifcblenderexport/blenderbim/bim/module/classification/add_classification.py @@ -0,0 +1,39 @@ +import ifcopenshell +import ifcopenshell.util.schema +import ifcopenshell.util.date + +class Usecase: + def __init__(self, file, settings=None): + self.file = file + self.settings = { + "classification": None, + } + for key, value in settings.items(): + self.settings[key] = value + + def execute(self): + edition_date = None + if self.settings["classification"].EditionDate: + edition_date = ifcopenshell.util.date.ifc2datetime(self.settings["classification"].EditionDate) + self.settings["classification"].EditionDate = None + + migrator = ifcopenshell.util.schema.Migrator() + result = migrator.migrate(self.settings["classification"], self.file) + + # TODO: should auto date migration be part of the migrator? + if self.file.schema == "IFC2X3" and edition_date: + result.EditionDate = ifcopenshell.util.date.datetime2ifc(edition_date, "IfcCalendarDate") + else: + result.EditionDate = ifcopenshell.util.date.datetime2ifc(edition_date, "IfcDate") + + self.file.create_entity("IfcRelAssociatesClassification", **{ + "GlobalId": ifcopenshell.guid.new(), + "RelatedObjects": [self.file.by_type("IfcProject")[0]], + "RelatingClassification": result + }) + return # See bug #1272 + try: + result = self.file.add(self.settings["classification"]) + except: + migrator = ifcopenshell.util.schema.Migrator() + result = migrator.migrate(self.settings["classification"], self.file) diff --git a/src/ifcblenderexport/blenderbim/bim/module/classification/add_reference.py b/src/ifcblenderexport/blenderbim/bim/module/classification/add_reference.py new file mode 100644 index 0000000000..5c880510eb --- /dev/null +++ b/src/ifcblenderexport/blenderbim/bim/module/classification/add_reference.py @@ -0,0 +1,63 @@ +import ifcopenshell +import ifcopenshell.util.schema + + +class Usecase: + def __init__(self, file, settings=None): + self.file = file + self.settings = { + "product": None, + "reference": None, + "classification": None, + } + for key, value in settings.items(): + self.settings[key] = value + + def execute(self): + relating_classification = None + + if hasattr(self.settings["reference"], "ItemReference"): + identification = self.settings["reference"].ItemReference # IFC2X3 + else: + identification = self.settings["reference"].Identification + + for reference in self.file.by_type("IfcClassificationReference"): + if self.file.schema == "IFC2X3": + if reference.ItemReference == identification: + relating_classification = reference + break + else: + if reference.Identification == identification: + relating_classification = reference + break + + if relating_classification: + association = self.get_association(relating_classification) + related_objects = set(association.RelatedObjects) + related_objects.add(self.settings["product"]) + association.RelatedObjects = list(related_objects) + return + + migrator = ifcopenshell.util.schema.Migrator() + # This removal patch is to support a lightweight classification + old_referenced_source = self.settings["reference"].ReferencedSource + self.settings["reference"].ReferencedSource = None + relating_classification = migrator.migrate(self.settings["reference"], self.file) + relating_classification.ReferencedSource = self.settings["classification"] + self.settings["reference"].ReferencedSource = old_referenced_source + self.file.create_entity( + "IfcRelAssociatesClassification", + **{ + "GlobalId": ifcopenshell.guid.new(), + "RelatedObjects": [self.settings["product"]], + "RelatingClassification": relating_classification, + } + ) + + def get_association(self, reference): + if self.file.schema == "IFC2X3": + for association in self.file.by_type("IfcRelAssociatesClassification"): + if association.RelatingClassification == reference: + return association + elif reference.ClassificationRefForObjects: + return reference.ClassificationRefForObjects[0] diff --git a/src/ifcblenderexport/blenderbim/bim/module/classification/data.py b/src/ifcblenderexport/blenderbim/bim/module/classification/data.py new file mode 100644 index 0000000000..204fd23be3 --- /dev/null +++ b/src/ifcblenderexport/blenderbim/bim/module/classification/data.py @@ -0,0 +1,67 @@ +import ifcopenshell +import ifcopenshell.util.date +from blenderbim.bim.ifc import IfcStore + + +class Data: + is_loaded = False + products = {} + classifications = {} + references = {} + library_file = None + library_classifications = {} + library_references = {} + + @classmethod + def load(cls, product_id=None): + cls._file = IfcStore.get_file() + if not cls._file: + return + if product_id: + return cls.load_product_classifications(product_id) + cls.load_classifications() + cls.load_references() + cls.is_loaded = True + + @classmethod + def load_product_classifications(cls, product_id): + product = cls._file.by_id(product_id) + cls.products[product_id] = [] + if not product.HasAssociations: + return + for association in product.HasAssociations: + if association.is_a("IfcRelAssociatesClassification"): + cls.products[product_id].append(association.RelatingClassification.id()) + + @classmethod + def load_classifications(cls): + cls.classifications = {} + for classification in cls._file.by_type("IfcClassification"): + data = classification.get_info() + if cls._file.schema == "IFC2X3" and data["EditionDate"]: + data["EditionDate"] = ifcopenshell.util.date(data.EditionDate).isoformat() + cls.classifications[classification.id()] = data + + @classmethod + def load_references(cls): + cls.references = {} + for reference in cls._file.by_type("IfcClassificationReference"): + data = reference.get_info() + if reference.ReferencedSource: + #data["ReferencedSource"] = cls.get_referenced_source(reference.ReferencedSource) + data["ReferencedSource"] = reference.ReferencedSource.id() + cls.references[reference.id()] = data + + @classmethod + def get_referenced_source(cls, reference): + if reference.is_a("IfcClassification"): + return reference + elif reference.is_a("IfcClassificationReference") and reference.ReferencedSource: + return cls.get_referenced_source(reference.ReferencedSource) + + @classmethod + def load_library(cls, filepath): + cls.library_file = ifcopenshell.open(filepath) + cls.library_classifications = {} + for classification in cls.library_file.by_type("IfcClassification"): + cls.library_classifications[classification.id()] = classification.Name diff --git a/src/ifcblenderexport/blenderbim/bim/module/classification/edit_classification.py b/src/ifcblenderexport/blenderbim/bim/module/classification/edit_classification.py new file mode 100644 index 0000000000..58fd59ea9a --- /dev/null +++ b/src/ifcblenderexport/blenderbim/bim/module/classification/edit_classification.py @@ -0,0 +1,13 @@ +class Usecase: + def __init__(self, file, settings=None): + self.file = file + self.settings = { + "classification": None, + "attributes": {} + } + for key, value in settings.items(): + self.settings[key] = value + + def execute(self): + for name, value in self.settings["attributes"].items(): + setattr(self.settings["classification"], name, value) diff --git a/src/ifcblenderexport/blenderbim/bim/module/classification/edit_reference.py b/src/ifcblenderexport/blenderbim/bim/module/classification/edit_reference.py new file mode 100644 index 0000000000..b538bafb46 --- /dev/null +++ b/src/ifcblenderexport/blenderbim/bim/module/classification/edit_reference.py @@ -0,0 +1,13 @@ +class Usecase: + def __init__(self, file, settings=None): + self.file = file + self.settings = { + "reference": None, + "attributes": {} + } + for key, value in settings.items(): + self.settings[key] = value + + def execute(self): + for name, value in self.settings["attributes"].items(): + setattr(self.settings["reference"], name, value) diff --git a/src/ifcblenderexport/blenderbim/bim/module/classification/operator.py b/src/ifcblenderexport/blenderbim/bim/module/classification/operator.py new file mode 100644 index 0000000000..1150f138f3 --- /dev/null +++ b/src/ifcblenderexport/blenderbim/bim/module/classification/operator.py @@ -0,0 +1,229 @@ +import bpy +import json +import blenderbim.bim.module.classification.add_classification as add_classification +import blenderbim.bim.module.classification.remove_classification as remove_classification +import blenderbim.bim.module.classification.edit_classification as edit_classification +import blenderbim.bim.module.classification.add_reference as add_reference +import blenderbim.bim.module.classification.remove_reference as remove_reference +import blenderbim.bim.module.classification.edit_reference as edit_reference +from blenderbim.bim.ifc import IfcStore +from blenderbim.bim.module.classification.data import Data +from blenderbim.bim.module.classification.prop import getClassifications, getReferences + + +class LoadClassificationLibrary(bpy.types.Operator): + bl_idname = "bim.load_classification_library" + bl_label = "Load Classification Library" + filename_ext = ".ifc" + filter_glob: bpy.props.StringProperty(default="*.ifc;*.ifczip;*.ifcxml", options={"HIDDEN"}) + filepath: bpy.props.StringProperty(subtype="FILE_PATH") + + def execute(self, context): + Data.load_library(self.filepath) + getClassifications(self, context) + return {"FINISHED"} + + def invoke(self, context, event): + context.window_manager.fileselect_add(self) + return {"RUNNING_MODAL"} + + +class AddClassification(bpy.types.Operator): + bl_idname = "bim.add_classification" + bl_label = "Add Classification" + + def execute(self, context): + props = context.scene.BIMClassificationProperties + add_classification.Usecase( + IfcStore.get_file(), {"classification": Data.library_file.by_id(int(props.available_classifications))} + ).execute() + Data.load() + return {"FINISHED"} + + +class EnableEditingClassification(bpy.types.Operator): + bl_idname = "bim.enable_editing_classification" + bl_label = "Enable Editing Classification" + classification: bpy.props.IntProperty() + + def execute(self, context): + props = context.scene.BIMClassificationProperties + while len(props.classification_attributes) > 0: + props.classification_attributes.remove(0) + classification_data = Data.classifications[self.classification] + for attribute in IfcStore.get_schema().declaration_by_name("IfcClassification").all_attributes(): + new = props.classification_attributes.add() + new.name = attribute.name() + new.is_null = classification_data[attribute.name()] is None + new.is_optional = attribute.optional() + if attribute.name() == "ReferenceTokens": + new.string_value = "" if new.is_null else json.dumps(classification_data[attribute.name()]) + else: + new.string_value = "" if new.is_null else classification_data[attribute.name()] + props.active_classification_id = self.classification + return {"FINISHED"} + + +class DisableEditingClassification(bpy.types.Operator): + bl_idname = "bim.disable_editing_classification" + bl_label = "Disable Editing Classification" + + def execute(self, context): + context.scene.BIMClassificationProperties.active_classification_id = 0 + return {"FINISHED"} + + +class RemoveClassification(bpy.types.Operator): + bl_idname = "bim.remove_classification" + bl_label = "Remove Classification" + classification: bpy.props.IntProperty() + + def execute(self, context): + self.file = IfcStore.get_file() + remove_classification.Usecase(self.file, {"classification": self.file.by_id(self.classification)}).execute() + Data.load() + return {"FINISHED"} + + +class EditClassification(bpy.types.Operator): + bl_idname = "bim.edit_classification" + bl_label = "Edit Classification" + + def execute(self, context): + props = context.scene.BIMClassificationProperties + attributes = {} + for attribute in props.classification_attributes: + if attribute.is_null: + attributes[attribute.name] = None + elif attribute.name == "ReferenceTokens": + attributes[attribute.name] = json.loads(attribute.string_value) + else: + attributes[attribute.name] = attribute.string_value + self.file = IfcStore.get_file() + edit_classification.Usecase( + self.file, {"classification": self.file.by_id(props.active_classification_id), "attributes": attributes} + ).execute() + Data.load() + bpy.ops.bim.disable_editing_classification() + return {"FINISHED"} + + +class EnableEditingClassificationReference(bpy.types.Operator): + bl_idname = "bim.enable_editing_classification_reference" + bl_label = "Enable Editing Classification Reference" + reference: bpy.props.IntProperty() + obj: bpy.props.StringProperty() + + def execute(self, context): + obj = bpy.data.objects.get(self.obj) if self.obj else bpy.context.active_object + props = obj.BIMClassificationReferenceProperties + while len(props.reference_attributes) > 0: + props.reference_attributes.remove(0) + reference_data = Data.references[self.reference] + for attribute in IfcStore.get_schema().declaration_by_name("IfcClassificationReference").all_attributes(): + if attribute.name() == "ReferencedSource": + continue + new = props.reference_attributes.add() + new.name = attribute.name() + new.is_null = reference_data[attribute.name()] is None + new.is_optional = attribute.optional() + new.string_value = "" if new.is_null else reference_data[attribute.name()] + props.active_reference_id = self.reference + return {"FINISHED"} + + +class DisableEditingClassificationReference(bpy.types.Operator): + bl_idname = "bim.disable_editing_classification_reference" + bl_label = "Disable Editing Classification Reference" + obj: bpy.props.StringProperty() + + def execute(self, context): + obj = bpy.data.objects.get(self.obj) if self.obj else bpy.context.active_object + obj.BIMClassificationReferenceProperties.active_reference_id = 0 + return {"FINISHED"} + + +class RemoveClassificationReference(bpy.types.Operator): + bl_idname = "bim.remove_classification_reference" + bl_label = "Remove Classification Reference" + reference: bpy.props.IntProperty() + obj: bpy.props.StringProperty() + + def execute(self, context): + obj = bpy.data.objects.get(self.obj) if self.obj else bpy.context.active_object + self.file = IfcStore.get_file() + remove_reference.Usecase( + self.file, + { + "reference": self.file.by_id(self.reference), + "product": self.file.by_id(obj.BIMObjectProperties.ifc_definition_id), + }, + ).execute() + Data.load(obj.BIMObjectProperties.ifc_definition_id) + Data.load() + return {"FINISHED"} + + +class EditClassificationReference(bpy.types.Operator): + bl_idname = "bim.edit_classification_reference" + bl_label = "Edit Classification Reference" + obj: bpy.props.StringProperty() + + def execute(self, context): + obj = bpy.data.objects.get(self.obj) if self.obj else bpy.context.active_object + props = obj.BIMClassificationReferenceProperties + attributes = {} + for attribute in props.reference_attributes: + if attribute.is_null: + attributes[attribute.name] = None + else: + attributes[attribute.name] = attribute.string_value + self.file = IfcStore.get_file() + edit_reference.Usecase( + self.file, {"reference": self.file.by_id(props.active_reference_id), "attributes": attributes} + ).execute() + Data.load() + bpy.ops.bim.disable_editing_classification_reference() + return {"FINISHED"} + + +class AddClassificationReference(bpy.types.Operator): + bl_idname = "bim.add_classification_reference" + bl_label = "Add Classification Reference" + reference: bpy.props.IntProperty() + obj: bpy.props.StringProperty() + + def execute(self, context): + obj = bpy.data.objects.get(self.obj) if self.obj else bpy.context.active_object + self.file = IfcStore.get_file() + + classification = None + + props = context.scene.BIMClassificationProperties + classification_name = Data.library_classifications[int(props.available_classifications)] + for classification_id, classification in Data.classifications.items(): + if classification["Name"] == classification_name: + classification = self.file.by_id(classification_id) + break + + add_reference.Usecase( + self.file, + { + "reference": Data.library_file.by_id(self.reference), + "product": self.file.by_id(obj.BIMObjectProperties.ifc_definition_id), + "classification": classification + }, + ).execute() + Data.load(obj.BIMObjectProperties.ifc_definition_id) + Data.load() + return {"FINISHED"} + + +class ChangeClassificationLevel(bpy.types.Operator): + bl_idname = "bim.change_classification_level" + bl_label = "Change Classification Level" + parent_id: bpy.props.IntProperty() + + def execute(self, context): + getReferences(self, context, parent_id=self.parent_id) + return {"FINISHED"} diff --git a/src/ifcblenderexport/blenderbim/bim/module/classification/prop.py b/src/ifcblenderexport/blenderbim/bim/module/classification/prop.py new file mode 100644 index 0000000000..3793b29ec3 --- /dev/null +++ b/src/ifcblenderexport/blenderbim/bim/module/classification/prop.py @@ -0,0 +1,71 @@ +import bpy +from blenderbim.bim.prop import StrProperty, Attribute +from blenderbim.bim.module.classification.data import Data +from bpy.types import PropertyGroup +from bpy.props import ( + PointerProperty, + StringProperty, + EnumProperty, + BoolProperty, + IntProperty, + FloatProperty, + FloatVectorProperty, + CollectionProperty, +) + +classification_enum = [] + + +def getClassifications(self, context): + global classification_enum + if len(classification_enum) < 1: + classification_enum.clear() + classification_enum.extend([(str(i), n, "") for i, n in Data.library_classifications.items()]) + if classification_enum: + getReferences(self, context, parent_id=int(classification_enum[0][0])) + return classification_enum + + +def updateClassification(self, context): + getReferences(self, context, parent_id=int(self.available_classifications)) + + +def getReferences(self, context, parent_id=None): + props = context.scene.BIMClassificationProperties + while len(props.available_library_references) > 0: + props.available_library_references.remove(0) + for reference in Data.library_file.by_id(parent_id).HasReferences: + new = props.available_library_references.add() + new.identification = reference.Identification or "" + new.name = reference.Name or "" + new.ifc_definition_id = reference.id() + new.has_references = bool(reference.HasReferences) + new.referenced_source + if reference.ReferencedSource.is_a("IfcClassificationReference"): + props.active_library_referenced_source = reference.ReferencedSource.ReferencedSource.id() + else: + props.active_library_referenced_source = 0 + + +class ClassificationReference(PropertyGroup): + name: StringProperty(name="Name") + identification: StringProperty(name="Identification") + ifc_definition_id: IntProperty(name="IFC Definition ID") + has_references: BoolProperty(name="Has References") + referenced_source: IntProperty(name="IFC Definition ID") + + +class BIMClassificationProperties(PropertyGroup): + available_classifications: EnumProperty( + items=getClassifications, name="Available Classifications", update=updateClassification + ) + classification_attributes: CollectionProperty(name="Classification Attributes", type=Attribute) + active_classification_id: IntProperty(name="Active Classification Id") + available_library_references: CollectionProperty(name="Available Library References", type=ClassificationReference) + active_library_referenced_source: IntProperty(name="Active Library Referenced Source") + active_library_reference_index: IntProperty(name="Active Library Reference Index") + + +class BIMClassificationReferenceProperties(PropertyGroup): + reference_attributes: CollectionProperty(name="Reference Attributes", type=Attribute) + active_reference_id: IntProperty(name="Active Reference Id") diff --git a/src/ifcblenderexport/blenderbim/bim/module/classification/remove_classification.py b/src/ifcblenderexport/blenderbim/bim/module/classification/remove_classification.py new file mode 100644 index 0000000000..a6708c871c --- /dev/null +++ b/src/ifcblenderexport/blenderbim/bim/module/classification/remove_classification.py @@ -0,0 +1,24 @@ +class Usecase: + def __init__(self, file, settings=None): + self.file = file + self.settings = {"classification": None} + for key, value in settings.items(): + self.settings[key] = value + + def execute(self): + references = self.get_references(self.settings["classification"]) + for reference in references: + self.file.remove(reference) + self.file.remove(self.settings["classification"]) + for rel in self.file.by_type("IfcRelAssociatesClassification"): + if not rel.RelatingClassification: + self.file.remove(rel) + + def get_references(self, classification): + results = [] + if not classification.HasReferences: + return results + for reference in classification.HasReferences: + results.append(reference) + results.extend(self.get_references(reference)) + return results diff --git a/src/ifcblenderexport/blenderbim/bim/module/classification/remove_reference.py b/src/ifcblenderexport/blenderbim/bim/module/classification/remove_reference.py new file mode 100644 index 0000000000..5ca45a19cf --- /dev/null +++ b/src/ifcblenderexport/blenderbim/bim/module/classification/remove_reference.py @@ -0,0 +1,28 @@ +import ifcopenshell.util.schema + + +class Usecase: + def __init__(self, file, settings=None): + self.file = file + self.settings = {"reference": None, "product": None} + for key, value in settings.items(): + self.settings[key] = value + + def execute(self): + total_related_objects = 0 + for association in self.file.by_type("IfcRelAssociatesClassification"): + if association.RelatingClassification == self.settings["reference"] and association.RelatedObjects: + total_related_objects += len(association.RelatedObjects) + related_objects = list(association.RelatedObjects) + try: + related_objects.remove(self.settings["product"]) + except: + continue + if len(related_objects): + association.RelatedObjects = related_objects + else: + self.file.remove(association) + + # TODO: we only handle lightweight classifications here + if total_related_objects == 1: + self.file.remove(self.settings["reference"]) diff --git a/src/ifcblenderexport/blenderbim/bim/module/classification/ui.py b/src/ifcblenderexport/blenderbim/bim/module/classification/ui.py new file mode 100644 index 0000000000..122201c8e9 --- /dev/null +++ b/src/ifcblenderexport/blenderbim/bim/module/classification/ui.py @@ -0,0 +1,159 @@ +from bpy.types import Panel, UIList +from blenderbim.bim.ifc import IfcStore +from blenderbim.bim.module.classification.data import Data + + +class BIM_PT_classifications(Panel): + bl_label = "IFC Classifications" + bl_idname = "BIM_PT_classifications" + bl_options = {"DEFAULT_CLOSED"} + bl_space_type = "PROPERTIES" + bl_region_type = "WINDOW" + bl_context = "scene" + + @classmethod + def poll(cls, context): + return IfcStore.get_file() + + def draw(self, context): + if not Data.is_loaded: + Data.load() + + self.props = context.scene.BIMClassificationProperties + + if Data.library_file: + row = self.layout.row(align=True) + row.prop(self.props, "available_classifications", text="") + row.operator("bim.load_classification_library", text="", icon="IMPORT") + row.operator("bim.add_classification", text="", icon="ADD") + else: + row = self.layout.row(align=True) + row.label(text="No Active Classification Library") + row.operator("bim.load_classification_library", text="", icon="IMPORT") + + for classification_id, classification in Data.classifications.items(): + if self.props.active_classification_id == classification_id: + self.draw_editable_ui(classification) + else: + self.draw_ui(classification_id, classification) + + def draw_editable_ui(self, classification): + row = self.layout.row(align=True) + row.prop(self.props.classification_attributes.get("Name"), "string_value", text="", icon="ASSET_MANAGER") + row.operator("bim.edit_classification", text="", icon="CHECKMARK") + row.operator("bim.disable_editing_classification", text="", icon="X") + + for attribute in self.props.classification_attributes: + if attribute.name == "Name": + continue + row = self.layout.row(align=True) + row.prop(attribute, "string_value", text=attribute.name) + if attribute.is_optional: + row.prop(attribute, "is_null", icon="RADIOBUT_OFF" if attribute.is_null else "RADIOBUT_ON", text="") + + def draw_ui(self, classification_id, classification): + row = self.layout.row(align=True) + row.label(text=classification["Name"], icon="ASSET_MANAGER") + if not self.props.active_classification_id: + op = row.operator("bim.enable_editing_classification", text="", icon="GREASEPENCIL") + op.classification = classification_id + row.operator("bim.remove_classification", text="", icon="X").classification = classification_id + + +class BIM_PT_classification_references(Panel): + bl_label = "IFC Classification References" + bl_idname = "BIM_PT_classification_references" + bl_options = {"DEFAULT_CLOSED"} + bl_space_type = "PROPERTIES" + bl_region_type = "WINDOW" + bl_context = "object" + + @classmethod + def poll(cls, context): + return bool(context.active_object.BIMObjectProperties.ifc_definition_id) + + def draw(self, context): + obj = context.active_object + self.oprops = obj.BIMObjectProperties + self.sprops = context.scene.BIMClassificationProperties + self.props = obj.BIMClassificationReferenceProperties + self.file = IfcStore.get_file() + if not Data.is_loaded: + Data.load() + if self.oprops.ifc_definition_id not in Data.products: + Data.load(self.oprops.ifc_definition_id) + + self.draw_add_ui() + + reference_ids = Data.products[self.oprops.ifc_definition_id] + if not reference_ids: + row = self.layout.row(align=True) + row.label(text="No References") + + for reference_id in reference_ids: + reference = Data.references[reference_id] + if self.props.active_reference_id == reference_id: + self.draw_editable_ui(reference) + else: + self.draw_ui(reference_id, reference) + + def draw_add_ui(self): + if not self.sprops.available_classifications: + return + + name = Data.library_classifications[int(self.sprops.available_classifications)] + if name in [c["Name"] for c in Data.classifications.values()]: + row = self.layout.row(align=True) + row.prop(self.sprops, "available_classifications", text="") + if self.sprops.active_library_referenced_source: + op = row.operator("bim.change_classification_level", text="", icon="FRAME_PREV") + op.parent_id = self.sprops.active_library_referenced_source + op = row.operator("bim.add_classification_reference", text="", icon="ADD") + op.reference = self.sprops.available_library_references[ + self.sprops.active_library_reference_index + ].ifc_definition_id + self.layout.template_list( + "BIM_UL_classifications", + "", + self.sprops, + "available_library_references", + self.sprops, + "active_library_reference_index", + ) + + def draw_editable_ui(self, reference): + row = self.layout.row(align=True) + row.prop(self.props.reference_attributes.get("Name"), "string_value", text="", icon="ASSET_MANAGER") + row.operator("bim.edit_classification_reference", text="", icon="CHECKMARK") + row.operator("bim.disable_editing_classification_reference", text="", icon="X") + + for attribute in self.props.reference_attributes: + if attribute.name == "Name": + continue + row = self.layout.row(align=True) + row.prop(attribute, "string_value", text=attribute.name) + if attribute.is_optional: + row.prop(attribute, "is_null", icon="RADIOBUT_OFF" if attribute.is_null else "RADIOBUT_ON", text="") + + def draw_ui(self, reference_id, reference): + row = self.layout.row(align=True) + if self.file.schema == "IFC2X3": + name = reference["ItemReference"] or "No Identification" + else: + name = reference["Identification"] or "No Identification" + row.label(text=name, icon="ASSET_MANAGER") + row.label(text=reference["Name"] or "") + if not self.props.active_reference_id: + op = row.operator("bim.enable_editing_classification_reference", text="", icon="GREASEPENCIL") + op.reference = reference_id + row.operator("bim.remove_classification_reference", text="", icon="X").reference = reference_id + + +class BIM_UL_classifications(UIList): + def draw_item(self, context, layout, data, item, icon, active_data, active_propname): + if item: + if item.has_references: + op = layout.operator("bim.change_classification_level", text="", icon="DISCLOSURE_TRI_RIGHT") + op.parent_id = item.ifc_definition_id + layout.label(text=item.identification) + layout.label(text=item.name) diff --git a/src/ifcblenderexport/blenderbim/bim/module/constraint/__init__.py b/src/ifcblenderexport/blenderbim/bim/module/constraint/__init__.py new file mode 100644 index 0000000000..190ce3fe12 --- /dev/null +++ b/src/ifcblenderexport/blenderbim/bim/module/constraint/__init__.py @@ -0,0 +1,33 @@ +import bpy +from . import ui, prop, operator + +classes = ( + operator.LoadObjectives, + operator.DisableConstraintEditingUI, + operator.EnableEditingConstraint, + operator.DisableEditingConstraint, + operator.AddObjective, + operator.EditObjective, + operator.RemoveConstraint, + operator.EnableAssigningConstraint, + operator.DisableAssigningConstraint, + operator.AssignConstraint, + operator.UnassignConstraint, + prop.Constraint, + prop.BIMConstraintProperties, + prop.BIMObjectConstraintProperties, + ui.BIM_PT_constraints, + ui.BIM_PT_object_constraints, + ui.BIM_UL_constraints, + ui.BIM_UL_object_constraints, +) + + +def register(): + bpy.types.Scene.BIMConstraintProperties = bpy.props.PointerProperty(type=prop.BIMConstraintProperties) + bpy.types.Object.BIMObjectConstraintProperties = bpy.props.PointerProperty(type=prop.BIMObjectConstraintProperties) + + +def unregister(): + del bpy.types.Scene.BIMConstraintProperties + del bpy.types.Object.BIMObjectConstraintProperties diff --git a/src/ifcblenderexport/blenderbim/bim/module/constraint/add_objective.py b/src/ifcblenderexport/blenderbim/bim/module/constraint/add_objective.py new file mode 100644 index 0000000000..5891bfb49d --- /dev/null +++ b/src/ifcblenderexport/blenderbim/bim/module/constraint/add_objective.py @@ -0,0 +1,15 @@ +import ifcopenshell + +class Usecase: + def __init__(self, file, settings={}): + self.file = file + self.settings = {} + for key, value in settings.items(): + self.settings[key] = value + + def execute(self): + return self.file.create_entity("IfcObjective", **{ + "Name": "Unnamed", + "ConstraintGrade": "NOTDEFINED", + "ObjectiveQualifier": "NOTDEFINED" + }) diff --git a/src/ifcblenderexport/blenderbim/bim/module/constraint/assign_constraint.py b/src/ifcblenderexport/blenderbim/bim/module/constraint/assign_constraint.py new file mode 100644 index 0000000000..6a8a8755ae --- /dev/null +++ b/src/ifcblenderexport/blenderbim/bim/module/constraint/assign_constraint.py @@ -0,0 +1,28 @@ +import ifcopenshell + + +class Usecase: + def __init__(self, file, settings=None): + self.file = file + self.settings = { + "product": None, + "constraint": None, + } + for key, value in settings.items(): + self.settings[key] = value + + def execute(self): + rel = self.get_constraint_rel() + related_objects = set(rel.RelatedObjects) if rel.RelatedObjects else set() + related_objects.add(self.settings["product"]) + rel.RelatedObjects = list(related_objects) + + def get_constraint_rel(self): + for rel in self.file.by_type("IfcRelAssociatesConstraint"): + if rel.RelatingConstraint == self.settings["constraint"]: + return rel + return self.file.create_entity("IfcRelAssociatesConstraint", **{ + "GlobalId": ifcopenshell.guid.new(), + # TODO: owner history + "RelatingConstraint": self.settings["constraint"] + }) diff --git a/src/ifcblenderexport/blenderbim/bim/module/constraint/data.py b/src/ifcblenderexport/blenderbim/bim/module/constraint/data.py new file mode 100644 index 0000000000..e756d9f2c8 --- /dev/null +++ b/src/ifcblenderexport/blenderbim/bim/module/constraint/data.py @@ -0,0 +1,43 @@ +import ifcopenshell +import ifcopenshell.util.date +from blenderbim.bim.ifc import IfcStore +from datetime import datetime + + +class Data: + is_loaded = False + products = {} + objectives = {} + + @classmethod + def load(cls, product_id=None): + cls._file = IfcStore.get_file() + if not cls._file: + return + if product_id: + return cls.load_product(product_id) + cls.load_objectives() + cls.is_loaded = True + + @classmethod + def load_product(cls, product_id): + product = cls._file.by_id(product_id) + cls.products[product_id] = [] + if not product.HasAssociations: + return + for association in product.HasAssociations: + if association.is_a("IfcRelAssociatesConstraint"): + if not association.RelatingConstraint.is_a("IfcObjective"): + continue # not yet implemented + cls.products[product_id].append(association.RelatingConstraint.id()) + + @classmethod + def load_objectives(cls): + cls.objectives = {} + for constraint in cls._file.by_type("IfcObjective"): + data = constraint.get_info() + if cls._file.schema == "IFC2X3": + for attribute in ["CreationTime"]: + if data[attribute]: + data[attribute] = ifcopenshell.util.date.ifc2datetime(data[attribute]).isoformat() + cls.objectives[constraint.id()] = data diff --git a/src/ifcblenderexport/blenderbim/bim/module/constraint/edit_objective.py b/src/ifcblenderexport/blenderbim/bim/module/constraint/edit_objective.py new file mode 100644 index 0000000000..9a1003ba70 --- /dev/null +++ b/src/ifcblenderexport/blenderbim/bim/module/constraint/edit_objective.py @@ -0,0 +1,13 @@ +class Usecase: + def __init__(self, file, settings=None): + self.file = file + self.settings = { + "objective": None, + "attributes": {} + } + for key, value in settings.items(): + self.settings[key] = value + + def execute(self): + for name, value in self.settings["attributes"].items(): + setattr(self.settings["objective"], name, value) diff --git a/src/ifcblenderexport/blenderbim/bim/module/constraint/operator.py b/src/ifcblenderexport/blenderbim/bim/module/constraint/operator.py new file mode 100644 index 0000000000..999f2dace7 --- /dev/null +++ b/src/ifcblenderexport/blenderbim/bim/module/constraint/operator.py @@ -0,0 +1,188 @@ +import bpy +import json +import blenderbim.bim.module.constraint.add_objective as add_objective +import blenderbim.bim.module.constraint.edit_objective as edit_objective +import blenderbim.bim.module.constraint.remove_constraint as remove_constraint +import blenderbim.bim.module.constraint.assign_constraint as assign_constraint +import blenderbim.bim.module.constraint.unassign_constraint as unassign_constraint +from blenderbim.bim.ifc import IfcStore +from blenderbim.bim.module.constraint.data import Data + + +class LoadObjectives(bpy.types.Operator): + bl_idname = "bim.load_objectives" + bl_label = "Load Objectives" + + def execute(self, context): + props = context.scene.BIMConstraintProperties + while len(props.constraints) > 0: + props.constraints.remove(0) + for constraint_id, constraint in Data.objectives.items(): + new = props.constraints.add() + new.name = constraint["Name"] or "Unnamed" + new.ifc_definition_id = constraint_id + props.is_editing = "IfcObjective" + bpy.ops.bim.disable_editing_constraint() + return {"FINISHED"} + + +class DisableConstraintEditingUI(bpy.types.Operator): + bl_idname = "bim.disable_constraint_editing_ui" + bl_label = "Disable Constraint Editing UI" + + def execute(self, context): + context.scene.BIMConstraintProperties.is_editing = "" + bpy.ops.bim.disable_editing_constraint() + return {"FINISHED"} + + +class EnableEditingConstraint(bpy.types.Operator): + bl_idname = "bim.enable_editing_constraint" + bl_label = "Enable Editing Constraint" + constraint: bpy.props.IntProperty() + + def execute(self, context): + props = context.scene.BIMConstraintProperties + while len(props.constraint_attributes) > 0: + props.constraint_attributes.remove(0) + + if props.is_editing == "IfcObjective": + data = Data.objectives[self.constraint] + + for attribute in IfcStore.get_schema().declaration_by_name(props.is_editing).all_attributes(): + data_type = str(attribute.type_of_attribute) + if "" in data_type: + new.string_value = "" if new.is_null else data[attribute.name()] + new.data_type = "string" + elif " 0: + props.documents.remove(0) + for information_id, information in Data.information.items(): + new = props.documents.add() + new.name = information["Name"] or "Unnamed" + if self.file.schema == "IFC2X3": + new.identification = information["DocumentId"] or "*" + else: + new.identification = information["Identification"] or "*" + new.ifc_definition_id = information_id + props.is_editing = "information" + bpy.ops.bim.disable_editing_document() + return {"FINISHED"} + + +class LoadDocumentReferences(bpy.types.Operator): + bl_idname = "bim.load_document_references" + bl_label = "Load Document References" + + def execute(self, context): + self.file = IfcStore.get_file() + props = context.scene.BIMDocumentProperties + while len(props.documents) > 0: + props.documents.remove(0) + for reference_id, reference in Data.references.items(): + new = props.documents.add() + new.name = reference["Name"] or "Unnamed" + if self.file.schema == "IFC2X3": + new.identification = reference["ItemReference"] or "*" + else: + new.identification = reference["Identification"] or "*" + new.ifc_definition_id = reference_id + props.is_editing = "reference" + bpy.ops.bim.disable_editing_document() + return {"FINISHED"} + + +class DisableDocumentEditingUI(bpy.types.Operator): + bl_idname = "bim.disable_document_editing_ui" + bl_label = "Disable Document Editing UI" + + def execute(self, context): + context.scene.BIMDocumentProperties.is_editing = "" + bpy.ops.bim.disable_editing_document() + return {"FINISHED"} + + +class EnableEditingDocument(bpy.types.Operator): + bl_idname = "bim.enable_editing_document" + bl_label = "Enable Editing Document" + document: bpy.props.IntProperty() + + def execute(self, context): + props = context.scene.BIMDocumentProperties + while len(props.document_attributes) > 0: + props.document_attributes.remove(0) + + if props.is_editing == "information": + data = Data.information[self.document] + ifc_class = "IfcDocumentInformation" + elif props.is_editing == "reference": + data = Data.references[self.document] + ifc_class = "IfcDocumentReference" + + for attribute in IfcStore.get_schema().declaration_by_name(ifc_class).all_attributes(): + data_type = str(attribute.type_of_attribute) + if "" in data_type: + new.string_value = "" if new.is_null else data[attribute.name()] + new.data_type = "string" + elif " 0: + props.layers.remove(0) + for layer_id, layer in Data.layers.items(): + new = props.layers.add() + new.name = layer["Name"] or "Unnamed" + new.ifc_definition_id = layer_id + props.is_editing = True + bpy.ops.bim.disable_editing_layer() + return {"FINISHED"} + + +class DisableLayerEditingUI(bpy.types.Operator): + bl_idname = "bim.disable_layer_editing_ui" + bl_label = "Disable Layer Editing UI" + + def execute(self, context): + context.scene.BIMLayerProperties.is_editing = False + return {"FINISHED"} + + +class EnableEditingLayer(bpy.types.Operator): + bl_idname = "bim.enable_editing_layer" + bl_label = "Enable Editing Layer" + layer: bpy.props.IntProperty() + + def execute(self, context): + props = context.scene.BIMLayerProperties + while len(props.layer_attributes) > 0: + props.layer_attributes.remove(0) + + data = Data.layers[self.layer] + + for attribute in IfcStore.get_schema().declaration_by_name("IfcPresentationLayerAssignment").all_attributes(): + data_type = str(attribute.type_of_attribute) + if " 0: + props.property_templates.remove(0) + return {"FINISHED"} + + +class RemovePropertySetTemplate(bpy.types.Operator): + bl_idname = "bim.remove_property_set_template" + bl_label = "Remove Property Set Template" + + def execute(self, context): + props = context.scene.BIMPsetTemplateProperties + template = IfcStore.pset_template_file.by_guid(props.property_set_templates) + IfcStore.pset_template_file.remove(template) + IfcStore.pset_template_file.write(IfcStore.pset_template_path) + refreshPropertySetTemplates(self, context) + return {"FINISHED"} + + +class EditPropertySetTemplate(bpy.types.Operator): + bl_idname = "bim.edit_property_set_template" + bl_label = "Edit Property Set Template" + + def execute(self, context): + props = context.scene.BIMPsetTemplateProperties + template = IfcStore.pset_template_file.by_guid(props.property_set_templates) + props.active_property_set_template.global_id = template.GlobalId + props.active_property_set_template.name = template.Name + props.active_property_set_template.description = template.Description + props.active_property_set_template.template_type = template.TemplateType + props.active_property_set_template.applicable_entity = template.ApplicableEntity + + while len(props.property_templates) > 0: + props.property_templates.remove(0) + + if template.HasPropertyTemplates: + for property_template in template.HasPropertyTemplates: + if not property_template.is_a("IfcSimplePropertyTemplate"): + continue + new = props.property_templates.add() + new.global_id = property_template.GlobalId + new.name = property_template.Name + new.description = property_template.Description + new.primary_measure_type = property_template.PrimaryMeasureType + return {"FINISHED"} + + +class SavePropertySetTemplate(bpy.types.Operator): + bl_idname = "bim.save_property_set_template" + bl_label = "Save Property Set Template" + + def execute(self, context): + props = context.scene.BIMPsetTemplateProperties + blender_property_set_template = props.active_property_set_template + if blender_property_set_template.global_id: + template = IfcStore.pset_template_file.by_guid(blender_property_set_template.global_id) + else: + template = IfcStore.pset_template_file.createIfcPropertySetTemplate() + template.GlobalId = ifcopenshell.guid.new() + template.Name = blender_property_set_template.name + template.Description = blender_property_set_template.description + template.TemplateType = blender_property_set_template.template_type + template.ApplicableEntity = blender_property_set_template.applicable_entity + + saved_global_ids = [] + + for blender_property_template in props.property_templates: + if blender_property_template.global_id: + property_template = IfcStore.pset_template_file.by_guid(blender_property_template.global_id) + else: + property_template = IfcStore.pset_template_file.createIfcSimplePropertyTemplate() + property_template.GlobalId = ifcopenshell.guid.new() + if template.HasPropertyTemplates: + has_property_templates = list(template.HasPropertyTemplates) + else: + has_property_templates = [] + has_property_templates.append(property_template) + template.HasPropertyTemplates = has_property_templates + property_template.Name = blender_property_template.name + property_template.Description = blender_property_template.description + property_template.PrimaryMeasureType = blender_property_template.primary_measure_type + property_template.TemplateType = "P_SINGLEVALUE" + property_template.AccessState = "READWRITE" + saved_global_ids.append(property_template.GlobalId) + + for element in template.HasPropertyTemplates: + if element.GlobalId not in saved_global_ids: + IfcStore.pset_template_file.remove(element) + + IfcStore.pset_template_file.write(IfcStore.pset_template_path) + refreshPropertySetTemplates(self, context) + return {"FINISHED"} + + +class AddPropertyTemplate(bpy.types.Operator): + bl_idname = "bim.add_property_template" + bl_label = "Add Property Template" + + def execute(self, context): + context.scene.BIMPsetTemplateProperties.property_templates.add() + return {"FINISHED"} + + +class RemovePropertyTemplate(bpy.types.Operator): + bl_idname = "bim.remove_property_template" + bl_label = "Remove Property Template" + index: bpy.props.IntProperty() + + def execute(self, context): + bpy.context.scene.BIMPsetTemplateProperties.property_templates.remove(self.index) + return {"FINISHED"} diff --git a/src/ifcblenderexport/blenderbim/bim/module/pset_template/prop.py b/src/ifcblenderexport/blenderbim/bim/module/pset_template/prop.py new file mode 100644 index 0000000000..c2c5690d6c --- /dev/null +++ b/src/ifcblenderexport/blenderbim/bim/module/pset_template/prop.py @@ -0,0 +1,229 @@ +import os +import bpy +import ifcopenshell +from blenderbim.bim.prop import StrProperty, Attribute +from blenderbim.bim.ifc import IfcStore +from bpy.types import PropertyGroup +from bpy.props import ( + PointerProperty, + StringProperty, + EnumProperty, + BoolProperty, + IntProperty, + FloatProperty, + FloatVectorProperty, + CollectionProperty, +) + + +psettemplatefiles_enum = [] +propertysettemplates_enum = [] + + +def refreshPropertySetTemplates(self, context): + global propertysettemplates_enum + propertysettemplates_enum.clear() + getPropertySetTemplates(self, context) + + +def getPsetTemplateFiles(self, context): + global psettemplatefiles_enum + if len(psettemplatefiles_enum) < 1: + files = os.listdir(os.path.join(context.scene.BIMProperties.data_dir, "pset")) + psettemplatefiles_enum.extend([(f.replace(".ifc", ""), f.replace(".ifc", ""), "") for f in files]) + return psettemplatefiles_enum + + +def getPropertySetTemplates(self, context): + global propertysettemplates_enum + if len(propertysettemplates_enum) < 1: + IfcStore.pset_template_path = os.path.join( + context.scene.BIMProperties.data_dir, "pset", context.scene.BIMPsetTemplateProperties.pset_template_files + ".ifc" + ) + IfcStore.pset_template_file = ifcopenshell.open(IfcStore.pset_template_path) + templates = IfcStore.pset_template_file.by_type("IfcPropertySetTemplate") + propertysettemplates_enum.extend([(t.GlobalId, t.Name, "") for t in templates]) + return propertysettemplates_enum + + +class PropertySetTemplate(PropertyGroup): + global_id: StringProperty(name="Global ID") + name: StringProperty(name="Name") + description: StringProperty(name="Description") + template_type: EnumProperty( + items=[ + ( + "PSET_TYPEDRIVENONLY", + "Pset - IfcTypeObject", + "The property sets defined by this IfcPropertySetTemplate can only be assigned to subtypes of IfcTypeObject.", + ), + ( + "PSET_TYPEDRIVENOVERRIDE", + "Pset - IfcTypeObject - Override", + "The property sets defined by this IfcPropertySetTemplate can only be assigned to subtypes of IfcTypeObject.", + ), + ( + "PSET_OCCURRENCEDRIVEN", + "Pset - IfcObject", + "The property sets defined by this IfcPropertySetTemplate can only be assigned to subtypes of IfcObject.", + ), + ( + "PSET_PERFORMANCEDRIVEN", + "Pset - IfcPerformanceHistory", + "The property sets defined by this IfcPropertySetTemplate can only be assigned to IfcPerformanceHistory.", + ), + ( + "QTO_TYPEDRIVENONLY", + "Qto - IfcTypeObject", + "The element quantity defined by this IfcPropertySetTemplate can only be assigned to subtypes of IfcTypeObject.", + ), + ( + "QTO_TYPEDRIVENOVERRIDE", + "Qto - IfcTypeObject - Override", + "The element quantity defined by this IfcPropertySetTemplate can be assigned to subtypes of IfcTypeObject and can be overridden by an element quantity with same name at subtypes of IfcObject.", + ), + ( + "QTO_OCCURRENCEDRIVEN", + "Qto - IfcObject", + "The element quantity defined by this IfcPropertySetTemplate can only be assigned to subtypes of IfcObject.", + ), + ( + "NOTDEFINED", + "Not defined", + "No restriction provided, the property sets defined by this IfcPropertySetTemplate can be assigned to any entity, if not otherwise restricted by the ApplicableEntity attribute.", + ), + ], + name="Template Type", + ) + applicable_entity: StringProperty(name="Applicable Entity") + + +class PropertyTemplate(PropertyGroup): + global_id: StringProperty(name="Global ID") + name: StringProperty(name="Name") + description: StringProperty(name="Description") + primary_measure_type: EnumProperty( + items=[ + (x, x, "") + for x in [ + "IfcInteger", + "IfcReal", + "IfcBoolean", + "IfcIdentifier", + "IfcText", + "IfcLabel", + "IfcLogical", + "IfcDateTime", + "IfcDate", + "IfcTime", + "IfcDuration", + "IfcTimeStamp", + "IfcPositiveInteger", + "IfcBinary", + "IfcVolumeMeasure", + "IfcTimeMeasure", + "IfcThermodynamicTemperatureMeasure", + "IfcSolidAngleMeasure", + "IfcPositiveRatioMeasure", + "IfcRatioMeasure", + "IfcPositivePlaneAngleMeasure", + "IfcPlaneAngleMeasure", + "IfcParameterValue", + "IfcNumericMeasure", + "IfcMassMeasure", + "IfcPositiveLengthMeasure", + "IfcLengthMeasure", + "IfcElectricCurrentMeasure", + "IfcDescriptiveMeasure", + "IfcCountMeasure", + "IfcContextDependentMeasure", + "IfcAreaMeasure", + "IfcAmountOfSubstanceMeasure", + "IfcLuminousIntensityMeasure", + "IfcNormalisedRatioMeasure", + "IfcComplexNumber", + "IfcNonNegativeLengthMeasure", + "IfcAbsorbedDoseMeasure", + "IfcAccelerationMeasure", + "IfcAngularVelocityMeasure", + "IfcAreaDensityMeasure", + "IfcCompoundPlaneAngleMeasure", + "IfcCurvatureMeasure", + "IfcDoseEquivalentMeasure", + "IfcDynamicViscosityMeasure", + "IfcElectricCapacitanceMeasure", + "IfcElectricChargeMeasure", + "IfcElectricConductanceMeasure", + "IfcElectricResistanceMeasure", + "IfcElectricVoltageMeasure", + "IfcEnergyMeasure", + "IfcForceMeasure", + "IfcFrequencyMeasure", + "IfcHeatFluxDensityMeasure", + "IfcHeatingValueMeasure", + "IfcIlluminanceMeasure", + "IfcInductanceMeasure", + "IfcIntegerCountRateMeasure", + "IfcIonConcentrationMeasure", + "IfcIsothermalMoistureCapacityMeasure", + "IfcKinematicViscosityMeasure", + "IfcLinearForceMeasure", + "IfcLinearMomentMeasure", + "IfcLinearStiffnessMeasure", + "IfcLinearVelocityMeasure", + "IfcLuminousFluxMeasure", + "IfcLuminousIntensityDistributionMeasure", + "IfcMagneticFluxDensityMeasure", + "IfcMagneticFluxMeasure", + "IfcMassDensityMeasure", + "IfcMassFlowRateMeasure", + "IfcMassPerLengthMeasure", + "IfcModulusOfElasticityMeasure", + "IfcModulusOfLinearSubgradeReactionMeasure", + "IfcModulusOfRotationalSubgradeReactionMeasure", + "IfcModulusOfSubgradeReactionMeasure", + "IfcMoistureDiffusivityMeasure", + "IfcMolecularWeightMeasure", + "IfcMomentOfInertiaMeasure", + "IfcMonetaryMeasure", + "IfcPHMeasure", + "IfcPlanarForceMeasure", + "IfcPowerMeasure", + "IfcPressureMeasure", + "IfcRadioActivityMeasure", + "IfcRotationalFrequencyMeasure", + "IfcRotationalMassMeasure", + "IfcRotationalStiffnessMeasure", + "IfcSectionModulusMeasure", + "IfcSectionalAreaIntegralMeasure", + "IfcShearModulusMeasure", + "IfcSoundPowerLevelMeasure", + "IfcSoundPowerMeasure", + "IfcSoundPressureLevelMeasure", + "IfcSoundPressureMeasure", + "IfcSpecificHeatCapacityMeasure", + "IfcTemperatureGradientMeasure", + "IfcTemperatureRateOfChangeMeasure", + "IfcThermalAdmittanceMeasure", + "IfcThermalConductivityMeasure", + "IfcThermalExpansionCoefficientMeasure", + "IfcThermalResistanceMeasure", + "IfcThermalTransmittanceMeasure", + "IfcTorqueMeasure", + "IfcVaporPermeabilityMeasure", + "IfcVolumetricFlowRateMeasure", + "IfcWarpingConstantMeasure", + "IfcWarpingMomentMeasure", + ] + ], + name="Primary Measure Type", + ) + + +class BIMPsetTemplateProperties(PropertyGroup): + pset_template_files: EnumProperty( + items=getPsetTemplateFiles, name="Pset Template Files", update=refreshPropertySetTemplates + ) + property_set_templates: EnumProperty(items=getPropertySetTemplates, name="Pset Template Files") + active_property_set_template: PointerProperty(type=PropertySetTemplate) + property_templates: CollectionProperty(name="Property Templates", type=PropertyTemplate) diff --git a/src/ifcblenderexport/blenderbim/bim/module/pset_template/ui.py b/src/ifcblenderexport/blenderbim/bim/module/pset_template/ui.py new file mode 100644 index 0000000000..881b175a0c --- /dev/null +++ b/src/ifcblenderexport/blenderbim/bim/module/pset_template/ui.py @@ -0,0 +1,47 @@ +import bpy +from bpy.types import Panel + + +class BIM_PT_pset_template(Panel): + bl_label = "IFC Property Set Templates" + bl_idname = "BIM_PT_pset_template" + bl_options = {"DEFAULT_CLOSED"} + bl_space_type = "PROPERTIES" + bl_region_type = "WINDOW" + bl_context = "scene" + + def draw(self, context): + layout = self.layout + props = context.scene.BIMPsetTemplateProperties + + row = layout.row(align=True) + row.prop(props, "pset_template_files", text="") + + row = layout.row(align=True) + row.prop(props, "property_set_templates", text="") + row.operator("bim.add_property_set_template", text="", icon="ADD") + row.operator("bim.remove_property_set_template", text="", icon="PANEL_CLOSE") + row.operator("bim.edit_property_set_template", text="", icon="IMPORT") + row.operator("bim.save_property_set_template", text="", icon="EXPORT") + + row = layout.row(align=True) + row.prop(props.active_property_set_template, "name") + row = layout.row(align=True) + row.prop(props.active_property_set_template, "description") + row = layout.row(align=True) + row.prop(props.active_property_set_template, "template_type") + row = layout.row(align=True) + row.prop(props.active_property_set_template, "applicable_entity") + + layout.label(text="Property Templates:") + + row = layout.row(align=True) + row.operator("bim.add_property_template") + + for index, template in enumerate(props.property_templates): + row = layout.row(align=True) + row.prop(template, "name", text="") + row.prop(template, "description", text="") + row.prop(template, "primary_measure_type", text="") + row.operator("bim.remove_property_template", icon="X", text="").index = index + diff --git a/src/ifcblenderexport/blenderbim/bim/module/qto/__init__.py b/src/ifcblenderexport/blenderbim/bim/module/qto/__init__.py new file mode 100644 index 0000000000..cc11de2cde --- /dev/null +++ b/src/ifcblenderexport/blenderbim/bim/module/qto/__init__.py @@ -0,0 +1,18 @@ +import bpy +from . import ui, prop, operator + +classes = ( + operator.CalculateEdgeLengths, + operator.CalculateFaceAreas, + operator.CalculateObjectVolumes, + prop.BIMQtoProperties, + ui.BIM_PT_qto_utilities, +) + + +def register(): + bpy.types.Scene.BIMQtoProperties = bpy.props.PointerProperty(type=prop.BIMQtoProperties) + + +def unregister(): + del bpy.types.Scene.BIMQtoProperties diff --git a/src/ifcblenderexport/blenderbim/bim/module/qto/operator.py b/src/ifcblenderexport/blenderbim/bim/module/qto/operator.py new file mode 100644 index 0000000000..a83c3183ff --- /dev/null +++ b/src/ifcblenderexport/blenderbim/bim/module/qto/operator.py @@ -0,0 +1,51 @@ +import bpy +import bmesh + + +class CalculateEdgeLengths(bpy.types.Operator): + bl_idname = "bim.calculate_edge_lengths" + bl_label = "Calculate Edge Lengths" + + def execute(self, context): + result = 0 + for obj in bpy.context.selected_objects: + if not obj.data or not obj.data.edges: + continue + for edge in obj.data.edges: + if edge.select: + result += (obj.data.vertices[edge.vertices[1]].co - obj.data.vertices[edge.vertices[0]].co).length + bpy.context.scene.BIMQtoProperties.qto_result = str(round(result, 3)) + return {"FINISHED"} + + +class CalculateFaceAreas(bpy.types.Operator): + bl_idname = "bim.calculate_face_areas" + bl_label = "Calculate Face Areas" + + def execute(self, context): + result = 0 + for obj in bpy.context.selected_objects: + if not obj.data or not obj.data.polygons: + continue + for polygon in obj.data.polygons: + if polygon.select: + result += polygon.area + bpy.context.scene.BIMQtoProperties.qto_result = str(round(result, 3)) + return {"FINISHED"} + + +class CalculateObjectVolumes(bpy.types.Operator): + bl_idname = "bim.calculate_object_volumes" + bl_label = "Calculate Object Volumes" + + def execute(self, context): + result = 0 + for obj in bpy.context.selected_objects: + if not obj.data or not isinstance(obj.data, bpy.types.Mesh): + continue + bm = bmesh.new() + bm.from_mesh(obj.data) + result += bm.calc_volume() + bm.free() + bpy.context.scene.BIMQtoProperties.qto_result = str(round(result, 3)) + return {"FINISHED"} diff --git a/src/ifcblenderexport/blenderbim/bim/module/qto/prop.py b/src/ifcblenderexport/blenderbim/bim/module/qto/prop.py new file mode 100644 index 0000000000..ef3b6f0369 --- /dev/null +++ b/src/ifcblenderexport/blenderbim/bim/module/qto/prop.py @@ -0,0 +1,17 @@ +import bpy +from blenderbim.bim.prop import StrProperty, Attribute +from blenderbim.bim.module.owner.data import Data +from bpy.types import PropertyGroup +from bpy.props import ( + PointerProperty, + StringProperty, + EnumProperty, + BoolProperty, + IntProperty, + FloatProperty, + FloatVectorProperty, + CollectionProperty, +) + +class BIMQtoProperties(PropertyGroup): + qto_result: StringProperty(default="", name="Qto Result") diff --git a/src/ifcblenderexport/blenderbim/bim/module/qto/ui.py b/src/ifcblenderexport/blenderbim/bim/module/qto/ui.py new file mode 100644 index 0000000000..d31283183a --- /dev/null +++ b/src/ifcblenderexport/blenderbim/bim/module/qto/ui.py @@ -0,0 +1,23 @@ +from bpy.types import Panel + + +class BIM_PT_qto_utilities(Panel): + bl_idname = "BIM_PT_qto_utilities" + bl_label = "Quantity Take-off" + bl_space_type = "VIEW_3D" + bl_region_type = "UI" + bl_category = "BlenderBIM" + + def draw(self, context): + layout = self.layout + props = context.scene.BIMQtoProperties + + row = layout.row() + row.prop(props, "qto_result", text="Results") + + row = layout.row(align=True) + row.operator("bim.calculate_edge_lengths") + row = layout.row(align=True) + row.operator("bim.calculate_face_areas") + row = layout.row(align=True) + row.operator("bim.calculate_object_volumes") diff --git a/src/ifcblenderexport/blenderbim/bim/module/root/__init__.py b/src/ifcblenderexport/blenderbim/bim/module/root/__init__.py index 32d3c5e695..0d41a5eba9 100644 --- a/src/ifcblenderexport/blenderbim/bim/module/root/__init__.py +++ b/src/ifcblenderexport/blenderbim/bim/module/root/__init__.py @@ -1,5 +1,5 @@ import bpy -from . import ui, operator +from . import ui, prop, operator classes = ( operator.EnableReassignClass, @@ -7,13 +7,16 @@ classes = ( operator.ReassignClass, operator.AssignClass, operator.UnassignClass, + operator.UnlinkObject, + operator.CopyClass, + prop.BIMRootProperties, ui.BIM_PT_class, ) def register(): - pass + bpy.types.Scene.BIMRootProperties = bpy.props.PointerProperty(type=prop.BIMRootProperties) def unregister(): - pass + del bpy.types.Scene.BIMRootProperties diff --git a/src/ifcblenderexport/blenderbim/bim/module/root/copy_class.py b/src/ifcblenderexport/blenderbim/bim/module/root/copy_class.py new file mode 100644 index 0000000000..04ba418578 --- /dev/null +++ b/src/ifcblenderexport/blenderbim/bim/module/root/copy_class.py @@ -0,0 +1,37 @@ +import ifcopenshell + + +class Usecase: + def __init__(self, file, settings=None): + self.file = file + self.settings = {"product": None} + for key, value in settings.items(): + self.settings[key] = value + + def execute(self): + result = self.file.create_entity(self.settings["product"].is_a()) + self.schema = ifcopenshell.ifcopenshell_wrapper.schema_by_name(self.file.schema) + self.copy_attributes(self.settings["product"], result) + for inverse in self.file.get_inverse(self.settings["product"]): + for i, value in enumerate(inverse): + if value == self.settings["product"]: + new_inverse = self.file.create_entity(inverse.is_a()) + self.copy_attributes(inverse, new_inverse) + new_inverse[i] = result + elif isinstance(value, (tuple, list)) and self.settings["product"] in value: + new_value = list(value) + new_value.append(result) + inverse[i] = new_value + if result.is_a("IfcProduct"): + result.Representation = None + elif result.is_a("IfcTypeProduct"): + result.RepresentationMaps = None + return result + + def copy_attributes(self, from_element, to_element): + declaration = self.schema.declaration_by_name(from_element.is_a()) + for attribute in declaration.all_attributes(): + if attribute.name() == "GlobalId": + setattr(to_element, attribute.name(), ifcopenshell.guid.new()) + else: + setattr(to_element, attribute.name(), getattr(from_element, attribute.name())) diff --git a/src/ifcblenderexport/blenderbim/bim/module/root/create_product.py b/src/ifcblenderexport/blenderbim/bim/module/root/create_product.py index b42b7e1a59..6951854560 100644 --- a/src/ifcblenderexport/blenderbim/bim/module/root/create_product.py +++ b/src/ifcblenderexport/blenderbim/bim/module/root/create_product.py @@ -8,12 +8,16 @@ class Usecase: "ifc_class": "IfcBuildingElementProxy", "predefined_type": None, "name": None, + "OwnerHistory": None, } for key, value in settings.items(): self.settings[key] = value def execute(self): - element = self.file.create_entity(self.settings["ifc_class"], **{"GlobalId": ifcopenshell.guid.new()}) + element = self.file.create_entity(self.settings["ifc_class"], **{ + "GlobalId": ifcopenshell.guid.new(), + "OwnerHistory": self.settings["OwnerHistory"] + }) element.Name = self.settings["name"] or None if self.settings["predefined_type"] and hasattr(element, "PredefinedType"): try: diff --git a/src/ifcblenderexport/blenderbim/bim/module/root/operator.py b/src/ifcblenderexport/blenderbim/bim/module/root/operator.py index 3d71a7778c..16e6f0bf05 100644 --- a/src/ifcblenderexport/blenderbim/bim/module/root/operator.py +++ b/src/ifcblenderexport/blenderbim/bim/module/root/operator.py @@ -5,6 +5,8 @@ import ifcopenshell.util.schema import blenderbim.bim.module.root.create_product as create_product import blenderbim.bim.module.root.remove_product as remove_product import blenderbim.bim.module.root.reassign_class as reassign_class +import blenderbim.bim.module.root.copy_class as copy_class +from blenderbim.bim.module.owner.api import create_owner_history from blenderbim.bim.ifc import IfcStore @@ -14,6 +16,7 @@ class EnableReassignClass(bpy.types.Operator): def execute(self, context): obj = bpy.context.active_object + self.file = IfcStore.get_file() ifc_class = obj.name.split("/")[0] bpy.context.active_object.BIMObjectProperties.is_reassigning_class = True ifc_products = [ @@ -28,11 +31,11 @@ class EnableReassignClass(bpy.types.Operator): ] for ifc_product in ifc_products: if ifcopenshell.util.schema.is_a(IfcStore.get_schema().declaration_by_name(ifc_class), ifc_product): - bpy.context.scene.BIMProperties.ifc_product = ifc_product - bpy.context.scene.BIMProperties.ifc_class = obj.name.split("/")[0] - predefined_type = obj.BIMObjectProperties.attributes.get("PredefinedType") - if predefined_type: - bpy.context.scene.BIMProperties.ifc_predefined_type = predefined_type.string_value + bpy.context.scene.BIMRootProperties.ifc_product = ifc_product + element = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id) + bpy.context.scene.BIMRootProperties.ifc_class = element.is_a() + if hasattr(element, "PredefinedType") and element.PredefinedType: + bpy.context.scene.BIMRootProperties.ifc_predefined_type = element.PredefinedType return {"FINISHED"} @@ -52,14 +55,14 @@ class ReassignClass(bpy.types.Operator): def execute(self, context): obj = bpy.context.active_object self.file = IfcStore.get_file() - predefined_type = bpy.context.scene.BIMProperties.ifc_predefined_type + predefined_type = bpy.context.scene.BIMRootProperties.ifc_predefined_type if predefined_type == "USERDEFINED": - predefined_type = bpy.context.scene.BIMProperties.ifc_userdefined_type + predefined_type = bpy.context.scene.BIMRootProperties.ifc_userdefined_type product = reassign_class.Usecase( self.file, { "product": self.file.by_id(obj.BIMObjectProperties.ifc_definition_id), - "ifc_class": bpy.context.scene.BIMProperties.ifc_class, + "ifc_class": bpy.context.scene.BIMRootProperties.ifc_class, "predefined_type": predefined_type, }, ).execute() @@ -91,10 +94,10 @@ class AssignClass(bpy.types.Operator): for material in obj.data.materials: if not material.BIMMaterialProperties.ifc_style_id: bpy.ops.bim.add_style(material=material.name) - self.assign_class(obj) + self.assign_class(context, obj) return {"FINISHED"} - def assign_class(self, obj): + def assign_class(self, context, obj): if obj.BIMObjectProperties.ifc_definition_id: return product = create_product.Usecase( @@ -103,6 +106,7 @@ class AssignClass(bpy.types.Operator): "ifc_class": self.ifc_class, "predefined_type": self.predefined_type, "name": obj.name, + "OwnerHistory": create_owner_history() }, ).execute() obj.name = "{}/{}".format(product.is_a(), obj.name) @@ -122,6 +126,7 @@ class AssignClass(bpy.types.Operator): self.place_in_spatial_collection(obj) else: self.assign_potential_spatial_container(obj) + context.view_layer.objects.active = obj def place_in_types_collection(self, obj): for project in [c for c in bpy.context.view_layer.layer_collection.children if "IfcProject" in c.name]: @@ -182,3 +187,49 @@ class UnassignClass(bpy.types.Operator): if "/" in obj.name and obj.name[0:3] == "Ifc": obj.name = "/".join(obj.name.split("/")[1:]) return {"FINISHED"} + + +class UnlinkObject(bpy.types.Operator): + bl_idname = "bim.unlink_object" + bl_label = "Unlink Object" + obj: bpy.props.StringProperty() + + def execute(self, context): + self.file = IfcStore.get_file() + if self.obj: + objects = [bpy.data.objects.get(self.obj)] + else: + objects = bpy.context.selected_objects + for obj in objects: + if obj.BIMObjectProperties.ifc_definition_id: + obj.BIMObjectProperties.ifc_definition_id = 0 + return {"FINISHED"} + + +class CopyClass(bpy.types.Operator): + bl_idname = "bim.copy_class" + bl_label = "Copy Class" + obj: bpy.props.StringProperty() + + def execute(self, context): + self.file = IfcStore.get_file() + if self.obj: + objects = [bpy.data.objects.get(self.obj)] + else: + objects = bpy.context.selected_objects + for obj in objects: + if not obj.BIMObjectProperties.ifc_definition_id: + continue + result = copy_class.Usecase(self.file, { + "product": self.file.by_id(obj.BIMObjectProperties.ifc_definition_id) + }).execute() + obj.BIMObjectProperties.ifc_definition_id = result.id() + if obj.data.users == 1: + bpy.ops.bim.add_representation(obj=obj.name) + else: + obj_data = obj.data.name + temporary_mesh = bpy.data.meshes.new("Temporary Mesh") + obj.data = temporary_mesh + bpy.ops.bim.map_representation(obj=obj.name, obj_data=obj_data) + bpy.data.meshes.remove(temporary_mesh) + return {"FINISHED"} diff --git a/src/ifcblenderexport/blenderbim/bim/module/root/prop.py b/src/ifcblenderexport/blenderbim/bim/module/root/prop.py new file mode 100644 index 0000000000..ea8c6ded28 --- /dev/null +++ b/src/ifcblenderexport/blenderbim/bim/module/root/prop.py @@ -0,0 +1,94 @@ +import bpy +from blenderbim.bim.ifc import IfcStore +from bpy.types import PropertyGroup +from bpy.props import ( + PointerProperty, + StringProperty, + EnumProperty, + BoolProperty, + IntProperty, + FloatProperty, + FloatVectorProperty, + CollectionProperty, +) + +products_enum = [] +classes_enum = [] +types_enum = [] + + +def getIfcPredefinedTypes(self, context): + global types_enum + file = IfcStore.get_file() + if len(types_enum) < 1 and file: + declaration = IfcStore.get_schema().declaration_by_name(self.ifc_class) + for attribute in declaration.attributes(): + if attribute.name() == "PredefinedType": + types_enum.extend( + [(e, e, "") for e in attribute.type_of_attribute().declared_type().enumeration_items()] + ) + break + return types_enum + + +def refreshClasses(self, context): + global classes_enum + classes_enum.clear() + enum = getIfcClasses(self, context) + context.scene.BIMRootProperties.ifc_class = enum[0][0] + + +def refreshPredefinedTypes(self, context): + global types_enum + types_enum.clear() + enum = getIfcPredefinedTypes(self, context) + context.scene.BIMRootProperties.ifc_predefined_type = enum[0][0] + + +def getIfcProducts(self, context): + global products_enum + file = IfcStore.get_file() + if len(products_enum) < 1: + products_enum.extend( + [ + (e, e, "") + for e in [ + "IfcElement", + "IfcElementType", + "IfcSpatialElement", + "IfcGroup", + "IfcStructuralItem", + "IfcContext", + "IfcAnnotation", + ] + ] + ) + if file.schema == "IFC2X3": + products_enum[2] = ("IfcSpatialStructureElement", "IfcSpatialStructureElement", "") + return products_enum + + +def getIfcClasses(self, context): + global classes_enum + file = IfcStore.get_file() + if len(classes_enum) < 1 and file: + declaration = IfcStore.get_schema().declaration_by_name(self.ifc_product) + + def get_classes(declaration): + results = [] + if not declaration.is_abstract(): + results.append(declaration.name()) + for subtype in declaration.subtypes(): + results.extend(get_classes(subtype)) + return results + + classes = get_classes(declaration) + classes_enum.extend([(c, c, "") for c in sorted(classes)]) + return classes_enum + + +class BIMRootProperties(PropertyGroup): + ifc_product: EnumProperty(items=getIfcProducts, name="Products", update=refreshClasses) + ifc_class: EnumProperty(items=getIfcClasses, name="Class", update=refreshPredefinedTypes) + ifc_predefined_type: EnumProperty(items=getIfcPredefinedTypes, name="Predefined Type", default=None) + ifc_userdefined_type: StringProperty(name="Userdefined Type") diff --git a/src/ifcblenderexport/blenderbim/bim/module/root/ui.py b/src/ifcblenderexport/blenderbim/bim/module/root/ui.py index 1d3e6180bd..def15004cb 100644 --- a/src/ifcblenderexport/blenderbim/bim/module/root/ui.py +++ b/src/ifcblenderexport/blenderbim/bim/module/root/ui.py @@ -34,6 +34,8 @@ class BIM_PT_class(Panel): name += "[{}]".format(data["PredefinedType"]) row = self.layout.row(align=True) row.label(text=name) + row.operator("bim.copy_class", icon="DUPLICATE", text="").obj = context.active_object.name + row.operator("bim.unlink_object", icon="UNLINKED", text="").obj = context.active_object.name row.operator("bim.enable_reassign_class", icon="GREASEPENCIL", text="") row.operator("bim.unassign_class", icon="X", text="").obj = context.active_object.name else: @@ -41,12 +43,12 @@ class BIM_PT_class(Panel): row = self.layout.row(align=True) op = row.operator("bim.assign_class") op.obj = context.active_object.name - op.ifc_class = bpy.context.scene.BIMProperties.ifc_class - op.predefined_type = bpy.context.scene.BIMProperties.ifc_predefined_type - op.userdefined_type = bpy.context.scene.BIMProperties.ifc_userdefined_type + op.ifc_class = bpy.context.scene.BIMRootProperties.ifc_class + op.predefined_type = bpy.context.scene.BIMRootProperties.ifc_predefined_type + op.userdefined_type = bpy.context.scene.BIMRootProperties.ifc_userdefined_type def draw_class_dropdowns(self): - props = bpy.context.scene.BIMProperties + props = bpy.context.scene.BIMRootProperties row = self.layout.row() row.prop(props, "ifc_product") row = self.layout.row() diff --git a/src/ifcblenderexport/blenderbim/bim/module/search/__init__.py b/src/ifcblenderexport/blenderbim/bim/module/search/__init__.py new file mode 100644 index 0000000000..827083f847 --- /dev/null +++ b/src/ifcblenderexport/blenderbim/bim/module/search/__init__.py @@ -0,0 +1,23 @@ +import bpy +from . import ui, prop, operator + +classes = ( + operator.SelectGlobalId, + operator.SelectIfcClass, + operator.SelectAttribute, + operator.SelectPset, + operator.ColourByAttribute, + operator.ColourByPset, + operator.ColourByClass, + operator.ResetObjectColours, + prop.BIMSearchProperties, + ui.BIM_PT_search, +) + + +def register(): + bpy.types.Scene.BIMSearchProperties = bpy.props.PointerProperty(type=prop.BIMSearchProperties) + + +def unregister(): + del bpy.types.Scene.BIMSearchProperties diff --git a/src/ifcblenderexport/blenderbim/bim/module/search/operator.py b/src/ifcblenderexport/blenderbim/bim/module/search/operator.py new file mode 100644 index 0000000000..a27d54f6c4 --- /dev/null +++ b/src/ifcblenderexport/blenderbim/bim/module/search/operator.py @@ -0,0 +1,188 @@ +import re +import bpy +import ifcopenshell +import ifcopenshell.util.element +from blenderbim.bim.ifc import IfcStore +from itertools import cycle + + +colour_list = [ + (0.651, 0.81, 0.892, 1), + (0.121, 0.471, 0.706, 1), + (0.699, 0.876, 0.54, 1), + (0.199, 0.629, 0.174, 1), + (0.983, 0.605, 0.602, 1), + (0.89, 0.101, 0.112, 1), + (0.989, 0.751, 0.427, 1), + (0.986, 0.497, 0.1, 1), + (0.792, 0.699, 0.839, 1), + (0.414, 0.239, 0.603, 1), + (0.993, 0.999, 0.6, 1), + (0.693, 0.349, 0.157, 1), +] + + +def does_keyword_exist(pattern, string): + string = str(string) + if ( + bpy.context.scene.BIMSearchProperties.should_use_regex + and bpy.context.scene.BIMSearchProperties.should_ignorecase + and re.search(pattern, string, flags=re.IGNORECASE) + ): + return True + elif bpy.context.scene.BIMSearchProperties.should_use_regex and re.search(pattern, string): + return True + elif bpy.context.scene.BIMSearchProperties.should_ignorecase and string.lower() == pattern.lower(): + return True + elif string == pattern: + return True + + +class SelectGlobalId(bpy.types.Operator): + bl_idname = "bim.select_global_id" + bl_label = "Select GlobalId" + + def execute(self, context): + self.file = IfcStore.get_file() + props = context.scene.BIMSearchProperties + for obj in context.visible_objects: + if not obj.BIMObjectProperties.ifc_definition_id: + continue + element = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id) + if element.GlobalId == props.global_id: + obj.select_set(True) + break + return {"FINISHED"} + + +class SelectIfcClass(bpy.types.Operator): + bl_idname = "bim.select_ifc_class" + bl_label = "Select IFC Class" + + def execute(self, context): + self.file = IfcStore.get_file() + props = context.scene.BIMSearchProperties + for obj in context.visible_objects: + if not obj.BIMObjectProperties.ifc_definition_id: + continue + element = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id) + if does_keyword_exist(props.ifc_class, element.is_a()): + obj.select_set(True) + return {"FINISHED"} + + +class SelectAttribute(bpy.types.Operator): + bl_idname = "bim.select_attribute" + bl_label = "Select Attribute" + + def execute(self, context): + self.file = IfcStore.get_file() + props = context.scene.BIMSearchProperties + pattern = props.search_attribute_value + attribute_name = props.search_attribute_name + for obj in context.visible_objects: + if not obj.BIMObjectProperties.ifc_definition_id: + continue + element = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id) + if does_keyword_exist(pattern, getattr(element, attribute_name, None)): + obj.select_set(True) + return {"FINISHED"} + + +class SelectPset(bpy.types.Operator): + bl_idname = "bim.select_pset" + bl_label = "Select Pset" + + def execute(self, context): + self.file = IfcStore.get_file() + props = context.scene.BIMSearchProperties + search_pset_name = props.search_pset_name + search_prop_name = props.search_prop_name + pattern = props.search_pset_value + for obj in context.visible_objects: + if not obj.BIMObjectProperties.ifc_definition_id: + continue + element = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id) + psets = ifcopenshell.util.element.get_psets(element) + props = psets.get(search_pset_name, {}) + if does_keyword_exist(pattern, props.get(search_prop_name, None)): + obj.select_set(True) + return {"FINISHED"} + + +class ColourByAttribute(bpy.types.Operator): + bl_idname = "bim.colour_by_attribute" + bl_label = "Colour by Attribute" + + def execute(self, context): + self.file = IfcStore.get_file() + colours = cycle(colour_list) + values = {} + attribute_name = context.scene.BIMSearchProperties.search_attribute_name + for obj in context.visible_objects: + if not obj.BIMObjectProperties.ifc_definition_id: + continue + element = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id) + value = getattr(element, attribute_name, None) + if value not in values: + values[value] = next(colours) + obj.color = values[value] + area = next(area for area in context.screen.areas if area.type == "VIEW_3D") + area.spaces[0].shading.color_type = "OBJECT" + return {"FINISHED"} + + +class ColourByPset(bpy.types.Operator): + bl_idname = "bim.colour_by_pset" + bl_label = "Colour by Pset" + + def execute(self, context): + self.file = IfcStore.get_file() + colours = cycle(colour_list) + values = {} + search_pset_name = context.scene.BIMSearchProperties.search_pset_name + search_prop_name = context.scene.BIMSearchProperties.search_prop_name + for obj in context.visible_objects: + if not obj.BIMObjectProperties.ifc_definition_id: + continue + element = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id) + psets = ifcopenshell.util.element.get_psets(element) + props = psets.get(search_pset_name, {}) + value = str(props.get(search_prop_name, None)) + if value not in values: + values[value] = next(colours) + obj.color = values[value] + area = next(area for area in context.screen.areas if area.type == "VIEW_3D") + area.spaces[0].shading.color_type = "OBJECT" + return {"FINISHED"} + + +class ColourByClass(bpy.types.Operator): + bl_idname = "bim.colour_by_class" + bl_label = "Colour by Class" + + def execute(self, context): + self.file = IfcStore.get_file() + colours = cycle(colour_list) + ifc_classes = {} + for obj in bpy.context.visible_objects: + if not obj.BIMObjectProperties.ifc_definition_id: + continue + element = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id) + ifc_class = element.is_a() + if ifc_class not in ifc_classes: + ifc_classes[ifc_class] = next(colours) + obj.color = ifc_classes[ifc_class] + area = next(area for area in bpy.context.screen.areas if area.type == "VIEW_3D") + area.spaces[0].shading.color_type = "OBJECT" + return {"FINISHED"} + + +class ResetObjectColours(bpy.types.Operator): + bl_idname = "bim.reset_object_colours" + bl_label = "Reset Colours" + + def execute(self, context): + for obj in bpy.context.selected_objects: + obj.color = (1, 1, 1, 1) + return {"FINISHED"} diff --git a/src/ifcblenderexport/blenderbim/bim/module/search/prop.py b/src/ifcblenderexport/blenderbim/bim/module/search/prop.py new file mode 100644 index 0000000000..7723434add --- /dev/null +++ b/src/ifcblenderexport/blenderbim/bim/module/search/prop.py @@ -0,0 +1,23 @@ +import bpy +from bpy.types import PropertyGroup +from bpy.props import ( + PointerProperty, + StringProperty, + EnumProperty, + BoolProperty, + IntProperty, + FloatProperty, + FloatVectorProperty, + CollectionProperty, +) + +class BIMSearchProperties(PropertyGroup): + should_use_regex: BoolProperty(name="Search With Regex", default=False) + should_ignorecase: BoolProperty(name="Search Ignoring Case", default=True) + global_id: StringProperty(name="GlobalId") + ifc_class: StringProperty(name="IFC Class") + search_attribute_name: StringProperty(name="Search Attribute Name") + search_attribute_value: StringProperty(name="Search Attribute Value") + search_pset_name: StringProperty(name="Search Pset Name") + search_prop_name: StringProperty(name="Search Prop Name") + search_pset_value: StringProperty(name="Search Pset Value") diff --git a/src/ifcblenderexport/blenderbim/bim/module/search/ui.py b/src/ifcblenderexport/blenderbim/bim/module/search/ui.py new file mode 100644 index 0000000000..e45d3b2798 --- /dev/null +++ b/src/ifcblenderexport/blenderbim/bim/module/search/ui.py @@ -0,0 +1,44 @@ +import bpy +from bpy.types import Panel + + +class BIM_PT_search(Panel): + bl_label = "IFC Search" + bl_idname = "BIM_PT_search" + bl_options = {"DEFAULT_CLOSED"} + bl_space_type = "PROPERTIES" + bl_region_type = "WINDOW" + bl_context = "scene" + + def draw(self, context): + props = context.scene.BIMSearchProperties + + row = self.layout.row() + row.prop(props, "should_use_regex") + row = self.layout.row() + row.prop(props, "should_ignorecase") + + row = self.layout.row(align=True) + row.operator("bim.reset_object_colours", icon="BRUSH_DATA") + + row = self.layout.row(align=True) + row.prop(props, "global_id", text="", icon="TRACKER") + row.operator("bim.select_global_id", text="", icon="VIEWZOOM") + + row = self.layout.row(align=True) + row.prop(props, "ifc_class", text="", icon="OBJECT_DATA") + row.operator("bim.select_ifc_class", text="", icon="VIEWZOOM") + row.operator("bim.colour_by_class", text="", icon="BRUSH_DATA") + + row = self.layout.row(align=True) + row.prop(props, "search_attribute_name", text="", icon="PROPERTIES") + row.prop(props, "search_attribute_value", text="") + row.operator("bim.select_attribute", text="", icon="VIEWZOOM") + row.operator("bim.colour_by_attribute", text="", icon="BRUSH_DATA") + + row = self.layout.row(align=True) + row.prop(props, "search_pset_name", text="", icon="COPY_ID") + row.prop(props, "search_prop_name", text="") + row.prop(props, "search_pset_value", text="") + row.operator("bim.select_pset", text="", icon="VIEWZOOM") + row.operator("bim.colour_by_pset", text="", icon="BRUSH_DATA") diff --git a/src/ifcblenderexport/blenderbim/bim/module/void/operator.py b/src/ifcblenderexport/blenderbim/bim/module/void/operator.py index 5e3b998c48..cd1f9956e5 100644 --- a/src/ifcblenderexport/blenderbim/bim/module/void/operator.py +++ b/src/ifcblenderexport/blenderbim/bim/module/void/operator.py @@ -17,6 +17,7 @@ class AddOpening(bpy.types.Operator): def execute(self, context): obj = bpy.data.objects.get(self.obj) if self.obj else bpy.context.active_object opening = bpy.data.objects.get(self.opening) + opening.display_type = "WIRE" if not opening.BIMObjectProperties.ifc_definition_id: body_context_id = None if not ContextData.is_loaded: diff --git a/src/ifcblenderexport/blenderbim/bim/module/void/ui.py b/src/ifcblenderexport/blenderbim/bim/module/void/ui.py index be4a49f194..c878d8ccea 100644 --- a/src/ifcblenderexport/blenderbim/bim/module/void/ui.py +++ b/src/ifcblenderexport/blenderbim/bim/module/void/ui.py @@ -1,6 +1,7 @@ import bpy from bpy.types import Panel from blenderbim.bim.module.void.data import Data +from blenderbim.bim.ifc import IfcStore class BIM_PT_voids(Panel): @@ -11,6 +12,10 @@ class BIM_PT_voids(Panel): bl_region_type = "WINDOW" bl_context = "object" + @classmethod + def poll(cls, context): + return IfcStore.get_file() + def draw(self, context): props = context.active_object.BIMObjectProperties if props.ifc_definition_id not in Data.products: diff --git a/src/ifcblenderexport/blenderbim/bim/operator.py b/src/ifcblenderexport/blenderbim/bim/operator.py index dd95539cdf..9f3e4404c1 100644 --- a/src/ifcblenderexport/blenderbim/bim/operator.py +++ b/src/ifcblenderexport/blenderbim/bim/operator.py @@ -12,7 +12,6 @@ import ifcopenshell.util.selector import ifcopenshell.util.geolocation import ifcopenshell.util.element import ifcopenshell.util.schema -import tempfile import numpy as np from . import export_ifc from . import import_ifc @@ -25,28 +24,11 @@ from . import ifc from . import annotation from . import helper from bpy_extras.io_utils import ImportHelper -from itertools import cycle from mathutils import Vector, Matrix, Euler, geometry import bmesh from math import radians, degrees, atan, tan, cos, sin -from pathlib import Path from bpy.app.handlers import persistent -colour_list = [ - (0.651, 0.81, 0.892, 1), - (0.121, 0.471, 0.706, 1), - (0.699, 0.876, 0.54, 1), - (0.199, 0.629, 0.174, 1), - (0.983, 0.605, 0.602, 1), - (0.89, 0.101, 0.112, 1), - (0.989, 0.751, 0.427, 1), - (0.986, 0.497, 0.1, 1), - (0.792, 0.699, 0.839, 1), - (0.414, 0.239, 0.603, 1), - (0.993, 0.999, 0.6, 1), - (0.693, 0.349, 0.157, 1), -] - @persistent def depsgraph_update_pre_handler(scene): @@ -82,6 +64,8 @@ class ExportIFC(bpy.types.Operator): bl_label = "Export IFC" filename_ext = ".ifc" filepath: bpy.props.StringProperty(subtype="FILE_PATH") + json_version: bpy.props.EnumProperty(items=[("4", "4", ""), ("5a", "5a", "")], name="IFC JSON Version") + json_compact: bpy.props.BoolProperty(name="Export Compact IFCJSON", default=False) def invoke(self, context, event): if not self.filepath: @@ -103,13 +87,16 @@ class ExportIFC(bpy.types.Operator): output_file = bpy.path.ensure_ext(self.filepath, ".ifcjson") else: output_file = bpy.path.ensure_ext(self.filepath, ".ifc") - ifc_export_settings = export_ifc.IfcExportSettings.factory(context, output_file, logger) - qto_calculator = None # TODO: remove from export - ifc_parser = export_ifc.IfcParser(ifc_export_settings, qto_calculator) - ifc_exporter = export_ifc.IfcExporter(ifc_export_settings, ifc_parser) - ifc_export_settings.logger.info("Starting export") - ifc_exporter.export(context.selected_objects) - ifc_export_settings.logger.info("Export finished in {:.2f} seconds".format(time.time() - start)) + + settings = export_ifc.IfcExportSettings.factory(context, output_file, logger) + settings.json_version = self.json_version + settings.json_compact = self.json_compact + + ifc_exporter = export_ifc.IfcExporter(settings) + settings.logger.info("Starting export") + ifc_exporter.export() + settings.logger.info("Export finished in {:.2f} seconds".format(time.time() - start)) + print("Export finished in {:.2f} seconds".format(time.time() - start)) if not bpy.context.scene.DocProperties.ifc_files: new = bpy.context.scene.DocProperties.ifc_files.add() new.name = output_file @@ -124,184 +111,63 @@ class ImportIFC(bpy.types.Operator, ImportHelper): filename_ext = ".ifc" filter_glob: bpy.props.StringProperty(default="*.ifc;*.ifczip;*.ifcxml", options={"HIDDEN"}) + should_import_type_representations: bpy.props.BoolProperty(name="Import Type Representations", default=False) + should_import_curves: bpy.props.BoolProperty(name="Import Curves", default=False) + should_import_spaces: bpy.props.BoolProperty(name="Import Spaces", default=False) + should_auto_set_workarounds: bpy.props.BoolProperty(name="Automatically Set Vendor Workarounds", default=True) + should_use_cpu_multiprocessing: bpy.props.BoolProperty(name="Import with CPU Multiprocessing", default=True) + should_merge_by_class: bpy.props.BoolProperty(name="Import and Merge by Class", default=False) + should_merge_by_material: bpy.props.BoolProperty(name="Import and Merge by Material", default=False) + should_merge_materials_by_colour: bpy.props.BoolProperty(name="Import and Merge Materials by Colour", default=False) + should_clean_mesh: bpy.props.BoolProperty(name="Import and Clean Mesh", default=True) + deflection_tolerance: bpy.props.FloatProperty(name="Import Deflection Tolerance", default=0.001) + angular_tolerance: bpy.props.FloatProperty(name="Import Angular Tolerance", default=0.5) + should_allow_non_element_aggregates: bpy.props.BoolProperty(name="Import Non-Element Aggregates", default=False) + should_offset_model: bpy.props.BoolProperty(name="Import and Offset Model", default=False) + model_offset_coordinates: bpy.props.StringProperty(name="Model Offset Coordinates", default="0,0,0") + ifc_import_filter: bpy.props.EnumProperty( + items=[("NONE", "None", ""), ("WHITELIST", "Whitelist", ""), ("BLACKLIST", "Blacklist", ""),], + name="Import Filter", + ) + ifc_selector: bpy.props.StringProperty(default="", name="IFC Selector") + def execute(self, context): start = time.time() logger = logging.getLogger("ImportIFC") logging.basicConfig( filename=bpy.context.scene.BIMProperties.data_dir + "process.log", filemode="a", level=logging.DEBUG ) - ifc_import_settings = import_ifc.IfcImportSettings.factory(context, self.filepath, logger) - ifc_import_settings.logger.info("Starting import") - ifc_importer = import_ifc.IfcImporter(ifc_import_settings) + + settings = import_ifc.IfcImportSettings.factory(context, self.filepath, logger) + settings.should_import_type_representations = self.should_import_type_representations + settings.should_import_curves = self.should_import_curves + settings.should_import_spaces = self.should_import_spaces + settings.should_auto_set_workarounds = self.should_auto_set_workarounds + settings.should_use_cpu_multiprocessing = self.should_use_cpu_multiprocessing + settings.should_merge_by_class = self.should_merge_by_class + settings.should_merge_by_material = self.should_merge_by_material + settings.should_merge_materials_by_colour = self.should_merge_materials_by_colour + settings.should_clean_mesh = self.should_clean_mesh + settings.deflection_tolerance = self.deflection_tolerance + settings.angular_tolerance = self.angular_tolerance + settings.should_allow_non_element_aggregates = self.should_allow_non_element_aggregates + settings.should_offset_model = self.should_offset_model + settings.model_offset_coordinates = ( + [float(o) for o in self.model_offset_coordinates.split(",")] + if self.model_offset_coordinates + else (0, 0, 0) + ) + settings.ifc_import_filter = self.ifc_import_filter + settings.ifc_selector = self.ifc_selector + + settings.logger.info("Starting import") + ifc_importer = import_ifc.IfcImporter(settings) ifc_importer.execute() - ifc_import_settings.logger.info("Import finished in {:.2f} seconds".format(time.time() - start)) + settings.logger.info("Import finished in {:.2f} seconds".format(time.time() - start)) print("Import finished in {:.2f} seconds".format(time.time() - start)) return {"FINISHED"} -class SelectGlobalId(bpy.types.Operator): - bl_idname = "bim.select_global_id" - bl_label = "Select GlobalId" - - def execute(self, context): - for obj in bpy.context.visible_objects: - index = obj.BIMObjectProperties.attributes.find("GlobalId") - if ( - index != -1 - and obj.BIMObjectProperties.attributes[index].string_value == bpy.context.scene.BIMProperties.global_id - ): - obj.select_set(True) - break - return {"FINISHED"} - - -class SelectAttribute(bpy.types.Operator): - bl_idname = "bim.select_attribute" - bl_label = "Select Attribute" - - def execute(self, context): - import re - - search_value = bpy.context.scene.BIMProperties.search_attribute_value - for object in bpy.context.visible_objects: - index = object.BIMObjectProperties.attributes.find(bpy.context.scene.BIMProperties.search_attribute_name) - if index == -1: - continue - value = object.BIMObjectProperties.attributes[index].string_value - if ( - bpy.context.scene.BIMProperties.search_regex - and bpy.context.scene.BIMProperties.search_ignorecase - and re.search(search_value, value, flags=re.IGNORECASE) - ): - object.select_set(True) - elif bpy.context.scene.BIMProperties.search_regex and re.search(search_value, value): - object.select_set(True) - elif bpy.context.scene.BIMProperties.search_ignorecase and value.lower() == search_value.lower(): - object.select_set(True) - elif value == search_value: - object.select_set(True) - return {"FINISHED"} - - -class SelectPset(bpy.types.Operator): - bl_idname = "bim.select_pset" - bl_label = "Select Pset" - - def execute(self, context): - import re - - search_pset_name = bpy.context.scene.BIMProperties.search_pset_name - search_prop_name = bpy.context.scene.BIMProperties.search_prop_name - search_value = bpy.context.scene.BIMProperties.search_pset_value - for object in bpy.context.visible_objects: - pset_index = object.BIMObjectProperties.psets.find(search_pset_name) - if pset_index == -1: - continue - prop_index = object.BIMObjectProperties.psets[pset_index].properties.find(search_prop_name) - if prop_index == -1: - continue - value = object.BIMObjectProperties.psets[pset_index].properties[prop_index].string_value - if ( - bpy.context.scene.BIMProperties.search_regex - and bpy.context.scene.BIMProperties.search_ignorecase - and re.search(search_value, value, flags=re.IGNORECASE) - ): - object.select_set(True) - elif bpy.context.scene.BIMProperties.search_regex and re.search(search_value, value): - object.select_set(True) - elif bpy.context.scene.BIMProperties.search_ignorecase and value.lower() == search_value.lower(): - object.select_set(True) - elif value == search_value: - object.select_set(True) - return {"FINISHED"} - - -class SelectClass(bpy.types.Operator): - bl_idname = "bim.select_class" - bl_label = "Select IFC Class" - - def execute(self, context): - for object in bpy.context.visible_objects: - if ( - "/" in object.name - and object.name[0:3] == "Ifc" - and object.name.split("/")[0] == bpy.context.scene.BIMProperties.ifc_class - ): - object.select_set(True) - return {"FINISHED"} - - -class SelectType(bpy.types.Operator): - bl_idname = "bim.select_type" - bl_label = "Select IFC Type" - - def execute(self, context): - for object in bpy.context.visible_objects: - if ( - "/" in object.name - and object.name[0:3] == "Ifc" - and object.name.split("/")[0] == bpy.context.scene.BIMProperties.ifc_class - and "PredefinedType" in object.BIMObjectProperties.attributes - and object.BIMObjectProperties.attributes["PredefinedType"].string_value - == bpy.context.scene.BIMProperties.ifc_predefined_type - ): - if bpy.context.scene.BIMProperties.ifc_predefined_type != "USERDEFINED": - object.select_set(True) - elif ( - "ObjectType" in object.BIMObjectProperties.attributes - and object.BIMObjectProperties.attributes["ObjectType"].string_value - == bpy.context.scene.BIMProperties.ifc_userdefined_type - ): - object.select_set(True) - return {"FINISHED"} - - -class ColourByAttribute(bpy.types.Operator): - bl_idname = "bim.colour_by_attribute" - bl_label = "Colour by Attribute" - - def execute(self, context): - colours = cycle(colour_list) - values = {} - attribute_name = bpy.context.scene.BIMProperties.search_attribute_name - for obj in bpy.context.visible_objects: - index = obj.BIMObjectProperties.attributes.find(attribute_name) - if index == -1: - continue - value = obj.BIMObjectProperties.attributes[index].string_value - if value not in values: - values[value] = next(colours) - obj.color = values[value] - area = next(area for area in bpy.context.screen.areas if area.type == "VIEW_3D") - area.spaces[0].shading.color_type = "OBJECT" - return {"FINISHED"} - - -class ColourByPset(bpy.types.Operator): - bl_idname = "bim.colour_by_pset" - bl_label = "Colour by Pset" - - def execute(self, context): - colours = cycle(colour_list) - values = {} - search_pset_name = bpy.context.scene.BIMProperties.search_pset_name - search_prop_name = bpy.context.scene.BIMProperties.search_prop_name - for obj in bpy.context.visible_objects: - pset_index = obj.BIMObjectProperties.psets.find(search_pset_name) - if pset_index == -1: - continue - prop_index = obj.BIMObjectProperties.psets[pset_index].properties.find(search_prop_name) - if prop_index == -1: - continue - value = obj.BIMObjectProperties.psets[pset_index].properties[prop_index].string_value - if value not in values: - values[value] = next(colours) - obj.color = values[value] - area = next(area for area in bpy.context.screen.areas if area.type == "VIEW_3D") - area.spaces[0].shading.color_type = "OBJECT" - return {"FINISHED"} - - class OpenUri(bpy.types.Operator): bl_idname = "bim.open_uri" bl_label = "Open URI" @@ -312,278 +178,6 @@ class OpenUri(bpy.types.Operator): return {"FINISHED"} -class AddQto(bpy.types.Operator): - bl_idname = "bim.add_qto" - bl_label = "Add Qto" - - def execute(self, context): - name = bpy.context.active_object.BIMObjectProperties.qto_name - qto_template = schema.ifc.psetqto.get_by_name(name) - if not qto_template: - return {"FINISHED"} - for obj in bpy.context.selected_objects: - if "/" not in obj.name or obj.BIMObjectProperties.qtos.find(name) != -1: - continue - applicable_qtos = schema.ifc.psetqto.get_applicable_names(obj.name.split("/")[0], qto_only=True) - if name not in applicable_qtos: - continue - qto = obj.BIMObjectProperties.qtos.add() - qto.name = name - for prop_name in (p.Name for p in qto_template.HasPropertyTemplates): - prop = qto.properties.add() - prop.name = prop_name - return {"FINISHED"} - - -class RemoveQto(bpy.types.Operator): - bl_idname = "bim.remove_qto" - bl_label = "Remove Qto" - index: bpy.props.IntProperty() - - def execute(self, context): - name = bpy.context.active_object.BIMObjectProperties.qtos[self.index].name - for obj in bpy.context.selected_objects: - if "/" not in obj.name: - continue - index = obj.BIMObjectProperties.qtos.find(name) - if index != -1: - obj.BIMObjectProperties.qtos.remove(index) - return {"FINISHED"} - - -class AddMaterialPset(bpy.types.Operator): - bl_idname = "bim.add_material_pset" - bl_label = "Add Material Pset" - - def execute(self, context): - material = bpy.context.active_object.active_material - name = material.BIMMaterialProperties.pset_name - pset_template = schema.ifc.psetqto.get_by_name(name) - if not pset_template: - return {"FINISHED"} - if material.BIMMaterialProperties.psets.find(name) != -1: - return {"FINISHED"} - pset = material.BIMMaterialProperties.psets.add() - pset.name = name - for prop_name in (p.Name for p in pset_template.HasPropertyTemplates): - prop = pset.properties.add() - prop.name = prop_name - return {"FINISHED"} - - -class RemoveMaterialPset(bpy.types.Operator): - bl_idname = "bim.remove_material_pset" - bl_label = "Remove Pset" - pset_index: bpy.props.IntProperty() - - def execute(self, context): - bpy.context.active_object.active_material.BIMMaterialProperties.psets.remove(self.pset_index) - return {"FINISHED"} - - -class AddConstraint(bpy.types.Operator): - bl_idname = "bim.add_constraint" - bl_label = "Add Constraint" - - def execute(self, context): - constraint = bpy.context.scene.BIMProperties.constraints.add() - constraint.name = "New Constraint" - return {"FINISHED"} - - -class RemoveConstraint(bpy.types.Operator): - bl_idname = "bim.remove_constraint" - bl_label = "Remove Constraint" - index: bpy.props.IntProperty() - - def execute(self, context): - bpy.context.scene.BIMProperties.constraints.remove(self.index) - return {"FINISHED"} - - -class AssignConstraint(bpy.types.Operator): - bl_idname = "bim.assign_constraint" - bl_label = "Assign Constraint" - - def execute(self, context): - identification = bpy.context.scene.BIMProperties.constraints[ - bpy.context.scene.BIMProperties.active_constraint_index - ].name - for obj in bpy.context.selected_objects: - if obj.BIMObjectProperties.constraints.get(identification): - continue - constraint = obj.BIMObjectProperties.constraints.add() - constraint.name = identification - return {"FINISHED"} - - -class UnassignConstraint(bpy.types.Operator): - bl_idname = "bim.unassign_constraint" - bl_label = "Unassign Constraint" - - def execute(self, context): - identification = bpy.context.scene.BIMProperties.constraints[ - bpy.context.scene.BIMProperties.active_constraint_index - ].name - for obj in bpy.context.selected_objects: - index = obj.BIMObjectProperties.constraints.find(identification) - if index >= 0: - obj.BIMObjectProperties.constraints.remove(index) - return {"FINISHED"} - - -class RemoveObjectConstraint(bpy.types.Operator): - bl_idname = "bim.remove_object_constraint" - bl_label = "Remove Object Constraint" - index: bpy.props.IntProperty() - - def execute(self, context): - bpy.context.active_object.BIMObjectProperties.constraints.remove(self.index) - return {"FINISHED"} - - -class AddDocumentInformation(bpy.types.Operator): - bl_idname = "bim.add_document_information" - bl_label = "Add Document Information" - - def execute(self, context): - info = bpy.context.scene.BIMProperties.document_information.add() - info.name = "New Document ID" - return {"FINISHED"} - - -class RemoveDocumentInformation(bpy.types.Operator): - bl_idname = "bim.remove_document_information" - bl_label = "Remove Document Information" - index: bpy.props.IntProperty() - - def execute(self, context): - bpy.context.scene.BIMProperties.document_information.remove(self.index) - return {"FINISHED"} - - -class AssignDocumentInformation(bpy.types.Operator): - bl_idname = "bim.assign_document_information" - bl_label = "Assign Document Information" - index: bpy.props.IntProperty() - - def execute(self, context): - reference = bpy.context.scene.BIMProperties.document_references[self.index] - index = bpy.context.scene.BIMProperties.active_document_information_index - info = bpy.context.scene.BIMProperties.document_information - if index < len(info): - reference.referenced_document = info[index].name - return {"FINISHED"} - - -class AddDocumentReference(bpy.types.Operator): - bl_idname = "bim.add_document_reference" - bl_label = "Add Document Reference" - - def execute(self, context): - document = bpy.context.scene.BIMProperties.document_references.add() - document.name = "New Document Reference ID" - return {"FINISHED"} - - -class RemoveDocumentReference(bpy.types.Operator): - bl_idname = "bim.remove_document_reference" - bl_label = "Remove Document Reference" - index: bpy.props.IntProperty() - - def execute(self, context): - bpy.context.scene.BIMProperties.document_references.remove(self.index) - return {"FINISHED"} - - -class RemoveObjectDocumentReference(bpy.types.Operator): - bl_idname = "bim.remove_object_document_reference" - bl_label = "Remove Object Document Reference" - index: bpy.props.IntProperty() - - def execute(self, context): - bpy.context.active_object.BIMObjectProperties.document_references.remove(self.index) - return {"FINISHED"} - - -class AssignDocumentReference(bpy.types.Operator): - bl_idname = "bim.assign_document_reference" - bl_label = "Assign Document Reference" - - def execute(self, context): - identification = bpy.context.scene.BIMProperties.document_references[ - bpy.context.scene.BIMProperties.active_document_reference_index - ].name - for obj in bpy.context.selected_objects: - if obj.BIMObjectProperties.document_references.get(identification): - continue - reference = obj.BIMObjectProperties.document_references.add() - reference.name = identification - return {"FINISHED"} - - -class UnassignDocumentReference(bpy.types.Operator): - bl_idname = "bim.unassign_document_reference" - bl_label = "Unassign Document Reference" - - def execute(self, context): - identification = bpy.context.scene.BIMProperties.document_references[ - bpy.context.scene.BIMProperties.active_document_reference_index - ].name - for obj in bpy.context.selected_objects: - index = obj.BIMObjectProperties.document_references.find(identification) - if index >= 0: - obj.BIMObjectProperties.document_references.remove(index) - return {"FINISHED"} - - -class RemoveObjectDocumentReference(bpy.types.Operator): - bl_idname = "bim.remove_object_document_reference" - bl_label = "Remove Object Document Reference" - index: bpy.props.IntProperty() - - def execute(self, context): - bpy.context.active_object.BIMObjectProperties.document_references.remove(self.index) - return {"FINISHED"} - - -class GenerateGlobalId(bpy.types.Operator): - bl_idname = "bim.generate_global_id" - bl_label = "Regenerate GlobalId" - - def execute(self, context): - index = bpy.context.active_object.BIMObjectProperties.attributes.find("GlobalId") - if index >= 0: - global_id = bpy.context.active_object.BIMObjectProperties.attributes[index] - else: - global_id = bpy.context.active_object.BIMObjectProperties.attributes.add() - global_id.name = "GlobalId" - global_id.data_type = "string" - global_id.string_value = ifcopenshell.guid.new() - return {"FINISHED"} - - -class AddMaterialAttribute(bpy.types.Operator): - bl_idname = "bim.add_material_attribute" - bl_label = "Add Material Attribute" - - def execute(self, context): - if bpy.context.active_object.active_material.BIMMaterialProperties.applicable_attributes: - attribute = bpy.context.active_object.active_material.BIMMaterialProperties.attributes.add() - attribute.name = bpy.context.active_object.active_material.BIMMaterialProperties.applicable_attributes - return {"FINISHED"} - - -class RemoveMaterialAttribute(bpy.types.Operator): - bl_idname = "bim.remove_material_attribute" - bl_label = "Remove Material Attribute" - attribute_index: bpy.props.IntProperty() - - def execute(self, context): - bpy.context.active_object.active_material.BIMMaterialProperties.attributes.remove(self.attribute_index) - return {"FINISHED"} - - class AddSweptSolid(bpy.types.Operator): bl_idname = "bim.add_swept_solid" bl_label = "Add Swept Solid" @@ -728,392 +322,6 @@ class SelectExternalMaterialDir(bpy.types.Operator): return {"RUNNING_MODAL"} -class ExecuteIfcPatch(bpy.types.Operator): - bl_idname = "bim.execute_ifc_patch" - bl_label = "Execute IFCPatch" - file_format: bpy.props.StringProperty() - - def execute(self, context): - import ifcpatch - - ifcpatch.execute( - { - "input": bpy.context.scene.BIMProperties.ifc_patch_input, - "output": bpy.context.scene.BIMProperties.ifc_patch_output, - "recipe": bpy.context.scene.BIMProperties.ifc_patch_recipes, - "arguments": json.loads("[" + bpy.context.scene.BIMProperties.ifc_patch_args + "]"), - "log": bpy.context.scene.BIMProperties.data_dir + "process.log", - } - ) - return {"FINISHED"} - - -class ExportClashSets(bpy.types.Operator): - bl_idname = "bim.export_clash_sets" - bl_label = "Export Clash Sets" - filename_ext = ".json" - filepath: bpy.props.StringProperty(subtype="FILE_PATH") - - def invoke(self, context, event): - self.filepath = bpy.path.ensure_ext(bpy.data.filepath, ".json") - WindowManager = context.window_manager - WindowManager.fileselect_add(self) - return {"RUNNING_MODAL"} - - def execute(self, context): - self.filepath = bpy.path.ensure_ext(self.filepath, ".json") - clash_sets = [] - for clash_set in bpy.context.scene.BIMProperties.clash_sets: - self.a = [] - self.b = [] - for ab in ["a", "b"]: - for data in getattr(clash_set, ab): - clash_source = {"file": data.name} - if data.selector: - clash_source["selector"] = data.selector - clash_source["mode"] = data.mode - getattr(self, ab).append(clash_source) - clash_sets.append({"name": clash_set.name, "tolerance": clash_set.tolerance, "a": self.a, "b": self.b}) - with open(self.filepath, "w") as destination: - destination.write(json.dumps(clash_sets, indent=4)) - return {"FINISHED"} - - -class ImportClashSets(bpy.types.Operator): - bl_idname = "bim.import_clash_sets" - bl_label = "Import Clash Sets" - filename_ext = ".json" - filepath: bpy.props.StringProperty(subtype="FILE_PATH") - - def invoke(self, context, event): - self.filepath = bpy.path.ensure_ext(bpy.data.filepath, ".json") - WindowManager = context.window_manager - WindowManager.fileselect_add(self) - return {"RUNNING_MODAL"} - - def execute(self, context): - with open(self.filepath) as f: - clash_sets = json.load(f) - for clash_set in clash_sets: - new = bpy.context.scene.BIMProperties.clash_sets.add() - new.name = clash_set["name"] - new.tolerance = clash_set["tolerance"] - for clash_source in clash_set["a"]: - new_source = new.a.add() - new_source.name = clash_source["file"] - if "selector" in clash_source: - new_source.selector = clash_source["selector"] - new_source.mode = clash_source["mode"] - if clash_set["b"]: - for clash_source in clash_set["b"]: - new_source = new.b.add() - new_source.name = clash_source["file"] - if "selector" in clash_source: - new_source.selector = clash_source["selector"] - new_source.mode = clash_source["mode"] - return {"FINISHED"} - - -class AddClashSet(bpy.types.Operator): - bl_idname = "bim.add_clash_set" - bl_label = "Add Clash Set" - - def execute(self, context): - new = bpy.context.scene.BIMProperties.clash_sets.add() - new.name = "New Clash Set" - new.tolerance = 0.01 - return {"FINISHED"} - - -class RemoveClashSet(bpy.types.Operator): - bl_idname = "bim.remove_clash_set" - bl_label = "Remove Clash Set" - index: bpy.props.IntProperty() - - def execute(self, context): - bpy.context.scene.BIMProperties.clash_sets.remove(self.index) - return {"FINISHED"} - - -class AddClashSource(bpy.types.Operator): - bl_idname = "bim.add_clash_source" - bl_label = "Add Clash Source" - group: bpy.props.StringProperty() - - def execute(self, context): - clash_set = bpy.context.scene.BIMProperties.clash_sets[bpy.context.scene.BIMProperties.active_clash_set_index] - source = getattr(clash_set, self.group).add() - return {"FINISHED"} - - -class RemoveClashSource(bpy.types.Operator): - bl_idname = "bim.remove_clash_source" - bl_label = "Remove Clash Source" - index: bpy.props.IntProperty() - group: bpy.props.StringProperty() - - def execute(self, context): - clash_set = bpy.context.scene.BIMProperties.clash_sets[bpy.context.scene.BIMProperties.active_clash_set_index] - getattr(clash_set, self.group).remove(self.index) - return {"FINISHED"} - - -class SelectClashSource(bpy.types.Operator): - bl_idname = "bim.select_clash_source" - bl_label = "Select Clash Source" - filepath: bpy.props.StringProperty(subtype="FILE_PATH") - index: bpy.props.IntProperty() - group: bpy.props.StringProperty() - - def execute(self, context): - clash_set = bpy.context.scene.BIMProperties.clash_sets[bpy.context.scene.BIMProperties.active_clash_set_index] - getattr(clash_set, self.group)[self.index].name = self.filepath - return {"FINISHED"} - - def invoke(self, context, event): - context.window_manager.fileselect_add(self) - return {"RUNNING_MODAL"} - - -class SelectClashResults(bpy.types.Operator): - bl_idname = "bim.select_clash_results" - bl_label = "Select Clash Results" - filepath: bpy.props.StringProperty(subtype="FILE_PATH") - - def execute(self, context): - bpy.context.scene.BIMProperties.clash_results_path = self.filepath - return {"FINISHED"} - - def invoke(self, context, event): - context.window_manager.fileselect_add(self) - return {"RUNNING_MODAL"} - - -class SelectSmartGroupedClashesPath(bpy.types.Operator): - bl_idname = "bim.select_smart_grouped_clashes_path" - bl_label = "Select Smart-Grouped Clashes Path" - filepath: bpy.props.StringProperty(subtype="FILE_PATH") - - def execute(self, context): - bpy.context.scene.BIMProperties.smart_grouped_clashes_path = self.filepath - return {"FINISHED"} - - def invoke(self, context, event): - context.window_manager.fileselect_add(self) - return {"RUNNING_MODAL"} - - -class ExecuteIfcClash(bpy.types.Operator): - bl_idname = "bim.execute_ifc_clash" - bl_label = "Execute IFC Clash" - filename_ext = ".bcf" - filepath: bpy.props.StringProperty(subtype="FILE_PATH") - - def invoke(self, context, event): - if ".json" not in bpy.data.filepath: - self.filepath = bpy.path.ensure_ext(bpy.data.filepath, ".bcf") - WindowManager = context.window_manager - WindowManager.fileselect_add(self) - return {"RUNNING_MODAL"} - - def execute(self, context): - import ifcclash - - settings = ifcclash.IfcClashSettings() - if ".json" not in self.filepath: - self.filepath = bpy.path.ensure_ext(self.filepath, ".bcf") - settings.output = self.filepath - settings.logger = logging.getLogger("Clash") - settings.logger.setLevel(logging.DEBUG) - ifc_clasher = ifcclash.IfcClasher(settings) - - if bpy.context.scene.BIMProperties.should_create_clash_snapshots: - - def get_viewpoint_snapshot(self, viewpoint, mat): - camera = bpy.data.objects.get("IFC Clash Camera") - if not camera: - camera = bpy.data.objects.new("IFC Clash Camera", bpy.data.cameras.new("IFC Clash Camera")) - bpy.context.scene.collection.objects.link(camera) - camera.matrix_world = Matrix(mat) - bpy.context.scene.camera = camera - camera.data.angle = radians(60) - area = next(area for area in bpy.context.screen.areas if area.type == "VIEW_3D") - area.spaces[0].region_3d.view_perspective = "CAMERA" - area.spaces[0].shading.show_xray = True - bpy.context.scene.render.resolution_x = 480 - bpy.context.scene.render.resolution_y = 270 - bpy.context.scene.render.image_settings.file_format = "PNG" - bpy.context.scene.render.filepath = os.path.join( - bpy.context.scene.BIMProperties.data_dir, "snapshot.png" - ) - bpy.ops.render.opengl(write_still=True) - return bpy.context.scene.render.filepath - - ifcclash.IfcClasher.get_viewpoint_snapshot = get_viewpoint_snapshot - - ifc_clasher.clash_sets = [] - for clash_set in bpy.context.scene.BIMProperties.clash_sets: - self.a = [] - self.b = [] - for ab in ["a", "b"]: - for data in getattr(clash_set, ab): - clash_source = {"file": data.name} - if data.selector: - clash_source["selector"] = data.selector - clash_source["mode"] = data.mode - getattr(self, ab).append(clash_source) - ifc_clasher.clash_sets.append( - {"name": clash_set.name, "tolerance": clash_set.tolerance, "a": self.a, "b": self.b} - ) - ifc_clasher.clash() - ifc_clasher.export() - return {"FINISHED"} - - -class SelectIfcClashResults(bpy.types.Operator): - bl_idname = "bim.select_ifc_clash_results" - bl_label = "Select IFC Clash Results" - filename_ext = ".json" - filepath: bpy.props.StringProperty(subtype="FILE_PATH") - - def invoke(self, context, event): - self.filepath = bpy.path.ensure_ext(bpy.data.filepath, ".json") - WindowManager = context.window_manager - WindowManager.fileselect_add(self) - return {"RUNNING_MODAL"} - - def execute(self, context): - self.filepath = bpy.path.ensure_ext(self.filepath, ".json") - with open(self.filepath) as f: - clash_sets = json.load(f) - clash_set_name = bpy.context.scene.BIMProperties.clash_sets[ - bpy.context.scene.BIMProperties.active_clash_set_index - ].name - global_ids = [] - for clash_set in clash_sets: - if clash_set["name"] != clash_set_name: - continue - if not "clashes" in clash_set.keys(): - self.report({"WARNING"}, "No clashes found for the selected Clash Set.") - return {"CANCELLED"} - for clash in clash_set["clashes"].values(): - global_ids.extend([clash["a_global_id"], clash["b_global_id"]]) - for obj in bpy.context.visible_objects: - global_id = obj.BIMObjectProperties.attributes.get("GlobalId") - if global_id and global_id.string_value in global_ids: - obj.select_set(True) - return {"FINISHED"} - - -class SmartClashGroup(bpy.types.Operator): - bl_idname = "bim.smart_clash_group" - bl_label = "Smart Group Clashes" - filepath: bpy.props.StringProperty(subtype="FILE_PATH") - - def execute(self, context): - import ifcclash - - settings = ifcclash.IfcClashSettings() - self.filepath = bpy.path.ensure_ext(bpy.context.scene.BIMProperties.clash_results_path, ".json") - settings.output = self.filepath - settings.logger = logging.getLogger("Clash") - settings.logger.setLevel(logging.DEBUG) - ifc_clasher = ifcclash.IfcClasher(settings) - - with open(self.filepath) as f: - clash_sets = json.load(f) - - # execute the smart grouping - save_path = bpy.path.ensure_ext(bpy.context.scene.BIMProperties.smart_grouped_clashes_path, ".json") - smart_grouped_clashes = ifc_clasher.smart_group_clashes( - clash_sets, bpy.context.scene.BIMProperties.smart_clash_grouping_max_distance - ) - - # save smart_groups to json - with open(save_path, "w") as f: - f.write(json.dumps(smart_grouped_clashes)) - - clash_set_name = bpy.context.scene.BIMProperties.clash_sets[ - bpy.context.scene.BIMProperties.active_clash_set_index - ].name - - # Reset the list of smart_clash_groups for the UI - bpy.context.scene.BIMProperties.smart_clash_groups.clear() - - for clash_set, smart_groups in smart_grouped_clashes.items(): - # Only select the clashes that correspond to the actively selected IFC Clash Set - if clash_set != clash_set_name: - continue - else: - for smart_group, global_id_pairs in smart_groups[0].items(): - new_group = bpy.context.scene.BIMProperties.smart_clash_groups.add() - new_group.number = f"{smart_group}" - - for pair in global_id_pairs: - for id in pair: - new_global_id = new_group.global_ids.add() - new_global_id.name = id - - return {"FINISHED"} - - -class LoadSmartGroupsForActiveClashSet(bpy.types.Operator): - bl_idname = "bim.load_smart_groups_for_active_clash_set" - bl_label = "Load Smart Groups for Active Clash Set" - - def execute(self, context): - smart_groups_path = bpy.path.ensure_ext(bpy.context.scene.BIMProperties.smart_grouped_clashes_path, ".json") - - clash_set_name = bpy.context.scene.BIMProperties.clash_sets[ - bpy.context.scene.BIMProperties.active_clash_set_index - ].name - - with open(smart_groups_path) as f: - smart_grouped_clashes = json.load(f) - - # Reset the list of smart_clash_groups for the UI - bpy.context.scene.BIMProperties.smart_clash_groups.clear() - - for clash_set, smart_groups in smart_grouped_clashes.items(): - # Only select the clashes that correspond to the actively selected IFC Clash Set - if clash_set != clash_set_name: - continue - else: - for smart_group, global_id_pairs in smart_groups[0].items(): - new_group = bpy.context.scene.BIMProperties.smart_clash_groups.add() - new_group.number = f"{smart_group}" - for pair in global_id_pairs: - for id in pair: - new_global_id = new_group.global_ids.add() - new_global_id.name = id - - return {"FINISHED"} - - -class SelectSmartGroup(bpy.types.Operator): - bl_idname = "bim.select_smart_group" - bl_label = "Select Smart Group" - - def execute(self, context): - # Select smart group in view - selected_smart_group = bpy.context.scene.BIMProperties.smart_clash_groups[ - bpy.context.scene.BIMProperties.active_smart_group_index - ] - # print(selected_smart_group.number) - - for obj in bpy.context.visible_objects: - global_id = obj.BIMObjectProperties.attributes.get("GlobalId") - if global_id: - for id in selected_smart_group.global_ids: - # print("Id: ", id) - # print("Global id: ", global_id.string_value) - if global_id.string_value in id.name: - # print("object match: ", global_id) - obj.select_set(True) - - return {"FINISHED"} - - class SelectIfcFile(bpy.types.Operator): bl_idname = "bim.select_ifc_file" bl_label = "Select IFC File" @@ -1156,106 +364,6 @@ class SelectSchemaDir(bpy.types.Operator): return {"RUNNING_MODAL"} -class LoadClassification(bpy.types.Operator): - bl_idname = "bim.load_classification" - bl_label = "Load Classification" - is_file: bpy.props.BoolProperty() - classification_index: bpy.props.IntProperty() - - def execute(self, context): - from . import prop - - if self.is_file: - prop.ClassificationView.raw_data = schema.ifc.load_classification( - context.scene.BIMProperties.classification - ) - else: - prop.ClassificationView.raw_data = schema.ifc.load_classification( - context.scene.BIMProperties.classifications[self.classification_index].name, self.classification_index - ) - context.scene.BIMProperties.classification_references.root = "" - return {"FINISHED"} - - -class AddClassification(bpy.types.Operator): - bl_idname = "bim.add_classification" - bl_label = "Add Classification" - - def execute(self, context): - if context.scene.BIMProperties.classification not in schema.ifc.classifications: - return {"FINISHED"} - data = schema.ifc.classifications[context.scene.BIMProperties.classification] - classification = context.scene.BIMProperties.classifications.add() - data_map = { - "name": "Name", - "source": "Source", - "edition": "Edition", - "edition_date": "EditionDate", - "description": "Description", - "location": "Location", - "reference_tokens": "ReferenceTokens", - } - for key, value in data_map.items(): - if hasattr(data, value) and getattr(data, value): - setattr(classification, key, str(getattr(data, value))) - classification.data = schema.ifc.classification_files[context.scene.BIMProperties.classification].to_string() - return {"FINISHED"} - - -class RemoveClassification(bpy.types.Operator): - bl_idname = "bim.remove_classification" - bl_label = "Remove Classification" - classification_index: bpy.props.IntProperty() - - def execute(self, context): - bpy.context.scene.BIMProperties.classifications.remove(self.classification_index) - return {"FINISHED"} - - -class AssignClassification(bpy.types.Operator): - bl_idname = "bim.assign_classification" - bl_label = "Assign Classification" - - def execute(self, context): - for obj in bpy.context.selected_objects: - classification = obj.BIMObjectProperties.classifications.add() - refs = bpy.context.scene.BIMProperties.classification_references - data = refs.root["children"][refs.children[refs.active_index].name] - if data["identification"]: - classification.name = data["identification"] - if data["name"]: - classification.human_name = data["name"] - for key in ["location", "description"]: - if data[key]: - setattr(classification, key, data[key]) - classification.referenced_source = bpy.context.scene.BIMProperties.active_classification_name - return {"FINISHED"} - - -class UnassignClassification(bpy.types.Operator): - bl_idname = "bim.unassign_classification" - bl_label = "Unassign Classification" - - def execute(self, context): - refs = bpy.context.scene.BIMProperties.classification_references - key = refs.children[refs.active_index].name - for obj in bpy.context.selected_objects: - index = obj.BIMObjectProperties.classifications.find(key) - if index != -1: - obj.BIMObjectProperties.classifications.remove(index) - return {"FINISHED"} - - -class RemoveClassificationReference(bpy.types.Operator): - bl_idname = "bim.remove_classification_reference" - bl_label = "Remove Classification Reference" - classification_index: bpy.props.IntProperty() - - def execute(self, context): - bpy.context.active_object.BIMObjectProperties.classifications.remove(self.classification_index) - return {"FINISHED"} - - class FetchExternalMaterial(bpy.types.Operator): bl_idname = "bim.fetch_external_material" bl_label = "Fetch External Material" @@ -1286,15 +394,6 @@ class FetchExternalMaterial(bpy.types.Operator): return -class FetchLibraryInformation(bpy.types.Operator): - bl_idname = "bim.fetch_library_information" - bl_label = "Fetch Library Information" - - def execute(self, context): - # TODO - return {"FINISHED"} - - class FetchObjectPassport(bpy.types.Operator): bl_idname = "bim.fetch_object_passport" bl_label = "Fetch Object Passport" @@ -1712,6 +811,7 @@ class CopyAttributeToSelection(bpy.types.Operator): attribute_value: bpy.props.StringProperty() def execute(self, context): + # TODO: reimplement self.schema = ifcopenshell.ifcopenshell_wrapper.schema_by_name(bpy.context.scene.BIMProperties.export_schema) self.applicable_attributes_cache = {} for obj in bpy.context.selected_objects: @@ -1735,150 +835,6 @@ class CopyAttributeToSelection(bpy.types.Operator): return self.applicable_attributes_cache[ifc_class] -class BIM_OT_ChangeClassificationLevel(bpy.types.Operator): - bl_idname = "bim.change_classification_level" - bl_label = "Change Classification Level" - - # string representing the id-data (e.g. the scene). - path_sid: bpy.props.StringProperty() - # path from the id-data to the classification view object - path_lst: bpy.props.StringProperty() - # name of child entity to enter (empty = go up one level) - path_itm: bpy.props.StringProperty() - - def invoke(self, context, event): - id_data = eval(self.path_sid) - lst = id_data.path_resolve(self.path_lst) - if self.path_itm: - lst.root = self.path_itm - else: - lst.root = "" - return {"FINISHED"} - - -class AddPropertySetTemplate(bpy.types.Operator): - bl_idname = "bim.add_property_set_template" - bl_label = "Add Property Set Template" - - def execute(self, context): - context.scene.BIMProperties.active_property_set_template.global_id = "" - context.scene.BIMProperties.active_property_set_template.name = "New_Pset" - context.scene.BIMProperties.active_property_set_template.description = "" - context.scene.BIMProperties.active_property_set_template.template_type = "PSET_TYPEDRIVENONLY" - context.scene.BIMProperties.active_property_set_template.applicable_entity = "IfcTypeObject" - while len(bpy.context.scene.BIMProperties.property_templates) > 0: - bpy.context.scene.BIMProperties.property_templates.remove(0) - return {"FINISHED"} - - -class RemovePropertySetTemplate(bpy.types.Operator): - bl_idname = "bim.remove_property_set_template" - bl_label = "Remove Property Set Template" - - def execute(self, context): - template = ifc.IfcStore.pset_template_file.by_guid(context.scene.BIMProperties.property_set_templates) - ifc.IfcStore.pset_template_file.remove(template) - ifc.IfcStore.pset_template_file.write(ifc.IfcStore.pset_template_path) - from . import prop - - prop.refreshPropertySetTemplates(self, context) - return {"FINISHED"} - - -class EditPropertySetTemplate(bpy.types.Operator): - bl_idname = "bim.edit_property_set_template" - bl_label = "Edit Property Set Template" - - def execute(self, context): - template = ifc.IfcStore.pset_template_file.by_guid(context.scene.BIMProperties.property_set_templates) - context.scene.BIMProperties.active_property_set_template.global_id = template.GlobalId - context.scene.BIMProperties.active_property_set_template.name = template.Name - context.scene.BIMProperties.active_property_set_template.description = template.Description - context.scene.BIMProperties.active_property_set_template.template_type = template.TemplateType - context.scene.BIMProperties.active_property_set_template.applicable_entity = template.ApplicableEntity - - while len(bpy.context.scene.BIMProperties.property_templates) > 0: - bpy.context.scene.BIMProperties.property_templates.remove(0) - - if template.HasPropertyTemplates: - for property_template in template.HasPropertyTemplates: - if not property_template.is_a("IfcSimplePropertyTemplate"): - continue - new = context.scene.BIMProperties.property_templates.add() - new.global_id = property_template.GlobalId - new.name = property_template.Name - new.description = property_template.Description - new.primary_measure_type = property_template.PrimaryMeasureType - return {"FINISHED"} - - -class SavePropertySetTemplate(bpy.types.Operator): - bl_idname = "bim.save_property_set_template" - bl_label = "Save Property Set Template" - - def execute(self, context): - blender_property_set_template = context.scene.BIMProperties.active_property_set_template - if blender_property_set_template.global_id: - template = ifc.IfcStore.pset_template_file.by_guid(blender_property_set_template.global_id) - else: - template = ifc.IfcStore.pset_template_file.createIfcPropertySetTemplate() - template.GlobalId = ifcopenshell.guid.new() - template.Name = blender_property_set_template.name - template.Description = blender_property_set_template.description - template.TemplateType = blender_property_set_template.template_type - template.ApplicableEntity = blender_property_set_template.applicable_entity - - saved_global_ids = [] - - for blender_property_template in context.scene.BIMProperties.property_templates: - if blender_property_template.global_id: - property_template = ifc.IfcStore.pset_template_file.by_guid(blender_property_template.global_id) - else: - property_template = ifc.IfcStore.pset_template_file.createIfcSimplePropertyTemplate() - property_template.GlobalId = ifcopenshell.guid.new() - if template.HasPropertyTemplates: - has_property_templates = list(template.HasPropertyTemplates) - else: - has_property_templates = [] - has_property_templates.append(property_template) - template.HasPropertyTemplates = has_property_templates - property_template.Name = blender_property_template.name - property_template.Description = blender_property_template.description - property_template.PrimaryMeasureType = blender_property_template.primary_measure_type - property_template.TemplateType = "P_SINGLEVALUE" - property_template.AccessState = "READWRITE" - saved_global_ids.append(property_template.GlobalId) - - for element in template.HasPropertyTemplates: - if element.GlobalId not in saved_global_ids: - ifc.IfcStore.pset_template_file.remove(element) - - ifc.IfcStore.pset_template_file.write(ifc.IfcStore.pset_template_path) - from . import prop - - prop.refreshPropertySetTemplates(self, context) - return {"FINISHED"} - - -class AddPropertyTemplate(bpy.types.Operator): - bl_idname = "bim.add_property_template" - bl_label = "Add Property Template" - - def execute(self, context): - context.scene.BIMProperties.property_templates.add() - return {"FINISHED"} - - -class RemovePropertyTemplate(bpy.types.Operator): - bl_idname = "bim.remove_property_template" - bl_label = "Remove Property Template" - index: bpy.props.IntProperty() - - def execute(self, context): - bpy.context.scene.BIMProperties.property_templates.remove(self.index) - return {"FINISHED"} - - class AddSectionPlane(bpy.types.Operator): bl_idname = "bim.add_section_plane" bl_label = "Add Temporary Section Cutaway" @@ -2290,103 +1246,6 @@ class PropagateTextData(bpy.types.Operator): return {"FINISHED"} -class SelectIfcPatchInput(bpy.types.Operator): - bl_idname = "bim.select_ifc_patch_input" - bl_label = "Select IFC Patch Input" - filter_glob: bpy.props.StringProperty(default="*.ifc", options={"HIDDEN"}) - filepath: bpy.props.StringProperty(subtype="FILE_PATH") - - def execute(self, context): - bpy.context.scene.BIMProperties.ifc_patch_input = self.filepath - return {"FINISHED"} - - def invoke(self, context, event): - context.window_manager.fileselect_add(self) - return {"RUNNING_MODAL"} - - -class SelectIfcPatchOutput(bpy.types.Operator): - bl_idname = "bim.select_ifc_patch_output" - bl_label = "Select IFC Patch Output" - filename_ext = ".ifc" - filepath: bpy.props.StringProperty(subtype="FILE_PATH") - - def execute(self, context): - bpy.context.scene.BIMProperties.ifc_patch_output = self.filepath - return {"FINISHED"} - - def invoke(self, context, event): - context.window_manager.fileselect_add(self) - return {"RUNNING_MODAL"} - - -class CalculateEdgeLengths(bpy.types.Operator): - bl_idname = "bim.calculate_edge_lengths" - bl_label = "Calculate Edge Lengths" - - def execute(self, context): - result = 0 - for obj in bpy.context.selected_objects: - if not obj.data or not obj.data.edges: - continue - for edge in obj.data.edges: - if edge.select: - result += (obj.data.vertices[edge.vertices[1]].co - obj.data.vertices[edge.vertices[0]].co).length - bpy.context.scene.BIMProperties.qto_result = str(round(result, 3)) - return {"FINISHED"} - - -class CalculateFaceAreas(bpy.types.Operator): - bl_idname = "bim.calculate_face_areas" - bl_label = "Calculate Face Areas" - - def execute(self, context): - result = 0 - for obj in bpy.context.selected_objects: - if not obj.data or not obj.data.polygons: - continue - for polygon in obj.data.polygons: - if polygon.select: - result += polygon.area - bpy.context.scene.BIMProperties.qto_result = str(round(result, 3)) - return {"FINISHED"} - - -class CalculateObjectVolumes(bpy.types.Operator): - bl_idname = "bim.calculate_object_volumes" - bl_label = "Calculate Object Volumes" - - def execute(self, context): - # TODO: reimplement - qto_calculator = qto.QtoCalculator() - result = 0 - for obj in bpy.context.selected_objects: - if not obj.data: - continue - result += qto_calculator.get_volume(obj) - bpy.context.scene.BIMProperties.qto_result = str(round(result, 3)) - return {"FINISHED"} - - -class AddOpening(bpy.types.Operator): - bl_idname = "bim.add_opening" - bl_label = "Add Opening" - - def execute(self, context): - if context.active_object.children and "IfcOpeningElement/" in context.active_object.children[0].name: - opening = context.active_object.children[0] - else: - opening = context.active_object - if context.selected_objects[0] != context.active_object: - obj = context.selected_objects[0] - else: - obj = context.selected_objects[1] - modifier = obj.modifiers.new("IfcOpeningElement", "BOOLEAN") - modifier.operation = "DIFFERENCE" - modifier.object = opening - return {"FINISHED"} - - class SetOverrideColour(bpy.types.Operator): bl_idname = "bim.set_override_colour" bl_label = "Set Override Colour" @@ -2659,120 +1518,6 @@ class RemoveSchedule(bpy.types.Operator): return {"FINISHED"} -class AddMaterialLayer(bpy.types.Operator): - bl_idname = "bim.add_material_layer" - bl_label = "Add Material Layer" - - def execute(self, context): - new = bpy.context.active_object.BIMObjectProperties.material_set.material_layers.add() - new.material = bpy.data.materials[0] - new.name = "Material Layer" - return {"FINISHED"} - - -class RemoveMaterialLayer(bpy.types.Operator): - bl_idname = "bim.remove_material_layer" - bl_label = "Remove Material Layer" - index: bpy.props.IntProperty() - - def execute(self, context): - bpy.context.active_object.BIMObjectProperties.material_set.material_layers.remove(self.index) - return {"FINISHED"} - - -class MoveMaterialLayer(bpy.types.Operator): - bl_idname = "bim.move_material_layer" - bl_label = "Move Material Layer" - direction: bpy.props.StringProperty() - - def execute(self, context): - props = bpy.context.active_object.BIMObjectProperties.material_set - index = props.active_material_layer_index - if self.direction == "UP" and index - 1 >= 0: - props.material_layers.move(index, index - 1) - props.active_material_layer_index = index - 1 - elif self.direction == "DOWN" and index + 1 < len(props.material_layers): - props.material_layers.move(index, index + 1) - props.active_material_layer_index = index + 1 - return {"FINISHED"} - - -class AddMaterialConstituent(bpy.types.Operator): - bl_idname = "bim.add_material_constituent" - bl_label = "Add Material Constituent" - - def execute(self, context): - new = bpy.context.active_object.BIMObjectProperties.material_set.material_constituents.add() - new.material = bpy.data.materials[0] - new.name = "Material Constituent" - return {"FINISHED"} - - -class RemoveMaterialConstituent(bpy.types.Operator): - bl_idname = "bim.remove_material_constituent" - bl_label = "Remove Material Constituent" - index: bpy.props.IntProperty() - - def execute(self, context): - bpy.context.active_object.BIMObjectProperties.material_set.material_constituents.remove(self.index) - return {"FINISHED"} - - -class MoveMaterialConstituent(bpy.types.Operator): - bl_idname = "bim.move_material_constituent" - bl_label = "Move Material Constituent" - direction: bpy.props.StringProperty() - - def execute(self, context): - props = bpy.context.active_object.BIMObjectProperties.material_set - index = props.active_material_constituent_index - if self.direction == "UP" and index - 1 >= 0: - props.material_constituents.move(index, index - 1) - props.active_material_constituent_index = index - 1 - elif self.direction == "DOWN" and index + 1 < len(props.material_constituents): - props.material_constituents.move(index, index + 1) - props.active_material_constituent_index = index + 1 - return {"FINISHED"} - - -class AddMaterialProfile(bpy.types.Operator): - bl_idname = "bim.add_material_profile" - bl_label = "Add Material Profile" - - def execute(self, context): - new = bpy.context.active_object.BIMObjectProperties.material_set.material_profiles.add() - new.material = bpy.data.materials[0] - new.name = "Material Profile" - return {"FINISHED"} - - -class RemoveMaterialProfile(bpy.types.Operator): - bl_idname = "bim.remove_material_profile" - bl_label = "Remove Material Profile" - index: bpy.props.IntProperty() - - def execute(self, context): - bpy.context.active_object.BIMObjectProperties.material_set.material_profiles.remove(self.index) - return {"FINISHED"} - - -class MoveMaterialProfile(bpy.types.Operator): - bl_idname = "bim.move_material_profile" - bl_label = "Move Material Profile" - direction: bpy.props.StringProperty() - - def execute(self, context): - props = bpy.context.active_object.BIMObjectProperties.material_set - index = props.active_material_profile_index - if self.direction == "UP" and index - 1 >= 0: - props.material_profiles.move(index, index - 1) - props.active_material_profile_index = index - 1 - elif self.direction == "DOWN" and index + 1 < len(props.material_profiles): - props.material_profiles.move(index, index + 1) - props.active_material_profile_index = index + 1 - return {"FINISHED"} - - class SelectScheduleFile(bpy.types.Operator): bl_idname = "bim.select_schedule_file" bl_label = "Select Documentation IFC File" @@ -2831,71 +1576,6 @@ class SetViewportShadowFromSun(bpy.types.Operator): return {"FINISHED"} -class AssignPresentationLayer(bpy.types.Operator): - bl_idname = "bim.assign_presentation_layer" - bl_label = "Assign Presentation Layer" - index: bpy.props.IntProperty() - - def execute(self, context): - layer = bpy.context.scene.BIMProperties.presentation_layers[self.index] - for obj in bpy.context.selected_objects: - if not obj.data or not hasattr(obj.data, "BIMMeshProperties"): - continue - obj.data.BIMMeshProperties.presentation_layer_index = self.index - obj.hide_set(not layer.layer_on) - return {"FINISHED"} - - -class UnassignPresentationLayer(bpy.types.Operator): - bl_idname = "bim.unassign_presentation_layer" - bl_label = "Unassign Presentation Layer" - - def execute(self, context): - for obj in bpy.context.selected_objects: - if not obj.data or not hasattr(obj.data, "BIMMeshProperties"): - continue - obj.data.BIMMeshProperties.presentation_layer_index = -1 - return {"FINISHED"} - - -class AddPresentationLayer(bpy.types.Operator): - bl_idname = "bim.add_presentation_layer" - bl_label = "Add Presentation Layer" - - def execute(self, context): - new = bpy.context.scene.BIMProperties.presentation_layers.add() - new.name = "New Presentation Layer" - new.layer_on = True - new.layer_frozen = False - new.layer_blocked = False - return {"FINISHED"} - - -class RemovePresentationLayer(bpy.types.Operator): - bl_idname = "bim.remove_presentation_layer" - bl_label = "Remove Presentation Layer" - index: bpy.props.IntProperty() - - def execute(self, context): - bpy.context.scene.BIMProperties.presentation_layers.remove(self.index) - return {"FINISHED"} - - -class UpdatePresentationLayer(bpy.types.Operator): - bl_idname = "bim.update_presentation_layer" - bl_label = "Hide/Show selected Presentation Layer" - index: bpy.props.IntProperty() - - def execute(self, context): - for obj in bpy.context.scene.objects: - if not obj.data or not hasattr(obj.data, "BIMMeshProperties"): - continue - if obj.data.BIMMeshProperties.presentation_layer_index == self.index: - set_status = obj.hide_get() - obj.hide_set(not obj.hide_get()) - return {"FINISHED"} - - class AddDrawingStyleAttribute(bpy.types.Operator): bl_idname = "bim.add_drawing_style_attribute" bl_label = "Add Drawing Style Attribute" @@ -2934,88 +1614,6 @@ class RefreshDrawingList(bpy.types.Operator): return {"FINISHED"} -class BlenderClasher: - def process_clash_set(self): - import collision - - a_cm = collision.CollisionManager() - b_cm = collision.CollisionManager() - self.add_to_cm(a_cm, bpy.context.scene.BIMProperties.blender_clash_set_a) - self.add_to_cm(b_cm, bpy.context.scene.BIMProperties.blender_clash_set_b) - results = a_cm.in_collision_other(b_cm, return_data=True) - if not results[0]: - print("No clashes") - return - for contact in results[1]: - if contact.raw.penetration_depth < 0.01: - continue - print("-----") - print(contact.names) - print(contact.raw.normal) - print(contact.raw.pos) - - def add_to_cm(self, cm, object_names): - import numpy as np - import ifcclash - - for object_name in object_names: - name = object_name.name - obj = bpy.data.objects[name] - triangulated_mesh = self.triangulate_mesh(obj) - mesh = ifcclash.Mesh() - mesh.vertices = np.array([tuple(obj.matrix_world @ v.co) for v in triangulated_mesh.vertices]) - mesh.faces = np.array([tuple(p.vertices) for p in triangulated_mesh.polygons]) - cm.add_object(name, mesh) - - def triangulate_mesh(self, obj): - import bmesh - - mesh = obj.evaluated_get(bpy.context.evaluated_depsgraph_get()).to_mesh() - bm = bmesh.new() - bm.from_mesh(mesh) - bmesh.ops.triangulate(bm, faces=bm.faces) - bm.to_mesh(mesh) - bm.free() - del bm - return mesh - - -class SetBlenderClashSetA(bpy.types.Operator): - bl_idname = "bim.set_blender_clash_set_a" - bl_label = "Set Blender Clash Set A" - - def execute(self, context): - while len(bpy.context.scene.BIMProperties.blender_clash_set_a) > 0: - bpy.context.scene.BIMProperties.blender_clash_set_a.remove(0) - for obj in bpy.context.selected_objects: - new = bpy.context.scene.BIMProperties.blender_clash_set_a.add() - new.name = obj.name - return {"FINISHED"} - - -class SetBlenderClashSetB(bpy.types.Operator): - bl_idname = "bim.set_blender_clash_set_b" - bl_label = "Set Blender Clash Set B" - - def execute(self, context): - while len(bpy.context.scene.BIMProperties.blender_clash_set_b) > 0: - bpy.context.scene.BIMProperties.blender_clash_set_b.remove(0) - for obj in bpy.context.selected_objects: - new = bpy.context.scene.BIMProperties.blender_clash_set_b.add() - new.name = obj.name - return {"FINISHED"} - - -class ExecuteBlenderClash(bpy.types.Operator): - bl_idname = "bim.execute_blender_clash" - bl_label = "Execute Blender Clash" - - def execute(self, context): - blender_clasher = BlenderClasher() - blender_clasher.process_clash_set() - return {"FINISHED"} - - class CleanWireframes(bpy.types.Operator): bl_idname = "bim.clean_wireframes" bl_label = "Clean Wireframes" diff --git a/src/ifcblenderexport/blenderbim/bim/prop.py b/src/ifcblenderexport/blenderbim/bim/prop.py index bf67b2e656..3e240a9709 100644 --- a/src/ifcblenderexport/blenderbim/bim/prop.py +++ b/src/ifcblenderexport/blenderbim/bim/prop.py @@ -26,20 +26,8 @@ from bpy.props import ( cwd = os.path.dirname(os.path.realpath(__file__)) diagram_scales_enum = [] -products_enum = [] -profiledef_enum = [] -classes_enum = [] -types_enum = [] -availablematerialpsets_enum = [] -ifcpatchrecipes_enum = [] titleblocks_enum = [] materialpsetnames_enum = [] -psetfiles_enum = [] -psettemplatefiles_enum = [] -propertysettemplates_enum = [] -classification_enum = [] -attributes_enum = [] -materialattributes_enum = [] contexts_enum = [] subcontexts_enum = [] target_views_enum = [] @@ -47,6 +35,12 @@ sheets_enum = [] vector_styles_enum = [] +@persistent +def clearIfcStore(scene): + IfcStore.file = None + IfcStore.schema = None + + @persistent def setDefaultProperties(scene): if len(bpy.context.scene.DocProperties.drawing_styles) == 0: @@ -118,32 +112,6 @@ def setDefaultProperties(scene): bpy.ops.bim.save_drawing_style(index="2") -def getIfcPredefinedTypes(self, context): - global types_enum - file = IfcStore.get_file() - if len(types_enum) < 1 and file: - declaration = IfcStore.get_schema().declaration_by_name(self.ifc_class) - for attribute in declaration.attributes(): - if attribute.name() == "PredefinedType": - types_enum.extend([(e, e, "") for e in attribute.type_of_attribute().declared_type().enumeration_items()]) - break - return types_enum - - -def refreshClasses(self, context): - global classes_enum - classes_enum.clear() - enum = getIfcClasses(self, context) - context.scene.BIMProperties.ifc_class = enum[0][0] - - -def refreshPredefinedTypes(self, context): - global types_enum - types_enum.clear() - enum = getIfcPredefinedTypes(self, context) - context.scene.BIMProperties.ifc_predefined_type = enum[0][0] - - def getDiagramScales(self, context): global diagram_scales_enum if ( @@ -240,62 +208,10 @@ def refreshActiveDrawingIndex(self, context): bpy.ops.bim.activate_view(drawing_index=context.scene.DocProperties.active_drawing_index) -def getIfcProducts(self, context): - global products_enum - file = IfcStore.get_file() - if len(products_enum) < 1: - products_enum.extend( - [ - (e, e, "") - for e in [ - "IfcElement", - "IfcElementType", - "IfcSpatialElement", - "IfcGroup", - "IfcStructuralItem", - "IfcContext", - "IfcAnnotation", - ] - ] - ) - if file.schema == "IFC2X3": - products_enum[2] = ("IfcSpatialStructureElement", "IfcSpatialStructureElement", "") - return products_enum - - -def getIfcClasses(self, context): - global classes_enum - file = IfcStore.get_file() - if len(classes_enum) < 1 and file: - declaration = IfcStore.get_schema().declaration_by_name(self.ifc_product) - def get_classes(declaration): - results = [] - if not declaration.is_abstract(): - results.append(declaration.name()) - for subtype in declaration.subtypes(): - results.extend(get_classes(subtype)) - return results - classes = get_classes(declaration) - classes_enum.extend([(c, c, "") for c in sorted(classes)]) - return classes_enum - - def getAttributeEnumValues(self, context): return [(e, e, "") for e in json.loads(self.enum_items)] -def getIfcPatchRecipes(self, context): - global ifcpatchrecipes_enum - if len(ifcpatchrecipes_enum) < 1: - ifcpatchrecipes_enum.clear() - ifcpatch_path = Path(importlib.util.find_spec("ifcpatch").submodule_search_locations[0]) - for filename in ifcpatch_path.joinpath("recipes").glob("*.py"): - f = str(filename.stem) - if f == "__init__": - continue - ifcpatchrecipes_enum.append((f, f, "")) - return ifcpatchrecipes_enum - def getTitleblocks(self, context): global titleblocks_enum if len(titleblocks_enum) < 1: @@ -331,47 +247,6 @@ def toggleDecorationsOnLoad(*args): decoration.DecorationsHandler.uninstall() -def getPsetTemplateFiles(self, context): - global psettemplatefiles_enum - if len(psettemplatefiles_enum) < 1: - files = os.listdir(os.path.join(self.data_dir, "pset")) - psettemplatefiles_enum.extend([(f.replace(".ifc", ""), f.replace(".ifc", ""), "") for f in files]) - return psettemplatefiles_enum - - -def refreshPropertySetTemplates(self, context): - global propertysettemplates_enum - propertysettemplates_enum.clear() - getPropertySetTemplates(self, context) - - -def getPropertySetTemplates(self, context): - global propertysettemplates_enum - if len(propertysettemplates_enum) < 1: - ifc.IfcStore.pset_template_path = os.path.join( - context.scene.BIMProperties.data_dir, "pset", context.scene.BIMProperties.pset_template_files + ".ifc" - ) - ifc.IfcStore.pset_template_file = ifcopenshell.open(ifc.IfcStore.pset_template_path) - templates = ifc.IfcStore.pset_template_file.by_type("IfcPropertySetTemplate") - propertysettemplates_enum.extend([(t.GlobalId, t.Name, "") for t in templates]) - return propertysettemplates_enum - - -def getClassifications(self, context): - global classification_enum - if len(classification_enum) < 1: - classification_enum.clear() - files = os.listdir(os.path.join(self.schema_dir, "classifications")) - classification_enum.extend([(f.replace(".ifc", ""), f.replace(".ifc", ""), "") for f in files]) - return classification_enum - - -def refreshReferences(self, context): - context.scene.BIMProperties.classification_references.root = None - ClassificationView.raw_data = schema.ifc.load_classification(context.scene.BIMProperties.classification) - context.scene.BIMProperties.classification_references.root = "" - - def getMaterialPsetNames(self, context): global materialpsetnames_enum materialpsetnames_enum.clear() @@ -380,28 +255,24 @@ def getMaterialPsetNames(self, context): return materialpsetnames_enum -def getApplicableMaterialAttributes(self, context): - global materialattributes_enum - materialattributes_enum.clear() - if "/" in context.active_object.name: - ifc_schema = ifcopenshell.ifcopenshell_wrapper.schema_by_name(bpy.context.scene.BIMProperties.export_schema) - entity = ifc_schema.declaration_by_name("IfcMaterial") - materialattributes_enum.extend( - [(a.name(), a.name(), "") for a in entity.all_attributes() if self.attributes.find(a.name()) == -1] - ) - return materialattributes_enum - - def getContexts(self, context): from blenderbim.bim.module.context.data import Data + if not Data.is_loaded: Data.load() results = [] for ifc_id, context in Data.contexts.items(): results.append((str(ifc_id), context["ContextType"], "")) for ifc_id2, subcontext in context["HasSubContexts"].items(): - results.append((str(ifc_id2), "{}/{}/{}".format( - subcontext["ContextType"], subcontext["ContextIdentifier"], subcontext["TargetView"]), "")) + results.append( + ( + str(ifc_id2), + "{}/{}/{}".format( + subcontext["ContextType"], subcontext["ContextIdentifier"], subcontext["TargetView"] + ), + "", + ) + ) return results @@ -409,7 +280,18 @@ def getSubcontexts(self, context): global subcontexts_enum subcontexts_enum.clear() # TODO: allow override of generated subcontexts? - subcontexts = export_ifc.IfcExportSettings().subcontexts + subcontexts = [ + "Annotation", + "Axis", + "Box", + "FootPrint", + "Reference", + "Body", + "Clearance", + "CoG", + "Profile", + "SurveyPoints", + ] for subcontext in subcontexts: subcontexts_enum.append((subcontext, subcontext, "")) return subcontexts_enum @@ -418,7 +300,18 @@ def getSubcontexts(self, context): def getTargetViews(self, context): global target_views_enum target_views_enum.clear() - for target_view in export_ifc.IfcExportSettings().target_views: + target_views = [ + "GRAPH_VIEW", + "SKETCH_VIEW", + "MODEL_VIEW", + "PLAN_VIEW", + "REFLECTED_PLAN_VIEW", + "SECTION_VIEW", + "ELEVATION_VIEW", + "USERDEFINED", + "NOTDEFINED", + ] + for target_view in target_views: target_views_enum.append((target_view, target_view, "")) return target_views_enum @@ -489,7 +382,11 @@ class DrawingStyle(PropertyGroup): name: StringProperty(name="Name") raster_style: StringProperty(name="Raster Style") render_type: EnumProperty( - items=[("NONE", "None", ""), ("DEFAULT", "Default", ""), ("VIEWPORT", "Viewport", ""),], + items=[ + ("NONE", "None", ""), + ("DEFAULT", "Default", ""), + ("VIEWPORT", "Viewport", ""), + ], name="Render Type", default="VIEWPORT", ) @@ -514,8 +411,9 @@ class DocProperties(PropertyGroup): ifc_files: CollectionProperty(name="IFCs", type=StrProperty) drawing_styles: CollectionProperty(name="Drawing Styles", type=DrawingStyle) should_draw_decorations: BoolProperty(name="Should Draw Decorations", update=toggleDecorations) - decorations_colour: FloatVectorProperty(name="Decorations Colour", subtype="COLOR", default=(1, 0, 0, 1), - min=0.0, max=1.0, size=4) + decorations_colour: FloatVectorProperty( + name="Decorations Colour", subtype="COLOR", default=(1, 0, 0, 1), min=0.0, max=1.0, size=4 + ) class BIMCameraProperties(PropertyGroup): @@ -565,7 +463,11 @@ class BIMTextProperties(PropertyGroup): name="Font Size", ) symbol: EnumProperty( - items=[("None", "None", ""), ("rectangle-tag", "Rectangle Tag", ""), ("door-tag", "Door Tag", ""),], + items=[ + ("None", "None", ""), + ("rectangle-tag", "Rectangle Tag", ""), + ("door-tag", "Door Tag", ""), + ], update=refreshFontSize, name="Symbol", ) @@ -573,519 +475,19 @@ class BIMTextProperties(PropertyGroup): variables: CollectionProperty(name="Variables", type=Variable) -class DocumentInformation(PropertyGroup): - name: StringProperty(name="Identification") - human_name: StringProperty(name="Name") - description: StringProperty(name="Description") - location: StringProperty(name="Location") - purpose: StringProperty(name="Purpose") - intended_use: StringProperty(name="Intended Use") - scope: StringProperty(name="Scope") - revision: StringProperty(name="Revision") - document_owner: StringProperty(name="Owner") - editors: StringProperty(name="Editors") - creation_time: StringProperty(name="Created On") - last_revision_time: StringProperty(name="Last Revised") - electronic_format: StringProperty(name="Format") - valid_from: StringProperty(name="Valid From") - valid_until: StringProperty(name="Valid Until") - confidentiality: EnumProperty( - items=[ - ("NOTDEFINED", "NOTDEFINED", "Not defined."), - ("PUBLIC", "PUBLIC", "Document is publicly available."), - ("RESTRICTED", "RESTRICTED", "Document availability is restricted."), - ( - "CONFIDENTIAL", - "CONFIDENTIAL", - "Document is confidential and its contents should not be revealed without permission.", - ), - ("PERSONAL", "PERSONAL", "Document is personal to the author."), - ("USERDEFINED", "USERDEFINED", "Describe confidentiality elsewhere."), - ], - name="Confidentiality", - ) - status: EnumProperty( - items=[ - ("NOTDEFINED", "NOTDEFINED", "Not defined"), - ("DRAFT", "DRAFT", "Document is a draft."), - ("FINALDRAFT", "FINALDRAFT", "Document is a final draft."), - ("FINAL", "FINAL", "Document is final."), - ("REVISION", "REVISION", "Document has undergone revision."), - ], - name="Status", - ) - - -class DocumentReference(PropertyGroup): - location: StringProperty(name="Location") - name: StringProperty(name="Identification") - human_name: StringProperty(name="Name") - description: StringProperty(name="Description") - referenced_document: StringProperty(name="Referenced Document") - - -class ClashSource(PropertyGroup): - name: StringProperty(name="File") - selector: StringProperty(name="Selector") - mode: EnumProperty( - items=[ - ("i", "Include", "Only the selected objects are included for clashing"), - ("e", "Exclude", "All objects except the selected objects are included for clashing"), - ], - name="Mode", - ) - - -class ClashSet(PropertyGroup): - name: StringProperty(name="Name") - tolerance: FloatProperty(name="Tolerance") - a: CollectionProperty(name="Group A", type=ClashSource) - b: CollectionProperty(name="Group B", type=ClashSource) - - -class PresentationLayer(PropertyGroup): - name: StringProperty(name="Name") - description: StringProperty(name="Description") - identifier: StringProperty(name="Identifier") - layer_on: BoolProperty(name="LayerOn", default=True) - layer_frozen: BoolProperty(name="LayerFrozen", default=False) - layer_blocked: BoolProperty(name="LayerBlocked", default=False) - -class SmartClashGroup(PropertyGroup): - number: StringProperty(name="Number") - global_ids: CollectionProperty(name="GlobalIDs", type=StrProperty) - - -class Constraint(PropertyGroup): - name: StringProperty(name="Name") - description: StringProperty(name="Description") - constraint_grade: EnumProperty( - items=[ - ( - "HARD", - "HARD", - "Qualifies a constraint such that it must be followed rigidly within or at the values set.", - ), - ("SOFT", "SOFT", "Qualifies a constraint such that it should be followed within or at the values set."), - ( - "ADVISORY", - "ADVISORY", - "Qualifies a constraint such that it is advised that it is followed within or at the values set.", - ), - ( - "USERDEFINED", - "USERDEFINED", - "A user-defined grade indicated by a separate attribute at the referencing entity.", - ), - ("NOTDEFINED", "NOTDEFINED", "Grade has not been specified."), - ], - name="Grade", - ) - constraint_source: StringProperty(name="Source") - user_defined_grade: StringProperty(name="Custom Grade") - objective_qualifier: EnumProperty( - items=[ - ( - "CODECOMPLIANCE", - "CODECOMPLIANCE", - "A constraint whose objective is to ensure satisfaction of a code compliance provision.", - ), - ( - "CODEWAIVER", - "CODEWAIVER", - "A constraint whose objective is to identify an agreement that code compliance requirements (the waiver) will not be enforced.", - ), - ( - "DESIGNINTENT", - "DESIGNINTENT", - "A constraint whose objective is to ensure satisfaction of a design intent provision.", - ), - ( - "EXTERNAL", - "EXTERNAL", - "A constraint whose objective is to synchronize data with an external source such as a file", - ), - ( - "HEALTHANDSAFETY", - "HEALTHANDSAFETY", - "A constraint whose objective is to ensure satisfaction of a health and safety provision.", - ), - ( - "MERGECONFLICT", - "MERGECONFLICT", - "A constraint whose objective is to resolve a conflict such as merging data from multiple sources.", - ), - ( - "MODELVIEW", - "MODELVIEW", - "A constraint whose objective is to ensure data conforms to a model view definition.", - ), - ( - "PARAMETER", - "PARAMETER", - "A constraint whose objective is to calculate a value based on other referenced values.", - ), - ( - "REQUIREMENT", - "REQUIREMENT", - "A constraint whose objective is to ensure satisfaction of a project requirement provision.", - ), - ( - "SPECIFICATION", - "SPECIFICATION", - "A constraint whose objective is to ensure satisfaction of a specification provision.", - ), - ( - "TRIGGERCONDITION", - "TRIGGERCONDITION", - "A constraint whose objective is to indicate a limiting value beyond which the condition of an object requires a particular form of attention.", - ), - ("USERDEFINED", "USERDEFINED", ""), - ("NOTDEFINED", "NOTDEFINED", ""), - ], - name="Qualifier", - ) - user_defined_qualifier: StringProperty(name="Custom Qualifier") - - -class PropertySetTemplate(PropertyGroup): - global_id: StringProperty(name="Global ID") - name: StringProperty(name="Name") - description: StringProperty(name="Description") - template_type: EnumProperty( - items=[ - ( - "PSET_TYPEDRIVENONLY", - "Pset - IfcTypeObject", - "The property sets defined by this IfcPropertySetTemplate can only be assigned to subtypes of IfcTypeObject.", - ), - ( - "PSET_TYPEDRIVENOVERRIDE", - "Pset - IfcTypeObject - Override", - "The property sets defined by this IfcPropertySetTemplate can only be assigned to subtypes of IfcTypeObject.", - ), - ( - "PSET_OCCURRENCEDRIVEN", - "Pset - IfcObject", - "The property sets defined by this IfcPropertySetTemplate can only be assigned to subtypes of IfcObject.", - ), - ( - "PSET_PERFORMANCEDRIVEN", - "Pset - IfcPerformanceHistory", - "The property sets defined by this IfcPropertySetTemplate can only be assigned to IfcPerformanceHistory.", - ), - ( - "QTO_TYPEDRIVENONLY", - "Qto - IfcTypeObject", - "The element quantity defined by this IfcPropertySetTemplate can only be assigned to subtypes of IfcTypeObject.", - ), - ( - "QTO_TYPEDRIVENOVERRIDE", - "Qto - IfcTypeObject - Override", - "The element quantity defined by this IfcPropertySetTemplate can be assigned to subtypes of IfcTypeObject and can be overridden by an element quantity with same name at subtypes of IfcObject.", - ), - ( - "QTO_OCCURRENCEDRIVEN", - "Qto - IfcObject", - "The element quantity defined by this IfcPropertySetTemplate can only be assigned to subtypes of IfcObject.", - ), - ( - "NOTDEFINED", - "Not defined", - "No restriction provided, the property sets defined by this IfcPropertySetTemplate can be assigned to any entity, if not otherwise restricted by the ApplicableEntity attribute.", - ), - ], - name="Template Type", - ) - applicable_entity: StringProperty(name="Applicable Entity") - - -class PropertyTemplate(PropertyGroup): - global_id: StringProperty(name="Global ID") - name: StringProperty(name="Name") - description: StringProperty(name="Description") - primary_measure_type: EnumProperty( - items=[ - (x, x, "") - for x in [ - "IfcInteger", - "IfcReal", - "IfcBoolean", - "IfcIdentifier", - "IfcText", - "IfcLabel", - "IfcLogical", - "IfcDateTime", - "IfcDate", - "IfcTime", - "IfcDuration", - "IfcTimeStamp", - "IfcPositiveInteger", - "IfcBinary", - "IfcVolumeMeasure", - "IfcTimeMeasure", - "IfcThermodynamicTemperatureMeasure", - "IfcSolidAngleMeasure", - "IfcPositiveRatioMeasure", - "IfcRatioMeasure", - "IfcPositivePlaneAngleMeasure", - "IfcPlaneAngleMeasure", - "IfcParameterValue", - "IfcNumericMeasure", - "IfcMassMeasure", - "IfcPositiveLengthMeasure", - "IfcLengthMeasure", - "IfcElectricCurrentMeasure", - "IfcDescriptiveMeasure", - "IfcCountMeasure", - "IfcContextDependentMeasure", - "IfcAreaMeasure", - "IfcAmountOfSubstanceMeasure", - "IfcLuminousIntensityMeasure", - "IfcNormalisedRatioMeasure", - "IfcComplexNumber", - "IfcNonNegativeLengthMeasure", - "IfcAbsorbedDoseMeasure", - "IfcAccelerationMeasure", - "IfcAngularVelocityMeasure", - "IfcAreaDensityMeasure", - "IfcCompoundPlaneAngleMeasure", - "IfcCurvatureMeasure", - "IfcDoseEquivalentMeasure", - "IfcDynamicViscosityMeasure", - "IfcElectricCapacitanceMeasure", - "IfcElectricChargeMeasure", - "IfcElectricConductanceMeasure", - "IfcElectricResistanceMeasure", - "IfcElectricVoltageMeasure", - "IfcEnergyMeasure", - "IfcForceMeasure", - "IfcFrequencyMeasure", - "IfcHeatFluxDensityMeasure", - "IfcHeatingValueMeasure", - "IfcIlluminanceMeasure", - "IfcInductanceMeasure", - "IfcIntegerCountRateMeasure", - "IfcIonConcentrationMeasure", - "IfcIsothermalMoistureCapacityMeasure", - "IfcKinematicViscosityMeasure", - "IfcLinearForceMeasure", - "IfcLinearMomentMeasure", - "IfcLinearStiffnessMeasure", - "IfcLinearVelocityMeasure", - "IfcLuminousFluxMeasure", - "IfcLuminousIntensityDistributionMeasure", - "IfcMagneticFluxDensityMeasure", - "IfcMagneticFluxMeasure", - "IfcMassDensityMeasure", - "IfcMassFlowRateMeasure", - "IfcMassPerLengthMeasure", - "IfcModulusOfElasticityMeasure", - "IfcModulusOfLinearSubgradeReactionMeasure", - "IfcModulusOfRotationalSubgradeReactionMeasure", - "IfcModulusOfSubgradeReactionMeasure", - "IfcMoistureDiffusivityMeasure", - "IfcMolecularWeightMeasure", - "IfcMomentOfInertiaMeasure", - "IfcMonetaryMeasure", - "IfcPHMeasure", - "IfcPlanarForceMeasure", - "IfcPowerMeasure", - "IfcPressureMeasure", - "IfcRadioActivityMeasure", - "IfcRotationalFrequencyMeasure", - "IfcRotationalMassMeasure", - "IfcRotationalStiffnessMeasure", - "IfcSectionModulusMeasure", - "IfcSectionalAreaIntegralMeasure", - "IfcShearModulusMeasure", - "IfcSoundPowerLevelMeasure", - "IfcSoundPowerMeasure", - "IfcSoundPressureLevelMeasure", - "IfcSoundPressureMeasure", - "IfcSpecificHeatCapacityMeasure", - "IfcTemperatureGradientMeasure", - "IfcTemperatureRateOfChangeMeasure", - "IfcThermalAdmittanceMeasure", - "IfcThermalConductivityMeasure", - "IfcThermalExpansionCoefficientMeasure", - "IfcThermalResistanceMeasure", - "IfcThermalTransmittanceMeasure", - "IfcTorqueMeasure", - "IfcVaporPermeabilityMeasure", - "IfcVolumetricFlowRateMeasure", - "IfcWarpingConstantMeasure", - "IfcWarpingMomentMeasure", - ] - ], - name="Primary Measure Type", - ) - - -class Classification(PropertyGroup): - name: StringProperty(name="Name") - source: StringProperty(name="Source") - edition: StringProperty(name="Edition") - edition_date: StringProperty(name="Edition Date") - description: StringProperty(name="Description") - location: StringProperty(name="Location") - reference_tokens: StringProperty(name="Reference Tokens") - data: StringProperty(name="Data") - - -class ClassificationReference(PropertyGroup): - name: StringProperty(name="Identification") - location: StringProperty(name="Location") - human_name: StringProperty(name="Name") - referenced_source: StringProperty(name="Source") - description: StringProperty(name="Description") - sort: StringProperty(name="Sort") - - -class ClassificationView(PropertyGroup): - crumbs: None - children: None - active_index: bpy.props.IntProperty() - raw_data = {} - - @property - def root(self): - data = self.raw_data - for crumb in self.crumbs: - data = data["children"].get(crumb.name) - if not data: - raise TypeError("Cannot resolve crumb path") - return data - - @root.setter - def root(self, rt): - if rt == None: - self.crumbs.clear() - self.children.clear() - elif rt == "": - if self.crumbs: - self.crumbs.remove(len(self.crumbs) - 1) - self.children.clear() - for child in self.root["children"].keys(): - self.children.add().name = child - else: - data = self.root - if rt in data["children"].keys(): - self.crumbs.add().name = rt - self.children.clear() - for child in data["children"][rt]["children"].keys(): - self.children.add().name = child - - def draw_stub(self, context, layout): - if not self.children: - op = layout.operator("bim.change_classification_level", text="@Toplevel") - else: - op = layout.operator("bim.change_classification_level", text=self.root["name"]) - op.path_sid = "%r" % self.id_data - op.path_lst = self.path_from_id() - op.path_itm = "" - layout.template_list("BIM_UL_classifications", self.path_from_id(), self, "children", self, "active_index") - - -# Monkey-patched, just to keep registration in one block -ClassificationView.__annotations__["crumbs"] = bpy.props.CollectionProperty(type=StrProperty) -ClassificationView.__annotations__["children"] = bpy.props.CollectionProperty(type=StrProperty) - - class BIMProperties(PropertyGroup): schema_dir: StringProperty(default=os.path.join(cwd, "schema") + os.path.sep, name="Schema Directory") data_dir: StringProperty(default=os.path.join(cwd, "data") + os.path.sep, name="Data Directory") ifc_file: StringProperty(name="IFC File") - ifc_product: EnumProperty(items=getIfcProducts, name="Products", update=refreshClasses) - ifc_class: EnumProperty(items=getIfcClasses, name="Class", update=refreshPredefinedTypes) - ifc_predefined_type: EnumProperty(items=getIfcPredefinedTypes, name="Predefined Type", default=None) - ifc_userdefined_type: StringProperty(name="Userdefined Type") export_schema: EnumProperty(items=[("IFC4", "IFC4", ""), ("IFC2X3", "IFC2X3", "")], name="IFC Schema") - export_json_version: EnumProperty(items=[("4", "4", ""), ("5a", "5a", "")], name="IFC JSON Version") - export_json_compact: BoolProperty(name="Export Compact IFCJSON", default=False) - export_has_representations: BoolProperty(name="Export Representations", default=True) - export_should_guess_quantities: BoolProperty(name="Export with Guessed Quantities", default=False) - export_should_use_presentation_style_assignment: BoolProperty( - name="Export with Presentation Style Assignment", default=False - ) - export_should_force_faceted_brep: BoolProperty(name="Export with Faceted Breps", default=False) - export_should_force_triangulation: BoolProperty(name="Export with Triangulation", default=False) - export_should_export_from_memory: BoolProperty(name="Export from Memory", default=True) - import_should_import_type_representations: BoolProperty(name="Import Type Representations", default=False) - import_should_import_curves: BoolProperty(name="Import Curves", default=False) - import_should_import_opening_elements: BoolProperty(name="Import Opening Elements", default=False) - import_should_import_spaces: BoolProperty(name="Import Spaces", default=False) - import_should_auto_set_workarounds: BoolProperty(name="Automatically Set Vendor Workarounds", default=True) - import_should_import_native: BoolProperty(name="Import Native Representations", default=False) - import_export_should_roundtrip_native: BoolProperty(name="Roundtrip Native Representations", default=True) - import_should_use_cpu_multiprocessing: BoolProperty(name="Import with CPU Multiprocessing", default=True) - import_should_import_with_profiling: BoolProperty(name="Import with Profiling", default=True) - import_should_import_aggregates: BoolProperty(name="Import Aggregates", default=True) - import_should_merge_aggregates: BoolProperty(name="Import and Merge Aggregates", default=False) - import_should_merge_by_class: BoolProperty(name="Import and Merge by Class", default=False) - import_should_merge_by_material: BoolProperty(name="Import and Merge by Material", default=False) - import_should_merge_materials_by_colour: BoolProperty(name="Import and Merge Materials by Colour", default=False) - import_should_clean_mesh: BoolProperty(name="Import and Clean Mesh", default=True) - import_deflection_tolerance: FloatProperty(name="Import Deflection Tolerance", default=0.001) - import_angular_tolerance: FloatProperty(name="Import Angular Tolerance", default=0.5) - import_should_allow_non_element_aggregates: BoolProperty(name="Import Non-Element Aggregates", default=False) - import_should_offset_model: BoolProperty(name="Import and Offset Model", default=False) - import_model_offset_coordinates: StringProperty(name="Model Offset Coordinates", default="0,0,0") - - has_georeferencing: BoolProperty(name="Has Georeferencing", default=False) - has_library: BoolProperty(name="Has Project Library", default=False) - search_regex: BoolProperty(name="Search With Regex", default=False) - search_ignorecase: BoolProperty(name="Search Ignoring Case", default=True) - global_id: StringProperty(name="GlobalId") - search_attribute_name: StringProperty(name="Search Attribute Name") - search_attribute_value: StringProperty(name="Search Attribute Value") - search_pset_name: StringProperty(name="Search Pset Name") - search_prop_name: StringProperty(name="Search Prop Name") - search_pset_value: StringProperty(name="Search Pset Value") - classification: EnumProperty(items=getClassifications, name="Classification", update=refreshReferences) - active_classification_name: StringProperty(name="Active Classification Name") - classifications: CollectionProperty(name="Classifications", type=Classification) contexts: EnumProperty(items=getContexts, name="Contexts") available_contexts: EnumProperty(items=[("Model", "Model", ""), ("Plan", "Plan", "")], name="Available Contexts") available_subcontexts: EnumProperty(items=getSubcontexts, name="Available Subcontexts") available_target_views: EnumProperty(items=getTargetViews, name="Available Target Views") - classification_references: PointerProperty(type=ClassificationView) - pset_template_files: EnumProperty( - items=getPsetTemplateFiles, name="Pset Template Files", update=refreshPropertySetTemplates - ) - property_set_templates: EnumProperty(items=getPropertySetTemplates, name="Pset Template Files") - active_property_set_template: PointerProperty(type=PropertySetTemplate) - property_templates: CollectionProperty(name="Property Templates", type=PropertyTemplate) should_section_selected_objects: BoolProperty(name="Section Selected Objects", default=False) section_plane_colour: FloatVectorProperty( name="Temporary Section Cutaway Colour", subtype="COLOR", default=(1, 0, 0), min=0.0, max=1.0 ) - ifc_import_filter: EnumProperty( - items=[("NONE", "None", ""), ("WHITELIST", "Whitelist", ""), ("BLACKLIST", "Blacklist", ""),], - name="Import Filter", - ) - ifc_selector: StringProperty(default="", name="IFC Selector") - document_information: CollectionProperty(name="Document Information", type=DocumentInformation) - active_document_information_index: IntProperty(name="Active Document Information Index") - document_references: CollectionProperty(name="Document References", type=DocumentReference) - active_document_reference_index: IntProperty(name="Active Document Reference Index") - blender_clash_set_a: CollectionProperty(name="Blender Clash Set A", type=StrProperty) - blender_clash_set_b: CollectionProperty(name="Blender Clash Set B", type=StrProperty) - clash_sets: CollectionProperty(name="Clash Sets", type=ClashSet) - should_create_clash_snapshots: BoolProperty(name="Create Snapshots", default=True) - clash_results_path: StringProperty(name="Clash Results Path") - smart_grouped_clashes_path: StringProperty(name="Smart Grouped Clashes Path") - active_clash_set_index: IntProperty(name="Active Clash Set Index") - smart_clash_groups: CollectionProperty(name="Smart Clash Groups", type=SmartClashGroup) - active_smart_group_index: IntProperty(name="Active Smart Group Index") - smart_clash_grouping_max_distance: IntProperty(name="Smart Clash Grouping Max Distance", default=3, soft_min=1, soft_max=10) - constraints: CollectionProperty(name="Constraints", type=Constraint) - active_constraint_index: IntProperty(name="Active Constraint Index") - ifc_patch_recipes: EnumProperty(items=getIfcPatchRecipes, name="Recipes") - ifc_patch_input: StringProperty(default="", name="IFC Patch Input IFC") - ifc_patch_output: StringProperty(default="", name="IFC Patch Output IFC") - ifc_patch_args: StringProperty(default="", name="Arguments") - qto_result: StringProperty(default="", name="Qto Result") area_unit: EnumProperty( default="SQUARE_METRE", items=[ @@ -1132,17 +534,6 @@ class BIMProperties(PropertyGroup): override_colour: FloatVectorProperty( name="Override Colour", subtype="COLOR", default=(1, 0, 0, 1), min=0.0, max=1.0, size=4 ) - active_presentation_layer_index: IntProperty(name="Active Presentation Layer Index") - presentation_layers: CollectionProperty(name="Presentation Layers", type=PresentationLayer) - - -class BIMLibrary(PropertyGroup): - name: StringProperty(name="Name") - version: StringProperty(name="Version") - publisher: StringProperty(name="Publisher") - version_date: StringProperty(name="Version Date") - location: StringProperty(name="Location") - description: StringProperty(name="Description") class IfcParameter(PropertyGroup): @@ -1184,11 +575,6 @@ class BIMObjectProperties(PropertyGroup): relating_structure: PointerProperty(name="Spatial Container", type=bpy.types.Object) psets: CollectionProperty(name="Psets", type=PsetQto) qtos: CollectionProperty(name="Qtos", type=PsetQto) - document_references: CollectionProperty(name="Document References", type=DocumentReference) - active_document_reference_index: IntProperty(name="Active Document Reference Index") - constraints: CollectionProperty(name="Constraints", type=Constraint) - active_constraint_index: IntProperty(name="Active Constraint Index") - classifications: CollectionProperty(name="Classifications", type=ClassificationReference) has_boundary_condition: BoolProperty(name="Has Boundary Condition") boundary_condition: PointerProperty(name="Boundary Condition", type=BoundaryCondition) structural_member_connection: PointerProperty(name="Structural Member Connection", type=bpy.types.Object) @@ -1202,7 +588,6 @@ class BIMMaterialProperties(PropertyGroup): pset_name: EnumProperty(items=getMaterialPsetNames, name="Pset Name") psets: CollectionProperty(name="Psets", type=PsetQto) attributes: CollectionProperty(name="Attributes", type=Attribute) - applicable_attributes: EnumProperty(items=getApplicableMaterialAttributes, name="Attribute Names") # In Blender, a material object can map to an IFC material, IFC surface style, or both ifc_style_id: IntProperty(name="IFC Style ID") @@ -1232,6 +617,4 @@ class BIMMeshProperties(PropertyGroup): is_parametric: BoolProperty(name="Is Parametric", default=False) ifc_definition: StringProperty(name="IFC Definition") ifc_parameters: CollectionProperty(name="IFC Parameters", type=IfcParameter) - active_representation_item_index: IntProperty(name="Active Representation Item Index") - presentation_layer_index: IntProperty(name="Presentation Layer Index", default=-1) ifc_item_ids: CollectionProperty(name="IFC Item IDs", type=ItemSlotMap) diff --git a/src/ifcblenderexport/blenderbim/bim/ui.py b/src/ifcblenderexport/blenderbim/bim/ui.py index 4ea0116d6b..0ce6f6ccfc 100644 --- a/src/ifcblenderexport/blenderbim/bim/ui.py +++ b/src/ifcblenderexport/blenderbim/bim/ui.py @@ -5,483 +5,6 @@ from bpy.types import Panel from bpy.props import StringProperty -class BIM_PT_object_structural(Panel): - bl_label = "IFC Structural Relationships" - bl_idname = "BIM_PT_object_structural" - bl_options = {"DEFAULT_CLOSED"} - bl_space_type = "PROPERTIES" - bl_region_type = "WINDOW" - bl_context = "object" - - @classmethod - def poll(cls, context): - return context.active_object is not None and hasattr(context.active_object, "BIMObjectProperties") - - def draw(self, context): - if context.active_object is None: - return - layout = self.layout - props = context.active_object.BIMObjectProperties - row = layout.row() - row.prop(props, "has_boundary_condition") - - if bpy.context.active_object.BIMObjectProperties.has_boundary_condition: - row = layout.row() - row.prop(props.boundary_condition, "name") - for index, attribute in enumerate(props.boundary_condition.attributes): - row = layout.row(align=True) - row.prop(attribute, "name", text="") - row.prop(attribute, "string_value", text="") - - row = layout.row() - row.prop(props, "structural_member_connection") - - -class BIM_PT_document_information(Panel): - bl_label = "IFC Documents" - bl_idname = "BIM_PT_document_information" - bl_options = {"DEFAULT_CLOSED"} - bl_space_type = "PROPERTIES" - bl_region_type = "WINDOW" - bl_context = "scene" - - def draw(self, context): - layout = self.layout - layout.use_property_split = True - props = context.scene.BIMProperties - - row = layout.row() - row.operator("bim.add_document_information") - - if props.document_information: - layout.template_list( - "BIM_UL_document_information", - "", - props, - "document_information", - props, - "active_document_information_index", - ) - - if props.active_document_information_index < len(props.document_information): - information = props.document_information[props.active_document_information_index] - row = layout.row(align=True) - row.prop(information, "name") - row.operator( - "bim.remove_document_information", icon="X", text="" - ).index = props.active_document_information_index - row = layout.row() - row.prop(information, "human_name") - row = layout.row() - row.prop(information, "description") - row = layout.row() - row.prop(information, "location") - row = layout.row() - row.prop(information, "purpose") - row = layout.row() - row.prop(information, "intended_use") - row = layout.row() - row.prop(information, "scope") - row = layout.row() - row.prop(information, "revision") - row = layout.row() - row.prop(information, "creation_time") - row = layout.row() - row.prop(information, "last_revision_time") - row = layout.row() - row.prop(information, "electronic_format") - row = layout.row() - row.prop(information, "valid_from") - row = layout.row() - row.prop(information, "valid_until") - row = layout.row() - row.prop(information, "confidentiality") - row = layout.row() - row.prop(information, "status") - - row = layout.row() - row.operator("bim.add_document_reference") - - if props.document_references: - layout.template_list( - "BIM_UL_document_references", "", props, "document_references", props, "active_document_reference_index" - ) - - if props.active_document_reference_index < len(props.document_references): - reference = props.document_references[props.active_document_reference_index] - row = layout.row(align=True) - row.prop(reference, "name") - row.operator( - "bim.remove_document_reference", icon="X", text="" - ).index = props.active_document_reference_index - row = layout.row() - row.prop(reference, "human_name") - row = layout.row() - row.prop(reference, "location") - row = layout.row() - row.prop(reference, "description") - row = layout.row(align=True) - row.prop(reference, "referenced_document") - row.operator( - "bim.assign_document_information", icon="LINKED", text="" - ).index = props.active_document_reference_index - - row = layout.row(align=True) - row.operator("bim.assign_document_reference", text="Assign Reference") - row.operator("bim.unassign_document_reference", text="Unassign Reference") - - -class BIM_PT_constraints(Panel): - bl_label = "IFC Constraints" - bl_idname = "BIM_PT_constraints" - bl_options = {"DEFAULT_CLOSED"} - bl_space_type = "PROPERTIES" - bl_region_type = "WINDOW" - bl_context = "scene" - - def draw(self, context): - layout = self.layout - layout.use_property_split = True - props = context.scene.BIMProperties - - row = layout.row() - row.operator("bim.add_constraint") - - if props.constraints: - layout.template_list("BIM_UL_constraints", "", props, "constraints", props, "active_constraint_index") - - if props.active_constraint_index < len(props.constraints): - constraint = props.constraints[props.active_constraint_index] - row = layout.row(align=True) - row.prop(constraint, "name") - row.operator("bim.remove_constraint", icon="X", text="").index = props.active_constraint_index - row = layout.row() - row.prop(constraint, "description") - row = layout.row() - row.prop(constraint, "constraint_grade") - if constraint.constraint_grade == "USERDEFINED": - row = layout.row() - row.prop(constraint, "user_defined_grade") - row = layout.row() - row.prop(constraint, "constraint_source") - row = layout.row() - row.prop(constraint, "objective_qualifier") - if constraint.objective_qualifier == "USERDEFINED": - row = layout.row() - row.prop(constraint, "user_defined_qualifier") - - row = layout.row(align=True) - row.operator("bim.assign_constraint", text="Assign Constraint") - row.operator("bim.unassign_constraint", text="Unassign Constraint") - - -class BIM_PT_documents(Panel): - bl_label = "IFC Documents" - bl_idname = "BIM_PT_documents" - bl_options = {"DEFAULT_CLOSED"} - bl_space_type = "PROPERTIES" - bl_region_type = "WINDOW" - bl_context = "object" - - def draw(self, context): - layout = self.layout - layout.use_property_split = True - props = context.active_object.BIMObjectProperties - - if not props.document_references: - layout.label(text="No documents found") - - row = layout.row() - row.operator("bim.fetch_object_passport") - - if props.document_references: - layout.template_list( - "BIM_UL_document_references", "", props, "document_references", props, "active_document_reference_index" - ) - - if props.active_document_reference_index < len(props.document_references): - reference = props.document_references[props.active_document_reference_index] - row = layout.row(align=True) - row.prop(reference, "name") - if reference.name in bpy.context.scene.BIMProperties.document_references: - reference = bpy.context.scene.BIMProperties.document_references[reference.name] - row.operator( - "bim.remove_object_document_reference", icon="X", text="" - ).index = props.active_document_reference_index - row = layout.row() - row.prop(reference, "human_name") - row = layout.row() - row.prop(reference, "location") - row = layout.row() - row.prop(reference, "description") - row = layout.row() - row.prop(reference, "referenced_document") - else: - layout.label(text="Reference is invalid") - - -class BIM_PT_constraint_relations(Panel): - bl_label = "IFC Constraints" - bl_idname = "BIM_PT_constraint_relations" - bl_options = {"DEFAULT_CLOSED"} - bl_space_type = "PROPERTIES" - bl_region_type = "WINDOW" - bl_context = "object" - - def draw(self, context): - layout = self.layout - layout.use_property_split = True - props = context.active_object.BIMObjectProperties - - if not props.constraints: - layout.label(text="No constraints found") - - if props.constraints: - layout.template_list("BIM_UL_constraints", "", props, "constraints", props, "active_constraint_index") - - if props.active_constraint_index < len(props.constraints): - constraint = props.constraints[props.active_constraint_index] - row = layout.row(align=True) - row.prop(constraint, "name") - if constraint.name in bpy.context.scene.BIMProperties.constraints: - constraint = bpy.context.scene.BIMProperties.constraints[constraint.name] - row.operator( - "bim.remove_object_constraint", icon="X", text="" - ).index = props.active_constraint_index - row = layout.row() - row.prop(constraint, "description") - row = layout.row() - row.prop(constraint, "constraint_grade") - if constraint.constraint_grade == "USERDEFINED": - row = layout.row() - row.prop(constraint, "user_defined_grade") - row = layout.row() - row.prop(constraint, "constraint_source") - row = layout.row() - row.prop(constraint, "objective_qualifier") - if constraint.objective_qualifier == "USERDEFINED": - row = layout.row() - row.prop(constraint, "user_defined_qualifier") - else: - layout.label(text="Constraint is invalid") - - -class BIM_PT_classification_references(Panel): - bl_label = "IFC Classification References" - bl_idname = "BIM_PT_classification_references" - bl_options = {"DEFAULT_CLOSED"} - bl_space_type = "PROPERTIES" - bl_region_type = "WINDOW" - bl_context = "object" - - def draw(self, context): - layout = self.layout - layout.use_property_split = True - props = context.active_object.BIMObjectProperties - - if not props.classifications: - layout.label(text="No classifications found") - - for index, classification in enumerate(props.classifications): - row = layout.row(align=True) - row.prop(classification, "name") - row.operator("bim.remove_classification_reference", icon="X", text="").classification_index = index - row = layout.row(align=True) - row.prop(classification, "human_name") - row = layout.row(align=True) - row.prop(classification, "location") - row = layout.row(align=True) - row.prop(classification, "description") - row = layout.row(align=True) - row.prop(classification, "referenced_source") - - -class BIM_PT_psets(Panel): - bl_label = "IFC Property Sets" - bl_idname = "BIM_PT_psets" - bl_options = {"DEFAULT_CLOSED"} - bl_space_type = "PROPERTIES" - bl_region_type = "WINDOW" - bl_context = "scene" - - def draw(self, context): - layout = self.layout - props = context.scene.BIMProperties - - row = layout.row(align=True) - row.prop(props, "pset_template_files", text="") - - row = layout.row(align=True) - row.prop(props, "property_set_templates", text="") - row.operator("bim.add_property_set_template", text="", icon="ADD") - row.operator("bim.remove_property_set_template", text="", icon="PANEL_CLOSE") - row.operator("bim.edit_property_set_template", text="", icon="IMPORT") - row.operator("bim.save_property_set_template", text="", icon="EXPORT") - - row = layout.row(align=True) - row.prop(props.active_property_set_template, "name") - row = layout.row(align=True) - row.prop(props.active_property_set_template, "description") - row = layout.row(align=True) - row.prop(props.active_property_set_template, "template_type") - row = layout.row(align=True) - row.prop(props.active_property_set_template, "applicable_entity") - - layout.label(text="Property Templates:") - - row = layout.row(align=True) - row.operator("bim.add_property_template") - - for index, template in enumerate(props.property_templates): - row = layout.row(align=True) - row.prop(template, "name", text="") - row.prop(template, "description", text="") - row.prop(template, "primary_measure_type", text="") - row.operator("bim.remove_property_template", icon="X", text="").index = index - - -class BIM_PT_classifications(Panel): - bl_label = "IFC Classifications" - bl_idname = "BIM_PT_classifications" - bl_options = {"DEFAULT_CLOSED"} - bl_space_type = "PROPERTIES" - bl_region_type = "WINDOW" - bl_context = "scene" - - def draw(self, context): - layout = self.layout - props = context.scene.BIMProperties - - row = layout.row(align=True) - row.prop(props, "classification", text="") - row.operator("bim.add_classification", text="", icon="ADD") - - if context.scene.BIMProperties.classification_references.raw_data: - context.scene.BIMProperties.classification_references.draw_stub(context, layout) - row = layout.row(align=True) - row.operator("bim.assign_classification") - row.operator("bim.unassign_classification") - else: - row = layout.row(align=True) - row.operator("bim.load_classification").is_file = True - - if not props.classifications: - return - - layout.label(text="Classifications:") - - for index, classification in enumerate(props.classifications): - row = layout.row(align=True) - row.prop(classification, "name") - row.operator("bim.load_classification", icon="IMPORT", text="").classification_index = index - row.operator("bim.remove_classification", icon="X", text="").classification_index = index - row = layout.row(align=True) - row.prop(classification, "source") - row = layout.row(align=True) - row.prop(classification, "edition") - row = layout.row(align=True) - row.prop(classification, "edition_date") - row = layout.row(align=True) - row.prop(classification, "description") - row = layout.row(align=True) - row.prop(classification, "location") - row = layout.row(align=True) - row.prop(classification, "reference_tokens") - - row = layout.row() - row.prop(props, "classifications") - - -class BIM_PT_presentation_layer_data(Panel): - bl_label = "IFC Presentation Layers" - bl_idname = "BIM_PT_presentation" - bl_space_type = "PROPERTIES" - bl_region_type = "WINDOW" - bl_context = "data" - - @classmethod - def poll(cls, context): - return ( - context.active_object is not None - and context.active_object.type == "MESH" - and hasattr(context.active_object.data, "BIMMeshProperties") - ) - - def draw(self, context): - if not context.active_object.data: - return - layout = self.layout - props = context.active_object.data.BIMMeshProperties - scene_props = context.scene.BIMProperties - - if props.presentation_layer_index != -1: - layer = scene_props.presentation_layers[props.presentation_layer_index] - layout.label(text=f"Assigned to: {layer.name}") - layout.row().operator("bim.unassign_presentation_layer") - return - - if not scene_props.presentation_layers: - layout.label(text=f"No presentation layers are available") - return - - layout.template_list( - "BIM_UL_generic", - "", - scene_props, - "presentation_layers", - scene_props, - "active_presentation_layer_index", - ) - if scene_props.active_presentation_layer_index < len(scene_props.presentation_layers): - op = layout.row().operator("bim.assign_presentation_layer") - op.index = scene_props.active_presentation_layer_index - - -class BIM_PT_presentation_layers(Panel): - bl_label = "IFC Presentation Layers" - bl_idname = "BIM_PT_presentation_layer" - bl_options = {"DEFAULT_CLOSED"} - bl_space_type = "PROPERTIES" - bl_region_type = "WINDOW" - bl_context = "scene" - - def draw(self, context): - layout = self.layout - layout.use_property_split = True - props = context.scene.BIMProperties - - layout.row().operator("bim.add_presentation_layer") - - if not props.presentation_layers: - return - - layout.template_list( - "BIM_UL_generic", "", props, "presentation_layers", props, "active_presentation_layer_index" - ) - - if props.active_presentation_layer_index < len(props.presentation_layers): - layer = props.presentation_layers[props.active_presentation_layer_index] - - row = layout.row(align=True) - row.prop(layer, "name") - row.operator( - "bim.remove_presentation_layer", icon="X", text="" - ).index = props.active_presentation_layer_index - row = layout.row() - row.prop(layer, "description") - row = layout.row() - row.prop(layer, "identifier") - row = layout.row() - row.prop(layer, "layer_on") - row = layout.row() - row.prop(layer, "layer_frozen") - row = layout.row() - row.prop(layer, "layer_blocked") - - op = layout.row().operator("bim.update_presentation_layer") - op.index = props.active_presentation_layer_index - - class BIM_PT_drawings(Panel): bl_label = "SVG Drawings" bl_idname = "BIM_PT_drawings" @@ -732,219 +255,8 @@ class BIM_PT_text(Panel): row.prop(variable, "prop_key") -class BIM_PT_search(Panel): - bl_label = "IFC Search" - bl_idname = "BIM_PT_search" - bl_options = {"DEFAULT_CLOSED"} - bl_space_type = "PROPERTIES" - bl_region_type = "WINDOW" - bl_context = "scene" - - def draw(self, context): - layout = self.layout - - scene = context.scene - props = scene.BIMProperties - - row = layout.row() - row.prop(props, "search_regex") - row = layout.row() - row.prop(props, "search_ignorecase") - - layout.label(text="Global ID:") - row = layout.row(align=True) - row.prop(props, "global_id", text="") - row.operator("bim.select_global_id", text="", icon="VIEWZOOM") - - layout.label(text="Attribute:") - row = layout.row(align=True) - row.prop(props, "search_attribute_name", text="") - row.prop(props, "search_attribute_value", text="") - row.operator("bim.select_attribute", text="", icon="VIEWZOOM") - row.operator("bim.colour_by_attribute", text="", icon="BRUSH_DATA") - - layout.label(text="Pset:") - row = layout.row(align=True) - row.prop(props, "search_pset_name", text="") - row.prop(props, "search_prop_name", text="") - row.prop(props, "search_pset_value", text="") - row.operator("bim.select_pset", text="", icon="VIEWZOOM") - row.operator("bim.colour_by_pset", text="", icon="BRUSH_DATA") - - row = layout.row(align=True) - row.operator("bim.select_class") - row.operator("bim.select_type") - - -class BIM_PT_library(Panel): - bl_label = "IFC BIM Server Library" - bl_idname = "BIM_PT_library" - bl_options = {"DEFAULT_CLOSED"} - bl_space_type = "PROPERTIES" - bl_region_type = "WINDOW" - bl_context = "scene" - - def draw(self, context): - layout = self.layout - layout.use_property_split = True - - scene = context.scene - bim_properties = scene.BIMProperties - - layout.row().prop(scene.BIMProperties, "has_library") - - layout.label(text="Project Library:") - layout.row().prop(scene.BIMLibrary, "location") - layout.row().operator("bim.fetch_library_information") - layout.row().prop(scene.BIMLibrary, "name") - layout.row().prop(scene.BIMLibrary, "version") - layout.row().prop(scene.BIMLibrary, "version_date") - layout.row().prop(scene.BIMLibrary, "description") - - -class BIM_PT_patch(Panel): - bl_label = "IFC Patch" - bl_idname = "BIM_PT_patch" - bl_options = {"DEFAULT_CLOSED"} - bl_space_type = "PROPERTIES" - bl_region_type = "WINDOW" - bl_context = "scene" - - def draw(self, context): - layout = self.layout - layout.use_property_split = True - - scene = context.scene - props = scene.BIMProperties - - row = layout.row() - row.prop(props, "ifc_patch_recipes") - row = layout.row(align=True) - row.prop(props, "ifc_patch_input") - row.operator("bim.select_ifc_patch_input", icon="FILE_FOLDER", text="") - row = layout.row(align=True) - row.prop(props, "ifc_patch_output") - row.operator("bim.select_ifc_patch_output", icon="FILE_FOLDER", text="") - row = layout.row() - row.prop(props, "ifc_patch_args") - - row = layout.row() - op = row.operator("bim.execute_ifc_patch") - - -class BIM_PT_mvd(Panel): - bl_label = "Model View Definitions (MVD)" - bl_idname = "BIM_PT_mvd" - bl_options = {"DEFAULT_CLOSED"} - bl_space_type = "PROPERTIES" - bl_region_type = "WINDOW" - bl_context = "scene" - - def draw(self, context): - layout = self.layout - - scene = context.scene - bim_properties = scene.BIMProperties - - row = layout.row() - row.prop(bim_properties, "export_schema") - row = layout.row() - row.prop(bim_properties, "export_json_version") - - row = layout.row() - row.prop(bim_properties, "ifc_import_filter") - row = layout.row() - row.prop(bim_properties, "ifc_selector") - - layout.label(text="Custom MVD:") - - row = layout.row() - row.prop(bim_properties, "export_has_representations") - row = layout.row() - row.prop(bim_properties, "export_should_guess_quantities") - row = layout.row() - row.prop(bim_properties, "export_should_force_faceted_brep") - row = layout.row() - row.prop(bim_properties, "import_should_import_type_representations") - row = layout.row() - row.prop(bim_properties, "import_should_import_curves") - row = layout.row() - row.prop(bim_properties, "import_should_import_opening_elements") - row = layout.row() - row.prop(bim_properties, "import_should_import_spaces") - - layout.label(text="Experimental Modes:") - - row = layout.row() - row.prop(bim_properties, "import_should_import_native") - row = layout.row() - row.prop(bim_properties, "import_export_should_roundtrip_native") - row = layout.row() - row.prop(bim_properties, "export_should_export_from_memory") - row = layout.row() - row.prop(bim_properties, "import_should_use_cpu_multiprocessing") - row = layout.row() - row.prop(bim_properties, "import_should_import_with_profiling") - row = layout.row() - row.prop(bim_properties, "import_deflection_tolerance") - row = layout.row() - row.prop(bim_properties, "import_angular_tolerance") - row = layout.row() - row.prop(bim_properties, "export_json_compact") - - layout.label(text="Simplifications:") - - row = layout.row() - row.prop(bim_properties, "import_should_import_aggregates") - row = layout.row() - row.prop(bim_properties, "import_should_merge_aggregates") - row = layout.row() - row.prop(bim_properties, "import_should_merge_by_class") - row = layout.row() - row.prop(bim_properties, "import_should_merge_by_material") - row = layout.row() - row.prop(bim_properties, "import_should_merge_materials_by_colour") - row = layout.row() - row.prop(bim_properties, "import_should_clean_mesh") - - layout.label(text="Vendor Workarounds:") - - row = layout.row() - row.prop(bim_properties, "import_should_auto_set_workarounds") - - layout.label(text="RIB iTWO Workarounds:") - - row = layout.row() - row.prop(bim_properties, "export_should_force_faceted_brep") - - layout.label(text="DESITE BIM Workarounds:") - - row = layout.row() - row.prop(bim_properties, "export_should_force_faceted_brep") - - layout.label(text="Navisworks Workarounds:") - - row = layout.row() - row.prop(bim_properties, "export_should_force_triangulation") - - layout.label(text="ProStructures Workarounds:") - - row = layout.row() - row.prop(bim_properties, "import_should_allow_non_element_aggregates") - row = layout.row() - row.prop(bim_properties, "import_should_offset_model") - row = layout.row() - row.prop(bim_properties, "import_model_offset_coordinates") - - layout.label(text="Revit Workarounds:") - - row = layout.row() - row.prop(bim_properties, "export_should_use_presentation_style_assignment") - - class BIM_UL_generic(bpy.types.UIList): def draw_item(self, context, layout, data, item, icon, active_data, active_propname): - ob = data if item: layout.prop(item, "name", text="", emboss=False) else: @@ -971,70 +283,6 @@ class BIM_UL_topics(bpy.types.UIList): layout.label(text="", translate=False) -class BIM_UL_clash_sets(bpy.types.UIList): - def draw_item(self, context, layout, data, item, icon, active_data, active_propname): - ob = data - if item: - layout.prop(item, "name", text="", emboss=False) - else: - layout.label(text="", translate=False) - - -class BIM_UL_smart_groups(bpy.types.UIList): - def draw_item(self, context, layout, data, item, icon, active_data, active_propname): - ob = data - if item: - layout.label(text=str(item.number), translate=False, icon="NONE", icon_value=0) - else: - layout.label(text="", translate=False) - - -class BIM_UL_constraints(bpy.types.UIList): - def draw_item(self, context, layout, data, item, icon, active_data, active_propname): - ob = data - if item: - layout.prop(item, "name", text="", emboss=False) - else: - layout.label(text="", translate=False) - - -class BIM_UL_document_information(bpy.types.UIList): - def draw_item(self, context, layout, data, item, icon, active_data, active_propname): - ob = data - if item: - layout.prop(item, "name", text="", emboss=False) - else: - layout.label(text="", translate=False) - - -class BIM_UL_document_references(bpy.types.UIList): - def draw_item(self, context, layout, data, item, icon, active_data, active_propname): - ob = data - if item: - layout.prop(item, "name", text="", emboss=False) - else: - layout.label(text="", translate=False) - - -class BIM_UL_classifications(bpy.types.UIList): - def draw_item(self, context, layout, data, item, icon, active_data, active_propname): - if self.layout_type in {"DEFAULT", "COMPACT"}: - rt = data.root - ch = rt["children"] - itemdata = ch[item.name] - if itemdata.get("children", {}): - op = layout.operator( - "bim.change_classification_level", text="", emboss=False, icon="DISCLOSURE_TRI_RIGHT" - ) - op.path_sid = "%r" % active_data.id_data # get id-data - op.path_lst = active_data.path_from_id() # path to view - op.path_itm = item.name # name of child. empty = go up - else: - layout.label(text="", icon="BLANK1") - layout.prop(item, "name", text="", emboss=False) - layout.label(text=itemdata["name"]) - - class BIM_ADDON_preferences(bpy.types.AddonPreferences): bl_idname = "blenderbim" svg2pdf_command: StringProperty(name="SVG to PDF Command", description="E.g. [['inkscape', svg, '-o', pdf]]") @@ -1063,108 +311,6 @@ class BIM_ADDON_preferences(bpy.types.AddonPreferences): row.prop(self, "pdf_command") -class BIM_PT_ifcclash(Panel): - bl_label = "IFC Clash Sets" - bl_idname = "BIM_PT_ifcclash" - bl_options = {"DEFAULT_CLOSED"} - bl_space_type = "PROPERTIES" - bl_region_type = "WINDOW" - bl_context = "scene" - - def draw(self, context): - layout = self.layout - - scene = context.scene - props = scene.BIMProperties - - layout.label(text="Blender Clash:") - - row = layout.row(align=True) - row.operator("bim.set_blender_clash_set_a") - row.operator("bim.set_blender_clash_set_b") - - row = layout.row(align=True) - row.operator("bim.execute_blender_clash") - - layout.label(text="IFC Clash:") - - row = layout.row(align=True) - row.operator("bim.add_clash_set") - row.operator("bim.import_clash_sets", text="", icon="IMPORT") - row.operator("bim.export_clash_sets", text="", icon="EXPORT") - - if not props.clash_sets: - return - - layout.template_list("BIM_UL_clash_sets", "", props, "clash_sets", props, "active_clash_set_index") - - if props.active_clash_set_index < len(props.clash_sets): - clash_set = props.clash_sets[props.active_clash_set_index] - - row = layout.row(align=True) - row.prop(clash_set, "name") - row.operator("bim.remove_clash_set", icon="X", text="").index = props.active_clash_set_index - - row = layout.row(align=True) - row.prop(clash_set, "tolerance") - - layout.label(text="Group A:") - row = layout.row() - row.operator("bim.add_clash_source").group = "a" - - for index, source in enumerate(clash_set.a): - row = layout.row(align=True) - row.prop(source, "name", text="") - op = row.operator("bim.select_clash_source", icon="FILE_FOLDER", text="") - op.index = index - op.group = "a" - op = row.operator("bim.remove_clash_source", icon="X", text="") - op.index = index - op.group = "a" - - row = layout.row(align=True) - row.prop(source, "mode", text="") - row.prop(source, "selector", text="") - - layout.label(text="Group B:") - row = layout.row() - row.operator("bim.add_clash_source").group = "b" - - for index, source in enumerate(clash_set.b): - row = layout.row(align=True) - row.prop(source, "name", text="") - op = row.operator("bim.select_clash_source", icon="FILE_FOLDER", text="") - op.index = index - op.group = "b" - op = row.operator("bim.remove_clash_source", icon="X", text="") - op.index = index - op.group = "b" - - row = layout.row(align=True) - row.prop(source, "mode", text="") - row.prop(source, "selector", text="") - - row = layout.row() - row.prop(props, "should_create_clash_snapshots") - row = layout.row(align=True) - row.operator("bim.execute_ifc_clash") - row.operator("bim.select_ifc_clash_results") - - -class BIM_PT_modeling_utilities(Panel): - bl_idname = "BIM_PT_modeling_utilities" - bl_label = "Architectural" - bl_space_type = "VIEW_3D" - bl_region_type = "UI" - bl_category = "BlenderBIM" - - def draw(self, context): - layout = self.layout - - row = layout.row(align=True) - row.operator("bim.add_opening") - - class BIM_PT_annotation_utilities(Panel): bl_idname = "BIM_PT_annotation_utilities" bl_label = "Annotation" @@ -1240,70 +386,6 @@ class BIM_PT_annotation_utilities(Panel): layout.prop(props, "decorations_colour") -class BIM_PT_qto_utilities(Panel): - bl_idname = "BIM_PT_qto_utilities" - bl_label = "Quantity Take-off" - bl_space_type = "VIEW_3D" - bl_region_type = "UI" - bl_category = "BlenderBIM" - - def draw(self, context): - layout = self.layout - props = context.scene.BIMProperties - - row = layout.row() - layout.label(text="Results:") - row = layout.row() - row.prop(props, "qto_result", text="") - - row = layout.row(align=True) - row.operator("bim.calculate_edge_lengths") - row = layout.row(align=True) - row.operator("bim.calculate_face_areas") - row = layout.row(align=True) - row.operator("bim.calculate_object_volumes") - - -class BIM_PT_clash_manager(Panel): - bl_idname = "BIM_PT_clash_manager" - bl_label = "Clash Manager" - bl_space_type = "VIEW_3D" - bl_region_type = "UI" - bl_category = "BlenderBIM" - - def draw(self, context): - layout = self.layout - props = context.scene.BIMProperties - - row = layout.row() - layout.label(text="Select clash results to group:") - - row = layout.row(align=True) - row.prop(props, "clash_results_path", text="") - op = row.operator("bim.select_clash_results", icon="FILE_FOLDER", text="") - - row = layout.row() - layout.label(text="Select output path for smart-grouped clashes:") - - row = layout.row(align=True) - row.prop(props, "smart_grouped_clashes_path", text="") - op = row.operator("bim.select_smart_grouped_clashes_path", icon="FILE_FOLDER", text="") - - row = layout.row(align=True) - row.prop(props, "smart_clash_grouping_max_distance") - - row = layout.row(align=True) - row.operator("bim.smart_clash_group") - - row = layout.row(align=True) - row.operator("bim.load_smart_groups_for_active_clash_set") - - layout.template_list("BIM_UL_smart_groups", "", props, "smart_clash_groups", props, "active_smart_group_index") - - row = layout.row(align=True) - row.operator("bim.select_smart_group") - - class BIM_PT_misc_utilities(Panel): bl_idname = "BIM_PT_misc_utilities" bl_label = "Miscellaneous" diff --git a/src/ifcconvert/IfcConvert.cpp b/src/ifcconvert/IfcConvert.cpp index dc15a686f8..39d2a97fd3 100644 --- a/src/ifcconvert/IfcConvert.cpp +++ b/src/ifcconvert/IfcConvert.cpp @@ -378,6 +378,8 @@ int main(int argc, char** argv) { "Stores name and guid in a separate namespace as opposed to data-name, data-guid") ("svg-poly", "Uses the polygonal algorithm for hidden line rendering") + ("svg-project", + "Always enable hidden line rendering instead of only on elevations") ("door-arcs", "Draw door openings arcs for IfcDoor elements") ("section-height", po::value(§ion_height), "Specifies the cut section height for SVG 2D geometry.") @@ -967,6 +969,7 @@ int main(int argc, char** argv) { } static_cast(serializer.get())->setUseNamespace(vmap.count("svg-xmlns") > 0); static_cast(serializer.get())->setUseHlrPoly(vmap.count("svg-poly") > 0); + static_cast(serializer.get())->setAlwaysProject(vmap.count("svg-project") > 0); if (relative_center_x && relative_center_y) { static_cast(serializer.get())->setDrawingCenter(*relative_center_x, *relative_center_y); } diff --git a/src/ifccsv/ifccsv.py b/src/ifccsv/ifccsv.py index 91e0ced797..6dbf949b2e 100755 --- a/src/ifccsv/ifccsv.py +++ b/src/ifccsv/ifccsv.py @@ -10,7 +10,7 @@ import lark import argparse -class IfcAttributeExtractor: +class IfcAttributeSetter: @staticmethod def set_element_key(ifc_file, element, key, value): if key == "type" and element.is_a() != value: @@ -22,14 +22,14 @@ class IfcAttributeExtractor: return element if key[0:3] == "Qto": qto, prop = key.split(".", 1) - qto = IfcAttributeExtractor.get_element_qto(element, qto_name) + qto = IfcAttributeSetter.get_element_qto(element, qto_name) if qto: - IfcAttributeExtractor.set_qto_property(qto, prop, value) + IfcAttributeSetter.set_qto_property(qto, prop, value) return element pset_name, prop = key.split(".", 1) - pset = IfcAttributeExtractor.get_element_pset(element, pset_name) + pset = IfcAttributeSetter.get_element_pset(element, pset_name) if pset: - IfcAttributeExtractor.set_pset_property(pset, prop, value) + IfcAttributeSetter.set_pset_property(pset, prop, value) return element return element @@ -149,7 +149,7 @@ class IfcCsv: for i, value in enumerate(row): if i == 0: continue # Skip GlobalId - element = IfcAttributeExtractor.set_element_key(ifc_file, element, headers[i], value) + element = IfcAttributeSetter.set_element_key(ifc_file, element, headers[i], value) ifc_file.write(ifc) diff --git a/src/ifcgeom/IfcGeomShapes.cpp b/src/ifcgeom/IfcGeomShapes.cpp index 84fa4fea17..0e35ad63fb 100644 --- a/src/ifcgeom/IfcGeomShapes.cpp +++ b/src/ifcgeom/IfcGeomShapes.cpp @@ -91,6 +91,7 @@ #include #include +#include #include #include #include @@ -104,6 +105,8 @@ #include +#include + #include "../ifcgeom/IfcGeom.h" #include @@ -1024,11 +1027,22 @@ bool IfcGeom::Kernel::convert(const IfcSchema::IfcRectangularTrimmedSurface* l, bool IfcGeom::Kernel::convert(const IfcSchema::IfcSurfaceCurveSweptAreaSolid* l, TopoDS_Shape& shape) { gp_Trsf directrix; TopoDS_Shape face; + TopoDS_Face surface_face; TopoDS_Wire wire, section; - if (!l->ReferenceSurface()->declaration().is(IfcSchema::IfcPlane::Class())) { - Logger::Message(Logger::LOG_WARNING, "Reference surface not supported", l->ReferenceSurface()); - return false; + const bool is_plane = l->ReferenceSurface()->declaration().is(IfcSchema::IfcPlane::Class()); + + if (!is_plane) { + TopoDS_Shape surface_shell; + if (!convert_shape(l->ReferenceSurface(), surface_shell)) { + Logger::Error("Failed to convert reference surface", l); + return false; + } + if (count(surface_shell, TopAbs_FACE) != 1) { + Logger::Error("Non-continuous reference surface", l); + return false; + } + surface_face = TopoDS::Face(TopExp_Explorer(surface_shell, TopAbs_FACE).Current()); } gp_Trsf trsf; @@ -1048,25 +1062,28 @@ bool IfcGeom::Kernel::convert(const IfcSchema::IfcSurfaceCurveSweptAreaSolid* l, gp_Pln pln; gp_Pnt directrix_origin; gp_Vec directrix_tangent; - bool directrix_on_plane = true; - IfcGeom::Kernel::convert((IfcSchema::IfcPlane*) l->ReferenceSurface(), pln); + bool directrix_on_plane = is_plane; - // As per Informal propositions 2: The Directrix shall lie on the ReferenceSurface. - // This is not always the case with the test files in the repository. I am not sure - // how to deal with this and whether my interpretation of the propositions is - // correct. However, if it has been asserted that the vertices of the directrix do - // not conform to the ReferenceSurface, the ReferenceSurface is ignored. - { - for (TopExp_Explorer exp(wire, TopAbs_VERTEX); exp.More(); exp.Next()) { - if (pln.Distance(BRep_Tool::Pnt(TopoDS::Vertex(exp.Current()))) > ALMOST_ZERO) { - directrix_on_plane = false; - Logger::Message(Logger::LOG_WARNING, "The Directrix does not lie on the ReferenceSurface", l); - break; + if (is_plane) { + IfcGeom::Kernel::convert((IfcSchema::IfcPlane*) l->ReferenceSurface(), pln); + + // As per Informal propositions 2: The Directrix shall lie on the ReferenceSurface. + // This is not always the case with the test files in the repository. I am not sure + // how to deal with this and whether my interpretation of the propositions is + // correct. However, if it has been asserted that the vertices of the directrix do + // not conform to the ReferenceSurface, the ReferenceSurface is ignored. + { + for (TopExp_Explorer exp(wire, TopAbs_VERTEX); exp.More(); exp.Next()) { + if (pln.Distance(BRep_Tool::Pnt(TopoDS::Vertex(exp.Current()))) > ALMOST_ZERO) { + directrix_on_plane = false; + Logger::Message(Logger::LOG_WARNING, "The Directrix does not lie on the ReferenceSurface", l); + break; + } } } } - { + { TopExp_Explorer exp(wire, TopAbs_EDGE); TopoDS_Edge edge = TopoDS::Edge(exp.Current()); double u0, u1; @@ -1074,13 +1091,29 @@ bool IfcGeom::Kernel::convert(const IfcSchema::IfcSurfaceCurveSweptAreaSolid* l, crv->D1(u0, directrix_origin, directrix_tangent); } - if (pln.Axis().Direction().IsNormal(directrix_tangent, Precision::Approximation()) && directrix_on_plane) { + if (is_plane && pln.Axis().Direction().IsNormal(directrix_tangent, Precision::Approximation()) && directrix_on_plane) { directrix.SetTransformation(gp_Ax3(directrix_origin, directrix_tangent, pln.Axis().Direction()), gp::XOY()); + } else if (!is_plane) { + ShapeAnalysis_Surface sas(BRep_Tool::Surface(surface_face)); + auto pnt2d = sas.ValueOfUV(directrix_origin, getValue(GV_PRECISION) * 10.); + BRepGProp_Face prop(surface_face); + gp_Pnt _; + gp_Vec surface_normal; + prop.Normal(pnt2d.X(), pnt2d.Y(), _, surface_normal); + directrix.SetTransformation(gp_Ax3(directrix_origin, directrix_tangent, surface_normal), gp::XOY()); } else { directrix.SetTransformation(gp_Ax3(directrix_origin, directrix_tangent), gp::XOY()); } face = BRepBuilderAPI_Transform(face, directrix); + if (!is_plane) { + TopExp_Explorer exp(wire, TopAbs_EDGE); + for (; exp.More(); exp.Next()) { + ShapeFix_Edge sfe; + sfe.FixAddPCurve(TopoDS::Edge(exp.Current()), surface_face, false, getValue(GV_PRECISION)); + } + } + // NB: Note that StartParam and EndParam param are ignored and the assumption is // made that the parametric range over which to be swept matches the IfcCurve in // its entirety. @@ -1093,6 +1126,8 @@ bool IfcGeom::Kernel::convert(const IfcSchema::IfcSurfaceCurveSweptAreaSolid* l, builder.SetTransitionMode(BRepBuilderAPI_RightCorner); if (directrix_on_plane) { builder.SetMode(pln.Axis().Direction()); + } else if (!is_plane) { + builder.SetMode(surface_face); } builder.Build(); builder.MakeSolid(); diff --git a/src/ifcopenshell-python/ifcopenshell/express/DocAttribute.csv b/src/ifcopenshell-python/ifcopenshell/express/DocAttribute.csv index b65d4cab32..2d706f34f3 100644 --- a/src/ifcopenshell-python/ifcopenshell/express/DocAttribute.csv +++ b/src/ifcopenshell-python/ifcopenshell/express/DocAttribute.csv @@ -2758,7 +2758,7 @@ IFC2x4 NOTE: The attribute BaseQuantityConsumed has been renamed from Bas 2670;BaseQuantityProduced;" The basic (i.e. default, or recommended) unit that should be used for measuring the amount of the output (e.g. product volume) and the relative quantity of the output produced per input BaseQuantityConsumed. If the resource is not assigned to a task, then this value should be null. For production-based resources (e.g. carpentry labor), this value refers to a product-based spatial quantity (e.g. volume). For duration-based resources (e.g. safety inspector, fuel for equipment), this value refers to a task-based time quantity (IfcDuration). -
IFC2x4 New attribute +
IFC2x4 New attribute

@@ -2767,7 +2767,7 @@ The basic (i.e. default, or recommended) unit that should be used for measuring 2671;CostRatesConsumed;" Indicates the unit costs for which accrued cost amounts should be calculated. Such unit costs may be split into Name designations (e.g. 'Standard', 'Overtime'), and may contain a hierarchy of cost values that apply at different dates (using IfcCostValue.ApplicableDate and IfcCostValue.FixedUntilDate). The order of cost rates is significant; time series that break out Costs and Work will list such values in corresponding sequence - see the Time Series Use Definition for detail. -
IFC2x4 New attribute +
IFC2x4 New attribute

@@ -2778,7 +2778,7 @@ Identifies the quantity for which the BaseQuantityProduced applies. The
NOTE: The referenced instance may or may not be the same instance as that on the assigned product or process, as references to non-IfcRoot entities do not imply any semantic relationship, just value sharing (interning).
-
IFC2x4 New attribute +
IFC2x4 New attribute

@@ -2786,7 +2786,7 @@ Identifies the quantity for which the BaseQuantityProduced applies. The 2673;ResourceTime;" Indicates the work, usage, and times scheduled and completed. Some attributes on this object may have associated constraints or time series; see documentation of IfcResourceTime for specific usage. If the resource is nested, then certain values may be calculated based on the component resources as indicated at IfcResourceTime. -
IFC2x4 New attribute +
IFC2x4 New attribute

@@ -2794,43 +2794,43 @@ Indicates the work, usage, and times scheduled and completed. Some attributes o 2674;ResourceCost;" Indicates the costs budgeted and realized. Some attributes on this object may have associated time series; see documentation of IfcResourceCost for specific usage. If the resource is nested, then certain values may be calculated based on the component resources as indicated at IfcResourceCost. -
IFC2x4 New attribute +
IFC2x4 New attribute

" 2676;PredefinedType;" Defines types of construction equipment resources. -
IFC2x4 New attribute +
IFC2x4 New attribute

" 2690;PredefinedType;" Defines types of labor resources. -
IFC2x4 New attribute +
IFC2x4 New attribute

" 2715;PredefinedType;" Defines types of crew resources. -
IFC2x4 New attribute +
IFC2x4 New attribute

" 2723;PredefinedType;" Defines types of subcontract resources. -
IFC2x4 New attribute +
IFC2x4 New attribute

" 2731;PredefinedType;" Defines types of construction product resources. -
IFC2x4 New attribute +
IFC2x4 New attribute

" 2739;PredefinedType;" Defines types of construction material resources. -
IFC2x4 New attribute +
IFC2x4 New attribute

" @@ -3172,11 +3172,11 @@ It may be human readable (such as a key) or not (such as a handle or uuid) depen " 3776;Name;Optional name to further specify the reference. It can provide a human readable identifier (which does not necessarily need to have a counterpart in the internal structure of the document). 3777;ExternalReferenceForResources;" -Reference to all associations between this external reference and objects within the IfcResourceObjectSelect that are tagged by the external reference. +Reference to all associations between this external reference and objects within the IfcResourceObjectSelect that are tagged by the external reference.
- -IFC2x4 CHANGE  New inverse attribute added with upward compatibility. + +IFC2x4 CHANGE  New inverse attribute added with upward compatibility.
@@ -3308,11 +3308,11 @@ The document information with which objects are associated. 3847;Name;

A name used to identify or qualify the relationship.

3848;Description;

A description that may apply additional information about the relationship.

3850;RelatingReference;" -An external reference that can be used to tag an object within the range of IfcResourceObjectSelect. +An external reference that can be used to tag an object within the range of IfcResourceObjectSelect.

- -NOTE  External references can be a library reference (for example a dictionary or a catalogue reference), a classification reference, or a documentation reference. + +NOTE  External references can be a library reference (for example a dictionary or a catalogue reference), a classification reference, or a documentation reference.

@@ -4613,15 +4613,15 @@ The line of the axis of revolution. The curve used to define the sweeping operation. The solid is generated by sweeping the SELF\IfcSweptAreaSolid.SweptArea along the Directrix.
" 5372;StartParam;" -The parameter value on the Directrix at which the sweeping operation commences. If no value is provided the start of the sweeping operation is at the start of the Directrix.. -
-IFC2x4 CHANGE  The attribute has been changed to OPTIONAL with upward compatibility for file-based exchange. +The parameter value on the Directrix at which the sweeping operation commences. If no value is provided the start of the sweeping operation is at the start of the Directrix.. +
+IFC2x4 CHANGE  The attribute has been changed to OPTIONAL with upward compatibility for file-based exchange.
" 5373;EndParam;" -The parameter value on the Directrix at which the sweeping operation ends. If no value is provided the end of the sweeping operation is at the end of the Directrix.. -
-IFC2x4 CHANGE  The attribute has been changed to OPTIONAL with upward compatibility for file-based exchange. +The parameter value on the Directrix at which the sweeping operation ends. If no value is provided the end of the sweeping operation is at the end of the Directrix.. +
+IFC2x4 CHANGE  The attribute has been changed to OPTIONAL with upward compatibility for file-based exchange.
" 5374;ReferenceSurface;" @@ -4631,7 +4631,7 @@ The surface containing the Directrix. The curve used to define the sweeping operation. The solid is generated by sweeping the SELF\IfcSweptAreaSolid.SweptArea along the Directrix. " 5377;StartParam;" -The parameter value on the Directrix at which the sweeping operation commences. If no value is provided the start of the sweeping operation is at the start of the Directrix. +The parameter value on the Directrix at which the sweeping operation commences. If no value is provided the start of the sweeping operation is at the start of the Directrix. " 5378;EndParam;" The parameter value on the Directrix at which the sweeping operation ends. < style=""color:blue"">If no value is provided the end of the sweeping operation is at the end of the Directrix. @@ -4691,15 +4691,15 @@ The Radius of the circular disk to be swept along the directrix. D This attribute is optional, if present it defines the radius of a circular hole in the centre of the disk. " 5414;StartParam;" -The parameter value on the Directrix at which the sweeping operation commences. If no value is provided the start of the sweeping operation is at the start of the Directrix.. -
-IFC2x4 CHANGE  The attribute has been changed to OPTIONAL with upward compatibility for file-based exchange. +The parameter value on the Directrix at which the sweeping operation commences. If no value is provided the start of the sweeping operation is at the start of the Directrix.. +
+IFC2x4 CHANGE  The attribute has been changed to OPTIONAL with upward compatibility for file-based exchange.
" 5415;EndParam;" -The parameter value on the Directrix at which the sweeping operation ends. If no value is provided the end of the sweeping operation is at the end of the Directrix.. -
-IFC2x4 CHANGE  The attribute has been changed to OPTIONAL with upward compatibility for file-based exchange. +The parameter value on the Directrix at which the sweeping operation ends. If no value is provided the end of the sweeping operation is at the end of the Directrix.. +
+IFC2x4 CHANGE  The attribute has been changed to OPTIONAL with upward compatibility for file-based exchange.
" 5420;FilletRadius;" diff --git a/src/ifcopenshell-python/ifcopenshell/express/DocDefined.csv b/src/ifcopenshell-python/ifcopenshell/express/DocDefined.csv index b528e2521e..8de7585043 100644 --- a/src/ifcopenshell-python/ifcopenshell/express/DocDefined.csv +++ b/src/ifcopenshell-python/ifcopenshell/express/DocDefined.csv @@ -1826,22 +1826,22 @@ IFC2x3 CHANGE  The IfcBoxAlignment has been added. 5293;IfcGloballyUniqueId;"

An IfcGloballyUniqueId holds an encoded string identifier that is used to uniquely identify an IFC object. An IfcGloballyUniqueId is a Globally Unique Identifier (GUID) which is an auto-generated 128-bit number. Since this identifier is required for all IFC object instances, it is desirable to compress it to reduce overhead. The encoding of the base 64 character set is shown below: -

-
-

           1         2         3         4         5         6 -
 0123456789012345678901234567890123456789012345678901234567890123
- ""0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_$""; -

-
+

+
+

           1         2         3         4         5         6 +
 0123456789012345678901234567890123456789012345678901234567890123
+ ""0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_$""; +

+

The resulting string is a fixed 22 character length string to be exchanged within the IFC exchange file structure.

Refer to the BuildingSMART website (www.buildingsmart-tech.org) for more information and sample encoding algorithms.

-
-HISTORY  New type in IFC R1.5.1. -
- +
+HISTORY  New type in IFC R1.5.1. +
+
" 5463;IfcDimensionCount;" diff --git a/src/ifcopenshell-python/ifcopenshell/express/DocEntity.csv b/src/ifcopenshell-python/ifcopenshell/express/DocEntity.csv index 4e3c618056..a1b7c5b574 100644 --- a/src/ifcopenshell-python/ifcopenshell/express/DocEntity.csv +++ b/src/ifcopenshell-python/ifcopenshell/express/DocEntity.csv @@ -8904,8 +8904,8 @@ NOTE: The product representations are defined as representation maps (at the lev

A energy conversion type is used to define the common properties of a energy conversion device that may be applied to many occurrences of that type. -An energy conversion device is a building systems device that converts energy from one form into another such -as a boiler (i.e., combusting gas to heat water), chiller (i.e., using a refrigeration cycle to cool a +An energy conversion device is a building systems device that converts energy from one form into another such +as a boiler (i.e., combusting gas to heat water), chiller (i.e., using a refrigeration cycle to cool a liquid), or a cooling coil (i.e., using the phase-change characteristics of a refrigerant to cool air). Energy conversion types (or the instantiable subtypes) may be exchanged without being already assigned to occurrences.

@@ -8938,7 +8938,7 @@ liquid), or a cooling coil (i.e., using the phase-change characteristics of a re

A flow moving type is used to define the common properties of a flow moving device that may be applied to many occurrences of that type. -A flow moving device is a device that is used to produce a pressure differential in a distribution system, +A flow moving device is a device that is used to produce a pressure differential in a distribution system, such as a pump, fan, compressor, etc. Flow moving types (or the instantiable subtypes) may be exchanged without being already assigned to occurrences.

@@ -8971,8 +8971,8 @@ such as a pump, fan, compressor, etc.

A flow controller type is used to define the common properties of a flow controller that may be applied to many occurrences of that type. -A flow controller is a device that regulates flow within a distribution system, such as a valve in a piping -system, modulating damper in an air distribution system, or electrical switch in an electrical distribution +A flow controller is a device that regulates flow within a distribution system, such as a valve in a piping +system, modulating damper in an air distribution system, or electrical switch in an electrical distribution system. Flow controller types (or the instantiable subtypes) may be exchanged without being already assigned to occurrences.

@@ -9004,7 +9004,7 @@ system. Flow controller types (or the instantiable subtypes) may be exchanged

A flow segment type is used to define the common properties of a flow segment that may be applied to many occurrences of that type. -A flow segment is a section of a distribution system, such as a duct, pipe, conduit, etc. that typically has +A flow segment is a section of a distribution system, such as a duct, pipe, conduit, etc. that typically has only two ports. Flow segment types (or the instantiable subtypes) may be exchanged without being already assigned to occurrences.

@@ -9051,8 +9051,8 @@ only two ports.

A flow fitting type is used to define the common properties of a flow fitting that may be applied to many occurrences of that type. -A flow fitting is a device that is used to interconnect flow segments or other fittings within a distribution -system, such as a tee in a ducted system that branches flow into two directions, a junction box in an +A flow fitting is a device that is used to interconnect flow segments or other fittings within a distribution +system, such as a tee in a ducted system that branches flow into two directions, a junction box in an electrical distribution system, etc. Flow fitting types (or the instantiable subtypes) may be exchanged without being already assigned to occurrences.

@@ -9084,7 +9084,7 @@ HISTORY: New entity in IFC Release 2x2.
1123;IfcFlowTreatmentDeviceType;"

The element type IfcFlowTreatmentDeviceType defines a list of commonly shared property set definitions of a flow treatment device and an optional set of product representations. It is used to define a flow treatment device specification (the specific product information that is common to all occurrences of that product type).

-

A flow treatment device is a device used to change the physical properties of the medium, such as an air, oil +

A flow treatment device is a device used to change the physical properties of the medium, such as an air, oil or water filter (used to remove particulates from the fluid), or a duct silencer (used to attenuate noise). Flow treatment types (or the instantiable subtypes) may be exchanged without being already assigned to occurrences.

The occurrences of the IfcFlowTreatmentDeviceType are represented by instances of IfcFlowTreatmentDevice or its subtypes.

@@ -9418,7 +9418,7 @@ HISTORY: New entity in IFC R2x.

The distribution flow element IfcFlowStorageDevice defines the occurrence of a device that participates in a distribution system and is used for temporary storage of a fluid - such as a liquid or a gas (e.g., tank). Its type is defined by + such as a liquid or a gas (e.g., tank). Its type is defined by IfcFlowStorageDeviceType or its subtypes.

@@ -17685,8 +17685,8 @@ set for building system occurrences

An inventory is a list of items within an enterprise.

-

Various types of inventory can be included. These are identified by the range of values within the inventory type enumeration which includes space, asset, and furniture. User defined inventories can also be defined for lists of particular types of element such as may be required in operating and maintenance instructions. Such inventories should be constrained to contain a list of elements of a restricted type.

There are a number of actors that can be associated with an inventory, each actor having a role. Actors within the scope of the project are indicated using the IfcRelAssignsToActor relationship in which case roles should be defined through the IfcActorRole class; otherwise principal actors are identified as attributes of the class. In the existence of both, direct attributes take precedence.

There are a number of costs that can be associated with an inventory, each cost having a role. These are specified through the CurrentValue and OriginalValue attributes.

Various types of inventory can be included. These are identified by the range of values within the inventory type enumeration which includes space, asset, and furniture. User defined inventories can also be defined for lists of particular types of element such as may be required in operating and maintenance instructions. Such inventories should be constrained to contain a list of elements of a restricted type.

There are a number of actors that can be associated with an inventory, each actor having a role. Actors within the scope of the project are indicated using the IfcRelAssignsToActor relationship in which case roles should be defined through the IfcActorRole class; otherwise principal actors are identified as attributes of the class. In the existence of both, direct attributes take precedence.

There are a number of costs that can be associated with an inventory, each cost having a role. These are specified through the CurrentValue and OriginalValue attributes.

HISTORY: New entity in IFC2.0. Modified in IFC2x4 to make all attributes optional and remove Where Rule.

Assignment Use Definition

@@ -17704,8 +17704,8 @@ set for building system occurrences

An occupant is a type of actor that defines the form of occupancy of a property.

-

The principal purpose of IfcOccupant is to determine the nature of occupancy of a property for a particular actor. All characteristics relating to the actor (name and organization details) are inherited from the IfcActor class.

The principal purpose of IfcOccupant is to determine the nature of occupancy of a property for a particular actor. All characteristics relating to the actor (name and organization details) are inherited from the IfcActor class.

HISTORY: New entity in IFC2x

Assignment Use Definition

@@ -17749,8 +17749,8 @@ set for building system occurrences

Furniture defines complete furnishings such as a table, desk, chair, or cabinet, which may or may not be permanently attached to a building structure.

-

Occurrences of furniture that are built in (where the property Pset_FurnitureTypeCommon.IsBuiltIn is asserted to be TRUE) should have their connection relationship with a building element occurrence defined through the IfcRelConnectsElements relationship.

Occurrences of furniture that are built in (where the property Pset_FurnitureTypeCommon.IsBuiltIn is asserted to be TRUE) should have their connection relationship with a building element occurrence defined through the IfcRelConnectsElements relationship.

HISTORY: New entity in IFC2x2

Type Use Definition

@@ -18365,7 +18365,7 @@ IFC2x4 CHANGE Supertype changed to new IfcPreDefinedPropertySet. " -1847;IfcFlowInstrument;"

A flow instrument reads and displays the value of a particular property of a system at a point, or displays the difference in the value of a property between two points.

+1847;IfcFlowInstrument;"

A flow instrument reads and displays the value of a particular property of a system at a point, or displays the difference in the value of a property between two points.

Instrumentation is typically for the purpose of determining the value of the property at a point in time. It is not the purpose of an instrument to record or integrate the values over time (although they may be connected to recording devices that do perform such a function). This entity provides for all forms of mechanical flow instrument (thermometers, pressure gauges etc.) and electrical flow instruments (ammeters, voltmeters etc.)

HISTORY  New entity in IFC2x4
@@ -18419,8 +18419,8 @@ In this case a valid value for MethodOfMeasurement shall be provided.

Port Use Definition

-

The distribution ports relating to the IfcFlowInstrument are defined by IfcDistributionPort and attached by the IfcRelConnectsPortToElement relationship. - If the flow instrument occurrence is defined by IfcFlowInstrumentType, then the port occurrences must reflect those defined at the IfcFlowInstrumentType using the IfcRelDefinesByObject relationship. +

The distribution ports relating to the IfcFlowInstrument are defined by IfcDistributionPort and attached by the IfcRelConnectsPortToElement relationship. + If the flow instrument occurrence is defined by IfcFlowInstrumentType, then the port occurrences must reflect those defined at the IfcFlowInstrumentType using the IfcRelDefinesByObject relationship. Ports are specific to the IfcFlowInstrument PredefinedType as follows indicated by the IfcDistributionPort Name, PredefinedType, and FlowDirection:

  • Input (SIGNAL, SINK): Receives signal.
  • @@ -18492,14 +18492,14 @@ In this case a valid value for MethodOfMeasurement shall be provided.

    Port Use Definition

    -

    The distribution ports relating to the IfcActuator are defined by IfcDistributionPort and attached by the IfcRelConnectsPortToElement relationship. - If the actuator occurrence is defined by IfcActuatorType, then the port occurrences must reflect those defined at the IfcActuatorType using the IfcRelDefinesByObject relationship. +

    The distribution ports relating to the IfcActuator are defined by IfcDistributionPort and attached by the IfcRelConnectsPortToElement relationship. + If the actuator occurrence is defined by IfcActuatorType, then the port occurrences must reflect those defined at the IfcActuatorType using the IfcRelDefinesByObject relationship. Ports are specific to the IfcActuator PredefinedType as follows indicated by the IfcDistributionPort Name, PredefinedType, and FlowDirection:

    " -1874;IfcController;"

    A controller is a device that monitors inputs and controls outputs within a building automation system.

    +1874;IfcController;"

    A controller is a device that monitors inputs and controls outputs within a building automation system.

    A controller may be physical (having placement within a spatial structure) or logical (a software interface or aggregated within a programmable physical controller).

    HISTORY  New entity in IFC2x4
    @@ -18581,8 +18581,8 @@ In this case a valid value for MethodOfMeasurement shall be provided.

    Port Use Definition

    -

    The distribution ports relating to the IfcController are defined by IfcDistributionPort and attached by the IfcRelConnectsPortToElement relationship. - If the controller occurrence is defined by IfcControllerType, then the port occurrences must reflect those defined at the IfcControllerType using the IfcRelDefinesByObject relationship. +

    The distribution ports relating to the IfcController are defined by IfcDistributionPort and attached by the IfcRelConnectsPortToElement relationship. + If the controller occurrence is defined by IfcControllerType, then the port occurrences must reflect those defined at the IfcControllerType using the IfcRelDefinesByObject relationship. Ports are specific to the IfcController PredefinedType as follows indicated by the IfcDistributionPort Name, PredefinedType, and FlowDirection:

    • FLOATING @@ -18780,8 +18780,8 @@ In this case a valid value for MethodOfMeasurement shall be provided.

      Port Use Definition

      -

      The distribution ports relating to the IfcSensor are defined by IfcDistributionPort and attached by the IfcRelConnectsPortToElement relationship. - If the sensor occurrence is defined by IfcSensorType, then the port occurrences must reflect those defined at the IfcSensorType using the IfcRelDefinesByObject relationship. +

      The distribution ports relating to the IfcSensor are defined by IfcDistributionPort and attached by the IfcRelConnectsPortToElement relationship. + If the sensor occurrence is defined by IfcSensorType, then the port occurrences must reflect those defined at the IfcSensorType using the IfcRelDefinesByObject relationship. Ports are specific to the IfcSensor PredefinedType as follows indicated by the IfcDistributionPort Name, PredefinedType, and FlowDirection:

      • Output (SIGNAL, SOURCE): Transmits signal.
      • @@ -18790,7 +18790,7 @@ In this case a valid value for MethodOfMeasurement shall be provided.

        Figure 180 — Sensor port use

        " -1913;IfcAlarm;"

        An alarm is a device that signals the existence of a condition or situation that is outside the boundaries of normal expectation or that activates such a device.

        +1913;IfcAlarm;"

        An alarm is a device that signals the existence of a condition or situation that is outside the boundaries of normal expectation or that activates such a device.

        Alarms include the provision of break glass buttons and manual pull boxes that are used to activate alarms.

        HISTORY  New entity in IFC2x4
        @@ -18830,14 +18830,14 @@ In this case a valid value for MethodOfMeasurement shall be provided.

        Port Use Definition

        -

        The distribution ports relating to the IfcAlarm are defined by IfcDistributionPort and attached by the IfcRelConnectsPortToElement relationship. - If the alarm occurrence is defined by IfcAlarmType, then the port occurrences must reflect those defined at the IfcAlarmType using the IfcRelDefinesByObject relationship. +

        The distribution ports relating to the IfcAlarm are defined by IfcDistributionPort and attached by the IfcRelConnectsPortToElement relationship. + If the alarm occurrence is defined by IfcAlarmType, then the port occurrences must reflect those defined at the IfcAlarmType using the IfcRelDefinesByObject relationship. Ports are specific to the IfcAlarm PredefinedType as follows indicated by the IfcDistributionPort Name, PredefinedType, and FlowDirection:

        " -1926;IfcUnitaryControlElement;"

        A unitary control element combines a number of control components into a single product, such as a thermostat or humidistat.

        +1926;IfcUnitaryControlElement;"

        A unitary control element combines a number of control components into a single product, such as a thermostat or humidistat.

        A unitary control element provides a housing for an aggregation of control or electrical distribution elements that, in combination, perform a singular (unitary) purpose. Each item in the aggregation may have its own geometric representation and location.

        HISTORY  New entity in IFC2x4
        @@ -18887,8 +18887,8 @@ In this case a valid value for MethodOfMeasurement shall be provided.

        Port Use Definition

        -

        The distribution ports relating to the IfcUnitaryControlElement are defined by IfcDistributionPort and attached by the IfcRelConnectsPortToElement relationship. - If the unitary control element occurrence is defined by IfcUnitaryControlElementType, then the port occurrences must reflect those defined at the IfcUnitaryControlElementType using the IfcRelDefinesByObject relationship. +

        The distribution ports relating to the IfcUnitaryControlElement are defined by IfcDistributionPort and attached by the IfcRelConnectsPortToElement relationship. + If the unitary control element occurrence is defined by IfcUnitaryControlElementType, then the port occurrences must reflect those defined at the IfcUnitaryControlElementType using the IfcRelDefinesByObject relationship. Ports are specific to the IfcUnitaryControlElement PredefinedType as follows indicated by the IfcDistributionPort Name, PredefinedType, and FlowDirection:

        • THERMOSTAT @@ -23799,7 +23799,7 @@ HISTORY New entity in IFC2x4.

          The distribution ports relating to the IfcProtectiveDeviceTrippingUnitType type are defined by IfcDistributionPort and attached by the IfcRelConnectsPortToElement relationship. Ports are reflected at occurrences of this type using the IfcRelDefinesByObject relationship. Refer to the documentation at IfcProtectiveDeviceTrippingUnit for standard port definitions.

          " -3005;IfcCableSegment;"

          A cable segment is a flow segment used to carry electrical power, data, or telecommunications signals.

          +3005;IfcCableSegment;"

          A cable segment is a flow segment used to carry electrical power, data, or telecommunications signals.

          A cable segment is used to typically join two sections of an electrical network or a network of components carrying the electrical service.

          HISTORY  New entity in IFC2x4
          @@ -23887,8 +23887,8 @@ In this case a valid value for MethodOfMeasurement shall be provided.

          Port Use Definition

          -

          The distribution ports relating to the IfcCableSegment are defined by IfcDistributionPort and attached by the IfcRelConnectsPortToElement relationship. - If the cable segment occurrence is defined by IfcCableSegmentType, then the port occurrences must reflect those defined at the IfcCableSegmentType using the IfcRelDefinesByObject relationship. +

          The distribution ports relating to the IfcCableSegment are defined by IfcDistributionPort and attached by the IfcRelConnectsPortToElement relationship. + If the cable segment occurrence is defined by IfcCableSegmentType, then the port occurrences must reflect those defined at the IfcCableSegmentType using the IfcRelDefinesByObject relationship. Ports are specific to the IfcCableSegment PredefinedType as follows indicated by the IfcDistributionPort Name, PredefinedType, and FlowDirection:

          • CABLESEGMENT @@ -23967,8 +23967,8 @@ In this case a valid value for MethodOfMeasurement shall be provided.

            Port Use Definition

            -

            The distribution ports relating to the IfcCableCarrierSegment are defined by IfcDistributionPort and attached by the IfcRelConnectsPortToElement relationship. - If the cable carrier segment occurrence is defined by IfcCableCarrierSegmentType, then the port occurrences must reflect those defined at the IfcCableCarrierSegmentType using the IfcRelDefinesByObject relationship. +

            The distribution ports relating to the IfcCableCarrierSegment are defined by IfcDistributionPort and attached by the IfcRelConnectsPortToElement relationship. + If the cable carrier segment occurrence is defined by IfcCableCarrierSegmentType, then the port occurrences must reflect those defined at the IfcCableCarrierSegmentType using the IfcRelDefinesByObject relationship. Ports are specific to the IfcCableCarrierSegment PredefinedType as follows indicated by the IfcDistributionPort Name, PredefinedType, and FlowDirection:

            • Head (NOTDEFINED, SINK): Head connection.
            • @@ -24013,8 +24013,8 @@ In this case a valid value for MethodOfMeasurement shall be provided.

              Port Use Definition

              -

              The distribution ports relating to the IfcCableCarrierFitting are defined by IfcDistributionPort and attached by the IfcRelConnectsPortToElement relationship. - If the cable carrier fitting occurrence is defined by IfcCableCarrierFittingType, then the port occurrences must reflect those defined at the IfcCableCarrierFittingType using the IfcRelDefinesByObject relationship. +

              The distribution ports relating to the IfcCableCarrierFitting are defined by IfcDistributionPort and attached by the IfcRelConnectsPortToElement relationship. + If the cable carrier fitting occurrence is defined by IfcCableCarrierFittingType, then the port occurrences must reflect those defined at the IfcCableCarrierFittingType using the IfcRelDefinesByObject relationship. Ports are specific to the IfcCableCarrierFitting PredefinedType as follows indicated by the IfcDistributionPort Name, PredefinedType, and FlowDirection:

              • BEND @@ -24100,8 +24100,8 @@ In this case a valid value for MethodOfMeasurement shall be provided.

                Port Use Definition

                -

                The distribution ports relating to the IfcCableFitting are defined by IfcDistributionPort and attached by the IfcRelConnectsPortToElement relationship. - If the cable fitting occurrence is defined by IfcCableFittingType, then the port occurrences must reflect those defined at the IfcCableFittingType using the IfcRelDefinesByObject relationship. +

                The distribution ports relating to the IfcCableFitting are defined by IfcDistributionPort and attached by the IfcRelConnectsPortToElement relationship. + If the cable fitting occurrence is defined by IfcCableFittingType, then the port occurrences must reflect those defined at the IfcCableFittingType using the IfcRelDefinesByObject relationship. Ports are specific to the IfcCableFitting PredefinedType as follows indicated by the IfcDistributionPort Name, PredefinedType, and FlowDirection:

                • CONNECTOR @@ -24135,7 +24135,7 @@ In this case a valid value for MethodOfMeasurement shall be provided.

                " -3021;IfcJunctionBox;"

                A junction box is an enclosure within which cables are connected.

                +3021;IfcJunctionBox;"

                A junction box is an enclosure within which cables are connected.

                Cables may be members of an electrical circuit (for electrical power systems) or be information carriers (in a telecommunications system). A junction box is typically intended to conceal a cable junction from sight, eliminate tampering or provide a safe place for electrical connection.

                HISTORY  New entity in IFC2x4
                @@ -24184,8 +24184,8 @@ In this case a valid value for MethodOfMeasurement shall be provided.

                Port Use Definition

                -

                The distribution ports relating to the IfcJunctionBox are defined by IfcDistributionPort and attached by the IfcRelConnectsPortToElement relationship. - If the junction box occurrence is defined by IfcJunctionBoxType, then the port occurrences must reflect those defined at the IfcJunctionBoxType using the IfcRelDefinesByObject relationship. +

                The distribution ports relating to the IfcJunctionBox are defined by IfcDistributionPort and attached by the IfcRelConnectsPortToElement relationship. + If the junction box occurrence is defined by IfcJunctionBoxType, then the port occurrences must reflect those defined at the IfcJunctionBoxType using the IfcRelDefinesByObject relationship. Ports are specific to the IfcJunctionBox PredefinedType as follows indicated by the IfcDistributionPort Name, PredefinedType, and FlowDirection:

                • DATA @@ -24249,15 +24249,15 @@ In this case a valid value for MethodOfMeasurement shall be provided.

                  Port Use Definition

                  -

                  The distribution ports relating to the IfcElectricFlowStorageDevice are defined by IfcDistributionPort and attached by the IfcRelConnectsPortToElement relationship. - If the electric flow storage device occurrence is defined by IfcElectricFlowStorageDeviceType, then the port occurrences must reflect those defined at the IfcElectricFlowStorageDeviceType using the IfcRelDefinesByObject relationship. +

                  The distribution ports relating to the IfcElectricFlowStorageDevice are defined by IfcDistributionPort and attached by the IfcRelConnectsPortToElement relationship. + If the electric flow storage device occurrence is defined by IfcElectricFlowStorageDeviceType, then the port occurrences must reflect those defined at the IfcElectricFlowStorageDeviceType using the IfcRelDefinesByObject relationship. Ports are specific to the IfcElectricFlowStorageDevice PredefinedType as follows indicated by the IfcDistributionPort Name, PredefinedType, and FlowDirection:

                  • Line (ELECTRICAL, SINK): Incoming power used to charge the flow storage device.
                  • Load (ELECTRICAL, SOURCE): Outgoing power backed by the flow storage device.
                  " -3029;IfcOutlet;"

                  An outlet is a device installed at a point to receive one or more inserted plugs for electrical power or communications.

                  +3029;IfcOutlet;"

                  An outlet is a device installed at a point to receive one or more inserted plugs for electrical power or communications.

                  Power outlets are commonly connected within a junction box; data outlets may be directly connected to a wall. For power outlets sharing the same circuit within a junction box, the ports should indicate the logical wiring relationship to the enclosing junction box, even though they may be physically connected to a cable going to another outlet, switch, or fixture.

                  HISTORY  New entity in IFC2x4
                  @@ -24301,8 +24301,8 @@ In this case a valid value for MethodOfMeasurement shall be provided.

                  Port Use Definition

                  -

                  The distribution ports relating to the IfcOutlet are defined by IfcDistributionPort and attached by the IfcRelConnectsPortToElement relationship. - If the outlet occurrence is defined by IfcOutletType, then the port occurrences must reflect those defined at the IfcOutletType using the IfcRelDefinesByObject relationship. +

                  The distribution ports relating to the IfcOutlet are defined by IfcDistributionPort and attached by the IfcRelConnectsPortToElement relationship. + If the outlet occurrence is defined by IfcOutletType, then the port occurrences must reflect those defined at the IfcOutletType using the IfcRelDefinesByObject relationship. Ports are specific to the IfcOutlet PredefinedType as follows indicated by the IfcDistributionPort Name, PredefinedType, and FlowDirection:

                  • DATAOUTLET @@ -24391,8 +24391,8 @@ In this case a valid value for MethodOfMeasurement shall be provided.

                    Port Use Definition

                    -

                    The distribution ports relating to the IfcLightFixture are defined by IfcDistributionPort and attached by the IfcRelConnectsPortToElement relationship. - If the light fixture occurrence is defined by IfcLightFixtureType, then the port occurrences must reflect those defined at the IfcLightFixtureType using the IfcRelDefinesByObject relationship. +

                    The distribution ports relating to the IfcLightFixture are defined by IfcDistributionPort and attached by the IfcRelConnectsPortToElement relationship. + If the light fixture occurrence is defined by IfcLightFixtureType, then the port occurrences must reflect those defined at the IfcLightFixtureType using the IfcRelDefinesByObject relationship. Ports are specific to the IfcLightFixture PredefinedType as follows indicated by the IfcDistributionPort Name, PredefinedType, and FlowDirection:

                    • Line (ELECTRICAL, SINK): The power supply line, typically a cable connected to a switch.
                    • @@ -24452,8 +24452,8 @@ In this case a valid value for MethodOfMeasurement shall be provided.

                      Port Use Definition

                      -

                      The distribution ports relating to the IfcLamp are defined by IfcDistributionPort and attached by the IfcRelConnectsPortToElement relationship. - If the lamp occurrence is defined by IfcLampType, then the port occurrences must reflect those defined at the IfcLampType using the IfcRelDefinesByObject relationship. +

                      The distribution ports relating to the IfcLamp are defined by IfcDistributionPort and attached by the IfcRelConnectsPortToElement relationship. + If the lamp occurrence is defined by IfcLampType, then the port occurrences must reflect those defined at the IfcLampType using the IfcRelDefinesByObject relationship. Ports are specific to the IfcLamp PredefinedType as follows indicated by the IfcDistributionPort Name, PredefinedType, and FlowDirection:

                      • Socket (LIGHTING, SINK): The socket providing electricity.
                      • @@ -24462,7 +24462,7 @@ In this case a valid value for MethodOfMeasurement shall be provided.

                        Figure 203 — Lamp port use

                        " -3041;IfcElectricAppliance;"

                        A communications appliance transmits and receives electronic or digital information as data or sound.

                        +3041;IfcElectricAppliance;"

                        A communications appliance transmits and receives electronic or digital information as data or sound.

                        Communication appliances may be fixed in place or may be able to be moved from one space to another. Communication appliances require an electrical supply that may be supplied either by an electrical circuit or provided from a local battery source.

                        HISTORY  New entity in IFC2x4
                        @@ -24519,8 +24519,8 @@ In this case a valid value for MethodOfMeasurement shall be provided.

                        Port Use Definition

                        -

                        The distribution ports relating to the IfcElectricAppliance are defined by IfcDistributionPort and attached by the IfcRelConnectsPortToElement relationship. - If the electric appliance occurrence is defined by IfcElectricApplianceType, then the port occurrences must reflect those defined at the IfcElectricApplianceType using the IfcRelDefinesByObject relationship. +

                        The distribution ports relating to the IfcElectricAppliance are defined by IfcDistributionPort and attached by the IfcRelConnectsPortToElement relationship. + If the electric appliance occurrence is defined by IfcElectricApplianceType, then the port occurrences must reflect those defined at the IfcElectricApplianceType using the IfcRelDefinesByObject relationship. Ports are specific to the IfcElectricAppliance PredefinedType as follows indicated by the IfcDistributionPort Name, PredefinedType, and FlowDirection:

                        • DISHWASHER @@ -24580,7 +24580,7 @@ In this case a valid value for MethodOfMeasurement shall be provided.

                          Figure 197 — Electric appliance port use

                          " -3045;IfcAudioVisualAppliance;"

                          An audio-visual appliance is a device that displays, captures, transmits, or receives audio or video.

                          +3045;IfcAudioVisualAppliance;"

                          An audio-visual appliance is a device that displays, captures, transmits, or receives audio or video.

                          Audio-visual appliances may be fixed in place or may be able to be moved from one space to another. They may require an electrical supply that may be supplied either by an electrical circuit or provided from a local battery source. Audio-visual appliances may be connected to data circuits including specialist circuits for audio visual purposes only.

                          HISTORY  New entity in IFC2x4
                          @@ -24670,8 +24670,8 @@ In this case a valid value for MethodOfMeasurement shall be provided.

                          Port Use Definition

                          -

                          The distribution ports relating to the IfcAudioVisualAppliance are defined by IfcDistributionPort and attached by the IfcRelConnectsPortToElement relationship. - If the audio visual appliance occurrence is defined by IfcAudioVisualApplianceType, then the port occurrences must reflect those defined at the IfcAudioVisualApplianceType using the IfcRelDefinesByObject relationship. +

                          The distribution ports relating to the IfcAudioVisualAppliance are defined by IfcDistributionPort and attached by the IfcRelConnectsPortToElement relationship. + If the audio visual appliance occurrence is defined by IfcAudioVisualApplianceType, then the port occurrences must reflect those defined at the IfcAudioVisualApplianceType using the IfcRelDefinesByObject relationship. Ports are specific to the IfcAudioVisualAppliance PredefinedType as follows indicated by the IfcDistributionPort Name, PredefinedType, and FlowDirection:

                          • AMPLIFIER @@ -24786,7 +24786,7 @@ In this case a valid value for MethodOfMeasurement shall be provided.

                          " -3049;IfcCommunicationsAppliance;"

                          A communications appliance transmits and receives electronic or digital information as data or sound.

                          +3049;IfcCommunicationsAppliance;"

                          A communications appliance transmits and receives electronic or digital information as data or sound.

                          Communication appliances may be fixed in place or may be able to be moved from one space to another. Communication appliances require an electrical supply that may be supplied either by an electrical circuit or provided from a local battery source.

                          HISTORY  New entity in IFC2x4
                          @@ -24842,8 +24842,8 @@ In this case a valid value for MethodOfMeasurement shall be provided.

                          Port Use Definition

                          -

                          The distribution ports relating to the IfcCommunicationsAppliance are defined by IfcDistributionPort and attached by the IfcRelConnectsPortToElement relationship. - If the communications appliance occurrence is defined by IfcCommunicationsApplianceType, then the port occurrences must reflect those defined at the IfcCommunicationsApplianceType using the IfcRelDefinesByObject relationship. +

                          The distribution ports relating to the IfcCommunicationsAppliance are defined by IfcDistributionPort and attached by the IfcRelConnectsPortToElement relationship. + If the communications appliance occurrence is defined by IfcCommunicationsApplianceType, then the port occurrences must reflect those defined at the IfcCommunicationsApplianceType using the IfcRelDefinesByObject relationship. Ports are specific to the IfcCommunicationsAppliance PredefinedType as follows indicated by the IfcDistributionPort Name, PredefinedType, and FlowDirection:

                          • ANTENNA @@ -24905,7 +24905,7 @@ In this case a valid value for MethodOfMeasurement shall be provided.

                          " -3053;IfcSwitchingDevice;"

                          A switch is used in a cable distribution system (electrical circuit) to control or modulate the flow of electricity.

                          +3053;IfcSwitchingDevice;"

                          A switch is used in a cable distribution system (electrical circuit) to control or modulate the flow of electricity.

                          Switches include those used for electrical power, communications, audio-visual, or other distribution system types as determined by the available ports.

                          HISTORY  New entity in IFC2x4
                          @@ -24999,8 +24999,8 @@ In this case a valid value for MethodOfMeasurement shall be provided.

                          Port Use Definition

                          -

                          The distribution ports relating to the IfcSwitchingDevice are defined by IfcDistributionPort and attached by the IfcRelConnectsPortToElement relationship. - If the switching device occurrence is defined by IfcSwitchingDeviceType, then the port occurrences must reflect those defined at the IfcSwitchingDeviceType using the IfcRelDefinesByObject relationship. +

                          The distribution ports relating to the IfcSwitchingDevice are defined by IfcDistributionPort and attached by the IfcRelConnectsPortToElement relationship. + If the switching device occurrence is defined by IfcSwitchingDeviceType, then the port occurrences must reflect those defined at the IfcSwitchingDeviceType using the IfcRelDefinesByObject relationship. Ports are specific to the IfcSwitchingDevice PredefinedType as follows indicated by the IfcDistributionPort Name, PredefinedType, and FlowDirection:

                          • Line (ELECTRICAL, SINK): The supply line.
                          • @@ -25010,7 +25010,7 @@ In this case a valid value for MethodOfMeasurement shall be provided.

                            Figure 209 — Switching device port use

                            " -3057;IfcProtectiveDevice;"

                            A protective device breaks an electrical circuit when a stated electric current that passes through it is exceeded.

                            +3057;IfcProtectiveDevice;"

                            A protective device breaks an electrical circuit when a stated electric current that passes through it is exceeded.

                            A protective device provides protection against electrical current only (not as a general protective device). It may be used to represent the complete set of elements including both the tripping unit and the breaking unit that provide the protection. This may be particularly useful at earlier stages of design where the approach to breaking the electrical supply may be determined but the method of tripping may not. Alternatively, this entity may be used to specifically represent the breaking unit alone (in which case the tripping unit will also be specifically identified). This entity is specific to dedicated protective devices and excludes electrical outlets that may have circuit protection.

                            HISTORY  New entity in IFC2x4
                            @@ -25089,8 +25089,8 @@ In this case a valid value for MethodOfMeasurement shall be provided.

                            Port Use Definition

                            -

                            The distribution ports relating to the IfcProtectiveDevice are defined by IfcDistributionPort and attached by the IfcRelConnectsPortToElement relationship. - If the protective device occurrence is defined by IfcProtectiveDeviceType, then the port occurrences must reflect those defined at the IfcProtectiveDeviceType using the IfcRelDefinesByObject relationship. +

                            The distribution ports relating to the IfcProtectiveDevice are defined by IfcDistributionPort and attached by the IfcRelConnectsPortToElement relationship. + If the protective device occurrence is defined by IfcProtectiveDeviceType, then the port occurrences must reflect those defined at the IfcProtectiveDeviceType using the IfcRelDefinesByObject relationship. Ports are specific to the IfcProtectiveDevice PredefinedType as follows indicated by the IfcDistributionPort Name, PredefinedType, and FlowDirection:

                            • CIRCUITBREAKER @@ -25101,7 +25101,7 @@ In this case a valid value for MethodOfMeasurement shall be provided.

                            " -3061;IfcElectricDistributionBoard;"

                            A distribution board is a flow controller in which instances of electrical devices are brought together at a single place for a particular purpose.

                            +3061;IfcElectricDistributionBoard;"

                            A distribution board is a flow controller in which instances of electrical devices are brought together at a single place for a particular purpose.

                            A distribution provides a housing for connected electrical distribution elements so that they can be viewed, operated or acted upon from a single place. Each connected item may have its own geometric representation and location.

                            HISTORY  New entity in IFC2x4
                            @@ -25143,8 +25143,8 @@ In this case a valid value for MethodOfMeasurement shall be provided.

                            Port Use Definition

                            -

                            The distribution ports relating to the IfcElectricDistributionBoard are defined by IfcDistributionPort and attached by the IfcRelConnectsPortToElement relationship. - If the electric distribution board occurrence is defined by IfcElectricDistributionBoardType, then the port occurrences must reflect those defined at the IfcElectricDistributionBoardType using the IfcRelDefinesByObject relationship. +

                            The distribution ports relating to the IfcElectricDistributionBoard are defined by IfcDistributionPort and attached by the IfcRelConnectsPortToElement relationship. + If the electric distribution board occurrence is defined by IfcElectricDistributionBoardType, then the port occurrences must reflect those defined at the IfcElectricDistributionBoardType using the IfcRelDefinesByObject relationship. Ports are specific to the IfcElectricDistributionBoard PredefinedType as follows indicated by the IfcDistributionPort Name, PredefinedType, and FlowDirection:

                            • CONSUMERUNIT @@ -25204,15 +25204,15 @@ In this case a valid value for MethodOfMeasurement shall be provided.

                              Port Use Definition

                              -

                              The distribution ports relating to the IfcElectricTimeControl are defined by IfcDistributionPort and attached by the IfcRelConnectsPortToElement relationship. - If the electric time control occurrence is defined by IfcElectricTimeControlType, then the port occurrences must reflect those defined at the IfcElectricTimeControlType using the IfcRelDefinesByObject relationship. +

                              The distribution ports relating to the IfcElectricTimeControl are defined by IfcDistributionPort and attached by the IfcRelConnectsPortToElement relationship. + If the electric time control occurrence is defined by IfcElectricTimeControlType, then the port occurrences must reflect those defined at the IfcElectricTimeControlType using the IfcRelDefinesByObject relationship. Ports are specific to the IfcElectricTimeControl PredefinedType as follows indicated by the IfcDistributionPort Name, PredefinedType, and FlowDirection:

                              " -3069;IfcTransformer;"

                              A transformer is an inductive stationary device that transfers electrical energy from one circuit to another.

                              +3069;IfcTransformer;"

                              A transformer is an inductive stationary device that transfers electrical energy from one circuit to another.

                              IfcTransformer is used to transform electric power; conversion of electric signals for other purposes is handled at other entities: IfcController converts arbitrary signals, IfcAudioVisualAppliance converts signals for audio or video streams, and IfcCommunicationsAppliance converts signals for data or other communications usage.

                              HISTORY  New entity in IFC2x4
                              @@ -25251,8 +25251,8 @@ In this case a valid value for MethodOfMeasurement shall be provided.

                              Port Use Definition

                              -

                              The distribution ports relating to the IfcTransformer are defined by IfcDistributionPort and attached by the IfcRelConnectsPortToElement relationship. - If the transformer occurrence is defined by IfcTransformerType, then the port occurrences must reflect those defined at the IfcTransformerType using the IfcRelDefinesByObject relationship. +

                              The distribution ports relating to the IfcTransformer are defined by IfcDistributionPort and attached by the IfcRelConnectsPortToElement relationship. + If the transformer occurrence is defined by IfcTransformerType, then the port occurrences must reflect those defined at the IfcTransformerType using the IfcRelDefinesByObject relationship. Ports are specific to the IfcTransformer PredefinedType as follows indicated by the IfcDistributionPort Name, PredefinedType, and FlowDirection:

                              • Line (ELECTRICAL, SINK): Line to be transformed.
                              • @@ -25307,8 +25307,8 @@ In this case a valid value for MethodOfMeasurement shall be provided.

                                Port Use Definition

                                -

                                The distribution ports relating to the IfcElectricGenerator are defined by IfcDistributionPort and attached by the IfcRelConnectsPortToElement relationship. - If the electric generator occurrence is defined by IfcElectricGeneratorType, then the port occurrences must reflect those defined at the IfcElectricGeneratorType using the IfcRelDefinesByObject relationship. +

                                The distribution ports relating to the IfcElectricGenerator are defined by IfcDistributionPort and attached by the IfcRelConnectsPortToElement relationship. + If the electric generator occurrence is defined by IfcElectricGeneratorType, then the port occurrences must reflect those defined at the IfcElectricGeneratorType using the IfcRelDefinesByObject relationship. Ports are specific to the IfcElectricGenerator PredefinedType as follows indicated by the IfcDistributionPort Name, PredefinedType, and FlowDirection:

                                • Load (ELECTRICAL, SOURCE): Outgoing power from generator.
                                • @@ -25352,8 +25352,8 @@ In this case a valid value for MethodOfMeasurement shall be provided.

                                  Port Use Definition

                                  -

                                  The distribution ports relating to the IfcElectricMotor are defined by IfcDistributionPort and attached by the IfcRelConnectsPortToElement relationship. - If the electric motor occurrence is defined by IfcElectricMotorType, then the port occurrences must reflect those defined at the IfcElectricMotorType using the IfcRelDefinesByObject relationship. +

                                  The distribution ports relating to the IfcElectricMotor are defined by IfcDistributionPort and attached by the IfcRelConnectsPortToElement relationship. + If the electric motor occurrence is defined by IfcElectricMotorType, then the port occurrences must reflect those defined at the IfcElectricMotorType using the IfcRelDefinesByObject relationship. Ports are specific to the IfcElectricMotor PredefinedType as follows indicated by the IfcDistributionPort Name, PredefinedType, and FlowDirection:

                                  • Line (ELECTRICAL, SINK): Receives electrical power.
                                  • @@ -25398,8 +25398,8 @@ In this case a valid value for MethodOfMeasurement shall be provided.

                                    Port Use Definition

                                    -

                                    The distribution ports relating to the IfcMotorConnection are defined by IfcDistributionPort and attached by the IfcRelConnectsPortToElement relationship. - If the motor connection occurrence is defined by IfcMotorConnectionType, then the port occurrences must reflect those defined at the IfcMotorConnectionType using the IfcRelDefinesByObject relationship. +

                                    The distribution ports relating to the IfcMotorConnection are defined by IfcDistributionPort and attached by the IfcRelConnectsPortToElement relationship. + If the motor connection occurrence is defined by IfcMotorConnectionType, then the port occurrences must reflect those defined at the IfcMotorConnectionType using the IfcRelDefinesByObject relationship. Ports are specific to the IfcMotorConnection PredefinedType as follows indicated by the IfcDistributionPort Name, PredefinedType, and FlowDirection:

                                    • Motor (NOTDEFINED, SINK): Connection from the motor.
                                    • @@ -25444,8 +25444,8 @@ In this case a valid value for MethodOfMeasurement shall be provided.

                                      Port Use Definition

                                      -

                                      The distribution ports relating to the IfcSolarDevice are defined by IfcDistributionPort and attached by the IfcRelConnectsPortToElement relationship. - If the solar device occurrence is defined by IfcSolarDeviceType, then the port occurrences must reflect those defined at the IfcSolarDeviceType using the IfcRelDefinesByObject relationship. +

                                      The distribution ports relating to the IfcSolarDevice are defined by IfcDistributionPort and attached by the IfcRelConnectsPortToElement relationship. + If the solar device occurrence is defined by IfcSolarDeviceType, then the port occurrences must reflect those defined at the IfcSolarDeviceType using the IfcRelDefinesByObject relationship. Ports are specific to the IfcSolarDevice PredefinedType as follows indicated by the IfcDistributionPort Name, PredefinedType, and FlowDirection:

                                      • SOLARCOLLECTOR @@ -26732,8 +26732,8 @@ In this case a valid value for MethodOfMeasurement shall be provided.

                                        Port Use Definition

                                        -

                                        The distribution ports relating to the IfcPipeFitting are defined by IfcDistributionPort and attached by the IfcRelConnectsPortToElement relationship. - If the pipe fitting occurrence is defined by IfcPipeFittingType, then the port occurrences must reflect those defined at the IfcPipeFittingType using the IfcRelDefinesByObject relationship. +

                                        The distribution ports relating to the IfcPipeFitting are defined by IfcDistributionPort and attached by the IfcRelConnectsPortToElement relationship. + If the pipe fitting occurrence is defined by IfcPipeFittingType, then the port occurrences must reflect those defined at the IfcPipeFittingType using the IfcRelDefinesByObject relationship. Ports are specific to the IfcPipeFitting PredefinedType as follows indicated by the IfcDistributionPort Name, PredefinedType, and FlowDirection:

                                        • BEND @@ -26819,8 +26819,8 @@ In this case a valid value for MethodOfMeasurement shall be provided.

                                          Port Use Definition

                                          -

                                          The distribution ports relating to the IfcDuctFitting are defined by IfcDistributionPort and attached by the IfcRelConnectsPortToElement relationship. - If the duct fitting occurrence is defined by IfcDuctFittingType, then the port occurrences must reflect those defined at the IfcDuctFittingType using the IfcRelDefinesByObject relationship. +

                                          The distribution ports relating to the IfcDuctFitting are defined by IfcDistributionPort and attached by the IfcRelConnectsPortToElement relationship. + If the duct fitting occurrence is defined by IfcDuctFittingType, then the port occurrences must reflect those defined at the IfcDuctFittingType using the IfcRelDefinesByObject relationship. Ports are specific to the IfcDuctFitting PredefinedType as follows indicated by the IfcDistributionPort Name, PredefinedType, and FlowDirection:

                                          • BEND @@ -26921,8 +26921,8 @@ In this case a valid value for MethodOfMeasurement shall be provided.

                                            Port Use Definition

                                            -

                                            The distribution ports relating to the IfcPipeSegment are defined by IfcDistributionPort and attached by the IfcRelConnectsPortToElement relationship. - If the pipe segment occurrence is defined by IfcPipeSegmentType, then the port occurrences must reflect those defined at the IfcPipeSegmentType using the IfcRelDefinesByObject relationship. +

                                            The distribution ports relating to the IfcPipeSegment are defined by IfcDistributionPort and attached by the IfcRelConnectsPortToElement relationship. + If the pipe segment occurrence is defined by IfcPipeSegmentType, then the port occurrences must reflect those defined at the IfcPipeSegmentType using the IfcRelDefinesByObject relationship. Ports are specific to the IfcPipeSegment PredefinedType as follows indicated by the IfcDistributionPort Name, PredefinedType, and FlowDirection:

                                            • Inlet (NOTDEFINED, SINK): The flow inlet.
                                            • @@ -26975,8 +26975,8 @@ In this case a valid value for MethodOfMeasurement shall be provided.

                                              Port Use Definition

                                              -

                                              The distribution ports relating to the IfcDuctSegment are defined by IfcDistributionPort and attached by the IfcRelConnectsPortToElement relationship. - If the duct segment occurrence is defined by IfcDuctSegmentType, then the port occurrences must reflect those defined at the IfcDuctSegmentType using the IfcRelDefinesByObject relationship. +

                                              The distribution ports relating to the IfcDuctSegment are defined by IfcDistributionPort and attached by the IfcRelConnectsPortToElement relationship. + If the duct segment occurrence is defined by IfcDuctSegmentType, then the port occurrences must reflect those defined at the IfcDuctSegmentType using the IfcRelDefinesByObject relationship. Ports are specific to the IfcDuctSegment PredefinedType as follows indicated by the IfcDistributionPort Name, PredefinedType, and FlowDirection:

                                              • Inlet (NOTDEFINED, SINK): The flow inlet.
                                              • @@ -27045,8 +27045,8 @@ In this case a valid value for MethodOfMeasurement shall be provided.

                                                Port Use Definition

                                                -

                                                The distribution ports relating to the IfcFilter are defined by IfcDistributionPort and attached by the IfcRelConnectsPortToElement relationship. - If the filter occurrence is defined by IfcFilterType, then the port occurrences must reflect those defined at the IfcFilterType using the IfcRelDefinesByObject relationship. +

                                                The distribution ports relating to the IfcFilter are defined by IfcDistributionPort and attached by the IfcRelConnectsPortToElement relationship. + If the filter occurrence is defined by IfcFilterType, then the port occurrences must reflect those defined at the IfcFilterType using the IfcRelDefinesByObject relationship. Ports are specific to the IfcFilter PredefinedType as follows indicated by the IfcDistributionPort Name, PredefinedType, and FlowDirection:

                                                • AIRPARTICLEFILTER @@ -27129,8 +27129,8 @@ In this case a valid value for MethodOfMeasurement shall be provided.

                                                  Port Use Definition

                                                  -

                                                  The distribution ports relating to the IfcDuctSilencer are defined by IfcDistributionPort and attached by the IfcRelConnectsPortToElement relationship. - If the duct silencer occurrence is defined by IfcDuctSilencerType, then the port occurrences must reflect those defined at the IfcDuctSilencerType using the IfcRelDefinesByObject relationship. +

                                                  The distribution ports relating to the IfcDuctSilencer are defined by IfcDistributionPort and attached by the IfcRelConnectsPortToElement relationship. + If the duct silencer occurrence is defined by IfcDuctSilencerType, then the port occurrences must reflect those defined at the IfcDuctSilencerType using the IfcRelDefinesByObject relationship. Ports are specific to the IfcDuctSilencer PredefinedType as follows indicated by the IfcDistributionPort Name, PredefinedType, and FlowDirection:

                                                  • Inlet (NOTDEFINED, SINK): The flow inlet.
                                                  • @@ -27177,8 +27177,8 @@ In this case a valid value for MethodOfMeasurement shall be provided.

                                                    Port Use Definition

                                                    -

                                                    The distribution ports relating to the IfcCompressor are defined by IfcDistributionPort and attached by the IfcRelConnectsPortToElement relationship. - If the compressor occurrence is defined by IfcCompressorType, then the port occurrences must reflect those defined at the IfcCompressorType using the IfcRelDefinesByObject relationship. +

                                                    The distribution ports relating to the IfcCompressor are defined by IfcDistributionPort and attached by the IfcRelConnectsPortToElement relationship. + If the compressor occurrence is defined by IfcCompressorType, then the port occurrences must reflect those defined at the IfcCompressorType using the IfcRelDefinesByObject relationship. Ports are specific to the IfcCompressor PredefinedType as follows indicated by the IfcDistributionPort Name, PredefinedType, and FlowDirection:

                                                    • RefrigerantIn (REFRIGERATION, SINK): Uncompressed vapor refrigerant entering the compressor.
                                                    • @@ -27239,8 +27239,8 @@ In this case a valid value for MethodOfMeasurement shall be provided.

                                                      Port Use Definition

                                                      -

                                                      The distribution ports relating to the IfcFan are defined by IfcDistributionPort and attached by the IfcRelConnectsPortToElement relationship. - If the fan occurrence is defined by IfcFanType, then the port occurrences must reflect those defined at the IfcFanType using the IfcRelDefinesByObject relationship. +

                                                      The distribution ports relating to the IfcFan are defined by IfcDistributionPort and attached by the IfcRelConnectsPortToElement relationship. + If the fan occurrence is defined by IfcFanType, then the port occurrences must reflect those defined at the IfcFanType using the IfcRelDefinesByObject relationship. Ports are specific to the IfcFan PredefinedType as follows indicated by the IfcDistributionPort Name, PredefinedType, and FlowDirection:

                                                      • In (NOTDEFINED, SINK): Incoming air.
                                                      • @@ -27292,8 +27292,8 @@ In this case a valid value for MethodOfMeasurement shall be provided.

                                                        Port Use Definition

                                                        -

                                                        The distribution ports relating to the IfcPump are defined by IfcDistributionPort and attached by the IfcRelConnectsPortToElement relationship. - If the pump occurrence is defined by IfcPumpType, then the port occurrences must reflect those defined at the IfcPumpType using the IfcRelDefinesByObject relationship. +

                                                        The distribution ports relating to the IfcPump are defined by IfcDistributionPort and attached by the IfcRelConnectsPortToElement relationship. + If the pump occurrence is defined by IfcPumpType, then the port occurrences must reflect those defined at the IfcPumpType using the IfcRelDefinesByObject relationship. Ports are specific to the IfcPump PredefinedType as follows indicated by the IfcDistributionPort Name, PredefinedType, and FlowDirection:

                                                        • Power (ELECTRICAL, SINK): Receives electrical power.
                                                        • @@ -27347,8 +27347,8 @@ In this case a valid value for MethodOfMeasurement shall be provided.

                                                          Port Use Definition

                                                          -

                                                          The distribution ports relating to the IfcAirTerminal are defined by IfcDistributionPort and attached by the IfcRelConnectsPortToElement relationship. - If the air terminal occurrence is defined by IfcAirTerminalType, then the port occurrences must reflect those defined at the IfcAirTerminalType using the IfcRelDefinesByObject relationship. +

                                                          The distribution ports relating to the IfcAirTerminal are defined by IfcDistributionPort and attached by the IfcRelConnectsPortToElement relationship. + If the air terminal occurrence is defined by IfcAirTerminalType, then the port occurrences must reflect those defined at the IfcAirTerminalType using the IfcRelDefinesByObject relationship. Ports are specific to the IfcAirTerminal PredefinedType as follows indicated by the IfcDistributionPort Name, PredefinedType, and FlowDirection:

                                                          • DIFFUSER @@ -27371,7 +27371,7 @@ In this case a valid value for MethodOfMeasurement shall be provided.

                                                            Figure 211 — Air terminal port use

                                                            " -3536;IfcSpaceHeater;"

                                                            Space heaters utilize a combination of radiation and/or natural convection using a heating source such as electricity, steam or hot water to heat a limited space or area. Examples of space heaters include radiators, convectors, baseboard and finned-tube heaters.

                                                            +3536;IfcSpaceHeater;"

                                                            Space heaters utilize a combination of radiation and/or natural convection using a heating source such as electricity, steam or hot water to heat a limited space or area. Examples of space heaters include radiators, convectors, baseboard and finned-tube heaters.

                                                            IfcUnitaryEquipment should be used for packaged units supporting a combination of heating, cooling, and/or dehumidification; IfcCoil should be used for coil-based floor heating.

                                                            HISTORY  New entity in IFC2x4
                                                            @@ -27425,8 +27425,8 @@ In this case a valid value for MethodOfMeasurement shall be provided.

                                                            Port Use Definition

                                                            -

                                                            The distribution ports relating to the IfcSpaceHeater are defined by IfcDistributionPort and attached by the IfcRelConnectsPortToElement relationship. - If the space heater occurrence is defined by IfcSpaceHeaterType, then the port occurrences must reflect those defined at the IfcSpaceHeaterType using the IfcRelDefinesByObject relationship. +

                                                            The distribution ports relating to the IfcSpaceHeater are defined by IfcDistributionPort and attached by the IfcRelConnectsPortToElement relationship. + If the space heater occurrence is defined by IfcSpaceHeaterType, then the port occurrences must reflect those defined at the IfcSpaceHeaterType using the IfcRelDefinesByObject relationship. Ports are specific to the IfcSpaceHeater PredefinedType as follows indicated by the IfcDistributionPort Name, PredefinedType, and FlowDirection:

                                                            • CONVECTOR @@ -27445,7 +27445,7 @@ In this case a valid value for MethodOfMeasurement shall be provided.

                                                              Figure 230 — Space heater port use

                                                              " -3545;IfcMedicalDevice;"

                                                              A medical device is attached to a medical piping system and operates upon medical gases to perform a specific function. Medical gases include medical air, medical vacuum, oxygen, carbon dioxide, nitrogen, and nitrous oxide.

                                                              +3545;IfcMedicalDevice;"

                                                              A medical device is attached to a medical piping system and operates upon medical gases to perform a specific function. Medical gases include medical air, medical vacuum, oxygen, carbon dioxide, nitrogen, and nitrous oxide.

                                                              Outlets for medical gasses should use IfcValve with PredefinedType equal to GASTAP, containing an IfcDistributionPort with FlowDirection=SINK and PredefinedType equal to COMPRESSEDAIR, VACUUM, or CHEMICAL, and having property sets on the port further indicating the gas type and pressure. Tanks for medical gasses should use IfcTank with PredefinedType equal to PRESSUREVESSEL, containing an IfcDistributionPort with FlowDirection=SOURCE and PredefinedType=CHEMICAL, and having property sets on the port further indicating the gas type and pressure range.

                                                              HISTORY  New entity in IFC2x4
                                                              @@ -27484,8 +27484,8 @@ In this case a valid value for MethodOfMeasurement shall be provided.

                                                              Port Use Definition

                                                              -

                                                              The distribution ports relating to the IfcMedicalDevice are defined by IfcDistributionPort and attached by the IfcRelConnectsPortToElement relationship. - If the medical device occurrence is defined by IfcMedicalDeviceType, then the port occurrences must reflect those defined at the IfcMedicalDeviceType using the IfcRelDefinesByObject relationship. +

                                                              The distribution ports relating to the IfcMedicalDevice are defined by IfcDistributionPort and attached by the IfcRelConnectsPortToElement relationship. + If the medical device occurrence is defined by IfcMedicalDeviceType, then the port occurrences must reflect those defined at the IfcMedicalDeviceType using the IfcRelDefinesByObject relationship. Ports are specific to the IfcMedicalDevice PredefinedType as follows indicated by the IfcDistributionPort Name, PredefinedType, and FlowDirection:

                                                              • VACUUMSTATION @@ -27535,8 +27535,8 @@ In this case a valid value for MethodOfMeasurement shall be provided.

                                                                Port Use Definition

                                                                -

                                                                The distribution ports relating to the IfcAirTerminalBox are defined by IfcDistributionPort and attached by the IfcRelConnectsPortToElement relationship. - If the air terminal box occurrence is defined by IfcAirTerminalBoxType, then the port occurrences must reflect those defined at the IfcAirTerminalBoxType using the IfcRelDefinesByObject relationship. +

                                                                The distribution ports relating to the IfcAirTerminalBox are defined by IfcDistributionPort and attached by the IfcRelConnectsPortToElement relationship. + If the air terminal box occurrence is defined by IfcAirTerminalBoxType, then the port occurrences must reflect those defined at the IfcAirTerminalBoxType using the IfcRelDefinesByObject relationship. Ports are specific to the IfcAirTerminalBox PredefinedType as follows indicated by the IfcDistributionPort Name, PredefinedType, and FlowDirection:

                                                                • Inlet (AIRCONDITIONING, SINK): Incoming air.
                                                                • @@ -27615,8 +27615,8 @@ In this case a valid value for MethodOfMeasurement shall be provided.

                                                                  Port Use Definition

                                                                  -

                                                                  The distribution ports relating to the IfcDamper are defined by IfcDistributionPort and attached by the IfcRelConnectsPortToElement relationship. - If the damper occurrence is defined by IfcDamperType, then the port occurrences must reflect those defined at the IfcDamperType using the IfcRelDefinesByObject relationship. +

                                                                  The distribution ports relating to the IfcDamper are defined by IfcDistributionPort and attached by the IfcRelConnectsPortToElement relationship. + If the damper occurrence is defined by IfcDamperType, then the port occurrences must reflect those defined at the IfcDamperType using the IfcRelDefinesByObject relationship. Ports are specific to the IfcDamper PredefinedType as follows indicated by the IfcDistributionPort Name, PredefinedType, and FlowDirection:

                                                                  • AirIn (AIRCONDITIONING, SINK): Air entering damper.
                                                                  • @@ -27689,8 +27689,8 @@ In this case a valid value for MethodOfMeasurement shall be provided.

                                                                    Port Use Definition

                                                                    -

                                                                    The distribution ports relating to the IfcFlowMeter are defined by IfcDistributionPort and attached by the IfcRelConnectsPortToElement relationship. - If the flow meter occurrence is defined by IfcFlowMeterType, then the port occurrences must reflect those defined at the IfcFlowMeterType using the IfcRelDefinesByObject relationship. +

                                                                    The distribution ports relating to the IfcFlowMeter are defined by IfcDistributionPort and attached by the IfcRelConnectsPortToElement relationship. + If the flow meter occurrence is defined by IfcFlowMeterType, then the port occurrences must reflect those defined at the IfcFlowMeterType using the IfcRelDefinesByObject relationship. Ports are specific to the IfcFlowMeter PredefinedType as follows indicated by the IfcDistributionPort Name, PredefinedType, and FlowDirection:

                                                                    • ENERGYMETER @@ -27817,8 +27817,8 @@ In this case a valid value for MethodOfMeasurement shall be provided.

                                                                      Port Use Definition

                                                                      -

                                                                      The distribution ports relating to the IfcValve are defined by IfcDistributionPort and attached by the IfcRelConnectsPortToElement relationship. - If the valve occurrence is defined by IfcValveType, then the port occurrences must reflect those defined at the IfcValveType using the IfcRelDefinesByObject relationship. +

                                                                      The distribution ports relating to the IfcValve are defined by IfcDistributionPort and attached by the IfcRelConnectsPortToElement relationship. + If the valve occurrence is defined by IfcValveType, then the port occurrences must reflect those defined at the IfcValveType using the IfcRelDefinesByObject relationship. Ports are specific to the IfcValve PredefinedType as follows indicated by the IfcDistributionPort Name, PredefinedType, and FlowDirection:

                                                                      • AIRRELEASE @@ -28010,8 +28010,8 @@ In this case a valid value for MethodOfMeasurement shall be provided.

                                                                        Port Use Definition

                                                                        -

                                                                        The distribution ports relating to the IfcTank are defined by IfcDistributionPort and attached by the IfcRelConnectsPortToElement relationship. - If the tank occurrence is defined by IfcTankType, then the port occurrences must reflect those defined at the IfcTankType using the IfcRelDefinesByObject relationship. +

                                                                        The distribution ports relating to the IfcTank are defined by IfcDistributionPort and attached by the IfcRelConnectsPortToElement relationship. + If the tank occurrence is defined by IfcTankType, then the port occurrences must reflect those defined at the IfcTankType using the IfcRelDefinesByObject relationship. Ports are specific to the IfcTank PredefinedType as follows indicated by the IfcDistributionPort Name, PredefinedType, and FlowDirection:

                                                                        • Inlet (NOTDEFINED, SINK): Inlet.
                                                                        • @@ -28058,8 +28058,8 @@ In this case a valid value for MethodOfMeasurement shall be provided.

                                                                          Port Use Definition

                                                                          -

                                                                          The distribution ports relating to the IfcAirToAirHeatRecovery are defined by IfcDistributionPort and attached by the IfcRelConnectsPortToElement relationship. - If the air to air heat recovery occurrence is defined by IfcAirToAirHeatRecoveryType, then the port occurrences must reflect those defined at the IfcAirToAirHeatRecoveryType using the IfcRelDefinesByObject relationship. +

                                                                          The distribution ports relating to the IfcAirToAirHeatRecovery are defined by IfcDistributionPort and attached by the IfcRelConnectsPortToElement relationship. + If the air to air heat recovery occurrence is defined by IfcAirToAirHeatRecoveryType, then the port occurrences must reflect those defined at the IfcAirToAirHeatRecoveryType using the IfcRelDefinesByObject relationship. Ports are specific to the IfcAirToAirHeatRecovery PredefinedType as follows indicated by the IfcDistributionPort Name, PredefinedType, and FlowDirection:

                                                                          • AirInlet (AIRCONDITIONING, SINK): Cold air in.
                                                                          • @@ -28068,7 +28068,7 @@ In this case a valid value for MethodOfMeasurement shall be provided.

                                                                            ExhaustOutlet (VENTILATION, SOURCE): Hotter return air out.
                                                                          " -3581;IfcBoiler;"

                                                                          A boiler is a closed, pressure-rated vessel in which water or other fluid is heated using an energy source such as natural gas, heating oil, or electricity. The fluid in the vessel is then circulated out of the boiler for use in various processes or heating applications.

                                                                          +3581;IfcBoiler;"

                                                                          A boiler is a closed, pressure-rated vessel in which water or other fluid is heated using an energy source such as natural gas, heating oil, or electricity. The fluid in the vessel is then circulated out of the boiler for use in various processes or heating applications.

                                                                          IfcBoiler is a vessel solely used for heating of water or other fluids. Storage vessels, such as for drinking water storage are considered as tanks and use the IfcTank entity.

                                                                          HISTORY  New entity in IFC2x4
                                                                          @@ -28125,8 +28125,8 @@ In this case a valid value for MethodOfMeasurement shall be provided.

                                                                          Port Use Definition

                                                                          -

                                                                          The distribution ports relating to the IfcBoiler are defined by IfcDistributionPort and attached by the IfcRelConnectsPortToElement relationship. - If the boiler occurrence is defined by IfcBoilerType, then the port occurrences must reflect those defined at the IfcBoilerType using the IfcRelDefinesByObject relationship. +

                                                                          The distribution ports relating to the IfcBoiler are defined by IfcDistributionPort and attached by the IfcRelConnectsPortToElement relationship. + If the boiler occurrence is defined by IfcBoilerType, then the port occurrences must reflect those defined at the IfcBoilerType using the IfcRelDefinesByObject relationship. Ports are specific to the IfcBoiler PredefinedType as follows indicated by the IfcDistributionPort Name, PredefinedType, and FlowDirection:

                                                                          • STEAM @@ -28189,8 +28189,8 @@ In this case a valid value for MethodOfMeasurement shall be provided.

                                                                            Port Use Definition

                                                                            -

                                                                            The distribution ports relating to the IfcBurner are defined by IfcDistributionPort and attached by the IfcRelConnectsPortToElement relationship. - If the burner occurrence is defined by IfcBurnerType, then the port occurrences must reflect those defined at the IfcBurnerType using the IfcRelDefinesByObject relationship. +

                                                                            The distribution ports relating to the IfcBurner are defined by IfcDistributionPort and attached by the IfcRelConnectsPortToElement relationship. + If the burner occurrence is defined by IfcBurnerType, then the port occurrences must reflect those defined at the IfcBurnerType using the IfcRelDefinesByObject relationship. Ports are specific to the IfcBurner PredefinedType as follows indicated by the IfcDistributionPort Name, PredefinedType, and FlowDirection:

                                                                            • Gas (GAS, SINK): Gas inlet for burner.
                                                                            • @@ -28245,8 +28245,8 @@ In this case a valid value for MethodOfMeasurement shall be provided.

                                                                              Port Use Definition

                                                                              -

                                                                              The distribution ports relating to the IfcChiller are defined by IfcDistributionPort and attached by the IfcRelConnectsPortToElement relationship. - If the chiller occurrence is defined by IfcChillerType, then the port occurrences must reflect those defined at the IfcChillerType using the IfcRelDefinesByObject relationship. +

                                                                              The distribution ports relating to the IfcChiller are defined by IfcDistributionPort and attached by the IfcRelConnectsPortToElement relationship. + If the chiller occurrence is defined by IfcChillerType, then the port occurrences must reflect those defined at the IfcChillerType using the IfcRelDefinesByObject relationship. Ports are specific to the IfcChiller PredefinedType as follows indicated by the IfcDistributionPort Name, PredefinedType, and FlowDirection:

                                                                              • AIRCOOLED @@ -28274,7 +28274,7 @@ In this case a valid value for MethodOfMeasurement shall be provided.

                                                                                Figure 215 — Chiller port use

                                                                                " -3593;IfcCoil;"

                                                                                A coil is a device used to provide heat transfer between non-mixing media. A common example is a cooling coil, which utilizes a finned coil in which circulates chilled water, antifreeze, or refrigerant that is used to remove heat from air moving across the surface of the coil. A coil may be used either for heating or cooling purposes by placing a series of tubes (the coil) carrying a heating or cooling fluid into an airstream. The coil may be constructed from tubes bundled in a serpentine form or from finned tubes that give a extended heat transfer surface.

                                                                                +3593;IfcCoil;"

                                                                                A coil is a device used to provide heat transfer between non-mixing media. A common example is a cooling coil, which utilizes a finned coil in which circulates chilled water, antifreeze, or refrigerant that is used to remove heat from air moving across the surface of the coil. A coil may be used either for heating or cooling purposes by placing a series of tubes (the coil) carrying a heating or cooling fluid into an airstream. The coil may be constructed from tubes bundled in a serpentine form or from finned tubes that give a extended heat transfer surface.

                                                                                Coils may also be used for non-airflow cases such as embedded in a floor slab.

                                                                                HISTORY  New entity in IFC2x4
                                                                                @@ -28316,8 +28316,8 @@ In this case a valid value for MethodOfMeasurement shall be provided.

                                                                                Port Use Definition

                                                                                -

                                                                                The distribution ports relating to the IfcCoil are defined by IfcDistributionPort and attached by the IfcRelConnectsPortToElement relationship. - If the coil occurrence is defined by IfcCoilType, then the port occurrences must reflect those defined at the IfcCoilType using the IfcRelDefinesByObject relationship. +

                                                                                The distribution ports relating to the IfcCoil are defined by IfcDistributionPort and attached by the IfcRelConnectsPortToElement relationship. + If the coil occurrence is defined by IfcCoilType, then the port occurrences must reflect those defined at the IfcCoilType using the IfcRelDefinesByObject relationship. Ports are specific to the IfcCoil PredefinedType as follows indicated by the IfcDistributionPort Name, PredefinedType, and FlowDirection:

                                                                                • DXCOOLINGCOIL @@ -28389,8 +28389,8 @@ In this case a valid value for MethodOfMeasurement shall be provided.

                                                                                  Port Use Definition

                                                                                  -

                                                                                  The distribution ports relating to the IfcCondenser are defined by IfcDistributionPort and attached by the IfcRelConnectsPortToElement relationship. - If the condenser occurrence is defined by IfcCondenserType, then the port occurrences must reflect those defined at the IfcCondenserType using the IfcRelDefinesByObject relationship. +

                                                                                  The distribution ports relating to the IfcCondenser are defined by IfcDistributionPort and attached by the IfcRelConnectsPortToElement relationship. + If the condenser occurrence is defined by IfcCondenserType, then the port occurrences must reflect those defined at the IfcCondenserType using the IfcRelDefinesByObject relationship. Ports are specific to the IfcCondenser PredefinedType as follows indicated by the IfcDistributionPort Name, PredefinedType, and FlowDirection:

                                                                                  • AIRCOOLED @@ -28473,8 +28473,8 @@ In this case a valid value for MethodOfMeasurement shall be provided.

                                                                                    Port Use Definition

                                                                                    -

                                                                                    The distribution ports relating to the IfcCooledBeam are defined by IfcDistributionPort and attached by the IfcRelConnectsPortToElement relationship. - If the cooled beam occurrence is defined by IfcCooledBeamType, then the port occurrences must reflect those defined at the IfcCooledBeamType using the IfcRelDefinesByObject relationship. +

                                                                                    The distribution ports relating to the IfcCooledBeam are defined by IfcDistributionPort and attached by the IfcRelConnectsPortToElement relationship. + If the cooled beam occurrence is defined by IfcCooledBeamType, then the port occurrences must reflect those defined at the IfcCooledBeamType using the IfcRelDefinesByObject relationship. Ports are specific to the IfcCooledBeam PredefinedType as follows indicated by the IfcDistributionPort Name, PredefinedType, and FlowDirection:

                                                                                    • ChilledWaterIn (CHILLEDWATER, SINK): Chilled water entering.
                                                                                    • @@ -28536,8 +28536,8 @@ In this case a valid value for MethodOfMeasurement shall be provided.

                                                                                      Port Use Definition

                                                                                      -

                                                                                      The distribution ports relating to the IfcCoolingTower are defined by IfcDistributionPort and attached by the IfcRelConnectsPortToElement relationship. - If the cooling tower occurrence is defined by IfcCoolingTowerType, then the port occurrences must reflect those defined at the IfcCoolingTowerType using the IfcRelDefinesByObject relationship. +

                                                                                      The distribution ports relating to the IfcCoolingTower are defined by IfcDistributionPort and attached by the IfcRelConnectsPortToElement relationship. + If the cooling tower occurrence is defined by IfcCoolingTowerType, then the port occurrences must reflect those defined at the IfcCoolingTowerType using the IfcRelDefinesByObject relationship. Ports are specific to the IfcCoolingTower PredefinedType as follows indicated by the IfcDistributionPort Name, PredefinedType, and FlowDirection:

                                                                                      • CondenserWaterIn (CONDENSERWATER, SINK): Warmer water entering the cooling tower.
                                                                                      • @@ -28587,8 +28587,8 @@ In this case a valid value for MethodOfMeasurement shall be provided.

                                                                                        Port Use Definition

                                                                                        -

                                                                                        The distribution ports relating to the IfcEvaporativeCooler are defined by IfcDistributionPort and attached by the IfcRelConnectsPortToElement relationship. - If the evaporative cooler occurrence is defined by IfcEvaporativeCoolerType, then the port occurrences must reflect those defined at the IfcEvaporativeCoolerType using the IfcRelDefinesByObject relationship. +

                                                                                        The distribution ports relating to the IfcEvaporativeCooler are defined by IfcDistributionPort and attached by the IfcRelConnectsPortToElement relationship. + If the evaporative cooler occurrence is defined by IfcEvaporativeCoolerType, then the port occurrences must reflect those defined at the IfcEvaporativeCoolerType using the IfcRelDefinesByObject relationship. Ports are specific to the IfcEvaporativeCooler PredefinedType as follows indicated by the IfcDistributionPort Name, PredefinedType, and FlowDirection:

                                                                                        • WaterIn (DOMESTICCOLDWATER, SINK): Incoming water.
                                                                                        • @@ -28636,8 +28636,8 @@ In this case a valid value for MethodOfMeasurement shall be provided.

                                                                                          Port Use Definition

                                                                                          -

                                                                                          The distribution ports relating to the IfcEvaporator are defined by IfcDistributionPort and attached by the IfcRelConnectsPortToElement relationship. - If the evaporator occurrence is defined by IfcEvaporatorType, then the port occurrences must reflect those defined at the IfcEvaporatorType using the IfcRelDefinesByObject relationship. +

                                                                                          The distribution ports relating to the IfcEvaporator are defined by IfcDistributionPort and attached by the IfcRelConnectsPortToElement relationship. + If the evaporator occurrence is defined by IfcEvaporatorType, then the port occurrences must reflect those defined at the IfcEvaporatorType using the IfcRelDefinesByObject relationship. Ports are specific to the IfcEvaporator PredefinedType as follows indicated by the IfcDistributionPort Name, PredefinedType, and FlowDirection:

                                                                                          • DIRECTEXPANSION @@ -28669,7 +28669,7 @@ In this case a valid value for MethodOfMeasurement shall be provided.

                                                                                            Figure 223 — Evaporator port use

                                                                                            " -3617;IfcHeatExchanger;"

                                                                                            A heat exchanger is a device used to provide heat transfer between non-mixing media such as plate and shell and tube heat exchangers.

                                                                                            +3617;IfcHeatExchanger;"

                                                                                            A heat exchanger is a device used to provide heat transfer between non-mixing media such as plate and shell and tube heat exchangers.

                                                                                            IfcHeatExchanger is commonly used on water-side distribution systems to recover energy from a liquid to another liquid (typically water-based), whereas IfcAirToAirHeatRecovery is commonly used on air-side distribution systems to recover energy from a gas to a gas (usually air).

                                                                                            HISTORY  New entity in IFC2x4
                                                                                            @@ -28717,8 +28717,8 @@ In this case a valid value for MethodOfMeasurement shall be provided.

                                                                                            Port Use Definition

                                                                                            -

                                                                                            The distribution ports relating to the IfcHeatExchanger are defined by IfcDistributionPort and attached by the IfcRelConnectsPortToElement relationship. - If the heat exchanger occurrence is defined by IfcHeatExchangerType, then the port occurrences must reflect those defined at the IfcHeatExchangerType using the IfcRelDefinesByObject relationship. +

                                                                                            The distribution ports relating to the IfcHeatExchanger are defined by IfcDistributionPort and attached by the IfcRelConnectsPortToElement relationship. + If the heat exchanger occurrence is defined by IfcHeatExchangerType, then the port occurrences must reflect those defined at the IfcHeatExchangerType using the IfcRelDefinesByObject relationship. Ports are specific to the IfcHeatExchanger PredefinedType as follows indicated by the IfcDistributionPort Name, PredefinedType, and FlowDirection:

                                                                                            • HeatingInlet (NOTDEFINED, SINK): Inlet of substance to be heated.
                                                                                            • @@ -28766,8 +28766,8 @@ In this case a valid value for MethodOfMeasurement shall be provided.

                                                                                              Port Use Definition

                                                                                              -

                                                                                              The distribution ports relating to the IfcHumidifier are defined by IfcDistributionPort and attached by the IfcRelConnectsPortToElement relationship. - If the humidifier occurrence is defined by IfcHumidifierType, then the port occurrences must reflect those defined at the IfcHumidifierType using the IfcRelDefinesByObject relationship. +

                                                                                              The distribution ports relating to the IfcHumidifier are defined by IfcDistributionPort and attached by the IfcRelConnectsPortToElement relationship. + If the humidifier occurrence is defined by IfcHumidifierType, then the port occurrences must reflect those defined at the IfcHumidifierType using the IfcRelDefinesByObject relationship. Ports are specific to the IfcHumidifier PredefinedType as follows indicated by the IfcDistributionPort Name, PredefinedType, and FlowDirection:

                                                                                              • WaterIn (DOMESTICCOLDWATER, SINK): Incoming water.
                                                                                              • @@ -28822,8 +28822,8 @@ In this case a valid value for MethodOfMeasurement shall be provided.

                                                                                                Port Use Definition

                                                                                                -

                                                                                                The distribution ports relating to the IfcTubeBundle are defined by IfcDistributionPort and attached by the IfcRelConnectsPortToElement relationship. - If the tube bundle occurrence is defined by IfcTubeBundleType, then the port occurrences must reflect those defined at the IfcTubeBundleType using the IfcRelDefinesByObject relationship. +

                                                                                                The distribution ports relating to the IfcTubeBundle are defined by IfcDistributionPort and attached by the IfcRelConnectsPortToElement relationship. + If the tube bundle occurrence is defined by IfcTubeBundleType, then the port occurrences must reflect those defined at the IfcTubeBundleType using the IfcRelDefinesByObject relationship. Ports are specific to the IfcTubeBundle PredefinedType as follows indicated by the IfcDistributionPort Name, PredefinedType, and FlowDirection:

                                                                                                • Inlet (NOTDEFINED, SINK): Inlet.
                                                                                                • @@ -28891,8 +28891,8 @@ In this case a valid value for MethodOfMeasurement shall be provided.

                                                                                                  Port Use Definition

                                                                                                  -

                                                                                                  The distribution ports relating to the IfcUnitaryEquipment are defined by IfcDistributionPort and attached by the IfcRelConnectsPortToElement relationship. - If the unitary equipment occurrence is defined by IfcUnitaryEquipmentType, then the port occurrences must reflect those defined at the IfcUnitaryEquipmentType using the IfcRelDefinesByObject relationship. +

                                                                                                  The distribution ports relating to the IfcUnitaryEquipment are defined by IfcDistributionPort and attached by the IfcRelConnectsPortToElement relationship. + If the unitary equipment occurrence is defined by IfcUnitaryEquipmentType, then the port occurrences must reflect those defined at the IfcUnitaryEquipmentType using the IfcRelDefinesByObject relationship. Ports are specific to the IfcUnitaryEquipment PredefinedType as follows indicated by the IfcDistributionPort Name, PredefinedType, and FlowDirection:

                                                                                                  • AIRHANDLER @@ -28952,8 +28952,8 @@ In this case a valid value for MethodOfMeasurement shall be provided.

                                                                                                    Port Use Definition

                                                                                                    -

                                                                                                    The distribution ports relating to the IfcEngine are defined by IfcDistributionPort and attached by the IfcRelConnectsPortToElement relationship. - If the engine occurrence is defined by IfcEngineType, then the port occurrences must reflect those defined at the IfcEngineType using the IfcRelDefinesByObject relationship. +

                                                                                                    The distribution ports relating to the IfcEngine are defined by IfcDistributionPort and attached by the IfcRelConnectsPortToElement relationship. + If the engine occurrence is defined by IfcEngineType, then the port occurrences must reflect those defined at the IfcEngineType using the IfcRelDefinesByObject relationship. Ports are specific to the IfcEngine PredefinedType as follows indicated by the IfcDistributionPort Name, PredefinedType, and FlowDirection:

                                                                                                    • Fuel (GAS, SINK): The fuel inlet.
                                                                                                    • @@ -29282,7 +29282,7 @@ In this case a valid value for MethodOfMeasurement shall be provided.

                                                                                                      The distribution ports relating to the IfcFireSuppressionTerminalType type are defined by IfcDistributionPort and attached by the IfcRelConnectsPortToElement relationship. Ports are reflected at occurrences of this type using the IfcRelDefinesByObject relationship. Refer to the documentation at IfcFireSuppressionTerminal for standard port definitions.

                                                                                                      " -3708;IfcFireSuppressionTerminal;"

                                                                                                      A fire suppression terminal has the purpose of delivering a fluid (gas or liquid) that will suppress a fire.

                                                                                                      +3708;IfcFireSuppressionTerminal;"

                                                                                                      A fire suppression terminal has the purpose of delivering a fluid (gas or liquid) that will suppress a fire.

                                                                                                      A fire suppression terminal provides for all forms of sprinkler, spreader and other form of terminal that is connected to a pipework system and intended to act in the role of suppressing a fire.

                                                                                                      HISTORY  New entity in IFC2x4
                                                                                                      @@ -29346,8 +29346,8 @@ In this case a valid value for MethodOfMeasurement shall be provided.

                                                                                                      Port Use Definition

                                                                                                      -

                                                                                                      The distribution ports relating to the IfcFireSuppressionTerminal are defined by IfcDistributionPort and attached by the IfcRelConnectsPortToElement relationship. - If the fire suppression terminal occurrence is defined by IfcFireSuppressionTerminalType, then the port occurrences must reflect those defined at the IfcFireSuppressionTerminalType using the IfcRelDefinesByObject relationship. +

                                                                                                      The distribution ports relating to the IfcFireSuppressionTerminal are defined by IfcDistributionPort and attached by the IfcRelConnectsPortToElement relationship. + If the fire suppression terminal occurrence is defined by IfcFireSuppressionTerminalType, then the port occurrences must reflect those defined at the IfcFireSuppressionTerminalType using the IfcRelDefinesByObject relationship. Ports are specific to the IfcFireSuppressionTerminal PredefinedType as follows indicated by the IfcDistributionPort Name, PredefinedType, and FlowDirection:

                                                                                                      • FIREHYDRANT @@ -29449,8 +29449,8 @@ In this case a valid value for MethodOfMeasurement shall be provided.

                                                                                                        Port Use Definition

                                                                                                        -

                                                                                                        The distribution ports relating to the IfcSanitaryTerminal are defined by IfcDistributionPort and attached by the IfcRelConnectsPortToElement relationship. - If the sanitary terminal occurrence is defined by IfcSanitaryTerminalType, then the port occurrences must reflect those defined at the IfcSanitaryTerminalType using the IfcRelDefinesByObject relationship. +

                                                                                                        The distribution ports relating to the IfcSanitaryTerminal are defined by IfcDistributionPort and attached by the IfcRelConnectsPortToElement relationship. + If the sanitary terminal occurrence is defined by IfcSanitaryTerminalType, then the port occurrences must reflect those defined at the IfcSanitaryTerminalType using the IfcRelDefinesByObject relationship. Ports are specific to the IfcSanitaryTerminal PredefinedType as follows indicated by the IfcDistributionPort Name, PredefinedType, and FlowDirection:

                                                                                                        • BATH @@ -29552,8 +29552,8 @@ In this case a valid value for MethodOfMeasurement shall be provided.

                                                                                                          Port Use Definition

                                                                                                          -

                                                                                                          The distribution ports relating to the IfcStackTerminal are defined by IfcDistributionPort and attached by the IfcRelConnectsPortToElement relationship. - If the stack terminal occurrence is defined by IfcStackTerminalType, then the port occurrences must reflect those defined at the IfcStackTerminalType using the IfcRelDefinesByObject relationship. +

                                                                                                          The distribution ports relating to the IfcStackTerminal are defined by IfcDistributionPort and attached by the IfcRelConnectsPortToElement relationship. + If the stack terminal occurrence is defined by IfcStackTerminalType, then the port occurrences must reflect those defined at the IfcStackTerminalType using the IfcRelDefinesByObject relationship. Ports are specific to the IfcStackTerminal PredefinedType as follows indicated by the IfcDistributionPort Name, PredefinedType, and FlowDirection:

                                                                                                          • BIRDCAGE @@ -29573,7 +29573,7 @@ In this case a valid value for MethodOfMeasurement shall be provided.

                                                                                                          " -3720;IfcWasteTerminal;"

                                                                                                          A waste terminal has the purpose of collecting or intercepting waste from one or more sanitary terminals or other fluid waste generating equipment and discharging it into a single waste/drainage system.

                                                                                                          +3720;IfcWasteTerminal;"

                                                                                                          A waste terminal has the purpose of collecting or intercepting waste from one or more sanitary terminals or other fluid waste generating equipment and discharging it into a single waste/drainage system.

                                                                                                          A waste terminal provides for all forms of trap and waste point that collects discharge from a sanitary terminal and discharges it into a waste/drainage subsystem or that collects waste from several terminals and passes it into a single waste/drainage subsystem. This includes the P and S traps from soil sanitary terminals, sinks, and basins as well as floor wastes and gully traps that provide collection points.

                                                                                                          HISTORY  New entity in IFC2x4
                                                                                                          @@ -29652,8 +29652,8 @@ In this case a valid value for MethodOfMeasurement shall be provided.

                                                                                                          Port Use Definition

                                                                                                          -

                                                                                                          The distribution ports relating to the IfcWasteTerminal are defined by IfcDistributionPort and attached by the IfcRelConnectsPortToElement relationship. - If the waste terminal occurrence is defined by IfcWasteTerminalType, then the port occurrences must reflect those defined at the IfcWasteTerminalType using the IfcRelDefinesByObject relationship. +

                                                                                                          The distribution ports relating to the IfcWasteTerminal are defined by IfcDistributionPort and attached by the IfcRelConnectsPortToElement relationship. + If the waste terminal occurrence is defined by IfcWasteTerminalType, then the port occurrences must reflect those defined at the IfcWasteTerminalType using the IfcRelDefinesByObject relationship. Ports are specific to the IfcWasteTerminal PredefinedType as follows indicated by the IfcDistributionPort Name, PredefinedType, and FlowDirection:

                                                                                                          • FLOORTRAP @@ -29797,8 +29797,8 @@ In this case a valid value for MethodOfMeasurement shall be provided.

                                                                                                            Port Use Definition

                                                                                                            -

                                                                                                            The distribution ports relating to the IfcInterceptor are defined by IfcDistributionPort and attached by the IfcRelConnectsPortToElement relationship. - If the interceptor occurrence is defined by IfcInterceptorType, then the port occurrences must reflect those defined at the IfcInterceptorType using the IfcRelDefinesByObject relationship. +

                                                                                                            The distribution ports relating to the IfcInterceptor are defined by IfcDistributionPort and attached by the IfcRelConnectsPortToElement relationship. + If the interceptor occurrence is defined by IfcInterceptorType, then the port occurrences must reflect those defined at the IfcInterceptorType using the IfcRelDefinesByObject relationship. Ports are specific to the IfcInterceptor PredefinedType as follows indicated by the IfcDistributionPort Name, PredefinedType, and FlowDirection:

                                                                                                            • Inlet (DRAINAGE, SINK): Inlet drainage.
                                                                                                            • @@ -29913,29 +29913,29 @@ IFC2x4 CHANGE The attribute Description and inverse attribute HasRefer

                                                                                                              The IfcClassificationReference can be used as a form of 'lightweight' classification through the 'Identification' attribute inherited from the abstract IfcExternalReference class. In this case, the 'Identification' could take (for instance) the Uniclass notation ""L6814"" which, if the classification was well understood by all parties and was known to be taken from a particular classification source, would be sufficient. The Name attribute could be the title ""Tanking"". This would remove the need for the overhead of the more complete classification structure of the model.

                                                                                                              " -3787;IfcDocumentReference;" +3787;IfcDocumentReference;" -

                                                                                                              An IfcDocumentReference is a reference -to the location of a document. The reference is given by a system -interpretable Location attribute (a URL string) where the document can be found, and an optional inherited - internal reference Identification, which refers to a system - interpretable position within the document. The optional inherited -Name attribute is meant to have meaning for human readers. Optional -document metadata can also be captured through reference to -IfcDocumentInformation.

                                                                                                              +

                                                                                                              An IfcDocumentReference is a reference +to the location of a document. The reference is given by a system +interpretable Location attribute (a URL string) where the document can be found, and an optional inherited + internal reference Identification, which refers to a system + interpretable position within the document. The optional inherited +Name attribute is meant to have meaning for human readers. Optional +document metadata can also be captured through reference to +IfcDocumentInformation.

                                                                                                              + -
                                                                                                              -HISTORY: New Entity in IFC Release 2.0. +HISTORY: New Entity in IFC Release 2.0. Modified in IFC 2x.
                                                                                                              - - - - + + + +
                                                                                                              " 3792;IfcDocumentInformation;" diff --git a/src/ifcopenshell-python/ifcopenshell/geom/app.py b/src/ifcopenshell-python/ifcopenshell/geom/app.py index 8eedc41ec8..43d5762584 100644 --- a/src/ifcopenshell-python/ifcopenshell/geom/app.py +++ b/src/ifcopenshell-python/ifcopenshell/geom/app.py @@ -9,7 +9,14 @@ import operator import functools import multiprocessing -import OCC.AIS +try: + from OCC.Core import AIS + + USE_OCCT_HANDLE = False +except ImportError: + from OCC import AIS + + USE_OCCT_HANDLE = True from collections import defaultdict, OrderedDict @@ -426,30 +433,30 @@ class application(QtWidgets.QApplication): instanceSelected = QtCore.pyqtSignal([object]) - @staticmethod - def ais_to_key(ais_handle): - def yield_shapes(): - ais = ais_handle.GetObject() - if hasattr(ais, "Shape"): - yield ais.Shape() - return - shp = OCC.AIS.Handle_AIS_Shape.DownCast(ais_handle) - if not shp.IsNull(): - yield shp.Shape() - return - mult = ais_handle - if mult.IsNull(): - shp = OCC.AIS.Handle_AIS_Shape.DownCast(ais_handle) - if not shp.IsNull(): - yield shp - else: - li = mult.GetObject().ConnectedTo() - for i in range(li.Length()): - shp = OCC.AIS.Handle_AIS_Shape.DownCast(li.Value(i + 1)) - if not shp.IsNull(): - yield shp +# @staticmethod +# def ais_to_key(ais_handle): +# def yield_shapes(): +# ais = ais_handle.GetObject() +# if hasattr(ais, "Shape"): +# yield ais.Shape() +# return +# shp = OCC.AIS.Handle_AIS_Shape.DownCast(ais_handle) +# if not shp.IsNull(): +# yield shp.Shape() +# return +# mult = ais_handle +# if mult.IsNull(): +# shp = OCC.AIS.Handle_AIS_Shape.DownCast(ais_handle) +# if not shp.IsNull(): +# yield shp +# else: +# li = mult.GetObject().ConnectedTo() +# for i in range(li.Length()): +# shp = OCC.AIS.Handle_AIS_Shape.DownCast(li.Value(i + 1)) +# if not shp.IsNull(): +# yield shp - return tuple(shp.HashCode(1 << 24) for shp in yield_shapes()) +# return tuple(shp.HashCode(1 << 24) for shp in yield_shapes()) def __init__(self, widget): qtViewer3d.__init__(self, widget) @@ -479,8 +486,9 @@ class application(QtWidgets.QApplication): for shape in shapes: ais = display_shape(shape, viewer_handle=v) product = f[shape.data.id] - - ais.GetObject().SetSelectionPriority(self.counter) + + if USE_OCCT_HANDLE: + ais.GetObject().SetSelectionPriority(self.counter) self.ais_to_product[self.counter] = product self.product_to_ais[product] = ais self.counter += 1 diff --git a/src/ifcopenshell-python/ifcopenshell/util/date.py b/src/ifcopenshell-python/ifcopenshell/util/date.py new file mode 100644 index 0000000000..1da652f74e --- /dev/null +++ b/src/ifcopenshell-python/ifcopenshell/util/date.py @@ -0,0 +1,47 @@ +from re import findall +from datetime import datetime + + +def duration2dict(duration): + results = {} + for number, unit in findall("(?P\d+)(?PS|M|H|D|W|Y)", duration): + results[unit] = number + return results + + +def ifc2datetime(element): + if isinstance(element, str) and element[0] == "P": # IfcDuration + return duration2dict(element) + elif isinstance(element, str): # IfcDateTime, IfcDate + return datetime.fromisoformat(element) + elif isinstance(element, int): # IfcTimeStamp + return datetime.fromtimestamp(element) + elif element.is_a("IfcDateAndTime"): + return datetime( + element.DateComponent.YearComponent, + element.DateComponent.MonthComponent, + element.DateComponent.DayComponent, + element.TimeComponent.HourComponent, + element.TimeComponent.MinuteComponent, + element.TimeComponent.SecondComponent, + # TODO: implement TimeComponent timezone + ) + elif element.is_a("IfcCalendarDate"): + return datetime( + element.YearComponent, + element.MonthComponent, + element.DayComponent, + ) + + +def datetime2ifc(dt, ifc_type): + if isinstance(dt, str): + dt = datetime.fromisoformat(dt) + if ifc_type == "IfcTimeStamp": + return int(dt.timestamp()) + elif ifc_type == "IfcDateTime": + return dt.isoformat() + elif ifc_type == "IfcDate": + return dt.date().isoformat() + elif ifc_type == "IfcTime": + return dt.time().isoformat() diff --git a/src/ifcopenshell-python/ifcopenshell/util/element.py b/src/ifcopenshell-python/ifcopenshell/util/element.py index b67ec58cf3..f947dccfc0 100644 --- a/src/ifcopenshell-python/ifcopenshell/util/element.py +++ b/src/ifcopenshell-python/ifcopenshell/util/element.py @@ -83,6 +83,11 @@ def get_material(element): return relationship.RelatingMaterial +def get_container(element): + if hasattr(element, "ContainedInStructure") and element.ContainedInStructure: + return element.ContainedInStructure[0].RelatingStructure + + def replace_attribute(element, old, new): for i, attribute in enumerate(element): if attribute == old: @@ -113,6 +118,14 @@ def is_representation_of_context(representation, context, subcontext=None, targe return True +def remove_deep(ifc_file, element): + subgraph = list(ifc_file.traverse(element)) + subgraph_set = set(subgraph) + for ref in subgraph[::-1]: + if ref.id() and len(set(ifc_file.get_inverse(ref)) - subgraph_set) == 0: + ifc_file.remove(ref) + + def get_representation(element, context, subcontext=None, target_view=None): if element.is_a("IfcProduct") and element.Representation: for r in element.Representation.Representations: diff --git a/src/ifcopenshell-python/ifcopenshell/util/selector.py b/src/ifcopenshell-python/ifcopenshell/util/selector.py index 416a178e8e..5317dec333 100644 --- a/src/ifcopenshell-python/ifcopenshell/util/selector.py +++ b/src/ifcopenshell-python/ifcopenshell/util/selector.py @@ -227,6 +227,14 @@ class Selector: except: return key = ".".join(key.split(".")[1:]) + elif "." in key and key.split(".")[0] == "container": + try: + element = ifcopenshell.util.element.get_container(element) + if not element: + return None + except: + return + key = ".".join(key.split(".")[1:]) info = element.get_info() if key in info: return info[key] diff --git a/src/ifcparse/IfcParse.cpp b/src/ifcparse/IfcParse.cpp index 3ceb4205dc..5d2093faee 100644 --- a/src/ifcparse/IfcParse.cpp +++ b/src/ifcparse/IfcParse.cpp @@ -1903,6 +1903,16 @@ void IfcFile::removeEntity(IfcUtil::IfcBaseClass* entity) { break; } } + + // This entity_file_map remains obviously flawed, but until we have proper lookup by value, or another mechanism, + // to prevent duplicate definitions with usage of add() we have to keep it. This might be a good moment to clear it. + for (auto it = entity_file_map.begin(); it != entity_file_map.end();) { + if (it->second == entity) { + it = entity_file_map.erase(it); + } else { + ++it; + } + } delete entity; } diff --git a/src/ifcpatch/recipes/ExtractElements.py b/src/ifcpatch/recipes/ExtractElements.py index c42ee0ddef..f414515d7e 100644 --- a/src/ifcpatch/recipes/ExtractElements.py +++ b/src/ifcpatch/recipes/ExtractElements.py @@ -30,6 +30,9 @@ class Patcher: new_spatial_element = self.new.add(spatial_element) self.contained_ins.setdefault(spatial_element.GlobalId, set()).add(new_element) self.add_spatial_tree(spatial_element, new_spatial_element) + for opening in element.HasOpenings: + self.new.add(opening) + self.new.add(opening.RelatedOpeningElement) def add_spatial_tree(self, spatial_element, new_spatial_element): for rel in spatial_element.Decomposes: diff --git a/src/ifcsverchok/nodes/ifc/by_type.py b/src/ifcsverchok/nodes/ifc/by_type.py index bb1095883a..427a11a91a 100644 --- a/src/ifcsverchok/nodes/ifc/by_type.py +++ b/src/ifcsverchok/nodes/ifc/by_type.py @@ -4,8 +4,7 @@ import ifcsverchok.helper from bpy.props import StringProperty, EnumProperty from sverchok.node_tree import SverchCustomTreeNode from sverchok.data_structure import updateNode -from blenderbim.bim import schema -from blenderbim.bim.prop import getIfcClasses, getIfcProducts, refreshClasses, refreshPredefinedTypes +from blenderbim.bim.module.root.prop import getIfcClasses, getIfcProducts, refreshClasses, refreshPredefinedTypes class SvIfcByType(bpy.types.Node, SverchCustomTreeNode, ifcsverchok.helper.SvIfcCore): diff --git a/src/ifcsverchok/nodes/ifc/select_blender_objects.py b/src/ifcsverchok/nodes/ifc/select_blender_objects.py index 8808b8d3b9..9f9f15cad5 100644 --- a/src/ifcsverchok/nodes/ifc/select_blender_objects.py +++ b/src/ifcsverchok/nodes/ifc/select_blender_objects.py @@ -5,6 +5,7 @@ import ifcsverchok.helper from bpy.props import StringProperty from sverchok.node_tree import SverchCustomTreeNode from sverchok.data_structure import updateNode +from blenderbim.bim.ifc import IfcStore class SvIfcSelectBlenderObjectsRefresh(bpy.types.Operator): @@ -36,12 +37,14 @@ class SvIfcSelectBlenderObjects(bpy.types.Node, SverchCustomTreeNode, ifcsvercho ) def process(self): + self.file = IfcStore.get_file() self.sv_input_names = ["entities"] self.guids = [] super().process() for obj in bpy.context.visible_objects: - index = obj.BIMObjectProperties.attributes.find("GlobalId") - if index != -1 and obj.BIMObjectProperties.attributes[index].string_value in self.guids: + if not obj.BIMObjectProperties.ifc_definition_id: + continue + if self.file.by_id(obj.BIMObjectProperties.ifc_definition_id).GlobalId in self.guids: obj.select_set(True) def process_ifc(self, entities): diff --git a/src/serializers/SvgSerializer.cpp b/src/serializers/SvgSerializer.cpp index 1a470f4cc3..cc8f3c621c 100644 --- a/src/serializers/SvgSerializer.cpp +++ b/src/serializers/SvgSerializer.cpp @@ -409,6 +409,29 @@ void SvgSerializer::write(const IfcGeom::BRepElement* brep_obj) { write(data); } +namespace { + class hlr_writer { + const TopoDS_Shape& shape_; + + public: + hlr_writer(const TopoDS_Shape& shape) : shape_(shape) + {} + + void operator()(boost::blank&) const { + throw std::runtime_error(""); + } + + void operator()(Handle(HLRBRep_Algo)& algo) const { + algo->Add(shape_); + } + + void operator()(Handle(HLRBRep_PolyAlgo)& algo) const { + BRepMesh_IncrementalMesh(shape_, 0.10); + algo->Load(shape_); + } + }; +} + void SvgSerializer::write(const geometry_data& data) { std::vector section_heights_storage; const std::vector* section_heights_used = §ion_heights_storage; @@ -543,7 +566,7 @@ void SvgSerializer::write(const geometry_data& data) { IfcUtil::IfcBaseEntity* storey = nullptr; std::string drawing_name; - bool use_hlr = false; + bool use_hlr = always_project_; // @todo use visitor // horizontal_plan, horizontal_plan_at_element, vertical_section @@ -556,8 +579,10 @@ void SvgSerializer::write(const geometry_data& data) { range.first = -std::numeric_limits::infinity(); } projection_direction = gp::DZ(); + projection_plane = gp_Pln(gp_Ax3(gp_Pnt(0, 0, cut_z), gp_Dir(0, 0, 1), gp_Dir(1, 0, 0))); } else if (variant.which() == 1) { projection_direction = gp::DZ(); + projection_plane = gp_Pln(gp_Ax3(gp_Pnt(0, 0, cut_z), gp_Dir(0, 0, 1), gp_Dir(1, 0, 0))); } else if (variant.which() == 2) { const auto& section = boost::get(variant); projection_direction = section.plane.Axis().Direction(); @@ -568,7 +593,7 @@ void SvgSerializer::write(const geometry_data& data) { auto& compound_to_use = is_floor_plan_ ? compound : compound_unmirrored; - if (use_hlr && (hlr_poly || hlr_brep)) { + if (use_hlr) { // && (hlr.which())) { // Check if any of the bounding box points is on the correct side of the plane Bnd_Box bb; @@ -601,12 +626,22 @@ void SvgSerializer::write(const geometry_data& data) { } if (any) { - if (hlr_brep) { - hlr_brep->Add(compound_to_use); + if (is_floor_plan_ && storey) { + if (storey_hlr.find(storey) == storey_hlr.end()) { + if (use_hlr_poly_) { + storey_hlr[storey] = new HLRBRep_PolyAlgo; + } else { + storey_hlr[storey] = new HLRBRep_Algo; + } + } + hlr_writer vis(compound_to_use); + boost::apply_visitor(vis, storey_hlr[storey]); + // this is tricky, how can we change start_path()? Always include section/projection params? + // storey_hlr[] } - if (hlr_poly) { - BRepMesh_IncrementalMesh(compound_to_use, 0.10); - hlr_poly->Load(compound_to_use); + else { + hlr_writer vis(compound_to_use); + boost::apply_visitor(vis, hlr); } } } @@ -952,6 +987,88 @@ std::array, 3> SvgSerializer::resize() { return m; } +namespace { + class hlr_calc { + private: + const HLRAlgo_Projector& projector_; + + public: + hlr_calc(const HLRAlgo_Projector& projector) : projector_(projector) + {} + + TopoDS_Shape operator()(boost::blank&) const { + throw std::runtime_error(""); + } + + TopoDS_Shape operator()(Handle(HLRBRep_Algo)& algo) { + algo->Projector(projector_); + algo->Update(); + algo->Hide(); + HLRBRep_HLRToShape hlr_shapes(algo); + return hlr_shapes.VCompound(); + } + + TopoDS_Shape operator()(Handle(HLRBRep_PolyAlgo)& algo) { + algo->Projector(projector_); + algo->Update(); + HLRBRep_PolyHLRToShape hlr_shapes; + hlr_shapes.Update(algo); + return hlr_shapes.VCompound(); + } + }; +} + +void SvgSerializer::draw_hlr(const gp_Pln& pln, const drawing_key& drawing_name) { + gp_Trsf trsf; + trsf.SetTransformation(pln.Position()); + HLRAlgo_Projector projector(trsf, false, 1.); + + hlr_calc vis(projector); + TopoDS_Shape hlr_compound_unmirrored = boost::apply_visitor(vis, drawing_name.first ? this->storey_hlr[drawing_name.first] : hlr); + + if (!hlr_compound_unmirrored.IsNull()) { + // Compound 3D curves for mirroring to work + ShapeFix_Edge sfe; + TopExp_Explorer exp(hlr_compound_unmirrored, TopAbs_EDGE); + for (; exp.More(); exp.Next()) { + sfe.FixAddCurve3d(TopoDS::Edge(exp.Current())); + } + + // Mirror to match SVG coord system. + // @todo this is very wasteful. We better do the Y-mirror in the SVG writing and + // not on the TopoDS_Shape input. + + TopoDS_Shape hlr_compound; + if (drawing_name.first == nullptr) { + gp_Trsf trsf_mirror; + trsf_mirror.SetMirror(gp_Ax2(gp::Origin(), gp::DY())); + BRepBuilderAPI_Transform make_transform_mirror(hlr_compound_unmirrored, trsf_mirror, true); + make_transform_mirror.Build(); + hlr_compound = make_transform_mirror.Shape(); + } else { + // In case of building storey-based floor plan the mirroring has already + // been taken into account before projection. + hlr_compound = hlr_compound_unmirrored; + } + + exp.Init(hlr_compound, TopAbs_EDGE); + BRep_Builder B; + path_object* po; + if (drawing_name.first) { + po = &start_path(pln, drawing_name.first, "class=\"projection\""); + } else { + po = &start_path(pln, drawing_name.second, "class=\"projection\""); + } + for (; exp.More(); exp.Next()) { + TopoDS_Wire w; + B.MakeWire(w); + B.Add(w, exp.Current()); + write(*po, w); + } + + } +} + void SvgSerializer::resetScale() { // reset the bounding box, as a subsequent drawing (elevation, section) will be centered, but use the same scale. // this is a separate call now as we first need to read drawing extents for automatically positioning sections and @@ -963,6 +1080,10 @@ void SvgSerializer::resetScale() { } void SvgSerializer::finalize() { + for (auto& p : storey_hlr) { + draw_hlr(drawing_metadata[{p.first, ""}].pln_3d, { p.first, "" }); + } + auto m = resize(); // Update the paper space scale matrices @@ -1032,7 +1153,7 @@ void SvgSerializer::finalize() { is_floor_plan_ = false; for (auto& sd : *deferred_section_data_) { - bool use_hlr = false; + bool use_hlr = true; std::string drawing_name; if (sd.which() == 2) { const auto& section = boost::get(sd); @@ -1042,9 +1163,9 @@ void SvgSerializer::finalize() { if (use_hlr) { if (use_hlr_poly_) { - hlr_poly = new HLRBRep_PolyAlgo; + hlr = new HLRBRep_PolyAlgo; } else { - hlr_brep = new HLRBRep_Algo; + hlr = new HLRBRep_Algo; } } @@ -1055,58 +1176,9 @@ void SvgSerializer::finalize() { if (use_hlr) { const auto& section = boost::get(sd); - - gp_Trsf trsf; - trsf.SetTransformation(section.plane.Position()); - HLRAlgo_Projector projector(trsf, false, 1.); - - TopoDS_Shape hlr_compound_unmirrored; + const auto& ax = section.plane.Position(); - if (use_hlr_poly_) { - hlr_poly->Projector(projector); - - hlr_poly->Update(); - HLRBRep_PolyHLRToShape hlr_shapes; - hlr_shapes.Update(hlr_poly); - hlr_compound_unmirrored = hlr_shapes.VCompound(); - } else { - hlr_brep->Projector(projector); - - hlr_brep->Update(); - hlr_brep->Hide(); - HLRBRep_HLRToShape hlr_shapes(hlr_brep); - hlr_compound_unmirrored = hlr_shapes.VCompound(); - } - - if (!hlr_compound_unmirrored.IsNull()) { - // Compound 3D curves for mirroring to work - ShapeFix_Edge sfe; - TopExp_Explorer exp(hlr_compound_unmirrored, TopAbs_EDGE); - for (; exp.More(); exp.Next()) { - sfe.FixAddCurve3d(TopoDS::Edge(exp.Current())); - } - - // Mirror to match SVG coord system. - // @todo this is very wasteful. We better do the Y-mirror in the SVG writing and - // not on the TopoDS_Shape input. - - gp_Trsf trsf_mirror; - trsf_mirror.SetMirror(gp_Ax2(gp::Origin(), gp::DY())); - BRepBuilderAPI_Transform make_transform_mirror(hlr_compound_unmirrored, trsf_mirror, true); - make_transform_mirror.Build(); - auto hlr_compound = make_transform_mirror.Shape(); - - exp.Init(hlr_compound, TopAbs_EDGE); - BRep_Builder B; - auto& po = start_path(section.plane, drawing_name, "class=\"projection\""); - for (; exp.More(); exp.Next()) { - TopoDS_Wire w; - B.MakeWire(w); - B.Add(w, exp.Current()); - write(po, w); - } - - } + draw_hlr(ax, { nullptr, drawing_name }); } auto m3 = resize(); @@ -1116,8 +1188,8 @@ void SvgSerializer::finalize() { resetScale(); - if (hlr_brep) hlr_brep.Nullify(); - if (hlr_poly) hlr_poly.Nullify(); + // @todo does this probably call Nullify() + hlr = boost::blank(); } } diff --git a/src/serializers/SvgSerializer.h b/src/serializers/SvgSerializer.h index f1b3740ad6..e806789840 100644 --- a/src/serializers/SvgSerializer.h +++ b/src/serializers/SvgSerializer.h @@ -114,6 +114,12 @@ struct drawing_meta { std::array, 3> matrix_3; }; +typedef boost::variant< + boost::blank, + Handle(HLRBRep_Algo), + Handle(HLRBRep_PolyAlgo) +> hlr_t; + class SvgSerializer : public GeometrySerializer { public: typedef std::pair > path_object; @@ -128,12 +134,13 @@ protected: bool with_section_heights_from_storey_, rescale, print_space_names_, print_space_areas_; bool draw_door_arcs_, is_floor_plan_; bool auto_section_, auto_elevation_; - bool use_namespace_, use_hlr_poly_; + bool use_namespace_, use_hlr_poly_, always_project_; IfcParse::IfcFile* file; IfcUtil::IfcBaseEntity* storey_; std::multimap paths; std::map drawing_metadata; + std::map storey_hlr; float_item_list xcoords, ycoords, radii; size_t xcoords_begin, ycoords_begin, radii_begin; @@ -142,10 +149,14 @@ protected: std::list element_buffer_; - Handle(HLRBRep_Algo) hlr_brep; - Handle(HLRBRep_PolyAlgo) hlr_poly; + hlr_t hlr; + // Handle(HLRBRep_Algo) hlr_brep; + // Handle(HLRBRep_PolyAlgo) hlr_poly; std::string namespace_prefix_; + + void draw_hlr(const gp_Pln& pln, const drawing_key& drawing_name); + public: SvgSerializer(const std::string& out_filename, const SerializerSettings& settings) : GeometrySerializer(settings) @@ -164,6 +175,7 @@ public: , auto_elevation_(false) , use_namespace_(false) , use_hlr_poly_(false) + , always_project_(false) , file(0) , storey_(0) , xcoords_begin(0) @@ -221,6 +233,10 @@ public: use_hlr_poly_ = b; } + void setAlwaysProject(bool b) { + always_project_ = b; + } + void setScale(double s) { scale_ = s; } void setDrawingCenter(double x, double y) { center_x_ = x; center_y_ = y; diff --git a/win/build-deps.cmd b/win/build-deps.cmd index 722d140d8e..ad93b22238 100644 --- a/win/build-deps.cmd +++ b/win/build-deps.cmd @@ -55,6 +55,18 @@ if not %ERRORLEVEL%==0 ( :: Set up variables depending on the used Visual Studio version call vs-cfg.cmd %1 IF NOT %ERRORLEVEL%==0 GOTO :Error + +:: Set up the BuildDepsCache.txt filename +IF DEFINED VS_TOOLSET ( + set BUILDDEPTHCACHE=BuildDepsCache-%VS_PLATFORM%-%VS_TOOLSET%.txt +) ELSE ( + set BUILDDEPTHCACHE=BuildDepsCache-%VS_PLATFORM%.txt +) + +:: fix for Visual C++ hanging when compiling 32-bit release OCCT up to version 7.4.0 +:: see https://tracker.dev.opencascade.org/view.php?id=31628 +SET COMPILE_WITH_WPO=FALSE + call build-type-cfg.cmd %2 IF NOT %ERRORLEVEL%==0 GOTO :Error @@ -111,6 +123,10 @@ call cecho.cmd 0 13 "* CMake Generator`t= '`"%GENERATOR%`'`t echo - Passed to CMake -G option. call cecho.cmd 0 13 "* Target Architecture`t= %TARGET_ARCH%" echo - Whether were doing 32-bit (x86) or 64-bit (x64) build. +call cecho.cmd 0 13 "* Target Platform`t= %VS_PLATFORM%" +echo - Passed to CMake -A option. +call cecho.cmd 0 13 "* Target Toolset`t= %VS_TOOLSET%" +echo - Passed to CMake -T option. call cecho.cmd 0 13 "* Dependency Directory`t= %DEPS_DIR%" echo - The directory where %PROJECT_NAME% dependencies are fetched and built. call cecho.cmd 0 13 "* Installation Directory = %INSTALL_DIR%" @@ -146,7 +162,7 @@ set /p do_continue="> " if "%do_continue%"=="n" goto :Finish :: Cache last used CMake generator for other scripts to use -if defined GEN_SHORTHAND echo GEN_SHORTHAND=%GEN_SHORTHAND%>"%~dp0\BuildDepsCache-%TARGET_ARCH%.txt" +if defined GEN_SHORTHAND echo GEN_SHORTHAND=%GEN_SHORTHAND%>"%~dp0\%BUILDDEPTHCACHE%" echo. set START_TIME=%TIME% @@ -160,18 +176,18 @@ cd "%DEPS_DIR%" :: by modifying this file and using goto. :Boost :: NOTE Boost < 1.64 doesn't work without tricks if the user has only VS 2017 installed and no earlier versions. -set BOOST_VERSION=1.67.0 +set BOOST_VERSION=1.74.0 :: Version string with underscores instead of dots. set BOOST_VER=%BOOST_VERSION:.=_% :: DEPENDENCY_NAME is used for logging and DEPENDENCY_DIR for saving from some redundant typing set DEPENDENCY_NAME=Boost %BOOST_VERSION% set DEPENDENCY_DIR=%DEPS_DIR%\boost_%BOOST_VER% -set BOOST_LIBRARYDIR=%DEPENDENCY_DIR%\stage\%VS_PLATFORM%\lib +set BOOST_LIBRARYDIR=%DEPENDENCY_DIR%\stage\%GEN_SHORTHAND%\lib :: NOTE Also zip download exists, if encountering problems with 7z for some reason. set ZIP_EXT=7z set BOOST_ZIP=boost_%BOOST_VER%.%ZIP_EXT% -call :DownloadFile http://121.36.151.68:9008/download/boost/1.67.0/boost_1_67_0.7z "%DEPS_DIR%" %BOOST_ZIP% +call :DownloadFile http://121.36.151.68:9008/download/boost/1.74.0/boost_1_74_0.7z "%DEPS_DIR%" %BOOST_ZIP% IF NOT %ERRORLEVEL%==0 GOTO :Error call :ExtractArchive %BOOST_ZIP% "%DEPS_DIR%" "%DEPENDENCY_DIR%" @@ -183,7 +199,7 @@ if not exist "%DEPENDENCY_DIR%\project-config.jam". ( IF NOT EXIST "%DEPENDENCY_DIR%\boost.css" GOTO :Error cd "%DEPENDENCY_DIR%" call cecho.cmd 0 13 "Building Boost build script." - call bootstrap msvc + call bootstrap %BOOST_BOOTSTRAP_VER% IF NOT %ERRORLEVEL%==0 GOTO :Error ) @@ -192,14 +208,10 @@ set BOOST_LIBS=--with-system --with-regex --with-thread --with-program_options - cd "%DEPENDENCY_DIR%" call cecho.cmd 0 13 "Building %DEPENDENCY_NAME% %BOOST_LIBS% Please be patient, this will take a while." IF EXIST "%DEPENDENCY_DIR%\bin.v2\project-cache.jam" del "%DEPS_DIR%\boost\bin.v2\project-cache.jam" -:: BOOST_VC_VER can be empty (or needs to be) for newer VS versions -set BOOST_VC_VER= -if %VS_VER% LSS 2017 ( - set BOOST_VC_VER=-%VC_VER%.0 -) -call .\b2 toolset=msvc%BOOST_VC_VER% runtime-link=static address-model=%ARCH_BITS% -j%IFCOS_NUM_BUILD_PROCS% ^ - variant=%DEBUG_OR_RELEASE_LOWERCASE% %BOOST_LIBS% stage --stagedir=stage/vs%VS_VER%-%VS_PLATFORM% +call .\b2 toolset=%BOOST_TOOLSET% runtime-link=static address-model=%ARCH_BITS% -j%IFCOS_NUM_BUILD_PROCS% ^ + variant=%DEBUG_OR_RELEASE_LOWERCASE% %BOOST_WIN_API% %BOOST_LIBS% stage --stagedir=stage/%GEN_SHORTHAND% + IF NOT %ERRORLEVEL%==0 GOTO :Error :JSON @@ -237,10 +249,10 @@ if %IFCOS_USE_OCCT%==FALSE goto :OCE set OCCT_VERSION=7.3.0p3 SET OCCT_VER=V%OCCT_VERSION:.=_% -set OCC_INCLUDE_DIR=%INSTALL_DIR%\opencascade-%OCCT_VERSION%\inc>>"%~dp0\BuildDepsCache-%TARGET_ARCH%.txt" -set OCC_LIBRARY_DIR=%INSTALL_DIR%\opencascade-%OCCT_VERSION%\win%ARCH_BITS%\lib>>"%~dp0\BuildDepsCache-%TARGET_ARCH%.txt" -echo OCC_INCLUDE_DIR=%OCC_INCLUDE_DIR%>>"%~dp0\BuildDepsCache-%TARGET_ARCH%.txt" -echo OCC_LIBRARY_DIR=%OCC_LIBRARY_DIR%>>"%~dp0\BuildDepsCache-%TARGET_ARCH%.txt" +set OCC_INCLUDE_DIR=%INSTALL_DIR%\opencascade-%OCCT_VERSION%\inc>>"%~dp0\%BUILDDEPTHCACHE%" +set OCC_LIBRARY_DIR=%INSTALL_DIR%\opencascade-%OCCT_VERSION%\win%ARCH_BITS%\lib>>"%~dp0\%BUILDDEPTHCACHE%" +echo OCC_INCLUDE_DIR=%OCC_INCLUDE_DIR%>>"%~dp0\%BUILDDEPTHCACHE%" +echo OCC_LIBRARY_DIR=%OCC_LIBRARY_DIR%>>"%~dp0\%BUILDDEPTHCACHE%" :: OCCT has many dependencies but FreeType is the only mandatory set DEPENDENCY_NAME=FreeType @@ -280,11 +292,22 @@ cd "%DEPENDENCY_DIR%" call :RunCMake -DINSTALL_DIR="%INSTALL_DIR%\opencascade-%OCCT_VERSION%" -DBUILD_LIBRARY_TYPE="Static" -DCMAKE_DEBUG_POSTFIX=d ^ -DBUILD_MODULE_Draw=0 -D3RDPARTY_FREETYPE_DIR="%INSTALL_DIR%\freetype" if not %ERRORLEVEL%==0 goto :Error + +:: whole program optimization avoids Visual C++ hanging when compiling 32-bit release OCCT up to version 7.4.0 +IF %ARCH_BITS%==32 ( + IF %BUILD_CFG%==Release ( + SET COMPILE_WITH_WPO=TRUE + ) +) + call :BuildSolution "%DEPENDENCY_DIR%\%BUILD_DIR%\OCCT.sln" %BUILD_CFG% if not %ERRORLEVEL%==0 goto :Error call :InstallCMakeProject "%DEPENDENCY_DIR%\%BUILD_DIR%" %BUILD_CFG% if not %ERRORLEVEL%==0 goto :Error -:: Use a single lib directory for for release and debug libraries as is done with OCE + +SET COMPILE_WITH_WPO=FALSE + +:: Use a single lib directory for release and debug libraries as is done with OCE if not exist "%OCC_LIBRARY_DIR%". mkdir "%OCC_LIBRARY_DIR%" :: NOTE OCCT (at least occt-V7_0_0-9059ca1) directory creation code is hardcoded and doesn't seem handle future VC versions set OCCT_VC_VER=%VC_VER% @@ -303,10 +326,10 @@ del "%INSTALL_DIR%\opencascade-%OCCT_VERSION%\*.bat" goto :Python :OCE -set OCC_INCLUDE_DIR=%INSTALL_DIR%\oce\include\oce>>"%~dp0\BuildDepsCache-%TARGET_ARCH%.txt" -set OCC_LIBRARY_DIR=%INSTALL_DIR%\oce\Win%ARCH_BITS%\lib>>"%~dp0\BuildDepsCache-%TARGET_ARCH%.txt" -echo OCC_INCLUDE_DIR=%OCC_INCLUDE_DIR%>>"%~dp0\BuildDepsCache-%TARGET_ARCH%.txt" -echo OCC_LIBRARY_DIR=%OCC_LIBRARY_DIR%>>"%~dp0\BuildDepsCache-%TARGET_ARCH%.txt" +set OCC_INCLUDE_DIR=%INSTALL_DIR%\oce\include\oce>>"%~dp0\%BUILDDEPTHCACHE%" +set OCC_LIBRARY_DIR=%INSTALL_DIR%\oce\Win%ARCH_BITS%\lib>>"%~dp0\%BUILDDEPTHCACHE%" +echo OCC_INCLUDE_DIR=%OCC_INCLUDE_DIR%>>"%~dp0\%BUILDDEPTHCACHE%" +echo OCC_LIBRARY_DIR=%OCC_LIBRARY_DIR%>>"%~dp0\%BUILDDEPTHCACHE%" set DEPENDENCY_NAME=Open CASCADE Community Edition set DEPENDENCY_DIR=%DEPS_DIR%\oce @@ -346,8 +369,8 @@ set PYTHON_INSTALLER=python-%PYTHON_VERSION%%PYTHON_AMD64_POSTFIX%.msi :: NOTE/TODO 3.5.0 doesn't use MSI any longer, but exe: set PYTHON_INSTALLER=python-%PYTHON_VERSION%%PYTHON_AMD64_POSTFIX%.exe IF "%IFCOS_INSTALL_PYTHON%"=="TRUE" ( REM Store Python versions to BuildDepsCache.txt for run-cmake.bat - echo PY_VER_MAJOR_MINOR=%PY_VER_MAJOR_MINOR%>>"%~dp0\BuildDepsCache-%TARGET_ARCH%.txt" - echo PYTHONHOME=%PYTHONHOME%>>"%~dp0\BuildDepsCache-%TARGET_ARCH%.txt" + echo PY_VER_MAJOR_MINOR=%PY_VER_MAJOR_MINOR%>>"%~dp0\%BUILDDEPTHCACHE%" + echo PYTHONHOME=%PYTHONHOME%>>"%~dp0\%BUILDDEPTHCACHE%" cd "%DEPS_DIR%" call :DownloadFile http://121.36.151.68:9008/download/python/3.4.3/python-3.4.3.amd64.msi "%DEPS_DIR%" %PYTHON_INSTALLER% @@ -502,7 +525,13 @@ pushd %BUILD_DIR% :: TODO make deleting cache a parameter for this subroutine? We probably want to delete the :: cache always e.g. when we've had new changes in the repository. IF %BUILD_TYPE%==Rebuild IF EXIST CMakeCache.txt. del CMakeCache.txt -cmake .. -G %GENERATOR% %* + +IF DEFINED VS_TOOLSET ( + cmake .. -G %GENERATOR% -A %VS_PLATFORM% -T %VS_TOOLSET% %* +) ELSE ( + cmake .. -G %GENERATOR% -A %VS_PLATFORM% %* +) + set RET=%ERRORLEVEL% popd exit /b %RET% @@ -513,7 +542,13 @@ exit /b %RET% :: Params: %1 solutioName, %2 configuration :BuildSolution call cecho.cmd 0 13 "Building %2 %DEPENDENCY_NAME%. Please be patient, this will take a while." -%MSBUILD_CMD% %1 /p:configuration=%2;platform=%VS_PLATFORM% + +:: whole program optimization avoids Visual C++ hanging when compiling 32-bit release OCCT up to version 7.4.0 +IF %COMPILE_WITH_WPO%==FALSE ( + %MSBUILD_CMD% %1 /p:configuration=%2;platform=%VS_PLATFORM% +) ELSE ( + %MSBUILD_CMD% %1 /p:configuration=%2;platform=%VS_PLATFORM%;WholeProgramOptimization=TRUE +) exit /b %ERRORLEVEL% :: InstallCMakeProject - Builds the INSTALL project of CMake-based project @@ -523,7 +558,13 @@ exit /b %ERRORLEVEL% :InstallCMakeProject pushd %1 call cecho.cmd 0 13 "Installing %2 %DEPENDENCY_NAME%. Please be patient, this will take a while." -%INSTALL_CMD% INSTALL.%VCPROJ_FILE_EXT% /p:configuration=%2;platform=%VS_PLATFORM% + +:: whole program optimization avoids Visual C++ hanging when compiling 32-bit release OCCT up to version 7.4.0 +IF %COMPILE_WITH_WPO%==FALSE ( + %INSTALL_CMD% INSTALL.%VCPROJ_FILE_EXT% /p:configuration=%2;platform=%VS_PLATFORM% +) ELSE ( + %INSTALL_CMD% INSTALL.%VCPROJ_FILE_EXT% /p:configuration=%2;platform=%VS_PLATFORM%;WholeProgramOptimization=TRUE +) set RET=%ERRORLEVEL% popd exit /b %RET% @@ -537,7 +578,7 @@ echo 2. Install Git and make sure 'git' is accessible from PATH. echo - https://git-for-windows.github.io/ echo 3. Install CMake and make sure 'cmake' is accessible from PATH. echo - http://www.cmake.org/ -echo 4. Visual Studio 2008 or newer (2013 or newer recommended) with C++ toolset. +echo 4. Visual Studio 2013 or newer with C++ toolset. echo - https://www.visualstudio.com/ echo 5. Run this batch script with Visual Studio environment variables set. echo - https://msdn.microsoft.com/en-us/library/ms229859(v=vs.110).aspx diff --git a/win/readme.md b/win/readme.md index e8e4c5107a..31ada3fefb 100644 --- a/win/readme.md +++ b/win/readme.md @@ -18,36 +18,36 @@ of the shell scripts before using them. Note that contrary to MSVC, with MSYS al built or used as static libraries. Currently Release build is used for all libraries. ### MSVC -Execute `build-deps.cmd` to fetch, build and install the dependencies. The batch file will print the requirements for -a successful execution. The script allows a few user-configurable build options which are listed in the usage -instructions. Either edit the script file or set these values before running the script. +Launch the proper Visual Studio command prompt, cd to the 'win' directory inside the IfcOpenShell directory and execute `build-deps.cmd` to fetch, build and install the dependencies. The batch file will print the requirements for a successful execution. The script allows a few user-configurable build options which are listed below. -`build-deps.cmd` expects a CMake generator as `%1` and a build configuration type (`RelWithDebInfo`, `Release`, -`MinSizeRel`, or `Debug`, defaults to `RelWithDebInfo`) as `%2`. If the generator is not provided, the generator is -deduced from the MSVC environment variables. User-friendly VS generator shorthands are supported, e.g. -`vs2013-x86` or `vs2015-x64`, and these are converted to the appropriate CMake ones by the scripts. A build type -(`Build`, `Rebuild`, or `Clean`, defaults to `Build`) can be provided as `%3`. See `vs-cfg.cmd` if you wish to change -the defaults. The batch file will create `deps\` and `deps-vs--installed\` directories to the -project root. Debug and release builds of the dependencies can co-exist by simply running +`build-deps.cmd` expects a CMake generator as `%1` and a build configuration type (`RelWithDebInfo`, `Release`, `MinSizeRel`, or `Debug`, defaults to `RelWithDebInfo`) as `%2`. If the generator is not provided, the generator is deduced from the MSVC environment variables. + +User-friendly CMake Visual Studio generator shorthands are supported. They are converted to the appropriate CMake generators and options. Shorthands are indeed the preferable way to specify the generator, since they allow a more accurate platform and toolset configuration. Here are some examples: +``` +"vs2013" => cmake -G "Visual Studio 12 2013" -A Win32 +"vs2013-x86" => cmake -G "Visual Studio 12 2013" -A Win32 +"vs2015-x64" => cmake -G "Visual Studio 14 2015" -A x64 +"vs2017-ARM64" => cmake -G "Visual Studio 15 2017" -A ARM64 +"vs2019-x86-v141_xp" => cmake -G "Visual Studio 16 2019" -A Win32 -T v141_xp +``` +Of course not all Visual C++ compilers support any platform or toolset, refer to the Visual Studio and CMake documentation for this. If you do not specify a toolset, the compiler will use the default toolset for the version, i.e. vs2019 will use the v142 toolset. + +A build type (`Build`, `Rebuild`, or `Clean`, defaults to `Build`) can be provided as `%3`. + +See `vs-cfg.cmd` if you wish to change the defaults. The batch file will create `deps\` and `deps-vs-[-]-installed\` directories to the project root. Debug and release builds of the dependencies can co-exist by simply running: ``` > build-deps.cmd Debug > build-deps.cmd ``` -After the dependencies are build, execute `run-cmake.bat`. The batch file expects a CMake generator as `%1` and the -rest of possible parameters are passed as is. If a generator is not provided, the generator is read from the -BuildDepsCache file, or tried to be deduced from the location of `cl.exe`. If passing build options for the script, -the generator must be always passed as the first option: +After the dependencies are build, execute `run-cmake.bat`. The batch file expects a CMake generator as `%1`, that is interpreted just like the `build-deps.cmd` script, and the rest of possible parameters are passed as is. If a generator is not provided, the generator is read from the BuildDepsCache file, or tried to be deduced from the location of `cl.exe`. If passing build options for the script, the generator must be always passed as the first option: ``` > run-cmake.bat vs2015-x64 -DUSE_IFC4=1 -DBUILD_IFCPYTHON=0 ``` -**If you wish to use any library from a custom location, modify the paths in `run-cmake.bat` accordingly**. The batch -script will create a folder of form `build-vs-\` which will contain the solution and project -files for MSVC. +**If you wish to use any library from a custom location, modify the paths in `run-cmake.bat` accordingly**. The batch script will create a folder of form `build-vs-[-]\` which will contain the solution and project files for MSVC. -Note that building IfcOpenShell as 64-bit is recommended as many of real life IFC files has been observed to take -easily more than 2 GBs of RAM while converting. +Note that building IfcOpenShell as 64-bit is recommended as many of real life IFC files has been observed to take easily more than 2 GBs of RAM while converting. After this, one can build the project using the `IfcOpenShell.sln` file in the build folder. Build the `INSTALL` project if wanted. Convenience batch files `build-ifcopenshell.bat` and `install-ifcopenshell.bat` can also be used. The batch diff --git a/win/run-cmake.bat b/win/run-cmake.bat index 8154b3a4bc..ebf501cf97 100755 --- a/win/run-cmake.bat +++ b/win/run-cmake.bat @@ -52,18 +52,18 @@ if not (%1)==() ( ) pushd .. -set CMAKE_INSTALL_PREFIX=%CD%\installed-vs%VS_VER%-%TARGET_ARCH% +set CMAKE_INSTALL_PREFIX=%CD%\installed-%GEN_SHORTHAND% popd IF NOT EXIST ..\%BUILD_DIR%. mkdir ..\%BUILD_DIR% pushd ..\%BUILD_DIR% :: tfk: todo remove duplication -set BOOST_VERSION=1.67.0 +set BOOST_VERSION=1.74.0 set BOOST_VER=%BOOST_VERSION:.=_% set BOOST_ROOT=%DEPS_DIR%\boost_%BOOST_VER% -set BOOST_LIBRARYDIR=%BOOST_ROOT%\stage\vs%VS_VER%-%VS_PLATFORM%\lib +set BOOST_LIBRARYDIR=%BOOST_ROOT%\stage\%GEN_SHORTHAND%\lib if not defined OCC_INCLUDE_DIR set OCC_INCLUDE_DIR=%INSTALL_DIR%\oce\include\oce if not defined OCC_LIBRARY_DIR set OCC_LIBRARY_DIR=%INSTALL_DIR%\oce\Win%ARCH_BITS%\lib set OPENCOLLADA_INCLUDE_DIR=%INSTALL_DIR%\OpenCOLLADA\include\opencollada @@ -81,8 +81,10 @@ set JSON_INCLUDE_DIR=%INSTALL_DIR%\json echo. call cecho.cmd 0 10 "Script configuration:" -echo Generator = %GENERATOR% -echo Arguments = %ARGUMENTS% +echo Generator = %GENERATOR% +echo Architecture = %VS_PLATFORM% +echo Toolset = %VS_TOOLSET% +echo Arguments = %ARGUMENTS% echo. call cecho.cmd 0 10 "Dependency Environment Variables for %PROJECT_NAME%:" echo BOOST_ROOT = %BOOST_ROOT% @@ -107,7 +109,13 @@ set CMAKELISTS_DIR=..\cmake :: Delete CMakeCache.txt if command-line options were provided for this batch script. if not (%1)==() if exist CMakeCache.txt. del /Q CMakeCache.txt call cecho.cmd 0 13 "Running CMake for %PROJECT_NAME%." -cmake.exe %CMAKELISTS_DIR% -G %GENERATOR% -DCMAKE_INSTALL_PREFIX="%CMAKE_INSTALL_PREFIX%" %ARGUMENTS% + +IF DEFINED VS_TOOLSET ( + cmake.exe %CMAKELISTS_DIR% -G %GENERATOR% -A %VS_PLATFORM% -T %VS_TOOLSET% -DCMAKE_INSTALL_PREFIX="%CMAKE_INSTALL_PREFIX%" %ARGUMENTS% +) ELSE ( + cmake.exe %CMAKELISTS_DIR% -G %GENERATOR% -A %VS_PLATFORM% -DCMAKE_INSTALL_PREFIX="%CMAKE_INSTALL_PREFIX%" %ARGUMENTS% +) + IF NOT %ERRORLEVEL%==0 GOTO :Error echo. diff --git a/win/vs-cfg.cmd b/win/vs-cfg.cmd index 992de2ef5a..0f431c6c89 100644 --- a/win/vs-cfg.cmd +++ b/win/vs-cfg.cmd @@ -17,15 +17,23 @@ :: :: ::::::::::::::::::::::::::::::::::::::::::::::::::::::: :::::::::::::::::::::::::: -:: This script initializes various Visual Studio related environment variables needed for building. -:: the dependencies. This batch file expects a CMake generator as %1. If %1 is not provided, it is -:: deduced from the VisualStudioVersion environment variable and from the location of cl.exe. -:: User-friendly VS generators are allowed (e.g. "vs2013-x86") and converted to the appropriate CMake ones. - -:: NOTE This batch file expects the generator string to be CMake 3.0.0 and newer format, i.e. -:: "Visual Studio 10 2010" instead of "Visual Studio 10". However, one can use this batch file -:: also with CMake 2 as the generator will be converted into the older format if necessary. - +:: This script initializes various Visual Studio related environment variables. +:: +:: It expects a CMake generator as %1. If %1 is not provided, it is deduced from +:: the VisualStudioVersion environment variable and from the location of cl.exe. +:: +:: The generator string must be in the CMake 3.0 and newer format, i.e. +:: "Visual Studio 12 2013" instead of "Visual Studio 12" +:: +:: User-friendly VS generators are preferred, since they permit a more accurate +:: platform and toolset configuration. Some examples: +:: +:: "vs2013" => cmake -G "Visual Studio 12 2013" -A Win32 +:: "vs2013-x86" => cmake -G "Visual Studio 12 2013" -A Win32 +:: "vs2015-x64" => cmake -G "Visual Studio 14 2015" -A x64 +:: "vs2017-ARM64" => cmake -G "Visual Studio 15 2017" -A ARM64 +:: "vs2019-x86-v141_xp" => cmake -G "Visual Studio 16 2019" -A Win32 -T v141_xp +:: :: NOTE: The delayed environment variable expansion needs to be enabled before calling this. @echo off @@ -33,34 +41,42 @@ set GENERATOR=%1 :: Supported Visual Studio versions: -set GENERATORS[0]="Visual Studio 9 2008 Win64" -set GENERATORS[1]="Visual Studio 9 2008" -set GENERATORS[2]="Visual Studio 10 2010 Win64" -set GENERATORS[3]="Visual Studio 10 2010" -set GENERATORS[4]="Visual Studio 11 2012 Win64" -set GENERATORS[5]="Visual Studio 11 2012" -set GENERATORS[6]="Visual Studio 12 2013 Win64" -set GENERATORS[7]="Visual Studio 12 2013" -set GENERATORS[8]="Visual Studio 14 2015 Win64" -set GENERATORS[9]="Visual Studio 14 2015" -:: NOTE VC version for VS 2017 is not 15 but 14.1: have to wait and see -:: if CMake generator string is updated to reflect this. -set GENERATORS[10]="Visual Studio 15 2017 Win64" -set GENERATORS[11]="Visual Studio 15 2017" -set LAST_GENERATOR_IDX=11 +set GENERATORS[1]="Visual Studio 12 2013" +set GENERATORS[2]="Visual Studio 14 2015" +set GENERATORS[3]="Visual Studio 15 2017" +set GENERATORS[4]="Visual Studio 16 2019" +set LAST_GENERATOR_IDX=4 -set STEP=2 :: Is generator shorthand used? set GEN_SHORTHAND=!GENERATOR:vs=! + if not "!GEN_SHORTHAND!"=="" if !GEN_SHORTHAND!==!GENERATOR! goto :GeneratorShorthandCheckDone -set START=%LAST_GENERATOR_IDX% + +set "VS_PLATFORM=Win32" +:: use the command prompt target platform, at least initially +if %VSCMD_ARG_TGT_ARCH%==x86 set "VS_PLATFORM=Win32" +if %VSCMD_ARG_TGT_ARCH%==x64 set "VS_PLATFORM=x64" +if %VSCMD_ARG_TGT_ARCH%==arm set "VS_PLATFORM=ARM" +if %VSCMD_ARG_TGT_ARCH%==arm64 set "VS_PLATFORM=ARM64" + :: "echo if" trick from http://stackoverflow.com/a/8758579 -echo(!GEN_SHORTHAND! | findstr /c:"-x86" >nul && ( set START=1 ) -echo(!GEN_SHORTHAND! | findstr /c:"-x64" >nul && ( set START=0 ) -set VS_VER=!GEN_SHORTHAND:-x86=! -set VS_VER=!VS_VER:-x64=! +echo(!GEN_SHORTHAND! | findstr /c:"-x86" >nul && ( set "VS_PLATFORM=Win32" ) +echo(!GEN_SHORTHAND! | findstr /c:"-x64" >nul && ( set "VS_PLATFORM=x64" ) +echo(!GEN_SHORTHAND! | findstr /c:"-ARM" >nul && ( set "VS_PLATFORM=ARM" ) +echo(!GEN_SHORTHAND! | findstr /c:"-ARM64" >nul && ( set "VS_PLATFORM=ARM64" ) + +echo(!GEN_SHORTHAND! | findstr /c:"-v120" >nul && ( set "VS_TOOLSET=v120" ) && ( set "BOOST_TOOLSET=12.0" ) +echo(!GEN_SHORTHAND! | findstr /c:"-v120_xp" >nul && ( set "VS_TOOLSET=v120_xp" ) && ( set "BOOST_TOOLSET=12.0" ) +echo(!GEN_SHORTHAND! | findstr /c:"-v140" >nul && ( set "VS_TOOLSET=v140" ) && ( set "BOOST_TOOLSET=14.0" ) +echo(!GEN_SHORTHAND! | findstr /c:"-v140_xp" >nul && ( set "VS_TOOLSET=v140_xp" ) && ( set "BOOST_TOOLSET=14.0" ) +echo(!GEN_SHORTHAND! | findstr /c:"-v141" >nul && ( set "VS_TOOLSET=v141" ) && ( set "BOOST_TOOLSET=14.1" ) +echo(!GEN_SHORTHAND! | findstr /c:"-v141_xp" >nul && ( set "VS_TOOLSET=v141_xp" ) && ( set "BOOST_TOOLSET=14.1" ) +echo(!GEN_SHORTHAND! | findstr /c:"-v142" >nul && ( set "VS_TOOLSET=v142" ) && ( set "BOOST_TOOLSET=14.2" ) + +SET VS_VER=%GEN_SHORTHAND:~0,4% + echo(!GENERATOR! | findstr /c:"vs20" >nul && ( - for /l %%i in (!START!,!STEP!,%LAST_GENERATOR_IDX%) do ( + for /L %%i in (0,1,%LAST_GENERATOR_IDX%) do ( echo(!GENERATORS[%%i]! | findstr /c:"!VS_VER!" >nul && ( set GENERATOR=!GENERATORS[%%i]! goto :GeneratorShorthandCheckDone @@ -75,18 +91,20 @@ echo(!GENERATOR! | findstr /c:"vs20" >nul && ( where cl.exe | findstr "amd64 x64" >nul set START=%ERRORLEVEL% +:: NOTE add space before VC_VER so that e.g. "12" doesn't match with "2012" IF "!GENERATOR!"=="" IF NOT "%VisualStudioVersion%"=="" ( set VC_VER=%VisualStudioVersion:.0=% - FOR /l %%i in (%START%,%STEP%,%LAST_GENERATOR_IDX%) DO ( - REM NOTE add space before VC_VER so that e.g. "12" doesn't match with "2012" + FOR /L %%i in (%START%,1,%LAST_GENERATOR_IDX%) DO ( echo(!GENERATORS[%%i]! | findstr /c:" !VC_VER!" >nul && ( set GENERATOR=!GENERATORS[%%i]! call utils\cecho.cmd black cyan "Generator not passed, but VisualStudioVersion=%VisualStudioVersion% environment variable detected:" call utils\cecho.cmd black cyan "using '`"!GENERATOR!`'" as the generator." + SET VS_VER=!GENERATOR:~-5,4! GOTO :GeneratorValid ) ) ) + :: Check that the used CMake version supports the chosen generator set GENERATOR_CHECK=%GENERATOR: Win64=% cmake --help | findstr /c:%GENERATOR_CHECK% >nul @@ -95,21 +113,23 @@ call utils\cecho.cmd 0 12 "%~nx0: The used CMake version does not support '`"!GE exit /b 1 ) -FOR /l %%i in (0,1,%LAST_GENERATOR_IDX%) DO ( +FOR /L %%i in (0,1,%LAST_GENERATOR_IDX%) DO ( IF !GENERATOR!==!GENERATORS[%%i]! GOTO :GeneratorValid ) call utils\cecho.cmd 0 12 "%~nx0: Invalid or unsupported CMake generator string passed: '`"!GENERATOR!`'"- cannot proceed." echo Supported CMake generator strings: -FOR /l %%i in (0,1,%LAST_GENERATOR_IDX%) DO ( +FOR /L %%i in (0,1,%LAST_GENERATOR_IDX%) DO ( echo !GENERATORS[%%i]! ) exit /b 1 :GeneratorValid -:: Figure out the build configuration from the CMake generator string. + +IF DEFINED VS_PLATFORM goto :PlatformDefined + +:: If we do not have defined a platform yet, +:: figure out the build configuration from the CMake generator string. :: Are we building 32-bit or 64-bit version. -set ARCH_BITS=32 -set TARGET_ARCH=x86 :: Visual Studio platform name, Win32 (i.e. x86) or x64. set VS_PLATFORM=Win32 @@ -122,30 +142,88 @@ FOR %%i IN (%GENERATOR_SPLIT%) DO ( IF !LEN!==1 set VC_VER=%%i IF !LEN!==2 set VC_VER=%%i IF !LEN!==4 set VS_VER=%%i - REM Are going to perform a 64-bit build? + :: Are going to perform a 64-bit build? IF %%i==Win64 ( - set ARCH_BITS=64 - set TARGET_ARCH=x64 set VS_PLATFORM=x64 ) ) +:PlatformDefined + +:: determine the Visual C++ default version +IF %VS_VER%==2013 ( set "VC_VER=12.0" ) +IF %VS_VER%==2015 ( set "VC_VER=14.0" ) +IF %VS_VER%==2017 ( set "VC_VER=14.1" ) +IF %VS_VER%==2019 ( set "VC_VER=14.2" ) + +:: determine the argument for Boost bootstrap +set BOOST_BOOTSTRAP_VER=vc%VC_VER% +set BOOST_BOOTSTRAP_VER=%BOOST_BOOTSTRAP_VER:.=% + +:: determine the toolset and winapi for Boost b2 +IF DEFINED VS_TOOLSET ( + set BOOST_TOOLSET=msvc-%BOOST_TOOLSET% + if "!VS_TOOLSET:~-3!"=="_xp" ( + set BOOST_WIN_API=define=BOOST_USE_WINAPI_VERSION=0x0501 + ) +) ELSE ( + set BOOST_TOOLSET=msvc-%VC_VER% + set BOOST_WIN_API= +) + +IF %VS_PLATFORM%==Win32 ( + set ARCH_BITS=32 + set TARGET_ARCH=x86 +) + +IF %VS_PLATFORM%==x64 ( + set ARCH_BITS=64 + set TARGET_ARCH=x64 +) + +IF %VS_PLATFORM%==ARM ( + set ARCH_BITS=32 + set TARGET_ARCH=ARM +) + +IF %VS_PLATFORM%==ARM64 ( + set ARCH_BITS=64 + set TARGET_ARCH=ARM64 +) + :: Check CMake version and convert possible new format (>= 3.0) generator names to the old versions if using older CMake for VS <= 2013, :: see http://www.cmake.org/cmake/help/v3.0/release/3.0.0.html#other-changes FOR /f "delims=" %%i in ('where cmake') DO set CMAKE_PATH=%%i IF NOT "%CMAKE_PATH%"=="" ( FOR /f "delims=" %%i in ('cmake --version ^| findstr /C:"cmake version 3"') DO GOTO :CMake3AndNewer ) -:: CMake older than 3.0.0: convert new format generators to the old format (simple brute force for simplicity) -set GENERATOR=%GENERATOR: 2013=% -set GENERATOR=%GENERATOR: 2012=% -set GENERATOR=%GENERATOR: 2010=% + +:: reject older CMake, see also build-deps.cmd +echo "CMake v3.11.4 or higher is required" +exit /b 1 + :CMake3AndNewer -set GEN_SHORTHAND=vs%VS_VER%-%TARGET_ARCH% + :: check variables for debugging + echo GENERATOR: [!GENERATOR!] + echo VS_VER: [!VS_VER!] + echo VS_PLATFORM: [!VS_PLATFORM!] + echo VS_TOOLSET: [!VS_TOOLSET!] + echo VC_VER: [!VC_VER!] + echo ARCH_BITS: [!ARCH_BITS!] + echo TARGET_ARCH: [!TARGET_ARCH!] + echo BOOST_BOOTSTRAP_VER: [!BOOST_BOOTSTRAP_VER!] + echo BOOST_TOOLSET: [!BOOST_TOOLSET!] + echo BOOST_WIN_API: [!BOOST_WIN_API!] + +IF DEFINED VS_TOOLSET ( + set GEN_SHORTHAND=vs%VS_VER%-%VS_PLATFORM%-%VS_TOOLSET% +) ELSE ( + set GEN_SHORTHAND=vs%VS_VER%-%VS_PLATFORM% +) + :: VS project file extension is different on older VS versions set VCPROJ_FILE_EXT=vcxproj -IF %VS_VER%==2008 set VCPROJ_FILE_EXT=vcproj :: Add utils to PATH set ORIGINAL_PATH=%PATH% @@ -156,11 +234,11 @@ set PATH=%~dp0utils;%PATH% :: so no need for -%VS_VER%-%TARGET_ARCH% postfix. :: set DEPS_DIR=%CD%\deps-%VS_VER%-%TARGET_ARCH% pushd .. -set DEPS_DIR=%CD%\deps -set INSTALL_DIR=%CD%\deps-%GEN_SHORTHAND%-installed -REM set INSTALL_DIR=%CD%\deps-vs%VS_VER%-%TARGET_ARCH%-%DEBUG_OR_RELEASE_LOWERCASE%-installed +set DEPS_DIR=%CD%\_deps +set INSTALL_DIR=%CD%\_deps-%GEN_SHORTHAND%-installed +:: set INSTALL_DIR=%CD%\deps-vs%VS_VER%-%TARGET_ARCH%-%DEBUG_OR_RELEASE_LOWERCASE%-installed :: BUILD_DIR is a relative build directory used for CMake-based projects -set BUILD_DIR=build-%GEN_SHORTHAND% +set BUILD_DIR=_build-%GEN_SHORTHAND% popd GOTO :EOF