0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Revit API】IFC要素をRevit MEP要素へ置換する方法の模索をした

Last updated at Posted at 2025-10-10

RebroやT-fasでモデリングした3DモデルをRevitMEP要素として変換できたら、と今まで何度も考えました。
例えば、IFC(T-fas)→JSON→Revit
「JSONなどで座標情報などを取得し、Revitで生成できないか?」と思い、Revit APIを使ってIFC→MEP変換アドインに挑戦してみました。

目標

  • JSONファイルからダクト情報を読み込み
  • ダクト直線部分をRevit上に生成
  • 端点接続の自動化も試みる(現状未完成)

IFCデータをJSONファイルとして書き出す

using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Xbim.Ifc;
using Xbim.Ifc4.Interfaces;

namespace DuctImporter
{
    public class IfcToJsonConverter
    {
        public static void ConvertIfcToJson(string ifcPath, string outputFolder)
        {
            // フォルダがなければ作成
            if (!Directory.Exists(outputFolder))
                Directory.CreateDirectory(outputFolder);

            string jsonPath = Path.Combine(outputFolder, "myDucts.json");

            using (var model = IfcStore.Open(ifcPath))
            {
                var segments = model.Instances.OfType<IIfcFlowSegment>();
                var segmentList = new List<Dictionary<string, object>>();

                foreach (var seg in segments)
                {
                    string segName = seg.Name != null ? seg.Name.ToString() : "(null)";
                    string segType = seg.GetType().Name;

                    // デフォルト値
                    double width = 500.0;
                    double height = 500.0;
                    double diameter = 500.0;
                    string shape = "Rectangular"; // 丸ダクトなら "Round"

                    double[] start = { 0, 0, 0 };
                    double[] end = { 1, 0, 0 };

                    var psets = seg.IsDefinedBy
                                  .Select(r => r.RelatingPropertyDefinition)
                                  .OfType<IIfcPropertySet>();

                    foreach (var pset in psets)
                    {
                        foreach (var prop in pset.HasProperties.OfType<IIfcPropertySingleValue>())
                        {
                            string propName = prop.Name != null ? prop.Name.ToString() : "";
                            string valueStr = prop.NominalValue?.Value?.ToString() ?? "";
                            if (string.IsNullOrEmpty(valueStr)) continue;

                            // 幅・高さ
                            if (propName.Contains("connection_point_1_size"))
                            {
                                var arr = valueStr.Replace("W:", "").Replace("H:", "").Split(',');
                                if (arr.Length >= 2)
                                {
                                    double.TryParse(arr[0], out width);
                                    double.TryParse(arr[1], out height);
                                }
                            }
                            // 座標
                            else if (propName.Contains("connecting_point_1"))
                            {
                                var arr = valueStr.Split(',');
                                if (arr.Length >= 3)
                                {
                                    start[0] = Convert.ToDouble(arr[0]);
                                    start[1] = Convert.ToDouble(arr[1]);
                                    start[2] = Convert.ToDouble(arr[2]);
                                }
                            }
                            else if (propName.Contains("connecting_point_2"))
                            {
                                var arr = valueStr.Split(',');
                                if (arr.Length >= 3)
                                {
                                    end[0] = Convert.ToDouble(arr[0]);
                                    end[1] = Convert.ToDouble(arr[1]);
                                    end[2] = Convert.ToDouble(arr[2]);
                                }
                            }
                            // 形状
                            else if (propName.Contains("Shape") || propName.Contains("Round"))
                            {
                                // IFC で丸ダクトなら "Round", 角ダクトなら "Rectangular" を設定
                                shape = valueStr.ToLower().Contains("round") ? "Round" : "Rectangular";
                            }

                            // 丸ダクトなら直径も設定
                            if (shape == "Round" && propName.Contains("Diameter"))
                            {
                                double.TryParse(valueStr, out diameter);
                            }
                        }
                    }

                    var segDict = new Dictionary<string, object>
                    {
                        { "Name", segName },
                        { "Type", segType },
                        { "Shape", shape },
                        { "Width", width },
                        { "Height", height },
                        { "Diameter", diameter },
                        { "Start", start },
                        { "End", end }
                    };

                    segmentList.Add(segDict);
                }

                // JSON に書き出し
                File.WriteAllText(jsonPath, JsonConvert.SerializeObject(segmentList, Formatting.Indented));
            }

            Console.WriteLine($"JSON file generated at: {jsonPath}");
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("IFC Path:");
            string ifcPath = Console.ReadLine();

            Console.WriteLine("Output Folder:");
            string outputFolder = Console.ReadLine();

            IfcToJsonConverter.ConvertIfcToJson(ifcPath, outputFolder);

            Console.WriteLine("Done.");
        }
    }
}

