Skip to content

การดึงข้อมูลโมเดล (Core API)

การสื่อสารกับโมเดล Revit เริ่มต้นที่การ “ค้นหา” สิ่งที่เราต้องการ ใน Revit API เราใช้คลาสที่ชื่อว่า FilteredElementCollector ในการดึงข้อมูลออกมาครับ

ในบทนี้เราจะมาเขียนโค้ดเพื่อค้นหา เสาโครงสร้าง (Structural Columns) แจ้งจำนวนให้ผู้ใช้ทราบ

FilteredElementCollector คืออะไร?

Section titled “FilteredElementCollector คืออะไร?”

เปรียบเสมือนคำสั่ง SQL SELECT * FROM ... WHERE ... สำหรับ Revit หน้าที่ของมันคือการกรองเอาเฉพาะ Element ที่เราสนใจออกมาจากฐานข้อมูลของโมเดลทั้งหมดที่กำลังเปิดอยู่

1. การค้นหาเสาโครงสร้าง (Structural Columns)

Section titled “1. การค้นหาเสาโครงสร้าง (Structural Columns)”

เราจะแก้ไขไฟล์ Command.cs เดิม ให้เปลี่ยนจากการโชว์ข้อความเฉยๆ มาเป็นการนับจำนวนเสาแทน

Command.cs
using System.Linq; // ต้อง using System.Linq ด้วย
using Autodesk.Revit.Attributes;
using Autodesk.Revit.DB;
using Autodesk.Revit.UI;
namespace RevitToolkit;
[Transaction(TransactionMode.Manual)]
public class Command : IExternalCommand
{
public Result Execute(
ExternalCommandData commandData,
ref string message,
ElementSet elements)
{
// 1. เข้าถึง Document ปัจจุบันที่เปิดอยู่
Document doc = commandData.Application.ActiveUIDocument.Document;
// 2. สร้างตัวกรอง (Collector)
FilteredElementCollector collector = new FilteredElementCollector(doc);
// 3. กรองเอาเฉพาะหมวดหมู่ "เสาโครงสร้าง" (OST_StructuralColumns)
// สังเกตว่าเราใช้ WhereElementIsNotElementType() เพื่อไม่เอาประเภท (Family Type) มานับรวม
var columns = collector
.OfCategory(BuiltInCategory.OST_StructuralColumns)
.WhereElementIsNotElementType()
.ToElements();
// 4. แสดงผลจำนวนที่พบ
TaskDialog.Show("RevitToolkit", $"พบเสาโครงสร้างทั้งหมด {columns.Count} ต้นในโมเดลนี้");
return Result.Succeeded;
}
}

2. ตัวอย่างการกรองแบบอื่นๆ สำหรับงานโครงสร้าง

Section titled “2. ตัวอย่างการกรองแบบอื่นๆ สำหรับงานโครงสร้าง”

การกรองหา คาน (Structural Framing):

var beams = new FilteredElementCollector(doc)
.OfCategory(BuiltInCategory.OST_StructuralFraming)
.WhereElementIsNotElementType()
.ToElements();

การกรองหา เหล็กเสริม (Rebar) ทั่วทั้งโปรเจ็กต์:

var rebars = new FilteredElementCollector(doc)
.OfCategory(BuiltInCategory.OST_Rebar)
.WhereElementIsNotElementType()
.ToElements();

การดึงเฉพาะของที่ถูก Select ไว้บนหน้าจอ:

UIDocument uidoc = commandData.Application.ActiveUIDocument;
ICollection<ElementId> selectedIds = uidoc.Selection.GetElementIds();

🔍 เจาะลึกกลไกการค้นหาวัตถุและ Revit DB API ที่สำคัญ

Section titled “🔍 เจาะลึกกลไกการค้นหาวัตถุและ Revit DB API ที่สำคัญ”

