应用程序:MultithreadedCalculation

Revit平台:所有

Revit版本:2012.0

首次发布:2012.0

编程语言:C#

技能水平:高级

类别:几何

类型:ExternalCommand

主题:分析可视化框架

摘要:通过分析可视化框架、多线程、Revit的空闲事件和动态模型更新的组合,模拟分析结果在计算过程中如何周期性地更新,并在Revit模型发生更改时重新启动。

类:

Autodesk.Revit.DB.Analysis.FieldDomainPointsByUV

Autodesk.Revit.DB.Analysis.FieldValues

Autodesk.Revit.DB.Analysis.SpatialFieldManager

Autodesk.Revit.DB.UpdaterRegistry

System.Threading.Thread

项目文件:

MultithreadedCalculation.cs

描述:

1.提示用户选择一个元素,使用Selection.PickObject

2.找到元素的最大面。

3.获取活动视图的SpatialFieldManager,如果活动视图没有,则创建一个SpatialFieldManager

4.将元素最大面的引用添加到SpatialFieldManager中。

5.查找面的边界框的最小和最大UV

使用myUV类是因为它可以安全地传递到外部线程。myUV包含两个double值。本机的Revit类(如Autodesk.Revit.DB.UV)无法传递到线程。

6.创建并注册IUpdaterSpatialFieldUpdater),以便在更改或删除步骤1中选择的元素时,命令能够响应。

7.注册空闲事件。Revit将使用此事件来通知命令Revit已准备好接收和显示其他结果数据。

8.启动一个新线程,该线程将用于计算和显示结果

a.填充描述将计算结果的表面位置的myUV元素列表

b.计算结果

i.将结果存储通过向“结果”IList中添加resultData类的实例来存储。每个resultData实例包含一个myUVdouble值。

ii.IList正在被修改时,将锁定结果列表,以防止多个线程同时修改它。

iii.为了简化此示例中的问题,使用了DateTime.Now属性的当前秒。

iv.Thread.Sleep用于创建1/2秒的暂停,模拟执行复杂分析所需的时间,并使此示例的各种功能更加显着。

说明:

1.打开MultithreadedCalculation.rvt。

2.运行外部命令。

3.选择示例文件中的体量、楼板或墙体。

4.Revit将向所选元素的最大面添加结果数据。

5.随时(在计算运行期间或计算完成后),修改元素的几何形状。之前的结果数据将被删除,并计算新的结果数据。

源代码

完整的源代码请加入QQ群649037449,在群文件中下载RevitSDK.exe,解压后在文件夹中搜索本文中应用程序名称即可获得完整源码

MultithreadedCalculation.cs

//
// (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.Threading;
using System.Collections.Generic;
using System.Collections;
using System.Linq;
using Autodesk.Revit.ApplicationServices;
using Autodesk.Revit.DB;
using Autodesk.Revit.DB.Analysis;
using Autodesk.Revit.UI;
using Autodesk.Revit.UI.Events;
using Autodesk.Revit.UI.Selection;
using System.Diagnostics;

namespace Revit.SDK.Samples.MultithreadedCalculation.CS
{
    /// <summary>
    /// Command to set the target element and begin the multithreaded calculation.
    /// </summary>
    [Autodesk.Revit.Attributes.Transaction(Autodesk.Revit.Attributes.TransactionMode.Manual)]
    [Autodesk.Revit.Attributes.Regeneration(Autodesk.Revit.Attributes.RegenerationOption.Manual)]
    public class MultithreadedCalculation : IExternalCommand
    {
        static UpdaterId s_updaterId;
        static int s_spatialFieldId;
        static int s_oldSpatialFieldId;
        static string s_docName;

        static ElementId s_oldViewId;
        static ElementId s_activeViewId;

