본문 바로가기

Script/MaxScript

FBX EXPORTER & Anim for Unity

FBX 익스포트 (2017버전 기준)

  1. 스킨적용된 메쉬 익스포트
  2. 한 파일안의 애니메이션을  특정구간별 익스포트
  3. 마지막으로 선택한 디렉토리 및 모션 값을 저장 하거나 불러오기
  4. 버그수정
    • 모디파이어 패널이 아닐때(skin modifier 못찾는것) selectObj클릭시 오류수정
    • SelectObj 클릭시 본, 헬퍼 안보이게 한것 수정.
    • selectDir 클릭후 폴더를 선택하지 않고 창을 닫을떄 오류수정
    • FrameStart 최대가 100인것 수정
    • 모션 리스트 없거나 1나만 있을떄 List_컬러체킹용 클릭시 오류수정, 개선
  5. 맥스 뷰포트에서 새로운 창 띄우고 거기에서 정해진 프레임별 애니선택 및 체크 할수 있게 제작
  6. 배치용 익스포트

사용 예시

/*
FBX Export maxscript tool
Copyright (C) 2015-2018, JinKyu Kim
This program is distributed in the hope that it will be useful.

------------------------------------------------------------------------------
-- FBX Export split animation clips in maxfile
-- By Jinkyu Kim (http://chakanz.tistory.com/, contact : chakan0000@gmail.com)
-- Created On: 2015-04-08
-- tested using  Max 2017
-------------------------------------------------------------------------------
*/

global FBX_Export 

