651MB IGES를 Blender 디지털 트윈으로 — 1.5톤 리프트 시스템

1.5톤 리프트 시스템 렌더

작성자

카테고리:

651 MB 짜리 SolidWorks IGES 파일을 OpenCASCADE 로 읽어 삼각 메쉬로 바꾸고, Blender 씬으로 세워 전시용 렌더까지 뽑은 과정을 정리한다. 부품 1,533 개, 삼각형 346 만 개 규모다. 디지털 트윈의 첫 단계인 “형상을 정확히 옮기는 일”에서 실제로 부딪힌 함정들을 함께 적는다.

대상 모델1.5톤 리프트 시스템 (SolidWorks 출력 IGES)
입력 파일651 MB, 엔티티 1,606,806 개
변환 도구Python 3.9, cadquery-ocp 7.7.2 (OpenCASCADE)
렌더Blender 5.2 LTS, EEVEE Next, AgX
산출물mesh_lift.npz, lift_scene.blend, 렌더 6 컷

1. 머리말

설비의 디지털 트윈을 만들려면 먼저 형상이 있어야 한다. 그런데 설계에서 넘어오는 파일은 대개 중립 포맷이다. STEP 이면 다행이고, IGES 면 일이 는다.

이번 모델은 파일 이름이 ...STEP 으로 끝나는 폴더에 들어 있었으나 실제로는 IGES 였다. 확장자가 .IGS 였고 열어 보니 내용도 IGES 였다. 설계 쪽에서 “중립 파일” 이라는 뜻으로 폴더 이름을 그렇게 붙인 듯하다. 파일은 항상 이름이 아니라 내용으로 판단해야 한다.

IGES 가 까다로운 이유는 다음과 같다.

  • 솔리드(B-rep 셸) 구조 없이 곡면이 그냥 나열되어 있는 경우가 많다.
  • 어셈블리 계층이 없어 “어디까지가 한 부품인가” 를 파일이 알려 주지 않는다.
  • 색은 곡면 엔티티에 개별로 붙어 있어, 형상과 색을 따로 읽어 짝지어야 한다.

2. 파일을 열어 구조부터 본다

변환 코드를 짜기 전에 파일이 어떤 모양인지부터 확인했다. OpenCASCADE 의 IGESControl_Reader 로 읽고 엔티티 종류를 세어 보면 된다.

<표 1> 입력 IGES 의 구조

항목값의미
전체 엔티티1,606,806곡면, 곡선, 점, 색 정의 등 전부
트림 곡면 (타입 144)83,480실제 형상을 이루는 면
루트(root)1전체가 서브피겨 하나로 묶여 있음
색 정의 (타입 314)51 종SolidWorks 외관색

여기서 중요한 것은 루트가 1 개라는 점이다. 보통이라면 리더의 TransferRoots() 를 호출해 통째로 변환하면 된다. 그런데 이 파일은 전체가 서브피겨(308/408) 하나로 묶여 있어서, 통째로 변환하면 덩어리 하나가 나온다. 부품 1,533 개가 구분되지 않는다.

게다가 통째로 변환하면 곡면마다 붙어 있던 색이 사라진다. 리프트의 프레임 도장색, 스테인리스 가공품, 바닥 안전 표시 노란색을 그대로 살리려면 색이 필요하다.

그래서 트림 곡면을 하나씩 직접 변환하는 쪽을 택했다.


3. 곡면을 하나씩 변환하기

(1) 왜 CAF 를 쓰지 않았나

색까지 함께 읽는 표준적인 방법은 IGESCAFControl_Reader 로 XDE 문서에 올리는 것이다. 그런데 이 PC 의 cadquery-ocp 7.7.2 에는 IGESCAFControl 모듈이 없었다. 빌드에서 빠져 있었다.

차선책으로 model.Number(entity) 나 reader.TransferEntity() 를 써 보려 했으나, 이 바인딩에서는 Standard_Transient 를 인자로 받는 함수들이 타입 오류를 냈다. 결국 엔티티 자신의 메서드로 우회했다.

