Revitアドインで設備機器を部屋に自動的に配置するコード #002

#region Namespaces
using Autodesk.Revit.ApplicationServices;
using Autodesk.Revit.Attributes;
using Autodesk.Revit.DB;
using Autodesk.Revit.DB.Mechanical;
using Autodesk.Revit.UI;
using Autodesk.Revit.UI.Selection;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Windows.Forms;

#endregion

//当ソフトにはEPPlus4.5.3.3を使用しています。これはLGPLライセンスです。著作権はEPPlus Software社です。

namespace CopyAndPasteElement
{
    [Transaction(TransactionMode.Manual)]
    public class Command : IExternalCommand
    {
        public Result Execute(
          ExternalCommandData commandData,
          ref string message,
          ElementSet elements)
        {
            UIApplication uiapp = commandData.Application;
            UIDocument uidoc = uiapp.ActiveUIDocument;
            Autodesk.Revit.ApplicationServices.Application app = uiapp.Application;
            Document doc = uidoc.Document;

            //コピーする器具を選ぶ--------------------------------------
            Reference sel = uidoc.Selection.PickObject(ObjectType.Element,"コピーする器具を選んでください");
            if (sel == null)
            {
                TaskDialog.Show("エラー", "コピーする要素が正しく選ばれていません");
                return Result.Cancelled;
            }
            Element e = doc.GetElement(sel);
            ElementId elementId = e.Id;

            //現在のエレメントの位置
            LocationPoint Lp = e.Location as LocationPoint;
            XYZ ElementPoint = Lp.Point as XYZ;

            //器具サイズ
            double equipmentSizeX = Math.Abs(e.get_BoundingBox(doc.ActiveView).Max.X -
                e.get_BoundingBox(doc.ActiveView).Min.X);
            double equipmentSizeY = Math.Abs(e.get_BoundingBox(doc.ActiveView).Max.Y -
                e.get_BoundingBox(doc.ActiveView).Min.Y);

            //配置方法についてFormでユーザー入力---縦と横の台数を聞く------------------
            //横方向の台数、初期化
            int wNumber = 0;
            //縦方向の台数、初期化
            int dNumber = 0;

            using (Form1 thisForm = new Form1())
            {
                if (thisForm.ShowDialog() == DialogResult.OK)
                {
                    //OKボタンが押されたら入力内容を取り込む
                    //横方向(幅方向)の台数
                    wNumber = thisForm.getWNumber();
                    //縦方向(高さ方向)の台数
                    dNumber = thisForm.getDNumber();
                    //数字の適正チェック
                    if(wNumber < 1)
                    {
                        TaskDialog.Show("エラー", "横方向の台数は1以上でなければなりません");
                        return Result.Cancelled;
                    }
                    if (dNumber < 1)
                    {
                        TaskDialog.Show("エラー", "縦方向の台数は1以上でなければなりません");
                        return Result.Cancelled;
                    }
                }
                else
                {
                    return Result.Cancelled;
                }
            }

            //配置するスペースを選ぶ
            Space space = KUtil2.GetSpace(doc, uidoc);
            if (space == null)
            {
                TaskDialog.Show("エラー", "スペースが正しく選ばれていません");
                return Result.Cancelled;
            }

            //スペースの原点(左下の最小点)を取得する
            XYZ origin = KUtil2.SpaceMin(doc, space);

            //スペースの幅widthと奥行depth
            string[] wd = KUtil2.SpaceWidthAndDepth(doc, space);
            double width = Convert.ToDouble(wd[0]);
            double depth = Convert.ToDouble(wd[1]);

            //スペース内に指定台数の器具が納まるか確認する
            if(width < equipmentSizeX * wNumber)
            {
                TaskDialog.Show("エラー", "スペースの横方向に指定台数の器具が納まりません。台数を減らしてください");
                return Result.Cancelled;
            }
            if (depth < equipmentSizeY * dNumber)
            {
                TaskDialog.Show("エラー", "スペースの縦方向に指定台数の器具が納まりません。台数を減らしてください");
                return Result.Cancelled;
            }

            //横方向の間隔を計算する
            double wDistance = width / wNumber;
            //縦方向の間隔を計算する
            double dDistance = depth / dNumber;

            //左下の最初要素の位置を決定する。
            double lowerLevelOfSpace = space.Level.Elevation;//面付ではない器具を扱うとき、器具の高さ情報はGL面(基準面)からの高さになるのでスペースの下限レベルを引く
            XYZ newPosition = origin.Add(new XYZ(wDistance/2, dDistance/2, ElementPoint.Z - lowerLevelOfSpace));//高さ方向は現在の器具高さ採用

            //最初の器具を移動させるための移動ベクトルを計算
            XYZ moveVector = newPosition - ElementPoint;

            //コピーして配置する
            using (Transaction tx = new Transaction(doc))
            {
                tx.Start("Copy Elements in Space");

                //コピー元器具1個を移動ベクトル分だけ移動
                ElementTransformUtils.MoveElement(doc, elementId, moveVector);

                //まず横方向のコピー。あとでこの1列を縦方向にコピーする
                //横の台数のほうが多いケースがおおいのではないか。部屋が横に長い(推測)
                IList<ElementId> elementsToCopy = new List<ElementId>();//横一列の器具のIDたち
                elementsToCopy.Add(elementId);  // 最初の器具を追加

                //横方向のコピー
                for (int i = 1; i < wNumber; i++)  // i=1から開始し、wNumber未満まで
                {
                    XYZ copyVector = new XYZ(i * wDistance, 0, 0);
                    ICollection<ElementId> copiedElementIds = ElementTransformUtils.CopyElement(doc, elementId, copyVector);
                    // コピーされた器具のElementIdをリストに追加
                    foreach (ElementId copiedElementId in copiedElementIds)
                    {
                        elementsToCopy.Add(copiedElementId);
                    }
                }
                //縦方向のコピー
                for (int j = 1; j < dNumber; j++)
                {
                    //コピー元からコピー先までの移動距離ベクトル。ここではY方向のみ
                    XYZ copyVector = new XYZ(0, j * dDistance, 0);
                    
                    ElementTransformUtils.CopyElements(doc, elementsToCopy, copyVector);
                }

                tx.Commit();
                
            }
            return Result.Succeeded;
        }
    }
}
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace CopyAndPasteElement
{
    public partial class Form1 : Form
    {
        int wNumber;//横方向の台数
        int dNumber;//縦方向の台数(間口方向)

        public Form1()
        {
            InitializeComponent();
        }

        private void button2_Click(object sender, EventArgs e)
        {
            if (!Int32.TryParse(textBox1.Text, out wNumber))
            {
                this.DialogResult = DialogResult.Cancel;
            }
            if (!Int32.TryParse(textBox2.Text, out dNumber))
            {
                this.DialogResult = DialogResult.Cancel;
            }
            this.DialogResult = DialogResult.OK;
            Close();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            this.DialogResult = DialogResult.Cancel;
            Close();
        }
        public int getWNumber()
        {
            return wNumber;
        }
        public int getDNumber()
        {
            return dNumber;
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            this.textBox2.Focus();
        }
    }
}
using Autodesk.Revit.DB;
using Autodesk.Revit.DB.Mechanical;
using Autodesk.Revit.DB.Structure;
using Autodesk.Revit.UI;
using Autodesk.Revit.UI.Selection;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace CopyAndPasteElement
{
    public static class KUtil2
    {
        //Excelファイルを読むためのダイアログを表示する
        public static string OpenExcel()
        {
            string fileName = string.Empty;

            //OpenFileDialog
            using (OpenFileDialog openFileDialog = new OpenFileDialog())
            {
                openFileDialog.Title = "ファイル選択";
                openFileDialog.Filter = "ExcelFiles | *.xls;*.xlsx;*.xlsm";
                //初期表示フォルダはデスクトップ
                openFileDialog.InitialDirectory = Environment.GetFolderPath(Environment.SpecialFolder.Desktop);

                //ファイル選択ダイアログを開く
                if (openFileDialog.ShowDialog() == DialogResult.OK)
                {
                    fileName = openFileDialog.FileName;
                }
            }
            return fileName;
        }
        //ミリメーターを内部単位に変換する
        public static double CVmmToInt(double x)
        {
            return UnitUtils.ConvertToInternalUnits(x, UnitTypeId.Millimeters);
        }
        //内部単位をミリメーターに変換する
        public static double CVintTomm(double x)
        {
            return UnitUtils.ConvertFromInternalUnits(x, UnitTypeId.Millimeters);
        }
        //目的のレベル(階)を探して返す
        public static Level GetLevel(Document doc, string levelName)
        {
            Level result = null;
            //エレメントコレクターの作成
            FilteredElementCollector collector = new FilteredElementCollector(doc);
            //レベルを全て検出する
            ICollection<Element> collection = collector.OfClass(typeof(Level)).ToElements();
            //目的のレベルを探す
            foreach (Element element in collection)
            {
                Level level = element as Level;
                if (null != level)
                {
                    if (level.Name == levelName)
                    {
                        result = level;
                    }
                }
            }
            return result;
        }
        //床のためのカーブを作る
        public static void CreateGrids(Document doc)
        {
            double y1 = -100;
            double y2 = 30;
            double[] x = { 2, 34, 74, 114, 147 };
            string[] symbX = { "1", "2", "3", "4", "5" };

            double x1 = -30;
            double x2 = 180;
            double[] y = { -2, -33, -66 };
            string[] symbY = { "A", "B", "C" };

            for (int i = 0; i < x.Length; i++)
            {
                XYZ start = new XYZ(x[i], y1, 0);
                XYZ end = new XYZ(x[i], y2, 0);
                Line line = Line.CreateBound(start, end);
                Grid grid = Grid.Create(doc, line);
                grid.Name = symbX[i];
            }

            for (int i = 0; i < y.Length; i++)
            {
                XYZ start = new XYZ(x1, y[i], 0);
                XYZ end = new XYZ(x2, y[i], 0);
                Line line = Line.CreateBound(start, end);
                Grid grid = Grid.Create(doc, line);
                grid.Name = symbY[i];
            }
        }
        //ユーザーがピックアップすることでモデルからスペースを取得する(現在モデルとリンクモデルに対応)
        public static Space GetSpaceCurrentAndLink(Document doc, UIDocument uidoc)
        {
            Reference spaceRef;//スペースのリファレンス
            ElementInLinkSelectionFilter<Space> filter = new ElementInLinkSelectionFilter<Space>(doc);
            try
            {
                spaceRef = uidoc.Selection.PickObject(ObjectType.PointOnElement, filter, "配置先のスペースを選んでください(このモデルでもリンクモデルでも)");
            }
            catch (OperationCanceledException)
            {
                return null;
            }
            Space space;
            if (filter.LastCheckedWasFromLink)
            {
                space = filter.LinkedDocument.GetElement(spaceRef.LinkedElementId) as Space;
            }
            else
            {
                space = doc.GetElement(spaceRef) as Space;
            }
            return space;
        }
        public class ElementInLinkSelectionFilter<T> : ISelectionFilter where T : Element
        {
            private Document _doc;
            public ElementInLinkSelectionFilter(Document doc)
            {
                _doc = doc;
            }
            public Document LinkedDocument { get; private set; } = null;
            public bool LastCheckedWasFromLink
            {
                get { return null != LinkedDocument; }
            }
            public bool AllowElement(Element e)
            {
                return true;
            }
            public bool AllowReference(Reference r, XYZ p)
            {
                LinkedDocument = null;
                Element e = _doc.GetElement(r);
                if (e is RevitLinkInstance)
                {
                    RevitLinkInstance li = e as RevitLinkInstance;
                    LinkedDocument = li.GetLinkDocument();
                    e = LinkedDocument.GetElement(r.LinkedElementId);
                }
                return e is T;
            }
        }
        //ユーザーがピックアップすることでモデルからスペースを取得する(現在モデルのみ対応)
        public static Space GetSpace(Document doc, UIDocument uidoc)
        {
            try
            {
                Reference spaceRef = uidoc.Selection.PickObject(
                    ObjectType.Element,
                    new SpaceSelectionFilter(),
                    "配置先のスペースを選んでください");

                return doc.GetElement(spaceRef) as Space;
            }
            catch (OperationCanceledException)
            {
                //ユーザーがキャンセルした場合
                return null;
            }
        }

        public class SpaceSelectionFilter : ISelectionFilter
        {
            public bool AllowElement(Element elem)
            {
                return elem is Space;
            }

            public bool AllowReference(Reference reference, XYZ position)
            {
                return false;
            }
        }
        //スペースのバウンディングボックスを返す
        public static XYZ SpaceMin(Document doc, Space space)
        {
            Autodesk.Revit.DB.View activeView = doc.ActiveView;
            BoundingBoxXYZ box = space.get_BoundingBox(activeView);
            XYZ min = box.Min;
            return min;
        }
        public static XYZ SpaceMax(Document doc, Space space)
        {
            Autodesk.Revit.DB.View activeView = doc.ActiveView;
            BoundingBoxXYZ box = space.get_BoundingBox(activeView);
            XYZ max = box.Max;
            return max;
        }

        public static string[] SpaceWidthAndDepth(Document doc, Space space)
        {
            string[] result = new string[2];// width, height

            Autodesk.Revit.DB.View activeView = doc.ActiveView;
            BoundingBoxXYZ box = space.get_BoundingBox(activeView);
            XYZ min = box.Min;
            XYZ max = box.Max;
            double width = Math.Abs(max.X - min.X);
            double depth = Math.Abs(max.Y - min.Y);

            result[0] = width.ToString();
            result[1] = depth.ToString();

            return result;
        }
    }
}