ระบบตรวจจับการชน (Clash Detection)
หน้าที่คลาสสิกของวิศวกรโครงสร้างเวลาคุยกับทีมงานระบบ (MEP) หรือสถาปนิก คือการตรวจสอบและเคลียร์แบบว่า “มีท่อวิ่งทะลุคานตรงไหนไหม?” (Clash Detection)
การใช้โปรแกรมอย่าง Navisworks นั้นทำได้ดี แต่ถ้าเราเขียนปลั๊กอินตรวจจับด้วยตัวเองผ่าน Revit API เราจะสามารถบอกให้ปลั๊กอิน “เจาะรูคาน (Opening) อัตโนมัติ” ในตำแหน่งที่เกิดการชนได้ทันทีโดยไม่ต้องเปลี่ยนซอฟต์แวร์ครับ!
เบื้องหลังของเวทมนตร์นี้คือระบบเรขาคณิตที่เรียกว่า BooleanOperationsUtils ครับ
1. Boolean Operations คืออะไร?
Section titled “1. Boolean Operations คืออะไร?”ในทางคณิตศาสตร์ 3D (Solid Geometry) การทำ Boolean คือการเอาก้อนเนื้อวัตถุ 2 ก้อนมาทำปฏิกิริยากัน ซึ่งใน Revit API จะมีโหมดหลักๆ ดังนี้:
BooleanType.Union: รวมก้อน 2 ก้อนเข้าด้วยกันเป็นก้อนเดียวBooleanType.Difference: เอาก้อนที่ 1 ตั้ง ลบด้วยเนื้อของก้อนที่ 2 (การเจาะรู)BooleanType.Intersect: หาส่วนที่ “ทับซ้อนกัน” ระหว่างก้อนที่ 1 และก้อนที่ 2 (นี่คือคีย์เวิร์ดของการทำ Clash Detection)
2. วิธีเขียนโค้ดหาการชนกัน (Intersect)
Section titled “2. วิธีเขียนโค้ดหาการชนกัน (Intersect)”หลักการทำงานคือ:
- ดึงก้อน Solid ของชิ้นงานแรก (เช่น คาน) ออกมา
- ดึงก้อน Solid ของชิ้นงานที่สอง (เช่น ท่อ Pipe) ออกมา
- สั่งทำ
ExecuteBooleanOperation(Solid1, Solid2, BooleanType.Intersect) - ถ้าผลลัพธ์ที่ได้ออกมา มีปริมาตร (Volume > 0) แปลว่ามันชนกัน! แต่ถ้าปริมาตรเป็น 0 หรือว่างเปล่า แปลว่ามันรอด!
using System;using System.Collections.Generic;using Autodesk.Revit.Attributes;using Autodesk.Revit.DB;using Autodesk.Revit.UI;
namespace RevitToolkit;
[Transaction(TransactionMode.Manual)]public class ClashDetectorCommand : IExternalCommand{ public Result Execute(ExternalCommandData commandData, ref string message, ElementSet elements) { Document doc = commandData.Application.ActiveUIDocument.Document;
try { // 1. ดึงชิ้นงานคานโครงสร้างมา 1 ต้น (สมมติว่าเป็น ID 12345) // ในการใช้งานจริงคุณอาจใช้ PickObject ให้ผู้ใช้คลิกเลือก ElementId beamId = new ElementId(12345); Element beam = doc.GetElement(beamId);
// 2. ดึงชิ้นงานท่อ (Pipe) หรือ Duct มา 1 ชิ้น (สมมติว่าเป็น ID 67890) ElementId pipeId = new ElementId(67890); Element pipe = doc.GetElement(pipeId);
// 3. สกัดเอาก้อน Solid ออกมาจากชิ้นงานทั้งสอง Solid beamSolid = GetSolidFromElement(beam); Solid pipeSolid = GetSolidFromElement(pipe);
if (beamSolid == null || pipeSolid == null) { TaskDialog.Show("Error", "ไม่สามารถดึงรูปทรง 3D ออกจากชิ้นงานได้"); return Result.Cancelled; }
// 4. ทำการตรวจจับการชนด้วย Intersect Solid intersectionSolid = BooleanOperationsUtils.ExecuteBooleanOperation( beamSolid, pipeSolid, BooleanOperationsType.Intersect );
// 5. ตรวจสอบปริมาตรเนื้อที่ทับซ้อนกัน if (intersectionSolid != null && intersectionSolid.Volume > 0) { // แปลงหน่วยเป็นลูกบาศก์มิลลิเมตร (เพื่อความเข้าใจง่าย) double clashVolMm3 = UnitUtils.ConvertFromInternalUnits( intersectionSolid.Volume, UnitTypeId.CubicMillimeters );
TaskDialog.Show("🔥 ตรวจพบการชน (CLASH)!", $"ท่อทะลุคานครับ!\nมีปริมาตรการชนเท่ากับ: {clashVolMm3:F0} ลบ.มม."); } else { TaskDialog.Show("✅ ปลอดภัย", "ชิ้นงานทั้งสองไม่ได้สัมผัสหรือชนกันเลยครับ"); }
return Result.Succeeded; } catch (Exception ex) { message = ex.Message; return Result.Failed; } }
// ฟังก์ชันช่วย: งัดแงะเอาก้อน Solid ออกมาจากตัว Element private Solid GetSolidFromElement(Element elem) { Options geomOptions = new Options(); geomOptions.ComputeReferences = true; // ดึงข้อมูลอ้างอิงของหน้าตัดมาด้วย geomOptions.DetailLevel = ViewDetailLevel.Fine; // เอาความละเอียดสูงสุด
GeometryElement geomElem = elem.get_Geometry(geomOptions); if (geomElem == null) return null;
foreach (GeometryObject geomObj in geomElem) { if (geomObj is Solid solid && solid.Volume > 0) { return solid; // เจอ Solid ก้อนแรกที่มีเนื้อ ก็คืนค่าเลย } else if (geomObj is GeometryInstance geomInst) { // ถ้าเป็น Family Instance มันอาจจะซ่อน Solid ไว้ในก้อน Instance อีกชั้น GeometryElement instGeom = geomInst.GetInstanceGeometry(); foreach (GeometryObject instObj in instGeom) { if (instObj is Solid instSolid && instSolid.Volume > 0) { return instSolid; } } } } return null; }}