WPL 에서 나온 C24, B16 같은 싸구려 RC 카를 구입하여 arduino 를 이용하여 송수신기도 만들어보고 재미있게 취미생활을 시작하였지만 항상 눈에 밟히는건 Traxxas 나 Axial 같은 나름 명품 RC 카에 대한 로망이었습니다. 물론 직접 DIY 를 하며 즐기던 저의 취미였으니 WPL 이 나쁘다는건 아니었지만 로망 같은 그런게 있었죠.
그러던 어느날 친구 녀석이 Traxxas UDR 을 선물해준게 아니겠습니까? 한방에 끝판왕이 생겨버린 셈이었습니다.
뭐 어쨌거나 UDR 은 Traxxas 의 TRX4 나 Axial 의 SCX10 같은 트라이얼 RC 또는 Rock Crawler 는 아닙니다. 그냥 막 내질러 달리는 스트레스 해소 용 차량이죠. 물론 달릴 환경만 주어진다면 정말 끝판왕 맞습니다만 학교 운동장 말고는 달릴 곳이 없는 저에게는 아쉬움이 항상 남는 그런 차량이죠...
어쨌거나 UDR 의 그 높은 완성도와 완벽할만큼 뛰어난 스케일감을 맞보고 나니 WPL RC 카는 정말 장난감 같고 손에 잡히지가 않더라구요. 다시 Traxxas 나 Axial 같은 하이엔드 급 1/10 스케일의 트라이얼 RC 에 대한 갈증이 일었습니다.
어느날 퇴근 길에 당근마켓을 살펴보던 중 눈을 의심케 하는 글을 하나 보았습니다.
당근마켓 판매자느님께서 올린글
띠용!!!
axial ???
이거뭐야. AXIAL ?? 와우! scx 10 이 아닙니까? 25000원 ???????
빈티지 소품으로 활용해도 좋다고??????????
ㅋㅋ
이건 고장난거라도 사야겠다 싶어서 전철에서 당장 연락을 했습니다.
나 : "판매자 느님 빈티지 rc카 팔렸나요?"
판매자 : "아니오 아직 안팔렸습니다"
나 : "제가 살께요. 지금 바로 가도 될까요?"
판매자 : "아 8시 이후에.."
전 집에와서 저녁을 먹으며 와이프에게 단호하게, 일말의 망설임 없이, 절대 거절할수 없게 가장의 위엄을 한껏 담아..
빌었습니다.
"나 RC 카 하나만... 딱 하나만 더사도 될까?"
ㅋㅋㅋ
웃더군요.
뭔가 비장해 보였나 봅니다.
저녁을 입으로 먹는건지 코로 먹는건지 모르게 먹어 치운 후 판매자 느님께 달려갔습니다.
그리고 25000 원을 드리고 받아온 물건은 바로
두둥. 드디어 내손에 들어온 axial scx10
캬~~~!!
SCX10 jeep wrangler falken edition.
뭐 SCX10 2 시리즈가 아니어서 약간 아쉬운 감이 있자만 이게 어딘가요.
드디어 꿈에 그리던 1/10 스케일 Rock Crawler 가 단돈 25000 원에 생겨버렸습니다.
감사합니다. 판매자 느님.
그리고 판매자 분께서는 충전기가 없다고 하셨지만 전 이미 lipo 충전기가 있거든요. 판매자님, 원래 충전기는 없답니다. 게다가 장착되어 있는 배터리가 약간 배는 불렀지만 무려 13000mha 짜리 초대용량 lipo 7.4v 배터리가 들어있는게 아닙니까? 와.. 정말 계 탓네요.
살다보니 이런일도 있구나 싶더라구요.
암튼 본격적으로 RC카 데리고 등산다닐 생각하니 가슴이 두근 거립니다.
그리고 어찌된 영문인지 바닥 긁힘도 전혀 없는 실내 주행만 한 그런 제품이었습니다.
완전 새거나 다름없더군요.
어쨌든
당근마켓 만세!
판매자님 만세!
RC카도 만세!
구형 모델이다 보니 배터리 위치가 무게중심에 뒤쪽으로 쏠려 있어 전방으로 이동시키는 DIY 를 진행하였습니다.
블로그에 방문 주신 분께서 레이어 자동 저장 기능에 해당 레이어 크기로 이미지가 저장 되었으면 좋겠다는 의견을 주셔서 한번 만들어 보았습니다.
사실 사용하는 분마다 차이가 있을 것 같은데요. 아마 레이어의 크기로 저장되는 기능이 꼭 필요한 분도 계실 거라 생각되어 별도로 페이지를 하나 만들었습니다.
전체 스크립트는 큰 차이가 없으며 현재 레이어의 크기를 파악하고 그 크기로 저장하는 방법만 추가하면 될 것 같습니다.
아래의 내용을 추가할 계획입니다.
현재 레이어의 크기 (가로, 세로) 를 구해본다 : Layer.bounds 라는 개체를 이용
구해진 크기로 새로운 임시 도큐먼트를 생성한다 : Documents.Add( ) 이용
생성된 임시 도큐먼트에 해당 레이어를 복사하여 붙여 넣는다 : Layer.copy() // document.paste()
도큐먼트를 지정된 파일이름과 위치에 저장하고 임시 도큐먼트는 닫는다. : document.close()
기존과 동일하게 전체 크기로 저장하기 위한 옵션을 두어 선택할 수 있게 한다.
어렵지 않죠?
아래 작성된 코드를 보시죠.
var pngOption = newPNGSaveOptions()
pngOption.embedColorProfile = true;
pngOption.formatOptions = FormatOptions.STANDARDBASELINE;
pngOption.matte = MatteType.NONE;
pngOption.quality = 100;
pngOption.PNG8 = false; //24 bit PNG
pngOption.transparency = true;
pngOption.interlaced = true;
// jpg 파일 저장을 위한 설정
var jpgSaveOption = newJPEGSaveOptions()
jpgSaveOption.quality = 10;
var sType = "png"// jpg 로 파일을 저장하기를 원하는경우, png 로 저장을 하고 싶다면 "png"
var cDoc = app.activeDocument
var tempLayers = cDoc.layers
var cWidth = cDoc.width
var cHeight = cDoc.height
var cRes = cDoc.resolution
var savePath = cDoc.path + "\\"
var saveAsEachLayerSize = true// 일단 모든 레이어를 꺼줍니다.for (var i = 0 ; i < tempLayers.length; i++)
{
tempLayers[i].visible = false
}
// 아래의 한줄의 함수로 모든 레이어를 저장합니다.explorerLayerSet_new(savePath, cDoc)
function explorerLayerSet_new (cPath, lSet){
var dLyrs = lSet.layers
var documentSaveType = pngOption;
if (sType == "jpg")
{
documentSaveType = jpgSaveOption
}
for (var k = 0 ; k < dLyrs.length; k++){
if (dLyrs[k].typename == "LayerSet")
{
dLyrs[k].visible = true
ePathName = cPath + dLyrs[k].name + "\\"
ee = newFolder(ePathName)
ee.create();
explorerLayerSet_new (ePathName, dLyrs[k])
dLyrs[k].visible = false
}else{
dLyrs[k].visible = true
dFile = File(cPath + dLyrs[k].name + "." + sType)
if (saveAsEachLayerSize == false)
{
cDoc.saveAs(dFile, documentSaveType,true, Extension.LOWERCASE)
}else{ // 레이어의 크기에 맞추어 파일을 저장하는 기능 // layer.bounds 를 이용하여 레이어의 크기를 얻어올 수 있다.
var cBounds = dLyrs[k].bounds
var newWidth = cBounds[2] - cBounds[0];
var newHeight = cBounds[3] - cBounds[1];
dLyrs[k].copy();
// 레이어의 크기와 동일한 도큐먼트를 하나 생성한다.
var tmpDoc = app.documents.add(newWidth, newHeight, cDoc.resolution, dLyrs[k].name, NewDocumentMode.RGB, DocumentFill.TRANSPARENT);
tmpDoc.paste();
tmpDoc.saveAs(dFile, documentSaveType,true, Extension.LOWERCASE);
tmpDoc.close(SaveOptions.DONOTSAVECHANGES);
}
if (dLyrs[k].name.indexOf("All") == -1)
{
dLyrs[k].visible = false
}
}
}
lSet.visible = false
}
기존 saveAs 부분이 변경이 되었습니다.
png, jpg 를 판단하는 부분을 위쪽으로 빼서 documentSaveType 이라는 변수에 저장 옵션을 담아 버렸습니다.
그리고 위쪽에 saveAsEachLayerSize 라는 변수를 두어 True 일 경우 레이어 크기로, False 일 경우 전체 도큐먼트 크기로 저장이 되게 하여 기존의 방식이 필요한 분들도 하나의 스크립트로 사용할 수 있도록 하였습니다.
3DS MAX 스트립트를 이용하여 스크립팅을 하기 시작하면 MAX 로 할 수 있는 일이 너무나 많고 빠르게 진행된다는 것을 알 수 있게 됩니다. 이 놀라운 기능들을 스크립트를 매번 실행시키는 것이 번거롭고 여러가지 기능을 한번에 수행하거나 관리할 수 있게끔 만들고 싶은 욕심이 생길 때가 있습니다. 그럴때 그런 기능들을 한곳에 모아 실행하기 위하여는 마치 어떤 툴이나 프로그램 처럼 UI 가 있는 것이 실행하기도 편하고 보기에도 좋을 것입니다.
그래서 오늘은 3DS MAX Script 를 이용하여 다이얼로드 창을 만드는 방법, 그리고 간단한 UI 를 적용하는 방법을 알아 보겠습니다.
다이얼로그 창 만들기 / 버튼 만들어 기능 연결하기
롤아웃 플로터 만들기
먼저 3DS MAX 에서 다이얼로그 창을 만들어 보겠습니다.
다이얼로그 는 윈도우에서 예/아니오 경고창과 같이 간단한 타이틀바와 닫기 버튼, 내부에 간단한 UI 들을 담을 수 있는데요. 기본적으로는 rollout 을 만들어 rollout 내부에 UI 를 담고 만들어진 rollout 을 다이얼로그창의 형식으로 보여주는 형태로 되어 있습니다.
다이얼로그 창 만들기 / 버튼 만들어 기능 연결하기
가장 기본형은 아래와 같습니다.
rollout myFirstRollout "my menu 01"
(
button bt_myButton "press me!"
on bt_myButton pressed do
(
messageBox "You got it!"
)
)
createDialog myFirstRollout
먼저 롤아웃을 선언해 준 뒤 그안에 버튼을 하나 만들고 버튼을 눌렀을때 특정한 메시지를 팝업으로 보여주는 다이얼로그입니다.
바로 요렇게 만들어 지게 됩니다.
심플한 다이얼로그
가운데 있는 버튼을 눌러주면 아래와 같이 팝업으로 메시지가 출력이 됩니다.
messagebox 가 뜬 모습
자 이게 기본은 했고요. 다이얼로그의 크기, 위치, 버튼의 크기나 위치등을 지정하지 않았기 때문에 위와 같이 작게 표시가 되었는데요. 여기서 그 크기위 위치를 지정하는 방법을 알아보도록 하겠습니다.
제일 마지막 줄에 createDialog 라는 명령으로 만들어준 롤아웃을 다이얼로그라는 형태로 보여주었는데요. 이부분에서 다이얼로그릐 크기, 위치를 지정할 수 있습니다. 마지막 줄의 내용을 아래와 같이 수정해 보겠습니다.
createDialog myFirstRollout 300200100100
요런 식으로 정의 할 수 있는데요. 4개의 숫자를 뒤에 연속해서 써주게 되는데
[가로길이] [세로길이] [윈도우상의 가로위치] [윈도우상의 세로위치]
의 순서로 적어주면 되며 뒤의 두가지 숫자를 생략하게 되면 화면의 중앙에 다이얼로그가 생성이 됩니다.
위의 코드로 수정한 뒤 실행하게 되면 아래와 같이 윈도우 좌측 상단에 가로 300 세로 200 픽셀의 다이얼로그 창이 만들어 지게 됩니다.
화면의 좌상단에서 열린 다이얼로그 창
버튼을 하나 추가해 볼까요?
rollout myFirstRollout "my menu 01"
(
button bt_myButton1 "press me!"
button bt_myButton2 "don't press me!"
on bt_myButton1 pressed do
(
messageBox "You got it!"
)
on bt_myButton2 pressed do
(
destroyDialog myFirstRollout
)
)
createDialog myFirstRollout 300200100100
자 이제 버튼이 두개가 되었습니다. 두번째 버튼을 누르게 되면 다이얼 로그 창이 닫히는 그런 코드가 실행이 됩니다.
스크립트를 실행해 보면 아래와 같이 버튼이 두개가 표시됩니다.
다이얼로그에 버튼이 두개가 되었다.
그런데 저는 저 두개의 버튼을 가로로 배치하고 싶어서 코드를 약간 더 손을 보았습니다. 레이아웃을 조정하는 것이죠.
rollout myFirstRollout "my menu 01"
(
button bt_myButton1 "press me!" pos:[10,10] width:130 height:30
button bt_myButton2 "don't press me!" pos:[150,10] width:130 height:30
on bt_myButton1 pressed do
(
messageBox "You got it!"
)
on bt_myButton2 pressed do
(
destroyDialog myFirstRollout
)
)
createDialog myFirstRollout 300200100100
맨위에 버튼을 만들어주는 부분에 pos 와 width, height 를 설정해 줌으로써 사용자가 원하는 위치와 크기로 버튼을 배치할 수 있습니다.
실행해보면 아래와 같이 변경이 됩니다.
두개의 버튼이 가로로 배치되었다
네.. 그럴싸 해 졌습니다.
롤아웃 플로터 (rollout floater) 만들기
이번에는 좀더 고급 UI 를 이용해 보겠습니다. 3DS MAX 의 우측에 보면 여러가지 옵션이나 설정을 입력하고 기능을 선택하는 메뉴바가 나오는데요. command pannel 이라고 합니다. 해당 패널에 보면 기능이 많은 경우 기능을 접었다 폈다 할 수 있는 형태로 메뉴가 만들어져 있습니다.
유사한 기능끼리 그룹을 만들어 묶어주고 사용하지 않는 기능 그룹은 묶어 둘 수 있어 상당히 편리한 UI 라고 할 수 있겠습니다. 스크립트 UI 로도 거의 동일한 기능을 아주 쉽게 구현할 수 있습니다.
그런 기능들의 묶음이 위에서 보여주었던 하나의 롤아웃이 되며 롤아웃들을 하나의 창에 보여줄 수 있는 개체가 롤아웃 플로터 입니다.
간단한 예를 보여드리겠습니다.
rollout myRollout_ani "my Animation"
(
button bt_myButton1 "run" pos:[10,10] width:130 height:30
button bt_myButton2 "stop" pos:[150,10] width:130 height:30
on bt_myButton1 pressed do
(
messageBox "No function now"
)
on bt_myButton2 pressed do
(
messageBox "No function now"
)
)
rollout myRollout_geom "my Geometry"
(
button bt_myButton1 "convertTo mesh" pos:[10,10] width:130 height:30
button bt_myButton2 "remove Iso Vertex" pos:[150,10] width:130 height:30
on bt_myButton1 pressed do
(
messageBox "No function now"
)
on bt_myButton2 pressed do
(
messageBox "No function now"
)
)
myTool = newrolloutFloater "my custon tool"300300100100
addRollout myRollout_ani myTool
addRollout myRollout_geom myTool
myTool.open
두개의 롤아웃을 먼저 만들고 myTool 이라는 변수를 새로운 롤아웃 플로터로 설정한뒤 myTool 에 두개의 롤아웃을 추가합니다. 끝으로 만들어진 롤아웃 플로터를 보여주는 것이죠.
간단하지 않습니까?
실행하면 아래와 같이 됩니다.
두개의 롤아웃이 있는 메뉴가 만들어 졌다.
이렇게 만들어진 롤아웃 플로터내의 롤아웃 들은 아래와 같이 접는것도 가능합니다.
롤아웃은 말그대로 접어 올릴수 있는 메뉴다.
이제 제법 툴같이 만들어 졌습니다.
자 3DS MAX 에서 롤아웃을 만드는 것을 알아보았습니다.
이제 다음 강좌에서는 롤아웃 내의 버튼에 기능을 할당해 보도록 하겠습니다. 인터넷에서 긁어 모은 각종 스크립트 들을 저렇게 만든 롤아웃 플로터 하나에 모아두면 필요할때 빠르게 기능 동작이 가능할 것 입니다.
일을 하다 보면 가끔 업무를 진행하던 경로 하위에 있는 데이들의 리스트를 만들어야 하는 경우가 있습니다. 하나의 폴더라면 어떻게 해보겠는데 그 폴더가 하위 뎁스가 연속해서 있고 저장되어 있는 파일이 불특정으로 다수 있는 경우 이를 리스트로 만드는 일은 정말 끔찍한 일이 아닐 수 없습니다.
이런 경우 손쉽게 리스트로 작성하는 스크립트를 만들어 보았습니다.
폴더 및 하위 폴더의 파일 명칭은 물론 저장된 경로, 파일의 만든날짜, 수정한 날짜 등의 속성을 표시할 수 있도록 하고 파일의 용량과 타입까지 자동으로 리스트로 만들어 보려 합니다.
이러한 코드를 작성하기 위하여 개발을 할 내용을 준비해봅니다.
엑셀 vba 에서 사용 가능한 File 및 Directory 관련 개체를 알아보자.
지정한 폴더내에 파일이 있는 경우 파일의 이름과 속성등을 가져와 기록해주자
파일이 바뀔때마다 줄을 바꾸어 주자
폴더를 만나게 되면 해당 폴더 하위의 파일을 찾아주자.
2 ~ 4 번은 하위 폴더를 만날때 마다 계속해서 반복해주어야 하므로 재귀 함수로 동작하도록 구성하자. * 재귀함수란 자신이 자신을 호출하는 함수를 말합니다.
연속해서 위치를 이동시켜야 하므로 range.offset(y,x) 를 이용하되 x, y 는 전역 변수 (public)로 설정해주자
요 정도면 원하던 기능의 구현이 가능할 것 같습니다.
제가 작성한 코드를 아래에 올려보겠습니다.
보시면서 위에 설명드린 동작이 어떤 과정으로 일어나는지 확인해 보세요.
Public fso As Object
Public offsetY As Integer
Public offsetX As Integer
Public stRng As Range
Sub getSubFileList()
Dim fsoFolder As Object
Dim aSht As Worksheet
Dim rootpath As String
rootpath = "C:\Users\mariine\Pictures\excel_test\"
offsetX = 0
offsetY = 0
Set aSht = ActiveSheet
Set fso = CreateObject("Scripting.FileSystemObject")
Set fsoFolder = fso.GetFolder(rootpath)
Set stRng = aSht.Range("B2")
getDataRecursive fsoFolder
End Sub
Sub getDataRecursive(ByVal baseFolder As Object)
Dim tmpSubFolders As Object
Dim tmpFiles As Object
Dim tmpRng As Range
offsetX = offsetX + 1
Set tmpSubFolders = baseFolder.subFolders
Set tmpFiles = baseFolder.Files
For Each c In tmpFiles
stRng.Offset(offsetY, offsetX).Value = c.Name ''파일명
stRng.Offset(offsetY, offsetX - 2).Value = offsetY + 1 ''인덱스
stRng.Offset(offsetY, offsetX - 1).Value = c.ParentFolder.Path ''경로명
stRng.Offset(offsetY, offsetX + 1).Value = c.Size / 1000# & "kb" ''파일용량
stRng.Offset(offsetY, offsetX + 2).Value = c.DateCreated ''만든날짜
stRng.Offset(offsetY, offsetX + 3).Value = c.DateLastModified ''수정한날짜
stRng.Offset(offsetY, offsetX + 4).Value = c.Type ''파일타입
offsetY = offsetY + 1
Next c
offsetX = offsetX - 1
For Each d In tmpSubFolders
Dim tmpSub As Object
Set tmpSubs = fso.GetFolder(d)
getDataRecursive tmpSubs
Next d
End Sub
제일 상단의 4줄은 이번 코드에서 사용할 전역 변수를 설정해주는 모습입니다.
전역변수를 설정해주면 하위의 어떤 함수에서건 해당 값을 읽고 쓸 수 있게 됩니다. A 함수에서 변경한 전역 변수의 값을 B 함수에서도 그대로 사용이 가능하게 됩니다.
그 다음 만나는 Sub getSubFileList() 함수가 동작을 설정하는 함수가 되겠고요. FileSystemObject 라는 파일 및 디렉토리 작업이 가능한 COM 오브젝트를 선언하여 서브폴더나 폴더, 파일 등에 접근이 가능하도록 하고 있습니다.
그리고 그뒤로 이어지는 재귀 함수인 Sub getDataRecursive(ByVal baseFolder As Object) 를 호출하는 것으로 동작이 시작됩니다.
그렇게 시작된 재귀함수는 입력받은 폴더내에 파일이 있으면 파일의 이름 및 기타 속성들을 줄을 바꾸어 가며 기록을 진행하고 모든 파일에 대하여 기록이 끝나면 그아래 해당 폴더에 하위 폴더가 있는지 검사한뒤 하위 폴더가 있는 경우 또다시 자신 (폴더내의 파일을 찾아 기록하는 함수) 을 호출하여 주게 되는 겁니다.
이렇게 해주면 하나의 재귀함수로 아무리 깊고 많은 뎁스의 폴더안의 파일들도 모두 탐색이 가능해 지게 되겠습니다.
이렇게 만들어진 리스트는 아래와 같습니다.
만약 특정 확장자만 검색하고 싶으시면 실제 파일 속성을 기록하는 For 구문의 바로 첫 행에 IF 문을 이용하여 파일 이름내에 필요한 확장자가 있는지 검사를 진행하면 되겠습니다.
예를 들어 xlsx 파일만 리스트로 만들고 싶다면 해당 For 구문을 아래와 같이 변경하면 되겠습니다.
For Each c In tmpFiles
If InStr(c.Name, ".xlsx") Then
stRng.Offset(offsetY, offsetX).Value = c.Name '파일명
stRng.Offset(offsetY, offsetX - 2).Value = offsetY + 1 '인덱스
stRng.Offset(offsetY, offsetX - 1).Value = c.ParentFolder.Path '경로명
stRng.Offset(offsetY, offsetX + 1).Value = c.Size / 1000# & "kb" '파일용량
stRng.Offset(offsetY, offsetX + 2).Value = c.DateCreated '만든날짜
stRng.Offset(offsetY, offsetX + 3).Value = c.DateLastModified '수정한날짜
stRng.Offset(offsetY, offsetX + 4).Value = c.Type '파일타입
offsetY = offsetY + 1
End If
Next c
어렵지 않죠?
만약 있어서는 안되는 확장자가 들어있다면 눈에 띄게 표시를 하는것도 어렵지 않습니다.
예를 들어 폴더내에 hwp 파일만 있어야 한다고 했을때 해당 폴더에 .doc 파일처럼 또 다른 있는 경우 빨간색으로 셀을 표시할 수 있습니다.
For Each c In tmpFiles
stRng.Offset(offsetY, offsetX).Value = c.Name '파일명
stRng.Offset(offsetY, offsetX - 2).Value = offsetY + 1 '인덱스
stRng.Offset(offsetY, offsetX - 1).Value = c.ParentFolder.Path '경로명
stRng.Offset(offsetY, offsetX + 1).Value = c.Size / 1000# & "kb" '파일용량
stRng.Offset(offsetY, offsetX + 2).Value = c.DateCreated '만든날짜
stRng.Offset(offsetY, offsetX + 3).Value = c.DateLastModified '수정한날짜
stRng.Offset(offsetY, offsetX + 4).Value = c.Type '파일타입
If Not InStr(c.Name, ".hwp") Then
stRng.Offset(offsetY, offsetX).Interior.Color = RGB(255, 0, 0)
End If
offsetY = offsetY + 1
Next c
참 쉽죠?
이렇게 해서 폴더내의 모든 파일을 하위폴더를 포함하여 가져오는 스크립트를 알아보았습니다. 이런식의 작업은 엑셀의 워크시트 함수만을 이용하여 작업하기는 어렵기 때문에 vba를 좀 할줄 알면 아주 많은 시간을 절약하는 것이 가능합니다.
오늘은 여기까지 하도록 하겠습니다.
관련하여 사전 지식이 부족하신 분은 아래 관련 포스트를 링크해 드리니 참고하시면 되겠습니다.
Instagram, 요즘 가장 핫한 SNS 가 아닐까 싶습니다. 지하철에서 무심코 주변을 보니 눈에 보이는 핸드폰 화면 마다 Instagram 화면이 보이는 것을 보고 깜짝 놀랐습니다.
난 안하는데...
어쨌든 Instagram계정을 하나 만들어 보았습니다.
전 SNS 에 취미같은 건 없고 다만 뭔가 하나 만들어 보고 싶었는데 테스트 할 공간이 바로 Instagram 이었다고나 할까.
인스타 그램에 파노라마 사진을 올리면 사진이 다 보이지도 않거니와 얇고 길에 나와 보기에 썩 좋지 않더라고 합니다. 그래서 파노라마로 길게 찍힌 사진을 Instagram에 올리기 적당하게 네모 반듯하게 자동으로 잘라주는 스크립트를 만들어 보았습니다.
폰 app 중에는 Instagram 용 파노라마를 자연스럽게 올릴 수 있도록 잘라주는 APP 들이 있는데 PC 에 있는 사진을 올리고 싶다면 그걸 폰에 올려서 잘라서 업로드 하는게 역시 귀찮더군요. 바로 PC 에서 잘라서 올리면 좋겠다는 생각이 들어 만들어 보기로 하였습니다.
이렇게 만들려고 합니다.
인스타용 최적 이미지 사이즈는 1080 x 1080 이라고 한다
가로길이를 1080 으로 나누어 등분할 개수를 구한다. --> N.xx
1080으로 정확히 나누어 떨어질 리 없기 때문에 소숫점 첫자리 기준으로 반올림/내림 한다.
가로길이 : 1080 px x N , 세로길이 : 1080 px 로 리사이즈를 한다.
가로세로 1080 px 씩 좌측부터 selection 을 만들어 이미지를 복사한다.
새로운 1080 x 1080 짜리 document 를 만들어서 복사한 이미지를 붙인다.
이미지와 동일한 명칭의 폴더를 만든 후 복사한 이미지를 저장한다.
요렇게 하면 만들어 질 것 같았습니다.
먼저 테스트용 이미지는 아래와 같습니다.
무려 4664 x 1200 짜리 커다란 이미지. 우도 하고수동 해수욕장에서
대한민국의 보물 '우도' 에 자리잡고 있는 하고수동 해수욕장 되시겠습니다.
가로로 긴 이미지 인데요,
올리면 이렇게 보이게 됩니다.
Instagram 에 파노라마를 직접 올린 사진
이렇게 좌우를 자를 수 밖에 없는 상황이 됩니다.
그래서 Instagram 이미지 크기에 맞게 가로로 긴 이미지를 스크립트로 자동으로 자르면 어떻게 될까요?
포토샵의 가장 중요한 핵심 기능중의 하나가 레이어라는 구조일 것입니다. 이미지를 계층적으로 쌓아 올리고 각각의 이미지간 효과를 적용하거나 투명도등을 조절하여 완성된 하나의 이미지를 만들어 내는 것이죠.
오늘 강좌에서는 포토샵에 레이어를 생성하고, 이름을 붙이거나, 위치를 이동하는 등의 레이어를 관리하는 기능을 소개해 드릴까 합니다.
오늘 강좌에서 다룰 내용은 아래와 같습니다.
이미 있는 레이어를 복제하기, 위치 조정하기, 투명도 조정하기
이미 있는 레이어의 순서 변경하기(이동하기)
먼저 포토샵 스크립트 작성을 위하여 extend script toolkit 을 실행합니다.
그리고 당연히 포토샵 어플리케이션을 열어야 겠죠.
오늘은 제가 좋아하는 traxxas 로고를 이용하여 코드 연습을 해보겠습니다.
이 로고 이미지를 이용하여 wallpaper 를 한번 만들어 보겠습니다. 손으로 하자면 아주 귀찮을 그림을 만들어 보겠습니다.
//로고파일의 위치를 설정한다
traxxasLogo_250_File = "D:/01_works/용/DIY_HARD_RC/traxxas_LOGO_125x25.png"var logoFile = new File(traxxasLogo_250_File);
// 포토샵에 새로운 도큐먼트를 생성한다. 크기는 1920 1080var wallPaperPsd = app.documents.add(1920,1080,72)
// 검정색 컬러를 정의한다var newColor = new SolidColor();
newColor.rgb.red = 0;
newColor.rgb.green = 0;
newColor.rgb.blue = 10;
// 배경색상을 검정색으로 fill 한다
wallPaperPsd.selection.selectAll();
wallPaperPsd.selection.fill(newColor);
wallPaperPsd.selection.deselect();
// 로고 이미지를 열어서 포토샵으로 복사한다. var logoImg = app.open(logoFile);
var newLogoLyr = logoImg.layers[0].duplicate(wallPaperPsd.layers[0], ElementPlacement.PLACEBEFORE);
app.activeDocument = logoImg;
// 로고 파일은 닫는다.
logoImg.close();
app.activeDocument = wallPaperPsd;
//복사한 레이어의 이름을 변경한다.
newLogoLyr.name = "logo_1_1";
오늘 강좌의 핵심이되는 부분은 요 부분입니다.
var newLogoLyr = logoImg.layers[0].duplicate(wallPaperPsd.layers[0], ElementPlacement.PLACEBEFORE);
logoImg 파일의 0번째 레이어를 복사하는 명령어죠. duplicate 뒤에 오는 값들은 어디에 복사할 것인지를 지정합니다. 첫번째 인자는 복제될 대상을 지정합니다. 이번 예제에서는 다른 PSD 파일 (배경화면용 이미지)의 0번째 레이어가 대상이 되었습니다. 그뒤에 오는 인자는 지정한 대상과의 관계인데요. 이번 예제에서 사용한 것은 "대상 예제의 바로 전에 위치시켜라" 라는 명령입니다.
ElementPlacement 라는 개체의 하위 옵션으로 몇가지 옵션을 지정할 수 있는데요. PS 버전이 올라가면서 옵션이 많이 다양해 졌습니다. 필요한 위치에 맞추어 넣으시면 되겠습니다.
ElementPlacement.PLACEAFTER //cs6 - 대상개체 아래에
ElementPlacement.PLACEATEND //cs6 - 대상개체의 제일 마지막에
ElementPlacement.PLACEBEFORE //cs6 - 대상개체의 바로 위에
ElementPlacement.INSIDE //cc - 대상이 폴더인 경우 폴더 안에
ElementPlacement.PLACEATBEGINNING //cc - 대상 개체의 제일 처음에
어쨌든 요기까지 스크립트를 실행해보면 아래와 같은 그림이 만들어 집니다.
스크립트를 이용해 포토샵 psd 파일에 다른 파일의 이미지를 레이어로 복사한 상태
화면에 로고 이미지가 붙었죠?
요 이미지를 화면에 쫙 깔아 보겠습니다. 손으로 하려면 매우 귀찮은 작업이죠.
위에 작성한 코드를 포함하여 추가한 코드는 아래와 같습니다.
//로고파일의 위치를 설정한다
traxxasLogo_250_File = "D:/01_works/용/DIY_HARD_RC/traxxas_LOGO_125x25.png"var logoFile = new File(traxxasLogo_250_File);
// 포토샵에 새로운 도큐먼트를 생성한다. 크기는 1920 1080var wallPaperPsd = app.documents.add(1920,1080,72)
// 검정색 컬러를 정의한다var newColor = new SolidColor();
newColor.rgb.red = 0;
newColor.rgb.green = 0;
newColor.rgb.blue = 10;
// 배경색상을 검정색으로 fill 한다
wallPaperPsd.selection.selectAll();
wallPaperPsd.selection.fill(newColor);
wallPaperPsd.selection.deselect();
// 로고 이미지를 열어서 포토샵으로 복사한다. var logoImg = app.open(logoFile);
var newLogoLyr = logoImg.layers[0].duplicate(wallPaperPsd.layers[0], ElementPlacement.PLACEBEFORE);
app.activeDocument = logoImg;
// 로고 파일은 닫는다.
logoImg.close();
app.activeDocument = wallPaperPsd;
//복사한 레이어의 이름을 변경한다.
newLogoLyr.name = "logo_def";
// 이제 화면에 로고 이미지를 가득 채워 넣자//각 로고 간의 간격을 설정해주자var gapWidth = 100;
var gapHeight = 100;
// 로고 이미지의 영역을 구한다. bounds 는 레이어내의 이미지 영역 정보를 가져온다var logoBounds = newLogoLyr.bounds; //영역 정보는 [left, top, right, bottom] 순으로 들어온다.var logoWidth = logoBounds[2] - logoBounds[0]; // 레이어의 가로 크기var logoHeight = logoBounds[3] - logoBounds[1]; // 레이어의 세로크기// 화면 영역내에 몇개의 로고가 들어갈 수 있는지 계산한다.var numWidth = wallPaperPsd.width / (logoWidth + gapWidth)
var numHeight = wallPaperPsd.height / (logoHeight + gapHeight)
// 가로, 세로로 로고를 복사하여 붙여 넣는다.for (var yy = 0; yy < numHeight ; yy++) // 세로 개수만큼 반복
{
for (var xx = 0; xx < numWidth; xx++) // 가로 개수만큼 반복
{
var tmpLyr = newLogoLyr.duplicate(newLogoLyr, ElementPlacement.PLACEBEFORE);
tmpLyr.translate(xx * (logoWidth + gapWidth), yy * (logoHeight + gapHeight))
tmpLyr.name = "logo_" + xx + "_" + yy ;
}
}
로고 이미지를 가로세로로 100px 씩 간격을 띄고 화면에 꽉 채우는 스크립트가 되었습니다.
이렇게하면 아래와 같은 그림이 만들어 집니다.
traxxas 로고로 꽉 채워진 이미지가 만들어 졌다.
네 그런데 약간 아쉬운건 로고가 약간 치우쳐져서 배치가 되었네요.
그리고 약간 단조로워 보이는 듯 합니다. 그리고 바탕화면에 사용할 그림인데 너무 강렬한 감이 있습니다.
자 코드를 좀 수정해 보겠습니다. 이제 코드가 제법 길어지므로 집중해서 보셔야 합니다.
var currentUnitValue = app.preferences.rulerUnits
// 포토샵 유닛을 픽셀로 변경
app.preferences.rulerUnits = Units.PIXELS;
//로고파일의 위치를 설정한다
traxxasLogo_250_File = "D:/01_works/용/DIY_HARD_RC/traxxas_LOGO_125x25.png"var logoFile = new File(traxxasLogo_250_File);
// 포토샵에 새로운 도큐먼트를 생성한다. 크기는 1920 1080var wallPaperPsd = app.documents.add(1920,1080,72)
// 검정색 컬러를 정의한다var newColor = new SolidColor();
newColor.rgb.red = 0;
newColor.rgb.green = 0;
newColor.rgb.blue = 10;
// 배경색상을 검정색으로 fill 한다
wallPaperPsd.selection.selectAll();
wallPaperPsd.selection.fill(newColor);
wallPaperPsd.selection.deselect();
// 로고 이미지를 열어서 배경화면용 포토샵으로 복사한다. var logoImg = app.open(logoFile);
var newLogoLyr = logoImg.layers[0].duplicate(wallPaperPsd.layers[0], ElementPlacement.PLACEBEFORE);
app.activeDocument = logoImg;
// 로고 파일은 닫는다.
logoImg.close();
app.activeDocument = wallPaperPsd;
//복사한 레이어의 이름을 변경한다.
newLogoLyr.name = "logo_def";
// 이제 화면에 로고 이미지를 가득 채워 넣자//각 로고 간의 간격을 설정해주자. 숫자가 작을 수록 촘촘해지고 클수록 듬성듬성 붙는다.// 포토샵 스크립트 실행 속도가 느리므로 너무 작은 숫자는 넣지 말자.var gapWidth = 100;
var gapHeight = 100;
// 로고 이미지의 영역을 구한다. bounds 는 레이어내의 이미지 영역 정보를 가져온다var logoBounds = newLogoLyr.bounds; //영역 정보는 [left, top, right, bottom] 순으로 들어온다.var logoWidth = logoBounds[2] - logoBounds[0]; // 레이어의 가로 크기var logoHeight = logoBounds[3] - logoBounds[1]; // 레이어의 세로크기// 화면 영역내에 몇개의 로고가 들어갈 수 있는지 계산한다.var numWidth = Math.floor(wallPaperPsd.width / (logoWidth + gapWidth));
var numHeight = Math.floor(wallPaperPsd.height / (logoHeight + gapHeight));
// 가로, 세로로 가득 찼을때 남는 공간을 계산하여 offset 을 만들어 주자var offsetX = Math.floor(wallPaperPsd.width - (numWidth * (logoWidth + gapWidth) - gapWidth));
offsetX = offsetX * 0.5;
var offsetY = Math.floor(wallPaperPsd.height - (numHeight * (logoHeight + gapHeight) - gapHeight));
offsetY = offsetY * 0.5;
// 투명도를 조절하기 위한 값. 여기서 설정한 값에 의해 투명도가 자동으로 조절되어 아래로 내려갈 수록 투명해 진다. var maxOpacity = 50;
var minOpacity = 15;
// 각 단계별 투명도 변이 값을 계산하자.var opacityStep = (maxOpacity - minOpacity) / numHeight
// 가로, 세로로 로고를 복사하여 붙여 넣는다.for (var yy = 0; yy < numHeight ; yy++) // 세로 개수만큼 반복
{
for (var xx = 0; xx < numWidth; xx++) // 가로 개수만큼 반복
{
var tmpLyr = newLogoLyr.duplicate(newLogoLyr, ElementPlacement.PLACEBEFORE);
wallPaperPsd.activeLayer = tmpLyr; // 처음 레이어가 계속 활성화 되는 것을 막기 위하여 활성화된 레이어를 현재 복사된 레이어로 지정
tmpLyr.translate(-logoBounds[0], -logoBounds[1]); // 복사된 레이어를 0,0 으로 이동시킴
tmpLyr.translate((xx * (logoWidth + gapWidth)) + offsetX, (yy * (logoHeight + gapHeight)) + offsetY) // 지정된 위치로 이동
tmpLyr.name = "logo_" + xx + "_" + yy ;
tmpLyr.opacity = maxOpacity - (yy * opacityStep); // 아래로 갈 수록 투명하게// tmpLyr.opacity = minOpacity + (yy * opacityStep); // 위로 갈수록 투명하게 하려면 주석을 풀어 줍니다.
}
}
//처음에 복사한 레이어는 필요 없으니 숨겨줍니다.
newLogoLyr.visible = false;
//유닛을 스크립트 실행 이전상태로 복구함
app.preferences.rulerUnits = currentUnitValue;
이제 스크립트는 아래로 복제해 나가면서 점차 투명도를 주어 서서히 보이지 않도록 하고 있습니다. 이 투명도로 사용자가 설정한 최대값과 최소값을 이용하여 점차 변화하도록 하고 있습니다.
간격을 조정하여 촘촘하게 배치하거나 듬성듬성 배치하더라도 의도한데로 서서히 변화가 적용될 것 입니다.
적용된 화면을 다시 볼까요?
화면에 격자형으로 배치되지만 아래로 갈수록 투명도가 높은 상태가 되었다.
이렇게 하면 스크립트의 일부만 수정하여 다양한 화면을 만드는 것이 가능합니다.
반복하여 붙여넣을 파일의 경로, 반복 간격, 투명화 정도를 설정하는 것으로 다른 이미지에도 동일하게 적용이 가능합니다. 본 예제에서는 1920 1080 사이즈의 이미지로 제작하였지만 다른 사이즈를 입력하여도 아마 정확하게 동작할 것입니다.
여기에 마지막으로 화면 가운데에 좀 더 커다란 로고를 넣는다면 어떻게 해야 할까요?
직접 한번 도전해 보시면 어떨까요?
[wallpaper] traxxas logo 1920 x 1080[wallpaper] traxxas UDR 1920 x 1080
TV 전원을 켜기위해 TV 리모컨을 찾아야 하고 안드로이드 TV 셋톱을 켜기위해 안드로이드 TV 리모콘을 역시 찾아야 하며 막상 틀었더니 소리가 어마무시하게 커서 블루투스 사운드 바 소리를 줄이기 위해 사운드바 리모콘을 찾아서 겨우 소리를 줄였더니 TV 기본 사운드가 너무 큰 탓인지 사운드바의 볼륨 1에서도 소리가 커서 하나를 줄이면 소리가 아얘 나지 않는 상황이어서 다시 TV 리모콘을 집어 들고 TV 볼륨을 줄였으나 알고보니 영상의 기본 사운드가 너무 크게 인코딩 되어 있어 안드로이드 TV 에서 볼륨 레벨을 낮추기 위하여 다시 안드로이드 TV 리모콘을 집어들어야 하는 상황이 거짓말처럼 하루건너 발생하는 우리집이다.
아놔..
끌때도 TV 끄고, 안드로이드 TV 끄고 사운드바 끄고...
이걸 한번에 통합하는 건 불가능 한 것일까..
그래서 한번 만들어 보기로 하였습니다.
아두이노를 이용하여 IR 리모콘 신호를 해킹하는 예제를 언젠가 본적이 있어 구글링을 하여 보았습니다.
가서보면 아래와 같은 페이지가 보이는데요. 우측에 'clone or download' 를 누르시게 되면 압축 파일로 다운로드가 됩니다.
이렇게 다운로드 받은 zip 파일을 압축을 해제한 뒤 아두이노 library 폴더에 넣으시면 되는데요. library 폴더는 아두이노가 설치된 폴더에도 있고 내문서에 Arduino 폴더에도 있습니다.
저는 설치폴더 찾아 들어가기가 귀찮아서 그냥 내문서에 있는 라이브러리 폴더에 넣었습니다.
이제 아두이노 IDE 를 실행해 봅니다. (아두이노 IDE가 켜진 상태에서 라이브러리를 추가 하였다면 모두 종료하고 재실행하면 라이브러리 및 예제가 추가되어 있는 것을 알 수 있습니다)
그런다음 예제에서 IRremote 로 들어가고 그 하위에 있는 IRrecvDumpV2 를 선택해 줍니다.
그러면 아두이노 IDE 에 예제 파일이 보이게 되는데요.
여기에서 추가로 설정해주어야 하는 것이 입력 핀 번호 수정입니다.
아래 그림과 같이 int recvPin 을 2번으로 변경해 줍니다. 2번은 나노의 D2 에 해당됩니다.
끝으로 IR 라이브러리를 추가해주어야 하는데요.
요렇게 해주면 준비가 완료 됩니다.
이제 브래드 보드에 리모콘 수신부를 연결하도록 하겠습니다.
아래 사진과 같이 연결해 줍니다.
적외선 수신부의 가장 좌측 다리를 D2 에 연결하고 10k 저항을 통해 5v 를 인가합니다.
가운데 다리는 GND 에 우측 다리는 5v 에 직접 연결합니다.
준비 완료.
아두이노 IDE 에 작성해놓은 코드를 업로드 합니다.
혹시 처음 하시는 분은 아래와 같이 설정을 하면 됩니다.
COM 포트는 사용자마다 다를거에요. 어떤 USB 포트에 연결했느냐에 따라 다른 번호가 나타납니다.
USB 를 연결한뒤에 새로 나타나는 포트가 아두이노가 연결된 포트입니다.
이제 신호를 테스트 해보겠습니다.
USB 를 연결하고 시리얼 모니터를 켠 후 리모콘의 버튼을 눌러 필요한 신호를 보내봅니다.
무려 세개의 리모콘이 이번 통합 대상이다.리모콘을 수신부를 향해 눌러준다.
그럼 시리얼 모니터에 아래와 같이 출력이 됩니다.
시리얼 모니터를 통하여 입력된 정보
중요한 것은 여기 빨간색 네모 박스의 내용입니다.
입력된 신호는 NEC 라는 회사의 프로토콜이며 0x807FAA55 라는 HEX 코드가 바로 입력된 신호인 것이죠. 위에 rawData 라는 정보가 보이는데 이는 NEC, LG 등과 같이 프로토콜의 제조사를 알 수 없는 리모콘신호인 경우 rawData 를 직접 보내기 위하여 사용되는 정보 입니다.
저의 경우에는 NEC 라는 제조사의 신호임이 확인되었기 때문에 해당 제조사에서 약속한 0x807FAA55 라는 값을 보내면 됩니다.
가끔 시리얼 모니터에 제조사 정보에 UNKNOWN 이라고 나오는 경우가 있습니다. 실제로 제품 사용중에 리모콘을 눌러도 신호가 전달이 되지 않는 경우가 있죠? 이렇게 신호가 불분명하게 입력되는 경우입니다. 다시 눌러보면 정확히 들어오게 되는데 이때 들어온 정보를 사용하면 됩니다.
이렇게 3개의 리모콘을 이용하여 제가 모아본 신호는 아래와 같습니다.
* tv (NEC)
* power : 0x2DF10EF, 32
* input : 0x2DFD02F, 32
* chnnel up : 0x2DF00FF, 32
* chnnel dn : 0x2DF807F, 32
* volumn up : 0x2DF40BF, 32
* volumn dn : 0x2DFC03F, 32
* OK : 0x2DF22DD, 32
* sw up : 0x2DF02FD, 32
* sw dn : 0x2DF827D, 32
* sw left : 0x2DFE01F, 32
* sw right : 0x2DF609F, 32
*
* android tv(NEC)
* power : 0x807F02FD, 32
* sw up : 0x807F6897, 32
* sw dn : 0x807F58A7, 32
* sw left : 0x807F8A75, 32
* sw right : 0x807F0AF5, 32
* sw OK : 0x807FC837, 32
* back : 0x807F9867, 32
* volumn up : 0x807F18E7, 32
* volumn dn : 0x807F08F7, 32
*
*
* lonpoo speaker
* power : 0x40BF807F, 32
* volumn up : 0x40BF50AF, 32
* volumn dn : 0x40BFD02F, 32
* bt : 0x40BFA05F, 32
* bt esc : 0x40BF906F, 32
* opt : 0x40BF20DF, 32
*
*/