lst = rd.GiveList("iges-type(144)")          # 트림 곡면만 골라낸다
cs = IGESToBRep_CurveAndSurface()            # 곡면 -> BRep 저수준 변환기
cs.SetModel(model)

for k in range(1, n144 + 1):
    e = lst.Value(k)
    rgb = DEFAULT_RGB
    rc = e.RankColor()                       # >0 이면 표준색 번호
    if rc < 0:                               # <0 이면 색 엔티티(314) 참조
        r, g, b = e.Color().RGBIntensity()   # 0~100 스케일
        rgb = (r / 100.0, g / 100.0, b / 100.0)
    elif rc > 0:
        rgb = STD_COLORS.get(rc, DEFAULT_RGB)
    s = cs.TransferGeometry(e)

RankColor() 의 부호가 갈림길이다. 양수면 IGES 표준 8색 중 하나를 가리키는 번호이고, 음수면 별도의 색 정의 엔티티(314)를 가리키는 포인터다. SolidWorks 외관색은 대부분 후자이며, RGBIntensity() 가 0~100 스케일로 값을 돌려주므로 100 으로 나눠야 한다. 255 로 나누면 모든 색이 거의 검게 나온다.

(2) 결정적인 함정 — 형상 치유

★주 1) 직접 변환은 리더의 형상 치유를 거치지 않는다.
TransferRoots() 로 통째로 변환하면 OpenCASCADE 가 내부적으로 ShapeProcess 를 돌려 형상을 다듬는다. 그런데 TransferGeometry() 로 곡면을 직접 변환하면 그 과정이 생략된다. 처음 이 사실을 모르고 돌렸을 때, 테셀레이션 결과에서 면의 45% 가량이 통째로 비었다. 특히 회전곡면(타입 120) 기반의 면 — 파이프, 축, 원통 — 이 거의 전부 사라졌다. 화면에는 사각 프레임만 남고 둥근 것은 하나도 없었다.

해결은 두 줄이다.

s = cs.TransferGeometry(e)
BRepLib.BuildCurves3d_s(s)      # 파라미터 곡선만 있는 엣지에 3D 곡선을 만들어 준다
fx = ShapeFix_Shape(s)          # 허용오차, 방향, 끊어진 와이어를 다듬는다
fx.Perform()
s = fx.Shape()

BuildCurves3d_s() 가 핵심이다. IGES 의 트림 곡면은 경계 곡선이 곡면 파라미터 공간(uv)에만 정의되어 있는 경우가 흔한데, 메쉬 생성기는 3D 공간의 곡선을 요구한다. 이것이 없으면 면은 유효해 보이지만 삼각형이 하나도 생기지 않는다.

두 줄을 넣은 뒤 변환 실패 0 건, 삼각형이 생긴 면 83,471 / 83,480 이 되었다. 빠진 9 개는 면적이 0 에 가까운 퇴화면이라 무시해도 좋다.


4. 테셀레이션

면을 모두 모아 컴파운드로 묶고 한 번에 테셀레이션한다. 면마다 따로 돌리는 것보다 훨씬 빠르고, 병렬 옵션을 쓸 수 있다.

LIN_DEFLECTION = 0.8      # mm  - 곡면과 삼각형 사이의 최대 거리
ANG_DEFLECTION = 0.35     # rad - 인접 삼각형 법선의 최대 각도차

BRepMesh_IncrementalMesh(comp, LIN_DEFLECTION, False, ANG_DEFLECTION, True)
#                                              ^ 상대오차 아님   ^ 병렬

전체 치수가 9.5 m 인 설비에서 0.8 mm 는 넉넉한 값이다. 전시용 렌더가 목적이므로 이 정도면 곡면이 각져 보이지 않는다. 각도 편차 0.35 rad(약 20°)는 작은 구멍이나 필렛이 다각형으로 뭉개지는 것을 막아 준다.

