
Revit API로 무언가를 하려면 거의 항상 첫 단계가 같다. 모델에서 원하는 요소를 골라내는 것이다. 벽만, 특정 레벨의 문만, 배치된 인스턴스만. 이 조회를 담당하는 클래스가 FilteredElementCollector다. 그리고 골라낸 요소의 값을 바꾸려면 반드시 Transaction 안에서 해야 한다. 이 두 가지, “필터로 모으고 트랜잭션으로 고친다”는 패턴은 Revit 플러그인 코드의 8할을 차지한다. 이 글은 컬렉터의 필터 종류와 성능 차이, 그리고 트랜잭션의 시작·커밋·롤백을 실제 C# 코드와 함께 정리한다.
■ FilteredElementCollector의 기본형
컬렉터는 생성자에 Document를 넘기고 필터 메서드를 체인으로 이어 붙이는 방식으로 쓴다. 마지막에 ToElements()나 ToElementIds()로 결과를 뽑는다.
// 배치된 벽 인스턴스만 모으기
var walls = new FilteredElementCollector(doc)
.OfCategory(BuiltInCategory.OST_Walls) // 카테고리: 벽
.WhereElementIsNotElementType() // 타입 제외, 인스턴스만
.ToElements();
WhereElementIsNotElementType()를 빼면 벽 타입(WallType)까지 딸려 나온다. 인스턴스만 필요하면 반드시 붙이고, 타입 정의만 모을 때는 WhereElementIsElementType()를 쓴다. 런타임 클래스 기준으로 걸러야 하면 OfClass(typeof(Wall))을 붙인다.
■ 필터의 종류: 빠른 필터와 느린 필터
Revit의 요소 필터는 크게 세 갈래다. 성능을 좌우하는 것은 Quick(빠른) 필터냐 Slow(느린) 필터냐의 구분이다. 빠른 필터는 요소의 헤더 정보만 읽어 통과 여부를 판단하므로 요소를 메모리로 완전히 펼치지 않아도 된다. 느린 필터는 각 요소를 메모리에 전개해야 판단할 수 있어 비용이 크다.
| 분류 | 대표 필터/메서드 | 특징 |
|---|---|---|
| 빠른(Quick) | OfCategory / OfCategoryId | 카테고리로 거름, 헤더만 읽음 |
| 빠른(Quick) | OfClass | 런타임 클래스로 거름 |
| 빠른(Quick) | WhereElementIsNotElementType / IsElementType | 인스턴스/타입 구분 |
| 느린(Slow) | ElementParameterFilter | 파라미터 값 조건, 요소 전개 필요 |
| 느린(Slow) | ElementLevelFilter, 기하 기반 필터 | 레벨·교차 등, 비용 큼 |
| 논리 | LogicalAndFilter / LogicalOrFilter | 필터 조합 |
원칙은 하나다. 빠른 필터로 최대한 범위를 좁힌 뒤에 느린 필터나 LINQ를 적용한다. OfCategory 같은 단축 메서드는 모두 빠른 필터에 대응하므로, 이것부터 걸어 후보 집합을 줄이면 느린 조건이 훑을 요소 수가 확 준다. 처음부터 LINQ의 Where()로 전체를 훑는 코드는 대형 모델에서 눈에 띄게 느려진다.