        public Autodesk.Revit.UI.Result Execute(ExternalCommandData commandData, ref string message, ElementSet elements)
        {
            UIApplication uiApp = commandData.Application;
            UIDocument uiDoc = uiApp.ActiveUIDocument;
            Document doc = uiDoc.Document;
            s_docName = doc.PathName;

            Element element = null;
            try
            {
                element = doc.GetElement(uiDoc.Selection.PickObject(ObjectType.Element, "Select an element for the AVF demonstration."));
            }
            catch (System.Exception)
            {
                message = "User aborted the tool.";
                return Result.Cancelled;
            }
            
            // Set up SpatialFieldManager to hold results
            s_activeViewId = doc.ActiveView.Id; 
            SpatialFieldManager oldSfm = null; 
            View oldView = null;
            if (s_oldViewId != null) oldView = doc.GetElement(s_oldViewId) as View;
            if (oldView != null) oldSfm = SpatialFieldManager.GetSpatialFieldManager(oldView);
            // If a previous SFM was being managed, delete it
            if (oldSfm != null) oldSfm.RemoveSpatialFieldPrimitive(s_oldSpatialFieldId);

            // Setup container object for executing the calculation
            MultithreadedCalculationContainer container = CreateContainer(element);

            // Register updater to watch for geometry changes
            SpatialFieldUpdater updater = new SpatialFieldUpdater(container,uiApp.ActiveAddInId);
            if (!UpdaterRegistry.IsUpdaterRegistered(updater.GetUpdaterId())) 
                UpdaterRegistry.RegisterUpdater(updater, doc);
            IList<ElementId> idCollection = new List<ElementId>();
            idCollection.Add(element.Id);
            UpdaterRegistry.RemoveAllTriggers(s_updaterId);
            UpdaterRegistry.AddTrigger(updater.GetUpdaterId(), doc, idCollection, Element.GetChangeTypeGeometry());

            // Register idling event
            uiApp.Idling += new EventHandler<IdlingEventArgs>(container.UpdateWhileIdling);

            // Start new thread
            Thread thread = new Thread(new ThreadStart(container.Run));
            thread.Start();

            return Autodesk.Revit.UI.Result.Succeeded;
        }

        /// <summary>
        /// Prepares a container object that carries out the calculations without invoking Revit API calls.
        /// </summary>
        /// <param name="element">The element for the calculations.</param>
        /// <returns>The container.</returns>
        public static MultithreadedCalculationContainer CreateContainer(Element element)
        {
            Document doc = element.Document;
            View activeView = doc.GetElement(s_activeViewId) as View;

            // Figure out which is the largest face facing the user
            XYZ viewDirection = activeView.ViewDirection.Normalize();   
            Face biggestFace = GetBiggestFaceFacingUser(element, viewDirection);
                 
            // Get or create SpatialFieldManager for AVF results
            SpatialFieldManager sfm = SpatialFieldManager.GetSpatialFieldManager(activeView);
            if (sfm == null) sfm = SpatialFieldManager.CreateSpatialFieldManager(activeView, 1);

            // Reference the target face
            s_spatialFieldId = sfm.AddSpatialFieldPrimitive(biggestFace.Reference);

            // Compute the range of U and V for the calculation
            BoundingBoxUV bbox = biggestFace.GetBoundingBox();

            return new MultithreadedCalculationContainer(doc.PathName, bbox.Min, bbox.Max);
       }

