본문 바로가기
블렌더

3D 블렌더 2.83과 파이썬 / 오퍼레이터로 팝업창을!

첫 게시글 보기 : https://itadventure.tistory.com/319

 

3D 블렌더 2.83 + 파이썬 스크립트와의 만남

3D 블렌더 프로그램에는 파이썬 스크립트 엔진이 내장되어 있는데요. 관련 스크립트를 통해 재미난 것들을 할 수가 있지요. 앞으로 이걸로 어디까지 할 수 있을지 알아 보는 시간을 가져보도록 �

itadventure.tistory.com

3D 블렌더의 모든 기능은 F3 단축키에서 만나볼 수 있는데요.
블렌더를 실행한 후 3차원 뷰에서 F3키를 치면 아래와 같은 팝업창을 만나 볼 수 있습니다.

화살표키를 아래로 쭉 내려보면 어마 무시한 내용들이 들어 있습니다.
아쉽게도 블렌더 내에서는 한글 타이핑이 안되서 유용하게 사용하기에는 좀 어려움이 있습니다.

한번 메모장에서 '섭디'를 타이핑한 다음 복사해서 돋보기 우측에 붙여넣기해보시겠어요?
그러면 아래와 같이 하나의 선택 내용이 등장할 겁니다.

마우스로 더블클릭하면 마치 섭디비전(Subdivision) 모디파이어를 적용한 것처럼 상자가 바뀌는데요.

섭디비전을 적용한 것처럼이 아니라, 실제 섭디비전이 적용이 된 것입니다.
화면 오른쪽의 모디파이어 아이콘 탭을 선택하면 아래와 같이 섭디비전 모디파이어가 적용된 것을 확인하실 수 있지요.

이렇게 F3 키를 치면 나오는 목록 하나 하나를 '오퍼레이터'라고 부릅니다.
온갖 UI 화면에서 이 오퍼레이터를 사용해서 기능을 구현하는 것이지요.

재미있는 것은 파이썬 스크립트를 통해 직접 오퍼레이터를 만들 수도 있다는 것입니다.

블렌더를 다시 새로 시작하고 큐브가 선택된 상태에서,
파이썬 스크립트 창을 준비하신 다음, 아래 스크립트를 입력해서 실행해 주세요.

import bpy

# print 내장 콘솔 출력 - 한글 지원
def print(data):
    window=bpy.context.window_manager.windows[0]
    screen = window.screen
    for area in screen.areas:
        if area.type == 'CONSOLE':
            bpy.ops.console.scrollback_append(
                {'window': window, 'screen': screen, 'area': area},
                text=str(data))

class CustomArrayOperator(bpy.types.Operator):
    # 오퍼레이터 아이디값[
    bl_idname = "object.custom_draw"
    # 팝업창 이름
    bl_label = "Arr 배열 복사"

    # 속성 정의
    x_repeat = bpy.props.IntProperty(name="갯수")

    def invoke(self, context, event):
        wm = context.window_manager
        return wm.invoke_props_dialog(self)

    def draw(self, context):
        layout = self.layout
        
        # 행 추가
        row = layout.row()
        # 입력 항목 - 속성 매칭
        row.prop(self, "x_repeat")
        
    def execute(self, context):    
        
        selected_objects=bpy.context.selected_objects
        if len(selected_objects) == 0:
            self.report({'ERROR'}, "오브젝트를 선택하세요")
            return {'FINISHED'}

        org_name=selected_objects[0].name;

        for x in range(1, self.x_repeat + 1):
            bpy.data.objects[org_name].select_set(True)
            bpy.ops.object.duplicate_move(
                OBJECT_OT_duplicate={"mode":'TRANSLATION'},
                TRANSFORM_OT_translate={"value":(x * 4, 0, 0)})
            bpy.ops.object.select_all(action='DESELECT')

        self.report({'INFO'}, "오브젝트가 복사되었습니다.")
        
        return {'FINISHED'}

