언어:

Python 을 사용한 에디터 스크립팅

언리얼 엔진

언리얼 에디터에서 Python 사용을 시작하는 방법 설명서입니다.

왜 Python 인가?

Python 은 최근 몇 년 사이, 특히 미디어 및 엔터테인먼트 업계의 3D 앱 사이 제작 파이프라인과 상호 운용성을 위한 공용어가 되었습니다. Python 을 지원하는 앱이 광범위한 것도 한 몫 합니다. 제작 파이프라인의 복잡도와 관련 앱 수가 계속 많아지면서, 공통 스크립팅 언어가 있으면 대규모 애셋 관리 시스템을 만들고 유지하기가 수월해집니다.

이러한 외부 고려사항이나 다른 앱으로 작업할 필요가 없어도, Python 은 언리얼 에디터의 워크플로 자동화를 위한 탁월한 선택입니다. 프로그래밍 초보가 시작하기에 상대적으로 쉽고, PySide 같은 모듈을 통해 복잡하고 완전한 기능을 갖춘 유저 인터페이스를 만들 수 있는 기능을 제공하며, 커뮤니티에 무료로 사용할 수 있는 유용한 모듈도 많아 개발자의 삶이 편해집니다.

언리얼 에디터에서 Python 으로 할 수 있는 작업은 다음과 같습니다.

  • 언리얼 에디터를 조직에서 사용하는 다른 3D 앱과 연결하는 대규모 애셋 관리 파이프라인 또는 워크플로를 구축합니다.

  • 언리얼 에디터에서 스태틱 메시의 LOD 생성과 같은 시간 소모가 많은 애셋 관리 작업을 자동화합니다.

  • 레벨에 콘텐츠를 절차적으로 배치합니다.

  • Python 으로 직접 만든 UI 로 언리얼 에디터를 제어합니다.

Python 을 사용하도록 프로젝트 설정

언리얼 에디터의 Python 지원은 Python Editor Script (Python 에디터 스크립트) 플러그인이 필요합니다. 에디터에서 Python 스크립트를 실행하기에 앞서, 현재 프로젝트에서 이 플러그인을 활성화해야 합니다.

현재 각 프로젝트마다 플러그인을 별도로 활성화해야 합니다.

플러그인 활성화 방법입니다.

  1. 프로젝트를 열고, 메인 메뉴에서 Edit > Plugins (편집 > 플러그인)을 선택합니다.

  2. 플러그인 창에서 Scripting (스크립팅) 섹션으로 이동합니다.
    오른쪽 패널에서 Python Editor Script Plugin (Python 에디터 스크립트 플러그인)을 찾은 뒤 그 옆의 Enabled (활성화) 박스를 체크합니다.
    Python 에디터 스크립트 플러그인 활성화
    Editor Scripting Utilities (에디터 스크립팅 유틸리티) 플러그인도 활성화하면, 흔한 에디터 작업을 위한 단순화 API 를 쓸 수 있습니다. 자세한 내용은 에디터 자동화와 스크립팅 문서를 참고하세요.

  3. 에디터를 재시작합니다.

Python 2.7

Python 에디터 스크립트 플러그인에는 Python 2.7 임베디드 버전이 들어있습니다.

컴퓨터에 별도로 Python 을 설치할 필요가 없다는 뜻입니다.

언리얼은 기본으로 Python 2.7 을 사용하는데, 현재 VFX Reference Platform 의 중요 부분이기 때문입니다. 기준 플랫폼이 3.x 버전으로 올라가면 따라갈 계획입니다.
그 동안 다른 버전의 (3.x 포함) Python 을 사용해야 하는 경우, UE_PYTHON_DIR 환경 변수에 임베딩하려는 설치 버전을 지정한 뒤, 소스에서 언리얼 엔진을 리빌드하면 됩니다.

언리얼 에디터에서 Python 코드를 실행하는 방법

언리얼 에디터에서 Python 스크립트를 실행할 수 있는 방법은 여러가지인데, 각각 사용 시나리오가 약간씩 다르게 설계되었습니다. 어느 것이든 필요에 따라 선택하면 됩니다.