■ 성능을 살리는 조회 예시
“1층에 있는, 높이가 3000mm를 넘는 벽”을 찾는다고 하자. 빠른 필터로 벽 인스턴스를 먼저 좁히고, 값 조건은 그 뒤에 건다.
// 빠른 필터로 벽 인스턴스만 확보 (여기서 대부분 걸러진다)
var collector = new FilteredElementCollector(doc)
.OfCategory(BuiltInCategory.OST_Walls)
.WhereElementIsNotElementType();
// 값 조건(느림)은 좁혀진 집합에만 적용
double limitFt = UnitUtils.ConvertToInternalUnits(3000, UnitTypeId.Millimeters);
var tallWalls = collector
.Where(w => {
var p = w.get_Parameter(BuiltInParameter.WALL_USER_HEIGHT_PARAM);
return p != null && p.AsDouble() > limitFt;
})
.ToList();
주의할 점: 하나의 컬렉터 인스턴스는 결과를 한 번 소비하면 재사용이 어렵다. 같은 조건을 여러 번 쓸 거라면 ToElements()로 리스트를 뽑아 두고 그 리스트를 재사용한다. 또 Revit 내부 길이 단위는 피트이므로 mm 값을 비교할 때는 위처럼 UnitUtils로 환산해야 값이 어긋나지 않는다.
■ Transaction: 시작·커밋·롤백
모델을 바꾸는 모든 수정은 트랜잭션 안이어야 한다. 밖에서 값을 바꾸려 하면 예외로 막힌다. 트랜잭션은 Start()로 열고 Commit()으로 확정하며, 문제가 생기면 RollBack()으로 통째로 되돌린다. 사용자 입장에선 이 한 덩어리가 실행 취소(Undo) 한 단계로 묶인다.
using (Transaction t = new Transaction(doc, "벽 주석 일괄 입력"))
{
t.Start();
try
{
foreach (Element w in tallWalls)
{
Parameter cmt = w.LookupParameter("Comments");
if (cmt != null && !cmt.IsReadOnly)
cmt.Set("검토완료");
}
t.Commit(); // 정상 종료 → 하나의 Undo 단위로 확정
}
catch (Exception ex)
{
if (t.HasStarted() && !t.HasEnded())
t.RollBack(); // 예외 시 전부 되돌림
throw;
}
}
using으로 감싸면 커밋이나 롤백을 빠뜨려도 트랜잭션이 안전하게 정리된다. 여러 트랜잭션을 묶어 한 번에 되돌리고 싶으면 TransactionGroup을, 반복 안에서 성능이 급할 때는 재생성 억제 옵션을 검토한다.
■ 파라미터 수정 시 자주 나는 실수
| 증상 | 원인 | 대응 |
|---|---|---|
| Set 호출에서 예외 | 트랜잭션 밖에서 수정 | Start~Commit 안으로 이동 |
| LookupParameter가 null | 이름 오타 또는 미바인딩 매개변수 | 존재·바인딩 여부 확인 후 접근 |
| IsReadOnly 매개변수에 Set | 계산식/시스템 매개변수 | IsReadOnly 검사 후 건너뜀 |
| 길이 값이 이상함 | 피트↔mm 단위 미환산 | UnitUtils로 내부 단위 변환 |

■ 눈여겨볼 것
같은 이름의 매개변수가 여러 개라도 LookupParameter는 그중 하나만 돌려준다. 결과가 예측과 다르면 GetParameters(name)로 목록을 확인하는 편이 안전하다. 성능 면에서 가장 큰 실수는 반복문 안에서 컬렉터를 새로 만드는 것이다. 조회는 반복 밖에서 한 번만 하고, 결과 리스트를 돌려야 한다. 이 “빠른 필터 → 값 조건 → 트랜잭션 수정” 패턴은 Revit 2026 API에서도 동일하며, pyRevit이나 Dynamo의 파이썬 노드에서도 문법만 파이썬으로 바뀔 뿐 구조는 그대로 쓴다.
■ 이런 분께 도움이 됩니다
- Revit 플러그인을 처음 짜는 개발자 — 컬렉터와 트랜잭션은 거의 모든 스크립트의 뼈대다
- 대형 모델에서 스크립트가 느린 실무자 — 필터 순서만 바꿔도 체감 속도가 달라진다
- Dynamo·pyRevit 사용자 — 같은 개념을 파이썬으로 그대로 옮길 수 있다
■ 참고
BuiltInCategory·BuiltInParameter 열거형 이름과 단위 식별자(UnitTypeId 등)는 Revit 버전에 따라 달라질 수 있으므로, 배포 전 대상 버전의 API 문서에서 확인한다. 대량 수정 스크립트는 반드시 사본 모델에서 먼저 돌려 결과를 검증한 뒤 실제 프로젝트에 적용한다.
공식 참고 자료 및 적용 범위
이 글은 Revit API의 요소 조회와 트랜잭션 사용 원칙을 설명합니다. 필터 결과·수정 가능성·예외 처리는 Revit API 버전, 문서·요소 상태와 실행 컨텍스트에 따라 달라질 수 있습니다.
아래 자료는 확인일 기준의 제조사·표준기구 또는 공공기관 공식 문서입니다. 제품 기능·표준·정책은 개정될 수 있으므로 실제 프로젝트 적용 전에는 사용 중인 버전의 최신 원문과 프로젝트 기준을 확인하세요.
확인일: 2026-08-15




