2023년 4월 26일 수요일

OpenCASCADE 파이썬 라이브러리 사용해 캐드 3차원 모델링 코딩하는 방법

이 글은 OpenCASCADE(OCC) 파이썬 라이브러리 사용해 캐드 3차원 모델링 디자인 코딩하는 방법을 간략히 나눔한다. OpenCASCADE는 캐드 엔진을 개발하기 위해 필요한 솔리드 모델링 기능을 제공하는 오픈소스 라이브러리이다. 이를 이용하면, 다음과 같은 기능을 개발할 수 있다.
  • 3차원 모델링 기능: 2D sketch, extrude, sweep, revolve
  • 2/3차원 곡선 모델링 기능: line, arc, circle, spline, nurbs
  • 3차원 곡면 모델링 기능: nurbs
  • 솔리드 모델 파일 입출력 기능: step 등
OCC 사용 예시

환경설정
아나콘다 환경에서 다음 패키지를 설치한다.
  • Python OCC library
  • PyQT5 tool
  • matplotlib and mpl_toolkits
터미널에서 다음 명령을 실행한다. 
conda install occt
conda install pythonocc-core
git clone https://github.com/tpaviot/pythonocc-demos


실행 후, pythonocc-demos 폴더 안에 예제를 확인할 수 있다. 설치된 아나콘다 환경에서 파이썬이나 vscode로 실행해 본다.

파라볼라 커브 코딩
다음은 파라볼라 커브 모델링 코드를 보여준다.
from __future__ import print_function

# OCC 라이브러리 임포트
from OCC.Core.gp import gp_Pnt2d, gp_Dir2d, gp_Ax22d, gp_Parab2d
from OCC.Core.GCE2d import GCE2d_MakeParabola
from OCC.Core.Geom2d import Geom2d_TrimmedCurve
from OCC.Display.SimpleGui import init_display

display, start_display, add_menu, add_function_to_menu = init_display()

# 파라볼라 모델링 함수 정의
def parabola(event=None):
    # P is the vertex point
    # P and D give the axis of symmetry
    # 6 is the focal length of the parabola
    a_pnt = gp_Pnt2d(2, 3)               # 좌표점 설정
    a_dir = gp_Dir2d(4, 5)                # 방향 설정
    an_ax = gp_Ax22d(a_pnt, a_dir, True)    # 축 설정
    para = gp_Parab2d(an_ax, 6)               # 모델링
    display.DisplayShape(a_pnt)                # 표시
    display.DisplayMessage(a_pnt, "P")

    aParabola = GCE2d_MakeParabola(para)           # 솔리드 생성
    gParabola = aParabola.Value()
    aTrimmedCurve = Geom2d_TrimmedCurve(gParabola, -100, 100, True)       # 트림 곡선 생성

    display.DisplayShape(aTrimmedCurve, update=True)

if __name__ == "__main__":
    parabola()
    start_display()

결과는 다음과 같다. 

곡면 모델링 코딩
다음은 곡면을 모델링하는 코드이다. 

from __future__ import print_function
from OCC.Core.gp import gp_Pnt, gp_Vec
from OCC.Core.GeomFill import (
    GeomFill_BSplineCurves,
    GeomFill_StretchStyle,
    GeomFill_CoonsStyle,
    GeomFill_CurvedStyle,
)
from OCC.Core.GeomAPI import GeomAPI_PointsToBSpline
from OCC.Core.Geom import Geom_BSplineCurve
from OCC.Display.SimpleGui import init_display
from OCC.Extend.ShapeFactory import point_list_to_TColgp_Array1OfPnt, make_face

display, start_display, add_menu, add_function_to_menu = init_display()

