//
// 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);
}
}
}