Files
IfcOpenShell/src/ifcgeom/mapping/mapping.cpp
T
2026-04-10 09:54:28 +02:00

1376 lines
55 KiB
C++

/********************************************************************************
* *
* 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/>. *
* *
********************************************************************************/
#define _USE_MATH_DEFINES
#include <cmath>
#include "mapping.h"
#include "../../ifcparse/logger.h"
#include "../../ifcparse/file.h"
#include "../../ifcparse/si_prefix.h"
using namespace ifcopenshell;
using namespace ifcopenshell::geometry;
using namespace IfcGeom;
namespace {
struct POSTFIX_SCHEMA(factory_t) {
abstract_mapping* operator()(ifcopenshell::file* file, Settings& settings) const {
ifcopenshell::geometry::POSTFIX_SCHEMA(mapping)* m = new ifcopenshell::geometry::POSTFIX_SCHEMA(mapping)(file, settings);
return m;
}
};
}
void MAKE_INIT_FN(MappingImplementation)(ifcopenshell::geometry::impl::MappingFactoryImplementation* mapping) {
static const std::string schema_name = STRINGIFY(IfcSchema);
POSTFIX_SCHEMA(factory_t) factory;
mapping->bind(schema_name, factory);
}
#define mapping POSTFIX_SCHEMA(mapping)
std::vector<IfcSchema::IfcProduct> mapping::products_represented_by(const IfcSchema::IfcRepresentation& representation, IfcSchema::IfcRepresentationMap& rmap, bool only_direct) {
std::vector<IfcSchema::IfcProduct> products;
std::vector<IfcSchema::IfcProductRepresentation> prodreps = representation.OfProductRepresentation();
for (auto& prodrep : prodreps) {
// http://buildingsmart-tech.org/ifc/IFC2x3/TC1/html/ifcrepresentationresource/lexical/ifcproductrepresentation.htm
// IFC2x Edition 3 NOTE Users should not instantiate the entity IfcProductRepresentation from IFC2x Edition 3 onwards.
// It will be changed into an ABSTRACT supertype in future releases of IFC.
// IfcProductRepresentation also lacks the INVERSE relation to IfcProduct
// Let's find the IfcProducts that reference the IfcProductRepresentation anyway
auto invs = prodrep.file()->get_inverse(prodrep.id(), &IfcSchema::IfcProduct::Class(), -1);
for (auto& inv : invs) {
products.push_back(inv.as<IfcSchema::IfcProduct>());
}
}
if (only_direct) {
return products;
}
std::vector<IfcSchema::IfcRepresentationMap> maps = representation.RepresentationMap();
if (maps.size() == 1) {
rmap = maps.front();
if (not_reusable_maps_.find(rmap) != not_reusable_maps_.end()) {
return products;
}
taxonomy::matrix4::ptr origin = taxonomy::cast<taxonomy::matrix4>(map(rmap.MappingOrigin()));
if (origin->is_identity()) {
std::vector<IfcSchema::IfcMappedItem> items = rmap.MapUsage();
for (auto& item : items) {
if (item.StyledByItem().size() != 0) continue;
taxonomy::matrix4::ptr target;
try {
target = taxonomy::cast<taxonomy::matrix4>(map(item.MappingTarget()));
} catch (const std::exception& e) {
logger::error(e);
continue;
}
if (!target->is_identity()) {
continue;
}
auto reps = item.file()->get_inverse(item.id(), (&IfcSchema::IfcRepresentation::Class()), -1);
for (auto& rep : reps) {
if (rep.as<IfcSchema::IfcRepresentation>().Items().size() != 1) continue;
std::vector<IfcSchema::IfcProductRepresentation> prodreps_mapped = rep.as<IfcSchema::IfcRepresentation>().OfProductRepresentation();
for (auto& prm : prodreps_mapped) {
auto ps = prm.file()->get_inverse(prm.id(), (&IfcSchema::IfcProduct::Class()), -1);
for (auto& p : ps) {
products.push_back(p.as<IfcSchema::IfcProduct>());
}
}
}
}
}
}
return products;
}
namespace {
std::vector<IfcSchema::IfcProduct> filter_products(const std::vector<IfcSchema::IfcProduct>& unfiltered_products, const std::vector<filter_t>& filters) {
std::vector<IfcSchema::IfcProduct> ifcproducts;
for (auto& prod : unfiltered_products) {
if (boost::all(filters, [prod](const filter_t& f) { return f(prod); })) {
ifcproducts.push_back(prod);
}
}
return ifcproducts;
}
}
bool mapping::reuse_ok_(const std::vector<IfcSchema::IfcProduct>& products) {
// With world coords enabled, object transformations are directly applied to
// the BRep. There is no way to re-use the geometry for multiple products.
if (settings_.get<settings::UseWorldCoords>().get()) {
return false;
}
if (products.size() == 1) {
return true;
}
std::set<std::optional<express::Base>> associated_single_materials;
for (auto& product : products) {
if (!settings_.get<settings::DisableOpeningSubtractions>().get() && !find_openings(product).empty()) {
return false;
}
if (settings_.get<settings::ApplyLayerSets>().get()) {
std::vector<IfcSchema::IfcRelAssociates> associations = product.HasAssociations();
for (auto& assoc : associations) {
if (auto assocm = assoc.as<IfcSchema::IfcRelAssociatesMaterial>()) {
if (assocm.RelatingMaterial().declaration().is(IfcSchema::IfcMaterialLayerSetUsage::Class())) {
// TODO: Check whether single layer?
return false;
}
}
}
}
auto mat = get_single_material_association(product);
associated_single_materials.insert(mat ? std::optional<express::Base>{mat} : std::nullopt);
if (associated_single_materials.size() > 1) return false;
}
return associated_single_materials.size() == 1;
}
std::vector<express::Base> mapping::find_openings(const express::Base& inst) {
std::vector<express::Base> openings;
if (auto rep = inst.as<IfcSchema::IfcRepresentation>()) {
// @todo this is essentially only for hybrid kernel trying to guess
// when not to use a simple kernel.
IfcSchema::IfcRepresentationMap rmap;
auto prods = products_represented_by(rep, rmap, true);
for (auto& p : prods) {
auto ops = find_openings(p);
openings.insert(openings.end(), ops.begin(), ops.end());
}
return openings;
}
if (inst.as<IfcSchema::IfcElement>() && !inst.as<IfcSchema::IfcFeatureElementSubtraction>()) {
auto element = inst.as<IfcSchema::IfcElement>();
auto rels = element.HasOpenings();
for (auto& rel : rels) {
openings.push_back(rel.RelatedOpeningElement());
}
}
// Is the IfcElement a decomposition of an IfcElement with any IfcOpeningElements?
auto obdef = inst.as<IfcSchema::IfcObjectDefinition>();
if (obdef) {
for (;;) {
auto decomposes = obdef.Decomposes();
if (decomposes.size() != 1) {
// If we have multiple decompositions, not allowed by schema,
// openings associated to relating decompositions are not
// considered;
break;
}
if (!decomposes.front().as<IfcSchema::IfcRelAggregates>()) {
// Only aggregation, not nesting is considered.
break;
}
auto rel_obdef = decomposes.front().as<IfcSchema::IfcRelAggregates>().RelatingObject();
if (rel_obdef.as<IfcSchema::IfcElement>() && !rel_obdef.as<IfcSchema::IfcFeatureElementSubtraction>()) {
auto element = rel_obdef.as<IfcSchema::IfcElement>();
auto rels = element.HasOpenings();
for (auto& rel : rels) {
openings.push_back(rel.RelatedOpeningElement());
}
}
obdef = rel_obdef;
}
}
return openings;
}
void mapping::get_representations(std::vector<geometry_conversion_task>& tasks, std::vector<filter_t>& filters) {
std::vector<IfcSchema::IfcRepresentation> representations;
if (!settings_.get<settings::ContextIds>().has()) {
addRepresentationsFromDefaultContexts(representations);
} else {
addRepresentationsFromContextIds(representations);
}
std::vector<IfcSchema::IfcRepresentation> ok_mapped_representations;
int task_index = 0;
for (auto representation : representations) {
IfcSchema::IfcRepresentationMap rmap;
std::vector<IfcSchema::IfcProduct> ifcproducts = filter_products(products_represented_by(representation, rmap, false), filters);
if (ifcproducts.empty()) {
continue;
}
auto geometry_reuse_ok_for_current_representation_ = reuse_ok_(ifcproducts);
if (!geometry_reuse_ok_for_current_representation_ && rmap) {
not_reusable_maps_.insert(rmap);
}
std::vector<IfcSchema::IfcRepresentationMap> maps = representation.RepresentationMap();
if (!geometry_reuse_ok_for_current_representation_ && maps.size() == 1) {
// unfiltered_products contains products represented by this representation by means of mapped items.
// For example because of openings applied to products, reuse might not be acceptable and then the
// products will be processed by means of their immediate representation and not the mapped representation.
// IfcRepresentationMaps are also used for IfcTypeProducts, so an additional check is performed whether the map
// is indeed used by IfcMappedItems.
auto& map = maps.front();
if (map.MapUsage().size() > 0) {
continue;
}
}
// Check if this representation has (or will be) processed as part its mapped representation
bool representation_processed_as_mapped_item = false;
auto representation_mapped_to_result = representation_mapped_to(representation);
if (representation_mapped_to_result) {
representation_processed_as_mapped_item = geometry_reuse_ok_for_current_representation_ && (
std::find(ok_mapped_representations.begin(), ok_mapped_representations.end(), representation_mapped_to_result) != ok_mapped_representations.end() ||
reuse_ok_(products_represented_by(representation_mapped_to_result, rmap)));
}
if (representation_processed_as_mapped_item) {
ok_mapped_representations.push_back(representation_mapped_to_result);
continue;
}
if (!geometry_reuse_ok_for_current_representation_ && ifcproducts.size() > 1) {
// reuse_ok is taken into account in products_represented_by(), but not when
// the same IfcRepresentation is directly assigned to multiple products.
for (auto& p : ifcproducts) {
geometry_conversion_task task;
task.index = task_index++;
task.representation = representation;
task.products.push_back(p);
tasks.emplace_back(task);
}
} else {
geometry_conversion_task task;
task.index = task_index++;
task.representation = representation;
task.products.insert(task.products.end(), ifcproducts.begin(), ifcproducts.end());
tasks.emplace_back(task);
}
}
}
const express::Base mapping::get_product_type(const express::Base& product_) {
auto product = product_.as<IfcSchema::IfcProduct>();
#ifdef SCHEMA_IfcObject_HAS_IsTypedBy
auto rels = product.IsTypedBy();
#else // IFC2X3.
auto rels = product.IsDefinedBy();
#endif
for (auto it = rels.begin(); it != rels.end(); ++it) {
#ifdef SCHEMA_IfcObject_HAS_IsTypedBy
auto rel = *it;
#else // IFC2X3.
auto rel = (*it).as<IfcSchema::IfcRelDefinesByType>();
if (!rel) {
continue;
}
#endif
// Avoid segfault if RelatingType is unset.
if (rel.get("RelatingType").isNull()){
break;
}
return rel.RelatingType();
}
return express::Base{};
}
const express::Base mapping::get_single_material_association(const express::Base& product_) {
auto product = product_.as<IfcSchema::IfcObjectDefinition>();
IfcSchema::IfcMaterial single_material;
auto associations = product.HasAssociations();
std::vector<IfcSchema::IfcRelAssociatesMaterial> associated_materials;
for (auto& assoc : associations) {
if (auto assocm = assoc.as<IfcSchema::IfcRelAssociatesMaterial>()) {
associated_materials.push_back(assocm);
}
}
if (associated_materials.size() == 1) {
express::Base associated_material;
try {
associated_material = associated_materials.front().RelatingMaterial().concrete();
} catch(ifcopenshell::exception& e) {
logger::error(e.what());
}
if (associated_material) {
single_material = associated_material.as<IfcSchema::IfcMaterial>();
// NB: Single-layer layersets are also considered, regardless of --enable-layerset-slicing, this
// in accordance with other viewers.
if (!single_material) {
if (associated_material.as<IfcSchema::IfcMaterialLayerSetUsage>() || associated_material.as<IfcSchema::IfcMaterialLayerSet>()) {
IfcSchema::IfcMaterialLayerSet layerset;
if (auto m = associated_material.as<IfcSchema::IfcMaterialLayerSetUsage>()) {
if (m.get("ForLayerSet").isNull()) {
logger::warning("Missing ForLayerSet for:", m);
return express::Base{};
}
layerset = m.ForLayerSet();
} else {
layerset = associated_material.as<IfcSchema::IfcMaterialLayerSet>();
}
if (settings_.get<settings::LayersetFirst>().value ? layerset.MaterialLayers().size() >= 1 : layerset.MaterialLayers().size() == 1) {
IfcSchema::IfcMaterialLayer layer = layerset.MaterialLayers().front();
if (auto m_ = layer.Material()) {
single_material = m_;
}
}
}
#ifdef SCHEMA_HAS_IfcMaterialProfileSet
if (associated_material.as<IfcSchema::IfcMaterialProfileSetUsage>() || associated_material.as<IfcSchema::IfcMaterialProfileSet>()) {
IfcSchema::IfcMaterialProfileSet profileset;
if (auto m = associated_material.as<IfcSchema::IfcMaterialProfileSetUsage>()) {
if (m.get("ForProfileSet").isNull()) {
logger::warning("Missing ForProfileSet for:", m);
return express::Base{};
}
profileset = m.ForProfileSet();
} else {
profileset = associated_material.as<IfcSchema::IfcMaterialProfileSet>();
}
if (settings_.get<settings::LayersetFirst>().value ? profileset.MaterialProfiles().size() >= 1 : profileset.MaterialProfiles().size() == 1) {
IfcSchema::IfcMaterialProfile profile = profileset.MaterialProfiles().front();
if (auto m_ = profile.Material()) {
single_material = m_;
}
}
}
#endif
#ifdef SCHEMA_HAS_IfcMaterialConstituentSet
if (associated_material.as<IfcSchema::IfcMaterialConstituentSet>() && associated_material.as<IfcSchema::IfcMaterialConstituentSet>().MaterialConstituents()) {
IfcSchema::IfcMaterialConstituentSet constituentset = associated_material.as<IfcSchema::IfcMaterialConstituentSet>();
if (settings_.get<settings::LayersetFirst>().value ? constituentset.MaterialConstituents().value().size() >= 1 : constituentset.MaterialConstituents().value().size() == 1) {
IfcSchema::IfcMaterialConstituent constituent = constituentset.MaterialConstituents().value().front();
if (auto m_ = constituent.Material()) {
single_material = m_;
}
}
}
#endif
}
}
}
return single_material;
}
IfcSchema::IfcRepresentation mapping::representation_mapped_to(const IfcSchema::IfcRepresentation& representation) {
IfcSchema::IfcRepresentation representation_mapped_to;
std::vector<IfcSchema::IfcRepresentationItem> items = representation.Items();
if (items.size() == 1) {
IfcSchema::IfcRepresentationItem& item = items.front();
if (item.declaration().is(IfcSchema::IfcMappedItem::Class())) {
if (item.StyledByItem().size() == 0) {
IfcSchema::IfcMappedItem mapped_item = item.as<IfcSchema::IfcMappedItem>();
taxonomy::matrix4::ptr target;
try {
target = taxonomy::cast<taxonomy::matrix4>(map(mapped_item.MappingTarget()));
} catch (const std::exception& e) {
logger::error(e);
}
if (target && target->is_identity()) {
IfcSchema::IfcRepresentationMap rmap = mapped_item.MappingSource();
taxonomy::matrix4::ptr origin = taxonomy::cast<taxonomy::matrix4>(map(rmap.MappingOrigin()));
if (origin->is_identity()) {
representation_mapped_to = rmap.MappedRepresentation();
}
}
}
}
}
return representation_mapped_to;
}
namespace {
const IfcSchema::IfcRepresentationItem find_item_carrying_style(IfcSchema::IfcRepresentationItem item) {
if (!item.StyledByItem().empty()) {
return item;
}
while (auto booleanresult = item.as<IfcSchema::IfcBooleanClippingResult>()) {
// All instantiations of IfcBooleanOperand (type of FirstOperand) are subtypes of
// IfcGeometricRepresentationItem
// @nb this is not really how the select hierarchy is structured, not all representation items are selected here
item = booleanresult.FirstOperand().concrete().as<IfcSchema::IfcRepresentationItem>();
if (!item.StyledByItem().empty()) {
return item;
}
}
// TODO: Ideally this would be done for other entities (such as IfcCsgSolid) as well.
// But neither are these very prevalent, nor does the current IfcOpenShell style
// mechanism enable to conveniently style subshapes, which would be necessary for
// distinctly styled union operands.
return item;
}
template <typename T>
std::pair<IfcSchema::IfcSurfaceStyle, T> get_surface_style(const IfcSchema::IfcStyledItem& si) {
std::vector<IfcSchema::IfcPresentationStyle> prs_styles;
#ifdef SCHEMA_HAS_IfcStyleAssignmentSelect
auto style_assignments = si.Styles();
for (auto kt = style_assignments.begin(); kt != style_assignments.end(); ++kt) {
// Using IfcPresentationStyleAssignment is deprecated, use the direct assignment of a subtype of IfcPresentationStyle instead.
auto style_k = (*kt).as<IfcSchema::IfcPresentationStyle>();
if (style_k) {
prs_styles.push_back(style_k);
continue;
}
auto style_assignment = (*kt).as<IfcSchema::IfcPresentationStyleAssignment>();
if (!style_assignment) {
continue;
}
// Only in case of 2x3 or old style IfcPresentationStyleAssignment
auto styles = style_assignment.Styles();
#elif defined(SCHEMA_HAS_IfcPresentationStyleAssignment)
std::vector<IfcSchema::IfcPresentationStyleAssignment> style_assignments = si.Styles();
for (auto& style_assignment : style_assignments) {
// Only in case of 2x3 or old style IfcPresentationStyleAssignment
auto styles = style_assignment.Styles();
#else
auto styles = si.Styles();
#endif
for (auto lt = styles.begin(); lt != styles.end(); ++lt) {
auto style_l = (*lt).as<IfcSchema::IfcPresentationStyle>();
if (style_l) {
prs_styles.push_back(style_l);
}
}
#if defined(SCHEMA_HAS_IfcStyleAssignmentSelect) || defined(SCHEMA_HAS_IfcPresentationStyleAssignment)
}
#endif
IfcSchema::IfcSurfaceStyle surface_style_;
for (auto& style : prs_styles) {
if (auto surface_style = style.as<IfcSchema::IfcSurfaceStyle>()) {
if (surface_style.Side() != IfcSchema::IfcSurfaceSide::IfcSurfaceSide_NEGATIVE) {
surface_style_ = surface_style;
auto styles_elements = surface_style.Styles();
for (auto mt = styles_elements.begin(); mt != styles_elements.end(); ++mt) {
if (auto mtt = (*mt).template as<T>()) {
return std::make_pair(surface_style, mtt);
}
}
}
}
}
return std::make_pair(surface_style_, T{});
}
bool process_colour(const IfcSchema::IfcColourRgb& colour, std::array<double, 3>& rgb) {
if (colour) {
rgb[0] = colour.Red();
rgb[1] = colour.Green();
rgb[2] = colour.Blue();
}
return colour;
}
bool process_colour(const IfcSchema::IfcNormalisedRatioMeasure& factor, std::array<double, 3>& rgb) {
if (factor) {
const double f = factor;
rgb[0] = rgb[1] = rgb[2] = f;
}
return factor;
}
bool process_colour(const IfcSchema::IfcColourOrFactor& colour_or_factor, std::array<double, 3>& rgb) {
if (!colour_or_factor) {
return false;
} else if (auto crgb = colour_or_factor.as<IfcSchema::IfcColourRgb>()) {
return process_colour(crgb, rgb);
} else if (auto ratio = colour_or_factor.as<IfcSchema::IfcNormalisedRatioMeasure>()) {
return process_colour(ratio, rgb);
} else {
return false;
}
}
}
IfcSchema::IfcStyledItem mapping::find_style(const IfcSchema::IfcRepresentationItem& representation_item_) {
// For certain representation items, most notably boolean operands,
// a style definition might reside on one of its operands.
auto representation_item = representation_item_;
representation_item = find_item_carrying_style(representation_item);
if (auto st = representation_item.as<IfcSchema::IfcStyledItem>()) {
return st;
}
auto styled_items = representation_item.StyledByItem();
if (styled_items.size()) {
// StyledByItem is a SET [0:1] OF IfcStyledItem, so we return after the first IfcStyledItem:
return styled_items.front();
}
return IfcSchema::IfcStyledItem{};
}
taxonomy::ptr mapping::map_impl(const IfcSchema::IfcMaterial& material) {
std::vector<IfcSchema::IfcMaterialDefinitionRepresentation> defs = material.HasRepresentation();
if (defs.size() == 0) {
failed_on_purpose_.insert(material);
}
for (auto jt = defs.begin(); jt != defs.end(); ++jt) {
std::vector<IfcSchema::IfcRepresentation> reps = (*jt).Representations();
std::vector<IfcSchema::IfcStyledItem> styles;
for (auto it = reps.begin(); it != reps.end(); ++it) {
auto itms = it->Items();
for (auto& itm : itms) {
if (auto si = itm.as<IfcSchema::IfcStyledItem>()) {
styles.push_back(si);
}
}
}
if (styles.size() == 1) {
IfcSchema::IfcStyledItem& styled_item = styles.front();
auto mapped_item = map(styled_item);
if (mapped_item) {
return mapped_item;
}
// Check if it's failed or just some unsupported case.
if (failed_on_purpose_.find(styled_item) == failed_on_purpose_.end()) {
return nullptr;
}
logger::warning("Skipping unsupported material style for material: ", material);
}
}
// When material does not have a representation we don't create a style from it
return nullptr;
/*
taxonomy::style::ptr material_style = taxonomy::make<taxonomy::style>();
material_style->instance = material;
if (settings_.get<settings::UseMaterialNames>().get()) {
material_style->name = material->Name();
} else {
std::ostringstream oss;
oss << material->declaration().name() << "-" << material->id();
material_style->name = oss.str();
}
return material_style;
*/
// @todo
// IfcGeom::SurfaceStyle material_style = IfcGeom::SurfaceStyle(material->data().id(), material->Name());
// return &(style_cache[material->data().id()] = material_style);
}
taxonomy::ptr mapping::map_impl(const IfcSchema::IfcStyledItem& inst) {
auto style_pair = get_surface_style<IfcSchema::IfcSurfaceStyleShading>(inst);
auto [style, shading] = style_pair;
if (!style) {
// E.g. IfcCurveStyle is skipped as unsupported.
logger::warning("Only IfcSurfaceStyle is supported, couldn't find it in IfcStyledItem: ", inst);
failed_on_purpose_.insert(inst);
return nullptr;
}
// map and not map_impl otherwise no caching
return map(style);
}
taxonomy::ptr mapping::map_impl(const IfcSchema::IfcSurfaceStyle& style) {
auto styles = style.Styles();
IfcSchema::IfcSurfaceStyleShading shading;
for (auto& s : styles) {
if (shading = s.as<IfcSchema::IfcSurfaceStyleShading>()) {
break;
}
}
taxonomy::style::ptr surface_style = taxonomy::make<taxonomy::style>();
surface_style->instance = style;
if (settings_.get<settings::UseMaterialNames>().get() && style.Name()) {
surface_style->name = *style.Name();
} else {
std::ostringstream oss;
if (shading) {
oss << shading.declaration().name() << "-" << shading.id();
} else {
oss << "-";
}
surface_style->name = oss.str();
}
if (!shading) {
// E.g. IfcSurface style has only IfcExternallyDefinedSurfaceStyle.
return surface_style;
}
surface_style->use_surface_color = settings_.get<settings::SurfaceColour>().get();
static taxonomy::colour white = taxonomy::colour(1., 1., 1.);
std::array<double, 3> rgb;
if (process_colour(shading.SurfaceColour(), rgb)) {
surface_style->surface.components() << rgb[0], rgb[1], rgb[2];
surface_style->diffuse = surface_style->surface;
}
if (auto rendering_style = shading.as<IfcSchema::IfcSurfaceStyleRendering>()) {
if (rendering_style.DiffuseColour() && process_colour(rendering_style.DiffuseColour(), rgb)) {
const taxonomy::colour& old_diffuse = surface_style->diffuse ? surface_style->diffuse : white;
surface_style->diffuse = taxonomy::colour(old_diffuse.r() * rgb[0], old_diffuse.g() * rgb[1], old_diffuse.b() * rgb[2]);
}
if (rendering_style.DiffuseTransmissionColour()) {
// Not supported
}
if (rendering_style.ReflectionColour()) {
// Not supported
}
if (rendering_style.SpecularColour() && process_colour(rendering_style.SpecularColour(), rgb)) {
surface_style->specular = taxonomy::colour(rgb[0], rgb[1], rgb[2]);
}
if (rendering_style.SpecularHighlight()) {
IfcSchema::IfcSpecularHighlightSelect highlight = rendering_style.SpecularHighlight();
if (auto roughness_ = highlight.as<IfcSchema::IfcSpecularRoughness>()) {
double roughness = roughness_;
if (roughness >= 1e-9) {
surface_style->specularity = (1.0 / roughness);
}
} else if (auto exponent = highlight.as<IfcSchema::IfcSpecularExponent>()) {
surface_style->specularity = exponent;
}
}
if (rendering_style.TransmissionColour()) {
// Not supported
}
#ifndef SCHEMA_IfcSurfaceStyleShading_HAS_Transparency
// ifc2x3
if (rendering_style.Transparency()) {
const double d = *rendering_style.Transparency();
surface_style->transparency = d;
}
#endif
}
#ifdef SCHEMA_IfcSurfaceStyleShading_HAS_Transparency
// ifc4 and onwards
if (shading.Transparency()) {
const double d = *shading.Transparency();
surface_style->transparency = d;
}
#endif
return surface_style;
}
taxonomy::ptr mapping::map(const express::Base& inst) {
auto iden = inst.identity();
if (use_caching_) {
std::lock_guard<std::mutex> guard(cache_guard_);
auto it = cache_.find(iden);
if (it != cache_.end()) {
return it->second;
}
}
taxonomy::ptr item = nullptr;
// @todo we should check whether there is a notice performance impact on the large sequence
// of if-statements and whether a switch on e.g inst.declaration()->index_in_schema()
// isn't more efficient (which would disable inheritance though).
bool matched = false;
#include "bind_convert_impl.i"
if (item) {
if (use_caching_) {
std::lock_guard<std::mutex> guard(cache_guard_);
cache_.insert({iden, item});
}
} else if (!matched) {
logger::message(logger::LOG_ERROR, "No operation defined for:", inst);
}
return item;
}
namespace {
express::Base get_RelatingObject(IfcSchema::IfcRelDecomposes& decompose) {
#ifdef SCHEMA_IfcRelDecomposes_HAS_RelatingObject
return decompose.RelatingObject();
#else
IfcSchema::IfcRelAggregates aggr = decompose.as<IfcSchema::IfcRelAggregates>();
if (aggr) {
return aggr.RelatingObject();
}
return express::Base{};
#endif
}
}
express::Base mapping::get_decomposing_entity(const express::Base& inst, bool include_openings) {
IfcSchema::IfcObjectDefinition parent;
auto product = inst.as<IfcSchema::IfcProduct>();
if (!product) {
return parent;
}
/* In case of an opening element, parent to the RelatingBuildingElement */
if (include_openings && product.declaration().is(IfcSchema::IfcOpeningElement::Class())) {
IfcSchema::IfcOpeningElement opening = product.as<IfcSchema::IfcOpeningElement>();
std::vector<IfcSchema::IfcRelVoidsElement> voids = opening.VoidsElements();
if (voids.size()) {
IfcSchema::IfcRelVoidsElement& ifc_void = voids.front();
parent = ifc_void.RelatingBuildingElement();
}
} else if (product.declaration().is(IfcSchema::IfcElement::Class())) {
IfcSchema::IfcElement element = product.as<IfcSchema::IfcElement>();
std::vector<IfcSchema::IfcRelFillsElement> fills = element.FillsVoids();
/* In case of a RelatedBuildingElement parent to the opening element */
if (fills.size() && include_openings) {
for (auto& fill : fills) {
IfcSchema::IfcObjectDefinition ifc_objectdef = fill.RelatingOpeningElement();
if (product == ifc_objectdef) continue;
parent = ifc_objectdef;
}
}
/* Else simply parent to the containing structure */
if (!parent) {
std::vector<IfcSchema::IfcRelContainedInSpatialStructure> parents = element.ContainedInStructure();
if (parents.size()) {
IfcSchema::IfcRelContainedInSpatialStructure& container = parents.front();
parent = container.RelatingStructure();
}
}
}
/* Parent decompositions to the RelatingObject */
if (!parent) {
std::vector<express::Entity> parents = product.file()->get_inverse(product.id(), (&IfcSchema::IfcRelAggregates::Class()), -1);
auto nests = product.file()->get_inverse(product.id(), (&IfcSchema::IfcRelNests::Class()), -1);
parents.insert(parents.end(), nests.begin(), nests.end());
for (auto it = parents.begin(); it != parents.end(); ++it) {
IfcSchema::IfcRelDecomposes decompose = (*it).as<IfcSchema::IfcRelDecomposes>();
express::Base ifc_objectdef;
ifc_objectdef = get_RelatingObject(decompose);
if (!ifc_objectdef || product == ifc_objectdef) continue;
parent = ifc_objectdef.as<IfcSchema::IfcObjectDefinition>();
}
}
return parent;
}
std::map<std::string, express::Base> mapping::get_layers(const express::Base& inst) {
auto prod = inst.as<IfcSchema::IfcProduct>();
std::map<std::string, express::Base> layers;
if (prod.Representation()) {
std::vector<express::Base> representations = ifcopenshell::traverse(prod.Representation());
for (auto& inst : representations) {
if (auto repr = inst.as<IfcSchema::IfcRepresentation>()) {
std::vector<IfcSchema::IfcPresentationLayerAssignment> a = repr.LayerAssignments();
for (auto& b : a) {
layers[b.Name()] = b;
}
}
}
}
return layers;
}
void mapping::initialize_units_() {
// Set default units, set length to meters, angles to undefined
length_unit_ = 1.;
angle_unit_ = -1.;
length_unit_name_ = "METER";
#ifdef SCHEMA_HAS_IfcContext
auto projects = file_->instances_by_type<IfcSchema::IfcContext>();
#else
auto projects = file_->instances_by_type<IfcSchema::IfcProject>();
#endif
IfcSchema::IfcUnitAssignment unit_assignment;
if (projects.size() == 1) {
auto& project = projects.front();
unit_assignment = project.UnitsInContext();
} else {
logger::warning("Not a single project or context in file");
}
if (!unit_assignment) {
logger::warning("Unable to detect unit information");
return;
}
bool length_unit_encountered = false, angle_unit_encountered = false;
try {
auto units = unit_assignment.Units();
if (units.empty()) {
logger::warning("No unit information found");
} else {
for (auto& base : units) {
if (auto named_unit = base.as<IfcSchema::IfcNamedUnit>()) {
if (named_unit.UnitType() == IfcSchema::IfcUnitEnum::IfcUnit_LENGTHUNIT ||
named_unit.UnitType() == IfcSchema::IfcUnitEnum::IfcUnit_PLANEANGLEUNIT) {
std::string current_unit_name;
const double current_unit_magnitude = ifcopenshell::get_SI_equivalent<IfcSchema>(named_unit);
if (current_unit_magnitude != 0.) {
if (auto u = named_unit.as<IfcSchema::IfcConversionBasedUnit>()) {
current_unit_name = u.Name();
} else if (auto si_unit = named_unit.as<IfcSchema::IfcSIUnit>()) {
if (si_unit.Prefix()) {
current_unit_name = IfcSchema::IfcSIPrefix::ToString(*si_unit.Prefix());
}
current_unit_name += IfcSchema::IfcSIUnitName::ToString(si_unit.Name());
}
if (named_unit.UnitType() == IfcSchema::IfcUnitEnum::IfcUnit_LENGTHUNIT) {
length_unit_name_ = current_unit_name;
length_unit_ = current_unit_magnitude;
length_unit_encountered = true;
} else {
angle_unit_ = current_unit_magnitude;
angle_unit_encountered = true;
}
}
}
}
}
}
} catch (const ifcopenshell::exception& ex) {
std::stringstream ss;
ss << "Failed to determine unit information '" << ex.what() << "'";
logger::message(logger::LOG_ERROR, ss.str());
}
if (!length_unit_encountered) {
logger::warning("No length unit encountered");
}
if (!angle_unit_encountered) {
logger::warning("No plane angle unit encountered");
}
// @todo move to a more descriptive function
if (settings_.get<settings::BuildingLocalPlacement>().get()) {
placement_rel_to_type_ = file_->schema()->declaration_by_name("IfcBuilding");
}
if (settings_.get<settings::SiteLocalPlacement>().get()) {
placement_rel_to_type_ = file_->schema()->declaration_by_name("IfcSite");
}
// Translation is applied first, then rotation.
if (settings_.get<ModelOffset>().has()) {
auto vs = settings_.get<ModelOffset>().get();
if (vs.size() == 3) {
offset_and_rotation_ *= Eigen::Affine3d(Eigen::Translation3d(vs[0], vs[1], vs[2])).matrix();
} else {
logger::error("Expected 3 values for model-offset setting");
}
}
if (settings_.get<ModelRotation>().has()) {
auto vs = settings_.get<ModelRotation>().get();
if (vs.size() == 4) {
// @nb W, X, Y, Z
auto m3 = Eigen::Quaterniond(vs[3], vs[0], vs[1], vs[2]).normalized().matrix();
Eigen::Matrix4d m4 = Eigen::Matrix4d::Identity();
m4 << m3;
offset_and_rotation_ *= m4;
} else {
logger::error("Expected 4 values for model-rotation setting");
}
}
}
void mapping::initialize_settings() {
settings_.get<settings::LengthUnit>().value = length_unit_;
settings_.get<settings::PlaneUnit>().value = angle_unit_;
// Set precision from file
double lowest_precision_encountered = std::numeric_limits<double>::infinity();
bool any_precision_encountered = false;
std::vector<IfcSchema::IfcGeometricRepresentationContext> contexts =
file_->instances_by_type_excl_subtypes<IfcSchema::IfcGeometricRepresentationContext>();
for (auto& context : contexts) {
// See if there is a context_id filter and whether the context is selected
if (settings_.get<settings::ContextIds>().has()) {
auto cids = settings_.get<settings::ContextIds>().get();
if (cids.find(context.id()) == cids.end()) {
bool selected_sub_context = false;
auto subs = context.HasSubContexts();
for (auto& sub : subs) {
if (cids.find(context.id()) != cids.end()) {
selected_sub_context = true;
break;
}
}
if (!selected_sub_context) {
continue;
}
}
}
auto fp = settings_.get<settings::PrecisionFactor>().get();
if (context.Precision() && (*context.Precision() * length_unit_ * fp) < lowest_precision_encountered) {
// Some arbitrary factor that has proven to work better for the models in the set of test files.
lowest_precision_encountered = *context.Precision() * length_unit_ * fp;
any_precision_encountered = true;
}
}
double precision_to_set = 1.e-5;
if (any_precision_encountered) {
if (lowest_precision_encountered < 1.e-7) {
logger::message(logger::LOG_WARNING, "Precision lower than 0.0000001 meter not enforced");
precision_to_set = 1.e-7;
} else {
precision_to_set = lowest_precision_encountered;
}
}
settings_.get<Precision>().value = precision_to_set;
}
bool mapping::get_layerset_information(const express::Base& p, layerset_information& info, int &)
{
auto product = p.as<IfcSchema::IfcProduct>();
if (!product) {
return false;
}
IfcSchema::IfcMaterialLayerSetUsage usage;
// Handle_Geom_Surface reference_surface;
std::vector<IfcSchema::IfcRelAssociates> associations = product.HasAssociations();
for (auto it = associations.begin(); it != associations.end(); ++it) {
IfcSchema::IfcRelAssociatesMaterial associates_material = (*it).as<IfcSchema::IfcRelAssociatesMaterial>();
if (associates_material) {
usage = associates_material.RelatingMaterial().as<IfcSchema::IfcMaterialLayerSetUsage>();
break;
}
}
if (!usage) {
return false;
}
IfcSchema::IfcRepresentation body_representation = find_representation(product, "Body");
if (!body_representation) {
logger::warning("No body representation for product", product);
return false;
}
const IfcSchema::IfcMaterialLayerSet layerset = usage.ForLayerSet();
const bool positive = usage.DirectionSense() == IfcSchema::IfcDirectionSenseEnum::IfcDirectionSense_POSITIVE;
double offset = usage.OffsetFromReferenceLine() * this->length_unit_;
std::vector<IfcSchema::IfcMaterialLayer> material_layers = layerset.MaterialLayers();
if (product.declaration().is(IfcSchema::IfcWall::Class())) {
IfcSchema::IfcRepresentation axis_representation = find_representation(product, "Axis");
if (!axis_representation) {
logger::message(logger::LOG_WARNING, "No axis representation for:", product);
return false;
}
auto curve = map(axis_representation);
auto product_node = taxonomy::cast<taxonomy::geom_item>(map(product));
auto& m4 = product_node->matrix;
auto c2 = flatten(taxonomy::cast<taxonomy::collection>(curve));
if (c2->children.empty()) {
return false;
}
#ifdef TAXONOMY_USE_NAKED_PTR
delete curve;
delete product_node;
#endif
auto c = c2->children[0];
auto Z = taxonomy::make<taxonomy::direction3>(0, 0, 1);;
auto ofc = taxonomy::make<taxonomy::offset_curve>();
ofc->offset = -offset;
ofc->reference = Z;
ofc->basis = c2->children[0];
ofc->matrix = m4;
info.layers.push_back(ofc);
for (auto it = material_layers.begin(); it != material_layers.end(); ++it) {
info.styles.push_back(*taxonomy::cast<taxonomy::style>(map((*it).Material())));
double thickness = (*it).LayerThickness() * this->length_unit_;
info.thicknesses.push_back(thickness);
if (!positive) {
thickness *= -1;
}
offset += thickness;
if (fabs(offset) < 1.e-7) {
auto ofc = c;
c->matrix = m4;
info.layers.push_back(ofc);
} else {
auto ofc = taxonomy::make<taxonomy::offset_curve>();
ofc->offset = -offset;
ofc->reference = Z;
ofc->basis = c2;
ofc->matrix = m4;
info.layers.push_back(ofc);
}
}
#ifdef TAXONOMY_USE_NAKED_PTR
delete c2;
#endif
if (positive) {
std::reverse(info.thicknesses.begin(), info.thicknesses.end());
std::reverse(info.styles.begin(), info.styles.end());
std::reverse(info.layers.begin(), info.layers.end());
}
} else {
auto resources = ifcopenshell::traverse(body_representation);
std::vector<IfcSchema::IfcExtrudedAreaSolid> extrusions;
for (auto& r : resources) {
if (auto ex = r.as<IfcSchema::IfcExtrudedAreaSolid>()) {
extrusions.push_back(ex);
}
}
if (extrusions.size() != 1) {
logger::message(logger::LOG_WARNING, "No single extrusion found in body representation for:", product);
return false;
}
IfcSchema::IfcExtrudedAreaSolid& extrusion = extrusions.front();
taxonomy::matrix4::ptr extrusion_position;
bool has_position = true;
#ifdef SCHEMA_IfcSweptAreaSolid_Position_IS_OPTIONAL
has_position = !!extrusion.Position();
#endif
if (has_position) {
auto m4 = taxonomy::cast<taxonomy::matrix4>(map(extrusion.Position()));
if (!m4) {
logger::message(logger::LOG_ERROR, "Failed to convert placement for extrusion of:", product);
return false;
} else {
extrusion_position = m4;
}
}
taxonomy::direction3::ptr extrusion_direction = taxonomy::cast<taxonomy::direction3>(map(extrusion.ExtrudedDirection()));
if (!extrusion_direction) {
logger::message(logger::LOG_ERROR, "Failed to convert direction for extrusion of:", product);
return false;
}
// @todo I don't think this is correct actually. This shouldn't take into account extrusion direction?
// reference_surface = new Geom_Plane(extrusion_position.TranslationPart(), extrusion_direction);
{
auto pln = taxonomy::make<taxonomy::plane>();
pln->matrix = extrusion_position;
info.layers.push_back(pln);
}
for (auto& layer : material_layers) {
info.styles.push_back(*taxonomy::cast<taxonomy::style>(map(layer.Material())));
double thickness = layer.LayerThickness() * this->length_unit_;
info.thicknesses.push_back(thickness);
if (!positive) {
thickness *= -1;
}
offset += thickness;
auto offset_matrix = taxonomy::make<taxonomy::matrix4>();
offset_matrix->components()(2, 3) = offset;
offset_matrix->components()(3, 3) = 1.;
offset_matrix->components() *= extrusion_position->components();
auto pln = taxonomy::make<taxonomy::plane>();
pln->matrix = offset_matrix;
info.layers.push_back(pln);
}
if (positive) {
std::reverse(info.thicknesses.begin(), info.thicknesses.end());
std::reverse(info.styles.begin(), info.styles.end());
std::reverse(info.layers.begin(), info.layers.end());
}
}
return true;
}
bool mapping::get_wall_neighbours(const express::Base&, std::vector<endpoint_connection>&) {
return false;
}
IfcSchema::IfcRepresentation mapping::find_representation(const IfcSchema::IfcProduct& product, const std::string& identifier) {
if (auto prod_rep = product.Representation()) {
std::vector<IfcSchema::IfcRepresentation> reps = prod_rep.Representations();
for (auto& rep : reps) {
if (rep.RepresentationIdentifier() && *rep.RepresentationIdentifier() == identifier) {
return rep;
}
}
}
return IfcSchema::IfcRepresentation{};
}
void mapping::addRepresentationsFromContextIds(std::vector<IfcSchema::IfcRepresentation>& representations) {
for (auto context_id : settings_.get<settings::ContextIds>().get()) {
IfcSchema::IfcGeometricRepresentationContext context;
try {
context = file_->instance_by_id(context_id).as<IfcSchema::IfcGeometricRepresentationContext>();
} catch (ifcopenshell::exception& e) {
logger::error(e);
continue;
}
if (!context) {
logger::error("Failed to process context ID " + std::to_string(context_id));
continue;
}
auto reps_in_context = context.RepresentationsInContext();
for (auto& rep : reps_in_context) {
representations.push_back(rep);
}
}
}
void mapping::addRepresentationsFromDefaultContexts(std::vector<IfcSchema::IfcRepresentation>& representations) {
std::set<std::string> allowed_context_types;
allowed_context_types.insert("model");
allowed_context_types.insert("plan");
allowed_context_types.insert("notdefined");
std::set<std::string> context_types;
if (this->settings_.get<settings::OutputDimensionality>().get() != settings::CURVES) {
// Really this should only be 'Model', as per
// the standard 'Design' is deprecated. So,
// just for backwards compatibility:
context_types.insert("model");
context_types.insert("design");
// Some earlier (?) versions DDS-CAD output their own ContextTypes
context_types.insert("model view");
context_types.insert("detail view");
}
if (this->settings_.get<settings::OutputDimensionality>().get() != settings::SURFACES_AND_SOLIDS) {
context_types.insert("plan");
}
auto contexts =
file_->instances_by_type<IfcSchema::IfcGeometricRepresentationContext>();
std::vector<IfcSchema::IfcGeometricRepresentationContext> filtered_contexts;
for (auto& context : contexts) {
if (context.declaration().is(IfcSchema::IfcGeometricRepresentationSubContext::Class())) {
// Continue, as the list of subcontexts will be considered
// by the parent's context inverse attributes.
continue;
}
try {
if (context.ContextType()) {
std::string context_type = *context.ContextType();
boost::to_lower(context_type);
if (allowed_context_types.find(context_type) == allowed_context_types.end()) {
logger::warning(std::string("ContextType '") + *context.ContextType() + "' not allowed:", context);
}
if (context_types.find(context_type) != context_types.end()) {
filtered_contexts.push_back(context);
}
}
} catch (const std::exception& e) {
logger::error(e);
}
}
// In case no contexts are identified based on their ContextType, all contexts are
// considered. Note that sub contexts are excluded as they are considered later on.
if (filtered_contexts.empty()) {
for (auto& context : contexts) {
if (!context.declaration().is(IfcSchema::IfcGeometricRepresentationSubContext::Class())) {
filtered_contexts.push_back(context);
}
}
}
for (auto& context : filtered_contexts) {
auto reps_in_context = context.RepresentationsInContext();
representations.insert(representations.end(), reps_in_context.begin(), reps_in_context.end());
std::vector<IfcSchema::IfcGeometricRepresentationSubContext> sub_contexts = context.HasSubContexts();
for (auto& subcontext : sub_contexts) {
auto reps_in_subcontext = subcontext.RepresentationsInContext();
representations.insert(representations.end(), reps_in_subcontext.begin(), reps_in_subcontext.end());
}
// There is no need for full recursion as the following is governed by the schema:
// WR31: The parent context shall not be another geometric representation sub context.
}
if (representations.empty()) {
logger::warning("No representations encountered in relevant contexts, using all");
auto all_reps = file_->instances_by_type<IfcSchema::IfcRepresentation>();
representations = all_reps;
}
}
void mapping::ensureRepresentationContextCache_() {
const auto has_context_ids = settings_.get<settings::ContextIds>().has();
const auto dimensionality = settings_.get<settings::OutputDimensionality>().get();
const auto context_ids = has_context_ids ? settings_.get<settings::ContextIds>().get() : std::set<int>{};
std::lock_guard<std::mutex> guard(representation_context_cache_guard_);
if (representation_context_cache_valid_ &&
representation_context_cache_has_context_ids_ == has_context_ids &&
representation_context_cache_dimensionality_ == dimensionality &&
representation_context_cache_ids_ == context_ids) {
return;
}
std::vector<IfcSchema::IfcRepresentation> representations;
if (!has_context_ids) {
addRepresentationsFromDefaultContexts(representations);
} else {
addRepresentationsFromContextIds(representations);
}
std::unordered_set<uint32_t> representation_ids;
representation_ids.reserve(representations.size());
for (auto& representation : representations) {
if (representation) {
representation_ids.insert((uint32_t)representation.id());
}
}
representation_context_cache_ = std::move(representation_ids);
representation_context_cache_ids_ = std::move(context_ids);
representation_context_cache_dimensionality_ = dimensionality;
representation_context_cache_has_context_ids_ = has_context_ids;
representation_context_cache_valid_ = true;
}
express::Base mapping::representation_of(const express::Base& product) {
std::vector<IfcSchema::IfcRepresentation> of_product;
std::vector<IfcSchema::IfcRepresentation> intersection;
std::vector<IfcSchema::IfcRepresentation> intersection_no_box;
ensureRepresentationContextCache_();
if (product.as<IfcSchema::IfcProduct>().Representation()) {
of_product = product.as<IfcSchema::IfcProduct>().Representation().Representations();
}
{
std::lock_guard<std::mutex> guard(representation_context_cache_guard_);
for (auto& r : of_product) {
if (representation_context_cache_.find((uint32_t)r.id()) != representation_context_cache_.end()) {
intersection.push_back(r);
}
}
}
if (intersection.size() == 0 && settings_.get<settings::ContextIds>().has() && this->settings_.get<settings::OutputDimensionality>().get() == settings::CURVES) {
for (auto& r : of_product) {
if (r.RepresentationIdentifier() && *r.RepresentationIdentifier() == "Axis") {
intersection.push_back(r);
}
}
}
if (intersection.size() == 0) {
return express::Base{};
} else {
for (auto& r : intersection) {
auto resources = ifcopenshell::traverse(r);
auto is_bounding_box = std::any_of(resources.begin(), resources.end(), [](const auto& res) { return res.declaration().is(IfcSchema::IfcBoundingBox::Class()); });
if (is_bounding_box) {
continue;
}
intersection_no_box.push_back(r);
}
if (intersection_no_box.size() > 1) {
logger::warning("Multiple applicable representations found for element, selecting arbitrary");
}
if (intersection_no_box.size()) {
return intersection_no_box.front();
} else {
return intersection.front();
}
}
}