เพื่อให้เข้าใจว่าโค้ดสั้นๆ ด้านบนค้นหาวัตถุในโปรเจ็กต์อันมหาศาลได้อย่างไร เรามาชำแหละ API สำคัญแต่ละชิ้นกันครับ:

  • มันคืออะไร: ตัวแทนอย่างเป็นทางการของไฟล์โครงการหรือแบบจำลองโมเดล Revit (.rvt)
  • หน้าที่ในโค้ด: เป็นสะพานหลักที่เก็บข้อมูลวัตถุทางกายภาพ การตั้งค่าระดับชั้น และพิกัดโครงการ โดยตัวแปร Document doc = commandData.Application.ActiveUIDocument.Document; จะช่วยจับจุดเชื่อมโยงชี้ไปยังแบบจำลองที่เรากำลังคลิกเปิดทำงานอยู่
  • ทำไมต้องใช้: ก่อนจะกรองหาของอะไรในระบบ เราต้องรู้ก่อนว่าจะไปคุ้ยหาที่ “เอกสารโครงการไหน” คลาสนี้จึงเป็นจุดตั้งต้นหลักของการกระทำแทบทุกชนิด
  • มันคืออะไร: เครื่องมือสืบค้นฐานข้อมูลหลัก (Query Engine) ของระบบ Revit
  • หน้าที่ในโค้ด: ทำหน้าที่กวาดหาวัตถุในไฟล์ โดยจะทำงานร่วมกับระบบความเร็วสูงระดับล่าง (C++) ของ Revit ทำให้สามารถคัดกรองวัตถุจากหลักหมื่นชิ้นให้เหลือหลักสิบชิ้นได้ในเสี้ยววินาที
  • ทำไมต้องใช้: โครงสร้างโมเดล Revit มีความสลับซับซ้อนมาก การใช้ Collector จะค้นหาได้รวดเร็วกว่าการดึงข้อมูลวัตถุทั้งหมดขึ้นมาวนลูปตรวจสอบในภาษา C# ด้วยตนเองหลายร้อยเท่า
  • มันคืออะไร: ลิสต์รายชื่อหมวดหมู่ชิ้นวัตถุมาตรฐานที่ Autodesk ตีตราติดตั้งมาให้กับระบบของ Revit (เช่น ประตู, หน้าต่าง, เสา, คาน, ผนัง)
  • หน้าที่ในโค้ด: ใช้บอกฟังก์ชัน .OfCategory(...) เพื่อจำกัดความต้องการอย่างเฉพาะเจาะจง:
    • BuiltInCategory.OST_StructuralColumns: ระบุว่าค้นหาเฉพาะ เสาโครงสร้าง
    • BuiltInCategory.OST_StructuralFraming: ระบุว่าค้นหาเฉพาะ คานโครงสร้าง
    • BuiltInCategory.OST_Rebar: ระบุว่าค้นหาเฉพาะ เหล็กเสริม
  • ทำไมต้องใช้: ช่วยให้ปลั๊กอินทำงานได้อย่างยืดหยุ่นในเครื่องคอมพิวเตอร์ที่ใช้ระบบภาษาของโปรแกรมแตกต่างกัน (เช่น รันบน Revit ภาษาไทย, อังกฤษ หรือ ญี่ปุ่น ก็ยังคงค้นเจอหมวดหมู่เดียวกัน)

4. WhereElementIsNotElementType() (Method)

Section titled “4. WhereElementIsNotElementType() (Method)”
  • มันคืออะไร: ตัวกรองแยกตัวตนโมเดลทางกายภาพ (Instances) ออกจากพิมพ์เขียวต้นแบบ (Types)
  • หน้าที่ในโค้ด: คัดแยกเอาเฉพาะชิ้นโมเดลที่ถูกวางไว้จริงในโครงการ และละเว้นการดึงพวก “รายการขนาดโมเดลต้นแบบ” (Family Symbols) มานับรวมด้วย
  • ยกตัวอย่างเห็นภาพ: หากในแบบของเรามีโมเดลเสาขนาด 30x30 ซม. ถูกตั้งค่าต้นแบบไว้ 1 ตัว แต่ถูกนำไปคัดลอกวางใช้งานจริงในอาคาร 50 จุด เมธอดนี้จะกรองคัดกรองให้เหลือเสา 50 ต้นที่ตั้งอยู่จริงบนพิกัดงาน โดยไม่อัญเชิญเอาตัว “ข้อมูลขนาดความกว้างเสาในตระกูล” มานับให้งงเล่นครับ
  • ทำหน้าที่อะไร: ประมวลผลคำสั่งกรองทั้งหมด แล้วจัดระเบียบแปลงข้อมูลวัตถุออกมาให้อยู่ในรูปของลิสต์อะเรย์ C# (IList<Element>) เพื่อส่งต่อให้โค้ดส่วนอื่นนำไปใช้งาน
  • ทำไมต้องระวัง: หากข้ามขั้นตอนนี้ วัตถุที่ได้จะยังอยู่ในรูปตัวสะสมข้อมูล (Collector Stream) ซึ่งไม่สามารถเข้าถึงข้อมูลมิติ พิกัด หรือเขียนค่าทับลงไปแบบเดี่ยวๆ ได้อย่างสะดวก

3. การปรับแต่งความเร็วในการดึงข้อมูล (Quick Filters vs Slow Filters)

Section titled “3. การปรับแต่งความเร็วในการดึงข้อมูล (Quick Filters vs Slow Filters)”

เมื่อโมเดลของคุณเริ่มใหญ่ขึ้น (เช่น มีวัตถุถึงแสนหรือล้านชิ้นในงานโครงสร้างช่วงตึกสูง) ลำดับการกรองวัตถุด้วย FilteredElementCollector จะเป็นตัวตัดสินหลักเลยครับว่าปลั๊กอินของคุณจะรันเสร็จใน 0.1 วินาที หรือต้องรอค้างไปนานกว่า 10 วินาที!