블루프린트와 달리 Python 환경은 언리얼 에디터 내부 에서만 사용 가능하며, 에디터에서 플레이, 독립형 게임, 쿠킹한 실행파일 등 언리얼 엔진 다른 모드에서 프로젝트를 실행할 때는 사용할 수 없습니다. 즉 에디터 자동화 및 스크립트를 짜거나 애셋 제작 파이프라인을 만들 때는 Python 을 마음대로 사용할 수 있지만, 현재 게임플레이 스크립트 언어로는 사용할 수 없다는 뜻입니다.

출력 로그의 Python 콘솔

언리얼 에디터의 콘솔 입력줄을 언리얼 콘솔 명령이 아닌 Python 코드를 받도록 전환할 수 있습니다.

Python 콘솔

위에서 처럼 출력 로그 패널에서, 또는 ~ 키를 눌러 콘솔 입력줄을 띄울 때 가능합니다.

콘솔이 Python 모드일 때:

  • 이 콘솔에 Python 코드 줄을 입력하여 에디터가 각각을 즉시 실행하도록 할 수 있습니다. 명령 창에 대화식 Python 콘솔을 사용하는 것과 똑같습니다. 이는 Python 코드를 줄 단위로 실행하는 유일한 방법입니다. 아래 나열된 다른 모든 접근 방식은 지정한 스크립트 파일을 실행합니다.

  • Shift+Enter 로 각 줄을 분리해서 입력하거나, 텍스트 에디터에서 복사하여 붙여넣은 여러 줄 코드를 한 번에 실행할 수 있습니다.

  • 콘솔에 파일명만 입력해도 Python 스크립트 파일을 실행할 수 있습니다. Python 스크립트에 추가 명령줄 인수가 필요한 경우, 스크립트 이름 뒤에 붙여줍니다.

Python 내장 print 함수의 출력 역시 출력 로그 패널로 전송합니다.

py 콘솔 명령

일반 콘솔에서 py 명령을 사용하면 나머지 줄을 Python 코드로 실행할 수 있습니다. 위에 설명한 Python 콘솔에 입력했을 때와 똑같습니다.

예를 들어 다음 명령은 지정한 스크립트 파일을 실행합니다.

The py console command

에디터를 시작할 때 ExecCmd 명령줄 파라미터 값으로 이 명령을 실행하는 것은 권장하지 않습니다. 그러면 에디터 환경이 준비되기 전, 예를 들어 스타트업 레벨 로드가 끝나기도 전에 스크립트가 실행될 수 있습니다. 더 나은 옵션은 아래 섹션을 참고하세요.

파일 메뉴

언리얼 에디터 메인 창의 File (파일) 메뉴는 Python 스크립트 파일 실행에 사용할 수 있는 새 옵션을 제공합니다.

  • 이전에 실행한 적 없는 컴퓨터에서 새 스크립트 파일을 찾으려면 Execute Python Script (Python 스크립트 실행)을 사용하세요.

  • 이전에 실행한 스크립트를 재실행하려면 Recent Python scripts (최근 Python 스크립트) 목록을 사용하세요. 디스크에서 파일을 다시 읽으므로 중간에 스크립트를 변경한 경우 새 버전을 실행합니다.

파일 메뉴에서 Python 실행

명령줄

명령줄 또는 스크립트에서 언리얼 에디터를 시작하는 경우, 명령줄 인수에 Python 스크립트 파일을 지정할 수 있습니다. Python 스크립트에 추가 명령줄 인수가 필요한 경우, 스크립트 이름 뒤에 붙여줍니다.

명령줄에서 Python 스크립트를 실행하는 방법은 두 가지입니다. 두 방법 모두 Python 스크립트 실행 직후 에디터가 종료됩니다.

옵션 1: 전체 에디터

이 방법에서는 전체 언리얼 에디터를 실행하고, 지정한 프로젝트를 열어, 기본 스타트업 레벨을 로드한 뒤, 모두 로드와 준비가 끝나면 Python 스크립트를 실행합니다. 이 방법은 로드 시간이 조금 걸릴 수 있는 레벨이나 프로젝트의 콘텐츠를 스크립트로 상호작용할 필요가 있을 때 좋습니다.

명령줄에 ExecutePythonScript 인수를 추가하고, 그 값을 실행할 Python 스크립트로 설정합니다. 예:

> UE4Editor-Cmd.exe MyProject.uproject -ExecutePythonScript="c:\my_script.py"

위 방법은 프로젝트에 Editor Scripting Utilities (에디터 스크립팅 유틸리티) 플러그인 활성화가 필요합니다.

옵션 2: 커맨드렛

