merge update

This commit is contained in:
admin
2021-01-28 13:56:47 +08:00
parent 371e4137f1
commit 33ec80e54d
151 changed files with 8173 additions and 7954 deletions
Binary file not shown.
+632
View File
@@ -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 <http://www.gnu.org/licenses/>. *
* *
********************************************************************************/
#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/xml_parser.hpp>
#include <boost/version.hpp>
#include <boost/foreach.hpp>
#include "XmlSerializer.h"
#include <algorithm>
#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<std::string, std::string> 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<std::string> format_attribute(const Argument* argument, IfcUtil::ArgumentType argument_type, const std::string& argument_name) {
boost::optional<std::string> 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<int> 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<std::string>(*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<IfcSchema::IfcLocalPlacement>();
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<std::string>(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<std::string, std::string>::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<std::string> 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("<xmlattr>.xlink:href", std::string("#") + *value);
}
}
else {
std::stringstream stream;
stream << "<xmlattr>." << 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<std::string>(inst->data().id());
}
// A function to be called recursively. Template specialization is used
// to descend into decomposition, containment and property relationships.
template <typename A>
ptree* descend(A* instance, ptree& tree, IfcUtil::IfcBaseClass* parent = nullptr) {
if (instance->declaration().is(IfcSchema::IfcObjectDefinition::Class())) {
return descend(instance->template as<IfcSchema::IfcObjectDefinition>(), 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 T, typename U, typename V, typename F, typename G>
typename V::list::ptr get_related(T* t, F f, G g) {
typename U::list::ptr li = (*t.*f)()->template as<U>();
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<V>());
}
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<IfcSchema::IfcElement>()->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<IfcSchema::IfcOpeningElement*>(product);
IfcSchema::IfcElement::list::ptr fills = get_related<IfcSchema::IfcOpeningElement, IfcSchema::IfcRelFillsElement, IfcSchema::IfcElement>(
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
<IfcSchema::IfcSpatialStructureElement, IfcSchema::IfcRelContainedInSpatialStructure, IfcSchema::IfcObjectDefinition>
(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<IfcSchema::IfcElement*>(product);
IfcSchema::IfcOpeningElement::list::ptr openings = get_related<IfcSchema::IfcElement, IfcSchema::IfcRelVoidsElement, IfcSchema::IfcOpeningElement>(
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
<IfcSchema::IfcObjectDefinition, IfcSchema::IfcRelDecomposes, IfcSchema::IfcObjectDefinition>
(product, &IfcSchema::IfcObjectDefinition::IsDecomposedBy, &IfcSchema::IfcRelDecomposes::RelatedObjects);
#else
IfcSchema::IfcObjectDefinition::list::ptr structures = get_related
<IfcSchema::IfcObjectDefinition, IfcSchema::IfcRelAggregates, IfcSchema::IfcObjectDefinition>
(product, &IfcSchema::IfcObjectDefinition::IsDecomposedBy, &IfcSchema::IfcRelAggregates::RelatedObjects);
structures->push(get_related
<IfcSchema::IfcObjectDefinition, IfcSchema::IfcRelNests, IfcSchema::IfcObjectDefinition>
(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::IfcObject>();
IfcSchema::IfcPropertySetDefinition::list::ptr property_sets = get_related
<IfcSchema::IfcObject, IfcSchema::IfcRelDefinesByProperties, IfcSchema::IfcPropertySetDefinition>
(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
<IfcSchema::IfcObject, IfcSchema::IfcRelDefinesByType, IfcSchema::IfcTypeObject>
(object, &IfcSchema::IfcObject::IsTypedBy, &IfcSchema::IfcRelDefinesByType::RelatingType);
#else
IfcSchema::IfcTypeObject::list::ptr types = get_related
<IfcSchema::IfcObject, IfcSchema::IfcRelDefinesByType, IfcSchema::IfcTypeObject>
(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<std::string, IfcUtil::IfcBaseEntity*> layers = IfcGeom::Kernel::get_layers(product);
for (std::map<std::string, IfcUtil::IfcBaseEntity*>::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("<xmlattr>.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::IfcRelAssociatesMaterial>()) {
IfcSchema::IfcMaterialSelect* mat = (*it)->as<IfcSchema::IfcRelAssociatesMaterial>()->RelatingMaterial();
ptree node;
node.put("<xmlattr>.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::set<std::string>notRootGroups)
{
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<IfcSchema::IfcGroup>(), *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<IfcSchema::IfcProject>();
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<IfcSchema::IfcPropertySet>();
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<IfcSchema::IfcGroup>();
std::set<std::string> 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<std::string>("<xmlattr>.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<IfcSchema::IfcElementQuantity>();
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<IfcSchema::IfcTypeObject>();
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<IfcSchema::IfcNamedUnit>();
ptree* node = format_entity_instance(named_unit, units);
if (node) {
node->put("<xmlattr>.SI_equivalent", IfcParse::get_SI_equivalent<IfcSchema>(named_unit));
}
}
else if ((*it)->declaration().is(IfcSchema::IfcMonetaryUnit::Class())) {
format_entity_instance((*it)->as<IfcSchema::IfcMonetaryUnit>(), 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<std::string> layer_names;
IfcSchema::IfcPresentationLayerAssignment::list::ptr layer_assignments = file->instances_by_type<IfcSchema::IfcPresentationLayerAssignment>();
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("<xmlattr>.id", name);
format_entity_instance(*it, node, layers);
}
}
IfcSchema::IfcRelAssociatesMaterial::list::ptr materal_associations = file->instances_by_type<IfcSchema::IfcRelAssociatesMaterial>();
std::set<IfcSchema::IfcMaterialSelect*> 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("<xmlattr>.id", qualify_unrooted_instance(mat));
if (mat->as<IfcSchema::IfcMaterialLayerSetUsage>() || mat->as<IfcSchema::IfcMaterialLayerSet>()) {
IfcSchema::IfcMaterialLayerSet* layerset = mat->as<IfcSchema::IfcMaterialLayerSet>();
if (!layerset) {
layerset = mat->as<IfcSchema::IfcMaterialLayerSetUsage>()->ForLayerSet();
}
if (layerset->hasLayerSetName()) {
node.put("<xmlattr>.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("<xmlattr>.Name", (*jt)->Material()->Name());
}
format_entity_instance(*jt, subnode, node);
}
}
else if (mat->as<IfcSchema::IfcMaterialList>()) {
IfcSchema::IfcMaterial::list::ptr mats = mat->as<IfcSchema::IfcMaterialList>()->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.<xmlattr>.xmlns:xlink", "http://www.w3.org/1999/xlink");
#if BOOST_VERSION >= 105600
boost::property_tree::xml_writer_settings<ptree::key_type> settings = boost::property_tree::xml_writer_make_settings<ptree::key_type>('\t', 1);
#else
boost::property_tree::xml_writer_settings<char> settings('\t', 1);
#endif
std::ofstream f(IfcUtil::path::from_utf8(xml_filename).c_str());
boost::property_tree::write_xml(f, root, settings);
}
+635
View File
@@ -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 <http://www.gnu.org/licenses/>. *
* *
********************************************************************************/
#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/xml_parser.hpp>
#include <boost/version.hpp>
#include <boost/foreach.hpp>
#include "XmlSerializer.h"
#include <algorithm>
#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<std::string, std::string> 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<std::string> format_attribute(const Argument* argument, IfcUtil::ArgumentType argument_type, const std::string& argument_name) {
boost::optional<std::string> 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<int> 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<std::string>(*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<IfcSchema::IfcLocalPlacement>();
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<std::string>(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<std::string, std::string>::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<std::string> 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("<xmlattr>.xlink:href", std::string("#") + *value);
}
}
else {
std::stringstream stream;
stream << "<xmlattr>." << 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<std::string>(inst->data().id());
}
// A function to be called recursively. Template specialization is used
// to descend into decomposition, containment and property relationships.
template <typename A>
ptree* descend(A* instance, ptree& tree, IfcUtil::IfcBaseClass* parent = nullptr) {
if (instance->declaration().is(IfcSchema::IfcObjectDefinition::Class())) {
return descend(instance->template as<IfcSchema::IfcObjectDefinition>(), 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 T, typename U, typename V, typename F, typename G>
typename V::list::ptr get_related(T* t, F f, G g) {
typename U::list::ptr li = (*t.*f)()->template as<U>();
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<V>());
}
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<IfcSchema::IfcElement>()->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<IfcSchema::IfcOpeningElement*>(product);
IfcSchema::IfcElement::list::ptr fills = get_related<IfcSchema::IfcOpeningElement, IfcSchema::IfcRelFillsElement, IfcSchema::IfcElement>(
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
<IfcSchema::IfcSpatialStructureElement, IfcSchema::IfcRelContainedInSpatialStructure, IfcSchema::IfcObjectDefinition>
(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<IfcSchema::IfcElement*>(product);
IfcSchema::IfcOpeningElement::list::ptr openings = get_related<IfcSchema::IfcElement, IfcSchema::IfcRelVoidsElement, IfcSchema::IfcOpeningElement>(
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
<IfcSchema::IfcObjectDefinition, IfcSchema::IfcRelDecomposes, IfcSchema::IfcObjectDefinition>
(product, &IfcSchema::IfcObjectDefinition::IsDecomposedBy, &IfcSchema::IfcRelDecomposes::RelatedObjects);
#else
IfcSchema::IfcObjectDefinition::list::ptr structures = get_related
<IfcSchema::IfcObjectDefinition, IfcSchema::IfcRelAggregates, IfcSchema::IfcObjectDefinition>
(product, &IfcSchema::IfcObjectDefinition::IsDecomposedBy, &IfcSchema::IfcRelAggregates::RelatedObjects);
structures->push(get_related
<IfcSchema::IfcObjectDefinition, IfcSchema::IfcRelNests, IfcSchema::IfcObjectDefinition>
(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::IfcObject>();
IfcSchema::IfcPropertySetDefinition::list::ptr property_sets = get_related
<IfcSchema::IfcObject, IfcSchema::IfcRelDefinesByProperties, IfcSchema::IfcPropertySetDefinition>
(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
<IfcSchema::IfcObject, IfcSchema::IfcRelDefinesByType, IfcSchema::IfcTypeObject>
(object, &IfcSchema::IfcObject::IsTypedBy, &IfcSchema::IfcRelDefinesByType::RelatingType);
#else
IfcSchema::IfcTypeObject::list::ptr types = get_related
<IfcSchema::IfcObject, IfcSchema::IfcRelDefinesByType, IfcSchema::IfcTypeObject>
(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<std::string, IfcUtil::IfcBaseEntity*> layers = IfcGeom::Kernel::get_layers(product);
for (std::map<std::string, IfcUtil::IfcBaseEntity*>::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("<xmlattr>.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::IfcRelAssociatesMaterial>()) {
IfcSchema::IfcMaterialSelect* mat = (*it)->as<IfcSchema::IfcRelAssociatesMaterial>()->RelatingMaterial();
ptree node;
node.put("<xmlattr>.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<IfcSchema::IfcProject>();
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<IfcSchema::IfcPropertySet>();
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<IfcSchema::IfcGroup>();
std::set<std::string> notRootGroups;//selfname, fathername
std::function<void(IfcSchema::IfcGroup* group, ptree& node)> 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<IfcSchema::IfcGroup>(), *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<std::string>("<xmlattr>.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<IfcSchema::IfcElementQuantity>();
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<IfcSchema::IfcTypeObject>();
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<IfcSchema::IfcNamedUnit>();
ptree* node = format_entity_instance(named_unit, units);
if (node) {
node->put("<xmlattr>.SI_equivalent", IfcParse::get_SI_equivalent<IfcSchema>(named_unit));
}
}
else if ((*it)->declaration().is(IfcSchema::IfcMonetaryUnit::Class())) {
format_entity_instance((*it)->as<IfcSchema::IfcMonetaryUnit>(), 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<std::string> layer_names;
IfcSchema::IfcPresentationLayerAssignment::list::ptr layer_assignments = file->instances_by_type<IfcSchema::IfcPresentationLayerAssignment>();
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("<xmlattr>.id", name);
format_entity_instance(*it, node, layers);
}
}
IfcSchema::IfcRelAssociatesMaterial::list::ptr materal_associations = file->instances_by_type<IfcSchema::IfcRelAssociatesMaterial>();
std::set<IfcSchema::IfcMaterialSelect*> 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("<xmlattr>.id", qualify_unrooted_instance(mat));
if (mat->as<IfcSchema::IfcMaterialLayerSetUsage>() || mat->as<IfcSchema::IfcMaterialLayerSet>()) {
IfcSchema::IfcMaterialLayerSet* layerset = mat->as<IfcSchema::IfcMaterialLayerSet>();
if (!layerset) {
layerset = mat->as<IfcSchema::IfcMaterialLayerSetUsage>()->ForLayerSet();
}
if (layerset->hasLayerSetName()) {
node.put("<xmlattr>.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("<xmlattr>.Name", (*jt)->Material()->Name());
}
format_entity_instance(*jt, subnode, node);
}
}
else if (mat->as<IfcSchema::IfcMaterialList>()) {
IfcSchema::IfcMaterial::list::ptr mats = mat->as<IfcSchema::IfcMaterialList>()->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.<xmlattr>.xmlns:xlink", "http://www.w3.org/1999/xlink");
#if BOOST_VERSION >= 105600
boost::property_tree::xml_writer_settings<ptree::key_type> settings = boost::property_tree::xml_writer_make_settings<ptree::key_type>('\t', 1);
#else
boost::property_tree::xml_writer_settings<char> settings('\t', 1);
#endif
std::ofstream f(IfcUtil::path::from_utf8(xml_filename).c_str());
boost::property_tree::write_xml(f, root, settings);
}
+647
View File
@@ -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 <http://www.gnu.org/licenses/>. *
* *
********************************************************************************/
#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/xml_parser.hpp>
#include <boost/version.hpp>
#include <boost/foreach.hpp>
#include "XmlSerializer.h"
#include <algorithm>
#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<std::string, std::string> 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<std::string> format_attribute(const Argument* argument, IfcUtil::ArgumentType argument_type, const std::string& argument_name) {
boost::optional<std::string> 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<int> 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<std::string>(*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<IfcSchema::IfcLocalPlacement>();
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<std::string>(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<std::string, std::string>::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<std::string> 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("<xmlattr>.xlink:href", std::string("#") + *value);
}
}
else {
std::stringstream stream;
stream << "<xmlattr>." << 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<std::string>(inst->data().id());
}
// A function to be called recursively. Template specialization is used
// to descend into decomposition, containment and property relationships.
template <typename A>
ptree* descend(A* instance, ptree& tree, IfcUtil::IfcBaseClass* parent = nullptr) {
if (instance->declaration().is(IfcSchema::IfcObjectDefinition::Class())) {
return descend(instance->template as<IfcSchema::IfcObjectDefinition>(), 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 T, typename U, typename V, typename F, typename G>
typename V::list::ptr get_related(T* t, F f, G g) {
typename U::list::ptr li = (*t.*f)()->template as<U>();
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<V>());
}
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<IfcSchema::IfcElement>()->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<IfcSchema::IfcOpeningElement*>(product);
IfcSchema::IfcElement::list::ptr fills = get_related<IfcSchema::IfcOpeningElement, IfcSchema::IfcRelFillsElement, IfcSchema::IfcElement>(
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
<IfcSchema::IfcSpatialStructureElement, IfcSchema::IfcRelContainedInSpatialStructure, IfcSchema::IfcObjectDefinition>
(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<IfcSchema::IfcElement*>(product);
IfcSchema::IfcOpeningElement::list::ptr openings = get_related<IfcSchema::IfcElement, IfcSchema::IfcRelVoidsElement, IfcSchema::IfcOpeningElement>(
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
<IfcSchema::IfcObjectDefinition, IfcSchema::IfcRelDecomposes, IfcSchema::IfcObjectDefinition>
(product, &IfcSchema::IfcObjectDefinition::IsDecomposedBy, &IfcSchema::IfcRelDecomposes::RelatedObjects);
#else
IfcSchema::IfcObjectDefinition::list::ptr structures = get_related
<IfcSchema::IfcObjectDefinition, IfcSchema::IfcRelAggregates, IfcSchema::IfcObjectDefinition>
(product, &IfcSchema::IfcObjectDefinition::IsDecomposedBy, &IfcSchema::IfcRelAggregates::RelatedObjects);
structures->push(get_related
<IfcSchema::IfcObjectDefinition, IfcSchema::IfcRelNests, IfcSchema::IfcObjectDefinition>
(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::IfcObject>();
IfcSchema::IfcPropertySetDefinition::list::ptr property_sets = get_related
<IfcSchema::IfcObject, IfcSchema::IfcRelDefinesByProperties, IfcSchema::IfcPropertySetDefinition>
(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
<IfcSchema::IfcObject, IfcSchema::IfcRelDefinesByType, IfcSchema::IfcTypeObject>
(object, &IfcSchema::IfcObject::IsTypedBy, &IfcSchema::IfcRelDefinesByType::RelatingType);
#else
IfcSchema::IfcTypeObject::list::ptr types = get_related
<IfcSchema::IfcObject, IfcSchema::IfcRelDefinesByType, IfcSchema::IfcTypeObject>
(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<std::string, IfcUtil::IfcBaseEntity*> layers = IfcGeom::Kernel::get_layers(product);
for (std::map<std::string, IfcUtil::IfcBaseEntity*>::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("<xmlattr>.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::IfcRelAssociatesMaterial>()) {
IfcSchema::IfcMaterialSelect* mat = (*it)->as<IfcSchema::IfcRelAssociatesMaterial>()->RelatingMaterial();
ptree node;
node.put("<xmlattr>.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<IfcSchema::IfcProject>();
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<IfcSchema::IfcPropertySet>();
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<IfcSchema::IfcGroup>();
std::set<unsigned> notRootGroups;//selfname, fathername
std::map<unsigned, int> rootGroups;
std::function<void(IfcSchema::IfcGroup* group, ptree& node)> 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<IfcSchema::IfcGroup>(), *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<int> 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<IfcSchema::IfcElementQuantity>();
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<IfcSchema::IfcTypeObject>();
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<IfcSchema::IfcNamedUnit>();
ptree* node = format_entity_instance(named_unit, units);
if (node) {
node->put("<xmlattr>.SI_equivalent", IfcParse::get_SI_equivalent<IfcSchema>(named_unit));
}
}
else if ((*it)->declaration().is(IfcSchema::IfcMonetaryUnit::Class())) {
format_entity_instance((*it)->as<IfcSchema::IfcMonetaryUnit>(), 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<std::string> layer_names;
IfcSchema::IfcPresentationLayerAssignment::list::ptr layer_assignments = file->instances_by_type<IfcSchema::IfcPresentationLayerAssignment>();
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("<xmlattr>.id", name);
format_entity_instance(*it, node, layers);
}
}
IfcSchema::IfcRelAssociatesMaterial::list::ptr materal_associations = file->instances_by_type<IfcSchema::IfcRelAssociatesMaterial>();
std::set<IfcSchema::IfcMaterialSelect*> 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("<xmlattr>.id", qualify_unrooted_instance(mat));
if (mat->as<IfcSchema::IfcMaterialLayerSetUsage>() || mat->as<IfcSchema::IfcMaterialLayerSet>()) {
IfcSchema::IfcMaterialLayerSet* layerset = mat->as<IfcSchema::IfcMaterialLayerSet>();
if (!layerset) {
layerset = mat->as<IfcSchema::IfcMaterialLayerSetUsage>()->ForLayerSet();
}
if (layerset->hasLayerSetName()) {
node.put("<xmlattr>.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("<xmlattr>.Name", (*jt)->Material()->Name());
}
format_entity_instance(*jt, subnode, node);
}
}
else if (mat->as<IfcSchema::IfcMaterialList>()) {
IfcSchema::IfcMaterial::list::ptr mats = mat->as<IfcSchema::IfcMaterialList>()->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.<xmlattr>.xmlns:xlink", "http://www.w3.org/1999/xlink");
#if BOOST_VERSION >= 105600
boost::property_tree::xml_writer_settings<ptree::key_type> settings = boost::property_tree::xml_writer_make_settings<ptree::key_type>('\t', 1);
#else
boost::property_tree::xml_writer_settings<char> settings('\t', 1);
#endif
std::ofstream f(IfcUtil::path::from_utf8(xml_filename).c_str());
boost::property_tree::write_xml(f, root, settings);
}
+12
View File
@@ -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")
+9 -8
View File
@@ -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:
+2 -2
View File
@@ -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
+34 -64
View File
@@ -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
+28 -132
View File
@@ -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
File diff suppressed because it is too large Load Diff
+34 -380
View File
@@ -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
@@ -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="")
@@ -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,
@@ -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"}
@@ -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
@@ -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"
@@ -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
@@ -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),
@@ -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")
@@ -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
@@ -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"}
@@ -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
)
@@ -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)
@@ -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
@@ -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)
@@ -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]
@@ -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
@@ -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)
@@ -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)
@@ -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"}
@@ -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")
@@ -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
@@ -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"])
@@ -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)
@@ -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
@@ -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"
})
@@ -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"]
})
@@ -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
@@ -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)
@@ -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 "<entity" in data_type:
continue
new = props.constraint_attributes.add()
new.name = attribute.name()
new.is_null = data[attribute.name()] is None
new.is_optional = attribute.optional()
if "<string>" in data_type:
new.string_value = "" if new.is_null else data[attribute.name()]
new.data_type = "string"
elif "<enumeration" in data_type:
new.enum_items = json.dumps(attribute.type_of_attribute().declared_type().enumeration_items())
new.data_type = "enum"
if data[attribute.name()]:
new.enum_value = data[attribute.name()]
props.active_constraint_id = self.constraint
return {"FINISHED"}
class DisableEditingConstraint(bpy.types.Operator):
bl_idname = "bim.disable_editing_constraint"
bl_label = "Disable Editing Constraint"
def execute(self, context):
context.scene.BIMConstraintProperties.active_constraint_id = 0
return {"FINISHED"}
class AddObjective(bpy.types.Operator):
bl_idname = "bim.add_objective"
bl_label = "Add Objective"
def execute(self, context):
result = add_objective.Usecase(IfcStore.get_file()).execute()
Data.load()
bpy.ops.bim.load_objectives()
bpy.ops.bim.enable_editing_constraint(constraint=result.id())
return {"FINISHED"}
class EditObjective(bpy.types.Operator):
bl_idname = "bim.edit_objective"
bl_label = "Edit Objective"
def execute(self, context):
props = context.scene.BIMConstraintProperties
attributes = {}
for attribute in props.constraint_attributes:
if attribute.is_null:
attributes[attribute.name] = None
elif attribute.enum_items:
attributes[attribute.name] = attribute.enum_value
else:
attributes[attribute.name] = attribute.string_value
self.file = IfcStore.get_file()
edit_objective.Usecase(
self.file, {"objective": self.file.by_id(props.active_constraint_id), "attributes": attributes}
).execute()
Data.load()
bpy.ops.bim.load_objectives()
return {"FINISHED"}
class RemoveConstraint(bpy.types.Operator):
bl_idname = "bim.remove_constraint"
bl_label = "Remove Constraint"
constraint: bpy.props.IntProperty()
def execute(self, context):
props = context.scene.BIMConstraintProperties
self.file = IfcStore.get_file()
remove_constraint.Usecase(self.file, {"constraint": self.file.by_id(self.constraint)}).execute()
Data.load()
if props.is_editing == "IfcObjective":
bpy.ops.bim.load_objectives()
return {"FINISHED"}
class EnableAssigningConstraint(bpy.types.Operator):
bl_idname = "bim.enable_assigning_constraint"
bl_label = "Enable Assigning Constraint"
obj: bpy.props.StringProperty()
def execute(self, context):
obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
props = obj.BIMObjectConstraintProperties
if props.available_constraint_types == "IfcObjective":
bpy.ops.bim.load_objectives()
props.is_adding = props.available_constraint_types
return {"FINISHED"}
class DisableAssigningConstraint(bpy.types.Operator):
bl_idname = "bim.disable_assigning_constraint"
bl_label = "Disable Assigning Constraint"
obj: bpy.props.StringProperty()
def execute(self, context):
obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
props = obj.BIMObjectConstraintProperties
props.is_adding = ""
return {"FINISHED"}
class AssignConstraint(bpy.types.Operator):
bl_idname = "bim.assign_constraint"
bl_label = "Assign Constraint"
obj: bpy.props.StringProperty()
constraint: bpy.props.IntProperty()
def execute(self, context):
obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
self.file = IfcStore.get_file()
assign_constraint.Usecase(self.file, {
"product": self.file.by_id(obj.BIMObjectProperties.ifc_definition_id),
"constraint": self.file.by_id(self.constraint)
}).execute()
Data.load(obj.BIMObjectProperties.ifc_definition_id)
return {"FINISHED"}
class UnassignConstraint(bpy.types.Operator):
bl_idname = "bim.unassign_constraint"
bl_label = "Unassign Constraint"
obj: bpy.props.StringProperty()
constraint: bpy.props.IntProperty()
def execute(self, context):
obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
self.file = IfcStore.get_file()
unassign_constraint.Usecase(self.file, {
"product": self.file.by_id(obj.BIMObjectProperties.ifc_definition_id),
"constraint": self.file.by_id(self.constraint)
}).execute()
Data.load(obj.BIMObjectProperties.ifc_definition_id)
return {"FINISHED"}
@@ -0,0 +1,33 @@
import bpy
from blenderbim.bim.prop import StrProperty, Attribute
from bpy.types import PropertyGroup
from bpy.props import (
PointerProperty,
StringProperty,
EnumProperty,
BoolProperty,
IntProperty,
FloatProperty,
FloatVectorProperty,
CollectionProperty,
)
class Constraint(PropertyGroup):
name: StringProperty(name="Name")
ifc_definition_id: IntProperty(name="IFC Definition ID")
class BIMConstraintProperties(PropertyGroup):
constraint_attributes: CollectionProperty(name="Constraint Attributes", type=Attribute)
active_constraint_id: IntProperty(name="Active Constraint Id")
constraints: CollectionProperty(name="Constraints", type=Constraint)
active_constraint_index: IntProperty(name="Active Constraint Index")
is_editing: StringProperty(name="Is Editing")
class BIMObjectConstraintProperties(PropertyGroup):
is_adding: StringProperty(name="Is Adding")
available_constraint_types: EnumProperty(
items=[(c, c, "") for c in ["IfcObjective"]], name="Available Constraint Types"
)
@@ -0,0 +1,12 @@
class Usecase:
def __init__(self, file, settings=None):
self.file = file
self.settings = {"constraint": None}
for key, value in settings.items():
self.settings[key] = value
def execute(self):
self.file.remove(self.settings["constraint"])
for rel in self.file.by_type("IfcRelAssociatesConstraint"):
if not rel.RelatingConstraint:
self.file.remove(rel)
@@ -0,0 +1,141 @@
from bpy.types import Panel, UIList
from blenderbim.bim.ifc import IfcStore
from blenderbim.bim.module.constraint.data import Data
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"
@classmethod
def poll(cls, context):
return IfcStore.get_file()
def draw(self, context):
if not Data.is_loaded:
Data.load()
self.props = context.scene.BIMConstraintProperties
if not self.props.is_editing or self.props.is_editing == "IfcObjective":
row = self.layout.row(align=True)
row.label(text="{} Objectives Found".format(len(Data.objectives)), icon="LIGHT")
if self.props.is_editing == "IfcObjective":
row.operator("bim.disable_constraint_editing_ui", text="", icon="CHECKMARK")
row.operator("bim.add_objective", text="", icon="ADD")
else:
row.operator("bim.load_objectives", text="", icon="GREASEPENCIL")
if self.props.is_editing:
self.layout.template_list(
"BIM_UL_constraints",
"",
self.props,
"constraints",
self.props,
"active_constraint_index",
)
if self.props.active_constraint_id:
self.draw_editable_ui(context)
return
def draw_editable_ui(self, context):
for attribute in self.props.constraint_attributes:
row = self.layout.row(align=True)
if attribute.data_type == "string":
row.prop(attribute, "string_value", text=attribute.name)
elif attribute.data_type == "enum":
row.prop(attribute, "enum_value", text=attribute.name)
if attribute.is_optional:
row.prop(attribute, "is_null", icon="RADIOBUT_OFF" if attribute.is_null else "RADIOBUT_ON", text="")
class BIM_PT_object_constraints(Panel):
bl_label = "IFC Constraints"
bl_idname = "BIM_PT_object_constraints"
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.BIMConstraintProperties
self.props = obj.BIMObjectConstraintProperties
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()
constraint_ids = Data.products[self.oprops.ifc_definition_id]
if not constraint_ids:
row = self.layout.row(align=True)
row.label(text="No Constraints", icon="LIGHT")
for constraint_id in constraint_ids:
try:
constraint = Data.objectives[constraint_id]
icon = "LIGHT"
except:
pass # Metric not implemented
row = self.layout.row(align=True)
row.label(text=constraint.get("Name") or "Unnamed")
row.operator("bim.unassign_constraint", text="", icon="X").constraint = constraint_id
def draw_add_ui(self):
if self.props.is_adding:
row = self.layout.row(align=True)
icon = "LIGHT" if self.props.is_adding == "IfcObjective" else "FILE_HIDDEN"
row.label(text="Adding {}".format(self.props.is_adding), icon=icon)
row.operator("bim.disable_assigning_constraint", text="", icon="X")
self.layout.template_list(
"BIM_UL_object_constraints",
"",
self.sprops,
"constraints",
self.sprops,
"active_constraint_index",
)
else:
row = self.layout.row(align=True)
row.prop(self.props, "available_constraint_types", text="")
row.operator("bim.enable_assigning_constraint", text="", icon="ADD")
class BIM_UL_constraints(UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
if item:
row = layout.row(align=True)
row.label(text=item.name)
if context.scene.BIMConstraintProperties.active_constraint_id == item.ifc_definition_id:
if context.scene.BIMConstraintProperties.is_editing == "IfcObjective":
row.operator("bim.edit_objective", text="", icon="CHECKMARK")
row.operator("bim.disable_editing_constraint", text="", icon="X")
elif context.scene.BIMConstraintProperties.active_constraint_id:
row.operator("bim.remove_constraint", text="", icon="X").constraint = item.ifc_definition_id
else:
op = row.operator("bim.enable_editing_constraint", text="", icon="GREASEPENCIL")
op.constraint = item.ifc_definition_id
row.operator("bim.remove_constraint", text="", icon="X").constraint = item.ifc_definition_id
class BIM_UL_object_constraints(UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
if item:
row = layout.row(align=True)
row.label(text=item.name)
row.operator("bim.assign_constraint", text="", icon="ADD").constraint = item.ifc_definition_id
@@ -0,0 +1,17 @@
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):
for rel in self.settings["product"].HasAssociations:
if rel.is_a("IfcRelAssociatesConstraint") and rel.RelatingConstraint == self.settings["constraint"]:
self.file.remove(rel)
@@ -1,6 +1,9 @@
import bpy
import json
import ifcopenshell
import ifcopenshell.util.element
from math import degrees, atan2
from blenderbim.bim.ifc import IfcStore
from .api import Api
@@ -70,6 +73,7 @@ class RunAnalysis(bpy.types.Operator):
bl_label = "Run Analysis"
def execute(self, context):
self.file = IfcStore.get_file()
self.inputs = {
"floors": [],
"walls": [],
@@ -94,16 +98,13 @@ class RunAnalysis(bpy.types.Operator):
return {"FINISHED"}
def get_rotation_angle(self):
if (
not bpy.context.scene.BIMProperties.has_georeferencing
or not bpy.context.scene.MapConversion.x_axis_abscissa
or not bpy.context.scene.MapConversion.x_axis_ordinate
):
if not self.file.by_type("IfcMapConversion"):
return 0
map_conversion = self.file.by_type("IfcMapConversion")[0]
rotation = -1 * degrees(
atan2(
float(bpy.context.scene.MapConversion.x_axis_ordinate),
float(bpy.context.scene.MapConversion.x_axis_abscissa),
float(map_conversion.XAxisOrdinate or 0),
float(map_conversion.XAxisAbscissa or 1),
)
)
if rotation < 0:
@@ -164,34 +165,36 @@ class RunAnalysis(bpy.types.Operator):
def get_covetool_category(self, obj):
if not hasattr(obj, "data") or not isinstance(obj.data, bpy.types.Mesh):
return
if "IfcSlab" in obj.name:
if not obj.BIMObjectProperties.ifc_definition_id:
return
element = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id)
ifc_class = element.is_a()
if "IfcSlab" in ifc_class:
return "floors"
elif "IfcRoof" in obj.name:
elif "IfcRoof" in ifc_class:
return "roofs"
elif "IfcWall" in obj.name:
if self.is_wall_internal(obj):
elif "IfcWall" in ifc_class:
if self.is_wall_internal(element):
return "interior_walls"
return "walls"
elif "IfcWindow" in obj.name:
if self.is_window_skylight(obj):
elif "IfcWindow" in ifc_class:
if self.is_window_skylight(element):
return "skylights"
return "windows"
elif "IfcShadingDevice" in obj.name:
elif "IfcShadingDevice" in ifc_class:
return "shading_devices"
def is_wall_internal(self, obj):
pset_wallcommon = obj.BIMObjectProperties.psets.get("Pset_WallCommon")
if pset_wallcommon:
is_external = pset_wallcommon.properties.get("IsExternal")
if is_external:
if is_external.string_value == "True":
return False
else:
return True
predefined_type = obj.BIMObjectProperties.attributes.get("PredefinedType")
if predefined_type and predefined_type.string_value in ["MOVABLE", "PARTITIONING", "PLUMBINGWALL"]:
def is_wall_internal(self, element):
psets = ifcopenshell.util.element.get_psets(element)
pset = psets.get("Pset_WallCommon")
if pset:
is_external = pset.get("IsExternal", None)
if is_external is not None:
return is_external
predefined_type = element.get_info().get("PredefinedType")
if predefined_type and predefined_type in ["MOVABLE", "PARTITIONING", "PLUMBINGWALL"]:
return True
def is_window_skylight(self, obj):
predefined_type = obj.BIMObjectProperties.attributes.get("PredefinedType")
def is_window_skylight(self, element):
predefined_type = element.get_info().get("PredefinedType")
return predefined_type and predefined_type.string_value == "SKYLIGHT"
@@ -7,6 +7,7 @@ classes = (
operator.ExportIfcCsv,
operator.ImportIfcCsv,
operator.EyedropIfcCsv,
operator.SelectCsvIfcFile,
prop.CsvProperties,
ui.BIM_PT_ifccsv,
)
@@ -9,24 +9,25 @@ import tempfile
from blenderbim.bim.ifc import IfcStore
class AddCsvAttribute(bpy.types.Operator):
bl_idname = "bim.add_csv_attribute"
bl_label = "Add CSV Attribute"
def execute(self, context):
attribute = bpy.context.scene.CsvProperties.csv_attributes.add()
attribute = context.scene.CsvProperties.csv_attributes.add()
return {"FINISHED"}
class RemoveCsvAttribute(bpy.types.Operator):
bl_idname = "bim.remove_csv_attribute"
bl_label = "Remove CSV Attribute"
index: bpy.props.IntProperty()
def execute(self, context):
bpy.context.scene.CsvProperties.csv_attributes.remove(self.index)
context.scene.CsvProperties.csv_attributes.remove(self.index)
return {"FINISHED"}
class ExportIfcCsv(bpy.types.Operator):
bl_idname = "bim.export_ifccsv"
bl_label = "Export IFC to CSV"
@@ -42,21 +43,26 @@ class ExportIfcCsv(bpy.types.Operator):
def execute(self, context):
import ifccsv
props = context.scene.CsvProperties
self.filepath = bpy.path.ensure_ext(self.filepath, ".csv")
ifc_file = ifcopenshell.open(bpy.context.scene.CsvProperties.csv_ifc_file)
if props.should_load_from_memory:
ifc_file = IfcStore.get_file()
else:
ifc_file = ifcopenshell.open(props.csv_ifc_file)
selector = ifcopenshell.util.selector.Selector()
results = selector.parse(ifc_file, bpy.context.scene.CsvProperties.ifc_selector)
results = selector.parse(ifc_file, props.ifc_selector)
ifc_csv = ifccsv.IfcCsv()
ifc_csv.output = self.filepath
ifc_csv.attributes = [a.name for a in bpy.context.scene.CsvProperties.csv_attributes]
ifc_csv.attributes = [a.name for a in props.csv_attributes]
ifc_csv.selector = selector
if bpy.context.scene.CsvProperties.csv_delimiter == "CUSTOM":
ifc_csv.delimiter = bpy.context.scene.CsvProperties.csv_custom_delimiter
if props.csv_delimiter == "CUSTOM":
ifc_csv.delimiter = props.csv_custom_delimiter
else:
ifc_csv.delimiter = bpy.context.scene.CsvProperties.csv_delimiter
ifc_csv.delimiter = props.csv_delimiter
ifc_csv.export(ifc_file, results)
return {"FINISHED"}
class ImportIfcCsv(bpy.types.Operator):
bl_idname = "bim.import_ifccsv"
bl_label = "Import CSV to IFC"
@@ -74,18 +80,36 @@ class ImportIfcCsv(bpy.types.Operator):
ifc_csv = ifccsv.IfcCsv()
ifc_csv.output = self.filepath
ifc_csv.Import(bpy.context.scene.CsvProperties.csv_ifc_file)
ifc_csv.Import(context.scene.CsvProperties.csv_ifc_file)
return {"FINISHED"}
class EyedropIfcCsv(bpy.types.Operator):
bl_idname = "bim.eyedrop_ifccsv"
bl_label = "Query Selected Items"
def execute(self, context):
global_ids = []
for obj in bpy.context.selected_objects:
if hasattr(obj, "BIMObjectProperties") and obj.BIMObjectProperties.attributes.get("GlobalId"):
global_ids.append("#" + obj.BIMObjectProperties.attributes.get("GlobalId").string_value)
bpy.context.scene.CsvProperties.ifc_selector = "|".join(global_ids)
self.file = IfcStore.get_file()
for obj in context.selected_objects:
if hasattr(obj, "BIMObjectProperties") and obj.BIMObjectProperties.ifc_definition_id:
global_ids.append("#" + self.file.by_id(obj.BIMObjectProperties.ifc_definition_id).GlobalId)
context.scene.CsvProperties.ifc_selector = "|".join(global_ids)
return {"FINISHED"}
class SelectCsvIfcFile(bpy.types.Operator):
bl_idname = "bim.select_csv_ifc_file"
bl_label = "Select CSV IFC File"
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):
context.scene.CsvProperties.csv_ifc_file = self.filepath
return {"FINISHED"}
def invoke(self, context, event):
context.window_manager.fileselect_add(self)
return {"RUNNING_MODAL"}
@@ -18,10 +18,14 @@ class CsvProperties(PropertyGroup):
ifc_selector: StringProperty(default="", name="IFC Selector")
csv_attributes: CollectionProperty(name="CSV Attributes", type=StrProperty)
csv_delimiter: EnumProperty(
items=[(";", ";", ""), (",", ",", ""), (".", ".", ""), ("CUSTOM", "Custom", ""),],
items=[
(";", ";", ""),
(",", ",", ""),
(".", ".", ""),
("CUSTOM", "Custom", ""),
],
name="IFC CSV Delimiter",
default=",",
)
csv_custom_delimiter: StringProperty(default="", name="Custom Delimiter")
should_load_from_memory: BoolProperty(default=False, name="Load from Memory")
@@ -16,20 +16,20 @@ class BIM_PT_ifccsv(Panel):
scene = context.scene
props = scene.CsvProperties
if IfcStore.get_file():
if IfcStore.get_file():
row = layout.row()
row.prop(props, "should_load_from_memory")
if not IfcStore.get_file() or not props.should_load_from_memory:
row = layout.row(align=True)
row.prop(props, "csv_ifc_file")
row.operator("bim.import_ifccsv", icon="FILE_FOLDER", text="")
row = layout.row(align=True)
row.prop(props, "ifc_selector")
row.operator("bim.eyedrop_ifccsv", icon="EYEDROPPER", text="")
row.operator("bim.select_csv_ifc_file", icon="FILE_FOLDER", text="")
row = layout.row(align=True)
row.prop(props, "ifc_selector")
row.operator("bim.eyedrop_ifccsv", icon="EYEDROPPER", text="")
row = layout.row()
row.label(text="Add IFC attributes to filter", icon="FILE_BLANK")
row.operator("bim.add_csv_attribute")
for index, attribute in enumerate(props.csv_attributes):
@@ -40,11 +40,10 @@ class BIM_PT_ifccsv(Panel):
row = layout.row(align=True)
row.prop(props, "csv_delimiter")
if(props.csv_delimiter == 'CUSTOM'):
if props.csv_delimiter == "CUSTOM":
row = layout.row(align=True)
row.prop(props, "csv_custom_delimiter")
row = layout.row(align=True)
row.operator("bim.export_ifccsv", icon="EXPORT")
row.operator("bim.import_ifccsv", icon="IMPORT")
@@ -32,7 +32,6 @@ class VisualiseDiff(bpy.types.Operator):
for obj in bpy.context.visible_objects:
obj.color = (1.0, 1.0, 1.0, 0.2)
global_id = ifc_file.by_id(obj.BIMObjectProperties.ifc_definition_id).GlobalId
#global_id = obj.BIMObjectProperties.attributes.get("GlobalId")
if not global_id:
continue
if global_id.string_value in diff["deleted"]:
@@ -18,4 +18,3 @@ class DiffProperties(PropertyGroup):
diff_old_file: StringProperty(default="", name="Diff Old IFC File")
diff_new_file: StringProperty(default="", name="Diff New IFC File")
diff_relationships: StringProperty(default="", name="Diff Relationships")
@@ -0,0 +1,36 @@
import bpy
from . import ui, prop, operator
classes = (
operator.LoadInformation,
operator.LoadDocumentReferences,
operator.DisableDocumentEditingUI,
operator.EnableEditingDocument,
operator.DisableEditingDocument,
operator.AddInformation,
operator.AddDocumentReference,
operator.EditInformation,
operator.EditDocumentReference,
operator.RemoveDocument,
operator.EnableAssigningDocument,
operator.DisableAssigningDocument,
operator.AssignDocument,
operator.UnassignDocument,
prop.Document,
prop.BIMDocumentProperties,
prop.BIMObjectDocumentProperties,
ui.BIM_PT_documents,
ui.BIM_PT_object_documents,
ui.BIM_UL_documents,
ui.BIM_UL_object_documents,
)
def register():
bpy.types.Scene.BIMDocumentProperties = bpy.props.PointerProperty(type=prop.BIMDocumentProperties)
bpy.types.Object.BIMObjectDocumentProperties = bpy.props.PointerProperty(type=prop.BIMObjectDocumentProperties)
def unregister():
del bpy.types.Scene.BIMDocumentProperties
del bpy.types.Object.BIMObjectDocumentProperties
@@ -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):
id_attribute = "DocumentId" if self.file.schema == "IFC2X3" else "Identification"
return self.file.create_entity("IfcDocumentInformation", **{
id_attribute: ifcopenshell.guid.new(),
"Name": "Unnamed"
})
@@ -0,0 +1,14 @@
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):
id_attribute = "ItemReference" if self.file.schema == "IFC2X3" else "Identification"
return self.file.create_entity("IfcDocumentReference", **{
id_attribute: ifcopenshell.guid.new()
})
@@ -0,0 +1,41 @@
import ifcopenshell
class Usecase:
def __init__(self, file, settings=None):
self.file = file
self.settings = {
"product": None,
"document": None,
}
for key, value in settings.items():
self.settings[key] = value
def execute(self):
rel = self.get_document_rel()
related_objects = set(rel.RelatedObjects) if rel.RelatedObjects else set()
related_objects.add(self.settings["product"])
rel.RelatedObjects = list(related_objects)
def get_document_rel(self):
if self.file.schema == "IFC2X3":
for rel in self.file.by_type("IfcRelAssociatesDocument"):
if rel.RelatingDocument == self.settings["document"]:
return rel
else:
if (
hasattr(self.settings["document"], "DocumentRefForObjects")
and self.settings["document"].DocumentRefForObjects
):
return self.settings["document"].DocumentRefForObjects[0]
elif (
hasattr(self.settings["document"], "DocumentInfoForObjects")
and self.settings["document"].DocumentInfoForObjects
):
return self.settings["document"].DocumentInfoForObjects[0]
return self.file.create_entity("IfcRelAssociatesDocument", **{
"GlobalId": ifcopenshell.guid.new(),
# TODO: owner history
"RelatingDocument": self.settings["document"]
})
@@ -0,0 +1,59 @@
import ifcopenshell
import ifcopenshell.util.date
from blenderbim.bim.ifc import IfcStore
from datetime import datetime
class Data:
is_loaded = False
products = {}
references = {}
information = {}
@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_references()
cls.load_information()
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("IfcRelAssociatesDocument"):
cls.products[product_id].append(association.RelatingDocument.id())
@classmethod
def load_information(cls):
cls.information = {}
for information in cls._file.by_type("IfcDocumentInformation"):
data = information.get_info()
if cls._file.schema == "IFC2X3":
for attribute in ["CreationTime", "LastRevisionTime", "ValidFrom", "ValidUntil"]:
if data[attribute]:
data[attribute] = ifcopenshell.util.date.ifc2datetime(data[attribute]).isoformat()
if data["ElectronicFormat"]:
data["ElectronicFormat"] = "{}/{}".format(
information.ElectronicFormat.MimeContentType, information.ElectronicFormat.MimeSubtype
)
cls.information[information.id()] = data
@classmethod
def load_references(cls):
cls.references = {}
for reference in cls._file.by_type("IfcDocumentReference"):
data = reference.get_info()
if cls._file.schema == "IFC2X3":
if reference.ReferenceToDocument:
data["ReferencedDocument"] = reference.ReferenceToDocument[0].id()
elif reference.ReferencedDocument:
data["ReferencedDocument"] = reference.ReferencedDocument.id()
cls.references[reference.id()] = data
@@ -0,0 +1,13 @@
class Usecase:
def __init__(self, file, settings=None):
self.file = file
self.settings = {
"information": 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["information"], name, value)
@@ -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)
@@ -0,0 +1,258 @@
import bpy
import json
import blenderbim.bim.module.document.add_information as add_information
import blenderbim.bim.module.document.add_reference as add_reference
import blenderbim.bim.module.document.edit_information as edit_information
import blenderbim.bim.module.document.edit_reference as edit_reference
import blenderbim.bim.module.document.remove_document as remove_document
import blenderbim.bim.module.document.assign_document as assign_document
import blenderbim.bim.module.document.unassign_document as unassign_document
from blenderbim.bim.ifc import IfcStore
from blenderbim.bim.module.document.data import Data
class LoadInformation(bpy.types.Operator):
bl_idname = "bim.load_information"
bl_label = "Load Information"
def execute(self, context):
self.file = IfcStore.get_file()
props = context.scene.BIMDocumentProperties
while len(props.documents) > 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 "<entity" in data_type:
continue
new = props.document_attributes.add()
new.name = attribute.name()
new.is_null = data[attribute.name()] is None
new.is_optional = attribute.optional()
if "<string>" in data_type:
new.string_value = "" if new.is_null else data[attribute.name()]
new.data_type = "string"
elif "<enumeration" in data_type:
new.enum_items = json.dumps(attribute.type_of_attribute().declared_type().enumeration_items())
new.data_type = "enum"
if data[attribute.name()]:
new.enum_value = data[attribute.name()]
props.active_document_id = self.document
return {"FINISHED"}
class DisableEditingDocument(bpy.types.Operator):
bl_idname = "bim.disable_editing_document"
bl_label = "Disable Editing Document"
def execute(self, context):
context.scene.BIMDocumentProperties.active_document_id = 0
return {"FINISHED"}
class AddInformation(bpy.types.Operator):
bl_idname = "bim.add_information"
bl_label = "Add Information"
def execute(self, context):
result = add_information.Usecase(IfcStore.get_file()).execute()
Data.load()
bpy.ops.bim.load_information()
bpy.ops.bim.enable_editing_document(document=result.id())
return {"FINISHED"}
class AddDocumentReference(bpy.types.Operator):
bl_idname = "bim.add_document_reference"
bl_label = "Add Document Reference"
def execute(self, context):
result = add_reference.Usecase(IfcStore.get_file()).execute()
Data.load()
bpy.ops.bim.load_document_references()
bpy.ops.bim.enable_editing_document(document=result.id())
return {"FINISHED"}
class EditInformation(bpy.types.Operator):
bl_idname = "bim.edit_information"
bl_label = "Edit Information"
def execute(self, context):
props = context.scene.BIMDocumentProperties
attributes = {}
for attribute in props.document_attributes:
if attribute.is_null:
attributes[attribute.name] = None
elif attribute.enum_items:
attributes[attribute.name] = attribute.enum_value
else:
attributes[attribute.name] = attribute.string_value
self.file = IfcStore.get_file()
edit_information.Usecase(
self.file, {"information": self.file.by_id(props.active_document_id), "attributes": attributes}
).execute()
Data.load()
bpy.ops.bim.load_information()
return {"FINISHED"}
class EditDocumentReference(bpy.types.Operator):
bl_idname = "bim.edit_document_reference"
bl_label = "Edit Document Reference"
def execute(self, context):
props = context.scene.BIMDocumentProperties
attributes = {}
for attribute in props.document_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_document_id), "attributes": attributes}
).execute()
Data.load()
bpy.ops.bim.load_document_references()
return {"FINISHED"}
class RemoveDocument(bpy.types.Operator):
bl_idname = "bim.remove_document"
bl_label = "Remove Document"
document: bpy.props.IntProperty()
def execute(self, context):
props = context.scene.BIMDocumentProperties
self.file = IfcStore.get_file()
remove_document.Usecase(self.file, {"document": self.file.by_id(self.document)}).execute()
Data.load()
if props.is_editing == "information":
bpy.ops.bim.load_information()
elif props.is_editing == "reference":
bpy.ops.bim.load_document_references()
return {"FINISHED"}
class EnableAssigningDocument(bpy.types.Operator):
bl_idname = "bim.enable_assigning_document"
bl_label = "Enable Assigning Document"
obj: bpy.props.StringProperty()
def execute(self, context):
obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
props = obj.BIMObjectDocumentProperties
if props.available_document_types == "IfcDocumentInformation":
bpy.ops.bim.load_information()
elif props.available_document_types == "IfcDocumentReference":
bpy.ops.bim.load_document_references()
props.is_adding = props.available_document_types
return {"FINISHED"}
class DisableAssigningDocument(bpy.types.Operator):
bl_idname = "bim.disable_assigning_document"
bl_label = "Disable Assigning Document"
obj: bpy.props.StringProperty()
def execute(self, context):
obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
props = obj.BIMObjectDocumentProperties
props.is_adding = ""
return {"FINISHED"}
class AssignDocument(bpy.types.Operator):
bl_idname = "bim.assign_document"
bl_label = "Assign Document"
obj: bpy.props.StringProperty()
document: bpy.props.IntProperty()
def execute(self, context):
obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
self.file = IfcStore.get_file()
assign_document.Usecase(self.file, {
"product": self.file.by_id(obj.BIMObjectProperties.ifc_definition_id),
"document": self.file.by_id(self.document)
}).execute()
Data.load(obj.BIMObjectProperties.ifc_definition_id)
return {"FINISHED"}
class UnassignDocument(bpy.types.Operator):
bl_idname = "bim.unassign_document"
bl_label = "Unassign Document"
obj: bpy.props.StringProperty()
document: bpy.props.IntProperty()
def execute(self, context):
obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
self.file = IfcStore.get_file()
unassign_document.Usecase(self.file, {
"product": self.file.by_id(obj.BIMObjectProperties.ifc_definition_id),
"document": self.file.by_id(self.document)
}).execute()
Data.load(obj.BIMObjectProperties.ifc_definition_id)
return {"FINISHED"}
@@ -0,0 +1,34 @@
import bpy
from blenderbim.bim.prop import StrProperty, Attribute
from bpy.types import PropertyGroup
from bpy.props import (
PointerProperty,
StringProperty,
EnumProperty,
BoolProperty,
IntProperty,
FloatProperty,
FloatVectorProperty,
CollectionProperty,
)
class Document(PropertyGroup):
name: StringProperty(name="Name")
identification: StringProperty(name="Identification")
ifc_definition_id: IntProperty(name="IFC Definition ID")
class BIMDocumentProperties(PropertyGroup):
document_attributes: CollectionProperty(name="Document Attributes", type=Attribute)
active_document_id: IntProperty(name="Active Document Id")
documents: CollectionProperty(name="Documents", type=Document)
active_document_index: IntProperty(name="Active Document Index")
is_editing: StringProperty(name="Is Editing")
class BIMObjectDocumentProperties(PropertyGroup):
is_adding: StringProperty(name="Is Adding")
available_document_types: EnumProperty(
items=[(d, d, "") for d in ["IfcDocumentInformation", "IfcDocumentReference"]], name="Available Document Types"
)
@@ -0,0 +1,12 @@
class Usecase:
def __init__(self, file, settings=None):
self.file = file
self.settings = {"document": None}
for key, value in settings.items():
self.settings[key] = value
def execute(self):
self.file.remove(self.settings["document"])
for rel in self.file.by_type("IfcRelAssociatesDocument"):
if not rel.RelatingDocument:
self.file.remove(rel)
@@ -0,0 +1,163 @@
from bpy.types import Panel, UIList
from blenderbim.bim.ifc import IfcStore
from blenderbim.bim.module.document.data import Data
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 = "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.BIMDocumentProperties
if not self.props.is_editing or self.props.is_editing == "information":
row = self.layout.row(align=True)
row.label(text="{} Documents Found".format(len(Data.information)), icon="FILE")
if self.props.is_editing == "information":
row.operator("bim.add_information", text="", icon="ADD")
row.operator("bim.disable_document_editing_ui", text="", icon="X")
else:
row.operator("bim.load_information", text="", icon="IMPORT")
if not self.props.is_editing or self.props.is_editing == "reference":
row = self.layout.row(align=True)
row.label(text="{} References Found".format(len(Data.references)), icon="FILE_HIDDEN")
if self.props.is_editing == "reference":
row.operator("bim.add_document_reference", text="", icon="ADD")
row.operator("bim.disable_document_editing_ui", text="", icon="X")
else:
row.operator("bim.load_document_references", text="", icon="IMPORT")
if self.props.is_editing:
self.layout.template_list(
"BIM_UL_documents",
"",
self.props,
"documents",
self.props,
"active_document_index",
)
if self.props.active_document_id:
self.draw_editable_ui(context)
def draw_editable_ui(self, context):
for attribute in self.props.document_attributes:
row = self.layout.row(align=True)
if attribute.data_type == "string":
row.prop(attribute, "string_value", text=attribute.name)
elif attribute.data_type == "enum":
row.prop(attribute, "enum_value", text=attribute.name)
if attribute.is_optional:
row.prop(attribute, "is_null", icon="RADIOBUT_OFF" if attribute.is_null else "RADIOBUT_ON", text="")
class BIM_PT_object_documents(Panel):
bl_label = "IFC Documents"
bl_idname = "BIM_PT_object_documents"
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.BIMDocumentProperties
self.props = obj.BIMObjectDocumentProperties
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()
document_ids = Data.products[self.oprops.ifc_definition_id]
if not document_ids:
row = self.layout.row(align=True)
row.label(text="No Documents", icon="FILE")
for document_id in document_ids:
try:
document = Data.information[document_id]
document_type = "IfcDocumentInformation"
icon = "FILE"
except:
document = Data.references[document_id]
document_type = "IfcDocumentReference"
icon = "FILE_HIDDEN"
row = self.layout.row(align=True)
if self.file.schema == "IFC2X3":
if document_type == "IfcDocumentInformation":
row.label(text=document.get("DocumentId") or "*", icon=icon)
elif document_type == "IfcDocumentReference":
row.label(text=document.get("ItemReference") or "*", icon=icon)
else:
row.label(text=document.get("Identification") or "*", icon=icon)
row.label(text=document.get("Name") or "Unnamed")
row.operator("bim.unassign_document", text="", icon="X").document = document_id
def draw_add_ui(self):
if self.props.is_adding:
row = self.layout.row(align=True)
icon = "FILE" if self.props.is_adding == "IfcDocumentInformation" else "FILE_HIDDEN"
row.label(text="Adding {}".format(self.props.is_adding), icon=icon)
row.operator("bim.disable_assigning_document", text="", icon="X")
self.layout.template_list(
"BIM_UL_object_documents",
"",
self.sprops,
"documents",
self.sprops,
"active_document_index",
)
else:
row = self.layout.row(align=True)
row.prop(self.props, "available_document_types", text="")
row.operator("bim.enable_assigning_document", text="", icon="ADD")
class BIM_UL_documents(UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
if item:
row = layout.row(align=True)
row.label(text=item.identification)
row.label(text=item.name)
if context.scene.BIMDocumentProperties.active_document_id == item.ifc_definition_id:
if context.scene.BIMDocumentProperties.is_editing == "information":
row.operator("bim.edit_information", text="", icon="CHECKMARK")
elif context.scene.BIMDocumentProperties.is_editing == "reference":
row.operator("bim.edit_document_reference", text="", icon="CHECKMARK")
row.operator("bim.disable_editing_document", text="", icon="X")
elif context.scene.BIMDocumentProperties.active_document_id:
row.operator("bim.remove_document", text="", icon="X").document = item.ifc_definition_id
else:
op = row.operator("bim.enable_editing_document", text="", icon="GREASEPENCIL")
op.document = item.ifc_definition_id
row.operator("bim.remove_document", text="", icon="X").document = item.ifc_definition_id
class BIM_UL_object_documents(UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
if item:
row = layout.row(align=True)
row.label(text=item.identification)
row.label(text=item.name)
row.operator("bim.assign_document", text="", icon="ADD").document = item.ifc_definition_id
@@ -0,0 +1,17 @@
import ifcopenshell
class Usecase:
def __init__(self, file, settings=None):
self.file = file
self.settings = {
"product": None,
"document": None,
}
for key, value in settings.items():
self.settings[key] = value
def execute(self):
for rel in self.settings["product"].HasAssociations:
if rel.is_a("IfcRelAssociatesDocument") and rel.RelatingDocument == self.settings["document"]:
self.file.remove(rel)
@@ -1,22 +1,27 @@
import bpy
from . import ui, operator
from . import ui, prop, operator
classes = (
operator.EditObjectPlacement,
operator.AddRepresentation,
operator.MapRepresentation,
operator.SwitchRepresentation,
operator.RemoveRepresentation,
operator.UpdateMeshRepresentation,
operator.UpdateParametricRepresentation,
operator.GetRepresentationIfcParameters,
prop.BIMGeometryProperties,
ui.BIM_PT_representations,
ui.BIM_PT_mesh,
ui.BIM_PT_workarounds,
)
def register():
bpy.types.Scene.BIMGeometryProperties = bpy.props.PointerProperty(type=prop.BIMGeometryProperties)
bpy.types.OBJECT_PT_transform.append(ui.BIM_PT_transform)
def unregister():
bpy.types.OBJECT_PT_transform.remove(ui.BIM_PT_transform)
del bpy.types.Scene.BIMGeometryProperties
@@ -1,3 +1,5 @@
import bpy
import bmesh
import ifcopenshell.util.unit
@@ -7,10 +9,12 @@ class Usecase:
self.file = file
self.settings = {
"context": None, # IfcGeometricRepresentationContext
"blender_object": None, # This is (currently) a Blender object, hence this depends on Blender now
"geometry": None, # This is (currently) a Blender data object, hence this depends on Blender now
"total_items": 1, # How many representation items to create
"unit_scale": None, # A scale factor to apply for all vectors in case the unit is different
"should_force_faceted_brep": False, # If we should force faceted breps for meshes
"should_force_triangulation": False, # If we should force triangulation for meshes
"is_wireframe": False, # If the geometry is a wireframe
"is_curve": False, # If the geometry is a Blender curve
"is_point_cloud": False, # If the geometry is a point cloud
@@ -20,6 +24,7 @@ class Usecase:
self.settings[key] = value
def execute(self):
self.evaluate_geometry()
if self.settings["unit_scale"] is None:
self.settings["unit_scale"] = ifcopenshell.util.unit.calculate_unit_scale(self.file)
if self.settings["context"].ContextType == "Model":
@@ -28,6 +33,34 @@ class Usecase:
return self.create_plan_representation()
return self.create_variable_representation()
def evaluate_geometry(self):
self.boolean_modifiers = []
for modifier in self.settings["blender_object"].modifiers:
if not modifier.type == "BOOLEAN":
continue
modifier_data = {}
for name in ["operation", "operand_type", "object", "solver", "use_self"]:
modifier_data[name] = getattr(modifier, name)
self.boolean_modifiers.append(modifier_data)
self.settings["blender_object"].modifiers.remove(modifier)
if self.settings["should_force_triangulation"]:
mesh = self.settings["blender_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
self.settings["geometry"] = mesh
else:
self.settings["geometry"] = self.settings["blender_object"].evaluated_get(bpy.context.evaluated_depsgraph_get()).to_mesh()
for modifier in self.boolean_modifiers:
new = self.settings["blender_object"].modifiers.new("IfcOpeningElement", "BOOLEAN")
for key, value in modifier.items():
setattr(new, key, value)
def create_model_representation(self):
if self.settings["context"].is_a() == "IfcGeometricRepresentationContext":
return self.create_variable_representation()
@@ -1,18 +1,34 @@
class Usecase:
def __init__(self, file, settings=None):
self.file = file
self.settings = {
"product": None,
"representation": None
}
self.settings = {"product": None, "representation": None}
for key, value in settings.items():
self.settings[key] = value
def execute(self):
definition = self.settings["product"].Representation
if not definition:
definition = self.file.createIfcProductDefinitionShape()
self.settings["product"].Representation = definition
representations = list(definition.Representations) if definition.Representations else []
representations.append(self.settings["representation"])
definition.Representations = representations
if self.settings["product"].is_a("IfcProduct"):
definition = self.settings["product"].Representation
if not definition:
definition = self.file.createIfcProductDefinitionShape()
self.settings["product"].Representation = definition
representations = list(definition.Representations) if definition.Representations else []
representations.append(self.settings["representation"])
definition.Representations = representations
elif self.settings["product"].is_a("IfcTypeProduct"):
if self.settings["product"].RepresentationMaps:
maps = list(self.settings["product"].RepresentationMaps)
else:
maps = []
self.zero = self.file.createIfcCartesianPoint((0.0, 0.0, 0.0))
self.x_axis = self.file.createIfcDirection((1.0, 0.0, 0.0))
self.z_axis = self.file.createIfcDirection((0.0, 0.0, 1.0))
maps.append(
self.file.create_entity(
"IfcRepresentationMap",
**{
"MappingOrigin": self.file.createIfcAxis2Placement3D(self.zero, self.z_axis, self.x_axis),
"MappedRepresentation": self.settings["representation"],
}
)
)
self.settings["product"].RepresentationMaps = maps
@@ -39,7 +39,11 @@ class Usecase:
ifcopenshell.util.element.replace_attribute(
inverse, self.settings["product"].ObjectPlacement, placement
)
self.file.remove(self.settings["product"].ObjectPlacement)
old = self.settings["product"].ObjectPlacement
old.PlacementRelTo = None
self.settings["product"].ObjectPlacement = None
if not self.file.get_inverse(old):
ifcopenshell.util.element.remove_deep(self.file, old)
self.settings["product"].ObjectPlacement = placement
for settings in dependent_objects:
@@ -0,0 +1,43 @@
class Usecase:
def __init__(self, file, settings=None):
self.file = file
self.settings = {
"product": None,
"representation": None,
}
self.ifc_vertices = []
for key, value in settings.items():
self.settings[key] = value
def execute(self):
self.zero = self.file.createIfcCartesianPoint((0.0, 0.0, 0.0))
self.x_axis = self.file.createIfcDirection((1.0, 0.0, 0.0))
self.y_axis = self.file.createIfcDirection((0.0, 1.0, 0.0))
self.z_axis = self.file.createIfcDirection((0.0, 0.0, 1.0))
self.get_mapping_source()
mapping_target = self.file.createIfcCartesianTransformationOperator3D(
self.x_axis, self.y_axis, self.zero, 1, self.z_axis
)
mapped_item = self.file.createIfcMappedItem(self.get_mapping_source(), mapping_target)
return self.file.create_entity(
"IfcShapeRepresentation",
**{
"ContextOfItems": self.settings["representation"].ContextOfItems,
"RepresentationIdentifier": self.settings["representation"].RepresentationIdentifier,
"RepresentationType": "MappedRepresentation",
"Items": [mapped_item],
}
)
def get_mapping_source(self):
if self.settings["representation"].RepresentationMap:
return self.settings["representation"].RepresentationMap[0]
return self.file.create_entity(
"IfcRepresentationMap",
**{
"MappingOrigin": self.file.createIfcAxis2Placement3D(self.zero, self.z_axis, self.x_axis),
"MappedRepresentation": self.settings["representation"],
}
)
@@ -4,6 +4,7 @@ import ifcopenshell
import logging
import blenderbim.bim.module.geometry.edit_object_placement as edit_object_placement
import blenderbim.bim.module.geometry.add_representation as add_representation
import blenderbim.bim.module.geometry.map_representation as map_representation
import blenderbim.bim.module.geometry.assign_styles as assign_styles
import blenderbim.bim.module.geometry.assign_representation as assign_representation
import blenderbim.bim.module.geometry.remove_representation as remove_representation
@@ -18,31 +19,34 @@ class EditObjectPlacement(bpy.types.Operator):
obj: bpy.props.StringProperty()
def execute(self, context):
obj = bpy.data.objects.get(self.obj) if self.obj else bpy.context.active_object
objs = [bpy.data.objects.get(self.obj)] if self.obj else bpy.context.selected_objects
self.file = IfcStore.get_file()
# TODO: determine how to deal with this module dependency
props = bpy.context.scene.BIMGeoreferenceProperties
matrix = np.array(obj.matrix_world)
if props.has_blender_offset and props.blender_offset_type == "OBJECT_PLACEMENT":
self.calculate_unit_scale()
# TODO: np.array? Why not matrix?
matrix = np.array(
ifcopenshell.util.geolocation.local2global(
np.matrix(obj.matrix_world),
float(props.blender_eastings) * self.unit_scale,
float(props.blender_northings) * self.unit_scale,
float(props.blender_orthogonal_height) * self.unit_scale,
float(props.blender_x_axis_abscissa),
float(props.blender_x_axis_ordinate),
for obj in objs:
if not obj.BIMObjectProperties.ifc_definition_id:
continue
matrix = np.array(obj.matrix_world)
if props.has_blender_offset and props.blender_offset_type == "OBJECT_PLACEMENT":
self.calculate_unit_scale()
# TODO: np.array? Why not matrix?
matrix = np.array(
ifcopenshell.util.geolocation.local2global(
np.matrix(obj.matrix_world),
float(props.blender_eastings) * self.unit_scale,
float(props.blender_northings) * self.unit_scale,
float(props.blender_orthogonal_height) * self.unit_scale,
float(props.blender_x_axis_abscissa),
float(props.blender_x_axis_ordinate),
)
)
)
edit_object_placement.Usecase(
self.file,
{
"product": self.file.by_id(obj.BIMObjectProperties.ifc_definition_id),
"matrix": matrix,
},
).execute()
edit_object_placement.Usecase(
self.file,
{
"product": self.file.by_id(obj.BIMObjectProperties.ifc_definition_id),
"matrix": matrix,
},
).execute()
return {"FINISHED"}
def calculate_unit_scale(self):
@@ -76,8 +80,11 @@ class AddRepresentation(bpy.types.Operator):
self.file,
{
"context": self.file.by_id(context_id),
"blender_object": obj,
"geometry": obj.data,
"total_items": max(1, len(obj.material_slots)),
"should_force_faceted_brep": context.scene.BIMGeometryProperties.should_force_faceted_brep,
"should_force_triangulation": context.scene.BIMGeometryProperties.should_force_triangulation,
},
).execute()
if not result:
@@ -92,6 +99,7 @@ class AddRepresentation(bpy.types.Operator):
for s in obj.material_slots
if s.material
],
"should_use_presentation_style_assignment": context.scene.BIMGeometryProperties.should_use_presentation_style_assignment,
},
).execute()
assign_representation.Usecase(
@@ -167,50 +175,90 @@ class RemoveRepresentation(bpy.types.Operator):
return {"FINISHED"}
class MapRepresentation(bpy.types.Operator):
bl_idname = "bim.map_representation"
bl_label = "Map Representation"
obj: bpy.props.StringProperty()
obj_data: bpy.props.StringProperty()
def execute(self, context):
objs = [bpy.data.objects.get(self.obj)] if self.obj else bpy.context.selected_objects
obj_data = bpy.data.meshes.get(self.obj_data) if self.obj_data else bpy.context.active_object.data
objs = [o for o in objs if o.data != obj_data]
self.file = IfcStore.get_file()
for obj in objs:
bpy.ops.bim.edit_object_placement(obj=obj.name)
product = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id)
if obj.data.BIMMeshProperties.ifc_definition_id:
old_representation = self.file.by_id(obj.data.BIMMeshProperties.ifc_definition_id)
else:
old_representation = None
target_representation = self.file.by_id(obj_data.BIMMeshProperties.ifc_definition_id)
obj.data = obj_data
result = map_representation.Usecase(
self.file,
{
"product": product,
"representation": target_representation,
},
).execute()
assign_representation.Usecase(self.file, {"product": product, "representation": result}).execute()
if old_representation:
bpy.ops.bim.remove_representation(ifc_definition_id=old_representation.id())
Data.load(obj.BIMObjectProperties.ifc_definition_id)
return {"FINISHED"}
class UpdateMeshRepresentation(bpy.types.Operator):
bl_idname = "bim.update_mesh_representation"
bl_label = "Update Mesh Representation"
obj: bpy.props.StringProperty()
def execute(self, context):
obj = bpy.data.objects.get(self.obj) if self.obj else bpy.context.active_object
objs = [bpy.data.objects.get(self.obj)] if self.obj else bpy.context.selected_objects
self.file = IfcStore.get_file()
bpy.ops.bim.edit_object_placement(obj=obj.name)
for obj in objs:
bpy.ops.bim.edit_object_placement(obj=obj.name)
old_representation = self.file.by_id(obj.data.BIMMeshProperties.ifc_definition_id)
new_representation = add_representation.Usecase(
self.file,
{
"context": old_representation.ContextOfItems,
"geometry": obj.data,
"total_items": max(1, len(obj.material_slots)),
},
).execute()
if not new_representation:
print("Failed to write shape representation")
return {"FINISHED"}
old_representation = self.file.by_id(obj.data.BIMMeshProperties.ifc_definition_id)
new_representation = add_representation.Usecase(
self.file,
{
"context": old_representation.ContextOfItems,
"blender_object": obj,
"geometry": obj.data,
"total_items": max(1, len(obj.material_slots)),
"should_force_faceted_brep": context.scene.BIMGeometryProperties.should_force_faceted_brep,
"should_force_triangulation": context.scene.BIMGeometryProperties.should_force_triangulation,
},
).execute()
if not new_representation:
print("Failed to write shape representation")
return {"FINISHED"}
assign_styles.Usecase(
self.file,
{
"shape_representation": new_representation,
"styles": [
self.file.by_id(s.material.BIMMaterialProperties.ifc_style_id)
for s in obj.material_slots
if s.material
],
},
).execute()
assign_styles.Usecase(
self.file,
{
"shape_representation": new_representation,
"styles": [
self.file.by_id(s.material.BIMMaterialProperties.ifc_style_id)
for s in obj.material_slots
if s.material
],
"should_use_presentation_style_assignment": context.scene.BIMGeometryProperties.should_use_presentation_style_assignment,
},
).execute()
# TODO: move this into a replace_representation usecase or something
for inverse in self.file.get_inverse(old_representation):
ifcopenshell.util.element.replace_attribute(inverse, old_representation, new_representation)
# TODO: move this into a replace_representation usecase or something
for inverse in self.file.get_inverse(old_representation):
ifcopenshell.util.element.replace_attribute(inverse, old_representation, new_representation)
obj.data.BIMMeshProperties.ifc_definition_id = int(new_representation.id())
obj.data.name = f"{old_representation.ContextOfItems.id()}/{new_representation.id()}"
bpy.ops.bim.remove_representation(ifc_definition_id=old_representation.id())
Data.load(obj.BIMObjectProperties.ifc_definition_id)
obj.data.BIMMeshProperties.ifc_definition_id = int(new_representation.id())
obj.data.name = f"{old_representation.ContextOfItems.id()}/{new_representation.id()}"
bpy.ops.bim.remove_representation(ifc_definition_id=old_representation.id())
Data.load(obj.BIMObjectProperties.ifc_definition_id)
return {"FINISHED"}
@@ -0,0 +1,22 @@
import bpy
from blenderbim.bim.prop import StrProperty, Attribute
from bpy.types import PropertyGroup
from bpy.props import (
PointerProperty,
StringProperty,
EnumProperty,
BoolProperty,
IntProperty,
FloatProperty,
FloatVectorProperty,
CollectionProperty,
)
class BIMGeometryProperties(PropertyGroup):
# Revit workaround
should_use_presentation_style_assignment: BoolProperty(name="Force Presentation Style Assignment", default=False)
# RIB iTwo, DESITE BIM workaround
should_force_faceted_brep: BoolProperty(name="Force Faceted Breps", default=False)
# Navisworks workaround
should_force_triangulation: BoolProperty(name="Force Triangulation", default=False)
@@ -1,3 +1,5 @@
import ifcopenshell.util.element
class Usecase:
def __init__(self, file, settings=None):
self.file = file
@@ -6,17 +8,29 @@ class Usecase:
self.settings[key] = value
def execute(self):
styles = []
dummy_context = self.file.create_entity("IfcRepresentationContext")
for subelement in self.file.traverse(self.settings["representation"]):
if subelement.is_a("IfcRepresentationItem") and subelement.StyledByItem:
styles.append(subelement)
for style in styles:
self.remove_deep(style)
self.remove_deep(self.settings["representation"])
[self.file.remove(s) for s in subelement.StyledByItem]
elif subelement.is_a("IfcRepresentation"):
subelement.ContextOfItems = dummy_context
self.purge_representation_inverses(subelement)
self.purge_representation_inverses(self.settings["representation"])
ifcopenshell.util.element.remove_deep(self.file, self.settings["representation"])
def remove_deep(self, element):
subgraph = list(self.file.traverse(element))
subgraph_set = set(subgraph)
for ref in subgraph[::-1]:
if ref.id() and len(set(self.file.get_inverse(ref)) - subgraph_set) == 0:
self.file.remove(ref)
def purge_representation_inverses(self, element):
for inverse in self.file.get_inverse(element):
if inverse.is_a("IfcPresentationLayerAssignment"):
assigned_items = set(inverse.AssignedItems)
if len(assigned_items) == 1:
self.file.remove(inverse)
else:
assigned_items.remove(element)
inverse.AssignedItems == list(assigned_items)
elif inverse.is_a("IfcProductRepresentation"):
representations = set(inverse.Representations)
if len(representations) == 1:
self.file.remove(inverse)
else:
representations.remove(element)
inverse.Representations = list(representations)
@@ -1,6 +1,7 @@
import bpy
from bpy.types import Panel
from blenderbim.bim.module.geometry.data import Data
from blenderbim.bim.ifc import IfcStore
class BIM_PT_representations(Panel):
@@ -11,6 +12,10 @@ class BIM_PT_representations(Panel):
bl_region_type = "WINDOW"
bl_context = "object"
@classmethod
def poll(cls, context):
return IfcStore.get_file()
def draw(self, context):
layout = self.layout
props = context.active_object.BIMObjectProperties
@@ -50,6 +55,7 @@ class BIM_PT_mesh(Panel):
context.active_object is not None
and context.active_object.type == "MESH"
and hasattr(context.active_object.data, "BIMMeshProperties")
and context.active_object.data.BIMMeshProperties.ifc_definition_id
)
def draw(self, context):
@@ -62,6 +68,8 @@ class BIM_PT_mesh(Panel):
row.operator("bim.get_representation_ifc_parameters")
row = layout.row()
row.operator("bim.update_mesh_representation")
row = layout.row()
row.operator("bim.map_representation")
for index, ifc_parameter in enumerate(props.ifc_parameters):
row = layout.row(align=True)
row.prop(ifc_parameter, "name", text="")
@@ -73,3 +81,30 @@ def BIM_PT_transform(self, context):
if context.active_object and context.active_object.BIMObjectProperties.ifc_definition_id:
row = self.layout.row()
row.operator("bim.edit_object_placement")
class BIM_PT_workarounds(Panel):
bl_label = "IFC Vendor Workarounds"
bl_idname = "BIM_PT_workarounds"
bl_options = {"DEFAULT_CLOSED"}
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")
and context.active_object.data.BIMMeshProperties.ifc_definition_id
)
def draw(self, context):
props = context.scene.BIMGeometryProperties
row = self.layout.row()
row.prop(props, "should_force_faceted_brep")
row = self.layout.row()
row.prop(props, "should_force_triangulation")
row = self.layout.row()
row.prop(props, "should_use_presentation_style_assignment")
@@ -0,0 +1,26 @@
import bpy
from . import ui, prop, operator
classes = (
operator.LoadLayers,
operator.DisableLayerEditingUI,
operator.EnableEditingLayer,
operator.DisableEditingLayer,
operator.AddPresentationLayer,
operator.EditPresentationLayer,
operator.RemovePresentationLayer,
operator.AssignPresentationLayer,
operator.UnassignPresentationLayer,
prop.Layer,
prop.BIMLayerProperties,
ui.BIM_PT_layers,
ui.BIM_UL_layers,
)
def register():
bpy.types.Scene.BIMLayerProperties = bpy.props.PointerProperty(type=prop.BIMLayerProperties)
def unregister():
del bpy.types.Scene.BIMLayerProperties
@@ -0,0 +1,13 @@
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("IfcPresentationLayerAssignment", **{
"Name": "Unnamed"
})
@@ -0,0 +1,17 @@
import ifcopenshell
class Usecase:
def __init__(self, file, settings=None):
self.file = file
self.settings = {
"item": None,
"layer": None,
}
for key, value in settings.items():
self.settings[key] = value
def execute(self):
assigned_items = set(self.settings["layer"].AssignedItems) or set()
assigned_items.add(self.settings["item"])
self.settings["layer"].AssignedItems = list(assigned_items)
@@ -0,0 +1,30 @@
import ifcopenshell
import ifcopenshell.util.date
from blenderbim.bim.ifc import IfcStore
from datetime import datetime
class Data:
is_loaded = False
items = {}
layers = {}
@classmethod
def load(cls, item_id=None):
cls._file = IfcStore.get_file()
if not cls._file:
return
cls.load_layers()
cls.is_loaded = True
@classmethod
def load_layers(cls):
cls.layers = {}
cls.items = {}
for layer in cls._file.by_type("IfcPresentationLayerAssignment"):
data = layer.get_info()
if layer.AssignedItems:
for item in layer.AssignedItems:
cls.items.setdefault(item.id(), []).append(layer.id())
del data["AssignedItems"]
cls.layers[layer.id()] = data
@@ -0,0 +1,13 @@
class Usecase:
def __init__(self, file, settings=None):
self.file = file
self.settings = {
"layer": 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["layer"], name, value)
@@ -0,0 +1,151 @@
import bpy
import json
import blenderbim.bim.module.layer.add_layer as add_layer
import blenderbim.bim.module.layer.edit_layer as edit_layer
import blenderbim.bim.module.layer.remove_layer as remove_layer
import blenderbim.bim.module.layer.assign_layer as assign_layer
import blenderbim.bim.module.layer.unassign_layer as unassign_layer
from blenderbim.bim.ifc import IfcStore
from blenderbim.bim.module.layer.data import Data
class LoadLayers(bpy.types.Operator):
bl_idname = "bim.load_layers"
bl_label = "Load Layers"
def execute(self, context):
self.file = IfcStore.get_file()
props = context.scene.BIMLayerProperties
while len(props.layers) > 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 "<entity" in data_type:
continue
new = props.layer_attributes.add()
new.name = attribute.name()
new.is_null = data[attribute.name()] is None
new.is_optional = attribute.optional()
new.string_value = "" if new.is_null else data[attribute.name()]
props.active_layer_id = self.layer
return {"FINISHED"}
class DisableEditingLayer(bpy.types.Operator):
bl_idname = "bim.disable_editing_layer"
bl_label = "Disable Editing Layer"
def execute(self, context):
context.scene.BIMLayerProperties.active_layer_id = 0
return {"FINISHED"}
class AddPresentationLayer(bpy.types.Operator):
bl_idname = "bim.add_presentation_layer"
bl_label = "Add Layer"
def execute(self, context):
result = add_layer.Usecase(IfcStore.get_file()).execute()
Data.load()
bpy.ops.bim.load_layers()
bpy.ops.bim.enable_editing_layer(layer=result.id())
return {"FINISHED"}
class EditPresentationLayer(bpy.types.Operator):
bl_idname = "bim.edit_presentation_layer"
bl_label = "Edit Layer"
def execute(self, context):
props = context.scene.BIMLayerProperties
attributes = {}
for attribute in props.layer_attributes:
if attribute.is_null:
attributes[attribute.name] = None
else:
attributes[attribute.name] = attribute.string_value
self.file = IfcStore.get_file()
edit_layer.Usecase(
self.file, {"layer": self.file.by_id(props.active_layer_id), "attributes": attributes}
).execute()
Data.load()
bpy.ops.bim.load_layers()
return {"FINISHED"}
class RemovePresentationLayer(bpy.types.Operator):
bl_idname = "bim.remove_presentation_layer"
bl_label = "Remove Presentation Layer"
layer: bpy.props.IntProperty()
def execute(self, context):
props = context.scene.BIMLayerProperties
self.file = IfcStore.get_file()
remove_layer.Usecase(self.file, {"layer": self.file.by_id(self.layer)}).execute()
Data.load()
bpy.ops.bim.load_layers()
return {"FINISHED"}
class AssignPresentationLayer(bpy.types.Operator):
bl_idname = "bim.assign_presentation_layer"
bl_label = "Assign Presentation Layer"
item: bpy.props.StringProperty()
layer: bpy.props.IntProperty()
def execute(self, context):
item = bpy.data.meshes.get(self.item) if self.item else context.active_object.data
self.file = IfcStore.get_file()
assign_layer.Usecase(self.file, {
"item": self.file.by_id(item.BIMMeshProperties.ifc_definition_id),
"layer": self.file.by_id(self.layer)
}).execute()
Data.load()
return {"FINISHED"}
class UnassignPresentationLayer(bpy.types.Operator):
bl_idname = "bim.unassign_presentation_layer"
bl_label = "Unassign Presentation Layer"
item: bpy.props.StringProperty()
layer: bpy.props.IntProperty()
def execute(self, context):
item = bpy.data.meshes.get(self.item) if self.item else context.active_object.data
self.file = IfcStore.get_file()
unassign_layer.Usecase(self.file, {
"item": self.file.by_id(item.BIMMeshProperties.ifc_definition_id),
"layer": self.file.by_id(self.layer)
}).execute()
Data.load()
return {"FINISHED"}
@@ -0,0 +1,26 @@
import bpy
from blenderbim.bim.prop import StrProperty, Attribute
from bpy.types import PropertyGroup
from bpy.props import (
PointerProperty,
StringProperty,
EnumProperty,
BoolProperty,
IntProperty,
FloatProperty,
FloatVectorProperty,
CollectionProperty,
)
class Layer(PropertyGroup):
name: StringProperty(name="Name")
ifc_definition_id: IntProperty(name="IFC Definition ID")
class BIMLayerProperties(PropertyGroup):
layer_attributes: CollectionProperty(name="Layer Attributes", type=Attribute)
active_layer_id: IntProperty(name="Active Layer Id")
layers: CollectionProperty(name="Layers", type=Layer)
active_layer_index: IntProperty(name="Active Layer Index")
is_editing: BoolProperty(name="Is Editing", default=False)
@@ -0,0 +1,9 @@
class Usecase:
def __init__(self, file, settings=None):
self.file = file
self.settings = {"layer": None}
for key, value in settings.items():
self.settings[key] = value
def execute(self):
self.file.remove(self.settings["layer"])
@@ -0,0 +1,86 @@
from bpy.types import Panel, UIList, Mesh
from blenderbim.bim.ifc import IfcStore
from blenderbim.bim.module.layer.data import Data
class BIM_PT_layers(Panel):
bl_label = "IFC Presentation Layers"
bl_idname = "BIM_PT_layers"
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.BIMLayerProperties
if Data.layers:
row = self.layout.row(align=True)
row.label(text="{} Layers Found".format(len(Data.layers.keys())))
if self.props.is_editing:
row.operator("bim.add_presentation_layer", text="", icon="ADD")
row.operator("bim.disable_layer_editing_ui", text="", icon="X")
else:
row.operator("bim.load_layers", text="", icon="IMPORT")
else:
row = self.layout.row(align=True)
row.label(text="No Layers")
if self.props.is_editing:
self.layout.template_list(
"BIM_UL_layers",
"",
self.props,
"layers",
self.props,
"active_layer_index",
)
if self.props.active_layer_id:
self.draw_editable_ui(context)
def draw_editable_ui(self, context):
for attribute in self.props.layer_attributes:
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="")
class BIM_UL_layers(UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
if item:
row = layout.row(align=True)
row.label(text=item.name)
if context.active_object and isinstance(context.active_object.data, Mesh):
mprops = context.active_object.data.BIMMeshProperties
if (
mprops.ifc_definition_id in Data.items
and item.ifc_definition_id in Data.items[mprops.ifc_definition_id]
):
op = row.operator("bim.unassign_presentation_layer", text="", icon="KEYFRAME_HLT", emboss=False)
op.layer = item.ifc_definition_id
else:
op = row.operator("bim.assign_presentation_layer", text="", icon="KEYFRAME", emboss=False)
op.layer = item.ifc_definition_id
row.operator("bim.disable_editing_layer", text="", icon="HIDE_OFF", emboss=False)
row.operator("bim.disable_editing_layer", text="", icon="FREEZE", emboss=False)
if context.scene.BIMLayerProperties.active_layer_id == item.ifc_definition_id:
row.operator("bim.edit_presentation_layer", text="", icon="CHECKMARK")
row.operator("bim.disable_editing_layer", text="", icon="X")
elif context.scene.BIMLayerProperties.active_layer_id:
row.operator("bim.remove_presentation_layer", text="", icon="X").layer = item.ifc_definition_id
else:
op = row.operator("bim.enable_editing_layer", text="", icon="GREASEPENCIL")
op.layer = item.ifc_definition_id
row.operator("bim.remove_presentation_layer", text="", icon="X").layer = item.ifc_definition_id
@@ -0,0 +1,17 @@
import ifcopenshell
class Usecase:
def __init__(self, file, settings=None):
self.file = file
self.settings = {
"item": None,
"layer": None,
}
for key, value in settings.items():
self.settings[key] = value
def execute(self):
assigned_items = set(self.settings["layer"].AssignedItems) or set()
assigned_items.remove(self.settings["item"])
self.settings["layer"].AssignedItems = list(assigned_items)
@@ -2,6 +2,8 @@ import bpy
from . import ui, prop, operator
classes = (
operator.AddMaterial,
operator.RemoveMaterial,
operator.AssignMaterial,
operator.UnassignMaterial,
operator.AddConstituent,
@@ -17,6 +19,7 @@ classes = (
operator.DisableEditingMaterialSetItem,
operator.EditMaterialSetItem,
prop.BIMObjectMaterialProperties,
ui.BIM_PT_material,
ui.BIM_PT_object_material,
)
@@ -0,0 +1,12 @@
import ifcopenshell
class Usecase:
def __init__(self, file, settings=None):
self.file = file
self.settings = {"Name": "Unnamed"}
for key, value in settings.items():
self.settings[key] = value
def execute(self):
return self.file.create_entity("IfcMaterial", **{"Name": self.settings["Name"] or "Unnamed"})
@@ -31,25 +31,33 @@ class Data:
@classmethod
def load_materials(cls):
cls.materials = {}
cls.load_element("IfcMaterial", cls.materials)
@classmethod
def load_constituents(cls):
cls.constituent_sets = {}
cls.constituents = {}
cls.load_element("IfcMaterialConstituent", cls.constituents)
cls.load_element("IfcMaterialConstituentSet", cls.constituent_sets)
@classmethod
def load_layers(cls):
cls.layer_sets = {}
cls.layers = {}
cls.load_element("IfcMaterialLayer", cls.layers)
cls.load_element("IfcMaterialLayerSet", cls.layer_sets)
@classmethod
def load_profiles(cls):
cls.profile_sets = {}
cls.profiles = {}
cls.load_element("IfcMaterialProfile", cls.profiles)
cls.load_element("IfcMaterialProfileSet", cls.profile_sets)
@classmethod
def load_lists(cls):
cls.lists = {}
cls.load_element("IfcMaterialList", cls.lists)
@classmethod
@@ -1,4 +1,6 @@
import bpy
import blenderbim.bim.module.material.add_material as add_material
import blenderbim.bim.module.material.remove_material as remove_material
import blenderbim.bim.module.material.assign_material as assign_material
import blenderbim.bim.module.material.unassign_material as unassign_material
import blenderbim.bim.module.material.add_constituent as add_constituent
@@ -13,6 +15,36 @@ from blenderbim.bim.ifc import IfcStore
from blenderbim.bim.module.material.data import Data
class AddMaterial(bpy.types.Operator):
bl_idname = "bim.add_material"
bl_label = "Add Material"
obj: bpy.props.StringProperty()
def execute(self, context):
obj = bpy.data.materials.get(self.obj) if self.obj else bpy.context.active_object.active_material
self.file = IfcStore.get_file()
result = add_material.Usecase(self.file, {"Name": obj.name}).execute()
obj.BIMObjectProperties.ifc_definition_id = result.id()
Data.load()
return {"FINISHED"}
class RemoveMaterial(bpy.types.Operator):
bl_idname = "bim.remove_material"
bl_label = "Remove Material"
obj: bpy.props.StringProperty()
def execute(self, context):
obj = bpy.data.materials.get(self.obj) if self.obj else bpy.context.active_object.active_material
self.file = IfcStore.get_file()
result = remove_material.Usecase(
self.file, {"material": self.file.by_id(obj.BIMObjectProperties.ifc_definition_id)}
).execute()
obj.BIMObjectProperties.ifc_definition_id = 0
Data.load()
return {"FINISHED"}
class AssignMaterial(bpy.types.Operator):
bl_idname = "bim.assign_material"
bl_label = "Assign Material"
@@ -0,0 +1,23 @@
import ifcopenshell
class Usecase:
def __init__(self, file, settings=None):
self.file = file
self.settings = {"material": None}
for key, value in settings.items():
self.settings[key] = value
def execute(self):
inverse_elements = self.file.get_inverse(self.settings["material"])
self.file.remove(self.settings["material"])
# TODO: this is probably not robust enough
for inverse in inverse_elements:
if inverse.is_a("IfcMaterialConstituent"):
self.file.remove(inverse)
elif inverse.is_a("IfcMaterialLayer"):
self.file.remove(inverse)
elif inverse.is_a("IfcMaterialProfile"):
self.file.remove(inverse)
elif inverse.is_a("IfcRelAssociatesMaterial"):
self.file.remove(inverse)
@@ -3,6 +3,21 @@ from blenderbim.bim.module.material.data import Data
from blenderbim.bim.ifc import IfcStore
class BIM_PT_material(Panel):
bl_label = "IFC Material"
bl_idname = "BIM_PT_material"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "material"
def draw(self, context):
row = self.layout.row()
if bool(context.active_object.active_material.BIMObjectProperties.ifc_definition_id):
row.operator("bim.remove_material", icon="X", text="Remove IFC Material")
else:
row.operator("bim.add_material", icon="ADD", text="Create IFC Material")
class BIM_PT_object_material(Panel):
bl_label = "IFC Object Material"
bl_idname = "BIM_PT_object_material"
@@ -12,7 +27,12 @@ class BIM_PT_object_material(Panel):
@classmethod
def poll(cls, context):
return bool(context.active_object.BIMObjectProperties.ifc_definition_id)
props = context.active_object.BIMObjectProperties
if not props.ifc_definition_id:
return False
if not hasattr(IfcStore.get_file().by_id(props.ifc_definition_id), "HasAssociations"):
return False
return True
def draw(self, context):
self.file = IfcStore.get_file()
@@ -24,6 +44,11 @@ class BIM_PT_object_material(Panel):
Data.load(self.oprops.ifc_definition_id)
self.product_data = Data.products[self.oprops.ifc_definition_id]
if not Data.materials:
row = self.layout.row()
row.label(text="No Materials Available")
return
if self.product_data:
if self.product_data["type"] == "IfcMaterialConstituentSet":
self.material_set_data = Data.constituent_sets[self.product_data["id"]]
@@ -1,5 +1,5 @@
import bpy
from . import grid, wall, stair, door, window, slab, opening
from . import grid, wall, stair, door, window, slab, opening, pie
classes = (
grid.BIM_OT_add_object,
@@ -9,8 +9,20 @@ classes = (
window.BIM_OT_add_object,
slab.BIM_OT_add_object,
opening.BIM_OT_add_object,
pie.OpenPieClass,
pie.PieUpdateContainer,
pie.PieAddOpening,
pie.AssignIfcWall,
pie.AssignIfcSlab,
pie.AssignIfcStair,
pie.AssignIfcDoor,
pie.AssignIfcWindow,
pie.VIEW3D_MT_PIE_bim,
pie.VIEW3D_MT_PIE_bim_class,
)
addon_keymaps = []
def register():
bpy.types.VIEW3D_MT_mesh_add.append(grid.add_object_button)
@@ -20,6 +32,12 @@ def register():
bpy.types.VIEW3D_MT_mesh_add.append(window.add_object_button)
bpy.types.VIEW3D_MT_mesh_add.append(slab.add_object_button)
bpy.types.VIEW3D_MT_mesh_add.append(opening.add_object_button)
wm = bpy.context.window_manager
if wm.keyconfigs.addon:
km = wm.keyconfigs.addon.keymaps.new(name="3D View", space_type="VIEW_3D")
kmi = km.keymap_items.new("wm.call_menu_pie", "E", "PRESS", shift=True)
kmi.properties["name"] = "VIEW3D_MT_PIE_bim"
addon_keymaps.append((km, kmi))
def unregister():
@@ -30,3 +48,9 @@ def unregister():
bpy.types.VIEW3D_MT_mesh_add.remove(window.add_object_button)
bpy.types.VIEW3D_MT_mesh_add.remove(slab.add_object_button)
bpy.types.VIEW3D_MT_mesh_add.remove(opening.add_object_button)
wm = bpy.context.window_manager
kc = wm.keyconfigs.addon
if kc:
for km, kmi in addon_keymaps:
km.keymap_items.remove(kmi)
addon_keymaps.clear()
@@ -10,60 +10,61 @@ from mathutils import Vector
def add_object(self, context):
guid = ifcopenshell.guid.new()
leaf_width = self.overall_width - 0.045 - 0.045
verts = [
# Left lining
Vector((0, 0, 0)),
Vector((0, self.depth, 0)),
Vector((0.04, self.depth, 0)),
Vector((0.04, self.depth - 0.04, 0)),
Vector((0.065, self.depth - 0.04, 0)),
Vector((0.065, 0, 0)),
# Right lining
Vector((self.overall_width, 0, 0)),
Vector((self.overall_width, self.depth, 0)),
Vector((self.overall_width - 0.04, self.depth, 0)),
Vector((self.overall_width - 0.04, self.depth - 0.04, 0)),
Vector((self.overall_width - 0.065, self.depth - 0.04, 0)),
Vector((self.overall_width - 0.065, 0, 0)),
# Door panel
Vector((0.045, self.depth, 0)),
Vector((0.045, self.depth + leaf_width, 0)),
Vector((0.080, self.depth + leaf_width, 0)),
Vector((0.080, self.depth, 0)),
]
edges = [
[0, 1],
[1, 2],
[2, 3],
[3, 4],
[4, 5], # Left lining
[6, 7],
[7, 8],
[8, 9],
[9, 10],
[10, 11], # Right lining
[12, 13],
[13, 14],
[14, 15],
[15, 12], # Door panel
]
# Door swing
for i in range(0, 9):
verts.append(
Vector(
(
0.045 + (leaf_width * math.cos((math.pi / 2) / 8 * i)),
self.depth + (leaf_width * math.sin((math.pi / 2) / 8 * i)),
0,
)
)
)
edges.append([16 + i, 17 + i])
edges.pop()
faces = []
mesh = bpy.data.meshes.new(name="Plan/Annotation/PLAN_VIEW/" + guid)
mesh.use_fake_user = True
mesh.from_pydata(verts, edges, faces)
# TODO reimplement 2D. See #1222.
#verts = [
# # Left lining
# Vector((0, 0, 0)),
# Vector((0, self.depth, 0)),
# Vector((0.04, self.depth, 0)),
# Vector((0.04, self.depth - 0.04, 0)),
# Vector((0.065, self.depth - 0.04, 0)),
# Vector((0.065, 0, 0)),
# # Right lining
# Vector((self.overall_width, 0, 0)),
# Vector((self.overall_width, self.depth, 0)),
# Vector((self.overall_width - 0.04, self.depth, 0)),
# Vector((self.overall_width - 0.04, self.depth - 0.04, 0)),
# Vector((self.overall_width - 0.065, self.depth - 0.04, 0)),
# Vector((self.overall_width - 0.065, 0, 0)),
# # Door panel
# Vector((0.045, self.depth, 0)),
# Vector((0.045, self.depth + leaf_width, 0)),
# Vector((0.080, self.depth + leaf_width, 0)),
# Vector((0.080, self.depth, 0)),
#]
#edges = [
# [0, 1],
# [1, 2],
# [2, 3],
# [3, 4],
# [4, 5], # Left lining
# [6, 7],
# [7, 8],
# [8, 9],
# [9, 10],
# [10, 11], # Right lining
# [12, 13],
# [13, 14],
# [14, 15],
# [15, 12], # Door panel
#]
## Door swing
#for i in range(0, 9):
# verts.append(
# Vector(
# (
# 0.045 + (leaf_width * math.cos((math.pi / 2) / 8 * i)),
# self.depth + (leaf_width * math.sin((math.pi / 2) / 8 * i)),
# 0,
# )
# )
# )
# edges.append([16 + i, 17 + i])
#edges.pop()
#faces = []
#mesh = bpy.data.meshes.new(name="Plan/Annotation/PLAN_VIEW/" + guid)
#mesh.use_fake_user = True
#mesh.from_pydata(verts, edges, faces)
# Door lining profile
verts = [
@@ -96,6 +97,7 @@ def add_object(self, context):
bpy.ops.object.convert(target="CURVE")
obj2.data.dimensions = "2D"
obj2.data.bevel_mode = "OBJECT"
obj2.data.bevel_object = obj
obj2.rotation_euler[0] = math.pi / 2
@@ -145,27 +147,22 @@ def add_object(self, context):
obj4.parent = obj2
obj4.matrix_parent_inverse = obj2.matrix_world.inverted()
obj4.hide_render = True
obj4.name = "IfcOpeningElement/Dumb Door Opening"
attribute = obj4.BIMObjectProperties.attributes.add()
attribute.name = "PredefinedType"
attribute.string_value = "OPENING"
obj4.name = "Door Opening"
obj2.name = "IfcDoor/Dumb Door"
attribute = obj2.BIMObjectProperties.attributes.add()
attribute.name = "PredefinedType"
attribute.string_value = "DOOR"
obj2.data.name = "Model/Body/MODEL_VIEW/" + guid
obj2.data.use_fake_user = True
obj2.name = "Door"
#obj2.data.name = "Model/Body/MODEL_VIEW/" + guid
#obj2.data.use_fake_user = True
rep = obj2.BIMObjectProperties.representation_contexts.add()
rep.context = "Model"
rep.name = "Body"
rep.target_view = "MODEL_VIEW"
# TODO: reimplement. See #1222.
#rep = obj2.BIMObjectProperties.representation_contexts.add()
#rep.context = "Model"
#rep.name = "Body"
#rep.target_view = "MODEL_VIEW"
rep = obj2.BIMObjectProperties.representation_contexts.add()
rep.context = "Plan"
rep.name = "Annotation"
rep.target_view = "PLAN_VIEW"
#rep = obj2.BIMObjectProperties.representation_contexts.add()
#rep.context = "Plan"
#rep.name = "Annotation"
#rep.target_view = "PLAN_VIEW"
class BIM_OT_add_object(Operator, AddObjectHelper):
@@ -15,11 +15,8 @@ def add_object(self, context):
bm.to_mesh(mesh)
bm.free()
obj = object_data_add(context, mesh, operator=self)
obj.name = "IfcOpening/Dumb Opening"
obj.name = "Opening"
obj.display_type = "WIRE"
attribute = obj.BIMObjectProperties.attributes.add()
attribute.name = "PredefinedType"
attribute.string_value = "OPENING"
class BIM_OT_add_object(Operator, AddObjectHelper):
@@ -0,0 +1,125 @@
import bpy
class OpenPieClass(bpy.types.Operator):
bl_idname = "bim.open_pie_class"
bl_label = "Open Pie Class"
def execute(self, context):
bpy.ops.wm.call_menu_pie(name="VIEW3D_MT_PIE_bim_class")
return {"FINISHED"}
class AssignIfcWall(bpy.types.Operator):
bl_idname = "bim.assign_ifc_wall"
bl_label = "IfcWall"
def execute(self, context):
for obj in context.selected_objects:
if not obj.BIMObjectProperties.ifc_definition_id:
bpy.ops.bim.assign_class(obj=obj.name, ifc_class="IfcWall")
return {"FINISHED"}
class AssignIfcSlab(bpy.types.Operator):
bl_idname = "bim.assign_ifc_slab"
bl_label = "IfcSlab"
def execute(self, context):
for obj in context.selected_objects:
if not obj.BIMObjectProperties.ifc_definition_id:
bpy.ops.bim.assign_class(obj=obj.name, ifc_class="IfcSlab")
return {"FINISHED"}
class AssignIfcStair(bpy.types.Operator):
bl_idname = "bim.assign_ifc_stair"
bl_label = "IfcStair"
def execute(self, context):
for obj in context.selected_objects:
if not obj.BIMObjectProperties.ifc_definition_id:
bpy.ops.bim.assign_class(obj=obj.name, ifc_class="IfcStair")
return {"FINISHED"}
class AssignIfcDoor(bpy.types.Operator):
bl_idname = "bim.assign_ifc_door"
bl_label = "IfcDoor"
def execute(self, context):
for obj in context.selected_objects:
if not obj.BIMObjectProperties.ifc_definition_id:
bpy.ops.bim.assign_class(obj=obj.name, ifc_class="IfcDoor")
return {"FINISHED"}
class AssignIfcWindow(bpy.types.Operator):
bl_idname = "bim.assign_ifc_window"
bl_label = "IfcWindow"
def execute(self, context):
for obj in context.selected_objects:
if not obj.BIMObjectProperties.ifc_definition_id:
bpy.ops.bim.assign_class(obj=obj.name, ifc_class="IfcWindow")
return {"FINISHED"}
class PieAddOpening(bpy.types.Operator):
bl_idname = "bim.pie_add_opening"
bl_label = "Add Opening"
def execute(self, context):
if len(context.selected_objects) == 2:
opening_name = None
obj_name = None
for obj in context.selected_objects:
if "IfcOpeningElement" in obj.name or not obj.BIMObjectProperties.ifc_definition_id:
opening_name = obj.name
else:
opj_name = obj.name
bpy.ops.bim.add_opening(obj=opj_name, opening=opening_name)
return {"FINISHED"}
class PieUpdateContainer(bpy.types.Operator):
bl_idname = "bim.pie_update_container"
bl_label = "Update Spatial Container"
def execute(self, context):
for obj in context.selected_objects:
if not obj.BIMObjectProperties.ifc_definition_id:
continue
relating_structure = None
for collection in obj.users_collection:
relating_structure_obj = bpy.data.objects.get(collection.name)
if not relating_structure_obj or not relating_structure_obj.BIMObjectProperties.ifc_definition_id:
continue
relating_structure = relating_structure_obj
bpy.ops.bim.assign_container(relating_structure=relating_structure.name, related_element=obj.name)
return {"FINISHED"}
class VIEW3D_MT_PIE_bim(bpy.types.Menu):
bl_label = "Geometry"
def draw(self, context):
pie = self.layout.menu_pie()
pie.operator("bim.edit_object_placement")
pie.operator("bim.update_mesh_representation")
pie.operator("bim.map_representation")
pie.operator("bim.pie_add_opening")
pie.operator("bim.pie_update_container")
pie.operator("bim.open_pie_class", text="Assign IFC Class")
class VIEW3D_MT_PIE_bim_class(bpy.types.Menu):
bl_label = "IFC Class"
def draw(self, context):
pie = self.layout.menu_pie()
pie.operator("bim.assign_ifc_wall")
pie.operator("bim.assign_ifc_slab")
pie.operator("bim.assign_ifc_stair")
pie.operator("bim.assign_ifc_door")
pie.operator("bim.assign_ifc_window")
@@ -22,10 +22,7 @@ def add_object(self, context):
modifier.use_even_offset = True
modifier.offset = 1
modifier.thickness = self.depth
obj.name = "IfcSlab/Dumb Slab"
attribute = obj.BIMObjectProperties.attributes.add()
attribute.name = "PredefinedType"
attribute.string_value = "FLOOR"
obj.name = "Slab"
class BIM_OT_add_object(Operator, AddObjectHelper):
@@ -28,14 +28,13 @@ def add_object(self, context):
modifier.relative_offset_displace[0] = 0
modifier.relative_offset_displace[1] = 1
modifier.use_constant_offset = True
modifier.constant_offset_displace[0] = 0
modifier.constant_offset_displace[1] = 0
modifier.constant_offset_displace[2] = self.height / self.number_of_treads
modifier.count = self.number_of_treads
self.riser_height = self.height / self.number_of_treads
self.length = self.number_of_treads * self.tread_length
obj.name = "IfcStairFlight/Dumb Stair"
attribute = obj.BIMObjectProperties.attributes.add()
attribute.name = "PredefinedType"
attribute.string_value = "STRAIGHT"
obj.name = "Stair"
class BIM_OT_add_object(Operator, AddObjectHelper):
@@ -38,10 +38,8 @@ def add_object(self, context):
modifier = obj.modifiers.new("Wall Width", "SOLIDIFY")
modifier.use_even_offset = True
modifier.thickness = self.width
obj.name = "IfcWall/Dumb Wall"
attribute = obj.BIMObjectProperties.attributes.add()
attribute.name = "PredefinedType"
attribute.string_value = "STANDARD"
obj.name = "Wall"
return obj
class BIM_OT_add_object(Operator, AddObjectHelper):
@@ -10,48 +10,48 @@ from mathutils import Vector
def add_object(self, context):
guid = ifcopenshell.guid.new()
leaf_width = self.overall_width - 0.045 - 0.045
verts = [
# Left lining
Vector((0, 0, 0)),
Vector((0, self.depth, 0)),
Vector((0.04, self.depth, 0)),
Vector((0.04, 0, 0)),
# Right lining
Vector((self.overall_width, 0, 0)),
Vector((self.overall_width, self.depth, 0)),
Vector((self.overall_width - 0.04, self.depth, 0)),
Vector((self.overall_width - 0.04, 0, 0)),
# Bottom lining
Vector((0, 0, 0)),
Vector((self.overall_width, 0, 0)),
Vector((0, self.depth, 0)),
Vector((self.overall_width, self.depth, 0)),
# Window panel
Vector((0.04, (self.depth / 2) + 0.005, 0)),
Vector((0.04, (self.depth / 2) - 0.005, 0)),
Vector((self.overall_width - 0.04, (self.depth / 2) - 0.005, 0)),
Vector((self.overall_width - 0.04, (self.depth / 2) + 0.005, 0)),
]
edges = [
[0, 1],
[1, 2],
[2, 3],
[3, 0], # Left lining
[4, 5],
[5, 6],
[6, 7],
[7, 4], # Right lining
[8, 9],
[10, 11], # Bottom lining
[12, 13],
[13, 14],
[14, 15],
[15, 12], # Window panel
]
faces = []
mesh = bpy.data.meshes.new(name="Plan/Annotation/PLAN_VIEW/" + guid)
mesh.use_fake_user = True
mesh.from_pydata(verts, edges, faces)
#verts = [
# # Left lining
# Vector((0, 0, 0)),
# Vector((0, self.depth, 0)),
# Vector((0.04, self.depth, 0)),
# Vector((0.04, 0, 0)),
# # Right lining
# Vector((self.overall_width, 0, 0)),
# Vector((self.overall_width, self.depth, 0)),
# Vector((self.overall_width - 0.04, self.depth, 0)),
# Vector((self.overall_width - 0.04, 0, 0)),
# # Bottom lining
# Vector((0, 0, 0)),
# Vector((self.overall_width, 0, 0)),
# Vector((0, self.depth, 0)),
# Vector((self.overall_width, self.depth, 0)),
# # Window panel
# Vector((0.04, (self.depth / 2) + 0.005, 0)),
# Vector((0.04, (self.depth / 2) - 0.005, 0)),
# Vector((self.overall_width - 0.04, (self.depth / 2) - 0.005, 0)),
# Vector((self.overall_width - 0.04, (self.depth / 2) + 0.005, 0)),
#]
#edges = [
# [0, 1],
# [1, 2],
# [2, 3],
# [3, 0], # Left lining
# [4, 5],
# [5, 6],
# [6, 7],
# [7, 4], # Right lining
# [8, 9],
# [10, 11], # Bottom lining
# [12, 13],
# [13, 14],
# [14, 15],
# [15, 12], # Window panel
#]
#faces = []
#mesh = bpy.data.meshes.new(name="Plan/Annotation/PLAN_VIEW/" + guid)
#mesh.use_fake_user = True
#mesh.from_pydata(verts, edges, faces)
# Window lining profile
verts = [
@@ -83,6 +83,7 @@ def add_object(self, context):
obj2.data.splines[0].use_cyclic_u = True
obj2.data.dimensions = "2D"
obj2.data.bevel_mode = "OBJECT"
obj2.data.bevel_object = obj
obj2.rotation_euler[0] = math.pi / 2
@@ -132,27 +133,21 @@ def add_object(self, context):
obj4.parent = obj2
obj4.matrix_parent_inverse = obj2.matrix_world.inverted()
obj4.hide_render = True
obj4.name = "IfcOpeningElement/Dumb Window Opening"
attribute = obj4.BIMObjectProperties.attributes.add()
attribute.name = "PredefinedType"
attribute.string_value = "OPENING"
obj4.name = "Window Opening"
obj2.name = "IfcWindow/Dumb Window"
attribute = obj2.BIMObjectProperties.attributes.add()
attribute.name = "PredefinedType"
attribute.string_value = "WINDOW"
obj2.data.name = "Model/Body/MODEL_VIEW/" + guid
obj2.data.use_fake_user = True
obj2.name = "Window"
#obj2.data.name = "Model/Body/MODEL_VIEW/" + guid
#obj2.data.use_fake_user = True
rep = obj2.BIMObjectProperties.representation_contexts.add()
rep.context = "Model"
rep.name = "Body"
rep.target_view = "MODEL_VIEW"
#rep = obj2.BIMObjectProperties.representation_contexts.add()
#rep.context = "Model"
#rep.name = "Body"
#rep.target_view = "MODEL_VIEW"
rep = obj2.BIMObjectProperties.representation_contexts.add()
rep.context = "Plan"
rep.name = "Annotation"
rep.target_view = "PLAN_VIEW"
#rep = obj2.BIMObjectProperties.representation_contexts.add()
#rep.context = "Plan"
#rep.name = "Annotation"
#rep.target_view = "PLAN_VIEW"
class BIM_OT_add_object(Operator, AddObjectHelper):
@@ -0,0 +1,32 @@
import bpy
import addon_utils
import blenderbim.bim.module.owner.create_owner_history as create_owner_history_usecase
from blenderbim.bim.ifc import IfcStore
def create_owner_history(change_action=None):
file = IfcStore.get_file()
return create_owner_history_usecase.Usecase(
file,
{
"person": file.by_id(int(bpy.context.scene.BIMOwnerProperties.user_person)),
"organisation": file.by_id(int(bpy.context.scene.BIMOwnerProperties.user_organisation)),
"ApplicationIdentifier": "BlenderBIM",
"ApplicationFullName": "BlenderBIM Add-on",
"Version": get_application_version(),
"ChangeAction": change_action or "ADDED",
},
).execute()
def get_application_version():
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]
]
)
@@ -0,0 +1,101 @@
import time
import ifcopenshell
class Usecase:
def __init__(self, file, settings=None):
self.file = file
self.settings = {
"person": None,
"organisation": None,
"ApplicationIdentifier": "",
"ApplicationFullName": "",
"Version": "",
"ChangeAction": "NOTDEFINED",
}
for key, value in settings.items():
self.settings[key] = value
def execute(self):
user = self.get_user()
application = self.get_application()
return self.file.create_entity(
"IfcOwnerHistory",
**{
"OwningUser": user,
"OwningApplication": application,
"State": "READWRITE",
"ChangeAction": self.settings["ChangeAction"],
"LastModifiedDate": int(time.time()),
"LastModifyingUser": user,
"LastModifyingApplication": application,
"CreationDate": int(time.time()),
},
)
def get_user(self):
for element in self.file.by_type("IfcPersonAndOrganization"):
if (
element.ThePerson == self.settings["person"]
and element.TheOrganization == self.settings["organisation"]
):
return element
return self.file.create_entity(
"IfcPersonAndOrganization",
**{"ThePerson": self.settings["person"], "TheOrganization": self.settings["organisation"]},
)
def get_application(self):
for element in self.file.by_type("IfcApplication"):
if element.ApplicationIdentifier == self.settings["ApplicationIdentifier"]:
return element
return self.file.create_entity(
"IfcApplication",
**{
"ApplicationDeveloper": self.get_application_organisation(),
"Version": self.settings["Version"],
"ApplicationFullName": self.settings["ApplicationFullName"],
"ApplicationIdentifier": self.settings["ApplicationIdentifier"],
},
)
def get_application_organisation(self):
return 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",
},
),
],
},
)
@@ -22,7 +22,7 @@ def getPersons(self, context):
if "Id" in person:
identifier = person["Id"] or ""
else:
identifier = person["Identifier"] or ""
identifier = person["Identification"] or ""
results.append((str(ifc_id), identifier, ""))
return results
@@ -110,6 +110,7 @@ class BIM_PT_people(Panel):
self.file = IfcStore.get_file()
self.layout.use_property_split = True
self.layout.use_property_decorate = False
props = context.scene.BIMOwnerProperties
row = self.layout.row()
@@ -169,6 +170,7 @@ class BIM_PT_organisations(Panel):
self.file = IfcStore.get_file()
self.layout.use_property_split = True
self.layout.use_property_decorate = False
props = context.scene.BIMOwnerProperties
row = self.layout.row()
@@ -216,6 +218,7 @@ class BIM_PT_owner(Panel):
Data.load()
self.layout.use_property_split = True
self.layout.use_property_decorate = False
props = context.scene.BIMOwnerProperties
if not Data.people:

Some files were not shown because too many files have changed in this diff Show More