การดึงข้อมูลโมเดล (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 เดิม ให้เปลี่ยนจากการโชว์ข้อความเฉยๆ มาเป็นการนับจำนวนเสาแทน
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 สำคัญแต่ละชิ้นกันครับ:
1. Document (Class)
Section titled “1. Document (Class)”- มันคืออะไร: ตัวแทนอย่างเป็นทางการของไฟล์โครงการหรือแบบจำลองโมเดล Revit (
.rvt) - หน้าที่ในโค้ด: เป็นสะพานหลักที่เก็บข้อมูลวัตถุทางกายภาพ การตั้งค่าระดับชั้น และพิกัดโครงการ โดยตัวแปร
Document doc = commandData.Application.ActiveUIDocument.Document;จะช่วยจับจุดเชื่อมโยงชี้ไปยังแบบจำลองที่เรากำลังคลิกเปิดทำงานอยู่ - ทำไมต้องใช้: ก่อนจะกรองหาของอะไรในระบบ เราต้องรู้ก่อนว่าจะไปคุ้ยหาที่ “เอกสารโครงการไหน” คลาสนี้จึงเป็นจุดตั้งต้นหลักของการกระทำแทบทุกชนิด
2. FilteredElementCollector (Class)
Section titled “2. FilteredElementCollector (Class)”- มันคืออะไร: เครื่องมือสืบค้นฐานข้อมูลหลัก (Query Engine) ของระบบ Revit
- หน้าที่ในโค้ด: ทำหน้าที่กวาดหาวัตถุในไฟล์ โดยจะทำงานร่วมกับระบบความเร็วสูงระดับล่าง (C++) ของ Revit ทำให้สามารถคัดกรองวัตถุจากหลักหมื่นชิ้นให้เหลือหลักสิบชิ้นได้ในเสี้ยววินาที
- ทำไมต้องใช้: โครงสร้างโมเดล Revit มีความสลับซับซ้อนมาก การใช้ Collector จะค้นหาได้รวดเร็วกว่าการดึงข้อมูลวัตถุทั้งหมดขึ้นมาวนลูปตรวจสอบในภาษา C# ด้วยตนเองหลายร้อยเท่า
3. BuiltInCategory (Enum)
Section titled “3. BuiltInCategory (Enum)”- มันคืออะไร: ลิสต์รายชื่อหมวดหมู่ชิ้นวัตถุมาตรฐานที่ 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 ต้นที่ตั้งอยู่จริงบนพิกัดงาน โดยไม่อัญเชิญเอาตัว “ข้อมูลขนาดความกว้างเสาในตระกูล” มานับให้งงเล่นครับ
5. ToElements() (Method)
Section titled “5. ToElements() (Method)”- ทำหน้าที่อะไร: ประมวลผลคำสั่งกรองทั้งหมด แล้วจัดระเบียบแปลงข้อมูลวัตถุออกมาให้อยู่ในรูปของลิสต์อะเรย์ 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 กันครับ!