이 방법에서는, UI 또는 렌더링 없이 최소 환경에서 에디터를 시작합니다. 이 방법은 실행이 매우 빠르지만, 스크립트가 상호작용하려는 레벨이나 다른 애셋 유형을 로드하기가 까다로울 수 있습니다.

명령줄에 다음과 같이 인수를 추가합니다. -run=pythonscript -script=<script_file>

예:

> UE4Editor-Cmd.exe -run=pythonscript -script="c:\\my_script.py"

init_unreal.py 파일

에디터가 사용하도록 설정된 경로에서 (아래 "언리얼 에디터의 Python 경로" 참고) init_unreal.py 라는 스크립트 파일을 발견하면 그 즉시 자동 실행합니다.

프로젝트나 플러그인 작업을 할 때 어떤 콘텐츠 작업을 하는 사람이면 에디터를 시작할 때마다 똑같은 초기화 코드를 사용해야 하는 상황에 좋은 방법입니다. 이 이름으로 된 스크립트 안에 초기화 코드를 넣은 뒤 그 프로젝트 또는 플러그인 안의 Content/Python 폴더에 넣으면 됩니다.

스타트업 스크립트

프로젝트 세팅에서 프로젝트를 열 때마다 실행할 Python 스크립트를 원하는 만큼 지정할 수 있습니다. 에디터는 기본 스타트업 레벨 로드가 끝나면 이 스크립트를 실행합니다.

Edit > Project Settings... (편집 > 프로젝트 세팅...)을 선택합니다. Plugins (플러그인) 목록 아래 Python 을 선택합니다. 그런 다음 스크립트를 Startup scripts (스타트업 스크립트) 세팅에 추가합니다.

Python 스타트업 스크립트

완료되면 언리얼 에디터를 재시작합니다. 다음 번 에디터가 프로젝트를 로드할 때, 새로운 스타트업 스크립트를 실행할 것입니다.

언리얼 에디터의 Python 경로

위 방법으로 상대 경로를 사용해서 Python 스크립트를 실행할 때, 또는 스크립트 중 하나에서 import 명령으로 다른 스크립트 모듈을 가져올 때, 실행 또는 임포트하는 스크립트는 Python 환경의 sys.path 변수에 나열된 경로 어디든 있을 수 있습니다.

언리얼 에디터는 이 sys.path 목록에 다음과 같은 여러 경로를 자동 추가합니다.

  • 프로젝트 폴더 아래 Content/Python 서브 폴더.

  • 언리얼 엔진 설치 폴더 아래 Content/Python 서브 폴더.

  • 각 활성화 플러그인 폴더 아래 Content/Python 서브 폴더.

  • 사용자 디렉터리 내 Documents/UnrealEngine/Python 폴더. 예를 들어 Windows 10 의 경우, C:/Users/Username/Documents/UnrealEngine/Python 입니다.

이 목록에 다른 경로를 직접 추가하는 방법은 다음과 같습니다.

  • 프로젝트 세팅 방법입니다. Edit > Project Settings... (편집 > 프로젝트 세팅...)을 선택합니다. Plugins (플러그인) 목록 아래, Python 을 선택합니다. 그런 다음 Additional Paths (추가 경로) 세팅에 경로를 추가합니다. 완료되면 언리얼 에디터를 재시작합니다.
    추가 Python 경로

  • PYTHONPATH 환경 변수 값에 경로를 추가한 뒤 언리얼 에디터를 재시작합니다.

  • Python 스크립트 또는 Python 콘솔의 sys.path 목록에 경로를 직접 추가합니다.

언리얼 에디터 Python API 관련

Python 에디터 스크립트 플러그인에는 언리얼 에디터, 프로젝트의 애셋, 레벨의 콘텐츠와 상호작용하는 데 사용할 수 있는 다양한 클래스와 함수가 노출되어 있습니다. 이 API 는 unreal 모듈에 모두 들어있습니다. 액세스하려면 에디터의 Python 환경에서 실행하는 Python 스크립트 시작 부분에서 이 모듈을 임포트합니다.

import unreal

unreal 모듈은 C++ 에서 블루프린트로 에디터 환경에 노출한 거의 모든 것을 노출합니다. 미리 생성된 것은 아닙니다. 에디터에서 블루프린트로 사용할 수 있는 것은 무엇이든 자동 반영합니다. 언리얼 에디터에서 새 플러그인을 활성화하면, 그 플러그인에서 블루프린트로 노출한 것도 Python 에서 사용할 수 있게 됩니다. 프로젝트에서 작성하고 블루프린트로 노출시킨 C++ 코드도 마찬가지입니다.