bpy.utils.register_class(CustomArrayOperator)

# test call
bpy.ops.object.custom_draw('INVOKE_DEFAULT')

그러면 재미있는 일이 일어납니다. 바로 팝업창이 하나 짜잔 뜨게 되는데요.
아래와 같습니다.

한번 갯수를 5를 입력해볼까요? 그리고 OK 버튼을 누릅니다.

그러면 큐브가 아래와 같이 5개가 더 복사된 것을 보실 수 있습니다.

이 기능은 선택한 오브젝트 1개를 입력한 갯수만큼 복사해서 나열하는 기능인데요.
블렌더에 원래 있는 기능이 아닙니다. 금방 입력한 스크립트 때문에 작동하는 기능이지요.

이번에는 모든 오브젝트를 삭제하고, ( 3D 뷰 창에서  A 키 - Delete 키 )
원숭이 얼굴 오브젝트를 추가한 다음, ( Shift + A - 메쉬 - 원숭이 )

원숭이 머리가 선택된 상태에서

다시 한번 스크립틀 실행하고, 100을 입력, OK를 눌러 보세요

카메라를 돌려보면 원숭이 머리가 쭉쭉 뻗어나가도록 복사된 것을 보실 수 있습니다.

다시 모든 오브젝트를 삭제하고 원숭이 머리를 추가한 다음, 3D 뷰어 창의 빈 공간 아무 곳을 클릭해서
선택을 해제해 주세요

그리고 스크립트를 실행하여 다시 한번 100을 입력하고 OK 를 누르면,

엇 이번에는 좀 반응이 다릅니다. 복사가 되지 않고,
화면 아래 잠깐 오류 메시지가 뜨는데요.
"오브젝트를 선택하세요"라고 뜹니다.

대략 이런 기능입니다.
자, 기능 소개가 끝났으니 스크립트 분석에 들어가 봅시다.
소스가 이전 내용보다는 꽤 긴 편이지만 하나하나 뜯어보면서 자주 보면 익숙해지고,
익숙해지면 그리 어렵진 않으실 겁니다.

첫줄의 import bpy 는 이미 아실테니 넘어가고,
다음에 나오는 print 함수는 블렌더에서 자주 쓰는 디버깅용 print() 함수를 블렌더 내장 콘솔창에 출력하도록 해주는 
함수 정의입니다. 나중에 애드온으로 개조할 예정이니 이 부분은 넘어가셔도 좋습니다.

# print 내장 콘솔 출력 - 한글 지원
def print(data):
    window=bpy.context.window_manager.windows[0]
    screen = window.screen
    for area in screen.areas:
        if area.type == 'CONSOLE':
            bpy.ops.console.scrollback_append(
                {'window': window, 'screen': screen, 'area': area},
                text=str(data))

이어서 나오는 클래스가 바로 오퍼레이터입니다.

class CustomArrayOperator(bpy.types.Operator):
        :

팝업창을 띄우는 오퍼레이터는 보통 그 틀이 아래와 같습니다.
이 중에서 (bpy.types.Operator) 이라는 부분은 블렌더의 기본 오퍼레이터 기능을 상속받는 것이며,
오퍼레이터를 만들때 반드시 필요한 부분입니다.

class 오퍼레이터클래스명(bpy.types.Operator):
    # 각종 요소 초기화

    def invoke(self, context, event):
        wm = context.window_manager
        return wm.invoke_props_dialog(self)

    def draw(self, context):
        #화면 구성
        
    def execute(self, context):    
        #실행        
        return {'FINISHED'}

def 라고 된 부분은 보통 함수라고 부르는데요.
클래스 안에 있어서 클래스 함수라고도 부릅니다.