# 곡선에서 곡면 생성
def surface_from_curves():
    # 첫번째 스플라인 생성
    array = []
    array.append(gp_Pnt(-4, 0, 2))   # 곡선 제어점 설정
    array.append(gp_Pnt(-7, 2, 2))
    array.append(gp_Pnt(-6, 3, 1))
    array.append(gp_Pnt(-4, 3, -1))
    array.append(gp_Pnt(-3, 5, -2))

    pt_list1 = point_list_to_TColgp_Array1OfPnt(array)
    SPL1 = GeomAPI_PointsToBSpline(pt_list1).Curve()  # 제어점에서 베지어 스플라인 생성

    # 두번째 스플라인 생성
    a2 = []
    a2.append(gp_Pnt(-4, 0, 2))
    a2.append(gp_Pnt(-2, 2, 0))
    a2.append(gp_Pnt(2, 3, -1))
    a2.append(gp_Pnt(3, 7, -2))
    a2.append(gp_Pnt(4, 9, -1))
    pt_list2 = point_list_to_TColgp_Array1OfPnt(a2)
    SPL2 = GeomAPI_PointsToBSpline(pt_list2).Curve()

    # 스트래치 스타일 설정
    aGeomFill1 = GeomFill_BSplineCurves(SPL1, SPL2, GeomFill_StretchStyle)
    SPL3 = Geom_BSplineCurve.DownCast(SPL1.Translated(gp_Vec(10, 0, 0)))
    SPL4 = Geom_BSplineCurve.DownCast(SPL2.Translated(gp_Vec(10, 0, 0)))

    # Fill with CoonsStyle
    aGeomFill2 = GeomFill_BSplineCurves(SPL3, SPL4, GeomFill_CoonsStyle)
    SPL5 = Geom_BSplineCurve.DownCast(SPL1.Translated(gp_Vec(20, 0, 0)))
    SPL6 = Geom_BSplineCurve.DownCast(SPL2.Translated(gp_Vec(20, 0, 0)))

    # Fill with CurvedStyle
    aGeomFill3 = GeomFill_BSplineCurves(SPL5, SPL6, GeomFill_CurvedStyle)

    aBSplineSurface1 = aGeomFill1.Surface()
    aBSplineSurface2 = aGeomFill2.Surface()
    aBSplineSurface3 = aGeomFill3.Surface()

    display.DisplayShape(make_face(aBSplineSurface1, 1e-6))
    display.DisplayShape(make_face(aBSplineSurface2, 1e-6))
    display.DisplayShape(make_face(aBSplineSurface3, 1e-6), update=True)

if __name__ == "__main__":
    surface_from_curves()
    start_display()

결과는 다음과 같다. 

3차원 모델링 코드
다음 파이썬 코드를 입력하고, 실행한다.

import sys
from math import cos, pi

from OCC.Core.BRepAlgoAPI import BRepAlgoAPI_Fuse
from OCC.Core.BRepFilletAPI import BRepFilletAPI_MakeFillet
from OCC.Core.BRepPrimAPI import BRepPrimAPI_MakeBox, BRepPrimAPI_MakeCylinder
from OCC.Display.SimpleGui import init_display
from OCC.Core.TColgp import TColgp_Array1OfPnt2d
from OCC.Core.gp import gp_Ax2, gp_Pnt, gp_Dir, gp_Pnt2d
from OCC.Extend.TopologyUtils import TopologyExplorer

display, start_display, add_menu, add_function_to_menu = init_display()

def fillet(event=None):  # 필렛 모델링
    display.EraseAll()
    box = BRepPrimAPI_MakeBox(gp_Pnt(-400, 0, 0), 200, 230, 180).Shape()  # 박스 생성
    fillet = BRepFilletAPI_MakeFillet(box)  # 필렛
    # Add fillet on each edge
    for e in TopologyExplorer(box).edges():  # 각 모서리 별로 필렛
        fillet.Add(20, e)  # 20만큼 수행

    blended_box = fillet.Shape()   # 필렛 모델링 모형 리턴

    p_1 = gp_Pnt(250, 150, 75)
    s_1 = BRepPrimAPI_MakeBox(300, 200, 200).Shape()
    s_2 = BRepPrimAPI_MakeBox(p_1, 120, 180, 70).Shape()
    fused_shape = BRepAlgoAPI_Fuse(s_1, s_2).Shape()         # 박스 2개 만들어 그룹핑 처리

    fill = BRepFilletAPI_MakeFillet(fused_shape)           # 그룹핑된 모형 필렛
    for e in TopologyExplorer(fused_shape).edges():
        fill.Add(e)

    for i in range(1, fill.NbContours() + 1):  
        length = fill.Length(i)
        radius = 0.15 * length
        fill.SetRadius(radius, i, 1)

    blended_fused_solids = fill.Shape()

    display.DisplayShape(blended_box)
    display.DisplayShape(blended_fused_solids, update=True)