Python API 는 가급적 Python 개발자에게 친숙한 방식으로 네이티브 언리얼 API 를 노출하기 위해 모든 노력을 기울입니다. 예:

  • 단순 데이터 유형은 필요할 때마다 Python <-> 네이티브 유형을 투명하게 변환합니다.
    Python list, set, dict 를 전달하면, 언리얼 배열, 세트, 맵으로 자동 변환합니다. API 함수가 반환한 list, set, dict 값을 구할 때, 실제로 언리얼 클래스의 인스턴스를 구하지만, 해당 API 는 기본 Python list, set, dict 유형과 완전히 일치합니다.

  • Python 클래스는 자신이 나타내는 네이티브 유형과 동일한 상속 계층 구조를 유지합니다. 즉, isinstance(), type() 같은 내장 Python 함수를 사용하여 오브젝트가 지정한 클래스에서 파생 또는 일치하는지 테스트할 수 있다는 뜻입니다.

  • API 는 언리얼의 C++ 및 블루프린트 명명 규칙과 Python 명명 규칙 사이 균형을 유지하려 노력합니다. Python API 의 클래스와 오브젝트는 블루프린트에서도 이름이 같습니다. 보통 네이티브 유형에서 (UT 와 같은) 접두사를 뺀 것과 같습니다. 함수와 프로퍼티 이름은 snake_case 와 같이 소문자로 자동 노출합니다. 즉 예를 들어 함수 호출은 unreal.StaticMeshActor.get_actor_transform() 같은 식입니다. 열거형 값 이름은 SNAKE_CASE 와 같이 대문자로 자동 노출합니다.

  • 노출되는 모든 함수는 순서 파라미터, 아니면 순서 없는 네임드 파라미터로 받을 수 있습니다. 예를 들어, 다음 두 함수 호출은 정확히 같습니다.

    unreal.EditorLevelLibrary.join_static_mesh_actors(list_of_actors, my_options)
    unreal.EditorLevelLibrary.join_static_mesh_actors(join_options=my_options, actors_to_join=list_of_actors)

API 레퍼런스

언리얼 Python API 가 노출하는 모든 클랫스와 함수 관련 자세한 내용은, 아래 API Reference 를 참고하세요.

Unreal Editor Python API Reference

API Reference 는 플러그인이 Python 으로 노출할 수 있는 모든 것을 망라한 목록은 아닙니다. API Reference 에 포함되지 않은 추가 플러그인을 설치했는데 그 스크립팅 기능이 Python 에 노출되는 방식을 확인해야 하는 경우, 필요한 플러그인의 문서가 들어있는 API Reference 로컬 버전을 생성하면 됩니다. 자세한 방법은 언리얼 엔진 설치 폴더의 Engine\Plugins\Experimental\PythonScriptPlugin\SphinxDocs 아래 readme 파일을 참고하세요.

Python API 모범 사용 사례

여기서는 Python API 를 사용할 때 명심해야 할 몇 가지 사항에 대해 설명합니다.

애셋 작업

프로젝트의 애셋 작업이 필요한 경우, 항상 언리얼 Python API 의 함수를 사용하세요. 절대 Python 내장 파일 관리 모듈을 사용해서 디스크의 애셋 파일을 직접 작업하면 안됩니다. 예를 들어, 애셋을 다른 폴더로 이동해야 하는 경우, os.rename 또는 shutil.move 와 같은 Python 함수는 사용하지 마세요. 이 규칙을 지키지 않으면 언리얼 프로젝트와 애셋에 들어있는 내부 콘텐츠 레퍼런스가 깨질 수 있습니다.

그 대신 Editor Scripting Utilities (에디터 스크립팅 유틸리티) 플러그인에 들어있는 unreal.EditorAssetLibrary API 또는 언리얼 Python API 에 내장된 unreal.AssetTools 클래스를 사용할 것을 권장합니다.

에디터 프로퍼티 변경