우선 제일 처음 각종 요소 초기화 부분은 아래 스크립트로 구성되어 있는데요.

    # 오퍼레이터 아이디값[
    bl_idname = "object.custom_draw"
    # 팝업창 이름
    bl_label = "Arr 배열 복사"

    # 속성 정의
    x_repeat = bpy.props.IntProperty(name="갯수")

bl_idname 은 유일한 이름이 되어야 하며, 보통 독자적으로 이름을 지어주는 것이 일반적입니다.
bl_label 은 팝업창 제목을 의미합니다.

F3 키를 쳐서 오퍼레이션을 검색할 때도 이 명칭이 사용되니,
첫 글자는 영문으로 정해주시면 편리하지요 :)

다음으로 x_repeat 라는 항목이 등장하는데요.

    # 속성 정의
    x_repeat = bpy.props.IntProperty(name="갯수")

바로 여기에 들어갈 겁니다. bpy.props.IntProperty 라고 정의해주었는데요.
블렌더 팝업창 화면에서 정수만 입력하도록 제한하는 옵션입니다.
하지만 이렇게 정의한 것으로는 아직 팝업창 인터페이스에 포함된 상태는 아니고
팝업창에 넣는 부분은 따로 등장합니다.

다음으로 등장하는 코드는 invoke 인데요.
invoke 는 '호출'이라는 의미입니다.
즉 오퍼레이션을 실행하면 이 함수가 호출되는 것이지요.
이 형태는 항상 고정형태로 사용됩니다.

    def invoke(self, context, event):
        wm = context.window_manager
        return wm.invoke_props_dialog(self)

다음으로 나오는 부분이 바로 UI, 유저 인터페이스(User Interface)입니다.

    def draw(self, context):
        layout = self.layout
        
        # 행 추가
        row = layout.row()
        # 입력 항목 - 속성 매칭
        row.prop(self, "x_repeat")

layout = self.layout 은 자기자신 즉, 팝업창의 화면구성을 제어하는 레이아웃을 받아오는 부분입니다.

layout = self.layout


다음으로 레이아웃에 한 줄을 추가해서 row 라는 변수에 담아준 다음,

row = layout.row()

아까 전에 정의했던 x_repeat 정수 입력 항목을 row 에 할당해 주는 것이지요.
그러면 화면에 정수 입력 상자가 추가가 됩니다.

row.prop(self, "x_repeat")

여기까지가 화면 부분이고 다음으로는 execute 함수가 등장하는데요.

    def execute(self, context):    
        
        selected_objects=bpy.context.selected_objects
        if len(selected_objects) == 0:
            self.report({'ERROR'}, "오브젝트를 선택하세요")
            return {'FINISHED'}

        org_name=selected_objects[0].name;

        for x in range(1, self.x_repeat + 1):
            bpy.data.objects[org_name].select_set(True)
            bpy.ops.object.duplicate_move(
                OBJECT_OT_duplicate={"mode":'TRANSLATION'},
                TRANSFORM_OT_translate={"value":(x * 4, 0, 0)})
            bpy.ops.object.select_all(action='DESELECT')

        self.report({'INFO'}, "오브젝트가 복사되었습니다.")
        
        return {'FINISHED'}

execute 함수는 OK 버튼을 누를 때 실행할 기능을 정의하는 것입니다.

현재 선택된 오브젝트를 selected_objects 변수에 저장해 놓는데, 복수 선택할 수 있기 때문에 배열로 보관됩니다.

selected_objects=bpy.context.selected_objects

하지만 선택된 오브젝트가 하나도 없을 경우라면 화면 하단에 '오브젝트를 선택하세요'라는 오류 메시지를 띄우고 기능을 끝내는 것이지요.

        if len(selected_objects) == 0:
            self.report({'ERROR'}, "오브젝트를 선택하세요")
            return {'FINISHED'}

선택된 오브젝트가 있다면 첫번째 오브젝트의 이름을 받아온 다음에

        org_name=selected_objects[0].name;