두 값을 절반으로 줄이면 삼각형이 3~4 배로 늘어난다. 메모리와 Blender 반응 속도를 생각하면 먼저 거칠게 뽑아 형상을 확인하고, 필요한 부분만 조이는 편이 낫다.


5. 부품 단위로 묶기

가장 머리를 쓴 부분이다. IGES 에는 “이 면들이 한 부품” 이라는 정보가 없다. 83,471 개의 면이 평평하게 나열되어 있을 뿐이다. 이대로 Blender 에 넣으면 오브젝트가 8 만 개가 되어 씬을 열 수조차 없다.

착안점은 단순하다. 같은 부품의 면들은 꼭짓점을 공유한다. 서로 다른 부품은 닿아 있어도 꼭짓점이 정확히 일치하지 않는다. 그러니 꼭짓점을 공유하는 면들을 이어 붙이면 부품이 된다. 전형적인 서로소 집합(union-find) 문제다.

SNAP = 0.01                                   # mm, 꼭짓점 동일 판정 격자

key = np.round(allv / SNAP).astype(np.int64)  # 0.01mm 격자로 양자화
_, inv = np.unique(key, axis=0, return_inverse=True)

order = np.argsort(inv, kind="stable")        # 같은 꼭짓점끼리 모은다
inv_s, own_s = inv[order], owner[order]
same = inv_s[1:] == inv_s[:-1]
a, b = own_s[:-1][same], own_s[1:][same]      # 꼭짓점을 공유하는 면 쌍
pairs = np.unique(np.stack([a[diff], b[diff]], axis=1), axis=0)

dsu = DSU(nf)
for x, y in pairs:
    dsu.union(int(x), int(y))

꼭짓점 비교를 부동소수점으로 하면 미세한 오차 때문에 같은 점이 갈린다. 그래서 0.01 mm 격자로 반올림한 정수 키를 만들어 비교한다. 이 값이 너무 크면 닿아 있는 별개 부품이 하나로 붙고, 너무 작으면 한 부품이 쪼개진다. 0.01 mm 는 기계 가공 공차보다 작고 테셀레이션 오차보다는 커서 잘 맞았다.

비교 자체를 numpy 의 정렬로 처리한 덕에, 300 만 개 꼭짓점에서 공유 쌍 188,763 개를 찾는 데 몇 초면 끝난다. 이중 루프로 돌렸다면 하루가 걸렸을 것이다.

결과는 부품 1,533 개였다. 실제 조립도의 부품 수와 자릿수가 맞는다.


6. 변환 결과

<표 2> 변환 수치 (총 241 초)

단계결과경과
파일 읽기엔티티 1,606,80616.7 s
곡면 변환면 83,480 수집, 실패 0, 색 51 종188.6 s
테셀레이션linear 0.8 mm / angular 0.35 rad212.4 s
메쉬 추출삼각형 있는 면 83,471 / 83,480232.8 s
부품 묶기공유 꼭짓점 쌍 188,763 → 부품 1,533236.4 s
저장정점 3,059,633 / 삼각형 3,464,489241.1 s

전체 치수는 9,464 × 4,980 × 6,495 mm 로 나왔다. 도면과 대조해 일치하는 것을 확인했다. 치수 검증은 반드시 해야 한다. IGES 단위 설정이 어긋나면 1,000 배 차이가 나는데, 렌더만 보고는 알아채기 어렵다.

결과는 mesh_lift.npz 한 파일로 저장한다. 정점·삼각형 배열과 부품별 시작 인덱스, 부품별 바운딩 박스와 대표색을 함께 담았다. 651 MB 입력이 24 MB로 줄었다.


7. Blender 씬 세우기

(1) 방향과 바닥 찾기