        /// <summary>
        /// Gets the biggest face which faces the user.  Assumes that the element is a wall, or floor, or other "2-sided" element, and that
        /// one of the two biggest faces will be facing roughly towards the viewer.
        /// </summary>
        /// <param name="element">The element.</param>
        /// <param name="viewDirection">The view direction.</param>
        /// <returns>The face.  Face.Reference will also be populated.</returns>
        private static Face GetBiggestFaceFacingUser(Element element, XYZ viewDirection)
        {
            
            // Holds the faces sorted by area
            SortedDictionary<double, List<Face>> faceAreas = new SortedDictionary<double, List<Face>>();

            // Get the element geometry
            Options options = new Options();
            options.ComputeReferences = true;
            GeometryElement geomElem = element.get_Geometry(options);
            
            // Look at the faces in each solid
            foreach (GeometryObject geomObj in geomElem)
            {
                Solid solid = geomObj as Solid;
                if (solid != null)
                {
                    foreach (Face face in solid.Faces)
                    {
                        double area = face.Area;
                        // Save the face to the collection
                        if (faceAreas.ContainsKey(area))
                        {
                            faceAreas[area].Add(face);
                        }
                        else
                        {
                            List<Face> faces = new List<Face>();
                            faces.Add(face);
                            faceAreas.Add(area, faces);
                        }
                    }
                }
            }

            // Get biggest two faces.  There might be two faces in the last item, or one face in the last item.
            int count = faceAreas.Count;
            KeyValuePair<double, List<Face>> faceCollection1 = faceAreas.ElementAt<KeyValuePair<double, List<Face>>>(count - 1);
            KeyValuePair<double, List<Face>> faceCollection2 = faceAreas.ElementAt<KeyValuePair<double, List<Face>>>(count - 2);

            Face face1 = null;
            Face face2 = null;
            // Two or more equal faces.  Use the first two.
            if (faceCollection1.Value.Count > 1)
            {
                face1 = faceCollection1.Value[0];
                face2 = faceCollection1.Value[1];
            }
            // One largest face.  Use the first face from the next item for comparison.
            else
            {
                face1 = faceCollection1.Value[0];
                face2 = faceCollection2.Value[0];
            }

            // Compute face normal
            BoundingBoxUV box = face1.GetBoundingBox();
            UV faceCenter = (box.Max + box.Min) / 2;
            XYZ faceNormal = face1.ComputeNormal(faceCenter).Normalize();

            // Compute angle to the view direction.  If less than 90 degrees, keep this face.
            double angle = viewDirection.AngleTo(faceNormal);

            Face biggestFace = null;
            if (Math.Abs(angle) < Math.PI / 2)
                biggestFace = face1;
            else
                biggestFace = face2;

            return biggestFace;
        }

        /// <summary>
        /// Updater called when wall geometry changes, so analysis results can update.
        /// </summary>
        public class SpatialFieldUpdater : IUpdater
        {
            // The old container object.
            MultithreadedCalculationContainer containerOld;

            public SpatialFieldUpdater(MultithreadedCalculationContainer _container, AddInId addinId)
            {
                containerOld = _container;
                s_updaterId = new UpdaterId(addinId, new Guid("FBF2F6B2-4C06-42d4-97C1-D1B4EB593EFF"));
            }

            // Execution method for the updater
            public void Execute(UpdaterData data)
            {
                // Remove old idling event callback
                UIApplication uiApp = new UIApplication(data.GetDocument().Application);
                uiApp.Idling -= containerOld.UpdateWhileIdling;
                containerOld.Stop();

                // Clear the current AVF results
                Document doc = data.GetDocument();
                View activeView = doc.GetElement(s_activeViewId) as View;
                SpatialFieldManager sfm = SpatialFieldManager.GetSpatialFieldManager(activeView);
                sfm.Clear();

                // Restart the multithread calculation with a new container
                Element modifiedElem = doc.GetElement(data.GetModifiedElementIds().First<ElementId>());
                MultithreadedCalculationContainer container = MultithreadedCalculation.CreateContainer(modifiedElem);
                containerOld = container;

                // Setup the new idling callback
                uiApp.Idling += new EventHandler<IdlingEventArgs>(container.UpdateWhileIdling);

                // Start the thread
                Thread threadNew = new Thread(new ThreadStart(container.Run));
                threadNew.Start();
            }

            public string GetAdditionalInformation() { return "AVF DMU Thread sample"; }
            public ChangePriority GetChangePriority() { return ChangePriority.FloorsRoofsStructuralWalls; }
            public UpdaterId GetUpdaterId() { return s_updaterId; }
            public string GetUpdaterName() { return "AVF DMU Thread"; }
        }

        /// <summary>
        /// Container class that manages the multithreaded calculation and idling activity.
        /// </summary>
        public class MultithreadedCalculationContainer
        {
            private volatile bool m_stop = false;
            UV m_min;
            UV m_max;
            
            string m_docName;
            IList<ResultsData> results = new List<ResultsData>();
            IList<UV> m_uvToCalculate = new List<UV>();
            int m_uvToCalculateCount;
            IList<UV> uvPts = new List<UV>();
            IList<ValueAtPoint> valList = new List<ValueAtPoint>();

            public MultithreadedCalculationContainer(string _docName, UV _min, UV _max)
            {
                m_docName = _docName;
                m_min = _min;
                m_max = _max;
            }

            public void Run()
            {
                m_uvToCalculate = DetermineFacePoints();
                m_uvToCalculateCount = m_uvToCalculate.Count;
                Calculate();
            }

