2024-09-22 11:05:41 +08:00
|
|
|
|
using Autodesk.Revit.DB;
|
|
|
|
|
|
|
2026-02-21 16:31:24 +08:00
|
|
|
|
namespace ShrlAlgoToolkit.RevitCore.Extensions;
|
2025-07-11 09:20:23 +08:00
|
|
|
|
|
2025-12-28 11:47:54 +08:00
|
|
|
|
public static class ElementExtensions
|
2024-09-22 11:05:41 +08:00
|
|
|
|
{
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 镜像元素
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="element"></param>
|
|
|
|
|
|
/// <param name="plane"></param>
|
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
|
public static Element Mirror(this Element element, Plane plane)
|
|
|
|
|
|
{
|
|
|
|
|
|
ElementTransformUtils.MirrorElement(element.Document, element.Id, plane);
|
|
|
|
|
|
return element;
|
|
|
|
|
|
}
|
|
|
|
|
|
public static bool IsVisible(this Element elem, View view) => FilteredElementCollector.IsViewValidForElementIteration(elem.Document, view.Id)
|
|
|
|
|
|
&& new FilteredElementCollector(elem.Document, view.Id).ToElementIds().Any(id => id == elem.Id);
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 移动元素
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="element"></param>
|
|
|
|
|
|
/// <param name="deltaX"></param>
|
|
|
|
|
|
/// <param name="deltaY"></param>
|
|
|
|
|
|
/// <param name="deltaZ"></param>
|
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
|
public static Element Move(this Element element, double deltaX, double deltaY, double deltaZ)
|
|
|
|
|
|
{
|
|
|
|
|
|
ElementTransformUtils.MoveElement(element.Document, element.Id, new XYZ(deltaX, deltaY, deltaZ));
|
|
|
|
|
|
return element;
|
|
|
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 移动元素
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="element"></param>
|
|
|
|
|
|
/// <param name="vector"></param>
|
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
|
public static Element Move(this Element element, XYZ vector)
|
|
|
|
|
|
{
|
|
|
|
|
|
ElementTransformUtils.MoveElement(element.Document, element.Id, vector);
|
|
|
|
|
|
return element;
|
|
|
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 旋转元素
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="element"></param>
|
|
|
|
|
|
/// <param name="axis"></param>
|
|
|
|
|
|
/// <param name="angle"></param>
|
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
|
public static Element Rotate(this Element element, Line axis, double angle)
|
|
|
|
|
|
{
|
|
|
|
|
|
ElementTransformUtils.RotateElement(element.Document, element.Id, axis, angle);
|
|
|
|
|
|
return element;
|
|
|
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 能否镜像
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="element"></param>
|
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
|
public static bool CanBeMirrored(this Element element)
|
|
|
|
|
|
{
|
|
|
|
|
|
return ElementTransformUtils.CanMirrorElement(element.Document, element.Id);
|
|
|
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 获取标高
|
|
|
|
|
|
/// </summary>
|
2025-02-10 20:53:40 +08:00
|
|
|
|
/// <param name="element"></param>
|
2024-09-22 11:05:41 +08:00
|
|
|
|
/// <returns></returns>
|
2025-02-10 20:53:40 +08:00
|
|
|
|
public static ElementId GetLevelId(this Element element)
|
2024-09-22 11:05:41 +08:00
|
|
|
|
{
|
|
|
|
|
|
// 定义需要检查的参数列表
|
|
|
|
|
|
var parametersToCheck = new BuiltInParameter[]
|
|
|
|
|
|
{
|
|
|
|
|
|
BuiltInParameter.WALL_BASE_CONSTRAINT, // 墙
|
|
|
|
|
|
BuiltInParameter.SCHEDULE_BASE_LEVEL_PARAM, // 柱子标高
|
|
|
|
|
|
BuiltInParameter.INSTANCE_REFERENCE_LEVEL_PARAM, // 梁标高
|
|
|
|
|
|
BuiltInParameter.STAIRS_BASE_LEVEL_PARAM, // 楼梯标高
|
|
|
|
|
|
BuiltInParameter.INSTANCE_ELEVATION_PARAM, // 族实例明细表标高
|
|
|
|
|
|
BuiltInParameter.ROOF_CONSTRAINT_LEVEL_PARAM,//屋顶
|
|
|
|
|
|
BuiltInParameter.INSTANCE_SCHEDULE_ONLY_LEVEL_PARAM,// 族实例明细表标高
|
|
|
|
|
|
BuiltInParameter.RBS_START_LEVEL_PARAM// 管线标高
|
|
|
|
|
|
};
|
|
|
|
|
|
// 依次检查每个参数
|
|
|
|
|
|
foreach (var param in parametersToCheck)
|
|
|
|
|
|
{
|
2025-02-10 20:53:40 +08:00
|
|
|
|
var baseLevelId = element.get_Parameter(param)?.AsElementId();
|
2024-09-22 11:05:41 +08:00
|
|
|
|
if (baseLevelId != ElementId.InvalidElementId && baseLevelId != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
return baseLevelId;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//最后检查楼板或族基准标高
|
2025-02-10 20:53:40 +08:00
|
|
|
|
return element.LevelId;
|
2024-09-22 11:05:41 +08:00
|
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 转换类型
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <typeparam name="T"></typeparam>
|
|
|
|
|
|
/// <param name="element"></param>
|
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
|
public static T Cast<T>(this Element element) where T : Element
|
|
|
|
|
|
{
|
|
|
|
|
|
return (T)element;
|
|
|
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 合并几何
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="firstElement"></param>
|
|
|
|
|
|
/// <param name="secondElement"></param>
|
|
|
|
|
|
public static void JoinGeometry(this Element firstElement, Element secondElement)
|
|
|
|
|
|
{
|
|
|
|
|
|
JoinGeometryUtils.JoinGeometry(firstElement.Document, firstElement, secondElement);
|
|
|
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 分离几何
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="firstElement"></param>
|
|
|
|
|
|
/// <param name="secondElement"></param>
|
|
|
|
|
|
public static void UnJoinGeometry(this Element firstElement, Element secondElement)
|
|
|
|
|
|
{
|
|
|
|
|
|
JoinGeometryUtils.UnjoinGeometry(firstElement.Document, firstElement, secondElement);
|
|
|
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 是否连接
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="firstElement"></param>
|
|
|
|
|
|
/// <param name="secondElement"></param>
|
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
|
public static bool AreElementsJoined(this Element firstElement, Element secondElement)
|
|
|
|
|
|
{
|
|
|
|
|
|
return JoinGeometryUtils.AreElementsJoined(firstElement.Document, firstElement, secondElement);
|
|
|
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 切换连接顺序
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="firstElement"></param>
|
|
|
|
|
|
/// <param name="secondElement"></param>
|
|
|
|
|
|
public static void SwitchJoinOrder(this Element firstElement, Element secondElement)
|
|
|
|
|
|
{
|
|
|
|
|
|
JoinGeometryUtils.SwitchJoinOrder(firstElement.Document, firstElement, secondElement);
|
|
|
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 确定两个连接元素中的第一个是否正在切割第二个元素
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="firstElement"></param>
|
|
|
|
|
|
/// <param name="secondElement"></param>
|
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
|
public static bool IsCuttingElementInJoin(this Element firstElement, Element secondElement)
|
|
|
|
|
|
{
|
|
|
|
|
|
return JoinGeometryUtils.IsCuttingElementInJoin(firstElement.Document, firstElement, secondElement);
|
|
|
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 获取连接的元素
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="element"></param>
|
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
|
public static ICollection<ElementId> GetJoinedElements(this Element element)
|
|
|
|
|
|
{
|
|
|
|
|
|
return JoinGeometryUtils.GetJoinedElements(element.Document, element);
|
|
|
|
|
|
}
|
|
|
|
|
|
public static bool SetValue(this Parameter parameter, object value)
|
|
|
|
|
|
{
|
|
|
|
|
|
var result = false;
|
|
|
|
|
|
if (parameter.IsReadOnly) return false;
|
|
|
|
|
|
switch (parameter.StorageType)
|
|
|
|
|
|
{
|
|
|
|
|
|
case StorageType.Integer:
|
|
|
|
|
|
if (value is int i)
|
|
|
|
|
|
{
|
|
|
|
|
|
result = parameter.Set(i);
|
|
|
|
|
|
}
|
|
|
|
|
|
break;
|
|
|
|
|
|
case StorageType.Double:
|
|
|
|
|
|
if (value is double d)
|
|
|
|
|
|
{
|
|
|
|
|
|
result = parameter.Set(d);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
case StorageType.String:
|
|
|
|
|
|
if (value is string str)
|
|
|
|
|
|
{
|
|
|
|
|
|
result = parameter.Set(str);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
case StorageType.ElementId:
|
|
|
|
|
|
if (value is ElementId id)
|
|
|
|
|
|
{
|
|
|
|
|
|
result = parameter.Set(id);
|
|
|
|
|
|
}
|
|
|
|
|
|
break;
|
|
|
|
|
|
case StorageType.None:
|
|
|
|
|
|
break;
|
|
|
|
|
|
default:
|
|
|
|
|
|
throw new ArgumentOutOfRangeException();
|
|
|
|
|
|
}
|
|
|
|
|
|
return result;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 获取参数值
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="parameter"></param>
|
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
|
public static object GetValue(this Parameter parameter)
|
|
|
|
|
|
{
|
|
|
|
|
|
object result = null;
|
|
|
|
|
|
if (parameter == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
throw new ArgumentNullException(nameof(parameter));
|
|
|
|
|
|
}
|
|
|
|
|
|
switch (parameter.StorageType)
|
|
|
|
|
|
{
|
|
|
|
|
|
case StorageType.None:
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
case StorageType.Integer:
|
|
|
|
|
|
result = parameter.AsInteger();
|
|
|
|
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
case StorageType.Double:
|
|
|
|
|
|
result = parameter.AsDouble();
|
|
|
|
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
case StorageType.String:
|
|
|
|
|
|
//Revit数据库存储的值
|
|
|
|
|
|
var str = parameter.AsString();
|
|
|
|
|
|
if (string.IsNullOrEmpty(str))
|
|
|
|
|
|
{
|
|
|
|
|
|
//用户可见的前端显示,如根据单位设置而显示的值
|
|
|
|
|
|
result = parameter.AsValueString();
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
result = str;
|
|
|
|
|
|
}
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
case StorageType.ElementId:
|
|
|
|
|
|
result = parameter.AsElementId();
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public static void SetElementTransparency(this Element element, int transparency)
|
|
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
var document = element.Document;
|
|
|
|
|
|
var elementOverrides = document.ActiveView.GetElementOverrides(element.Id);
|
|
|
|
|
|
elementOverrides.SetSurfaceTransparency(transparency);
|
|
|
|
|
|
document.ActiveView.SetElementOverrides(element.Id, elementOverrides);
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception)
|
|
|
|
|
|
{
|
|
|
|
|
|
// ignored
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 优先获取实例参数,若无则获取类型参数
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="element"></param>
|
|
|
|
|
|
/// <param name="parameterName"></param>
|
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
|
public static Parameter GetParameter(this Element element, string parameterName)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!element.IsValidObject)
|
|
|
|
|
|
{
|
|
|
|
|
|
throw new ArgumentNullException(nameof(element));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//var parameterName = elem.LookupParameter(paramName);
|
|
|
|
|
|
//if (parameterName == null)
|
|
|
|
|
|
//{
|
|
|
|
|
|
// ElementId typeId = elem.GetTypeId();
|
|
|
|
|
|
// if (typeId != ElementId.InvalidElementId)
|
|
|
|
|
|
// {
|
2025-07-11 09:20:23 +08:00
|
|
|
|
// var elementFunc = elem.doc?.GetElement(typeId);
|
2024-09-22 11:05:41 +08:00
|
|
|
|
// parameterName = elementFunc?.LookupParameter(paramName);
|
|
|
|
|
|
|
|
|
|
|
|
// }
|
|
|
|
|
|
//}
|
|
|
|
|
|
var instanceParameter = element.LookupParameter(parameterName);
|
|
|
|
|
|
if (instanceParameter is { HasValue: true })
|
|
|
|
|
|
{
|
|
|
|
|
|
return instanceParameter;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var elementTypeId = element.GetTypeId();
|
|
|
|
|
|
if (elementTypeId == ElementId.InvalidElementId)
|
|
|
|
|
|
{
|
|
|
|
|
|
return instanceParameter;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var elementType = element.Document.GetElement(elementTypeId);
|
|
|
|
|
|
var symbolParameter = elementType.LookupParameter(parameterName);
|
|
|
|
|
|
return symbolParameter ?? instanceParameter;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 管理图元在各个视图的可见性
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="form"></param>
|
|
|
|
|
|
/// <param name="visibility"></param>
|
|
|
|
|
|
public static void AccessFamilyElementVisibility(this GenericForm form, FamilyElementVisibility visibility)
|
|
|
|
|
|
{
|
|
|
|
|
|
// 得到管理拉伸体的可见性的实例,并读取详细程度的设置
|
|
|
|
|
|
//FamilyElementVisibility visibility = form.GetVisibility();
|
|
|
|
|
|
//FamilyElementVisibilityType visibilityType = visibility.VisibilityType;
|
|
|
|
|
|
//bool shownInCoarse = visibility.IsShownInCoarse;
|
|
|
|
|
|
//bool shownInMedium = visibility.IsShownInMedium;
|
|
|
|
|
|
//bool shownInFine = visibility.IsShownInFine;
|
|
|
|
|
|
//// 设置为在各种详细程度中都显示拉伸体
|
|
|
|
|
|
//visibility.IsShownInTopBottom = false;
|
|
|
|
|
|
//visibility.IsShownInCoarse = true;
|
|
|
|
|
|
//visibility.IsShownInMedium = true;
|
|
|
|
|
|
//visibility.IsShownInFine = true;
|
|
|
|
|
|
// 注意:必须把可见性的修改设置回拉伸体
|
|
|
|
|
|
form.SetVisibility(visibility);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 线性阵列
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public static void CreateLinearArray(this Element element, XYZ vector, int count, ArrayAnchorMember arrayAnchorMember)
|
|
|
|
|
|
{
|
|
|
|
|
|
var document = element.Document;
|
|
|
|
|
|
LinearArray.Create(document, document.ActiveView, element.Id, count, vector, arrayAnchorMember);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public static void GetGeometryObj(this Element element, out List<Face> faces, out List<Edge> edges)
|
|
|
|
|
|
{
|
|
|
|
|
|
//实例族的GeometryElement集合中具有Solid等几何对象的原因是由于族实例对象碰撞后,软件自动会使用geometryInstance用来指定其位置(即使碰撞后,移开),用Solid作为其几何体
|
|
|
|
|
|
//GeometryInstance Transform是族实例在全局坐标系的变换
|
|
|
|
|
|
faces = [];
|
|
|
|
|
|
edges = [];
|
|
|
|
|
|
//var references = new List<Reference>();
|
|
|
|
|
|
Options geomOptions =
|
|
|
|
|
|
new()
|
|
|
|
|
|
{
|
|
|
|
|
|
ComputeReferences = true,
|
|
|
|
|
|
DetailLevel = ViewDetailLevel.Medium,
|
|
|
|
|
|
IncludeNonVisibleObjects = true
|
|
|
|
|
|
};
|
|
|
|
|
|
var geoElem = element.get_Geometry(geomOptions);
|
|
|
|
|
|
foreach (var obj in geoElem)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (obj is GeometryInstance instance)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (instance.GetSymbolGeometry().Any())
|
|
|
|
|
|
{
|
|
|
|
|
|
foreach (var item in instance.GetSymbolGeometry())
|
|
|
|
|
|
{
|
|
|
|
|
|
if (item is Solid solid)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (solid.Edges.Size > 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
edges.AddRange(solid.Edges.OfType<Edge>());
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (solid.Faces.Size > 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
faces.AddRange(solid.Faces.OfType<Face>());
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
if (obj is not Solid solid)
|
|
|
|
|
|
{
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (solid.Edges.Size > 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
edges.AddRange(from Edge edge in solid.Edges select edge);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (solid.Faces.Size > 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
faces.AddRange(from Face face in solid.Faces select face);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 修改元素面颜色(待测试)
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="element"></param>
|
|
|
|
|
|
/// <param name="materialName"></param>
|
|
|
|
|
|
/// <param name="color"></param>
|
|
|
|
|
|
public static void ModifyElementFaceDisplay(this Element element, string materialName, Color color)
|
|
|
|
|
|
{
|
|
|
|
|
|
var doc = element.Document;
|
|
|
|
|
|
var material = doc.GetElement(Material.Create(doc, materialName)) as Material;
|
|
|
|
|
|
material!.Color = color;
|
|
|
|
|
|
|
|
|
|
|
|
Options options = new() { ComputeReferences = true, DetailLevel = ViewDetailLevel.Medium };
|
|
|
|
|
|
|
|
|
|
|
|
var e = element.get_Geometry(options);
|
|
|
|
|
|
foreach (var geometryObject in e)
|
|
|
|
|
|
{
|
|
|
|
|
|
var geoInstance = geometryObject as GeometryInstance;
|
|
|
|
|
|
if (geoInstance != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
var geoElement = geoInstance.GetInstanceGeometry();
|
|
|
|
|
|
foreach (var obj2 in geoElement)
|
|
|
|
|
|
{
|
|
|
|
|
|
var solid = obj2 as Solid;
|
|
|
|
|
|
if (solid.SurfaceArea != 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
foreach (Face face in solid.Faces)
|
|
|
|
|
|
{
|
|
|
|
|
|
doc.Paint(element.Id, face, material.Id);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|