입력횟수만큼 반복 동작을 해 줍니다. 10을 입력하면 10번 복사하기 위해서이지요.

        for x in range(1, self.x_repeat + 1):

오브젝트는 복사를 하고 나면 복사한 오브젝트로 선택이 옮겨져 버립니다.
그래서 복사전 항상 원본을 선택해 준 다음에

            bpy.data.objects[org_name].select_set(True)

오브젝트를 복사, 새로운 위치에 놓습니다.
TRANSFORM_OT_translate 가 바로 상대 좌표를 의미하는데요.
복사한 오브젝트를 x축으로 x * 4 위치로 이동하니까 복사후 4, 8, 12, 16, .. 이런식으로 이동합니다.

            bpy.ops.object.duplicate_move(
                OBJECT_OT_duplicate={"mode":'TRANSLATION'},
                TRANSFORM_OT_translate={"value":(x * 4, 0, 0)})

복사 후에는 선택이 마지막 오브젝트로 가 있을 겁니다. 선택을 취소해주어야지,
취소를 안해주면 중복으로 복사가 될 수 있어 모든 선택을 해제해 줍니다

            bpy.ops.object.select_all(action='DESELECT')

이제 복사를 모두 마치고 난 다음에, 화면하단에 완료 메시지를 띄워줍니다.
아마도 눈치채지 못하셨을 수도 있지만요 :)

self.report({'INFO'}, "오브젝트가 복사되었습니다.")

그 다음으로 등장하는 return 문은 항상 동일한데요. 다른 경우가 있는지는 알게 되면 공유하겠습니다.
이렇게 안 써주면 오류가 발생하더라구요.

        return {'FINISHED'}

이어서 클래스 정의를 마치고 아래 코드가 이어지는데요.

bpy.utils.register_class(CustomArrayOperator)

사실 위 코드가 없으면 앞에서 정의한 클래스는 아무 소용이 없습니다.
클래스는 단지 설계도일 뿐이지 실체가 아니기 때문이지요.
바로 이 명령어가 설계도대로 '로보트를 만들어!' 이런 의미입니다.
'클래스를 등록'해준다고 표현하면 정확한데요.
이렇게 등록된 오퍼레이터 유형의 클래스가 바로 오퍼레이터가 되는 겁니다.
각종 요소 초기화는 바로 이 부분에서 실행이 됩니다.

F3 키를 누르면 찾아볼 수도 있고요, 아직 해보진 않았지만 아마도
다른 UI 화면에서 오퍼레이터를 호출해 사용할 수도 있을 것으로 예상합니다.

그 다음으로 나오는 마지막 한 줄은 그냥 이 클래스를 한번 작동시켜보는 테스트 코드입니다.

bpy.ops.object.custom_draw('INVOKE_DEFAULT')

오퍼레이터를 코드로 호출할 때는 bpy.ops 뒤에 앞에서 정의한 bl_idname 의 값을 적어주면 되는데요.
bl_idname 값이 "object.custom_draw" 이면 따옴표를 제외한 object.custom_draw 를 그대로 적어준 다음,
마지막에 ('INVOKE_DEFAULT') 까지 적어주면 됩니다.
그러면 클래스의 invoke 함수가 실행되고 invoke 는 draw 를 실행해서 다이얼로그를 보여준 다음,
OK 버튼을 누르면 execute() 함수가 실행되는 거지요.

필요하신 분에게 도움이 되셨는지요 ?
오늘도 방문해주셔서 감사합니다. :)

다음 게시글 : https://itadventure.tistory.com/330

 

3D블렌더2.83+파이썬+뷰어UI 만들기

첫 게시글 가기 : https://itadventure.tistory.com/319 3D 블렌더 2.83 + 파이썬 스크립트와의 만남 3D 블렌더 프로그램에는 파이썬 스크립트 엔진이 내장되어 있는데요. 관련 스크립트를 통해 재미난 것들을

itadventure.tistory.com

 

반응형