Python 을 사용해서 프로젝트의 오브젝트에 액세스하고 그 오브젝트의 구성 프로퍼티 다수를 프로그래밍적으로 설정할 수 있습니다. 예를 들어, Python 스크립트가 현재 레벨의 스태틱 메시 액터에 액세스하여 액터가 대미지를 받을 수 있는지, 게임에서 숨겨야 하는지와 같은 프로퍼티를 설정할 수 있습니다. 아니면 그 스태틱 메시 컴포넌트를 구해 그 컴포넌트에서 라이트매스 세팅이나 심지어 연결된 스태틱 메시 애셋같은 프로퍼티도 설정할 수 있습니다.

이 프로퍼티를 Python 에 노출시킬 수 있는 방법은 두 가지입니다.

  • BlueprintReadOnly 또는 BlueprintReadWrite 플래그가 있는 항목은 오브젝트에 단순 프로퍼티로 노출됩니다.
    다른 Python 오브젝트 프로퍼티를 액세스할 때처럼 이 프로퍼티도 읽고 수정할 수 있습니다.

  • ViewAnywhere 또는 EditAnywhere 플래그가 있는 항목은 "에디터 프로퍼티"로 노출됩니다.
    모든 오브젝트에 노출된 특수한 함수 짝 set_editor_property(), get_editor_property() 를 사용해서 이 값을 읽고 쓸 수 있습니다.

각 클래스에 대한 API Reference 에서, 클래스 설명 바로 뒤의 Editor Properties 목록을 찾을 수 있습니다. 이 모두가 set_editor_property(), get_editor_property() 함수로 설정하고 구할 수 있는 값입니다. 오브젝트의 구성 프로퍼티를 설정 또는 구해야 할 때마다 이 목록을 먼저 확인하여 원하는 프로퍼티가 목록에 있는지 확인하세요.

  • 오브젝트 프로퍼티와 에디터 프로퍼티 양쪽에 노출된 값을 읽어야 할 때, 프로퍼티를 직접 액세스한 결과는 보통 get_editor_property() 함수를 호출한 것과 같습니다. 하지만 get_editor_property() 함수는 종종 Python 오브젝트에 직접 노출되지 않은 프로퍼티에 액세스할 수 있습니다.

  • 오브젝트 프로퍼티와 에디터 프로퍼티 양쪽에 노출된 값을 설정해야 할 때, 대부분의 경우 오브젝트에서 값을 직접 설정하기 보다는 set_editor_property() 함수로 값을 설정해야 합니다. UI 의 프로퍼티를 조정할 때 에디터는 편집 사전/사후 변경 작업과 같은 내부적인 추가 작업을 하기도 합니다. 이 작업은 보통 어떤 방식으로든 선택한 사항에 반응하고, 에디터 UI 와 게임 월드 오브젝트의 동기화 상태를 유지합니다. 그런데 Python 오브젝트에서 직접 프로퍼티를 수정하면, 이 에디터 코드를 자동 실행하지 않습니다. 반면 set_editor_property() 를 호출하여 프로퍼티 상태를 설정하면, 에디터 UI 의 디테일 패널에서 세팅을 변경할 때와 마찬가지로 이 편집 사전/사후 코드를 발동시킵니다.

예를 들어 Media Player (미디어 플레이어) 오브젝트에는 Play on Open (열리면 재생) 세팅이 있습니다.

Details

이 세팅은 play_on_open 클래스 멤버의 unreal.MediaPlayer 클래스에 노출되어 있습니다.

import unreal
obj = unreal.MediaPlayer()
# 프로퍼티를 직접 수정하는 것은
# 에디터 UI 에서 세팅 변경과 결과가 다를 수 있습니다.
# 보통 다음과 같이 이 값을 직접 설정하는 것은 피하는 것이 좋습니다.
obj.play_on_open = True
# 이런 식의 프로퍼티 접근은 
# 에디터 UI 에서 세팅 변경과 결과가 똑같습니다.
obj.set_editor_property("play_on_open", True)
# 두 방식 모두 값 읽기 결과는 같습니다.
# 클래스가 프로퍼티를 양쪽 방식으로 노출한 경우, 다음과 같습니다.
play_value = obj.play_on_open
play_value = obj.get_editor_property("play_on_open")

가급적 언리얼 유형을 사용하세요

3D 좌표 조작이나 수학 연산용 클래스처럼 언리얼 Python API 에서 사용할 수 있는 유틸리티가 필요할 때는 직접 구현하기 보다는 언리얼 유틸리티를 사용하는 것이 좋습니다. 언리얼 버전은 엔진 환경에서 최상의 퍼포먼스를 내도록 최적화되어 있습니다.