ใน Revit API ตัวกรองถูกแบ่งออกเป็น 2 ประเภทใหญ่ตามความเร็วในการประมวลผล:

⚡ 1. Quick Filters (เร็วระดับมิลลิวินาที)

Section titled “⚡ 1. Quick Filters (เร็วระดับมิลลิวินาที)”

ตัวกรองประเภทนี้รันอยู่บนโครงสร้างหน่วยความจำหลักระบบ C++ Native ของ Revit ทำให้สามารถคัดแยกวัตถุจำนวนมากทิ้งได้ทันทีโดยไม่ต้องโหลดหน่วยความจำฝั่ง C# Managed ขึ้นมาครอบวัตถุ

  • ตัวอย่างที่ควรใช้แรกสุด:
    • .OfCategory(...) (กรองตาม Category)
    • .OfClass(...) (กรองตามชนิด Class ใน C#)
    • .WhereElementIsNotElementType() (กรองเอาเฉพาะตัววางในแบบ)
    • .WhereElementIsElementType() (กรองเอาเฉพาะต้นแบบดีไซน์)

🐢 2. Slow Filters (ช้ากว่าและใช้แรมมากกว่า)

Section titled “🐢 2. Slow Filters (ช้ากว่าและใช้แรมมากกว่า)”

ตัวกรองประเภทนี้กำหนดให้ Revit ต้องดึงโครงสร้างรายละเอียดเชิงลึกของชิ้นงานขึ้นมาสร้างเป็น C# Object เพื่อเปิดตรวจสอบข้อมูลภายใน (เช่น ตรวจรูปทรงเรขาคณิต หรือค่าพารามิเตอร์ข้างใน) จึงรันได้ช้ากว่ามาก

  • ตัวอย่าง:
    • ElementParameterFilter (กรองหาชิ้นงานตามค่าที่อยู่ในพารามิเตอร์)
    • BoundingBoxIntersectsFilter (กรองตามการชนกันของพื้นที่ 3D)
    • การวนลูปเช็กค่าด้วยคำสั่ง LINQ ของ C# (เช่น .Where(x => ... ))

🔑 กฎทองการเขียนฟิงเตอร์ความเร็วสูง (The Golden Rule)

Section titled “🔑 กฎทองการเขียนฟิงเตอร์ความเร็วสูง (The Golden Rule)”

“เริ่มด้วย Quick Filters เสมอเพื่อตัดข้อมูลส่วนใหญ่ออกไปก่อน แล้วค่อยตามด้วย Slow Filters หรือ LINQ ปิดท้าย!”

ลองมาดูเปรียบเทียบการกรองหาเสาโครงสร้างคอนกรีตที่มีพารามิเตอร์ระบุความแข็งแรง (Concrete_Strength_FC) เท่ากับ "C350" ระหว่างวิธีที่ “แย่” และ “ดีที่สุด” กันครับ:

การเปรียบเทียบลำดับการกรองความเร็วสูง
// ❌ วิธีที่แย่และช้าที่สุด: วนลูปเช็กวัตถุทั้งโปรเจ็กต์ด้วย LINQ (Revit จะรันช้าจนโปรแกรมค้าง)
var slowWay = new FilteredElementCollector(doc)
.WhereElementIsNotElementType()
.ToElements() // ดึงของทั้งหมดแสนชิ้นเข้ามาในฝั่ง C#
.Where(x => x.Category?.Id.IntegerValue == (int)BuiltInCategory.OST_StructuralColumns
&& x.LookupParameter("Concrete_Strength_FC")?.AsString() == "C350")
.ToList();
// วิธีที่ดีที่สุดและเร็วที่สุด: ใช้ Quick Filter สกัดทิ้งก่อน ค่อยเช็กเงื่อนไขเฉพาะเจาะจง
// 1. กรองเอาเฉพาะเสาโครงสร้างที่เป็น Instance (ใช้ Quick Filter ทั้งคู่ - คัดเหลือเสาไม่กี่ร้อยต้นอย่างรวดเร็ว)
var collector = new FilteredElementCollector(doc)
.OfCategory(BuiltInCategory.OST_StructuralColumns)
.WhereElementIsNotElementType();
// 2. ใช้ Slow Filter (หรือดึงออกมาวนลูปประมวลผลต่อฝั่ง C# กับลิสต์ขนาดเล็ก)
var result = new List<Element>();
foreach (Element col in collector)
{
Parameter param = col.LookupParameter("Concrete_Strength_FC");
if (param != null && param.AsString() == "C350")
{
result.Add(col);
}
}

ในบทต่อไป เราจะมาดูวิธีการ “แก้ไข/สร้าง” (Write) ข้อมูลกลับเข้าไปในโมเดลผ่านระบบ Transaction กันครับ!