// // BIM IFC library: this library works with Autodesk(R) Revit(R) to export IFC files containing model geometry. // Copyright (C) 2012 Autodesk, Inc. // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2.1 of the License, or (at your option) any later version. // // This library 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 GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA // using System; using System.Collections.Generic; using System.Linq; using System.Text; using Autodesk.Revit.DB; using Autodesk.Revit.DB.IFC; using Revit.IFC.Export.Utility; using Revit.IFC.Export.Toolkit; using Revit.IFC.Common.Utility; using System.Text.RegularExpressions; using Revit.IFC.Common.Enums; using Revit.IFC.Common.Extensions; namespace Revit.IFC.Export.Exporter.PropertySet { /// /// Provides static methods to create varies IFC classifications. /// public class ClassificationUtil { private static string GetUniformatURL() { return "https://www.csiresources.org/standards/uniformat"; } /// /// Creates uniformat classification for a single element handle /// /// The ExporterIFC /// the file /// the element /// the element handle public static void CreateUniformatClassification(IFCFile file, Element element, IFCAnyHandle elemHnd) { if (ExporterCacheManager.ExportOptionsCache.ExportGeometryOnly) return; IFCEntityType entType = IFCEntityType.IfcObjectDefinition; if (!Enum.TryParse(elemHnd.TypeName, out IFCEntityType hndEntType)) entType = hndEntType; List elemHnds = [ elemHnd ]; CreateUniformatClassification(file, element, elemHnds, entType); } private static IFCAnyHandle GetOrCreateClassification(IFCFile file, string uniformatKeyString) { if (!ExporterCacheManager.ClassificationCache.ClassificationHandles.TryGetValue(uniformatKeyString, out IFCAnyHandle classification)) { classification = IFCInstanceExporter.CreateClassification(file, "CSI (Construction Specifications Institute)", "1998", 0, 0, 0, uniformatKeyString, "UniFormat Classification", GetUniformatURL()); ExporterCacheManager.ClassificationCache.ClassificationHandles.Add(uniformatKeyString, classification); } return classification; } /// /// Creates uniformat classification. /// /// The file. /// The element. /// The list of element handles that are part of the aggregate of the element. /// The IFC entity of which type the classification to be considered public static void CreateUniformatClassification(IFCFile file, Element element, IList elemHnds, IFCEntityType constraintEntType) { if (ExporterCacheManager.ClassificationCache.UniformatOverridden) return; // Create Uniformat classification, if it is not set. IFCAnyHandle classification = null; string uniformatKeyString = "Uniformat"; string uniformatDescription = ""; foreach (IFCAnyHandle elemHnd in elemHnds) { if (!IFCAnyHandleUtil.IsSubTypeOf(elemHnd, constraintEntType)) continue; ElementId elementId = ExporterCacheManager.HandleToElementCache.Find(elemHnd); Element elementToUse = (MathUtil.IsInvalidElementId(elementId)) ? element : element?.Document?.GetElement(elementId); if (elementToUse == null) continue; (_, string uniformatCode) = ParameterUtil.GetStringValueFromElementOrSymbol(elementToUse, null, false, "Assembly Code"); if (string.IsNullOrEmpty(uniformatCode)) { uniformatCode = ParameterUtil.GetStringValueFromElementOrSymbol(elementToUse, null, false, BuiltInParameter.ASSEMBLY_CODE); } if (!string.IsNullOrWhiteSpace(uniformatCode)) { (_, string uniformatRefName) = ParameterUtil.GetStringValueFromElementOrSymbol(elementToUse, null, false, "Assembly Description"); if (string.IsNullOrEmpty(uniformatRefName)) { uniformatRefName = ParameterUtil.GetStringValueFromElementOrSymbol(elementToUse, null, false, BuiltInParameter.ASSEMBLY_DESCRIPTION); } classification ??= GetOrCreateClassification(file, uniformatKeyString); ClassificationReferenceKey key = new ClassificationReferenceKey(GetUniformatURL(), uniformatCode, uniformatRefName, uniformatDescription, classification); InsertClassificationReference(file, key, elemHnd); } } } /// /// Create IfcClassification references from hardwired or custom classification code fields. /// /// The exporterIFC class. /// The IFC file class. /// The element to export. /// The corresponding IFC entity handle. /// True if a classification or classification reference is created. public static bool CreateClassification(ExporterIFC exporterIFC, IFCFile file, Element element, IFCAnyHandle elemHnd) { if (ExporterCacheManager.ExportOptionsCache.ExportGeometryOnly) return false; bool createdClassification = false; string paramClassificationCode = string.Empty; string baseClassificationCodeFieldName = "ClassificationCode"; List customClassificationCodeNames = []; string classificationName = null; string classificationCode = null; string classificationDescription = null; string classificationRefName = null; int customPass = 0; int standardPass = 1; int numCustomCodes = ExporterCacheManager.ClassificationCache.CustomClassificationCodeNames.Count; // Note that we do the "custom" nodes first, and then the 10 standard ones. Element elementType = element.Document.GetElement(element.GetTypeId()); while (standardPass <= 10) { // Create a classification, if it is not set. string classificationCodeFieldName = null; if (customPass < numCustomCodes) { classificationCodeFieldName = ExporterCacheManager.ClassificationCache.CustomClassificationCodeNames[customPass]; customPass++; if (string.IsNullOrWhiteSpace(classificationCodeFieldName)) continue; } else { classificationCodeFieldName = baseClassificationCodeFieldName; if (standardPass > 1) classificationCodeFieldName += "(" + standardPass + ")"; standardPass++; } (_, paramClassificationCode) = ParameterUtil.GetStringValueFromElementOrSymbol(element, elementType, false, classificationCodeFieldName); if (string.IsNullOrEmpty(paramClassificationCode)) { continue; } ParseClassificationCode(paramClassificationCode, classificationCodeFieldName, out classificationName, out classificationCode, out classificationRefName); if (string.IsNullOrEmpty(classificationRefName)) { NamingUtil.IFCStringKey compName = new(classificationCodeFieldName); if (compName.IsEqualTo("ASSEMBLYCODE")) { classificationRefName = ParameterUtil.GetStringValueFromElementOrSymbol(element, elementType, false, BuiltInParameter.ASSEMBLY_DESCRIPTION); } else if (compName.IsEqualTo("CLASSIFICATIONNUMBER")) { classificationRefName = ParameterUtil.GetStringValueFromElementOrSymbol(element, elementType, false, BuiltInParameter.CLASSIFICATION_DESCRIPTION); } } // If classificationName is empty, there is no classification to export. if (string.IsNullOrEmpty(classificationName)) continue; IFCAnyHandle classification; if (!ExporterCacheManager.ClassificationCache.ClassificationHandles.TryGetValue(classificationName, out classification)) { IFCClassification savedClassification = new IFCClassification(); if (ExporterCacheManager.ClassificationCache.ClassificationsByName.TryGetValue(classificationName, out savedClassification)) { if (savedClassification.ClassificationEditionDate != null) { classification = IFCInstanceExporter.CreateClassification(file, savedClassification.ClassificationSource, savedClassification.ClassificationEdition, savedClassification.ClassificationEditionDate.Day, savedClassification.ClassificationEditionDate.Month, savedClassification.ClassificationEditionDate.Year, savedClassification.ClassificationName, null, savedClassification.ClassificationLocation); } else { classification = IFCInstanceExporter.CreateClassification(file, savedClassification.ClassificationSource, savedClassification.ClassificationEdition, 0, 0, 0, savedClassification.ClassificationName, null, savedClassification.ClassificationLocation); } if (!string.IsNullOrEmpty(savedClassification.ClassificationLocation)) ExporterCacheManager.ClassificationLocationCache.Add(classificationName, savedClassification.ClassificationLocation); } else { classification = IFCInstanceExporter.CreateClassification(file, "", "", 0, 0, 0, classificationName, null, null); } ExporterCacheManager.ClassificationCache.ClassificationHandles.Add(classificationName, classification); createdClassification = true; } string location = null; ExporterCacheManager.ClassificationLocationCache.TryGetValue(classificationName, out location); if (!string.IsNullOrEmpty(classificationCode)) { ClassificationReferenceKey key = new ClassificationReferenceKey(location, classificationCode, classificationRefName, classificationDescription, classification); InsertClassificationReference(file, key, elemHnd); createdClassification = true; } } return createdClassification; } /// /// /// /// /// ClassificationCode parameter name to check whether there is assignment in the UI /// the classificationName alwayws return something, default will be: "Default Classification" /// /// /// True if any classification was found. public static bool ParseClassificationCode(string paramClassificationCode, string classificationCodeFieldName, out string classificationName, out string classificationCode, out string classificationRefName) { // Processing the following format: [] | // Partial format will also be supported as long as it follows: (following existing OmniClass style for COBIe, using :) // // : // [] // [] : // Note that the classification code can contain brackets and colons without specifying the classification name and description if the following syntax is used: // []: classificationName = null; classificationCode = null; classificationRefName = null; if (string.IsNullOrWhiteSpace(paramClassificationCode)) return false; // do nothing if it is empty int numCodeparts = 0; // Only remove the left bracket if it is the first non-empty character in the string, and if there is a corresponding right bracket. string parsedParamClassificationCode = paramClassificationCode.Trim(); if (parsedParamClassificationCode[0] == '[') { int rightBracket = parsedParamClassificationCode.IndexOf("]"); if (rightBracket > 0) { // found [] if (rightBracket > 1) classificationName = parsedParamClassificationCode.Substring(1, rightBracket - 1); parsedParamClassificationCode = parsedParamClassificationCode.Substring(rightBracket + 1); numCodeparts++; } } // Now search for the last colon, for the description override. Note that we allow the classification name to have a colon in it if the last character is a colon. int colon = parsedParamClassificationCode.LastIndexOf(":"); if (colon >= 0) { if (colon < parsedParamClassificationCode.Count() - 1) { classificationRefName = parsedParamClassificationCode.Substring(colon + 1).Trim(); numCodeparts++; } parsedParamClassificationCode = parsedParamClassificationCode.Substring(0, colon); } classificationCode = parsedParamClassificationCode.Trim(); numCodeparts++; if (string.IsNullOrEmpty(classificationName)) { // No Classification Name specified, look for Classification Name assignment from the cache (from UI) if (string.IsNullOrEmpty(classificationCodeFieldName) || !ExporterCacheManager.ClassificationCache.FieldNameToClassificationNames.TryGetValue(classificationCodeFieldName, out classificationName)) classificationName = "Default Classification"; } return numCodeparts > 0; } /// /// Create a new classification reference and associate it with the Element (ElemHnd) /// /// The exporterIFC class. /// The IFC file class. /// The corresponding IFC entity handle. /// The classification name. /// The classification code. /// The classification description. /// The location of the classification. public static void InsertClassificationReference(IFCFile file, ClassificationReferenceKey key, IFCAnyHandle elemHnd) { string relName = key.Name + " " + key.ItemReference; string relGuid = GUIDUtil.GenerateIFCGuidFrom( GUIDUtil.CreateGUIDString(IFCEntityType.IfcRelAssociatesClassification, relName, elemHnd)); ExporterCacheManager.ClassificationCache.AddRelation(file, key, relGuid, null, elemHnd); } } }