書き出したJSONの一部(文字化けはRevit読み込み時から)

[
  {
    "Name": "’¼ü•”",
    "Type": "IfcFlowSegment",
    "Shape": "Rectangular",
    "Width": 500.0,
    "Height": 500.0,
    "Diameter": 500.0,
    "Start": [
      -72749.963521,
      -2214.907934,
      2800.0
    ],
    "End": [
      -72749.963521,
      -2214.907934,
      10000.0
    ]
  },
  {
    "Name": "’¼ŠÇ",
    "Type": "IfcFlowSegment",
    "Shape": "Rectangular",
    "Width": 500.0,
    "Height": 500.0,
    "Diameter": 500.0,
    "Start": [
      -81297.086139,
      2280.908854,
      2318.426936
    ],
    "End": [
      -81297.086139,
      2280.908854,
      790.0
    ]
  },
  {
    "Name": "’¼ŠÇ",
    "Type": "IfcFlowSegment",
    "Shape": "Rectangular",
    "Width": 500.0,
    "Height": 500.0,
    "Diameter": 500.0,
    "Start": [
      -72785.282844,
      564.054683,
      500.0
    ],
    "End": [
      -75398.276203,
      -2444.681048,
      500.0
    ]

JSONをRevitで読み込み、ダクトを生成

using Autodesk.Revit.DB;
using Autodesk.Revit.DB.Mechanical;
using Autodesk.Revit.UI;
using Autodesk.Revit.Attributes;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Windows.Forms;

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

            string jsonPath;
            using (OpenFileDialog ofd = new OpenFileDialog())
            {
                ofd.Filter = "JSON Files (*.json)|*.json";
                ofd.Title = "Select JSON file to import ducts";

                if (ofd.ShowDialog() != DialogResult.OK)
                    return Result.Cancelled;

                jsonPath = ofd.FileName;
            }

            try
            {
                DuctJsonImporter importer = new DuctJsonImporter();
                importer.ImportDuctsFromJson(doc, jsonPath);
            }
            catch (Exception ex)
            {
                message = ex.Message;
                return Result.Failed;
            }

            return Result.Succeeded;
        }
    }

    public class DuctJsonImporter
    {
        public void ImportDuctsFromJson(Document doc, string jsonPath)
        {
            string jsonText = File.ReadAllText(jsonPath);
            var ducts = JsonConvert.DeserializeObject<List<DuctData>>(jsonText);

            List<Duct> createdDucts = new List<Duct>();

            using (Transaction tx = new Transaction(doc, "Import and Connect Ducts"))
            {
                tx.Start();

                // 適当な DuctType, MEPSystemType, Level を取得
                DuctType ductType = new FilteredElementCollector(doc)
                                    .OfClass(typeof(DuctType))
                                    .Cast<DuctType>()
                                    .FirstOrDefault();

                MEPSystemType systemType = new FilteredElementCollector(doc)
                                           .OfClass(typeof(MEPSystemType))
                                           .Cast<MEPSystemType>()
                                           .FirstOrDefault();

                Level level = new FilteredElementCollector(doc)
                              .OfClass(typeof(Level))
                              .Cast<Level>()
                              .FirstOrDefault();

                if (ductType == null || systemType == null || level == null)
                    throw new Exception("Revit 内に DuctType, MEPSystemType または Level が見つかりません。");

                // ミリ → フィート換算
                double mmToFeet = 0.00328084;

                // ① 全ダクト生成
                foreach (var ductData in ducts)
                {
                    XYZ start = new XYZ(
                        ductData.Start[0] * mmToFeet,
                        ductData.Start[1] * mmToFeet,
                        ductData.Start[2] * mmToFeet
                    );

                    XYZ end = new XYZ(
                        ductData.End[0] * mmToFeet,
                        ductData.End[1] * mmToFeet,
                        ductData.End[2] * mmToFeet
                    );

                    Duct duct = Duct.Create(doc, systemType.Id, ductType.Id, level.Id, start, end);

                    if (ductData.Shape == "Round")
                    {
                        duct.get_Parameter(BuiltInParameter.RBS_CURVE_DIAMETER_PARAM)
                            ?.Set(UnitUtils.ConvertToInternalUnits(ductData.Diameter, UnitTypeId.Millimeters));
                    }
                    else
                    {
                        duct.get_Parameter(BuiltInParameter.RBS_CURVE_WIDTH_PARAM)
                            ?.Set(UnitUtils.ConvertToInternalUnits(ductData.Width, UnitTypeId.Millimeters));
                        duct.get_Parameter(BuiltInParameter.RBS_CURVE_HEIGHT_PARAM)
                            ?.Set(UnitUtils.ConvertToInternalUnits(ductData.Height, UnitTypeId.Millimeters));
                    }

                    createdDucts.Add(duct);
                }

                // ② 自動接続処理
                ConnectNearbyDucts(doc, createdDucts);

                tx.Commit();
            }
        }

        /// <summary>
        /// 端点が近いダクト同士を自動接続
        /// </summary>
        private void ConnectNearbyDucts(Document doc, List<Duct> ducts)
        {
            double tolerance = UnitUtils.ConvertToInternalUnits(5.0, UnitTypeId.Millimeters); // 5mm以内で接続
            var endPoints = new List<(Duct duct, Connector connector)>();

            // ① 各ダクトの端点コネクタだけ取得
            foreach (var d in ducts)
            {
                var connectors = d.ConnectorManager.Connectors.Cast<Connector>();
                var curve = ((LocationCurve)d.Location).Curve;

                // 端点に最も近いコネクタのみ抽出
                foreach (var c in connectors)
                {
                    if (c.Origin.DistanceTo(curve.GetEndPoint(0)) < tolerance ||
                        c.Origin.DistanceTo(curve.GetEndPoint(1)) < tolerance)
                    {
                        endPoints.Add((d, c));
                    }
                }
            }

            // ② ペア探索&接続
            for (int i = 0; i < endPoints.Count; i++)
            {
                for (int j = i + 1; j < endPoints.Count; j++)
                {
                    var ep1 = endPoints[i];
                    var ep2 = endPoints[j];

                    if (ep1.duct.Id == ep2.duct.Id) continue;

                    // 距離が許容範囲内なら接続
                    if (ep1.connector.Origin.DistanceTo(ep2.connector.Origin) < tolerance)
                    {
                        try
                        {
                            if (!ep1.connector.IsConnectedTo(ep2.connector))
                            {
                                ep1.connector.ConnectTo(ep2.connector);
                            }
                        }
                        catch { /* 失敗しても無視 */ }
                    }
                }
            }
        }


    }

    public class DuctData
    {
        public string Name { get; set; }
        public string Type { get; set; }
        public string Shape { get; set; }      // Round or Rectangular
        public double Width { get; set; }      // 角ダクトの幅
        public double Height { get; set; }     // 角ダクトの高さ
        public double Diameter { get; set; }   // 丸ダクトの直径
        public double[] Start { get; set; }
        public double[] End { get; set; }
    }
}

