// // (C) Copyright 2003-2019 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.Linq; using System.Text; using Autodesk.Revit.DB; using Autodesk.Revit.UI; using Autodesk.Revit.UI.Events; namespace Revit.SDK.Samples.DisplacementElementAnimation.CS { /// /// A class that executes an animation of structural model elements using DisplacementElements. /// class DisplacementStructureModelAnimator : IDisposable { /// /// Starts the animation by creating the needed displacement elements, and setting up the events to allow it proceed. /// public void StartAnimation() { // Instantiate or reset animation variables m_displacementElements = new List(); m_currentDisplacementIndex = 0; UIDocument uiDoc = uiApplication.ActiveUIDocument; View view = uiDoc.ActiveView; Document doc = uiDoc.Document; // Get levels in order of elevation FilteredElementCollector collector = new FilteredElementCollector(doc).OfClass(typeof(Level)); List> idGroupsInOrder = new List>(); IEnumerable levels = collector.Cast().OrderBy(lvl => lvl.Elevation); // Create lists of "elements on level" in ascending order: // * Foundation // * Framing (assumes framing consists of beams is placed to butt up to level above) // * Floors // * Walls // * Columns foreach (Level level in levels) { AddInstancesOnLevelToIdGroupList(idGroupsInOrder, level, BuiltInCategory.OST_StructuralFoundation); AddInstancesOnReferenceLevelToIdGroupList(idGroupsInOrder, level, BuiltInCategory.OST_StructuralFraming); AddInstancesOnLevelToIdGroupList(idGroupsInOrder, level, BuiltInCategory.OST_Floors); AddInstancesOnLevelToIdGroupList(idGroupsInOrder, level, BuiltInCategory.OST_Walls); AddInstancesOnLevelToIdGroupList(idGroupsInOrder, level, BuiltInCategory.OST_StructuralColumns); } // Initial setup of displacement elements for animation using (Transaction t = new Transaction(doc, "Start animation")) { t.Start(); foreach (ICollection idGroups in idGroupsInOrder) { BuildDisplacementElementGroup(doc, idGroups, view); } if (m_displacementElements.Count == 0) { t.RollBack(); return; } m_displacementElement = m_displacementElements[0]; UnhideDisplacedElements(); t.Commit(); } displacementParameter = 1.0; if (m_isUsingIdling) { // Register idling for animation frames uiApplication.Idling += IdlingResponse; // Register timer for animation framews m_timer = new System.Timers.Timer(); m_timer.Interval = timerInterval; m_timer.Elapsed += TimerElapsed; m_timer.Start(); } } /// /// Executes the next step on the animation. /// public void AnimateNextStep() { bool groupFinished = false; // Is the current animation group finished? bool allFinished = false; // Are all animation groups finished? if (displacementParameter <= 0) { displacementParameter = 1.0; groupFinished = true; m_currentDisplacementIndex++; if (m_currentDisplacementIndex == m_displacementElements.Count) { allFinished = true; if (m_isUsingIdling) { m_timer.Stop(); uiApplication.Idling -= IdlingResponse; } } } // Execute transaction for next animation using (Transaction t = new Transaction(uiApplication.ActiveUIDocument.Document, groupFinished ? "Next animation group" : "Animation step")) { t.Start(); if (groupFinished) { // Delete displacement element (and children) uiApplication.ActiveUIDocument.Document.Delete(m_displacementElement.Id); // Increment to next group m_displacementElement = allFinished ? null : m_displacementElements[m_currentDisplacementIndex]; UnhideDisplacedElements(); } else { ChangeDisplacementLocationForParent(); ChangeDisplacementLocationsForChildren(); // Decrement displacement parameter displacementParameter -= displacementIncrement; } t.Commit(); } } /// /// Unhides the elements in the next displacement group. /// private void UnhideDisplacedElements() { if (m_displacementElement != null) { View view = m_displacementElement.Document.GetElement(m_displacementElement.OwnerViewId) as View; view.UnhideElements(m_displacementElement.GetDisplacedElementIdsFromAllChildren()); } } /// /// Find all instances matching category and level, and add to the collection of groups of sorted ids. /// /// The collection of groups of ids, sorted. /// The level to match. /// The category to match. private static void AddInstancesOnLevelToIdGroupList(List> idGroupsInOrder, Level level, BuiltInCategory category) { FilteredElementCollector collector = new FilteredElementCollector(level.Document); collector.WherePasses(new ElementLevelFilter(level.Id)); collector.OfCategory(category); collector.WhereElementIsNotElementType(); ICollection idGroup = collector.ToElementIds(); // Only add non-empty groups if (idGroup.Count > 0) idGroupsInOrder.Add(idGroup); } /// /// Find all instances matching category and "Reference Level", and add to the collection of groups of sorted ids. /// /// The collection of groups of ids, sorted. /// The level to match. /// The category to match. private static void AddInstancesOnReferenceLevelToIdGroupList(List> idGroupsInOrder, Level level, BuiltInCategory category) { FilteredElementCollector collector = new FilteredElementCollector(level.Document); collector.OfCategory(category); collector.WhereElementIsNotElementType(); // Use a parameter filter to match the Reference Level parameter FilterRule rule = ParameterFilterRuleFactory.CreateEqualsRule(new ElementId(BuiltInParameter.INSTANCE_REFERENCE_LEVEL_PARAM), level.Id); collector.WherePasses(new ElementParameterFilter(rule)); ICollection idGroup = collector.ToElementIds(); // Only add non-empty groups if (idGroup.Count > 0) idGroupsInOrder.Add(idGroup); } /// /// Builds a group of displacement elements from a collection of element ids, and /// sets the displacement to be uniform in Z but vary in XY based on location. /// /// The document. /// The collection of ids. /// The view. private void BuildDisplacementElementGroup(Document doc, ICollection ids, View view) { // The last element will be in the "parent" displacement element. (At least one element // must remain assigned to the parent, so all child displacements will be relative to the // parent's displacement.) Element lastElement = doc.GetElement(ids.Last()); XYZ parentDisplacedLocation = GetDisplacementXYFor(lastElement, XYZ.Zero); parentDisplacedLocation = MoveToElevation(parentDisplacedLocation, initialHeight); // All elements are added to the parent displacement element. DisplacementElement parent = DisplacementElement.Create(doc, ids, parentDisplacedLocation, view, null); m_displacementElements.Add(parent); int count = ids.Count; List childIds = new List(); List idsList = ids.ToList(); // Add all elements except the last one to child displacement elements for (int index = 0; index < count - 1; index++) { ElementId childId = idsList[index]; Element e = doc.GetElement(childId); XYZ displacedLocation = GetDisplacementXYFor(e, parentDisplacedLocation); // Setup id container for child DisplacementElement creation childIds.Clear(); childIds.Add(childId); DisplacementElement child = DisplacementElement.Create(doc, childIds, displacedLocation, view, parent); } view.HideElements(ids); } /// /// Change the displacement location for the current parent element. /// private void ChangeDisplacementLocationForParent() { // Displacement includes displacement in Z XYZ displacement = GetDisplacementXY(m_displacementElement); displacement = MoveToElevation(displacement, GetHeightDisplacementValue()); m_displacementElement.SetRelativeDisplacement(displacement); } /// /// Change the displacement location for children elements. /// private void ChangeDisplacementLocationsForChildren() { IList subDisplacementElements = m_displacementElement.GetChildren(); foreach (DisplacementElement element in subDisplacementElements) { // Displacement does not include change in Z XYZ displacedLocation = GetDisplacementXY(element); element.SetRelativeDisplacement(displacedLocation); } } /// /// Identifies if the displacement element is a parent or a child. /// /// /// True if the element is a child of another element. private static bool DisplacementElementIsChild(DisplacementElement element) { return element.ParentId != ElementId.InvalidElementId; } /// /// Gets the XY displacement for a given element. /// /// The displacement element. /// The displacement. private XYZ GetDisplacementXY(DisplacementElement element) { // If the element is a child, need to take into account the displacement of the parent. XYZ displacementDueToParent = XYZ.Zero; if (DisplacementElementIsChild(element)) { DisplacementElement parent = element.Document.GetElement(element.ParentId) as DisplacementElement; displacementDueToParent = parent.GetRelativeDisplacement(); } // Assume one element per displacement, use that element to get the displacement needed. ElementId id = element.GetDisplacedElementIds().First(); Element e = element.Document.GetElement(id); return GetDisplacementXYFor(e, displacementDueToParent); } /// /// Gets the XY displacement for a given element relative to a parent displacement. /// /// The element. /// The parent element displacement. /// The XY displacement. private XYZ GetDisplacementXYFor(Element e, XYZ displacementDueToParent) { XYZ displacementDueToParentXY = MoveToElevationZero(displacementDueToParent); XYZ location = GetNominalCenterLocation(e); XYZ delta = (location - modelCenter); XYZ displacedLocation = delta * GetXYDisplacementRatio() - displacementDueToParentXY; return displacedLocation; } /// /// Returns the current XY displacement ratio. /// /// The XY displacement ratio. private double GetXYDisplacementRatio() { if (displacementParameter == 1.0) return initialXYRatio; return initialXYRatio * 1 / Math.Pow(initialHeight - GetHeightDisplacementValue(), 0.75); } /// /// Gets the current displcement in Z. /// /// private double GetHeightDisplacementValue() { return initialHeight * displacementParameter; } /// /// The timer callback for the animation. /// /// /// private void TimerElapsed(object sender, System.Timers.ElapsedEventArgs e) { timerTripped = true; } /// /// The idling callback for the animation. /// /// /// private void IdlingResponse(object senter, IdlingEventArgs e) { // Set to reraise idling immediately (unaffected by user activity) e.SetRaiseWithoutDelay(); if (timerTripped) { timerTripped = false; AnimateNextStep(); } } /// /// Constructs an animator instance. /// /// The application. /// The switch for using Idling event. public DisplacementStructureModelAnimator(UIApplication uiApp, bool isUsingIdling) { uiApplication = uiApp; m_isUsingIdling = isUsingIdling; } /// /// Utility to move a point to the Z=0 projection /// /// The point. /// The point at Z=0 projection. private static XYZ MoveToElevationZero(XYZ location) { return MoveToElevation(location, 0); } /// /// Utility to move a point to an arbitrary Z projection /// /// The point. /// The elevation to set for the point. /// The point at Z=0 projection. private static XYZ MoveToElevation(XYZ location, double z) { return new XYZ(location.X, location.Y, z); } /// /// Utility to get the "center" of the element based on location parameters, projected to Z=0. /// /// The element. /// The center location. private static XYZ GetNominalCenterLocation(Element e) { LocationPoint lp = e.Location as LocationPoint; if (lp != null) { return MoveToElevationZero(lp.Point); } LocationCurve lc = e.Location as LocationCurve; if (lc != null) { return MoveToElevationZero(lc.Curve.Evaluate(0.5, true)); } return XYZ.Zero; } /// /// The application. /// private UIApplication uiApplication; /// /// The collection of top level displacement elements. /// private List m_displacementElements; /// /// The current parent displacement element being animated. /// private DisplacementElement m_displacementElement = null; /// /// The index of the current parent element being animated. /// private int m_currentDisplacementIndex = 0; /// /// The displacement parameter (proceeds from 1 -> 0 during the animation) /// private double displacementParameter = 1.0; /// /// The increment by which the displacement parameter is reduced during animation. /// private double displacementIncrement = 0.05; /// /// The maximum ratio of displacement in XY. /// private double initialXYRatio = 1.25; /// /// The initial height for the initial displacement. /// private double initialHeight = 100; /// /// Signals that the timer has triggered. /// private bool timerTripped = false; /// /// The number of milliseconds in between frames. /// private int timerInterval = 60; /// /// The timer that governs the automation. /// private System.Timers.Timer m_timer = null; /// /// The model center. Currently hardcoded. /// private XYZ modelCenter = XYZ.Zero; /// /// The switch for using Idling event. /// private bool m_isUsingIdling = true; protected virtual void Dispose(bool disposing) { if (disposing) { m_timer?.Dispose(); ((IDisposable)m_displacementElement)?.Dispose(); } } public void Dispose() { Dispose(disposing: true); GC.SuppressFinalize(this); } } }