            /// <summary>
            /// Stops the thread/calculation and application via idling.
            /// </summary>
            public void Stop()
            {
                m_stop = true;
            }


            /// <summary>
            /// The idling callback which adds data to the AVF results.
            /// </summary>
            /// <param name="sender"></param>
            /// <param name="e"></param>
            public void UpdateWhileIdling(object sender, IdlingEventArgs e)
            {
                UIApplication uiApp = sender as UIApplication;

                // Get SpatialFieldManager

                AnalysisResultSchema resultSchema = new AnalysisResultSchema("Schema Name", "Description");
                SpatialFieldManager sfm = SpatialFieldManager.GetSpatialFieldManager(uiApp.ActiveUIDocument.Document.ActiveView);
                
                if (sfm == null) sfm = SpatialFieldManager.CreateSpatialFieldManager(uiApp.ActiveUIDocument.Document.ActiveView, 1);
                int schemaIndex = sfm.RegisterResult(resultSchema);
                // If stopping, clear results and unset event.
                if (m_stop)
                {
                    lock (results)
                    {
                        results.Clear();
                    }
                    uiApp.Idling -= UpdateWhileIdling;
                    return;
                }

                // If document was closed and new document opened, do not run the update.
                if (uiApp.ActiveUIDocument.Document.PathName == m_docName) 
                {
                    // Lock access to current calculated results
                    lock (results)
                    {
                        if (results.Count == 0) return;

                        // Turn each result to an AVF ValueAtPoint
                        foreach (ResultsData rData in results)
                        {
                            uvPts.Add(new UV(rData.UV.U, rData.UV.V));
                            IList<double> doubleList = new List<double>();
                            doubleList.Add(rData.Value);
                            valList.Add(new ValueAtPoint(doubleList));
                        }
                        FieldDomainPointsByUV pntsByUV = new FieldDomainPointsByUV(uvPts);
                        FieldValues fieldValues = new FieldValues(valList);

                        // Update with calculated values
                        Transaction t = new Transaction(uiApp.ActiveUIDocument.Document);
                        t.SetName("AVF");
                        t.Start();
                        if (!m_stop)
                            sfm.UpdateSpatialFieldPrimitive(s_spatialFieldId, pntsByUV, fieldValues, schemaIndex);
                        t.Commit();  

                        // Clear results already processed.
                        results.Clear();

                        // If no more results to process, remove the idling event
                        if (m_uvToCalculateCount == 0)
                        {
                            uiApp.Idling -= UpdateWhileIdling;
                            s_oldViewId = s_activeViewId;
                            s_oldSpatialFieldId = s_spatialFieldId;
                        }
                    }
                }
            }

            // Calculate the results in a loop 
            void Calculate()
            {
                foreach (UV uv in m_uvToCalculate)
                {
                    if (m_stop)
                    {
                        m_uvToCalculateCount = 0;
                        return;
                    }
                    // Lock access to results while the data is added
                    lock (results)
                    {
                        results.Add(new ResultsData(uv, 1000 * Math.Sin(Math.Abs(uv.U * uv.V))));
                        Thread.Sleep(500); // to simulate the effect of a complex computation
                        m_uvToCalculateCount--;
                    }
                }
            }

            private const int numberOfUPnts = 10;
            private const int numberOfVPnts = 5;

            // Setup the list of UV points to calculate results for
            IList<UV> DetermineFacePoints()
            {
                IList<UV> uvList = new List<UV>();
                double upnt = m_min.U;
                double incrementU = (m_max.U - m_min.U) / (numberOfUPnts - 1);
                double incrementV = (m_max.V - m_min.V) / (numberOfVPnts - 1);
                while (upnt <= m_max.U)
                {
                    double vpnt = m_min.V;
                    while (vpnt <= m_max.V)
                    {
                        uvList.Add(new UV(upnt,vpnt));
                        vpnt = vpnt + incrementV;
                    }
                    upnt = upnt + incrementU;
                }
                return uvList;
            }
        }

        // Represents a set of results for the calculation
        public class ResultsData
        {
            public UV UV;
            public double Value;
            public ResultsData(UV uv, double value)
            {
                this.UV = uv;
                Value = value;
            }
        }
    }
}