바로 다운로드 받아서 보는 방법이 있죠. 인터넷에 보면 유튜브 주소를 넣으면 음원 파일이나 영상 파일로 다운로드 해주는 사이트 들이 많이 있습니다. 지저분한 광고로 도배가 되어 있는가 하면 정말 답답하게 느리기도 하고요. 뭔가 상당히 찜찜함을 남기는 UI 나 뭐 거시기 합니다.
그래서 광고 없고, 빠르고, 무료인 깔끔한 프로그램을 직접 만들어 보았습니다.
MUSIC DOWNTOWN
네 이름 그대로 뮤직 다운타운 입니다.
설치할 필요 없고요. 압축 풀고 실행파일 (musicdowntown.exe) 실행하시면 됩니다.
압축 파일을 열어 보시면 아래와 같이 3개의 실행 파일이 있을텐데요.
yt-dlp 는 유튜브영상이나 음악을 다운로드 해주는 정말 놀라운 프로그램이고요, ffmpeg 는 아시는 분은 아시겠지만 영상, 음악 분야의 신의 손 정도로 부를 수 있는 대단한 프로그램 입니다. 물론 둘다 command line 으로 사용해야 하는 프로그램이다 보니 약간 일반 사용자들에게 익숙한 프로그램은 아니죠.
대충 감 잡으셨을지도 모르겠지만 musicdowntown 은 사실 저 두개의 프로그램을 조금더 사용하기 편리하게 만들어주기 위하여 적당한 명령을 날려주는 역할을 하는 HMI(Human Machine Interface) 정도로 보시면 되겠습니다.
yt-dlp 와 ffmpeg 프로그램을 직접 사용하시기 위한 사용법이나 자세한 내용이 궁금하시면 아래 사이트를 방문해 주세요
와우! 놀랍지 않습니까? 여러개의 음악을 하나로 묶어둔 음원 자르는것도 자동으로 됩니다. 마음에 드는 플레이 리스트가 있다면 한번에 모두 다운로드 받으세요.
사용방법
먼저 유튜브 주소를 복사합니다. 주소 표시줄의 주소를 그냥 복사 하시면 됩니다. 현재 보고 있는 영상의 주소가 플레이 리스트인데 재생중인 영상만 다운로드 받고 싶다면 영상 아래쪽에 공유 버튼을 이용해서 주소를 복사해도 됩니다.
복사한 주소를 youtube 옆에 있는 칸에 붙여 넣습니다. 그리고 아래 그림처럼 아래 줄에 음원이나 영상이 저장될 PC 의 경로를 넣어 주세요.
주의사항!
저장될 경로에 한글이 있거나 공백이 있으면 저장이 잘 되지 않습니다. 가능하면 공백 없는 영문으로만 이루어진 경로를 이용해 주세요.
자 download 버튼을 누르셨가면 이제 다운로드 되기 시작할텐데요
처음에는 요렇게 2개의 파일이 생성되는데요. 다운로드가 완료되면 아래와 같이 하나만 남게 됩니다.
영상으로 받고 싶으시면 MUSIC 버튼을 누르시면 VIDEO 로 변경이 되며 download 실행 시 영상 파일(mp4)로 다운로드가 됩니다.
현재 듣거나 보고 있는 영상의 주소가 playlist 중의 일부라면 자동으로 플래이 리스트의 모든 파일이 다운로드 됩니다. 아무래도 공식 영상이나 음원에 신경쓴 자료들은 한곡씩 있는 경우가 많죠? 또 어떤 분이 멋진 플레이 리스트를 생성해서 공유한 경우도 있을 테고요. 또 내가 좋아하는곡들만 모아듣고 싶을 때도 있습니다. 이럴때 한번에 받을 수 있는 기능입니다.
위에도 적었지만 만약 그 중 한 파일만 받고 싶으시면 주소 표시줄의 주소가 아닌 영상 하단의 '공유' 버튼을 통해 생성된 링크를 입력하셔야 하나만 다운로드 됩니다.
저렇게 list라는 표시가 있으면 해당 주소는 플레이 리스트 주소를 담고 있습니다. 제가 좋아하는 guns n roses 의 음원 모음이네요. (너무 좋음)
다운로드 받은 경로와 주소를 모두 붙여넣은 뒤 download 버튼을 눌러주시면 끝
아래쪽 검은색 칸에 진행상황이 나오고요..
요렇게 자동으로 쭉쭉쭉 다운로드가 됩니다.
어떠신가요 ? 마음에 드시나요?? ㅎ
뭉터기 음원 자동 자르기
중간에 single/list 라고 되어 있는 버튼을 눌러 주시면 multiple 로 변경이 되는데요, 이때 아래에 있는 download 버튼이 download cutting 으로 변경이 되고요. 그 옆에 name prefix 라는 부분에 이름을 적을 수 있는 칸이 활성화 됩니다.
예를 들어 '옛날노래' 라고 적으시면 아래와 같이 자동으로 음악을 잘라주게 됩니다. ㅋㅋ
음원에 따라 dB, duration 을 조정해야 하는 경우도 있긴 하지만 제 경험상 대부분 그냥 사용하시면 잘 동작합니다.
개발을 하다 보면 실제 소스코드 외에도 어떤 텍스트를 resource 에 넣어 두고 소스코드에서 불러와야 하는 경우가 있을 수 있습니다. 저의 경우는 포토샵, 일러스트 등의 프로그램을 연동하여 c# 으로 App 을 만들었을때 실제 수행되는 코드는 jsx 스크립트 파일로 만든 뒤 app 에서 해당 스크립트를 실행하도록 하는데 이때 resource 에 스크립트 파일을 추가해 놓고 실행하는 타이밍에 해당 내용을 불러와 실행하도록 하는 경우가 종종 있습니다.
일단 c# 보다 javascript 로 작성하는게 쉽고 나중에 오류가 생기더라도 app 이 아닌 기능 단위로 만들어진 스크립트만 수정하면 되기 때문인데요.
보통 인터넷에 그런 내용으로 검색해 보면 실행파일이 빌드되는 순간 빌드 폴더에 해당 스크립트 파일이 복사되고 실행 파일 경로를 기준으로 하여 해당 파일을 불러와서 사용하도록 안내하고 있는 예제가 대부분입니다.
해당 스크립트(텍스트)를 굳이 공개하고 싶지 않고 스크립트 코드(텍스트 파일)가 빌드된 실행파일 내에 포함되면서 해당 텍스트를 불러오는 방법을 이번에 소개해 드리겠습니다.
두가지 방법이 있는데요. 두가지 방법을 모두 보시고 본인이 편한 방법을 사용하면 되겠습니다.
1. Properties > Resources.resx 내에 추가하는 방법
개발 디렉토리 내 임의의 폴더를 생성
생성된 임의의 폴더에 원하는 텍스트 파일 작성
작성된 텍스트 파일을 Properties > Resources 내에 텍스트 리소스로 추가
소스코드 내에 리소스를 텍스트(string) 로 가져오는 코드 작성
자 실제 화면을 보면서 설명을 드려보면요
위와같이 임의의 폴더를 하나 개발 디렉토리내에 생성한 뒤 필요한 텍스트 파일을 추가 합니다.
저의 경우는 포토샵 스크립트 파일 포멧인 jsx 로 추가하였고요. 좌측 내용을 보면 실행하면 그냥 "hello photoshop" 을 팝업으로 띄워주는 스크립트 입니다.
그 다음 Properties > Resources.resx 를 더블클릭해서 열어준 뒤 좌측 상단의 리소스 타입을 '파일'로 변경해 줍니다.
그런 다음 리소스 추가 를 누르시고 '기존 파일 추가' 를 선택해 줍니다.
그럼 파일 선택 창이 나타나는데요. 아까 만들어준 JS 폴더 ( 본인이 생성한 폴더) 에 가보시면 생성한 텍스트 파일이 있을 거고요. 해당 파일을 선택해 주시면 됩니다.
그럼 이렇게 파일이 들어와 있는 것을 보실 수 있는데요. 이 파일이 텍스트 파일이라고 확인을 시켜 주어야 합니다.
우측 아래 속성 텝에 보면 이 파일의 타입이 Binary 로 되어 있는 것을 Text로 변경해 주어야 합니다.
Text 로 변경하고 나면 위와 같이 인코딩을 지정할 수 있으니 나중에 텍스트 오류가 나면 해당 인코딩을 변경해가면서 사용하시면 됩니다.
자 이제 리소스로 추가 하였으니 불러오는 방법을 알아야 겠죠?
위와 같이 string 문자열을 만들어서 불러오면 됩니다. PSXL_AUTO 는 솔루션 명으로 namespace 에 지정된 이름을 사용하시면 됩니다.
그 뒤로는 Properties 부터 자동완성 되므로 상세한 설명은 생략
MessageBox.Show(myResourceText) 를 이용하여 리소스 텍스트 내용을 불러와보면
요렇게 잘 불러와 진것을 볼 수 있습니다.
물론 이렇게 하면 자동으로 포함리소스가 되므로 런타임에 별도로 파일을 찾아야 하거나 할 필요가 없습니다. 빌드 폴더에도 역시 별도로 파일이 나오거나 하지 않습니다.
간단하죠?
막상 해보면 간단합니다.
2. 그냥 개발 디렉토리 내에 폴더를 생성하고 파일을 추가하는 방법
임의의 폴더를 생성한다
생성한 폴더에 스크립트 파일을 작성한다
실행할 때 해당 스크립트를 불러오는 코드를 포함하여 실행한다.
요 방법도 제가 자주 사용하는 방법인데요. 별도로 리소스로 등록할 필요가 없어서 스크립트를 만들고 사용하는 과정에서 좀더 간단한 방법이라 할 수 있겠습니다.
물론 실행하는 부분에서 코드가 약간 복잡하기는 하지만 그래도 텍스트를 만들고 또 리소스에 추가하는 과정이 없어 좀더 직관적이라고 할까요?
이번에도 생성한 폴더에 스크립트 파일을 추가하였습니다.
내용을 구분하기 위하여 hellow 를 bye로 변경하였습니다 ㅎ
속성 텝에서 아래와 같이 포함리소스, 출력디렉토리에 복사 안함 으로 설정해 줍니다.
자 이제 코드를 불러오는 내용을 작성해야 하는데요. resources 에 추가한 내용을 불러오는 것은 아주 간단했죠?
요건 약간 복잡합니다. 아래 코드를 참고해 주세요.
private string showtextString()
{
string myStr = "";
var assembly = Assembly.GetExecutingAssembly();
var myScript = "PSXL_Auto.JS.defPSScript.jsx";
using (Stream stream = assembly.GetManifestResourceStream(myScript))
{
using (StreamReader sr = new StreamReader(stream))
{
myStr = sr.ReadToEnd();
}
}
MessageBox.Show(myStr);
return myStr;
}
// 또는
private string showtextString(string txtName)
{
string myStr = "";
var assembly = Assembly.GetExecutingAssembly();
var myScript = "PSXL_Auto.JS." + txtName;
using (Stream stream = assembly.GetManifestResourceStream(myScript))
{
using (StreamReader sr = new StreamReader(stream))
{
myStr = sr.ReadToEnd();
}
}
MessageBox.Show(myStr);
return myStr;
}
// 실행하는 부분에서
showtextString("defPSScript.jsx");
일단 간단하게 해당 리소스 텍스트를 string 으로 돌려주는 함수처럼 만들었는데요. 코드 내에 "JS" 라는 부분은 사용자가 만든 폴더 명칭이라는 것을 아실 수 있으시죠? 물론 아래와 같이 리소스 파일명을 인자로 받아 해당 텍스트를 리턴해 줄 수 있도록 하면 사용할 때도 간단하게 사용할 수 있을 것입니다.
요렇게 만들어진 함수를 실행해서 메시지 박스로 출력해 보면
당연히 잘 출력 됩니다.
중간에 bye 로 나오는 것 잘 보이시죠?
당연히 위의 두가지 방법 모두 실행 파일 경로에 해당 텍스트 파일이 따라오지 않고 잘 숨겨져 있게 됩니다.
프로그래밍을 하다보면 16진수를 10진수로 바꾸거나 10진수를 16진수로 바꾸어야 하는 경우가 종종 있습니다.
읭? 16 진수?
라고 하실수도 있겠지만 막상 개발을 하다 보면 16진수가 쓰이는 경우를 생각보다 자주 만나게 됩니다.
대표적을 RGB 컬러 값을 HTML 에서 사용하는 #FFEACB 이런 값으로 변경하는 작업이 있겠습니다. 또는 반대로 0 ~ FF 가지 255개의 숫자가 16진수로 구성되어 있는 값을 0~255인 자연수로 변경을 해야 하는 경우가 있을 수도 있고요. 16진수는 1개의 자릿수에서 0~15 까지의 값을 표현할 수 있기 때문에 10진수에 비하여 간단한 텍스트로 더 큰 숫자를 표현할 수 있다는 장점도 있습니다.
자 쓸데 없는 말이 길었네요.
VBA를 이용해서 숫자의 형을 변환하는 여러가지 방법이 있지만 오늘 소개해드릴 방법은 그중에서도 단연 쉽고 간단한 방법입니다.
바로 worksheet의 함수를 이용하는 방법인데요. 엑셀 상단에 수식을 입력할 수 있는 칸이 있자나요? 여기서 사용되는 함수가 바로 worksheet function 입니다.
worksheet function 중에 DEC2HEX() 와 HEX2DEC() 가 바로 오늘의 주인공 입니다.
함수 명칭만 봐도 딱 감이 오시죠?
사용하는 방법은 아래와 같습니다.
먼저 위와 같이 엑셀에 16진수 값이 들어있는 셀이 있다고 가정을 하고요.
vba 에디터를 열어 아래와 같이 코드를 작성해봅니다.
Sub changeNumber()
Dim rngA As Range
Set rngA = [A1]
rngA.Offset(0, 1).Value = WorksheetFunction.Hex2Dec(rngA.Value)
rngA.Offset(0, 2).Value = WorksheetFunction.Dec2Hex(rngA.Offset(0, 1).Value)
End Sub
자 A1 셀에 있는 값을 바로 우측 옆 칸에 10진수로 바꾸는 함수를 적용하여 값을 넣어 줍니다.
그다음 B1 에 위에서 자동으로 입력된 값을 다시 16진수로 옆칸에 자동으로 입력해주는 코드 입니다.
실행하면요
요렇게 B1 에는 10진수로, 다시 C1 에는 16진수로 값이 변경된 것을 볼 수 있죠?
worksheetFunction 에는 물론 엑셀에서 사용할 수 있는 모든 함수를 지원해 주니까 vba 작성중에도 가져다가 사용할 만한 함수가 많지만 오늘 포스트의 주제에 맞게 숫자의 형 변환 관점에서만 보자면 아래와 같은 여러 함수들이 지원됩니다.
물론 Hex 로 부터 출발 하는 경우에도 마찬가지 입니다.
문장이 조금 길어 보여서 어렵거나 복잡하게 생각될 수도 있기는한데 막상 사용해보면 생각보다 단순 합니다. 사용하는데 따로 알아두어야 할 규칙 같은것도 없어서 코딩하는 시간도 많이 절약되는 방식이라 할 수 있겠습니다.
만약 아래와 같이 셀에 html 색상 값이 들어있는 경우
vba를 이용하여 해당 색상으로 셀의 배경 색상을 자동으로 칠해 줄 수 있습니다.
Sub changeNumber()
Dim rngA As Range
Set rngA = [A1]
Dim clrR As Integer
Dim clrG As Integer
Dim clrB As Integer
clrR = WorksheetFunction.Hex2Dec(Mid(rngA.Value, 2, 2))
clrG = WorksheetFunction.Hex2Dec(Mid(rngA.Value, 4, 2))
clrB = WorksheetFunction.Hex2Dec(Mid(rngA.Value, 6, 2))
rngA.Interior.Color = RGB(clrR, clrG, clrB)
End Sub
간단하죠? 위에서 소개해 드린 worksheetFunction을 이용하여 2자리씩 잘라낸 16진수를 10진수로 담은뒤에 셀의 배경 색상으로 지정하는 것이죠.
제가 올리는 글을 누가 보는가 싶은데.. 간혹 스크립트를 잘 봐주시는 방문객들이 계신듯 하여 포스팅을 하게 되었습니다.
오늘은 3DS MAX SCRIPT의 계층 구조에 대한 이야기 입니다.
왠만한 개발을 해 보신 분들은 사실 약간 상식적으로 알고 계신 내용일 것 같기는 합니만 그래도 역시 누군가에게는 도움이 될 듯 하여 글을 남겨 봅니다.
3DS MAX Scritp 에서 어떤 오브젝트는 개발 관점에서 보면 클래스 입니다. 상위 클래스가 있고 또 하위 클래스가 있는데요. 계층 구조라고 말씀 드린것처럼 클래스 안에 또 작은 클래스가 정의 되어 있고 또 그안에 작은 클래스 또는 구조체가 정의 되어 있다고 보시면 됩니다.
클래스라고???
개발이 생소하신 분들은 클래스라고 하면 잘 이해가 안되시죠?
우리 인간을 예를 들어 클래스라는 개념을 설명해 보면 다음과 같습니다. (그냥 예 입니다)
각 단계는 상위 클래스의 하위 클래스가 됩니다.
동물 - 호흡을 한다. 심장이 있다.
포유류 - 4개의 팔 또는 다리를 가지고 있다. 아기를 낳는다. 젖을 먹인다.
영장류 - 지능이 뛰어나다. 두발 또는 네발로 걸을 수 있다. 시각 능력이 좋다
인간 - 특별히 뇌가 발달했다. 다양한 언어를 구사할 수 있다. 손의 움직임이 뛰어나다
조류 ...
양서류...
파충류...
뭐 이런식으로 어떤 큰 개념을 정의하고 그 하위에 상위의 개념을 포함하지만 조금 세분화된 개념을 담는 그런 방식으로 한단계 한단계 세분화된 그런 개념을 담고 있는 것이 일반적인 클래스의 개념입니다. (위에 설명한 건 그냥 예를 든 것입니다. 시비 노노)
예를 들면 익히 잘 알고 있는 Box 는
요런 순서의 계층 구조를 가지고 있습니다.
우린 레퍼런스 파일을 통해서 Box() 라는 클래스가 가지고 있는 다양한 프라퍼티에 접근을 할 수 있습니다.
그런데 위에 계층 구조를 보면 Box 위에 GeometryClass 라는 상위 클래스가 보이죠? 레퍼런스에서 해당 위치를 클릭해보면 GeometryClass 라는 녀석에 대한 정보가 나오는데요,
"The mesh operations underlying the Boolean Compound Object in 3ds Max are accessible in MAXScript."
라는 글이 해당 페이지의 첫줄에 보이고
<node1> + <node2>
<node1>-<node2>
<node1>*<node2>
요런 수식들이 소개가 되어있습니다. 내용을 읽어 보면 GeometryClass 는 + - * 와 같은 연산자로 boolean 을 수행할 수 있다는 의미가 되겠습니다. Box 는 GeometryClass 이므로 당연히 Box 와 Box 는 위와 같은 boolean 연산이 된다고 볼 수 있겠습니다.
요런 결과가 리턴 됩니다. 첫째 줄은 생성된 sphere 에 대한 정보이고요, 두번째가 바로 작성한 코드의 결과물인데요. 위의 설명에 따르면 face 개수와 vertex 개수를 돌려 준다고 합니다. 생성된 sphere 는 224개의 face 와 114개의 vertex 로 이루어져 있다는 것을 알 수 있습니다. 다시 말하자면 우리는 생성한 geometryclass 가 뭐든간에 해당 개체의 폴리곤수를 바로 알 수 있게 되는 겁니다. mesh 나 poly 같은 개체의 geometry 속성 정보를 알기 위하여 class 구분을 하고 getNumfaces 나 polyop.getNumVertexs 를 사용하는 것처럼 개체의 클래스나 구성 방식 등을 검사를 할 필요가 없는거죠.
Node
자 그럼 한칸 더 상위로 올라가서 Node 를 볼까요?
Node 는 3DS MAX Script 에서 아주 중요한 개념으로 해당 클래스에서 사용할 수 있는 프라퍼티나 메소드가 아주 많습니다. 스크립팅의 아주 기본적인 부분도 많이 보이네요.
아주 간단한 예를 들면 우리가 scene 에서 보여지는 모든 개체는 node 라는 클래스라고 보면 됩니다. EditableMesh 건 EditablePoly 건 Camera 건 심지어 helper 나 dummy 같은 개체도 모두 node의 하위 클래스 입니다.
즉 Node 라는 개체에 대한 설명이 적혀있는 레퍼런스 페이지에 있는 모든 하위요소를 사용할 수 있습니다.
제가 아~~주 자주 사용하는 node 의 대표적인 프라퍼티 또는 메소드를 보면 아래와 같습니다.
IsValidNode <var>
move <node> <point3> -- mapped
scale <node> <point3> -- mapped
rotate <node> <eulerangles> -- mapped
copy <node> -- mapped
reference <node> -- mapped
instance <node> -- mapped
delete <node> -- mapped
classOf <node>
딱 봐도 어떤 일을 하는지 아시겠죠? 우리가 알고있는 대부분의 요소는 node 이며 위와 같은 명령어를 이용하여 컨트롤 할 수 있답니다. 복사하고 이동하고, 회전하고 삭제하고, 해당 개체가 어떤 클래스인지 확인하는 그런 것들이죠.
Node Common Methods
라는 항목을 보면 Node 에서 사용할 수 있는 더 많은 상세한 레퍼런스들을 확인할 수 있습니다.
이렇게 말이죠.
물론 Node 자신의 subClass 에는 GeometryClass, Shape, Light, Camera, Helper, SpacewarpObject, System 이러한 요소들이 있고 각기 자신에 해당하는 프라펕와 메소드들을 다룰 수 있지만 역시 node 에서 정의하고 있는 모든 프라퍼티와 메소드를 사용할 수 있는 것 입니다.
드디어 최상위 클래스 : Value
그럼 최상위 Value 라는 개체로 가 볼까요?
Value 개체는 3DS MAX 에서 정의하고 있는 class 의 최상위 개체입니다. 말하자면 모든 구조체, 클래스 등은 Value 에 속하므로 Value 에서 정의하고 있는 기능을 수행할 수 있는 것이라 할 수 있겠습니다.
최상위인 value 에는 대표적으로 다음과 같은 항목들이 정의되어 있습니다.
<value> == <value>
<value> != <value>
익히 잘 알고 있는 bool 연산자죠? if 구분 같은 곳에서 대상과 대상이 같은지 다른지 판단할 수 있고요.
print <value>
format <format_string>
와 같은 코드를 통해서 해당 정보를 출력해 볼 수 도 있습니다.
Working with Values
라는 레퍼런스 페이지에 보면 스크립팅을 하는 동안 Value 를 어떻게 다룰 수 있는지 잘 소개가 되어있습니다.
스크립트는 다른 개발 언어와 다르게 빠르게 작성하고 빠르게 컴파일, 실행을 할 수 있기 때문에 이런 구조체를 알면 디버깅도 빠르고 작성도 손쉽게 할 수 있겠습니다.
Value 에는 node 와 같은 scene 내의 개체 뿐만 아니라 Float ,Integer ,String ,BitArray ,Point3 ,Ray ,Quat ,AngAxis ,EulerAngles ,Matrix3 ,Point2 ,Color ,Arrays 와 같은 클래스나 구조체들도 포함되어 있습니다. Ray 에 대해서 지난 포스팅에 소개해 드렸었죠?
일러스트로 디자인을 하다 보면 뒤에 사진이나 이미지를 앉혀야 하는 경우가 자주 있는데 보통 한번에 컨펌이 나지 않기 때문에 place 시킬때 link 형식으로 앉히게 됩니다.
배경 이미지의 시안이 변경 될 경우 이미지만 변경하면 자동으로 일러스트에서도 변경이 되기 때문이기도 하고 또 작업하는 파일의 용량이 작아 HDD 디스크에 부담도 적기 때문이지요.
그런데!!
이걸 이제 인쇄를 넘기려면 링크된 모든 파일을 다 찾는 것도 일이고.. 보냈을때 하나라도 누락이 되었다면 사고가 나는것이죠.
그래서 embed 형식으로 변경하면 ai 파일 내에 이미지가 포함이 되서 이런 문제가 일어나지 않는데요.
place 된 이미지가 한 두개면 괜찮은데 그 숫자가 좀 많을 때는 여간 귀찮고 번거로운 일이 아닐 수 없습니다.
이렇게나 많다면 말이죠.
또 파일을 보낸 뒤에 누락된 개체는 없는지 확인할 동안 기다려야 되는 문제도 있겠네요.
이럴때 스크립트가 출동한다면!!!
스!
크!
립!
트!
네. 바로 스크립트라면 간단히 해결을 할 수 있습니다.
바로 스크립트를 볼까요?
var cDoc = app.activeDocument
var pItems = cDoc.pageItems
for (var k = 0; k < pItems.length; k++)
{
var cItem = pItems[k]
if (cItem.typename == "PlacedItem")
{
cItem.embed()
}
}
엥?
엄청 짧죠?
네 그런데 이게 됩니다.
위의 내용을 텍스트 에디터에 붙여 넣고 jsx 형식으로 저장합니다.
잘 모르시겠으면 그냥 .txt 파일로 저장한 뒤에 확장자만 jsx 로 바꾸어 주어도 됩니다.
그런 다음 일러스트에서 이 스크립트를 실행 시키면 되는데요.
간단합니다. 두가지 방법이 있는데
1. 그냥 스크립트 파일을 일러스트 창 안으로 drag and drop 한다
2. File - Script - Other Script --> 만들어 두었던 스크립트 파일 선택
간단하죠?
요렇게 하면 place 된 개체가 어느정도이냐에 따라 다르지만 많아야 많아야 1~2분이면 될거에요.
결과를 볼까요 ? Ctrl + Y 를 해서 보면 link 형식으로 place 된 개체는 X 표시가 되는 것을 알수 있는데요.
요렇게요.
그럼 스크립트를 실행한 ai 파일을 Ctrl + Y 해서 볼까요?
네. 단하나의 누락 없이 모두 embed 로 변경이 되었습니다.
캬!
역시 배워야 합니다.
오늘은 이만 마치도록 하겠습니다.
다시한번 말씀 드리지만 위에 제가 소개해드린 스크립트를 메모장 등으로 옮기신 후 jsx 파일로 저장, 일러스트에서 실행하시면 됩니다.
선택한 셀에 이미지 파일명을 아래와 같이 shell 명령을 사용하면 이미지가 열리게 됩니다.
Dim wsh As Object
Set wsh = VBA.CreateObject("WScript.Shell")
wsh.Run imgName
전체 코드를 볼까요?
Sub showImage()
Dim aSht As Worksheet
Dim currentSelection As Range
Dim imgExtName As String
Dim imgName As String
Dim curext As String
Dim wsh As Object
Set wsh = VBA.CreateObject("WScript.Shell")
Set aSht = ActiveSheet
Set currentSelection = Selection.Cells()
imgExtName = ".png.jpg.jpeg.bmp.gif.webp.pct.ico...."
curext = LCase(Right(currentSelection.Value, 4))
If (InStr(imgExtName, curext)) Then ' 현재 선택한 셀 내용 중 이미지 확장자를 가지고 있으면
imgName = currentSelection.Offset(0, -1).Value & "\" & currentSelection.Value
If Dir(imgName) <> "" Then
wsh.Run imgName
End If
Else
MsgBox ("이미지 파일명이 들어있는 셀을 선택해 주세요")
End If
End Sub
선택한 셀이 이미지 파일명인지 아닌지는 확장자로 검사를 하도록 했습니다. imgExtName 이라는 문자열에 이미지 확장자들을 쭈욱 넣어둔 뒤에 실제 선택한 셀의 텍스트에서 뒤에서 4글자를 떼어낸 뒤에 위에 말씀드린 문자열 중에 포함되는지를 검사하는 것이죠.
아주 정교한 방식이라고 볼 수는 없지만 간단하게 구현할 수 있는 이미지 검출 방식이라 할 수 있겠습니다.
어쨌든 이렇게 이미지 파일명이 맞는지 확인한 뒤에 앞에있는 경로명과 결합해서 해당 이미지가 실제로 존재하는 이미지 인지 추가로 확인한 뒤에 shell 명령을 통해 이미지를 열어주게 됩니다.
저는 바로 왼쪽 옆칸에 경로가 있어 offset(0,-1) 과 같은 방법으로 연결하였지만 별도 셀에 이미지 경로가 들어있다면 해당 경로를 따로 지정해 주거나 스크립트 내에 문자열로 넣어 주셔도 되겠습니다.
이렇게 만들어진 매크로를 버튼에 연결하는 방법은
버튼을 최초 생성할때도 보였겠지만 매크로를 연결하는 메뉴가 있습니다. 단추에서 오른쪽 클릭한 다음 "매크로지정" 을 선택하면 매크로 선택창이 나타나게 되는데요, 여기서 지금 만들어준 스크립트를 선택해주면 됩니다.
자 이제 어떻게 동작하는지 한번 볼까요?
먼저 이미지 들이 들어있는 폴더가 있을 거고요. 만약 이미지가 아닌 셀을 선택하면 경고창을, 이미지 이름을 선택하면 윈도우 이미지 뷰어로 바로 열리게 됩니다.
자 간단하게 원하는 기능이 구현되었습니다.
필요하신 분들은 소스 복사하셔서 사용하시면 될 것 같고요. 잘 응용하셔서 본인의 엑셀 문서에 딱 맞는 기능으로 추가하시면 되겠습니다..
가끔 아니 어쩌면 자주 3D 공간안에서 물체의 위치를 지정할때 이 물체가 저쪽에 닿는 면에 딱 위치하고 싶은데... 할때가 있죠?
예를 들면 나무나 돌덩이 이런걸 랜덤하게 막 생성한 다음 굴곡진 지형에 모두 딱 안착시키고 싶다거나요.
또는 어떤 물체를 기준으로 어떤 방향으로 뻗어 나갈때 무엇인가 닿는 물체가 있는지 검사하고 싶을때도 있지요.
이때 사용되는 개체가 바로 Ray 입니다.
실제로 Ray를 알기 전과 알고난 뒤는 정말 다른 세계라고 할수 있겠습니다.
자 오늘은 Ray 에 대하여 알아보도록 하겠습니다.
3D 는 정말 놀라울 정도로 기하학적인 수학의 결정체라 할 수 있겠습니다. 이를 구현하기 위하여 정말 얼마만큼의 수학적 노력이 있었는지 상상이 안될 정도 입니다.
오늘은 그 결정체중의 하나인 Ray에 대하여 알아볼텐데요. 3DS MAX 에서 Ray 는 두가지 명령어를 통해 사용됩니다.
intersectRay 와 intersectRayEx, intersectRayScene 이렇게 세가지 입니다. 사용하는 목적에 따라 구분하여 사용하시면 되는데요. 각각은 아래와 같습니다.
intersectRay : Ray 와 대상물체를 지정한 뒤 부딧히는 위치를 알아낸다.
intersectRayEx : 위와 동일하며 부딪힌 Face 정보와 부딪힌 면의 normal 정보를 돌려준다
intersectRayScene : 대상 개체를 지정하지 않고 Scene 내의 어떤 물체든 부딪힌 정보를 돌려준다. (느림)
사용하는 방법은 간단한데요, 먼저 Ray 를 생성해주고 필요하면 Ray의 출발 위치(pos)와 방향(dir)을 변경할 수 있습니다.
myRay = ray [0,0,0] [0,0,-1]
ray 를 생성하는 구문 인데요, [0,0,0] 위치를 출발점으로 하고 [0,0,-1] 을 방향으로 하는 ray 가 만들어 지게 됩니다. [0,0,-1] 이라 하면 z 축을 기준으로 아래쪽을 바라보는 방향이라고 보면 되겠죠?
이렇게 만들어진 Ray를 위의 명령어와 함께 사용해볼까요?
먼저 위와 같은 Scene 을 만들어서 테스트 해보려 합니다. 노란색 공이 지면에 부딪히는 위치로 이동 시킬거에요. 중간에 네모 박스가 있지만 네모박스는 무시하고 지면에 위치시켜야 한다면 아래와 같이 intersectray 를 이용하여 위치시킬 수 있습니다.
-- ray 를 생성해 준다
myRay = ray [0,0,0] [0,0,-1]
-- 이동시킬 물체를 지정하자
obj = $Sphere001
-- ray 의 출발 위치를 물체의 중심으로 설정해보자
myRay.pos = obj.pos
-- 위치 검출에 사용될 지형을 지정해보자
tern = $terrain_01
-- intersectray 를 이용하여 지형과 부딪히는 위치를 찾아내자
interResult = intersectray tern myRay
-- 어떤 결과가 나왔는지 print 를 이용해서 살펴보자
print(interResult)
-- 부딪힌 위치에 오브젝트를 위치시키자
obj.pos = interResult.pos
요렇게 하면 어떻게 되냐면요
요렇게 노란 공이 지면에 달라 붙게 됩니다. 코드에 주석을 달아 놓았으니 따로 설명은 안드려도 될 것 같은데요. 한가지만 설명해 드리자면 intersectray의 결과값을 보면 (ray [-1.62331,11.1008,1.22501] [-0.407511,-0.107746,0.906822]) 요런 식으로 ray 의 형식으로 결과가 나옵니다. ray 는 pos 와 dir 의 속성이 있지 않습니까?? 여기서 결과값의 pos 정보가 바로 부딧힌 면의 위치 정보가 됩니다. 그 뒤에 나오는 [-0.407511,-0.107746,0.906822] 는 부딪힌 면의 normal 값으로 만약 공이 부딪힌 다음 튕겨나간다거나 하는 다음 동작이 필요하다면 반드시 필요한 정보이지요.
어쨌든 intersectray 만 보자면 부딪힌 위치와 각도를 ray 의 형태로 돌려준다는 것을 알 수 있었습니다. 특징이라 하면 , 특정 개체를 대상으로 하기 때문에 속도가 매우 빠릅니다. 사실상 성능을 고려하지 않아도 되는 함수죠.
그런데 만약 부딪힌 면이 어디인지 알아야 하고 부딪힌 개체에도 뭔가 추가적인 액션이 필요할 수 있겠습니다. 사람일은 모르는거자나요? 누군가는 저 지형개체에 어떤 face에 물체가 부딪혔는지를 알아야 할수도 있지 않겠습니까? 이 때 intersectrayex 라는 함수를 이용하게 됩니다.
이번에는 intersectray 대신에 intersectrayex 를 사용해 볼 겁니다. 사용방법은 동일하고요. intersectrayEx [node] [ray] 와 같이 사용해 주면 됩니다.
-- intersectray 를 이용하여 지형과 부딪히는 위치를 찾아내자
interResult = intersectrayex tern myRay
-- 어떤 결과가 나왔는지 print 를 이용해서 살펴보자
print(interResult)
-- 결과는?
#((ray [-1.62331,11.1008,1.22501] [-0.382998,-0.128105,0.914823]), 240, [0.383149,0.282177,0.334673])
아까와는 결과가 조금 다르죠? #(oo,oo,oo) 형태로 나온걸 보면 배열로 결과가 나온걸 알 수 있습니다. 총 3개의 리턴값이 나왔는데요.
intersectray 와 마찬가지로 첫번째 결과에는 부딪힌 면의 위치, 방향을 담는 ray 정보가, 두번째에는 어떤 숫자가 나왔고요, 세번째에는 point3 형식으로 좌표가 나왔는데, 딱보니 위에 ray 에 부딪힌 면의 방향이라는 것을 알 수 있겠습니다. 두번째 숫자는 뭐냐고요?
바로 부딪힌 mesh 의 face 인덱스 입니다. 240 번이죠? 바로 저부분에 부딪힌 것 입니다. (이번에는 이동은 하지 않았기 때문에..) 만약 동일하게 이동해야 한다면 배열 안의 개체에서 위치 정보를 뽑아내야 하므로 interResult[1].pos 와 같은 방법으로 부딪힌 면의 위치를 가져오면 됩니다.
이런 기능을 이용해서 부딪힌 면의 색상을 바꾸어 준다거나 해당 face를 삭제하는 등의 기능을 넣을 수 있겠지요? 뭐 레이저총에 맞은 face 가 폭파되며 사라지는 등의... 네.. 그럴때 꼭 필요한 훌륭한 명령어 입니다.
참고로 intersectray 나 intersectrayex 는 mesh 오브젝트에서 정확한 정보를 받아 올 수 있습니다. 가끔 기본 primatine 에 modifier 로 edit_poly 같은것을 씌운 경우에는 동작하지 않을 수 있으니 만약 결과값이 undefined 와 같은 값이 나온다면 검출에 사용된 모델링을 mesh 로 변경한 뒤 (convertTomesh obj) 검사를 진행하시면 됩니다.
끝으로 intersectRayScene 은 위와는 다르게 Ray 가 어떤 개체에 부딪히는지 알수 없을 때 사용합니다.
모두 동일하고 해당 명령만 intersectRayScene 으로 변경해서 돌려볼까요?
interResult = intersectrayScene myRay
요렇게 말이죠. 이번에는 대상을 특정하지 않기 때문에 함수 뒤에 인자가 ray 개체 하나만 들어갔습니다.
요렇게 3개의 개체와 부딪혔다는 정보를 돌려준 것을 알 수 있습니다. 심지어는 본인(sphere) 에도 부딪힌 정보를 돌려 받았다는 것을 알 수 있네요 ㄷㄷㄷ. (저걸 원한건 아닌데) 부딪힌 정보를 보면 역시 내부 정보 역시 배열인데요, 첫번째는 부딪힌 대상 개체, 두번째는 부딪힌 위치정보 (ray 속성) 라는걸 알 수 있습니다.
만약 개체에 어떤 특정할만한 속성이 있다면 해당 개체에 물체를 위치 시킬 수 있습니다.
-- ray 를 생성해 준다
myRay = ray [0,0,0] [0,0,-1]
-- 이동시킬 물체를 지정하자
obj = $Sphere001
-- ray 의 출발 위치를 물체의 중심으로 설정해보자
myRay.pos = obj.pos
-- 위치 검출에 사용될 지형을 지정해보자
tern = $terrain_01
-- intersectray 를 이용하여 지형과 부딪히는 위치를 찾아내자
interResult = intersectrayScene myRay
-- 어떤 결과가 나왔는지 print 를 이용해서 살펴보자
print(interResult)
for i = 1 to interResult.count do
(
testObj = interResult[i][1]
if (findString testObj.name "Box") != undefined then
(
obj.pos = interResult[i][2].pos
)
)
요렇게요.
코드에서는 Box 라는 이름을 포함하는 개체를 만나면 해당 개체에 위치하도록 하는 코드 입니다.
이게 사실 한두개일 때는 간단한 이야기 인데 지면에 위치 시켜야 하는 개체가 수십개 ~ 수백개가 되면 일이 커집니다. 그럴때는 스크립트가 필요한 시점인거죠.
예를 들면 이렇게요..
모두 sphere 라는 이름을 갖고 있기때문에 간단하게 처리할 수 있겠네요. 지면에 닿는 개체는 지면에 닿도록 하고 box 에 닿는 개체는 box 에 위치시키도록 하겠습니다.
sphere 의 중심을 기준으로 하다보니 약간 파뭍히는 대상이 있었는데요, sphere 의 반지름 만큼 위쪽으로 위치하도록 코드를 짜 보겠습니다.
-- ray 를 생성해 준다
myRay = ray [0,0,0] [0,0,-1]
-- 이름이 sphere 로 시작되는 모든 개체를 변수 objs 에 담자.
objs = execute("$Sphere*")
for obj in objs do
(
-- 각 sphere 의 중심점으로 ray 의 위치를 지정함
myRay.pos = obj.pos
-- intersectray 를 이용하여 지형과 부딪히는 위치를 찾아내자
interResult = intersectrayScene myRay
for i = 1 to interResult.count do
(
testObj = interResult[i][1]
if (findString testObj.name "Box") != undefined then
(
obj.pos = interResult[i][2].pos
-- 파묻히지 않도록 반지름 만큼 위로 올림
obj.pos.z += obj.radius
)
else if (findString testObj.name "terrain") != undefined then
(
--- 부딪힌 정보가 terrain 일때 해당 위치로 이동 시킴
obj.pos = interResult[i][2].pos
-- 파묻히지 않도록 반지름 만큼 위로 올림
obj.pos.z += obj.radius
)
)
)
뭐 별거 없죠? 위에서 설명한 것을 반복문만 추가했을 뿐 입니다.
결과는요?
요렇게 예쁘게 안착된 것을 확인 할 수 있습니다.
물론 저는 본 기능을 설명 드리기 위해 위와 같은 예제를 들었지만요. 실제 제가 사용하는 함수에는 몇초만에 수천번 이상 위치를 검출하며 ray 가 사용되는 케이스가 허다분 합니다.
저는 주로 자동으로 모델링을 할때 모델링의 위치, 크기, 배치, 부딪힘, 확장 범위 등을 검사할때 사용하는데요, 이런 검사들을 통해 사람이 손을 데지 않고도 아주 정교한 모델링을 할 수 있는 근간이 됩니다.
참고로 intersectRayScene 의 경우 scene 의 모든 개체에 대하여 검사가 이루어지기 때문에 Scene 내에 아주 많은(수만개 이상의) node 들이 자리잡고 있는 경우에는 속도가 매우 느려질 수 있습니다. 그래서 시작 위치를 기준으로 반경내에 들어오는 대상을 먼저 골라낸 다음 속도가 빠른 intersectRay 를 이용하여 각각 부딪힌 위치를 검출하는 방식으로 사용하고 있습니다. 프로그래밍이라는게 그렇자나요. ㅎ 높은 성능과 효율성을 위해 다양하고 창의적인 방법으로 접근해 가는게 바로 프로그래밍의 묘미이지요.
키움증권에서는 API 를 제공하여 사용자가 자동매수, 매도를 할 수 있는 프로그램을 직접 개발할 수 있도록 제공하고 있습니다.
키움증권 API 를 이용하여 시작하는 방법은 여러 포스트에서 이미 다루고 있으므로 별도로 다루지는 않을 예정입니다.
많이 어렵지 않으므로 관련 포스트를 따라서 하시면 손쉽게 시작하실 수 있으며 이미 샘플 c# 코드도 키움증권에서 배포하고 있으므로 일일이 내용을 작성하지는 않을 것이고요. 제가 인터넷에서 쉽게 구하지 못한 코드들을 올려보려 합니다.
오늘은 키움증권 API 가 제공하는 전체 종목코드를 받아오는 코드를 소개해 드릴까 합니다.
키움증권 API 가 동작하는 방식
필요한 커맨드를 인자와 함께 전달한다.
API 는 서버에서 해당 정보를 내려받는다. (이벤트가 발생하는 경우가 있고 직접 결과를 리턴하는 경우가 있다)
결과를 리턴하는 경우 : 바로 처리
이벤트를 발생시키는 경우 : 이벤트 핸들러를 이용하여 함수로 구현하여 처리
대충 이런식인데요. 전체 종목 코드를 받아오는 것은 결과를 바로 리턴해주는 방식 입니다.
키움증권에서 배포하는 open API 가이드를 보면 아래와 같은 커맨드가 있음을 알 수 있습니다.
바로 14번에 해당하는 GetCodeListByMarket 에 해당하는 커맨드죠.
해당 커맨드의 상세한 항목은 아래와 같이 나와 있습니다.
문자열로 결과가 리턴되는 것을 알 수 있고, sMarket 부분에 필요한 주식시장의 종류를 넣을 수 있게 되어 있네요.
전 "장내"에 해당하는 항목을 알아보려 합니다.
바로 한번 구현을 해볼까요?
private AxKHOpenAPILib.AxKHOpenAPI axKHOpenAPI; // 키움 샘플이라면 이건 적을 필요 없음.
private void getAllCodeList()
{
string codelistResult = axKHOpenAPI.GetCodeListByMarket("0"); // 0: 장내
//종목간 구분은 ";" 로 한다고 하였으므로 아래와 같이 각각의 종목을 쪼개 줍니다.
string[] mylist = codelistResult.Split(';');
foreach (string s in mylist)
{
Console.WriteLine("종목코드 : " + s);
}
}
간단하죠?
이렇게하고 getAllCodeList 함수를 실행해 보면 아래와 같이 출력창에 종목 코드가 출력됩니다.
그런데 이렇게 하면 전체 코드는 얻었지만 해당 코드가 어떤 종목인지 알수가 없잖아요?
그래서 몇줄 추가해 보겠습니다. 해당 코드의 종목 명칭과 전일가격을 출력해보도록 하겠습니다.
private void getAllCodeList(ref List<string> rList)
{
rList.Clear();
string codelistResult = axKHOpenAPI.GetCodeListByMarket("0");
string[] mylist = codelistResult.Split(';');
MessageBox.Show("전체 종목 수는 " + mylist.Length.ToString() + "개 입니다");
foreach (string s in mylist)
{
rList.Add(s); // rList 에 종목 코드를 넣어준다. (최종 결과 반환용)
string stName = axKHOpenAPI.GetMasterCodeName(s); // 종목 명칭 얻기
string lastPrice = axKHOpenAPI.GetMasterLastPrice(s); // 종목 전일가 얻기
Console.WriteLine("종목코드 : " + s + ", 종목 명:" + stName + ", 전일가 :" + lastPrice);
}
}
몇 줄 추가를 하였습니다. 먼저 rList 라는 리스트를 참조 인자로 받아서 나중에 사용하려고 합니다. 이전 코드와 달라진점은 메시지 팝업으로 전체 종목수를 알려주는 것과 종목 명칭, 종목의 전일가격을 받는 커맨드를 추가하여 콘솔창에 좀더 직관적으로 가격을 보여주도록 한 것이 달라진 점 입니다.
이렇게 하면 출력창에는 아래와 같이 결과가 출력됩니다.
어떠신가요? 좀 더 직관적으로 볼 수 있겠죠?
프로그램을 직접 만드는 만큼 재가 가지고 있는 관심 종목만 살펴보기 보다는 전체 종목들의 추이를 보면서 내가 발견하지 못한 좋은 종목을 컴퓨터에게 분석을 시켜보려고 하는데요. 그러기 위해서는 전체 종목을 자동으로 받아오는 함수가 반드시 필요할 것 같아서 구현해 보았습니다.
다음번에는 이벤트 핸들러를 이용하여 결과값을 처리하는 방법을 소개해 드려 보도록 하겠습니다.
스크립트 초급자든 중급자든 반드시 필요한 것 하나를 꼽으라면 저는 레퍼런스라고 생각합니다. 기존에는 3DS MAX 설치 시에 함께 설치되어 스크립트 작성 창에서 F1 키를 누르면 chm 파일 형식의 레퍼런스 파일이 나타나 간편하게 사용을 했는데요. 언제부터인가 온라인 레퍼런스 사이트로 연결이 되기 시작했습니다.
내용상 큰 차이가 없더라도 매번 인터넷 페이지 안에서 검색하고 찾는 것보다 로컬에 저장되어 있는 chm 파일에서 찾고 검색하는 것이 훨씬 빠르고 쉽기 때문에 전느 chm 포멧의 레퍼런스를 선호하는 편입니다.
사실 autodesk 사이트에도 chm 파일을 배포하고 있는데 굳이 온라인 페이지로 연결 시켰는지 잘 이해가 가지 않는 부분입니다.
아래 경로에가시면 chm 형식으로 제작된 최신 레퍼런스 파일을 받으실 수 있으니 참고하시기 바랍니다.