//
// (C) Copyright 2003-2020 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.Windows.Forms;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using Autodesk.Revit;
using Autodesk.Revit.DB;
using Autodesk.Revit.UI;
using Autodesk.Revit.UI.Selection;
using Autodesk.Revit.DB.Architecture;
using Autodesk.Revit.DB.Analysis;
using TaskDialog = Autodesk.Revit.UI.TaskDialog;
namespace Revit.SDK.Samples.PathOfTravelCreation.CS
{
///
/// The options for creating the PathOfTravel.
///
public enum PathCreateOptions
{
///
/// Create from a single room's corners to single door
///
SingleRoomCornersToSingleDoor,
///
/// Create from all room's centerpoints to all doors
///
AllRoomCenterToSingleDoor,
///
/// Create from all room's corners to all doors
///
AllRoomCornersToAllDoors,
}
///
/// Implements the Revit add-in interface IExternalCommand
///
[Autodesk.Revit.Attributes.Transaction(Autodesk.Revit.Attributes.TransactionMode.Manual)]
[Autodesk.Revit.Attributes.Regeneration(Autodesk.Revit.Attributes.RegenerationOption.Manual)]
[Autodesk.Revit.Attributes.Journaling(Autodesk.Revit.Attributes.JournalingMode.NoCommandData)]
public class Command : IExternalCommand
{
#region Class Interface Implementation
///
/// Implement this method as an external command for Revit.
///
/// 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 virtual Result Execute(ExternalCommandData commandData
, ref string message, ElementSet elements)
{
try
{
UIDocument uiDoc = commandData.Application.ActiveUIDocument;
ViewPlan viewPlan = uiDoc.ActiveView as ViewPlan;
if (null == viewPlan)
{
TaskDialog td = new TaskDialog("Cannot create PathOfTravel.");
td.MainInstruction = String.Format("PathOfTravel can only be created for plan views.");
td.Show();
return Result.Succeeded;
}
using (CreateForm createForm = new CreateForm())
{
if (DialogResult.OK == createForm.ShowDialog())
{
if (createForm.PathCreateOption == PathCreateOptions.SingleRoomCornersToSingleDoor)
{
CreatePathsOfTravelInOneRoomMultiplePointsToOneDoor(uiDoc);
}
else if (createForm.PathCreateOption == PathCreateOptions.AllRoomCenterToSingleDoor)
{
CreatePathsOfTravelRoomCenterpointsToSingleDoor(uiDoc);
}
else
{
CreatePathsOfTravelInAllRoomsAllDoorsMultiplePointsManyToMany(uiDoc);
}
}
}
return Result.Succeeded;
}
catch (Exception ex)
{
message = ex.Message;
return Result.Failed;
}
}
#endregion
#region MainMethods
///
/// Generates paths of travel for near-corner points in one room to a single selected door.
///
private void CreatePathsOfTravelInOneRoomMultiplePointsToOneDoor(UIDocument uiDoc)
{
Document doc = uiDoc.Document;
ViewPlan viewPlan = uiDoc.ActiveView as ViewPlan;
ElementId levelId = viewPlan.GenLevel.Id;
// select room
Reference reference = uiDoc.Selection.PickObject(ObjectType.Element, new RoomSelectionFilter(), "Select a room");
Room room = doc.GetElement(reference) as Room;
// select exit door
Reference roomReference = uiDoc.Selection.PickObject(ObjectType.Element, new DoorSelectionFilter(), "Select a target door");
Instance doorElement = doc.GetElement(roomReference) as Instance;
Transform trf = doorElement.GetTransform();
XYZ endPoint = trf.Origin;
ResultsSummary resultsSummary = new ResultsSummary();
resultsSummary.numDoors = 1;
Stopwatch stopwatch = Stopwatch.StartNew();
GeneratePathsOfTravelForOneRoomOneDoor(doc, viewPlan, room, endPoint, resultsSummary);
stopwatch.Stop();
resultsSummary.elapsedMilliseconds = stopwatch.ElapsedMilliseconds;
ShowResults(resultsSummary);
}
///
/// Generates paths of travel from the center points of the room to a single door using the many to many approach. Does not collect and display results.
///
private void CreatePathsOfTravelRoomCenterpointsToSingleDoor(UIDocument uiDoc)
{
Document doc = uiDoc.Document;
ViewPlan viewPlan = uiDoc.ActiveView as ViewPlan;
ElementId levelId = viewPlan.GenLevel.Id;
// select exit door
Reference reference = uiDoc.Selection.PickObject(ObjectType.Element, new DoorSelectionFilter(), "Select a target door");
Instance doorElement = doc.GetElement(reference) as Instance;
Transform trf = doorElement.GetTransform();
XYZ endPoint = trf.Origin;
// find all rooms
FilteredElementCollector fec = new FilteredElementCollector(doc);
fec.WherePasses(new Autodesk.Revit.DB.Architecture.RoomFilter());
List startPoints = new List();
foreach (Room room in fec.Cast().Where(rm => rm.Level.Id == levelId))
{
LocationPoint location = room.Location as LocationPoint;
if (location == null)
continue;
XYZ roomPoint = location.Point;
startPoints.Add(roomPoint);
}
// generate paths
using (Transaction t = new Transaction(doc, "Generate paths of travel"))
{
t.Start();
IList statuses;
PathOfTravel.CreateMapped(viewPlan, startPoints, new List { endPoint }, out statuses);
t.Commit();
}
}
///
/// Creates paths of travel using all rooms on the given floor plan, starting from the near-corner points of those rooms, to all doors in the same floor plan.
/// This version uses Revit's many-to-many API with automatic mapping between start and endpoints.
///
private void CreatePathsOfTravelInAllRoomsAllDoorsMultiplePointsManyToMany(UIDocument uiDoc)
{
Document doc = uiDoc.Document;
ViewPlan viewPlan = uiDoc.ActiveView as ViewPlan;
CreatePathsOfTravelInAllRoomsAllDoorsMultiplePointsManyToMany(doc, viewPlan, false);
}
#endregion
#region RoomUtils
///
/// A selection filter that accepts selection of door elements only.
///
class DoorSelectionFilter : ISelectionFilter
{
public bool AllowElement(Element element)
{
if (element.Category.Id == new ElementId(BuiltInCategory.OST_Doors))
{
return true;
}
return false;
}
public bool AllowReference(Reference refer, XYZ point)
{
return false;
}
}
///
/// A selection filter that accepts selection of room elements only.
///
class RoomSelectionFilter : ISelectionFilter
{
public bool AllowElement(Element element)
{
if (element is Room)
{
return true;
}
return false;
}
public bool AllowReference(Reference refer, XYZ point)
{
return false;
}
}
///
/// Appends a list of the room's near-corner points to a pre-existing list.
///
///
/// A near-corner point is offset from the room boundaries by 1.5 ft (18 inches). The points are calculated geometrically and some situations may not return
/// all logical near-corner points, or may return points which are inside furniture, casework or other design elements. Only the first boundary region of the room is
/// currently processed.
///
///
///
///
private static void AppendRoomNearCornerPoints(Room room, List nearCornerPoints)
{
IList> segments = room.GetBoundarySegments(new SpatialElementBoundaryOptions());
if (segments == null || segments.Count == 0)
return;
// First region only
IList firstSegments = segments[0];
int numSegments = firstSegments.Count;
for (int i = 0; i < numSegments; i++)
{
BoundarySegment seg1 = firstSegments.ElementAt(i);
BoundarySegment seg2 = firstSegments.ElementAt(i == numSegments - 1 ? 0 : i + 1);
Curve curve1 = seg1.GetCurve();
Curve curve2 = seg2.GetCurve();
Curve offsetCurve1 = curve1.CreateOffset(-1.5, XYZ.BasisZ);
Curve offsetCurve2 = curve2.CreateOffset(-1.5, XYZ.BasisZ);
IntersectionResultArray intersections = null;
SetComparisonResult result = offsetCurve1.Intersect(offsetCurve2, out intersections);
// First intersection only
if (result == SetComparisonResult.Overlap && intersections.Size == 1)
{
nearCornerPoints.Add(intersections.get_Item(0).XYZPoint);
}
}
}
///
/// Returns a list of the room's near-corner points.
///
///
/// A near-corner point is offset from the room boundaries by 1.5 ft (18 inches). The points are calculated geometrically and some situations may not return
/// all logical near-corner points, or may return points which are inside furniture, casework or other design elements. Only the first boundary region of the room is
/// currently processed.
///
///
///
private static List GetRoomNearCornerPoints(Room room)
{
List nearCornerPoints = new List();
AppendRoomNearCornerPoints(room, nearCornerPoints);
return nearCornerPoints;
}
#endregion
#region PathOfTravelCreationUtils
///
/// Shared implementation for use of Path of Travel bulk creation routine from all near-corner room points to all doors.
///
///
///
///
private static void CreatePathsOfTravelInAllRoomsAllDoorsMultiplePointsManyToMany(Document doc, ViewPlan viewPlan, bool mapAllStartsToAllEnds)
{
ElementId levelId = viewPlan.GenLevel.Id;
// find rooms on level
FilteredElementCollector fec = new FilteredElementCollector(doc, viewPlan.Id);
fec.WherePasses(new Autodesk.Revit.DB.Architecture.RoomFilter());
// find doors on level
FilteredElementCollector fec2 = new FilteredElementCollector(doc, viewPlan.Id);
fec2.OfCategory(BuiltInCategory.OST_Doors);
// setup results
ResultsSummary resultsSummary = new ResultsSummary();
List endPoints = new List();
// Collect rooms
List rooms = fec.Cast().ToList();
// Loop on doors and collect target points (the door's origin)
foreach (Element element in fec2)
{
Instance doorElement = (Instance)element;
Transform trf = doorElement.GetTransform();
endPoints.Add(trf.Origin);
}
resultsSummary.numDoors = endPoints.Count;
using (TransactionGroup group = new TransactionGroup(doc, "Generate all paths of travel"))
{
group.Start();
GeneratePathsOfTravelForRoomsToEndpointsManyToMany(doc, viewPlan, rooms, endPoints, resultsSummary, mapAllStartsToAllEnds);
group.Assimilate();
}
ShowResults(resultsSummary);
}
///
/// Generates path of travels from room corner points to the corresponding list of end points.
///
///
///
///
///
///
///
private static void GeneratePathsOfTravelForRoomsToEndpointsManyToMany(Document doc, ViewPlan viewPlan, List rooms, List endPoints, ResultsSummary resultsSummary,
bool mapAllStartsToAllEnds)
{
List allSourcePoints = new List();
foreach (Room room in rooms)
{
AppendRoomNearCornerPoints(room, allSourcePoints);
}
// foreach (Room room in rooms)
// {
// LocationPoint location = room.Location as LocationPoint;
// if (location == null)
// continue;
// XYZ roomPoint = location.Point;
// allSourcePoints.Add(roomPoint);
// }
resultsSummary.numSourcePoints += allSourcePoints.Count;
List inputStartPoints = null;
List inputEndPoints = null;
// generate full lists of start and end points mapped to one another.
// This is for testing purposes, the API option to do this mapping is likely more efficient for this case.
if (mapAllStartsToAllEnds)
{
List allSourcePointsMappedToEnds = new List();
List allEndPointsMappedToEnds = new List();
foreach (XYZ source in allSourcePoints)
{
foreach (XYZ end in endPoints)
{
allSourcePointsMappedToEnds.Add(source);
allEndPointsMappedToEnds.Add(end);
}
}
inputStartPoints = allSourcePointsMappedToEnds;
inputEndPoints = allEndPointsMappedToEnds;
}
else
{
inputStartPoints = allSourcePoints;
inputEndPoints = endPoints;
}
GeneratePathsOfTravel(doc, viewPlan, inputStartPoints, inputEndPoints, resultsSummary, !mapAllStartsToAllEnds);
}
///
/// Wraps all calls to PathOfTravel.Create() with multiple start/ends.
///
///
///
///
///
///
///
private static void GeneratePathsOfTravel(Document doc, ViewPlan viewPlan, List startPoints, List endPoints, ResultsSummary resultsSummary, bool mapAllStartsToAllEnds)
{
// Performance monitoring
Stopwatch stopwatch = Stopwatch.StartNew();
using (Transaction t = new Transaction(doc, "Generate paths of travel"))
{
t.Start();
IList statuses;
IList pathsOfTravel;
if (mapAllStartsToAllEnds)
pathsOfTravel = PathOfTravel.CreateMapped(viewPlan, startPoints, endPoints, out statuses);
else
pathsOfTravel = PathOfTravel.CreateMultiple(viewPlan, startPoints, endPoints, out statuses);
int i = 0;
foreach (PathOfTravel pathOfTravel in pathsOfTravel)
{
if (pathOfTravel == null)
{
resultsSummary.numFailures++;
resultsSummary.failuresFound.Add(statuses[i]);
}
else resultsSummary.numSuccesses++;
i++;
}
t.Commit();
}
stopwatch.Stop();
resultsSummary.elapsedMilliseconds = stopwatch.ElapsedMilliseconds;
}
///
/// Generates paths of travel from points in one room to many target locations using the slower (one-at-a-time) method.
///
///
///
///
///
///
private static void GeneratePathsOfTravelForOneRoomManyDoors(Document doc, ViewPlan viewPlan, Room room, List endPoints, ResultsSummary resultsSummary)
{
List sourcePoints = GetRoomNearCornerPoints(room);
resultsSummary.numSourcePoints += sourcePoints.Count;
// generate paths
using (Transaction t = new Transaction(doc, "Generate paths of travel"))
{
t.Start();
IList statuses;
IList pathsOfTravel = PathOfTravel.CreateMapped(viewPlan, sourcePoints, endPoints, out statuses);
foreach (PathOfTravel pOT in pathsOfTravel)
{
if (pOT == null) resultsSummary.numFailures++;
else resultsSummary.numSuccesses++;
}
t.Commit();
}
}
///
/// Generates paths of travel from points in one room to a single target location using the slower (one-at-a-time) method.
///
///
///
///
///
///
private static void GeneratePathsOfTravelForOneRoomOneDoor(Document doc, ViewPlan viewPlan, Room room, XYZ endPoint, ResultsSummary resultsSummary)
{
GeneratePathsOfTravelForOneRoomManyDoors(doc, viewPlan, room, new List { endPoint }, resultsSummary);
}
#endregion
#region ResultsUtils
///
/// Class that aggregates the results of the path of travel creation for later display and/or logging.
///
class ResultsSummary
{
public int numSourcePoints { get; set; }
public int numDoors { get; set; }
public int numSuccesses { get; set; }
public int numFailures { get; set; }
public long elapsedMilliseconds { get; set; }
public List failuresFound { get; set; }
public ResultsSummary()
{
failuresFound = new List();
}
}
///
/// Displays the results from a run of path of travel creation using a TaskDialog.
///
///
private static void ShowResults(ResultsSummary resultsSummary)
{
CultureInfo ci = new CultureInfo("en-us");
int numOfPathsToCreate = resultsSummary.numSourcePoints * resultsSummary.numDoors;
double successRatePercent = (double)(resultsSummary.numSuccesses) / (double)(numOfPathsToCreate);
TaskDialog td = new TaskDialog("Results of PathOfTravel creation");
td.MainInstruction = String.Format("Path of Travel succeeded on {0} of known points", successRatePercent.ToString("P01", ci));
String details = String.Format("There were {0} room source points found in room analysis (via offsetting boundaries). " +
"They would be connected to {2} door target points. {1} failed to generate a Path of Travel out of {4} " +
"Processing took {3} milliseconds.",
resultsSummary.numSourcePoints, resultsSummary.numFailures, resultsSummary.numDoors, resultsSummary.elapsedMilliseconds, numOfPathsToCreate);
if (resultsSummary.numFailures > 0)
{
details += " Most likely reason for failures is an obstacle on or nearby to the source point.";
}
td.MainContent = details;
td.Show();
}
#endregion
}
}