端点接続はまだ完全には動作していませんが、試行として「端点距離が近いダクト同士を接続する」ロジックを組み込んでいます。

  • 現状では直線距離が近くても接続されない場合がある
  • 曲がりダクトや長距離接続の自動化はまだ課題

元のIFCデータ(T-fasモデル)

スクリーンショット 2025-10-10 095415.png

JSOONを介して生成したMEPモデル

スクリーンショット 2025-10-10 095124.png
ジョイントが生成されないため、手動で接続する必要があります。これくらいの量であればすぐに完了しますが、ビル一棟分となるとかなりの手間が必要です。
また、IFC要素にダクトサイズ情報が入っていないため、今回は500角統一で生成する設定にしましたが、一本一本サイズとダクト種類を修正しなおす作業も出てきます。
最初にルール決めをしたうえで、IFCからJSONに送る情報をなるべく多く正確に、Revit側のマッピング設定もJSONの情報と一致していないと自動化は難しいと感じました。

実際にやってみて分かったこと

  • JSONを使った一括作図は可能
  • 直線ダクト生成はうまくいく
  • 端点接続や曲がりダクトはまだ難しい
  • 作業効率化には一定の効果あり

今後の課題

  • 端点接続の安定化
  • 曲がりダクト・エルボ自動生成
  • 接続失敗時のログ出力
  • IFC→JSON変換時のパラメータ保持
  • Revit側のマッピング設定

まとめ

今回の挑戦で、JSONからの直線ダクト生成は現実的に可能と分かりました。
ただし、端点接続や曲がり部分の自動化はまだ課題です。

現状では「自動作図の半自動化」として使うのが現実的です。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?