def rake(event=None):       # 모서리 레이크 모델링
    display.EraseAll()
    # Create Box
    box = BRepPrimAPI_MakeBox(200, 200, 200).Shape()
    # Fillet
    rake = BRepFilletAPI_MakeFillet(box)
    expl = list(TopologyExplorer(box).edges())

    rake.Add(8, 50, expl[3])
    rake.Build()
    if rake.IsDone():
        evolved_box = rake.Shape()
        display.DisplayShape(evolved_box, update=True)
    else:
        print("Rake not done.")

def fillet_cylinder(event=None):  # 실린더 필렛 모델링
    display.EraseAll()
    # Create Cylinder
    cylinder = BRepPrimAPI_MakeCylinder(
        gp_Ax2(gp_Pnt(-300, 0, 0), gp_Dir(0, 0, 1)), 100, 200
    ).Shape()
    fillet = BRepFilletAPI_MakeFillet(cylinder)
    display.DisplayShape(cylinder, update=True)
    tab_point_2 = TColgp_Array1OfPnt2d(0, 20)
    for i in range(0, 20):
        point_2d = gp_Pnt2d(i * 2 * pi / 19, 60 * cos(i * pi / 19 - pi / 2) + 10)
        tab_point_2.SetValue(i, point_2d)
        display.DisplayShape(point_2d)

    expl2 = TopologyExplorer(cylinder).edges()
    fillet.Add(tab_point_2, next(expl2))
    fillet.Build()
    if fillet.IsDone():
        law_evolved_cylinder = fillet.Shape()
        display.DisplayShape(law_evolved_cylinder, update=True)
    else:
        print("fillet not done.")

def variable_filleting(event=None):
    a_pnt = gp_Pnt(350, 0, 0)
    box_2 = BRepPrimAPI_MakeBox(a_pnt, 200, 200, 200).Shape()
    a_fillet = BRepFilletAPI_MakeFillet(box_2)

    tab_point = TColgp_Array1OfPnt2d(1, 6)
    p_1 = gp_Pnt2d(0.0, 8.0)
    p_2 = gp_Pnt2d(0.2, 16.0)
    p_3 = gp_Pnt2d(0.4, 25.0)
    p_4 = gp_Pnt2d(0.6, 55.0)
    p_5 = gp_Pnt2d(0.8, 28.0)
    p_6 = gp_Pnt2d(1.0, 20.0)
    tab_point.SetValue(1, p_1)
    tab_point.SetValue(2, p_2)
    tab_point.SetValue(3, p_3)
    tab_point.SetValue(4, p_4)
    tab_point.SetValue(5, p_5)
    tab_point.SetValue(6, p_6)

    expl3 = list(TopologyExplorer(box_2).edges())

    a_fillet.Add(tab_point, expl3[9])
    a_fillet.Build()
    if a_fillet.IsDone():
        law_evolved_box = a_fillet.Shape()
        display.DisplayShape(law_evolved_box)
    else:
        print("aFillet not done.")
    display.FitAll()

def exit(event=None):
    sys.exit()

if __name__ == "__main__":
    add_menu("topology fillet operations")
    add_function_to_menu("topology fillet operations", fillet)
    add_function_to_menu("topology fillet operations", rake)
    add_function_to_menu("topology fillet operations", variable_filleting)
    add_function_to_menu("topology fillet operations", fillet_cylinder)
    add_function_to_menu("topology fillet operations", exit)
    start_display()

결과는 다음과 같다. 

마무리
OCC는 3차원 모델링에 필요한 기능을 제공한다. OCC는 역사가 매우 오랜된 솔리드 모델링 엔진이다. 개발은 C/C++로 코딩되었다. 처음에는 많은 오류가 있었고, 빌드환경을 구축하는 데 디펜던시 에러 등을 해결하는 데 많은 시간이 걸렸다. OCC는 파이썬 랩핑 라이브러리가 만들어지면서 많은 사람들이 편리하게 사용할 수 있게 되었다. pythonocc를 개발한 사람은 Thomas Paviot 박사로 3차원 캐드 모델링 분야를 연구하고, 관련된 기술을 꾸준히 개발하고 있다.
Piviot 박사

OCC 대부분 파라메터는 캐드, 계산 기하학에 사용되는 개념을 사용하므로, 사용전에 이를 공부할 필요가 있다.

레퍼런스

댓글 없음:

댓글 쓰기