try
    (   makedir (   (getdir #scripts) + @"\tmp\") ) catch   (print "error~~~~~~~~~~~~~")
    
    
-- 익스포트될 디렉토리 저장.
    Global inifilepath  =   (getdir #scripts )+ @"\tmp\Export_Setting.ini"

-- 배치용 디렉토리 저장.
    Global iniSourceFilepath    =   (getdir #scripts )+ @"\tmp\SourceFile_Setting.ini"

-- 애니메이션 리스트 저장
    Global aniSettingPath   =   maxfilepath+getFilenameFile maxfilename+ @".ini"
/*  
--  파일이름 관련  예시.
--  file="g:\\subdir1\\subdir2\\myImage.jpg"
--  filenameFromPath file -- returns: "myImage.jpg"
--  getFilenamePath file -- returns: "g:\subdir1\subdir2\"
--  getFilenameFile file -- returns: "myImage"
--  getFilenameType file -- returns: ".jpg"
--  pathIsNetworkPath "c:\\temp\\test.txt" --returns: false
--  pathIsNetworkPath "\\\\someserver\\temp\\test.txt" --returns: true
*/
    
global frame = #()

-- 익스포트할때 각 확장자_타입별 클래스.
global  theClasses = exporterPlugin.classes     

try DestroyDialog FBX_Export catch()


rollout FBX_Export "FBX_EXPORT" width:300 height:700
( 
    Group "OBJ Export"
        (
            button SelectObj        "SelectObj"                 pos:[5,20]          width:140 height:35
            label   showbox         "Select for Export"     pos:[35,65]         width:200 height:50
            button  btn_SelectDir   "SelectDir"                pos:[5,115]     width:50 height:20
            edittext text_SelDir        ""                             pos:[55,115]        width:200 height:20
        )
            button ExportMesh       "ExportMesh"              pos:[5,140]     width:140 height:35
        
    Group "Ani Export"
        (       
            button bt_ExportAni     "ExportAni"                 pos:[5,530]     width:140 height:35
            button bt_LoadAni        "Load"                 pos:[160,530]     width:40 height:35
            button bt_SaveAni        "Save"                 pos:[200,530]     width:40 height:35
            edittext ed_TakeName "TakeName "                pos:[10,200]    width:140 height:15
            spinner sp_FrameStart "FrameStart "                pos:[10,220]             width:200 height:15 range:[0,9000,0]  type:#integer
            spinner sp_FrameEnd  "FrameEnd  "                pos:[10,240]      width:200 height:15  range:[0,9000,0] type:#integer
        
            button  bt_AniAdd       "ADD"                      pos:[5,260]     width:60 height:35
            button  bt_AniRemove "REMOVE"                   pos:[65,260]        width:60 height:35
            button  bt_AniReset     "RESET"                     pos:[125,260]   width:60 height:35
            Label   ed_ItemList "0개의 모션"                   pos:[10,300]        width: 200 height:15
            button  bt_listcolor    "LIST_컬러체킹용"               pos:[200,260]   width:80 height:35

            dotnetControl lv_Object "System.Windows.Forms.ListView" pos:[10,320] width:280 height:200
            Label  ed_empty "" pos:[10,565]  width:140 height:15
        )
        
    Group   "Batch Export"
        (
            button bt_BatchExport   "BatchExport"       pos:[140,630]  width:140 height:35
            button  btn_SourceDir   "SourceDir"         pos:[5,610]     width:50 height:20
            edittext text_SourceDir         ""                    pos:[55,610]    width:200 height:20
            label   created       "Create by Jinkyu kim"     pos:[185,680]         width:200 height:50
        )   
        

            
    fn SelectSkinnedBones = 
    (
--Mesh선택하기 위해서 
        hidebyCategory.helpers = true
        hidebyCategory.bones = true
        max select all      
        
--모디파이 탭 선택되고 스킨안에 포함된 본들을 선택
        max modify mode

--본, 더미 다시 보이게
        hidebyCategory.helpers = false
        hidebyCategory.bones = false
        
        if $ !=undefined  then     
        (   
                SelectMesh = $
                
                if $.modifiers[#Skin] != undefined  do               
                (
                    Tskin = $.modifiers[#Skin] 

--스킨에 적용된 본갯수
                    BoneNum = skinOps.GetNumberBones Tskin                

--배열지정
                    SkinBoneList=#()                                                            

-- 본객수만큼 반복 .
                    for i=1 to BoneNum do  
                    (  
                        
--본 이름을 배열로 넣음                      
                        SkinBoneList[i] = skinOps.GetBoneName Tskin i 1     
                    )
                    
                    clearselection
                    
--본이름 배열 개수만큼반복.
                    for i=1 to SkinBoneList.count do                                     
                    (
                        
--배열값의 이름을 얻어온다.
                        nodeSkinBone = getnodebyname SkinBoneList[i]     
                        if nodeSkinBone !=undefined do
                        (
                            selectmore nodeSkinBone
                            FBX_Export.showbox.text ="모델네임 : "+ SelectMesh.name + "  \n" +"본 개수   : "+SkinBoneList.count as string +"_Bones"
                        )
                    )
                    
                )
        )
    )


    fn SelectDir = 
    (
-- 캡션은 선택창에서 보이는 글적는것.
        Global Target = getSavePath caption:"Keep Working~!!!!!!!!!!!!!!!!!!!!!!!!!! \nSelect Directory for Export"  initialDir:FBX_Export.text_SelDir.text         
        if Target != undefined  then
        (
            FBX_Export.text_SelDir.text = Target        
        )
    )
        
    
    fn SourceDir = 
    (
        Global SourceTarget = getSavePath caption:  "익스포트할 소스폴더를 선택 하세요.!"  initialDir:(getDir #maxroot)    
        if Target != undefined  then
        (
            FBX_Export.text_SourceDir.text = SourceTarget                                                                                   
        )
    )       
            

    fn ExportObj = 
    (               
-- 겟파일네임함수로 맥스파일네임을 가지고 온다.     
        export_name = getFilenameFile maxFileName                                                                                           
        
--처음에 Target를 썻었는데 에러가 뜸. 그래서 직접 FBX_Export.text_SelDir.text  를 넣어줌
--익스포트 클래스 5번째 항목인 FBX로 익스포트. -- "\"이것만 쓰면 영역지정이 안됨. 앞에 @ 붙여주니깐 됨.
        exportFile (FBX_Export.text_SelDir.text + @"\" + export_name)    #noPrompt  using:theClasses[9] selectedOnly:true           
        print (FBX_Export.text_SelDir.text + @"\"  + export_name)                                                                                                       
    )


    fn ExportFBX_Option=
    (
        FBXExporterSetParam "SmoothingGroups" true
        FBXExporterSetParam "Animation" false
        FBXExporterSetParam "Cameras" false
        FBXExporterSetParam "Lights" false
        FBXExporterSetParam "UpAxis" "Y"
        FBXExporterSetParam "ScaleFactor" 1.0
        FBXExporterSetParam "ConvertUnit" "cm"
        FBXExporterSetParam "Skin" true

--FBXExporterSetParam "embed Media" false  --embed Media 체크시 텍스쳐를 포함하는 FBX                                                                            
        FBXExporterSetParam "ASCII" false -- binaries are smaller
    )       

    
    local layout_def = #("", "TakeName", "StartFrame", "EndFrame")

    
    fn initListView lv =  
    (
        lv.gridLines = true
        lv.View = (dotNetClass "System.Windows.Forms.View").Details
        lv.MultiSelect = true
        lv.FullRowSelect=true
        lv.backColor = (dotNetClass "System.Drawing.color").DarkKhaki   
/*      
-- lv.Checkboxes = true
-- lv.LabelEdit = true --리스트아이템의 첫번쨰 열(세로)만 됨.
-- lv.Items.Item[1].backColor =  (dotNetClass "System.Drawing.color").Green
-- lv.Items.Item[1].SubItems.Item[3].backColor =  (dotNetClass "System.Drawing.color").Yellow
*/
        for i in layout_def do    

--layout_def의 배열 값을 하나씩 가져와 가로69인  항목을 추가한다.            
            (lv.Columns.add i 69 ) 
    )


    Local Item = #()
    Local ItemClass =dotNetClass "ListViewItem"
    Local NumF

    
    fn addList lv mNumF mTakenF mStartF mEndF =
    (   
        NumF = mNumF  as string
        TakenF = mTakenF as string
        StartF = mStartF  as string
        EndF = mEndF  as string
        data = #(NumF)
 
-- data의 배열인 i가  아래 4개의 값(i, TakenF, StartF, EndF) )을 모아서 item에 전달.
        item = for i in data collect                                           
        (
            dotNetObject ItemClass  #(i, TakenF, StartF, EndF) 
        )
        lv.items.addRange item
        lv.Update()
    )


    fn ListRemove lv =
    (
        local selitem = #()
        a = lv.SelectedItems
        SelCount = a.Count

        if SelCount !=0 then
        (

-- 선택된 아이템의 갯수만큼 반복한다. 반복할때마다 선택된아이템들 아이템을 수집해서 selitem의 배열에 집어넣는다. (((i-1 은 LISTVIEW 는 0부터 시작. 맥스스크립트는 1부서 시작)))
            selitem = for i=1 to SelCount collect (a.item[i-1])  

--selitem안에 selecteditems을 삭제. 
            for i in selitem do ( lv.items.remove  i)             
        )       
        else messagebox "선택 후 클릭 하십시요~~"
    )       

        
--아이템 위치 확인용        
    fn ListViewColorCheck lv= 
    (
        if  lv.items.count !=0 do
        (
            for i=1 to  lv.items.count do
            (  
--https://developer.mozilla.org/en-US/docs/Web/CSS/color_value--컬러색보는곳
                lv.items.item[i-1].UseItemStyleForSubItems = false
                lv.items.item[i-1].subitems.item[1].backcolor = (dotNetClass "System.Drawing.color").LightPink
                lv.items.item[i-1].subitems.item[2].backcolor = (dotNetClass "System.Drawing.color").darkslateblue
                lv.items.item[i-1].subitems.item[3].backcolor = (dotNetClass "System.Drawing.color").deepskyblue
--  print lv.items.item[0].subitems.item[i].text

                
            )
        )
        
    )

/*
--  global RangeStart = animationRange.start
--  global RangeEnd = animationRange.end
*/

    fn ExportAni lv = 
    (   
        AllCount =   lv.Items.count
        ListArray = #()
               
        for i=1 to AllCount do
        
        (    
            AniName =  lv.items.item[i-1].SubItems.item[1].text
            FStart =   lv.items.item[i-1].Subitems.item[2].text
            FEnd   =   lv.items.item[i-1].Subitems.item[3].text
            _AniName    = AniName as string
            _FStar      =  FStart as integer
            _FEnd       = FEnd as integer

-- 배열을 배열에 집어 넣음.
            append ListArray #(_AniName,    _FStar, _FEnd)                            
        )
  
-- 배열안의  1번쨰 배열을 선택해서 세개의 값을 가져갈수 있도록
        for m in ListArray do                                                                  
        (   
                animationRange = interval m[2] m[3]
                FBXExporterSetParam "Animation" true 
                FBXExporterSetParam "BakeAnimation" true 
                FBXExporterSetParam "BakeFrameStart"    m[2] 
                FBXExporterSetParam "BakeFrameEnd"   m[3]
                FBXExporterSetParam "BakeFrameStep" 1.0
-- FbxExporterSetParam "BakeResampleAnimation" true
-- FbxExporterSetParam "NormalsPerPoly" false
-- FBXExporterSetParam "SplitAnimationIntoTakes" m[1] m[2] m[3]  -- #(_AniName,    _FStar, _FEnd)   --2011 버전은 없는거 같음.
                FBXExporterSetParam "Cameras" false
                FBXExporterSetParam "Lights" false
                FBXExporterSetParam "UpAxis" "Y"
                FBXExporterSetParam "ScaleFactor" 1.0
                FBXExporterSetParam "ConvertUnit" "cm"
                FBXExporterSetParam "Skin" true
                FBXExporterSetParam "ASCII" false -- binaries are smaller
                export_name = getFilenameFile maxFileName
--  FBXExporterSetParam "embed Media" false           
            
-- 처음에 Target를 썻었는데 에러가 뜸. 그래서 직접 FBX_Export.text_SelDir.text  를 넣어줌.           
                exportFile (FBX_Export.text_SelDir.text + @"\" + export_name+"_" + m[1])    #noPrompt using:theClasses[9]   selectedOnly:true   --using:theClasses[9] 맥스 버전에 따라 다름., using:FBXEXP 써도 되나봄

-- exportFile(Target + @"\" + export_name + m[1])  #noPrompt selectedOnly:boolSelOnly using:exporterPlugin.classes[intFBXID]
--  FBXExporterSetParam "SplitAnimationIntoTakes" "-c"      -- [TakeName] StartValue EndValue  값초기화.        
        )               
    )

    
--최근 위치값 설정 저장.
    fn SaveInifile _path=
    (
        setINISetting _path  "SelectedDir"  "Export_Dir"     text_SelDir.text 
    )

    
--최근 위치값 설정 불러오기..
    fn LoadInifile _path=
    (
        text_SelDir.text    =   getINISetting _path  "SelectedDir"  "Export_Dir"    
    )

    
--최근 소스파일 위치값 설정 저장.
    fn SaveIniSourcefile _path=
    (
        setINISetting _path  "SourceDir"    "_Dir"   text_SourceDir.text 
    )

    
--최근 소스파일 위치값 설정 불러오기..
    fn LoadIniSourcefile _path=
    (
        text_SourceDir.text     =   getINISetting _path  "SourceDir"    "_Dir"  
    )


--애니 설정값 저장.
    fn SaveAni_Inifile _path=
    (
--저장시 이전 기록 삭제.(기존 기록 유지시 데이타 꼬임)   
        delINISetting _path "Animation"

-- 단순히 애니파일 구분하기위한 용도.
        null = "---------------"    
        
        for i= 1 to  lv_Object.items.count do
        (
            setINISetting _path  " Animation"   (null+ i as string)                 "Take"  
            setINISetting _path  " Animation"   ("AniName" + i as string)   lv_Object.items.item[i-1].SubItems.item[1].text
            setINISetting _path  " Animation"   ("FStart"   +i as string)       lv_Object.items.item[i-1].Subitems.item[2].text
            setINISetting _path  " Animation"   ("FEnd" +i as string )         lv_Object.items.item[i-1].Subitems.item[3].text
        )
            setINISetting _path  " Animation"   (null as string)                        "Total "  
            setINISetting _path  " Animation" "Count"  (lv_Object.items.count as string)
    )


--애니 설정값 불러오기.
    fn LoadAni_Inifile _path=
    (   
        for i= 1 to getINISetting _path  " Animation" "Count" as integer do
        (
            TakeName    =   getINISetting _path  " Animation"   ("AniName"  + i as string ) 
            FrameStart  =   getINISetting _path  " Animation"   ("FStart"       +i as string)   
            FrameEnd    =   getINISetting _path  " Animation"   ("FEnd"         +i as string )  
            NumF = "선택"
            addList lv_Object NumF TakeName FrameStart FrameEnd --위 라인에서 설정한 값들을 addlist(Listview에 숫자 넣는 함수) 함수에서 실행.       
        )                       
            FBX_EXPORT.ed_ItemList.Text=  getINISetting _path  " Animation" "Count" as string +"개의 모션 "   
    )


    fn ListReset  lv =
    (
        lv.items.clear()
    )

    
--ListView 아이템 갯수
    fn listNum lv =        
    (   
        lv.items.count
    ) 
    
        
    on FBX_Export open do
    (
        print "\--------------------------------------------------------------------"
        print "open"
        try(LoadInifile inifilepath)catch()
        LoadIniSourcefile iniSourceFilepath
        initListView lv_Object
        print  inifilepath
    )   

    
    on FBX_Export close do
    (
        SaveInifile inifilepath 
        SaveIniSourcefile iniSourceFilepath
        print "\--------------------------------------------------------------------"
        print "closed"          
    )

    
    on SelectObj pressed do
    (
        SelectSkinnedBones()
    )    

    
    on btn_SelectDir pressed do
    (
        SelectDir ()
    )
    
            
    on btn_SourceDir pressed do
    (
        SourceDir  ()
    )
    
    
    on bt_SaveAni pressed do
    (
        SaveAni_Inifile aniSettingPath
    )

    
    on bt_LoadAni pressed do
    (
        
--로딩전에 리스트 리셋하기 
        ListReset lv_Object
        LoadAni_Inifile aniSettingPath
    )
    
    
    on ExportMesh pressed do
        
    (
        ExportFBX_Option()  
        ExportObj()
    )


    on bt_ExportAni pressed do
        
    (   
        ExportAni lv_Object      
    )


    local NumCount = #()
        
    
    on bt_AniAdd pressed do 
    (
        NumF = "선택"
        local TakeName =  FBX_EXPORT.ed_TakeName.Text
        local FrameStart = FBX_EXPORT.sp_FrameStart.value
        local FrameEnd =  FBX_EXPORT.sp_FrameEnd.value
            
        if (FrameStart > FrameEnd) then
        messagebox "시작 값이 끝나는값보다 작아야 됩니다."
        
        else
        (
--위 라인에서 설정한 값들을 addlist 함수에서 실행.
            addList lv_Object NumF TakeName FrameStart FrameEnd 

--입력값 초기화
            TakeName =  FBX_EXPORT.ed_TakeName.Text=""             
            FrameStart =  FBX_EXPORT.sp_FrameStart.value=0
            FrameEnd  =  FBX_EXPORT.sp_FrameEnd.value=0
            FBX_EXPORT.ed_ItemList.Text=   listNum lv_Object as string  +"개의 모션 "   
        )
    )
        
    
    on bt_BatchExport pressed do
    (
--파일에 @가 포함이면   
        bAniFile        = matchpattern maxfilename pattern:     @"*@*"      

--파일에 Mesh가 포함이면                
        bMeshFile   = matchpattern maxfilename pattern:     @"*Mesh*"           
        
        maxfiles=#()
        maxfiles    =   getFiles ( text_SourceDir.text + "\*.max")
        
        for f in maxfiles do 
        (

--  loadMaxFile() will replace the current scene content with the file content without warning!     
            loadmaxfile f           

-- 셀렉트 스킨메쉬.            
            SelectSkinnedBones()
            
--  export 메쉬
            if(bMeshFile == true) then          
                (
                    ExportFBX_Option()  
                    ExportObj()
                )
-- export 애니                
            if(bAniFile == true) then               
            (
--  LoadAni_Inifile  각각의 애니셋팅 목록값을 불러온다.
                LoadAni_Inifile aniSettingPath  
                ExportAni lv_Object                     
            )   
        )
    )


    on bt_AniReset pressed do
    (
        ListReset lv_Object
--ADD버튼을 누릴떄마다 넘버가 늘어나는데 리셋후에도 계속 늘어나 코드 추가.    
        NumCount = #()    
        FBX_EXPORT.ed_ItemList.Text=   listNum lv_Object as string  +"개의 모션 "
    )
        

    on bt_AniRemove pressed do
    (
                ListRemove lv_Object
                FBX_EXPORT.ed_ItemList.Text=   listNum lv_Object as string  +"개의 모션 "
    )
        

    on bt_listcolor pressed do
    (
        ListViewColorCheck  lv_Object
    )

)

createdialog FBX_Export

 

 

 

 

FBX_Export_split_animationClips_in_maxfile.ms

 

drive.google.com

 

'Script > MaxScript' 카테고리의 다른 글

Pos2VertexColor for Foliage Animation wip  (0) 2020.06.19
pos2zero  (0) 2019.10.11
Random_Scatter_Surface  (0) 2019.10.11
TRI,VERTEX Viewport Count  (0) 2019.10.11