// // (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 } }