//
// (C) Copyright 2003-2023 by Autodesk, Inc.
//
// Permission to use, copy, modify, and distribute this software in
// object code form for any purpose and without fee is hereby granted,
// provided that the above copyright notice appears in all copies and
// that both that copyright notice and the limited warranty and
// restricted rights notice below appear in all supporting
// documentation.
//
// AUTODESK PROVIDES THIS PROGRAM "AS IS" AND WITH ALL FAULTS.
// AUTODESK SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTY OF
// MERCHANTABILITY OR FITNESS FOR A PARTICULAR USE. AUTODESK, INC.
// DOES NOT WARRANT THAT THE OPERATION OF THE PROGRAM WILL BE
// UNINTERRUPTED OR ERROR FREE.
//
// Use, duplication, or disclosure by the U.S. Government is subject to
// restrictions set forth in FAR 52.227-19 (Commercial Computer
// Software - Restricted Rights) and DFAR 252.227-7013(c)(1)(ii)
// (Rights in Technical Data and Computer Software), as applicable.
//
using System;
using System.Collections.Generic;
using System.Diagnostics;
using Autodesk.Revit;
using Autodesk.Revit.DB;
using Autodesk.Revit.UI;
using Autodesk.Revit.UI.Selection;
namespace Revit.SDK.Samples.FindColumns.CS
{
///
/// Find all walls that have embedded columns in them, and the ids of those embedded columns.
///
[Autodesk.Revit.Attributes.Transaction(Autodesk.Revit.Attributes.TransactionMode.ReadOnly)]
[Autodesk.Revit.Attributes.Regeneration(Autodesk.Revit.Attributes.RegenerationOption.Manual)]
public class Command : IExternalCommand
{
#region Class Members
///
/// This is the increment by which the code checks for embedded columns on a curved wall.
///
private static double WallIncrement = 0.5; // Check every 1/2'
///
/// This is a slight offset to ensure that the ray-trace occurs just outside the extents of the wall.
///
private static double WALL_EPSILON = (1.0 / 8.0) / 12.0; // 1/8"
///
/// Dictionary of columns and walls
///
private Dictionary> m_columnsOnWall = new Dictionary>();
///
/// ElementId list for columns which are on walls
///
private List m_allColumnsOnWalls = new List();
///
/// revit application
///
private Autodesk.Revit.UI.UIApplication m_app;
///
/// Revit active document
///
private Autodesk.Revit.DB.Document m_doc;
///
/// A 3d view
///
private View3D m_view3D;
#endregion
#region Class Interface Implementation
///
/// The top level command.
///
/// An object that is passed to the external application
/// which contains data related to the command,
/// such as the application object and active view.
/// A message that can be set by the external application
/// which will be displayed if a failure or cancellation is returned by
/// the external command.
/// A set of elements to which the external application
/// can add elements that are to be highlighted in case of failure or cancellation.
/// Return the status of the external command.
/// A result of Succeeded means that the API external method functioned as expected.
/// Cancelled can be used to signify that the user cancelled the external operation
/// at some point. Failure should be returned if the application is unable to proceed with
/// the operation.
public Autodesk.Revit.UI.Result Execute(ExternalCommandData revit,
ref string message,
Autodesk.Revit.DB.ElementSet elements)
{
// Initialization
m_app = revit.Application;
m_doc = revit.Application.ActiveUIDocument.Document;
// Find a 3D view to use for the ray tracing operation
Get3DView("{3D}");
Selection selection = revit.Application.ActiveUIDocument.Selection;
List wallsToCheck = new List();
// If wall(s) are selected, process them.
if (selection.GetElementIds().Count > 0)
{
foreach (Autodesk.Revit.DB.ElementId eId in selection.GetElementIds())
{
Autodesk.Revit.DB.Element e = revit.Application.ActiveUIDocument.Document.GetElement(eId);
if (e is Wall)
{
wallsToCheck.Add((Wall)e);
}
}
if (wallsToCheck.Count <= 0)
{
message = "No walls were found in the active document selection";
return Result.Cancelled;
}
}
// Find all walls in the document and process them.
else
{
FilteredElementCollector collector = new FilteredElementCollector(m_doc);
FilteredElementIterator iter = collector.OfClass(typeof(Wall)).GetElementIterator();
while (iter.MoveNext())
{
wallsToCheck.Add((Wall)iter.Current);
}
}
// Execute the check for embedded columns
CheckWallsForEmbeddedColumns(wallsToCheck);
// Process the results, in this case set the active selection to contain all embedded columns
ICollection toSelected = new List();
if (m_allColumnsOnWalls.Count > 0)
{
foreach (ElementId id in m_allColumnsOnWalls)
{
ElementId familyInstanceId = id;
Autodesk.Revit.DB.Element familyInstance = m_doc.GetElement(familyInstanceId);
toSelected.Add(familyInstance.Id);
}
selection.SetElementIds(toSelected);
}
return Result.Succeeded;
}
#endregion
#region Class Implementation
///
/// Check a list of walls for embedded columns.
///
/// The list of walls to check.
private void CheckWallsForEmbeddedColumns(List wallsToCheck)
{
foreach (Wall wall in wallsToCheck)
{
CheckWallForEmbeddedColumns(wall);
}
}
///
/// Checks a single wall for embedded columns.
///
/// The wall to check.
private void CheckWallForEmbeddedColumns(Wall wall)
{
LocationCurve locationCurve = wall.Location as LocationCurve;
Curve wallCurve = locationCurve.Curve;
if (wallCurve is Line)
{
LogWallCurve((Line)wallCurve);
CheckLinearWallForEmbeddedColumns(wall, locationCurve, (Line)wallCurve);
}
else
{
CheckProfiledWallForEmbeddedColumns(wall, locationCurve, wallCurve);
}
}
///
/// Checks a single linear wall for embedded columns.
///
/// The wall to check.
/// The location curve extracted from this wall.
/// The profile of the wall.
private void CheckLinearWallForEmbeddedColumns(Wall wall, LocationCurve locationCurve, Curve wallCurve)
{
double bottomHeight = GetElevationForRay(wall);
FindColumnsOnEitherSideOfWall(wall, locationCurve, wallCurve, 0, bottomHeight, wallCurve.Length);
}
///
/// Finds columns on either side of the given wall.
///
/// The wall.
/// The location curve of the wall.
/// The profile of the wall.
/// The normalized parameter along the wall profile which is being evaluated.
/// The elevation at which the rays are cast.
/// The maximum distance away that columns may be found.
private void FindColumnsOnEitherSideOfWall(Wall wall, LocationCurve locationCurve, Curve wallCurve, double parameter, double elevation, double within)
{
XYZ rayDirection = GetTangentAt(wallCurve, parameter);
XYZ wallLocation = wallCurve.Evaluate(parameter, true);
XYZ wallDelta = GetWallDeltaAt(wall, locationCurve, parameter);
XYZ rayStart = new XYZ(wallLocation.X + wallDelta.X, wallLocation.Y + wallDelta.Y, elevation);
FindColumnsByDirection(rayStart, rayDirection, within, wall);
rayStart = new XYZ(wallLocation.X - wallDelta.X, wallLocation.Y - wallDelta.Y, elevation);
FindColumnsByDirection(rayStart, rayDirection, within, wall);
}
///
/// Finds columns by projecting rays along a given direction.
///
/// The origin of the ray.
/// The direction of the ray.
/// The maximum distance away that columns may be found.
/// The wall that this search is associated with.
private void FindColumnsByDirection(XYZ rayStart, XYZ rayDirection, double within, Wall wall)
{
ReferenceIntersector referenceIntersector = new ReferenceIntersector(m_view3D);
IList intersectedReferences = referenceIntersector.Find(rayStart, rayDirection);
FindColumnsWithin(intersectedReferences, within, wall);
}
///
/// Checks a single curved/profiled wall for embedded columns.
///
/// The wall to check.
/// The location curve extracted from this wall.
/// The profile of the wall.
private void CheckProfiledWallForEmbeddedColumns(Wall wall, LocationCurve locationCurve, Curve wallCurve)
{
double bottomHeight = GetElevationForRay(wall);
// Figure out the increment for the normalized parameter based on how long the wall is.
double parameterIncrement = WallIncrement / wallCurve.Length;
// Find columns within 2' of the start of the ray. Any smaller, and you run the risk of not finding a boundary
// face of the column within the target range.
double findColumnWithin = 2;
// check for columns along every WallIncrement fraction of the wall
for (double parameter = 0; parameter < 1.0; parameter += parameterIncrement)
{
FindColumnsOnEitherSideOfWall(wall, locationCurve, wallCurve, parameter, bottomHeight, findColumnWithin);
}
}
///
/// Obtains the elevation for ray casting evaluation for a given wall.
///
/// The wall.
/// The elevation.
private double GetElevationForRay(Wall wall)
{
Level level = m_doc.GetElement(wall.LevelId) as Level;
// Start at 1 foot above the bottom level
double bottomHeight = level.Elevation + 1.0;
return bottomHeight;
}
///
/// Obtains the offset to the wall at a given location along the wall's profile.
///
/// The wall.
/// The location curve of the wall.
/// The normalized parameter along the location curve of the wall.
/// An XY vector representing the offset from the wall centerline.
private XYZ GetWallDeltaAt(Wall wall, LocationCurve locationCurve, double parameter)
{
XYZ wallNormal = GetNormalToWallAt(wall, locationCurve, parameter);
double wallWidth = wall.Width;
// The LocationCurve is always the wall centerline, regardless of the setting for the wall Location Line.
// So the delta to place the ray just outside the wall extents is always 1/2 the wall width + a little extra.
XYZ wallDelta = new XYZ(wallNormal.X * wallWidth / 2 + WALL_EPSILON, wallNormal.Y * wallWidth / 2 + WALL_EPSILON, 0);
return wallDelta;
}
///
/// Finds column elements which occur within a given set of references within the designated proximity, and stores them to the results.
///
/// The references obtained from FindReferencesByDirection()
/// The maximum proximity.
/// The wall from which these references were found.
private void FindColumnsWithin(IList references, double proximity, Wall wall)
{
foreach (ReferenceWithContext reference in references)
{
// Exclude items too far from the start point.
if (reference.Proximity < proximity)
{
Autodesk.Revit.DB.Element referenceElement = wall.Document.GetElement(reference.GetReference());
if (referenceElement is FamilyInstance)
{
FamilyInstance familyInstance = (FamilyInstance)referenceElement;
ElementId familyInstanceId = familyInstance.Id;
ElementId wallId = wall.Id;
BuiltInCategory categoryValue = referenceElement.Category.BuiltInCategory;
if (categoryValue == BuiltInCategory.OST_Columns || categoryValue == BuiltInCategory.OST_StructuralColumns)
{
// Add the column to the map of wall->columns
if (m_columnsOnWall.ContainsKey(wallId))
{
List columnsOnWall = m_columnsOnWall[wallId];
if (!columnsOnWall.Contains(familyInstanceId))
columnsOnWall.Add(familyInstanceId);
}
else
{
List columnsOnWall = new List();
columnsOnWall.Add(familyInstanceId);
m_columnsOnWall.Add(wallId, columnsOnWall);
}
// Add the column to the complete list of all embedded columns
if (!m_allColumnsOnWalls.Contains(familyInstanceId))
m_allColumnsOnWalls.Add(familyInstanceId);
}
}
}
}
}
///
/// Obtains the tangent of the given curve at the given parameter.
///
/// The curve.
/// The normalized parameter.
/// The normalized tangent vector.
private XYZ GetTangentAt(Curve curve, double parameter)
{
Transform t = curve.ComputeDerivatives(parameter, true);
// BasisX is the tangent vector of the curve.
return t.BasisX.Normalize();
}
///
/// Finds the normal to the wall centerline at the given parameter.
///
/// The wall.
/// The location curve of the wall.
/// The normalized parameter.
/// The normalized normal vector.
private XYZ GetNormalToWallAt(Wall wall, LocationCurve curve, double parameter)
{
Curve wallCurve = curve.Curve;
// There is no normal at a given point for a line. We need to get the normal based on the tangent of the wall location curve.
if (wallCurve is Line)
{
XYZ wallDirection = GetTangentAt(wallCurve, 0);
XYZ wallNormal = new XYZ(wallDirection.Y, wallDirection.X, 0);
return wallNormal;
}
else
{
Transform t = wallCurve.ComputeDerivatives(parameter, true);
// For non-linear curves, BasisY is the normal vector to the curve.
return t.BasisY.Normalize();
}
}
///
/// Dump wall's curve(end points) to log
///
/// Wall curve to be dumped.
private void LogWallCurve(Line wallCurve)
{
Debug.WriteLine("Wall curve is line: ");
Debug.WriteLine("Start point: " + XYZToString(wallCurve.GetEndPoint(0)));
Debug.WriteLine("End point: " + XYZToString(wallCurve.GetEndPoint(1)));
}
///
/// Format XYZ to string
///
///
///
private String XYZToString(XYZ point)
{
return "( " + point.X + ", " + point.Y + ", " + point.Z + ")";
}
///
/// Get a 3D view from active document
///
private void Get3DView(string viewName)
{
FilteredElementCollector collector = new FilteredElementCollector(m_app.ActiveUIDocument.Document);
foreach (Autodesk.Revit.DB.View3D v in collector.OfClass(typeof(View3D)).ToElements())
{
// skip view template here because view templates are invisible in project browsers
if (v != null && !v.IsTemplate && v.Name == viewName)
{
m_view3D = v as Autodesk.Revit.DB.View3D;
break;
}
}
}
#endregion
}
}