예를 들어 3D 공간의 좌표를 조작해야 할 때는 unreal.Vector 클래스를 사용하세요.

import unreal
v1 = unreal.Vector()
v1.x = 10
v2 = unreal.Vector(10, 20, 30)
v3 = (v1 + v2) * 2
print v3

로그와 피드백

언리얼 오브젝트는 코드에서 사용할 수 있는 함수를 노출하여 로그, 경고, 오류 메시지로 전송할 때 모든 엔진과 에디터 서브시스템이 사용하는 것과 똑같은 메시징 시스템을 통해 전송합니다. 스크립트에서 사용자에게 메시지를 전송해야 할 때면 이 표준화된 로깅 프레임워크를 사용할 것을 권장합니다.

  • 정보성 메시지는 unreal.log() 를 사용하세요. 편의를 위해 Python print() 함수 역시 내부적으로 unreal.log() 를 통하도록 구현했습니다.

  • 잠재적인 문제 경고는 unreal.log_warning() 를 사용하세요.

  • 스크립트가 예상대로 실행되지 못하게 하는 심각한 문제는 unreal.log_error() 를 사용하세요.

이 메시지는 다른 서브시스템이 전송한 메시지와 함께 Output Log (출력 로그) 패널에 나타납니다.

Python 로그 메시지

실행 취소와 다시 실행 지원

스크립트는 언리얼 에디터에 내장된 실행 취소 / 다시 실행 시스템을 전부 활용할 수 있습니다.

정의한 트랜잭션 에는 여러 개의 Python 연산을 포함할 수 있습니다. 이 트랜잭션을 사용하면 대규모 작업이나 여러 오브젝트에 대한 작업을 실행 취소 / 다시 실행 시스템의 한 항목에 묶을 수 있습니다. 보통 스크립트가 여러 오브젝트를 일정하게 변경하려는 경우, 변경할 때마다 실행 취소 / 다시 실행 히스토리에 항목을 별도로 만들기 보다는, 하나의 항목으로 모든 오브젝트에 대한 변경 전부를 실행 취소하는 것이 좋습니다.

트랜잭션 정의는 unreal.ScopedEditorTransaction 범위를 사용합니다. 예를 들어 다음 코드를 실행하면:

import unreal
obj = unreal.MediaPlayer()
with unreal.ScopedEditorTransaction("My Transaction Test") as trans:
    obj.set_editor_property("play_on_open", True)
    obj.set_editor_property("vertical_field_of_view", 60)

에디터의 Undo History (실행 취소 히스토리) 패널에 이제 트랜잭션이 이름별로 나열됩니다.

실행 취소 히스토리

일반적으로 범위가 지정된 트랜잭션에는 에디터 UI 에서도 실행 취소할 수 있는 작업이면 무엇이든 들어갈 수 있습니다. 하지만 모든 에디터 작업을 실행 취소할 수 있는 것은 아닙니다. 예를 들어 에디터 UI 에 모델 임포트는 실행 취소할 수 없으므로, unreal.ScopedEditorTransaction 안에서 모델을 임포트해도 예상대로 작동하지 않을 수 있습니다.

느린 작업의 진행 대화창

스크립트의 한 작업에 여러 애셋 또는 액터가 관여된 경우, 완료하는 데 시간이 약간 걸릴 수 있습니다. 하지만 언리얼 에디터가 Python 스크립트를 실행하는 동안 그 UI 는 다른 사용자 상호작용에 막힙니다. 사용자에게 큰 작업의 진행 상황에 대한 정보를 제공하고 에디터가 멈추거나 중단되는 것을 방지하려면, unreal.ScopedSlowTask 범위를 사용하면 됩니다.

예:

import unreal
total_frames = 100
text_label = "Working!"
with unreal.ScopedSlowTask(total_frames, text_label) as slow_task:
    slow_task.make_dialog(True)               # 대화창이 보이지 않은 경우 표시합니다.
    for i in range(total_frames):
        if slow_task.should_cancel():         # 사용자가 UI 에서 취소를 누른 경우 true 입니다.
            break
        slow_task.enter_progress_frame(1)     # 한 프레임 진행합니다.
                                            # 원한다면 이 호출에서 대화창 텍스트를 업데이트할 수도 있습니다.
        ...                                   # 여기서 현재 프레임 작업을 합니다!

범위 지정 느린 작업의 진행 바