CAD 에서 온 모델은 어느 축이 위인지 알 수 없다. SolidWorks 는 Y-up 이 기본이지만 이 모델은 이미 Z-up 이었다. 확인하는 가장 빠른 방법은 양쪽으로 한 번씩 뽑아 보는 것이다. Workbench 엔진으로 부품별 랜덤색 프리뷰를 만들면 몇 초면 나온다.

부품별 랜덤색 Workbench 프리뷰
<그림 1> 형상·방향 확인용 프리뷰. 부품마다 다른 색을 주면 묶기가 제대로 되었는지도 함께 보인다

바닥 높이는 베이스 레일 하단인 IGES 좌표 Z = 2,776 mm 로 잡았다. 이 값을 기준으로 그 아래로 내려가는 부품을 세면 피트(pit) 부품 99 개가 자동으로 걸러진다. 개구부 범위와 깊이 2.61 m 도 그 부품들의 바운딩 박스에서 바로 나온다. 사람이 눈으로 고르지 않아도 된다.

(2) 재질

SolidWorks 외관색 중 의미 있는 색 33 종은 그대로 살렸다. 문제는 설계자가 색을 지정하지 않아 기본 미색으로 남은 부품들이다. 이것들을 전부 같은 회색으로 두면 밋밋해진다.

그래서 부품 크기를 기준으로 재질을 나눴다. 큰 것은 프레임 도장, 중간은 가공 금속, 얇고 넓은 것은 강판, 작은 것은 소물 — 네 가지다. 완벽하지는 않지만 “큰 구조물은 도장, 작은 부품은 금속” 이라는 경험칙이 실제 설비와 대체로 맞아서, 손으로 지정한 것에 가까운 결과가 나왔다.

(3) 렌더

EEVEE Next 로 64 샘플, 색 변환은 AgX 를 썼다. AgX 는 금속 하이라이트가 흰색으로 타버리는 것을 막아 준다. 스테인리스가 많은 설비에서는 체감 차이가 크다.

1.5톤 리프트 시스템 전체 렌더
<그림 2> 메인 아이소 뷰. 프레임 도장색, 스테인리스 가공품, 바닥 안전 표시 노란색이 모두 원본 외관색이다

피트 내부는 바닥면을 숨긴 컷어웨이로 따로 뽑았다. 스크류 잭과 구동 샤프트, 기어박스의 배치가 드러난다. 이런 그림은 설비 설명 자료에서 특히 쓸모가 있다.

피트 내부 컷어웨이 렌더
<그림 3> 피트 컷어웨이. 승강 구동부 구조가 보인다

8. 정리

요점을 다시 추린다.

  1. 파일은 이름이 아니라 내용으로 판단한다. STEP 폴더 안의 IGES 였다.
  2. 루트가 하나로 묶인 IGES 는 통째로 변환하면 덩어리가 되고 색도 잃는다. 곡면을 하나씩 변환해야 색을 짝지을 수 있다.
  3. 직접 변환하면 형상 치유가 생략된다. BuildCurves3d_s() 와 ShapeFix_Shape 를 반드시 넣는다. 안 넣으면 회전곡면이 통째로 사라진다.
  4. 부품 구분이 없으면 공유 꼭짓점 + union-find 로 묶는다. 비교는 정수 격자로 양자화해 numpy 정렬로 처리한다.
  5. 치수를 도면과 대조한다. 단위 오류는 렌더만 봐서는 모른다.
  6. 바닥 높이 하나만 정하면 피트 부품, 개구부, 깊이가 자동으로 계산된다.

여기까지가 디지털 트윈의 절반이다. 형상은 정확히 옮겨졌지만 아직 움직이지 않는다. 다음은 승강 리그를 붙이고 PLC 의 D 레지스터 값을 물려 실제 설비와 같이 움직이게 하는 일이다. PLC 쪽 통신은 Melsec PLC 데이터 엑세스하기에 정리해 두었다. 두 갈래가 만나면 트윈이 된다.

코멘트

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다

어제 방문자 : 3 명오늘 방문자 : 1 명전체 방문자 : 584 명