<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>td_junopark.log</title>
        <link>https://velog.io/</link>
        <description>공부 중🙄</description>
        <lastBuildDate>Thu, 28 Aug 2025 01:46:05 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <copyright>Copyright (C) 2019. td_junopark.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/td_dreamtree" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[Python File System 벤치마크]]></title>
            <link>https://velog.io/@td_dreamtree/Python-File-System-%EB%B2%A4%EC%B9%98%EB%A7%88%ED%81%AC</link>
            <guid>https://velog.io/@td_dreamtree/Python-File-System-%EB%B2%A4%EC%B9%98%EB%A7%88%ED%81%AC</guid>
            <pubDate>Thu, 28 Aug 2025 01:46:05 GMT</pubDate>
            <description><![CDATA[<h2 id="1-테스트-목적">1. 테스트 목적</h2>
<p><code>Python</code>을 통해 여러 스크립트를 작성하다보면, 특정 디렉토리를 재귀적으로 탐색하며 파일을 필터링 해야 하는 경우가 상당히 많이 있다.</p>
<p>과거의 코드를 보면 대부분 <code>os.listdir</code> 메서드와 <code>re</code> 라이브러리를 활용해 작성된 경우가 많은데, 이러한 경우 탐색 시간이 지나치게 오래걸리는 문제가 있었다.</p>
<p>때문에 <code>os.scandir</code>, <code>os.walk</code> 등 다양한 메서드를 벤치마크하는 간단한 스크립트를 작성해보았다.</p>
<h2 id="2-스크립트-작성">2. 스크립트 작성</h2>
<pre><code class="language-python">import os
import re
import glob
import time
import shutil
import random
import string
from pathlib import Path

class FolderScanBenchmark:
    def __init__(self, base_path=&quot;test_directory&quot;, max_depth=100, total_files=100000):
        self.base_path = base_path
        self.max_depth = max_depth
        self.total_files = total_files
        self.file_extensions = [&#39;.txt&#39;, &#39;.py&#39;, &#39;.js&#39;, &#39;.json&#39;, &#39;.csv&#39;, &#39;.log&#39;, &#39;.md&#39;, &#39;.xml&#39;]
        self.target_extensions = [&#39;.txt&#39;, &#39;.py&#39;, &#39;.json&#39;]

    def create_test_structure(self):
        if os.path.exists(self.base_path):
            shutil.rmtree(self.base_path)

        print(f&quot;테스트 디렉토리 구조 생성 중... (깊이: {self.max_depth}, 파일: {self.total_files}개)&quot;)

        dir_paths = [self.base_path]
        current_path = self.base_path
        for d in range(self.max_depth):
            dir_name = f&quot;dir_depth_{d}_{self.generate_random_name(5)}&quot;
            current_path = os.path.join(current_path, dir_name)
            os.makedirs(current_path, exist_ok=True)
            dir_paths.append(current_path)

        files_created = 0
        all_dirs = dir_paths.copy()
        while files_created &lt; self.total_files:
            target_dir = random.choice(all_dirs)
            file_ext = random.choice(self.file_extensions)
            file_name = f&quot;file_{files_created}_{self.generate_random_name(8)}{file_ext}&quot;
            file_path = os.path.join(target_dir, file_name)
            with open(file_path, &#39;w&#39;) as f:
                f.write(f&quot;Test file content {files_created}&quot;)
            files_created += 1

        print(f&quot;디렉토리 구조 생성 완료: {files_created}개 파일, 최대 깊이 {self.max_depth}&quot;)
        return files_created, self.max_depth

    def generate_random_name(self, length):
        return &#39;&#39;.join(random.choices(string.ascii_lowercase, k=length))

    def method1_scandir_re(self):
        &quot;&quot;&quot;방법 1: os.scandir + re 모듈로 필터링&quot;&quot;&quot;
        pattern = re.compile(r&#39;\.(txt|py|json)$&#39;, re.IGNORECASE)
        found_files = []

        def scan_recursive(path):
            try:
                with os.scandir(path) as entries:
                    for entry in entries:
                        if entry.is_file():
                            if pattern.search(entry.name):
                                found_files.append(entry.path)
                        elif entry.is_dir():
                            scan_recursive(entry.path)
            except (PermissionError, OSError):
                pass

        scan_recursive(self.base_path)
        return found_files

    def method2_glob_recursive(self):
        &quot;&quot;&quot;방법 2: glob.glob with recursive pattern&quot;&quot;&quot;
        found_files = []
        for ext in self.target_extensions:
            pattern = os.path.join(self.base_path, &quot;**&quot;, f&quot;*{ext}&quot;)
            found_files.extend(glob.glob(pattern, recursive=True))
        return found_files

    def method3_pathlib_glob(self):
        &quot;&quot;&quot;방법 3: pathlib.Path.rglob&quot;&quot;&quot;
        found_files = []
        base = Path(self.base_path)
        for ext in self.target_extensions:
            found_files.extend([str(p) for p in base.rglob(f&quot;*{ext}&quot;)])
        return found_files

    def method4_walk_filter(self):
        &quot;&quot;&quot;방법 4: os.walk + 확장자 필터링&quot;&quot;&quot;
        found_files = []
        target_exts = set(self.target_extensions)

        for root, dirs, files in os.walk(self.base_path):
            for file in files:
                if any(file.lower().endswith(ext) for ext in target_exts):
                    found_files.append(os.path.join(root, file))

        return found_files

    def method5_scandir_optimized(self):
        &quot;&quot;&quot;방법 5: os.scandir 최적화된 버전 (set lookup)&quot;&quot;&quot;
        target_exts = set(ext.lower() for ext in self.target_extensions)
        found_files = []

        def scan_recursive(path):
            try:
                with os.scandir(path) as entries:
                    for entry in entries:
                        if entry.is_file():
                            file_ext = os.path.splitext(entry.name)[1].lower()
                            if file_ext in target_exts:
                                found_files.append(entry.path)
                        elif entry.is_dir():
                            scan_recursive(entry.path)
            except (PermissionError, OSError):
                pass

        scan_recursive(self.base_path)
        return found_files

    def method6_listdir_re(self):
        &quot;&quot;&quot;방법 6: os.listdir + re 모듈로 필터링&quot;&quot;&quot;
        pattern = re.compile(r&#39;\.(txt|py|json)$&#39;, re.IGNORECASE)
        found_files = []

        def scan_recursive(path):
            try:
                for entry in os.listdir(path):
                    full_path = os.path.join(path, entry)
                    if os.path.isfile(full_path):
                        if pattern.search(entry):
                            found_files.append(full_path)
                    elif os.path.isdir(full_path):
                        scan_recursive(full_path)
            except (PermissionError, OSError):
                pass

        scan_recursive(self.base_path)
        return found_files

    def run_benchmark(self, iterations=3):
        methods = {
            &quot;os.scandir + re&quot;: self.method1_scandir_re,
            &quot;glob.glob recursive&quot;: self.method2_glob_recursive, 
            &quot;pathlib.rglob&quot;: self.method3_pathlib_glob,
            &quot;os.walk + filter&quot;: self.method4_walk_filter,
            &quot;os.scandir optimized&quot;: self.method5_scandir_optimized,
            &quot;os.listdir + re&quot;: self.method6_listdir_re
        }

        results = {}

        for method_name, method_func in methods.items():
            print(f&quot;\n{method_name} 테스트 중...&quot;)
            times = []
            files_found = None

            for i in range(iterations):
                start_time = time.perf_counter()
                found_files = method_func()
                end_time = time.perf_counter()

                elapsed = end_time - start_time
                times.append(elapsed)

                if files_found is None:
                    files_found = len(found_files)

                print(f&quot;  반복 {i+1}: {elapsed:.4f}초, {len(found_files)}개 파일&quot;)

            avg_time = sum(times) / len(times)
            min_time = min(times)
            max_time = max(times)

            results[method_name] = {
                &#39;avg_time&#39;: avg_time,
                &#39;min_time&#39;: min_time,
                &#39;max_time&#39;: max_time,
                &#39;files_found&#39;: files_found,
                &#39;times&#39;: times
            }

        return results

    def print_results(self, results):
        print(&quot;\n&quot; + &quot;=&quot;*80)
        print(&quot;벤치마크 결과 요약&quot;)
        print(&quot;=&quot;*80)

        sorted_results = sorted(results.items(), key=lambda x: x[1][&#39;avg_time&#39;])

        print(f&quot;{&#39;방법&#39;:&lt;25} {&#39;평균시간&#39;:&lt;10} {&#39;최소시간&#39;:&lt;10} {&#39;최대시간&#39;:&lt;10} {&#39;찾은파일&#39;:&lt;8}&quot;)
        print(&quot;-&quot; * 80)

        for method_name, data in sorted_results:
            print(f&quot;{method_name:&lt;25} {data[&#39;avg_time&#39;]:.4f}s   {data[&#39;min_time&#39;]:.4f}s   &quot;
                  f&quot;{data[&#39;max_time&#39;]:.4f}s   {data[&#39;files_found&#39;]:&gt;6}&quot;)

        print(&quot;\n상대적 성능 비교:&quot;)
        print(&quot;-&quot; * 50)

        fastest_time = sorted_results[0][1][&#39;avg_time&#39;]

        for method_name, data in sorted_results:
            ratio = data[&#39;avg_time&#39;] / fastest_time
            print(f&quot;{method_name:&lt;25}: {ratio:.2f}x&quot;)

    def cleanup(self):
        if os.path.exists(self.base_path):
            shutil.rmtree(self.base_path)
            print(f&quot;\n테스트 디렉토리 &#39;{self.base_path}&#39; 삭제 완료&quot;)

def main():
    benchmark = FolderScanBenchmark()

    try:
        files_created, max_depth = benchmark.create_test_structure()

        print(f&quot;\n벤치마크 시작 - 타겟 확장자: {benchmark.target_extensions}&quot;)
        results = benchmark.run_benchmark(iterations=5)

        benchmark.print_results(results)

        print(f&quot;\n추가 정보:&quot;)
        print(f&quot;- 총 파일 수: {files_created}&quot;)
        print(f&quot;- 최대 깊이: {max_depth}&quot;)
        print(f&quot;- 타겟 확장자: {&#39;, &#39;.join(benchmark.target_extensions)}&quot;)

    finally:
        benchmark.cleanup()

if __name__ == &quot;__main__&quot;:
    main()</code></pre>
<p>이 스크립트는 <code>os.scandir + re</code>, <code>glob.glob</code>, <code>pathlib.glob</code>, <code>os.walk + filter</code>, <code>os.scandir (set lookup)</code>, <code>os.listdir + re</code> 6가지 경우를 벤치마킹하도록 작성되었다.
각 메서드로 5회 반복 테스트하여 평균치로 속도를 비교했다.
추가로, 디렉토리 깊이는 100, 총 파일 수는 100,000으로 설정 후 비교했다.</p>
<h2 id="3-테스트-환경">3. 테스트 환경</h2>
<blockquote>
<p><code>OS</code>: Windows 10
<code>CPU</code>: AMD Ryzen 9 9900X 12-Core
<code>RAM</code>: 128GB</p>
</blockquote>
<br>

<h2 id="4-테스트-결과">4. 테스트 결과</h2>
<pre><code class="language-bash">테스트 디렉토리 구조 생성 중... (깊이: 100, 파일: 100000개)
디렉토리 구조 생성 완료: 100000개 파일, 최대 깊이 100

벤치마크 시작 - 타겟 확장자: [&#39;.txt&#39;, &#39;.py&#39;, &#39;.json&#39;]

os.scandir + re 테스트 중...
  반복 1: 0.3101초, 37282개 파일
  반복 2: 0.2960초, 37282개 파일
  반복 3: 0.2844초, 37282개 파일
  반복 4: 0.2887초, 37282개 파일
  반복 5: 0.2825초, 37282개 파일

glob.glob recursive 테스트 중...
  반복 1: 1.5839초, 37282개 파일
  반복 2: 1.6048초, 37282개 파일
  반복 3: 1.5780초, 37282개 파일
  반복 4: 1.5927초, 37282개 파일
  반복 5: 1.5675초, 37282개 파일

pathlib.rglob 테스트 중...
  반복 1: 1.9131초, 37282개 파일
  반복 2: 1.8693초, 37282개 파일
  반복 3: 1.8965초, 37282개 파일
  반복 4: 1.9497초, 37282개 파일
  반복 5: 1.9430초, 37282개 파일

os.walk + filter 테스트 중...
  반복 1: 0.3879초, 37282개 파일
  반복 2: 0.3843초, 37282개 파일
  반복 3: 0.3589초, 37282개 파일
  반복 4: 0.3991초, 37282개 파일
  반복 5: 0.3971초, 37282개 파일

os.scandir optimized 테스트 중...
  반복 1: 0.3413초, 37282개 파일
  반복 2: 0.3708초, 37282개 파일
  반복 3: 0.3340초, 37282개 파일
  반복 4: 0.3543초, 37282개 파일
  반복 5: 0.3554초, 37282개 파일

os.listdir + re 테스트 중...
  반복 1: 2.1937초, 37282개 파일
  반복 2: 2.1917초, 37282개 파일
  반복 3: 2.3004초, 37282개 파일
  반복 4: 2.2001초, 37282개 파일
  반복 5: 2.2650초, 37282개 파일

================================================================================     
벤치마크 결과 요약
================================================================================     
방법                        평균시간       최소시간       최대시간       찾은파일    
--------------------------------------------------------------------------------     
os.scandir + re           0.2923s   0.2825s   0.3101s    37282
os.scandir optimized      0.3511s   0.3340s   0.3708s    37282
os.walk + filter          0.3855s   0.3589s   0.3991s    37282
glob.glob recursive       1.5854s   1.5675s   1.6048s    37282
pathlib.rglob             1.9143s   1.8693s   1.9497s    37282
os.listdir + re           2.2302s   2.1917s   2.3004s    37282

상대적 성능 비교:
--------------------------------------------------
os.scandir + re          : 1.00x
os.scandir optimized     : 1.20x
os.walk + filter         : 1.32x
glob.glob recursive      : 5.42x
pathlib.rglob            : 6.55x
os.listdir + re          : 7.63x

추가 정보:
- 총 파일 수: 100000
- 최대 깊이: 100
- 타겟 확장자: .txt, .py, .json

테스트 디렉토리 &#39;test_directory&#39; 삭제 완료</code></pre>
<blockquote>
<p>테스트 결과, <code>os.scandir (set lookup)</code> 방식이 가장 빨랐다.</p>
</blockquote>
<p>확실히 os.listdir의 경우 오버헤드가 큰 오래된 메서드다 보니 속도가 확연히 느린 것을 알 수 있다.
추가로, 조건을 변경하여 테스트 해본 결과 최대 깊이와 파일 수가 늘어날수록 <code>os.scandir + re</code>가 우세했다.</p>
<br>

<h2 id="5-결론">5. 결론</h2>
<p>양이 많지 않은 디렉토리를 탐색하는 경우엔 <code>os.listdir</code> 메서드를 사용하면 되겠으나,
테스트 결과 디렉토리 깊이가 1이고 파일 수가 100,000인 경우에도 여전히 <code>os.listdir</code>가 가장 오래 걸리는 것을 확인했다.</p>
<p><code>Python</code>을 다루는 다양한 커뮤니티에서도 <code>os.listdir</code>은 과거의 유산이라고 할 정도이니 되도록 사용하지 않는 것이 좋을 것 같다.</p>
<p>추가로, <code>os.scandir</code>의 경우 <code>is_file()</code>이나 <code>is_dir()</code>처럼 파일 엔트리에 접근할 때 추가적인 오버헤드가 발생하지 않기 때문에 일반적인 환경에서 성능이 훨씬 뛰어나다고 볼 수 있겠다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[ Rocky9 OpenStack 설치]]></title>
            <link>https://velog.io/@td_dreamtree/Rocky9-OpenStack-%EC%84%A4%EC%B9%98</link>
            <guid>https://velog.io/@td_dreamtree/Rocky9-OpenStack-%EC%84%A4%EC%B9%98</guid>
            <pubDate>Wed, 26 Feb 2025 06:47:03 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/td_dreamtree/post/2f306fd2-f65b-4d70-b324-e211497600ad/image.png" alt=""></p>
<p>클라우드 기반 컴퓨팅을 위한 오픈소스인 <code>OpenStack</code>을 활용하기 위해 먼저 <code>Packstack</code>을 통해 <code>OpenStack</code>을 설치하는 방법에 대해 알아보자.</p>
<h2 id="1-기본-요구사항">1. 기본 요구사항</h2>
<h4 id="openstack-설치-전-현재-시스템이-기본-요구사항에-적합한-지-확인하자">OpenStack 설치 전, 현재 시스템이 기본 요구사항에 적합한 지 확인하자.</h4>
<blockquote>
<ol>
<li>가상화를 지원하는 CPU</li>
<li>16GB RAM (권장)</li>
<li>한 개 이상의 네트워크 어댑터</li>
<li>고정 IP</li>
</ol>
</blockquote>
<p>필자의 경우 <code>VMWare</code>를 통해 <code>Rocky9</code>을 VM으로 올린 후 <code>OpenStack</code>설치를 진행했다.</p>
<br>

<h2 id="2-설치-준비">2. 설치 준비</h2>
<p>요구 사항에 적합한 시스템을 갖추었다면, 설치를 위한 준비를 하자.
<strong>모든 과정은 <code>sudo</code> 혹은 <code>su</code>를 통해 관리자 권한을 가진 상태에서 진행했다.</strong></p>
<h3 id="1-네트워크-설정">1) 네트워크 설정</h3>
<p><code>OpenStack</code>의 경우 자체적인 네트워크 인터페이스를 설정하고 관리하기 때문에, <strong>방화벽과 NetworkManager</strong>의 <strong>비활성화</strong>가 필요하다.</p>
<p><strong>a. SELINUX 상태 확인 후 비활성화</strong></p>
<pre><code class="language-sh">getenforce  //기댓값 = disabled</code></pre>
<p>만약 해당 값이 <code>disabled</code>가 아니라면, <code>/etc/sysconfig/selinux</code> 파일을 열어 <code>SELINUX=</code>값을 <code>disabled</code>로 수정하도록 하자.</p>
<p><strong>b. 방화벽 비활성화</strong></p>
<pre><code class="language-sh">systemctl disable firewalld
systemctl stop firewalld</code></pre>
<p><strong>c. NetworkManager 비활성화</strong></p>
<pre><code class="language-sh">systemctl disable NetworkManger
systemctl stop NetworkManger</code></pre>
<p><strong>d. network-scripts 설치 및 활성화</strong></p>
<pre><code class="language-sh">dnf install -y network-scripts

systemctl enable network
systemctl start network</code></pre>
<h3 id="2-mariadb-설치">2) MariaDB 설치</h3>
<p><code>OpenStack</code>의 컴포넌트인 <code>Keystone</code>, <code>Glance</code>, <code>Nova</code> 등에서 DB를 활용한다.
필자는 <code>MariaDB</code>를 설치해 활용했다.</p>
<pre><code class="language-sh">dnf install -y mariadb mariadb-server python3-PyMySQL

systemctl enable mariadb.service
systemctl start mariadb.service</code></pre>
<p>설치 후 서비스 실행까지 완료되었다면, <code>mysql_secure_installation</code> 명령어를 사용해 <strong>DB보안 프로그램을 적용</strong>하자.</p>
<pre><code class="language-sh">/usr/bin/mysql_secure_installation</code></pre>
<p>해당 스크립트를 실행하면 <code>root</code> 비밀번호를 지정하고 여러가지 설정을 변경할 수 있다.
본인에게 맞게 설정하자.</p>
<p>여기까지 완료되었다면 DB에 정상적으로 <strong>접속이 되는지 확인</strong>하자.</p>
<pre><code class="language-sh">mysql -uroot -p  //root 비밀번호 입력</code></pre>
<h3 id="3-openstack-릴리즈-및-packstack-설치">3) OpenStack 릴리즈 및 PackStack 설치</h3>
<p><strong>a. OpenStack 릴리즈 설치</strong>
<code>OpenStack</code>의 경우 업데이트가 이루어질 때마다 <code>Series</code>가 변경되어 릴리즈된다.
현재 시스템에서 사용할 수 있는 시리즈는 아래 명령어를 통해 검색할 수 있다.</p>
<pre><code class="language-sh">dnf search centos-release-openstack</code></pre>
<p>필자의 경우 2024년 2월 릴리즈된 <code>dalmatian</code> 시리즈를 설치했다.</p>
<p><img src="https://velog.velcdn.com/images/td_dreamtree/post/81e50886-d225-4f2a-bff9-6deb75843cb5/image.png" alt=""></p>
<pre><code class="language-sh">dnf install -y centos-release-openstack-dalmatian</code></pre>
<p><strong>b. PackStack 설치</strong>
추가로 <code>Packstack</code>도 설치한다. <code>Packstack</code>은 <code>OpenStack</code>의 컴포넌트를 일괄적으로 설치하기 위한 <code>RedHat</code> 기반의 자동화 설치 툴이다.</p>
<pre><code class="language-sh">dnf install -y openstack-packstack</code></pre>
<p><strong>c. PackStack 설치 정보 파일 생성</strong>
<code>PackStack</code>으로 <code>OpenStack</code>을 설치할 때 <strong><code>answer.txt</code>를 통해 다양한 옵션을 커스터마이즈</strong> 할 수 있는데, 
이를 위한 파일을 생성하고 본인에게 맞게 옵션을 변경하자. 필자의 경우 <code>/root</code> 경로에서 해당 파일을 생성했다.</p>
<pre><code class="language-sh">packstack --gen-answer-file answer.txt</code></pre>
<p><strong>d. PackStack을 통해 OpenStack 설치</strong>
이제 생성된 <code>answer.txt</code>를 통해 <code>OpenStack</code>을 설치하자.</p>
<pre><code class="language-sh">packstack --answer-file answer.txt</code></pre>
<p>설치 완료까지는 20-30분 정도가 소요된다.
설치 과정에서 다양한 오류가 발생할 수 있는데, <strong>설치 시작 시에 표시되는 경로</strong>에 로그가 상세히 기록되니 확인하며 오류를 수정하자.</p>
<pre><code class="language-sh">The installation log file is avilable at: /var/tmp/packstack/20250226-145502-4d3s5gh2/openstack-setup.log</code></pre>
<h3 id="4-dashboard-접속">4) Dashboard 접속</h3>
<p><img src="https://velog.velcdn.com/images/td_dreamtree/post/1a3fec7b-cb64-43c5-a224-a02ee044b006/image.png" alt=""></p>
<p>설치가 정상적으로 완료되었다면 <code>http://{Host_IP}/dashboard</code>에 접속해 <code>OpenStack</code>에 <code>dashboard</code>에 접근할 수 있다.
<strong>초기 admin계정과 demo계정 및 비밀번호는 <code>keystonerc_admin</code>, keystonerc_demo에 기입되어 있다.</strong></p>
<br>

<h2 id="마무리">마무리</h2>
<p>여기까지가 <code>PackStack</code>을 통한 <code>OpenStack</code> 기본 설치 과정이었다. 이제 <code>OpenStack</code>을 실행해 다양한 작업을 해보도록 하자.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Nuke - CopyCat]]></title>
            <link>https://velog.io/@td_dreamtree/Nuke-CopyCat</link>
            <guid>https://velog.io/@td_dreamtree/Nuke-CopyCat</guid>
            <pubDate>Tue, 25 Feb 2025 06:21:07 GMT</pubDate>
            <description><![CDATA[<p>**
Nuke13.0 버전부터 제공되는 Node로, 머신러닝을 통해 사용자가 직접 모델을 훈련시켜 다양한 작업에 활용할 수 있도록 지원하는 도구이다.**</p>
<blockquote>
<p><a href="https://learn.foundry.com/nuke/content/reference_guide/air_nodes/copycat.html">CopyCat 공식 링크</a></p>
</blockquote>
<h2 id="작성-환경">작성 환경</h2>
<ul>
<li><p>NukeX 15.1v5 (Nuke13.0 부터 지원)</p>
<blockquote>
<p><a href="https://youtu.be/5md3BvOI-9U?si=kZVKFKG0ZXuGq8CA">CopyCat 업데이트 관련 영상</a></p>
</blockquote>
</li>
<li><p>Windows 10</p>
</li>
</ul>
<br>

<h2 id="주요-기능">주요 기능</h2>
<ul>
<li><p><strong>가비지 매트 생성 (Garbage Matte):</strong></p>
<blockquote>
<p><a href="https://youtu.be/OEaqtZ0_7jQ?si=4RdTe2dm1uElA7Hz">https://youtu.be/OEaqtZ0_7jQ?si=4RdTe2dm1uElA7Hz</a></p>
</blockquote>
<p>  일부 프레임에서 수동으로 매트를 생성하고, 이후 CopyCat을 사용하여 전체 시퀀스에 동일한 매트를 자동으로 생성합니다.
  이를 통해 일관성 있는 매트를 빠르게 생성할 수 있습니다.</p>
</li>
<li><p><strong>뷰티 작업 (Remove, Beauty)):</strong></p>
<blockquote>
<p><a href="https://youtu.be/pwe6WakkAuc">https://youtu.be/pwe6WakkAuc</a></p>
</blockquote>
<p>  피부 결점 제거, 얼굴 미세 조정 등의 뷰티 작업을 일부 프레임에 적용한 후, CopyCat을 사용하여 전체 시퀀스에 자동으로 적용합니다. 
  시간이 오래 걸리는 뷰티 작업을 자동화하여 효율성을 높입니다.</p>
</li>
<li><p><strong>디블러링 (Deblur):</strong></p>
<blockquote>
<p><a href="https://youtu.be/AZOoXtye1og?si=wOo-RqK78afUR6C1">https://youtu.be/AZOoXtye1og?si=wOo-RqK78afUR6C1</a></p>
</blockquote>
<p>  특정 프레임에서 블러를 제거한 후, CopyCat을 사용하여 동일한 디블러링 작업을 전체 시퀀스에 적용합니다. 
  이 작업은 훈련된 모델을 사용하여 블러를 정확하게 제거할 수 있습니다,</p>
</li>
</ul>
<br>

<h2 id="노드node-설명">노드(Node) 설명</h2>
<p><img src="https://velog.velcdn.com/images/td_dreamtree/post/4604536d-9d03-46fb-a342-d6492943b802/image.png" alt=""></p>
<ul>
<li><strong>CopyCat Node Input</strong><ul>
<li><strong>Input</strong>: 효과 적용 전의 원본 데이터</li>
<li><strong>Ground Truth</strong>: 효과 적용 후의 기대 결과 데이터</li>
</ul>
</li>
</ul>
<p>CopyCat 노드는 <strong>Input</strong>과 <strong>Ground Truth</strong>를 입력받아 효과 적용 전후의 관계를 학습합니다.</p>
<p>두 값은 반드시 동일한 포맷과 크기를 유지해야 하며, 다양한 데이터셋을 활용하면 모델의 일반화 성능이 향상됩니다.</p>
<br>

<h2 id="옵션knob-설명">옵션(Knob) 설명</h2>
<h3 id="copycat-tab">CopyCat Tab</h3>
<p>CopyCat 노드에서 진행할 훈련에 대한 설정들이 모여있는 탭</p>
<p><img src="https://velog.velcdn.com/images/td_dreamtree/post/35ea4c75-5efd-45f8-a65c-8caadc6e4c56/image.png" alt=""></p>
<p><strong>Basic (기본):</strong></p>
<ul>
<li><strong>Local GPU (로컬 GPU)</strong>: 사용 가능한 경우, 렌더링에 사용되는 GPU를 표시합니다.</li>
<li><strong>Use GPU if available (사용 가능한 경우 GPU 사용)</strong>: 활성화하면, 사용 가능한 경우 GPU를 사용하여 렌더링합니다.</li>
<li><strong>Data Directory (데이터 디렉토리)</strong>: CopyCat이 결과물과 <code>.cat</code> 파일을 저장하는 위치를 설정합니다.</li>
<li><strong>Epochs (학습 횟수)</strong>: 데이터 세트를 학습하는 횟수를 설정합니다. 높은 값은 더 나은 결과를 제공할 수 있지만, 처리 시간이 길어집니다.</li>
<li><strong>Channels (채널)</strong>: Input과 Ground Truth 간의 채널을 설정합니다.</li>
<li><strong>Batch Size (그룹 크기)</strong>: 전체 데이터 셋 대비 소그룹에 해당하는 데이터 세트의 수를 의미합니다. 가용한 GPU 메모리를 사용해 자동으로 계산됩니다.</li>
<li><strong>Total Steps (총 스텝)</strong>: 지정된 Epochs를 완료하는데 필요한 단계의 값을 표시합니다.</li>
<li><strong>Start Training (학습 시작)</strong>: 현재 설정을 사용하여 네트워크 학습을 시작합니다.</li>
<li><strong>Resume Training (학습 재개)</strong>: 이전에 저장된 체크포인트에서 학습을 재개합니다.</li>
<li><strong>Create Inference (추론 생성)</strong>: 현재 CopyCat 노드의 <code>.cat</code> 파일을 참조하는 Inference 노드를 생성합니다.</li>
</ul>
<p><strong>Advanced (고급)</strong>:</p>
<ul>
<li><strong>Initial Weights (초기 가중치)</strong>: 학습 시작 시 사용할 초기 가중치를 설정합니다.<ul>
<li><strong>None (없음)</strong>: 가중치 없이 처음부터 학습을 시작합니다.</li>
<li><strong>Checkpoint (체크포인트)</strong>: 이전에 학습된 <code>.cat</code> 파일의 가중치를 사용하여 학습을 시작합니다.</li>
<li><strong>Deblur (디블러)</strong>: 디블러(흐림 제거) 효과에 최적화된 가중치를 사용하여 학습을 시작합니다.</li>
<li><strong>Upscale (업스케일)</strong>: 업스케일링에 최적화된 가중치를 사용하여 학습을 시작합니다.</li>
<li><strong>Human Matting (휴먼 매팅)</strong>: 사람의 매팅 작업에 최적화된 가중치를 사용하여 학습을 시작합니다.</li>
</ul>
</li>
<li><strong>Checkpoint File (체크포인트 파일)</strong>: Initial Weights가 Checkpoint로 설정된 경우, 사용할 <code>.cat</code> 파일의 경로를 지정합니다.</li>
<li><strong>Model Size (모델 크기)</strong>: Small, Medium, Large 옵션이 있으며, 모델의 크기가 커질수록 더 나은 결과를 도출할 수 있지만 더 많은 메모리를 사용하고 더 오랜 시간이 걸릴 수 있습니다.</li>
<li><strong>Batch Size (배치 크기)</strong>: 각 Epoch에서 학습에 사용할 이미지 쌍의 수를 설정합니다. 기본적으로 GPU 메모리를 기반으로 자동 계산되며, 수동으로 설정할 수도 있습니다. 일반적인 스크립트에서는 4에서 16 사이의 값을 권장합니다.</li>
<li><strong>Crop Size (크롭 크기)</strong>: 학습 중 사용할 이미지 크기를 설정합니다. 기본값은 256입니다.</li>
<li><strong>Checkpoint Interval (체크포인트 간격)</strong>: 학습 과정에서 모델의 상태를 <code>.cat</code> 파일로 저장하는 주기를 설정합니다. 예를 들어, <code>1000</code>으로 설정하면 학습 중 매 1000 스텝마다 체크포인트가 저장됩니다.</li>
<li><strong>Contact Sheet Interval (컨택트 시트 간격)</strong>: 학습 진행 상황을 모니터링하기 위해 출력 결과를 포함한 컨택트 시트를 <code>.png</code> 파일로 저장하는 주기를 설정합니다. 기본값은 <code>100</code> 스텝마다 저장되며, 이 값은 사용자가 조정할 수 있습니다.</li>
<li><strong>Use Multi-Resolution Training (다중 해상도 학습 사용)</strong>: 이 옵션을 활성화하면, 모델이 입력과 실제 값을 다양한 해상도로 학습하여 더 효율적인 학습을 할 수 있습니다. 학습 초기에는 낮은 해상도로 시작하여 점진적으로 해상도를 높여가며 학습합니다.</li>
</ul>
<br>

<h3 id="progress-tab">Progress Tab</h3>
<p>로컬 및 원격으로 진행 중인 분산 또는 단일 훈련의 정보를 표시하는 탭</p>
<p><img src="https://velog.velcdn.com/images/td_dreamtree/post/2ec538a9-91be-4563-becb-36631250d379/image.png" alt=""></p>
<p><strong>Progress (진행도):</strong></p>
<ul>
<li><strong>Runs (실행)</strong>:<ul>
<li><strong>Run Table</strong>: 데이터 디렉토리에서 모든 학습 실행 데이터를 표시하며, 각 실행의 곡선을 개별적으로 활성화하거나 비활성화할 수 있습니다. 
데이터 디렉토리 내 파일 이름을 더블 클릭하여 원하는 이름으로 변경할 수 있습니다.</li>
</ul>
</li>
<li><strong>Live Updates (실시간 업데이트)</strong>:<ul>
<li>활성화되면 분산 학습 네트워크에서 실시간 학습 데이터를 가져와 학습 진행 상황을 확인할 수 있습니다.</li>
<li>기본적으로 로컬 머신에서 학습할 때는 이 기능이 영향을 미치지 않습니다.</li>
</ul>
</li>
<li><strong>Graph (그래프)</strong>:<ul>
<li><strong>Log Scale (로그 스케일)</strong>:<ul>
<li>그래프의 y축을 선형에서 로그 스케일로 변환하여, 학습 초기 값에서 더 많은 세부사항을 확인할 수 있습니다.</li>
</ul>
</li>
<li><strong>Smoothness (부드러움)</strong>:<ul>
<li>손실 곡선의 부드러움을 조절합니다. 값이 낮으면 데이터 포인트가 더 정확해지지만 전체적인 추세를 읽기 어려울 수 있습니다.</li>
</ul>
</li>
<li><strong>Show Original Curve (원본 곡선 표시)</strong>:<ul>
<li>원본 그래프(스무딩 전)를 표시합니다. 이를 통해 원본과 부드러운 그래프를 동시에 비교할 수 있습니다.</li>
</ul>
</li>
</ul>
</li>
<li><strong>Graph (그래프) 표시</strong>:<ul>
<li>지정된 데이터 디렉토리에서 학습의 Step/Loss 데이터를 표시합니다.</li>
<li>그래프 상단에 있는 확대/축소 컨트롤을 사용하여 실시간으로 학습 상태를 모니터링할 수 있습니다.</li>
</ul>
</li>
</ul>
<br>

<h3 id="python-tab"><strong>Python Tab</strong></h3>
<p>훈련 진행 도중 특정 이벤트에 따라 자동으로 파이썬 함수를 호출하는 탭</p>
<p><img src="https://velog.velcdn.com/images/td_dreamtree/post/41a3b6df-2f32-4c7f-8385-bbad50dc9929/image.png" alt=""></p>
<ul>
<li><strong>before render (렌더링 시작 전)</strong>: <code>execute()</code> 함수가 실행되기 전에 호출되는 함수들입니다. 예외가 발생하면 렌더링이 중단됩니다.</li>
<li><strong>before each frame (각 프레임 렌더링 전)</strong>: 각 프레임 렌더링을 시작하기 전에 호출되는 함수들입니다. 예외가 발생하면 렌더링이 중단됩니다.</li>
<li><strong>after each frame (각 프레임 렌더링 후)</strong>: 각 프레임 렌더링이 완료된 후 호출되는 함수들입니다. 렌더링이 중단되면 호출되지 않으며, 예외가 발생하면 렌더링이 중단됩니다.</li>
<li><strong>after render (렌더링 완료 후)</strong>: 모든 프레임 렌더링이 끝난 후 호출되는 함수들입니다. 예외가 발생하면 렌더링이 중단됩니다.</li>
<li><strong>render progress (렌더링 진행 상태)</strong>: 렌더링 진행 중에 호출되는 함수들로, 진행 상태나 실패 여부를 확인할 수 있습니다.</li>
</ul>
<br>

<h2 id="copycat의-기대효과">CopyCat의 기대효과</h2>
<ol>
<li><p><strong>효율적인 반복 작업 자동화</strong>:</p>
<ul>
<li><strong>학습된 모델 활용</strong>: CopyCat 노드는 기존의 학습된 모델을 사용하여 반복적이고 시간이 많이 소요되는 작업을 자동화할 수 있습니다.</li>
<li><strong>훈련된 모델을 통한 정확도 향상</strong>: 여러 훈련된 모델을 선택하여 특정 효과에 최적화된 작업을 빠르고 정확하게 적용할 수 있습니다. 이는 장면에 최적화된 효과를 자동으로 적용해 주므로 작업 시간을 크게 단축시킵니다.</li>
</ul>
</li>
<li><p><strong>효율적인 리소스 활용:</strong></p>
<ul>
<li><strong>GPU 활용</strong>: CopyCat은 GPU를 활용하여 학습과 렌더링 속도를 높여주며, 이로 인해 대규모 데이터셋을 처리하는 데 필요한 시간이 줄어듭니다.</li>
<li><strong>멀티 해상도 학습</strong>: 여러 해상도로 훈련을 진행하여 다양한 해상도의 작업을 효율적으로 처리할 수 있습니다.</li>
</ul>
</li>
<li><p><strong>다양한 작업에 대한 일반화 가능성:</strong></p>
<ul>
<li><strong>다양한 작업에 적용 가능</strong>: CopyCat은 디블러, 업스케일, 휴먼 매팅 등 다양한 분야에 최적화된 모델을 제공하며, 한 번 학습된 모델을 다른 프로젝트나 작업에 재사용할 수 있어 작업의 일관성과 품질을 높입니다.</li>
<li><strong>입력/출력 데이터 간의 관계 학습</strong>: 효과 적용 전과 후의 데이터를 기반으로 모델이 학습되므로, 다양한 작업에서 기대하는 효과를 더 일관되게 얻을 수 있습니다.</li>
</ul>
</li>
<li><p><strong>시간 절약 및 비용 절감:</strong></p>
<ul>
<li><strong>훈련 시간 단축</strong>: 복잡한 작업을 수동으로 작업하는 데 드는 시간을 크게 줄여줍니다. 학습된 모델을 사용하여 반복적인 작업을 빠르게 처리할 수 있으며, 분산 학습을 통해 훈련 시간을 단축할 수 있습니다.</li>
<li><strong>인력 비용 절감</strong>: 자동화된 학습 모델을 사용하면, 반복적이고 시간이 많이 소요되는 작업을 줄여줄 수 있어 인적 자원을 다른 작업에 배분할 수 있습니다.</li>
</ul>
</li>
</ol>
<br>

<h2 id="copycat의-한계">CopyCat의 한계</h2>
<ol>
<li><p><strong>데이터 품질에 의존</strong>:</p>
<ul>
<li><strong>훈련 데이터셋 품질의 중요성</strong>: CopyCat은 훈련 데이터셋에 큰 의존성을 가집니다. <strong>Input</strong>과 <strong>Ground Truth</strong> 데이터가 정확하고 품질이 높은 경우에만 좋은 성능을 발휘할 수 있습니다. 잘못된 또는 불완전한 데이터로 학습하면 결과물이 왜곡되거나 기대에 미치지 못할 수 있습니다.</li>
<li><strong>다양성 부족</strong>: 훈련 데이터셋이 제한적이거나 특정 유형의 데이터만 포함되어 있으면 모델의 일반화 성능이 떨어질 수 있습니다. 다양한 조건과 변수를 다루기 위한 방대한 데이터셋이 필요합니다.</li>
</ul>
</li>
<li><p><strong>긴 훈련 시간:</strong></p>
<ul>
<li><strong>학습 시간</strong>: CopyCat은 모델을 훈련시키는 데 시간이 상당히 소요될 수 있습니다. 특히 대규모 데이터셋과 복잡한 작업을 다룰 때 훈련 시간이 길어질 수 있습니다.</li>
<li><strong>훈련 초기 단계에서의 성능</strong>: 모델이 처음 학습을 시작할 때는 그 성능이 낮을 수 있고, 많은 학습이 필요합니다. 최적의 성능을 얻기까지 많은 반복 학습과 검증이 필요할 수 있습니다.</li>
</ul>
</li>
<li><p><strong>GPU 리소스 소모</strong>:</p>
<ul>
<li><strong>고사양 하드웨어 요구</strong>: CopyCat은 GPU를 적극적으로 활용하지만, 고성능 GPU가 없으면 훈련 시간이 많이 늘어나거나 제대로 작동하지 않을 수 있습니다. 특히 복잡한 모델을 학습할 때는 고급 GPU 하드웨어가 필수적일 수 있습니다.</li>
<li><strong>메모리 부족 문제</strong>: 모델 크기나 데이터셋이 커질수록 GPU 메모리 용량이 부족해질 수 있습니다. 이를 해결하기 위해 모델을 축소하거나 더 많은 메모리를 가진 시스템을 사용해야 할 수도 있습니다.</li>
</ul>
</li>
<li><p><strong>학습 데이터와 실제 데이터 간의 차이</strong>:</p>
<ul>
<li><strong>실제 환경에서의 예기치 않은 결과</strong>: 학습 데이터셋과 실제로 적용될 환경 사이에 차이가 있을 경우, 모델이 예기치 않게 동작할 수 있습니다. 예를 들어, 훈련에 사용된 데이터가 매우 이상적일 경우, 실제 프로덕션에서 발생할 수 있는 다양한 변수에 대응하지 못할 수 있습니다.</li>
</ul>
</li>
<li><p><strong>특정 조건 하에서만 최적화</strong>:</p>
<ul>
<li><strong>특화된 용도에만 적합</strong>: CopyCat은 특정 작업(예: 디블러, 업스케일 등)에 특화된 모델을 제공하지만, 모든 종류의 VFX 작업에 적합하지 않을 수 있습니다. 예를 들어, 특정 스타일이나 복잡한 이펙트에는 잘 적용되지 않을 수 있습니다.</li>
</ul>
</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[multi-copier 제작]]></title>
            <link>https://velog.io/@td_dreamtree/multi-copier-%EC%A0%9C%EC%9E%91</link>
            <guid>https://velog.io/@td_dreamtree/multi-copier-%EC%A0%9C%EC%9E%91</guid>
            <pubDate>Wed, 03 Jul 2024 08:58:53 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><a href="https://github.com/junopark00/multi-copier">https://github.com/junopark00/multi-copier</a></p>
</blockquote>
<p><code>Multi Copier</code>는 <strong>파일과 디렉토리</strong>를 <strong>여러 목적지 경로로</strong> 효율적으로 <strong>복사</strong>하기 위해 설계된 <code>PySide2</code> 기반의 GUI 응용 프로그램입니다. </p>
<p>이 도구는 <strong>대량의 파일 복사 과정을 간소화</strong>하면서 실시간 진행 상황 업데이트와 중단 및 알림 옵션을 제공합니다.</p>
<p><img src="https://velog.velcdn.com/images/td_dreamtree/post/a79766df-4293-4c18-a781-b818ef0184c3/image.png" alt=""></p>
<h2 id="목차">목차</h2>
<ul>
<li><a href="#%EA%B8%B0%EB%8A%A5">기능</a></li>
<li><a href="#%EC%84%A4%EC%B9%98">설치</a></li>
<li><a href="#%EC%8B%A4%ED%96%89-%EB%B0%A9%EB%B2%95">실행 방법</a></li>
<li><a href="#%EC%82%AC%EC%9A%A9-%EB%B0%A9%EB%B2%95">사용 방법</a></li>
<li><a href="#%EC%BD%94%EB%93%9C-%EA%B5%AC%EC%A1%B0">코드 구조</a></li>
</ul>
<h2 id="기능">기능</h2>
<ul>
<li><p><strong>드래그 앤 드롭 인터페이스</strong>: 파일 탐색기에서 파일이나 디렉토리를 드래그 앤 드롭으로 쉽게 추가할 수 있습니다.</p>
</li>
<li><p><strong>여러 목적지 경로</strong>: 동시에 복사할 여러 목적지 경로를 정의할 수 있습니다.</p>
</li>
<li><p><strong>실시간 진행 상황 업데이트</strong>: 복사 중에 전체 진행 상황과 개별 파일 진행 상황을 추적할 수 있습니다.</p>
</li>
<li><p><strong>중지 기능</strong>: 중지 버튼으로 언제든지 복사 과정을 중단할 수 있습니다.</p>
</li>
<li><p><strong>알림</strong>: 완료 시 이메일 알림이나 RocketChat 메시지를 보내는 옵션 (더미 기능)을 제공합니다.</p>
</li>
</ul>
<h2 id="설치">설치</h2>
<p><code>Multi Copier</code>를 사용하려면 시스템에 <code>Python</code>과 몇 가지 종속성을 설치해야 합니다:</p>
<ul>
<li>PySide2</li>
<li>pyqtdarktheme</li>
</ul>
<p>이러한 종속성은 pip을 사용하여 설치할 수 있습니다:</p>
<pre><code class="language-bash">pip install PySide2 pyqtdarktheme</code></pre>
<p>저장소를 클론하고 프로젝트 디렉토리로 이동합니다:</p>
<pre><code class="language-bash">git clone https://github.com/junopark00/multi-copier.git
cd multi-copier</code></pre>
<h2 id="실행-방법">실행 방법</h2>
<p>메인 스크립트를 실행하여 애플리케이션을 실행합니다:</p>
<pre><code class="language-bash">python ./copier.py</code></pre>
<h2 id="사용-방법">사용 방법</h2>
<ol>
<li><p><strong>파일/디렉토리 추가:</strong></p>
<p><strong>파일</strong>이나 <strong>디렉토리</strong>를 <strong>&quot;Origin&quot;</strong> 섹션으로 <strong>드래그 앤 드롭</strong>합니다.</p>
<p>파일이나 디렉토리는 <strong>전체 경로</strong>와 함께 목록에 표시됩니다.</p>
</li>
<li><p><strong>목적지 경로 추가:</strong></p>
<p><strong>디렉토리</strong>를 <strong>&quot;Destination&quot;</strong> 섹션으로 <strong>드래그 앤 드롭</strong>합니다.</p>
<p>동시에 복사할 <strong>여러 목적지 경로</strong>를 정의할 수 있습니다.</p>
</li>
<li><p><strong>복사 시작:</strong></p>
<p><code>Copy</code> 버튼을 클릭하여 <strong>파일 복사</strong>를 시작합니다.
진행 막대가 실시간으로 업데이트되어 전체 및 개별 파일의 <strong>진행 상황을 표시</strong>합니다.</p>
</li>
<li><p><strong>복사 중지:</strong></p>
<p><code>Stop</code> 버튼을 클릭하여 복사 과정을 중단합니다.</p>
</li>
</ol>
<h2 id="코드-구조">코드 구조</h2>
<ul>
<li><code>copier.py</code>: 애플리케이션을 실행하는 메인 스크립트입니다.</li>
<li><code>copier_ui.py</code>: GUI를 설정하는 <code>CopierUI</code> 클래스를 정의합니다.</li>
<li><code>MultiCopier</code>: <code>CopierUI</code>를 상속받아 파일 복사 기능을 구현합니다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[multi-renamer 제작]]></title>
            <link>https://velog.io/@td_dreamtree/multi-renamer-%EC%A0%9C%EC%9E%91</link>
            <guid>https://velog.io/@td_dreamtree/multi-renamer-%EC%A0%9C%EC%9E%91</guid>
            <pubDate>Wed, 03 Jul 2024 08:48:14 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><a href="https://github.com/junopark00/multi-renamer">https://github.com/junopark00/multi-renamer</a></p>
</blockquote>
<p><strong>Multi Renamer</strong>는 지정된 디렉토리 내의 <strong>파일 이름을 일괄 변경하는 과정을 간소화</strong>하기 위해 설계된 <code>PySide2</code> 기반의 GUI 응용 프로그램입니다. </p>
<p>이 도구를 사용하면 사용자가 <strong>이름 변경 규칙을 정의</strong>하고 이를 <strong>여러 파일에 효율적으로 적용</strong>하여 대규모 파일 이름 변경 작업을 신속하고 쉽게 수행할 수 있습니다.</p>
<p><img src="https://velog.velcdn.com/images/td_dreamtree/post/0b6b9a4a-590a-4d51-b5e5-3d64e202bc37/image.png" alt=""></p>
<h2 id="목차">목차</h2>
<ul>
<li><a href="#%EA%B8%B0%EB%8A%A5">기능</a></li>
<li><a href="#%EC%84%A4%EC%B9%98">설치</a></li>
<li><a href="#%EC%8B%A4%ED%96%89-%EB%B0%A9%EB%B2%95">실행 방법</a></li>
<li><a href="#%EC%82%AC%EC%9A%A9-%EB%B0%A9%EB%B2%95">사용 방법</a></li>
<li><a href="#%EC%BD%94%EB%93%9C-%EA%B5%AC%EC%A1%B0">코드 구조</a></li>
</ul>
<h2 id="기능">기능</h2>
<ul>
<li><p><strong>디렉토리 탐색</strong>: 디렉토리를 선택하여 해당 디렉토리 내의 모든 파일을 나열합니다.</p>
</li>
<li><p><strong>이름 변경 규칙 추가</strong>: 변경할 텍스트와 대체 텍스트를 지정하여 여러 이름 변경 규칙을 정의할 수 있습니다.</p>
</li>
<li><p><strong>규칙 제거</strong>: 마지막으로 추가된 이름 변경 규칙을 제거합니다.</p>
</li>
<li><p><strong>파일 정렬</strong>: 나열된 파일을 오름차순 또는 내림차순으로 정렬합니다.</p>
</li>
<li><p><strong>일괄 이름 변경</strong>: 정의된 이름 변경 규칙을 디렉토리 내의 모든 파일에 한 번에 적용합니다.</p>
</li>
<li><p><strong>다크 테마</strong>: 애플리케이션은 세련되고 현대적인 느낌을 주는 다크 테마를 사용합니다.</p>
</li>
</ul>
<h2 id="설치">설치</h2>
<p><code>Multi Renamer</code>를 사용하려면 시스템에 Python이 설치되어 있어야 하며 다음 패키지도 필요합니다:</p>
<ul>
<li>PySide2</li>
<li>qdarktheme</li>
</ul>
<p>이러한 종속성을 pip을 사용하여 설치할 수 있습니다:</p>
<pre><code class="language-bash">pip install PySide2 pyqtdarktheme</code></pre>
<p>저장소를 클론하고 프로젝트 디렉토리로 이동합니다:</p>
<pre><code class="language-bash">git clone https://github.com/junopark00/multi-renamer.git
cd multi-renamer</code></pre>
<h2 id="실행-방법">실행 방법</h2>
<p>메인 스크립트를 실행하여 애플리케이션을 실행합니다:</p>
<pre><code class="language-bash">python ./renamer.py</code></pre>
<h2 id="사용-방법">사용 방법</h2>
<ol>
<li><p><strong>디렉토리 탐색</strong>:</p>
<p><code>Browse</code> 버튼을 클릭하여 이름을 변경하려는 파일이 있는 디렉토리를 선택합니다.</p>
<p>선택된 경로가 텍스트 필드에 표시되며, 파일 목록이 오른쪽에 나열됩니다.</p>
</li>
<li><p><strong>이름 변경 규칙 추가</strong>:</p>
<p><code>Add</code> 버튼을 클릭하여 새로운 이름 변경 규칙을 추가합니다.</p>
<p>나타나는 두 개의 텍스트 필드에 <strong>변경할 텍스트</strong>와 <strong>대체 텍스트</strong>를 입력합니다.</p>
</li>
<li><p><strong>이름 변경 규칙 제거</strong>:</p>
<p><code>Remove</code> 버튼을 클릭하여 마지막으로 추가된 이름 변경 규칙을 제거합니다.</p>
</li>
<li><p><strong>파일 정렬</strong>:</p>
<p><strong>위쪽</strong> 및 <strong>아래쪽</strong> 화살표 버튼을 사용하여 나열된 파일을 <strong>오름차순</strong> 또는 <strong>내림차순</strong>으로 정렬합니다.</p>
</li>
<li><p><strong>일괄 이름 변경</strong>:</p>
<p>모든 이름 변경 규칙을 정의한 후 <code>Click to Rename</code> 버튼을 클릭하여 디렉토리 내의 <strong>모든 파일에 규칙을 적용</strong>합니다.</p>
<p>완료되면 <strong>성공 메시지가 표시</strong>되고 파일 목록이 업데이트됩니다.</p>
</li>
</ol>
<h2 id="코드-구조">코드 구조</h2>
<p><code>renamer.py</code>: 애플리케이션을 실행하는 메인 스크립트입니다.</p>
<p><code>renamer_ui.py:</code> 사용자 인터페이스를 설정하는 <code>RenamerUI</code> 클래스를 포함합니다.</p>
<p><code>MultiRenamer</code>: <code>RenamerUI</code>를 상속받아 이름 변경 기능을 구현합니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[ShotGrid Toolkit 앱 개발하기]]></title>
            <link>https://velog.io/@td_dreamtree/ShotGrid-Toolkit-%EC%95%B1-%EA%B0%9C%EB%B0%9C%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@td_dreamtree/ShotGrid-Toolkit-%EC%95%B1-%EA%B0%9C%EB%B0%9C%ED%95%98%EA%B8%B0</guid>
            <pubDate>Wed, 03 Jul 2024 08:31:22 GMT</pubDate>
            <description><![CDATA[<p><strong>ShotGrid Toolkit 앱</strong>에는 <code>tk-multi-workfiles2</code>와 같이 오픈 소스로 공개된 앱과 개발자가 직접 제작한 커스텀 앱이 있다.</p>
<p>이번에는 자체 앱 제작을 위해 ShotGrid에서 지원하는 템플릿인 <code>tk-multi-starterapp</code>을 이용해 직접 앱을 개발하는 방법에 대해 알아보자.</p>
<h2 id="목차">목차</h2>
<ul>
<li><a href="#%EC%95%B1-%EA%B0%9C%EB%B0%9C-%EC%A4%80%EB%B9%84">앱 개발 준비</a><ul>
<li><a href="#%EC%8A%A4%ED%83%80%ED%84%B0-%EC%95%B1-%ED%8F%AC%ED%81%AC">스타터 앱 포크</a></li>
<li><a href="#%EA%B5%AC%EC%84%B1%EC%97%90-%EC%95%B1-%EC%B6%94%EA%B0%80">구성에 앱 추가</a></li>
</ul>
</li>
<li><a href="#%EC%95%B1-%EA%B0%9C%EB%B0%9C">앱 개발</a><ul>
<li><a href="#%EC%8A%A4%ED%83%80%ED%84%B0-%EC%95%B1%EC%9D%98-%EA%B5%AC%EC%84%B1">스타터 앱의 구성</a></li>
<li><a href="#apppy-%EC%9E%91%EC%84%B1">app.py 작성</a></li>
<li><a href="#infoyml-%EC%9E%91%EC%84%B1">info.yml 작성</a></li>
<li><a href="#pythonappuidialogpy-%EC%9E%91%EC%84%B1">python/app/ui/dialog.py 작성</a></li>
<li><a href="#pythonappdialogpy-%EC%9E%91%EC%84%B1">python/app/dialog.py 작성</a></li>
</ul>
</li>
</ul>
<h2 id="앱-개발-준비">앱 개발 준비</h2>
<p>앱을 개발하기 위해서는 ShotGrid에서 지원하는 템플릿을 이용하는 것이 가장 편하고 빠르다.</p>
<h3 id="스타터-앱-포크">스타터 앱 포크</h3>
<p>ShotGrid에서는 개발자들이 앱 개발을 쉽게 진행할 수 있도록 일종의 템플릿인 <code>tk-multi-starterapp</code>을 지원한다.</p>
<p>먼저, <a href="https://github.com/shotgunsoftware/tk-multi-starterapp">스타터 앱의 리포지토리</a>를 포크(Fork)하거나 다운로드하자.
필자의 경우 <code>git clone</code>을 통해 해당 리포지토리를 로컬에 복제해서 사용했다.</p>
<pre><code class="language-bash">git clone https://github.com/shotgunsoftware/tk-multi-starterapp</code></pre>
<h3 id="구성에-앱-추가">구성에 앱 추가</h3>
<p>이후 해당 앱을 실행할 엔진의 구성에 앱 정보를 추가한다.</p>
<pre><code class="language-yaml">tk-multi-starterapp:
  location:
    type: dev
    path: /path/to/source_code/tk-multi-starterapp</code></pre>
<blockquote>
<p>앱 추가에 대한 자세한 내용은 <a href="https://velog.io/@td_dreamtree/ShotGrid-Toolkit-%EC%A0%9C%EC%9E%91">ShotGrid Toolkit에 엔진, 앱 등록하기</a>를 참고</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/td_dreamtree/post/59d90a93-a1f4-4034-b391-40f5add4a3bf/image.png" alt=""></p>
<center>
▲정상적으로 스타터 앱 등록 후 실행한 모습

</center>


<h2 id="앱-개발">앱 개발</h2>
<p>이제 앱 개발을 위한 간단한 준비는 완료되었다. 이제 본격적으로 앱 개발을 해보자.</p>
<h3 id="스타터-앱의-구성">스타터 앱의 구성</h3>
<p>먼저, 스타터 앱이 어떻게 구성되어 있는지 알아야 한다.
아래는 스타터 앱을 구성하는 각 스크립트에 대한 설명이다.</p>
<ul>
<li><p><code>app.py</code> - 앱 진입점 및 메뉴 등록은 <code>app.py</code> 파일에서 찾을 수 있다. 
이 파일에서 보통 <strong>클래스를 설정</strong>하고, <strong>항목을 초기화</strong>하고, <strong>메뉴 항목을 등록</strong>한다.</p>
</li>
<li><p><code>info.yml</code> - <em>매니페스트 파일</em>이라고도 한다. 
앱 설치 시 필요한 다른 모든 <strong>설정</strong>과 해당하는 <strong>기본값(제공할 경우)을 정의</strong>한다. 
재사용 가능한 앱을 원하고, 앱 자체에서는 어떠한 값도 하드 코딩하고 싶지 않은 경우 대개 이러한 설정이 유용하다.</p>
</li>
<li><p><code>python/app/dialog.py</code> - 여기에는 기본 앱 창을 생성하는 <strong>로직, 이벤트 콜백</strong> 등이 포함된다.</p>
</li>
<li><p><code>python/app/ui</code> - 이 폴더에는 자동 생성된 <strong>UI 코드 및 리소스 파일</strong>이 포함된다. 
이 파일을 직접 편집하지 말고, 대신 resources 폴더의 <strong>Qt UI 파일</strong>을 편집해야 한다.</p>
</li>
<li><p><code>resources/</code> - resources 폴더에 있는 <code>dialog.ui</code> 파일은 사용자가 열어서 앱의 모양을 빠르게 디자인하고 정의하는 데 사용할 수 있는 <code>QT Designer</code> 파일이다. 
변경한 후에는 <code>build_resources.sh</code> 스크립트를 실행하여 UI 파일을 Python 코드로 변환하고 <code>/python/app/ui/dialog.py</code>로 저장해야 한다.</p>
</li>
<li><p><code>style.qss</code> - 이 파일에서 UI에 대한 QSS(Qt 스타일 시트)를 정의할 수 있다.</p>
</li>
</ul>
<p>정리하자면, </p>
<ol>
<li><code>app.py</code>에서 앱을 초기화하거나 메뉴를 등록</li>
<li><code>info.yml</code>에서 앱에 대한 설정을 작성</li>
<li><code>python/app/ui/dialog.py</code>에서 앱의 GUI를 생성</li>
<li><code>python/app/dialog.py</code>에 앱의 주요 로직 및 콜백을 작성</li>
</ol>
<p>위의 순서대로 앱을 개발해 나가면 된다.</p>
<h3 id="apppy-작성">app.py 작성</h3>
<p><code>app.py</code>를 작성하여 앱 초기화 및 엔진에 앱을 추가해야 한다.</p>
<p>예시:</p>
<pre><code class="language-python">import os
import sys
import sgtk
import traceback

class TestApp(sgtk.platform.Application):
    &quot;&quot;&quot;
    The app entry point. This class is responsible for intializing and tearing down
    the application, handle menu registration etc.
    &quot;&quot;&quot;

    def init_app(self):
        &quot;&quot;&quot;
        Called as the application is being initialized
        &quot;&quot;&quot;
        try:
            tk_desktop_timecard = self.import_module(&quot;tk_test_app&quot;)
            # register command
            cb = lambda: tk_desktop_timecard.show_dialog(self)
            menu_caption = &quot;Test App&quot;
            self.engine.register_command(menu_caption, cb)
        except Exception:
            traceback.print_exc()

    def destroy_app(self):
        &quot;&quot;&quot;
        Tear down the app
        &quot;&quot;&quot;
        self.log_debug(&quot;Destroying tk-test-app&quot;)</code></pre>
<h3 id="infoyml-작성">info.yml 작성</h3>
<p><code>info.yml</code>에 해당 앱에 대한 설정이나 조건을 추가한다.</p>
<p>필자의 경우 간단한 앱을 제작할 예정이라 따로 설정을 추가하지 않았다.</p>
<p>예시:</p>
<pre><code class="language-yaml"># expected fields in the configuration file for this engine
configuration:

# this app works in all engines - it does not contain
# any host application specific commands
supported_engines:

# the Shotgun fields that this engine needs in order to operate correctly
requires_shotgun_fields:

# More verbose description of this item
display_name: &quot;Test App&quot;
description: &quot;Test App&quot;

# Required minimum versions for this item to run
requires_shotgun_version:
requires_core_version:
requires_engine_version:

# the frameworks required to run this app
frameworks: </code></pre>
<h3 id="pythonappuidialogpy-작성"><code>python/app/ui/dialog.py</code> 작성</h3>
<p>이제 앱의 GUI를 구성하는 스크립트인 <code>python/app/ui/dialog.py</code>를 작성한다.</p>
<p>필자의 경우 간단하게 버튼만 추가해봤다.</p>
<pre><code class="language-python">from tank.platform.qt import QtCore, QtGui

class Ui_Dialog(object):
    def setupUi(self, Dialog):
        Dialog.setWindowTitle(&quot;TestApp&quot;)
        Dialog.resize(400, 300)
        self.verticalLayout = QtGui.QVBoxLayout(Dialog)
        self.label = QtGui.QLabel(Dialog)
        self.label.setText(&quot;Test&quot;)
        self.label.setAlignment(QtCore.Qt.AlignCenter)
        self.verticalLayout.addWidget(self.label)
        self.button_test = QtGui.QPushButton(Dialog)
        self.button_test.setText(&quot;Test&quot;)
        self.verticalLayout.addWidget(self.button_test)

        Dialog.setLayout(self.verticalLayout)

from . import resources_rc</code></pre>
<p>이후 앱을 실행하면 작성한 GUI가 나타난다.
<img src="https://velog.velcdn.com/images/td_dreamtree/post/b651e5a5-f7ff-421e-b66b-9a12450a40fd/image.png" alt=""></p>
<h3 id="pythonappdialogpy-작성"><code>python/app/dialog.py</code> 작성</h3>
<p>이제 UI와 기능을 연결하기 위해 <code>python/app/dialog.py</code>를 작성한다.</p>
<p>필자의 경우 <code>show_dialog</code>를 수정해 앱의 이름을 다르게 변경하고, 버튼을 클릭하면 <code>QLabel</code>의 글자가 바뀌도록 만들어봤다.</p>
<pre><code class="language-python">import sgtk

from sgtk.platform.qt import QtCore, QtGui
from .ui.dialog import Ui_Dialog

# standard toolkit logger
logger = sgtk.platform.get_logger(__name__)


def show_dialog(app_instance):
    &quot;&quot;&quot;
    Shows the main dialog window.
    &quot;&quot;&quot;
    app_instance.engine.show_dialog(&quot;Test App&quot;, app_instance, AppDialog)


class AppDialog(QtGui.QWidget):
    &quot;&quot;&quot;
    Main application dialog window
    &quot;&quot;&quot;

    def __init__(self):
        &quot;&quot;&quot;
        Constructor
        &quot;&quot;&quot;
        # first, call the base class and let it do its thing.
        QtGui.QWidget.__init__(self)

        # now load in the UI that was created in the UI designer
        self.ui = Ui_Dialog()
        self.ui.setupUi(self)

        # most of the useful accessors are available through the Application class instance
        # it is often handy to keep a reference to this. You can get it via the following method:
        self._app = sgtk.platform.current_bundle()

        # logging happens via a standard toolkit logger
        logger.info(&quot;Launching Starter Application...&quot;)

        self.connection()

    def connection(self):
        self.ui.button_test.clicked.connect(self.test)

    def test(self):
        print(&quot;Test&quot;)
        self.ui.label.setText(&quot;Test Clicked&quot;)</code></pre>
<p><img src="https://velog.velcdn.com/images/td_dreamtree/post/e0736845-08a8-4899-bc94-573ed148ac13/image.png" alt=""></p>
<center>

<p>▲상단 앱 이름이 변경되고 버튼-기능이 연결된 모습</p>
</center>

<br>

<p><strong>ShotGrid Toolkit 앱</strong>을 직접 개발하는 간단한 방법을 알아보았다.</p>
<p>필자가 작성한 예시는 정말 간단하게 UI를 작성하고 기능을 연결하는 정도였지만, 
개발자의 의도와 목적에 따라 무궁무진하게 발전시킬 수 있으니 <strong>ShotGrid Toolkit</strong>을 사용 중이라면 꼭 활용해보길 바란다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[ShotGrid Toolkit 앱이란?]]></title>
            <link>https://velog.io/@td_dreamtree/ShotGrid-Toolkit-%EC%95%B1%EC%9D%B4%EB%9E%80</link>
            <guid>https://velog.io/@td_dreamtree/ShotGrid-Toolkit-%EC%95%B1%EC%9D%B4%EB%9E%80</guid>
            <pubDate>Wed, 03 Jul 2024 07:04:16 GMT</pubDate>
            <description><![CDATA[<p><strong>ShotGrid Toolkit</strong>에서는 <strong>ShotGrid</strong>와 <strong>DCC</strong>를 연동하여 특정 작업을 수행하거나, 
다양한 자동화 작업 등을 위해 <strong>오픈소스</strong> 형태로 다양한 앱을 지원하며, 개발자가 <strong>자체적으로 앱을 개발</strong>하는 것 또한 지원한다.</p>
<p>이번 포스트에서는 <strong>ShotGrid Toolkit 앱</strong>이 무엇인지, 왜 사용하는지에 대해 알아보자.</p>
<h2 id="목차">목차</h2>
<ul>
<li><a href="#shotgrid-toolkit-%EC%95%B1%EC%9D%B4%EB%9E%80">ShotGrid Toolkit 앱이란?</a><ul>
<li><a href="#%EC%A3%BC%EC%9A%94-%EA%B8%B0%EB%8A%A5-%EB%B0%8F-%EC%97%AD%ED%95%A0">주요 기능 및 역할</a></li>
<li><a href="#%EC%A3%BC%EC%9A%94-%EC%95%B1-%EC%98%88%EC%8B%9C">주요 앱 예시</a></li>
<li><a href="#%EC%84%A4%EC%A0%95-%EB%B0%8F-%EC%BB%A4%EC%8A%A4%ED%84%B0%EB%A7%88%EC%9D%B4%EC%A7%95">설정 및 커스터마이징</a></li>
<li><a href="#%EC%95%B1-%EA%B0%9C%EB%B0%9C-%EB%B0%8F-%ED%99%95%EC%9E%A5">앱 개발 및 확장</a></li>
</ul>
</li>
<li><a href="#%EC%95%B1%EC%9D%98-%EA%B5%AC%EC%84%B1%EC%9A%94%EC%86%8C">앱의 구성요소</a><ul>
<li><a href="#%ED%8C%8C%EC%9D%BC-%EA%B5%AC%EC%84%B1">파일 구성</a></li>
<li><a href="#%ED%8F%B4%EB%8D%94-%EA%B5%AC%EC%84%B1">폴더 구성</a></li>
</ul>
</li>
</ul>
<h2 id="shotgrid-toolkit-앱이란">ShotGrid Toolkit 앱이란?</h2>
<p><strong>ShotGrid Toolkit 앱</strong>은 <strong>ShotGrid Toolkit 엔진</strong>에 의해 실행되는 도구이다.</p>
<p>앱은 구성에 따라 다양한 <strong>인터페이스</strong> 혹은 <strong>기능</strong>을 가질 수 있으며, 사용자로 하여금 <strong>ShotGrid Tollkit</strong> 내에서 <strong>다양한 작업</strong>을 할 수 있게 한다.</p>
<p>일반적으로 GUI를 갖추고 있으며, 단순히 특정 명령을 일컫는 말이기도 하다.</p>
<h3 id="주요-기능-및-역할">주요 기능 및 역할</h3>
<ul>
<li><strong>작업 관리</strong>: <strong>작업</strong>의 <strong>생성, 할당, 추적</strong> 및 <strong>관리</strong>를 쉽게 할 수 있도록 한다.</li>
<li><strong>파일 관리</strong>: 파일의 <strong>버전 관리, 승인 절차, 파일 경로 설정</strong> 등을 지원하여 작업 파일을 체계적으로 관리할 수 있다.</li>
<li><strong>출판 및 검토</strong>: 작업 결과물을 <strong>ShotGrid에 출판</strong>하고, 팀원들 간의 <strong>검토와 피드백</strong> 과정을 간편하게 진행할 수 있다.</li>
<li><strong>데이터 시각화</strong>: 프로젝트의 <strong>진행 상황, 작업 현황 등을 시각적으로 표현</strong>하여 전체 프로젝트의 상태를 한눈에 파악할 수 있게 한다.</li>
<li><strong>자동화</strong>: <strong>반복적인 작업을 자동화</strong>하여 작업 효율성을 높이고, 오류를 줄일 수 있다.</li>
</ul>
<p>이외에도 사용자가 어떻게 앱을 제작하느냐에 따라 무궁무진한 기능 및 역할을 할 수 있는 것이 앱의 특징이다.</p>
<h3 id="주요-앱-예시">주요 앱 예시</h3>
<ul>
<li><strong>tk-multi-workfiles2</strong>: 작업 파일을 관리하는 앱으로, <strong>파일의 저장, 열기, 버전 관리</strong> 등을 지원한다.<center>

</li>
</ul>
<p><img src="https://velog.velcdn.com/images/td_dreamtree/post/529ada3f-7217-4ec9-8aec-b07c5604cea2/image.png" alt=""></p>
</center>

<ul>
<li><strong>tk-multi-loader2</strong>: ShotGrid에서 <strong>파일을 검색하고 로드</strong>할 수 있는 앱으로, 필요한 파일을 쉽게 찾아서 애플리케이션에 불러올 수 있다.<center>

</li>
</ul>
<p><img src="https://velog.velcdn.com/images/td_dreamtree/post/24a41b23-6e11-4034-80ad-2e4e56c5902c/image.png" alt=""></p>
</center>

<ul>
<li><strong>tk-multi-publish2</strong>: 작업 결과물을 <strong>ShotGrid에 출판</strong>하는 앱으로, 출판 프로세스를 관리하고 파일을 ShotGrid에 업로드한다.<center>

</li>
</ul>
<p><img src="https://velog.velcdn.com/images/td_dreamtree/post/e2dd4bde-2a43-4bc8-86fb-c07eb4e55bf9/image.png" alt=""></p>
</center>

<h3 id="설정-및-커스터마이징">설정 및 커스터마이징</h3>
<p><strong>ShotGrid Toolkit 앱</strong>은 구성 파일(<code>settings/&lt;app_name&gt;.yml</code>)을 통해 설정할 수 있으며, 필요에 따라 <strong>커스터마이징</strong>이 가능하다.</p>
<p>이러한 설정 파일을 통해 각 앱의 동작 방식을 조정하고, 특정 프로젝트나 스튜디오의 요구에 맞게 최적화할 수 있다.</p>
<blockquote>
<p>앱 설정 에 대한 자세한 내용은 <a href="https://velog.io/@td_dreamtree/ShotGrid-Toolkit-%EC%A0%9C%EC%9E%91">ShotGrid Toolkit에 엔진, 앱 등록하기</a>를 참고</p>
</blockquote>
<h3 id="앱-개발-및-확장">앱 개발 및 확장</h3>
<p><strong>ShotGrid Toolkit</strong>은 오픈소스이기 때문에, 사용자나 개발자가 필요에 따라 새로운 앱을 개발하거나 기존 앱을 확장할 수 있다. 
이를 통해 다양한 애플리케이션과의 통합을 더욱 유연하게 관리할 수 있다. </p>
<p><strong>ShotGrid Toolkit 앱</strong>은 ShotGrid에서의 작업을 효율적으로 관리하기 위해 필수적이며, 이를 통해 사용자들은 더 나은 작업 흐름과 생산성을 경험할 수 있다.</p>
<h2 id="앱의-구성요소">앱의 구성요소</h2>
<p><strong>ShotGrid Toolkit 앱</strong>은 여러 파일과 스크립트로 구성되어 있으며, 이들은 각각 앱의 동작과 기능을 구성한다. 
<strong>ShotGrid Toolkit 앱</strong>의 기본적인 스크립트 구성에 대해 알아보자.</p>
<h3 id="파일-구성">파일 구성</h3>
<ul>
<li><p><code>app.py</code></p>
<p>앱의 핵심 로직을 포함하는 파일로, 애플리케이션과의 통신을 관리한다.</p>
<p>주요 클래스 및 메서드:</p>
<ul>
<li><code>App</code>: 앱의 주된 기능을 구현하는 클래스</li>
<li><code>init_app()</code>: 앱 초기화를 수행하는 메서드</li>
<li><code>destroy_app()</code>: 앱 종료 및 리소스 정리를 담당하는 메서드</li>
</ul>
</li>
<li><p><code>info.yml</code></p>
<p>앱의 메타데이터와 설정 정보를 포함하는 파일이다.</p>
<p>주요 항목:</p>
<ul>
<li><code>configuration</code>: 앱에 대한 다양한 설정</li>
<li><code>display_name</code>: 앱의 이름</li>
<li><code>description</code>: 앱의 설명</li>
<li><code>requires_shotgun_version</code>: 앱이 요구하는 <strong>ShotGun</strong>의 최소 버전</li>
<li><code>requires_core_version</code>: 앱이 요구하는 <strong>Core</strong>의 최소 버전</li>
<li><code>requires_engine_versio</code>n: 앱이 요구하는 <strong>Engine</strong>의 최소 버전</li>
</ul>
<p>예시:</p>
</li>
</ul>
<pre><code class="language-yaml">configuration:

  automatic_context_switch:
    type: bool
    description: &quot;Controls whether toolkit should attempt to automatically adjust its
                 context every time the currently loaded file changes. Defaults to True.&quot;
    default_value: True

display_name: &quot;Workfiles2 App for Maya&quot;
description: &quot;Workfiles2 App Integration in Maya&quot;

requires_shotgun_version: &quot;1.5.4&quot;
requires_core_version: &quot;v0.20.5&quot;
requires_engine_version: </code></pre>
<ul>
<li>기타 앱에 대한 파일 (아이콘, 라이센스 파일 등)</li>
</ul>
<h3 id="폴더-구성">폴더 구성</h3>
<ul>
<li><p><code>hooks</code> 폴더</p>
<p>앱의 다양한 이벤트에 대한 <strong>훅(hook) 스크립트</strong>를 포함하는 폴더이다. 
사용자가 특정 이벤트에 대해 <strong>커스터마이징</strong>된 동작을 정의할 수 있다.</p>
</li>
<li><p><code>python</code> 폴더</p>
<p>앱의 <strong>주요 파이썬 코드</strong> 파일을 포함하는 폴더, 다양한 기능이 모듈화되어 개별 스크립트로 작성되어 있는 경우가 많다.</p>
</li>
<li><p>전체적인 폴더 구조 예시</p>
</li>
</ul>
<pre><code class="language-arduino">tk-multi-workfiles2/
│
├── hooks/
│   ├── copy_file.py
│   ├── create_new_task.py
│   └── ...
│
├── python/
│   └── tk-multi-workfiles2/
│       ├── ui/
│       ├── file_list/
│       ├── browser_form.py
│       └── ...
│
├── app.py
├── info.yml
└── etc.</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[ShotGrid Toolkit 엔진 개발하기]]></title>
            <link>https://velog.io/@td_dreamtree/ShotGrid-Toolkit-%EC%97%94%EC%A7%84-%EA%B0%9C%EB%B0%9C%ED%95%98%EA%B8%B0-9hrgkbx3</link>
            <guid>https://velog.io/@td_dreamtree/ShotGrid-Toolkit-%EC%97%94%EC%A7%84-%EA%B0%9C%EB%B0%9C%ED%95%98%EA%B8%B0-9hrgkbx3</guid>
            <pubDate>Thu, 27 Jun 2024 08:15:17 GMT</pubDate>
            <description><![CDATA[<p>지난 번엔 공식적으로 제공되는 <strong>엔진</strong>을 <strong>ShotGrid Toolkit에 등록</strong>하는 방법에 대해 알아보았다.</p>
<p>그러나 <code>Katana</code>, <code>Clarisse</code>와 같이 공식적으로 지원되지 않는 DCC의 경우 사용자 혹은 개발자가 <strong>직접 엔진을 제작</strong>해야 한다.</p>
<p>완성된 <code>tk-katana</code>의 경우 <a href="https://github.com/junopark00/tk-katana">여기</a>에 있으니 참고하기 바라며, 
이 포스팅에서는 자세한 코드 설명 보다는 전체적인 개발 흐름에 대해 정리하도록 하겠다.</p>
<h2 id="목차">목차</h2>
<ul>
<li><a href="#%EC%97%94%EC%A7%84-%EA%B0%9C%EB%B0%9C-%EC%A4%80%EB%B9%84">엔진 개발 준비</a><ul>
<li><a href="#%EC%8B%9C%EC%9E%91-%EC%A0%84-%EC%9C%A0%EC%9D%98%EC%82%AC%ED%95%AD">시작 전 유의사항</a></li>
<li><a href="#%ED%8C%8C%EC%9D%BC-%EA%B5%AC%EC%A1%B0-%EC%83%9D%EC%84%B1">파일 구조 생성</a></li>
<li><a href="#%EC%97%94%EC%A7%84-%EC%84%A4%EC%A0%95-%ED%8C%8C%EC%9D%BC-%EC%83%9D%EC%84%B1">엔진 설정 파일 생성</a></li>
</ul>
</li>
<li><a href="#%EC%97%94%EC%A7%84-%EA%B0%9C%EB%B0%9C">엔진 개발</a><ul>
<li><a href="#startuppy">startup.py</a></li>
<li><a href="#enginepy">engine.py</a></li>
<li><a href="#menu_generationpy">menu_generation.py</a></li>
<li><a href="#bootstrappy">bootstrap.py</a></li>
<li><a href="#initpy">init.py</a></li>
</ul>
</li>
<li><a href="#%EB%A7%88%EB%AC%B4%EB%A6%AC">마무리</a></li>
</ul>
<h2 id="엔진-개발-준비">엔진 개발 준비</h2>
<p>엔진을 개발하기 위해서는 ShotGrid Toolkit 엔진이 어떤 방식으로 작동하는지 이해할 필요가 있다.</p>
<p>자세한 내용은 <a href="https://velog.io/@td_dreamtree/ShotGrid-Toolkit-%EC%97%94%EC%A7%84-%EA%B0%9C%EB%B0%9C%ED%95%98%EA%B8%B0">ShotGrid Toolkit 엔진이란?</a>을 참고.</p>
<p>가장 좋은 방법은 이미 만들어진 엔진을 참고하는 것이다. <code>tk-katana</code>의 경우 <code>tk-maya</code>를 레퍼런스로 제작된 엔진이다.</p>
<h3 id="시작-전-유의사항">시작 전 유의사항</h3>
<p>엔진을 제작하려는 DCC가 <strong>어떤 특성</strong>을 가지고 있는지 개발 전 완벽하게 <strong>숙지</strong>하는 것이 좋다.</p>
<p>예를 들어, <strong>DCC 자체에 QT를 포함</strong>하고 있는 지 여부, DCC <strong>실행 시 자동으로 불러오는 스크립트</strong>가 있는 지 여부 등을 파악해야 엔진 개발시 혼동을 줄일 수 있다.</p>
<p>자세한 내용은 <a href="https://help.autodesk.com/view/SGDEV/KOR/?guid=SGD_pg_developer_pg_sgtk_developer_engine_html">공식 문서</a> 참고
<br></p>
<p>다음은 <strong>원활한 엔진 개발</strong>이 가능한 소프트웨어의 위시리스트이다.
<br></p>
<ul>
<li><p><strong>Python 인터프리터</strong> 및 <strong>Qt, PySide</strong>가 내장된 소프트웨어: 
ShotGrid Toolkit 앱의 경우 PySide 기반으로 작동하기 때문에 위의 조건을 만족하면 개발이 훨씬 편하다.</p>
</li>
<li><p>소프트웨어 <strong>시작/초기화 시</strong> <strong>코드를 실행</strong>할 수 있는 능력:
<code>Nuke</code>의 <code>init.py</code>, <code>Maya</code>의 <code>userSetup.py</code>처럼 시작 시 스크립트를 실행할 수 있는지 확인해야 한다.</p>
</li>
<li><p>소프트웨어가 <strong>시작 및 실행되고 있을 때</strong>와 <strong>UI가 완전히 초기화되었을 때</strong>에 <strong>코드에 액세스</strong>하여 자동 실행할 수 있는 능력:
쉽게 말하면 스크립팅이 가능한지 여부를 말한다.</p>
</li>
<li><p><strong>파일 시스템 상호 작용</strong>을 래핑하는 <strong>API</strong>:
말 그대로 API를 통해 열기, 저장 등 파일 시스템을 관리할 수 있는지 여부를 말한다.</p>
</li>
<li><p><strong>UI 요소</strong>를 추가하기 위한 <strong>API</strong>:
ShotGrid 메뉴를 소프트웨어의 메뉴바에 생성하기 위해, UI와 관련된 API를 지원하는지 여부를 말한다.</p>
</li>
<li><p><strong>비동기적 UI</strong> 실행 지원:
DCC 내에서 ShotGrid Toolkit 앱을 실행해도 메인 스레드가 멈추지 않도록 비동기적인 UI를 제공하는지 여부를 말한다.</p>
</li>
<li><p><strong>커스텀 UI</strong> 창의 <strong>부모-자식</strong> 관계가 올바로 지정되도록 <strong>최상위 창에 핸들</strong> 제공:
말 그대로 UI의 종속 관계에 문제가 없도록 최상위 객체를 API에서 제공하는지 여부를 말한다.</p>
<br> 

</li>
</ul>
<p>업데이트가 지속되는 DCC라면 대부분 위의 조건을 만족할 것이다. 
그러나 Qt의 경우 <strong>PySide</strong>와 <strong>PyQt</strong>를 동시에 <strong>지원하지 않는</strong> 경우도 있으니 주의하자. <del>(Katana가 그렇다.)</del></p>
<h3 id="파일-구조-생성">파일 구조 생성</h3>
<p>먼저, 필수적인 폴더와 스크립트 파일을 생성하도록 하자.</p>
<pre><code class="language-aduino">tk-katana/
│
├── resources/
│   └── Katana
│       └── init.py
│
├── hooks/
│
├── python/
│   ├── startup/
│       └── bootstrap.py
│   └── tk-katana/
│       └── menu_generation.py
│
├── config/
│   └── env/
│       ├── includes/
│           ├── settings/
│               └── tk-katana.yml
│           └── engine_locations.yml
│       ├── asset_step.yml
│       └── shot_step.yml
│
├── startup.py
├── engine.py
├── info.yml
└── etc.</code></pre>
<p><code>tk-katana</code>의 파일 구조가 <code>tk-maya</code>의 파일 구조와 다른 점은 <code>resources</code> 디렉토리가 존재한다는 것이다.</p>
<p>그 이유는 <strong>DCC마다 시작될 때</strong> 호출하는 스크립트의 차이 때문인데, 
예를 들어 <code>Nuke</code>는 시작 시 <code>init.py</code>를 인식하여 작업을 수행하며, <code>Maya</code>는 <code>userSetup.py</code>를 인식하여 작업을 수행한다.</p>
<p>이처럼 각 DCC에서 시작 시 호출하는 스크립트의 이름과 방식엔 차이가 있으니, 해당 <strong>DCC의 공식 문서를 참고</strong>하는 것이 가장 좋다.</p>
<p><code>Katana</code>의 경우 <code>KATANA_RESOURCES</code>라는 환경변수에 등록된 디렉토리의 스크립트를 실행한다.
그렇기 때문에 <code>/tk-katana/resources/Katana</code> 디렉토리에 <code>init.py</code>를 생성한 것이다.</p>
<h3 id="엔진-설정-파일-작성">엔진 설정 파일 작성</h3>
<p>엔진을 구성하기 위해서는 <code>info.yml</code>을 작성해야 한다. 여기에는 엔진에 대한 <strong>기본적인 정보와 설정</strong>이 정의된다.</p>
<p>아래는 <code>info.yml</code>의 예시이다.</p>
<pre><code class="language-yaml">configuration:

    debug_logging:
        type: bool
        description: Controls whether debug messages should be emitted to the logger
        default_value: false

    menu_favourites:
        type: list
        description: &quot;Controls the favourites section on the main menu. This is a list
                     and each menu item is a dictionary with keys app_instance and name.
                     The app_instance parameter connects this entry to a particular
                     app instance defined in the environment configuration file. The name
                     is a menu name to make a favourite.&quot;
        allows_empty: True
        values:
            type: dict
            items:
                name: { type: str }
                app_instance: { type: str }

# the Shotgun fields that this engine needs in order to operate correctly
requires_shotgun_fields:

# More verbose description of this item
display_name: &quot;Katana Engine&quot;
description: &quot;Shotgun Pipeline Toolkit integration in Katana&quot;

# Required minimum versions for this item to run
requires_shotgun_version:
requires_core_version: &quot;v0.12.5&quot;</code></pre>
<p>필자의 경우 크게 추가할 설정이 없어 간단하게 정의했다.</p>
<h2 id="엔진-개발">엔진 개발</h2>
<p>이제 개발을 하기위한 준비가 끝났다.
본격적으로 ShotGrid Toolkit <strong>엔진</strong>을 구성하는 <strong>주요 스크립트</strong>를 작성해보자.</p>
<h3 id="startuppy">startup.py</h3>
<blockquote>
<p><a href="https://github.com/junopark00/tk-katana/blob/master/startup.py">tk-katana/startup.py</a></p>
</blockquote>
<p><code>startup.py</code>는 <code>tk-katana</code>에서 <code>Katana</code> 애플리케이션을 실행할 때 사용되는 초기화 및 부트스트랩 스크립트이다.
이 스크립트는 ShotGrid Toolkit과 <code>Katana</code>를 연결하고, <code>Katana</code>가 시작될 때 적절한 환경 설정을 통해 ShotGrid Toolkit이 올바르게 초기화되도록 한다.</p>
<p><code>KatanaLauncher</code> 클래스는 ShotGrid Toolkit의 <code>SoftwareLauncher</code> 클래스를 상속받아 <code>Katana</code> 애플리케이션을 실행하고, Katana 세션이 ShotGrid Toolkit 엔진과 함께 시작되도록 설정하는 역할을 한다.</p>
<h4 id="주요-기능">주요 기능</h4>
<ul>
<li><p><strong>Katana 실행 파일 찾기</strong>:</p>
<p><code>EXECUTABLE_MATCH_TEMPLATES</code>를 통해 각 운영 체제별로 <code>Katana</code> 실행 파일 경로를 정의하고,
<code>_find_software</code> 메서드를 통해 실제로 설치된 <code>Katana</code> 실행 파일을 찾는다.
<code>_extract_products_from_path</code> 메서드를 사용하여 발견된 실행 파일의 버전 및 제품 정보를 추출한다.</p>
</li>
<li><p><strong>지원 여부 확인</strong>:</p>
<p><code>_is_supported</code> 메서드를 통해 특정 <code>Katana</code> 버전이 지원되는지 확인한다.</p>
</li>
<li><p><strong>Katana 실행 준비</strong>:</p>
<p><code>prepare_launch</code> 메서드는 Katana를 실행할 때 필요한 환경 변수를 설정한다. 이 환경 변수에는 ShotGrid 컨텍스트와 엔진 정보가 포함된다.
<code>required_env</code> 딕셔너리에 ShotGrid 컨텍스트와 엔진 이름을 추가하여 <code>Katana</code>가 시작될 때 ShotGrid Toolkit과 연결될 수 있도록 한다.</p>
</li>
<li><p><strong>Katana 실행</strong>:</p>
<p><code>prepare_launch</code> 메서드는 최종적으로 <code>LaunchInformation</code> 객체를 반환하여 <code>Katana</code> 실행 파일 경로, 명령줄 인수, 그리고 필요한 환경 변수를 포함한다.</p>
</li>
</ul>
<h3 id="enginepy">engine.py</h3>
<blockquote>
<p><a href="https://github.com/junopark00/tk-katana/blob/master/engine.py">tk-katana/engine.py</a></p>
</blockquote>
<p><code>engine.py</code> 는 <code>Katana</code> 엔진의 핵심 엔트리 포인트로, <code>Katana</code>와 ShotGrid Toolkit 간의 상호 작용을 관리하고 <code>Katana</code> 환경에서 Toolkit 기능을 활성화한다. </p>
<p><code>KatanaEngine</code> 클래스는 ShotGrid Toolkit의 <code>sgtk.platform.Engine</code> 클래스를 상속받아 <code>Katana</code>와의 상호 작용을 정의한다.</p>
<h4 id="주요-기능-1">주요 기능</h4>
<ul>
<li><p><strong>버전 호환성 검사</strong>:
<code>pre_app_init</code> 메서드는 <code>Katana</code>가 초기화되기 전 호출되어 버전 호환성을 검사한다.</p>
</li>
<li><p><strong>초기화</strong>:
<code>__init__</code> 메서드에서 <code>Katana</code>가 UI 모드에서 실행 중인지 확인하고, <code>has_ui</code> 프로퍼티를 통해 bool을 반환한다.</p>
</li>
<li><p><strong>메뉴 생성</strong>:
<code>post_app_init</code> 메서드에서는 <code>Katana</code>가 UI 모드에서 실행되었는지 검증 후 <code>add_katana_menu</code> 메서드를 통해 ShotGrid 메뉴를 생성한다.
만약 UI모드가 아니라면 callback을 통해 다시 확인한다.</p>
</li>
<li><p><strong>종료 처리</strong>:
<code>destroy_engine</code> 메서드는 엔진이 종료될 때 호출되며, <code>Katana</code> 세션을 정리한다.</p>
</li>
<li><p><strong>Qt 환경 재정의</strong>:
<code>_define_qt_base</code> 메서드는 <code>PySide2</code> 기반의 앱을 <code>Katana</code>에서 사용하기 위해 <code>Qt</code> 환경을 재정의 한다.</p>
</li>
<li><p><strong>로깅</strong>:
<code>log_debug</code>, <code>log_info</code>, <code>log_warning</code>, <code>log_error</code> 메서드를 통해 다양한 수준의 로그 메시지를 ShotGrid 콘솔에 출력한다.</p>
</li>
</ul>
<h3 id="menu_generationpy">menu_generation.py</h3>
<blockquote>
<p><a href="https://github.com/junopark00/tk-katana/blob/master/python/tk_katana/menu_generation.py">tk-katana/python/tk-katana/menu_generation.py</a></p>
</blockquote>
<p><code>menu_generation.py</code>는 <code>engine.py</code>에 의해 호출되며, <code>Katana</code>에서 ShotGrid Toolkit 메뉴를 생성하고 관리하는 기능을 포함하고 있다. 
ShotGrid 메뉴 항목을 생성, 콜백을 연결, 그리고 사용자 인터페이스와의 상호 작용을 처리한다.</p>
<h4 id="주요-기능-2">주요 기능</h4>
<ul>
<li><p><strong>Katana 동작 생성</strong>:</p>
<ul>
<li><code>ActionFactory</code> 클래스는 <code>Katana</code>의 동작을 생성하고 관리한다.<ul>
<li><code>create_action</code>: <code>Katana</code> 액션을 생성하고 메뉴에 추가한다.</li>
<li><code>_execute_callback</code>: 액션이 트리거될 때 콜백을 실행한다.</li>
<li><code>clear</code>: 저장된 모든 액션을 제거한다.</li>
</ul>
</li>
</ul>
</li>
<li><p><strong>ShotGrid 메뉴 생성 및 관리</strong></p>
<ul>
<li><code>MenuGenerator</code> 클래스는 <code>Katana</code>에서 ShotGrid 메뉴를 생성하고 관리한다.<ul>
<li><code>create_menu</code>: ShotGrid 메뉴와 명령을 생성한다.</li>
<li><code>destroy_menu</code>: ShotGrid 메뉴를 제거한다.</li>
<li><code>setup_root_menu</code>: 루트 메뉴를 설정하거나 생성한다.</li>
<li><code>__build_shotgun_menu</code>: ShotGrid 메뉴를 생성한다.</li>
<li><code>__build_context_menu</code>: 현재 컨텍스트를 나타내는 메뉴를 생성한다.</li>
<li><code>_jump_to_sg</code>: 현재 컨텍스트의 ShotGrid URL을 연다.</li>
<li><code>_jump_to_fs</code>: 현재 컨텍스트의 파일 시스템 폴더를 연다.</li>
<li><code>__build_app_menu</code>: 애플리케이션 명령을 포함하는 서브메뉴를 생성한다.</li>
</ul>
</li>
</ul>
</li>
<li><p><strong>명령 실행</strong></p>
<ul>
<li><code>AppCommand</code> 클래스는 개별 ShotGrid 명령을 나타내며, 명령을 메뉴에 추가하는 역할을 한다.<ul>
<li><code>get_app_name</code>: 명령을 로드한 애플리케이션의 이름을 반환한다.</li>
<li><code>get_app_instance_name</code>: 명령을 로드한 애플리케이션 인스턴스의 이름을 반환한다.</li>
<li><code>get_type</code>: 명령의 유형을 반환한다.</li>
<li><code>add_to_menu</code>: 명령을 메뉴에 추가한다.</li>
</ul>
</li>
</ul>
</li>
</ul>
<h3 id="bootstrappy">bootstrap.py</h3>
<blockquote>
<p><a href="https://github.com/junopark00/tk-katana/blob/master/python/startup/bootstrap.py">tk-katana/python/startup/bootstrap.py</a></p>
</blockquote>
<p><code>bootstrap.py</code>는 <code>Katana</code> 환경을 설정하는 스크립트로, <code>Katana</code> 실행에 필요한 리소스 경로를 설정하고 환경 변수를 업데이트하는 기능을 포함하고 있다.</p>
<p>필자의 경우 <code>Rez</code>를 활용해 <code>KATANA_RESOURCES</code>를 불러왔기 때문에 실질적으로 사용되지 않은 스크립트이다.</p>
<h4 id="주요-기능-3">주요 기능</h4>
<ul>
<li><p><strong>엔진 경로 가져오기</strong>: 
<code>sgtk.platform.get_engine_path</code>를 사용하여 지정된 엔진의 경로를 가져온다.</p>
</li>
<li><p><strong>리소스 경로 추가</strong>: 
<code>_get_resource_paths</code> 함수를 호출하여 모든 앱의 리소스 경로를 가져와 <code>startup_paths</code> 리스트에 추가한다.</p>
</li>
<li><p><strong>환경 변수 설정</strong>: 
<code>startup_paths</code>를 <code>KATANA_RESOURCES</code> 환경 변수에 추가하여 <code>Katana</code>가 시작될 때 사용할 수 있도록 설정한다.</p>
</li>
</ul>
<h3 id="initpy">init.py</h3>
<blockquote>
<p><a href="https://github.com/junopark00/tk-katana/blob/master/resources/Katana/Startup/init.py">tk-katana/resources/Katana/Startup/init.py</a></p>
</blockquote>
<p><code>init.py</code>는 ShotGrid Toolkit (<code>sgtk</code>)를 사용하여 주어진 컨텍스트와 엔진을 기반으로 ShotGrid 엔진을 시작한다.
주요 함수는 bootstrap으로, 환경 변수 설정 확인, <code>sgtk</code> 모듈 로드, 컨텍스트 역직렬화 및 엔진 시작을 포함한다.</p>
<p>이 스크립트 또한 필자는 사용하지 않은 스크립트이다.</p>
<h4 id="주요-기능-4">주요 기능</h4>
<ul>
<li><p><strong>환경 변수 확인</strong>:
<code>SGTK_ENGINE</code>과 <code>SGTK_CONTEXT</code> 환경 변수가 설정되어 있는지 확인한다.
설정되지 않았다면 오류 메시지를 기록하고 종료한다.</p>
</li>
<li><p><strong><code>sgtk</code> 모듈 임포트</strong>:
<code>sgt</code>k 모듈을 임포트하여 ShotGrid 엔진을 시작하기 위한 준비를 한다.
임포트에 실패하면 오류 메시지를 기록하고 <code>sys.path</code>를 출력한 후 종료한다.</p>
</li>
<li><p><strong>컨텍스트 역직렬화</strong>:
직렬화된 컨텍스트를 역직렬화하여 ShotGrid 컨텍스트 객체를 생성한다.
실패 시 오류 메시지를 기록하고 종료한다.</p>
</li>
<li><p><strong>엔진 시작</strong>:
지정된 엔진 이름과 컨텍스트를 사용하여 ShotGrid 엔진을 시작한다.
실패 시 오류 메시지를 기록하고 로그 파일에 기록한다.</p>
</li>
<li><p><strong>임시 환경 변수 정리</strong>:
<code>SGTK_ENGINE</code>, <code>SGTK_CONTEXT</code>, <code>SGTK_FILE_TO_OPEN</code> 환경 변수를 제거하여 클린업을 수행한다.</p>
</li>
</ul>
<h2 id="마무리">마무리</h2>
<p>이제 기본적인 엔진 개발은 마무리되었다.</p>
<p>이제 엔진과 앱을 ShotGrid Toolkit에 등록해서 직접 사용할 수 있다.
ShotGrid Toolkit에 엔진과 앱을 등록하는 방법은 <a href="https://velog.io/@td_dreamtree/ShotGrid-Toolkit-%EC%A0%9C%EC%9E%91">여기</a>를 참고하자.</p>
<p>추가적으로 본인이 사용하고자 하는 ShotGrid Toolkit 앱에 대한 <code>hook</code>나 설정을 추가하면서 <strong>커스터마이징</strong>하면 된다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[ShotGrid Toolkit 엔진이란?]]></title>
            <link>https://velog.io/@td_dreamtree/ShotGrid-Toolkit-%EC%97%94%EC%A7%84-%EA%B0%9C%EB%B0%9C%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@td_dreamtree/ShotGrid-Toolkit-%EC%97%94%EC%A7%84-%EA%B0%9C%EB%B0%9C%ED%95%98%EA%B8%B0</guid>
            <pubDate>Tue, 11 Jun 2024 04:27:30 GMT</pubDate>
            <description><![CDATA[<p><strong>ShotGrid</strong>에서는 <strong>DCC</strong> 내에서 ShotGrid의 기능을 활용할 수 있도록 ShotGrid Toolkit에 각 <strong>DCC 별 엔진을 통합</strong>하는 것을 지원하고 있다.</p>
<p>그렇다면 이 ShotGrid Toolkit 엔진은 무엇인지, 왜 사용하는지에 대해 알아보자.</p>
<h2 id="목차">목차</h2>
<ul>
<li><a href="#shotgrid-toolkit-%EC%97%94%EC%A7%84%EC%9D%B4%EB%9E%80">ShotGrid Toolkit 엔진이란?</a><ul>
<li><a href="#%EC%A3%BC%EC%9A%94-%EA%B8%B0%EB%8A%A5-%EB%B0%8F-%EC%97%AD%ED%95%A0">주요 기능 및 역할</a></li>
<li><a href="#%EC%A3%BC%EC%9A%94-%EC%97%94%EC%A7%84-%EC%98%88%EC%8B%9C">주요 엔진 예시</a></li>
<li><a href="#%EC%84%A4%EC%A0%95-%EB%B0%8F-%EC%BB%A4%EC%8A%A4%ED%84%B0%EB%A7%88%EC%9D%B4%EC%A7%95">설정 및 커스터마이징</a></li>
<li><a href="#%EC%97%94%EC%A7%84-%EA%B0%9C%EB%B0%9C-%EB%B0%8F-%ED%99%95%EC%9E%A5">엔진 개발 및 확장</a></li>
</ul>
</li>
<li><a href="#%EC%97%94%EC%A7%84%EC%9D%98-%EA%B5%AC%EC%84%B1%EC%9A%94%EC%86%8C">엔진의 구성요소</a><ul>
<li><a href="#%ED%8C%8C%EC%9D%BC-%EA%B5%AC%EC%84%B1">파일 구성</a></li>
<li><a href="#%ED%8F%B4%EB%8D%94-%EA%B5%AC%EC%84%B1">폴더 구성</a></li>
</ul>
</li>
</ul>
<h2 id="shotgrid-toolkit-엔진이란">ShotGrid Toolkit 엔진이란?</h2>
<p><strong>ShotGrid Toolkit</strong>의 <strong>엔진</strong>은 <strong>애플리케이션</strong>과 <strong>ShotGrid</strong> 플랫폼 간의 통합을 관리하는 중요한 구성 요소이다.</p>
<p>엔진은 특정 애플리케이션(예: <code>Maya</code>, <code>Nuke</code>, <code>Houdini</code> 등) 내에서 <strong>ShotGrid Toolkit 앱을 실행</strong>하고 통신할 수 있도록 하는 인터페이스 역할을 한다.</p>
<p>엔진을 통해 <strong>다양한 툴과 기능이 애플리케이션 내에서 원활하게 작동</strong>하며, ShotGrid와의 상호작용을 가능하게 한다.</p>
<h3 id="주요-기능-및-역할">주요 기능 및 역할</h3>
<ul>
<li><p><strong>애플리케이션 통합</strong>: 각 엔진은 특정 소프트웨어 애플리케이션에 맞게 설계되어 해당 <strong>애플리케이션 내에서</strong> <strong>ShotGrid Toolkit 앱을 실행</strong>할 수 있게 한다. 예를 들어, <code>Maya</code> 엔진(<code>tk-maya</code>)은 <code>Maya</code>에서 작동하는 Toolkit 앱을 관리한다.</p>
</li>
<li><p><strong>환경 설정 및 초기화</strong>: 파일 경로 설정, 구성 파일 로드, 사용자 인터페이스 초기화 등의 역할을 한다.</p>
</li>
<li><p><strong>앱 실행 및 관리</strong>: 엔진은 여러 Toolkit <strong>앱을 로드</strong>하고 <strong>실행</strong>할 수 있으며, 이러한 앱 간의 통신을 관리한다. 이를 통해 사용자는 <strong>하나의 통합된 인터페이스</strong> 내에서 <strong>여러 ShotGrid 기능</strong>을 사용할 수 있다.</p>
</li>
<li><p><strong>이벤트 처리</strong>: 엔진은 애플리케이션 내의 <strong>이벤트(예: 파일 열기, 저장, 닫기 등)를 감지</strong>하고 이에 따라 적절한 액션을 수행한다. 이를 통해 <strong>ShotGrid와의 동기화가 실시간</strong>으로 이루어진다.</p>
</li>
<li><p><strong>사용자 인터페이스 통합</strong>: 엔진은 애플리케이션의 <strong>사용자 인터페이스에 ShotGrid 기능을 통합</strong>한다. 
예를 들어, <code>Maya</code>의 메뉴 바에 <strong>ShotGrid 관련 메뉴를 추가</strong>하거나, <code>Nuke</code>의 인터페이스에 <strong>ShotGrid 패널을 삽입</strong>하는 등의 작업을 수행할 수 있다.</p>
</li>
</ul>
<h3 id="주요-엔진-예시">주요 엔진 예시</h3>
<p><code>tk-maya</code>: Autodesk사의 <code>Maya</code>를 위한 엔진
<code>tk-nuke</code>: The Foundry사의 <code>Nuke</code>를 위한 엔진
<code>tk-houdini</code>: SideFX사의 <code>Houdini</code>를 위한 엔진
<code>tk-photoshopcc</code>: Adobe사의 <code>Photoshop</code>을 위한 엔진</p>
<h3 id="설정-및-커스터마이징">설정 및 커스터마이징</h3>
<p>ShotGrid Toolkit의 <strong>엔진</strong>은 구성 파일(<code>config.yml</code>)을 통해 설정할 수 있으며, 필요에 따라 <strong>커스터마이징</strong>이 가능하다.</p>
<p>이러한 설정 파일을 통해 각 엔진의 <strong>동작 방식을 조정</strong>하고, 특정 프로젝트나 스튜디오의 요구에 맞게 <strong>최적화</strong>할 수 있다.</p>
<blockquote>
<p><code>config.yml</code>에 대한 자세한 내용은 <a href="https://velog.io/@td_dreamtree/ShotGrid-Toolkit-%EC%A0%9C%EC%9E%91">ShotGrid Toolkit에 엔진, 앱 등록하기</a>를 참고</p>
</blockquote>
<h3 id="엔진-개발-및-확장">엔진 개발 및 확장</h3>
<p>ShotGrid Toolkit은 <strong>오픈소스</strong>이기 때문에, 사용자나 개발자가 필요에 따라 <strong>새로운 엔진을 개발</strong>하거나 <strong>기존 엔진을 확장</strong>할 수 있다. 
이를 통해 다양한 애플리케이션과의 통합을 더욱 <strong>유연하게 관리</strong>할 수 있다.</p>
<p>ShotGrid Toolkit의 엔진은 애플리케이션과 ShotGrid 간의 효율적인 통합을 위해 필수적인 요소이며, 이를 통해 사용자들은 <strong>더 나은 작업 흐름</strong>과 <strong>생산성</strong>을 경험할 수 있다.</p>
<h2 id="엔진의-구성요소">엔진의 구성요소</h2>
<p>ShotGrid Toolkit 엔진은 여러 파일과 스크립트로 구성되어 있으며, 이들은 각각 엔진의 동작과 기능을 구성한다.
엔진에 구성하는 기본적인 스크립트에 대해 알아보자.</p>
<h3 id="파일-구성">파일 구성</h3>
<ul>
<li><p><code>startup.py</code></p>
<ul>
<li><p>엔진의 초기화와 시작점을 정의하는 파일이다. 엔진이 로드될 때 가장 먼저 실행된다.</p>
</li>
<li><p>주요 역할: </p>
<ul>
<li>환경 변수 설정</li>
<li>초기 변수 설정</li>
<li>기본 설정 로드</li>
</ul>
</li>
</ul>
</li>
<li><p><code>engine.py</code></p>
<ul>
<li><p>엔진의 핵심 로직을 포함하는 파일로, 애플리케이션과의 통신을 관리한다.</p>
</li>
<li><p>주요 클래스 및 메서드:</p>
<ul>
<li><code>Engine</code>: 엔진의 주된 기능을 구현하는 클래스</li>
<li><code>init_engine()</code>: 엔진 초기화를 수행하는 메서드</li>
<li><code>post_engine_init()</code>: 엔진 초기화 후 추가 설정을 수행하는 메서드</li>
<li><code>destroy_engine()</code>: 엔진 종료 및 리소스 정리를 담당하는 메서드</li>
<li><code>register_command()</code>: 앱을 엔진에 등록하고 명령을 추가하는 메서드</li>
<li><code>log_debug()</code>, <code>log_info()</code>, <code>log_warning()</code>, <code>log_error()</code>: 로그 메시지를 출력하는 메서드</li>
</ul>
</li>
</ul>
</li>
<li><p><code>info.yml</code>
엔진의 메타데이터와 설정 정보를 포함하는 파일이다.</p>
<ul>
<li><p>주요 항목:</p>
<ul>
<li><code>configuration</code>: 엔진에 대한 다양한 설정</li>
<li><code>display_name</code>: 엔진의 이름</li>
<li><code>description</code>: 엔진의 설명</li>
<li><code>requires_shotgun_version</code>: 엔진이 요구하는 <code>ShotGun</code>의 최소 버전</li>
<li><code>requires_core_version</code>: 엔진이 요구하는 <code>Core</code>의 최소 버전</li>
</ul>
</li>
<li><p>예시:</p>
</li>
</ul>
</li>
</ul>
<pre><code class="language-yaml">          configuration:

            automatic_context_switch:
              type: bool
              description: &quot;Controls whether toolkit should attempt to automatically adjust its
                           context every time the currently loaded file changes. Defaults to True.&quot;
              default_value: True

          display_name: &quot;Flow Production Tracking Engine for Maya&quot;
          description: &quot;Flow Production Tracking Integration in Maya&quot;

          requires_shotgun_version: &quot;1.5.4&quot;
          requires_core_version: &quot;v0.20.5&quot;</code></pre>
<ul>
<li>기타 엔진에 대한 파일 (아이콘, 라이센스 파일 등)</li>
</ul>
<h3 id="폴더-구성">폴더 구성</h3>
<ul>
<li><p><code>config</code> 폴더</p>
<ul>
<li><p>엔진의 <strong>구성 파일</strong>을 포함하는 폴더로, <strong>애플리케이션에 대한 설정</strong> 파일과 각 <strong>Context 별 설정</strong> 파일을 포함한다.</p>
</li>
<li><p>파일 예시:</p>
<ul>
<li><code>tk-maya.yml</code>: <code>Maya</code>에 대한 설정 파일</li>
<li><code>asset_step.yml</code>: &#39;Asset Step&#39; Context에 대한 설정 파일</li>
<li><code>shot_step.yml</code>: &#39;Shot Step&#39; Context에 대한 설정 파일</li>
</ul>
<p>다음은 <code>Maya</code> 엔진 설정 파일(<code>tk-maya.yml</code>)의 예시이다.</p>
<pre><code class="language-yaml">engines:
  tk-maya:
    apps:
      tk-multi-workfiles2:
        location:
          name: tk-multi-workfiles2
          type: app_store
          version: v0.10.2
      tk-multi-publish2:
        location:
          name: tk-multi-publish2
          type: app_store
          version: v2.5.0
    location:
      name: tk-maya
      type: app_store
      version: v0.10.2</code></pre>
</li>
</ul>
</li>
</ul>
<p>이 예시는 <code>tk-maya</code> 엔진을 설정하고, 그 안에 <code>tk-multi-workfiles2</code>와 <code>tk-multi-publish2</code> 앱을 포함하는 방법을 보여준다. 
각각의 앱은 <code>app_store</code>에서 가져오며 특정 버전을 사용한다.</p>
<ul>
<li><p><code>hooks</code> 폴더</p>
<ul>
<li><p>엔진의 다양한 이벤트에 대한 <strong>훅(hook) 스크립트</strong>를 포함하는 폴더이다. 
사용자가 특정 이벤트에 대해 커스터마이징된 동작을 정의할 수 있다.</p>
</li>
<li><p>각 <strong>앱 별 작동 방식</strong>에 대한 스크립트도 포함한다.</p>
</li>
</ul>
</li>
<li><p><code>python</code> 폴더</p>
<ul>
<li><p>엔진의 <strong>주요 파이썬 코드 파일</strong>을 포함하는 폴더</p>
</li>
<li><p>주요 파일:</p>
<ul>
<li><code>menu_generation.py</code>: <strong>ShotGrid 메뉴</strong>를 DCC의 <strong>메뉴바에 생성</strong>하는 스크립트</li>
<li><code>bootstrap.py</code>: 엔진이 실행되기 전, 초기화 작업을 수행하는 스크립트</li>
</ul>
</li>
</ul>
</li>
<li><p>전체적인 폴더 구조 예시</p>
<pre><code class="language-arduino">tk-maya/
│
├── hooks/
│   ├── tk-multi-publish2/
│       ├── start_version_control.py
│       ├── publish_session.py
│   └── tk-multi-workfiles2/
│       └── scene_operation_tk_maya.py
│
├── python/
│   ├── startup/
│       └── bootstrap.py
│   └── tk-maya/
│       └── menu_generation.py
│
├── config/
│   └── env/
│       ├── includes/
│           ├── settings/
│               └── tk-maya.yml
│           └── engine_locations.yml
│       ├── asset_step.py
│       └── shot_step.py
│
├── startup.py
├── engine.py
├── info.yml
└── etc.</code></pre>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Rocky 9.4에 SG Desktop App 이식하기]]></title>
            <link>https://velog.io/@td_dreamtree/Rocky-9.4%EC%97%90-ShotGrid-Desktop-App-1.8.0-%EC%A0%81%EC%9A%A9%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@td_dreamtree/Rocky-9.4%EC%97%90-ShotGrid-Desktop-App-1.8.0-%EC%A0%81%EC%9A%A9%ED%95%98%EA%B8%B0</guid>
            <pubDate>Tue, 04 Jun 2024 06:26:00 GMT</pubDate>
            <description><![CDATA[<p>이전에 <strong>Centos7</strong>에서 개발된 <code>ShotGrid Desktop App 1.8.0</code> 엔진 및 앱을 <strong>Rocky 9.4</strong>에 이식하게 됐다.</p>
<p><strong>Centos7</strong>의 경우 <strong>Python2와 3를 모두 지원</strong>하지만 <strong>Rocky 9</strong>부터는 <strong>Python2를 공식적으로 지원하지 않기 때문에</strong></p>
<p><strong>Python2 기반의 DCC 버전은 사용하지 못할 것</strong>으로 예상된다.</p>
<p>또한 라이브러리의 버전이 호환되지 않을 수 있으므로 <strong>miniconda3</strong>를 이용해 <code>Python 3.7.7</code> 기반의 가상 환경을 구축하고 해당 라이브러리를 연동하는 식으로 <strong>라이브러리 호환성 문제를 해결</strong>했다.</p>
<h2 id="목차">목차</h2>
<ul>
<li><a href="#miniconda3-%EC%84%B8%ED%8C%85">miniconda3 세팅</a></li>
<li><a href="#rocky-94%EC%97%90%EC%84%9C-rez-%EC%84%B8%ED%8C%85">Rocky9.4에서 rez 세팅</a></li>
<li><a href="#%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%A8-%EC%8B%A4%ED%96%89-%ED%85%8C%EC%8A%A4%ED%8A%B8">프로그램 실행 테스트</a><ul>
<li><a href="#%ED%85%8C%EC%8A%A4%ED%8A%B8%ED%95%9C-dcc-%EB%B2%84%EC%A0%84">테스트한 DCC 버전</a></li>
</ul>
</li>
<li><a href="#shotgrid-desktop-app-v180">ShotGrid Desktop App v1.8.0</a></li>
<li><a href="#nuke">Nuke</a><ul>
<li><a href="#112v5--122v2">11.2v5 / 12.2v2</a></li>
<li><a href="#132v2">13.2v2</a></li>
<li><a href="#130v1">13.0v1</a></li>
</ul>
</li>
<li><a href="#maya">Maya</a><ul>
<li><a href="#2019--20192">2019 / 2019.2</a></li>
<li><a href="#20221--20224">2022.1 / 2022.4</a></li>
<li><a href="#20232">2023.2</a></li>
<li><a href="#20242">2024.2</a></li>
</ul>
</li>
<li><a href="#mari-60v2">Mari 6.0v2</a></li>
<li><a href="#houdini">Houdini</a><ul>
<li><a href="#195805">19.5.805</a></li>
<li><a href="#200688">20.0.688</a></li>
</ul>
</li>
<li><a href="#katana-60v4">Katana 6.0v4</a></li>
<li><a href="#clarisse-5011">Clarisse 5.0.11</a></li>
<li><a href="#%EC%84%A4%EC%B9%98%ED%95%9C-%EB%9D%BC%EC%9D%B4%EB%B8%8C%EB%9F%AC%EB%A6%AC-%EB%AA%A9%EB%A1%9D">설치한 라이브러리 목록</a></li>
<li><a href="#daily">Daily</a></li>
</ul>
<h2 id="miniconda3-세팅">miniconda3 세팅</h2>
<p><strong>Centos 7</strong>에서 <code>Python 3.7.7</code>로 대부분의 작업을 진행했기 때문에, <strong>Rocky 9</strong>에서도 최대한 충돌을 피하고자 <code>Python 3.7.7</code>을 사용해 <code>ShotGrid Desktop App</code>을 세팅하기로 결정했다.</p>
<p>또한 <strong>Clarisse</strong> 실행과 라이브러리 문제를 원활하게 해결하기 위해 <strong>miniconda3</strong>로 가상환경을 생성했다.</p>
<ul>
<li><p><code>miniconda3</code> 설치</p>
<pre><code class="language-sh">mkdir -p ~/miniconda3
wget https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh -O ~/miniconda3/miniconda.sh
bash ~/miniconda3/miniconda.sh -b -u -p ~/miniconda3
rm -rf ~/miniconda3/miniconda.sh

~/miniconda3/bin/conda init bash
~/miniconda3/bin/conda init zsh</code></pre>
</li>
<li><p>&#39;clarisse&#39; 가상환경을 <code>Python 3.7.7</code>로 생성</p>
<pre><code class="language-sh">]$ conda create -n &#39;clarisse&#39; python=3.7.7

...
Preparing transaction: done
Verifying transaction: done
Executing transaction: done
#
# To activate this environment, use
#
#     $ conda activate nn
#
# To deactivate an active environment, use
#
#     $ conda deactivate</code></pre>
</li>
</ul>
<h2 id="rocky-94에서-rez-세팅">Rocky 9.4에서 rez 세팅</h2>
<p><strong>Centos 7</strong>에서 구성된 <code>rez</code>의 패키지는 오직 <strong>Centos 7</strong>에서만 작동한다.
그렇기 때문에 <code>rez-bind</code>를 통해 <strong>Rocky 9</strong>에서 Python 패키지를 추가해줘야 <code>rez</code> 패키지들을 정상적으로 사용할 수 있다.</p>
<p>로컬의 존재하는 Python의 경우 <code>3.9.18</code>이므로 miniconda3에서 생성한 <code>Python 3.7.7</code> 가상환경 내에서 <code>rez-bind</code>를 실행하여 새로운 패키지를 생성했다.</p>
<pre><code class="language-sh">]$ conda activate clarisse</code></pre>
<pre><code class="language-sh">(clarisse)]$ rez-bind python

The following packages were installed:

PACKAGE   URI
-------   ---
arch      /home/username/packages/arch/x86_64/package.py
os        /home/username/packages/os/rocky-9.4/package.py
platform  /home/username/packages/platform/linux/package.py
python    /home/username/packages/python/3.7.7/package.py</code></pre>
<h2 id="프로그램-실행-테스트">프로그램 실행 테스트</h2>
<p>이제 기본적인 설정이 끝났다.</p>
<p>이후 모든 프로그램이 <strong>정상적으로 작동하는지 확인</strong>해야 한다.
설치가 필요한 라이브러리는 <strong>miniconda3에서 끌어와 해결</strong>하거나 <strong>로컬에 직접 설치</strong>했다.</p>
<h3 id="테스트한-dcc-버전">테스트한 DCC 버전</h3>
<ul>
<li>Nuke: <code>11.2v5</code>, <code>12.2v2</code>, <code>13.0v1</code>, <code>13.2v2</code></li>
<li>Maya: <code>2019</code>, <code>2019.2</code>, <code>2022.1</code>, <code>2022.4</code>, <code>2023.2</code>, <code>2024.2</code></li>
<li>Mari: <code>6.0v2</code></li>
<li>Houdini: <code>19.5.605</code>, <code>20.0.688</code></li>
<li>Katana: <code>6.0v4</code></li>
<li>Clarisse: <code>5.0.11</code></li>
</ul>
<h2 id="shotgrid-desktop-app-v180">ShotGrid Desktop App v1.8.0</h2>
<h4 id="libsslso11-에러-발생"><code>libssl.so.1.1</code> 에러 발생</h4>
<ul>
<li><a href="https://blanche-star.tistory.com/entry/APM-%EC%84%A4%EC%B9%98-openssl-%EC%B5%9C%EC%8B%A0%EB%B2%84%EC%A0%84%EC%84%A4%EC%B9%98%EC%86%8C%EC%8A%A4%EC%84%A4%EC%B9%98-shared%EC%84%A4%EC%B9%98">libssl.so.1.1 설치</a> 후 <strong>정상 작동 확인</strong></li>
</ul>
<h2 id="nuke">Nuke</h2>
<h3 id="112v5--122v2">11.2v5 / 12.2v2</h3>
<p><strong>Python2 기반으로 실행 불가능</strong></p>
<h3 id="132v2">13.2v2</h3>
<h4 id="libgluso1-에러-발생"><code>libGLU.so.1</code> 에러 발생</h4>
<ul>
<li><code>libGLU</code> 설치하여 해결<pre><code class="language-sh">sudo dnf install mesa-libGLU</code></pre>
<h4 id="qtqpaplugin-에러-발생"><code>qt.qpa.plugin</code> 에러 발생</h4>
</li>
<li><code>qt5-qtx11extras</code> 설치 후 <code>Nuke 13.2v2</code> <strong>정상 작동 확인</strong><pre><code class="language-sh">sudo dnf install qt5-qtx11extras</code></pre>
</li>
</ul>
<h3 id="130v1">13.0v1</h3>
<h4 id="libffiso6-에러-발생"><code>libffi.so.6</code> 에러 발생</h4>
<ul>
<li><code>miniconda3</code> 설치 후 <code>rez</code> 패키지에서 <code>LD_LIBRARY_PATH</code> 환경변수 추가</li>
<li><strong>정상 작동 확인</strong></li>
</ul>
<h2 id="maya">Maya</h2>
<h3 id="2019--20192">2019 / 2019.2</h3>
<h4 id="python2-기반으로-실행-불가능">Python2 기반으로 실행 불가능</h4>
<h3 id="20221--20224">2022.1 / 2022.4</h3>
<h4 id="libpng15so15-에러-발생"><code>libpng15.so.15</code> 에러 발생</h4>
<ul>
<li><code>libpng15</code>설치<pre><code class="language-sh">sudo dnf install libpng15</code></pre>
</li>
</ul>
<h4 id="libsslso10-에러-발생"><code>libssl.so.10</code> 에러 발생</h4>
<ul>
<li><code>libssl.so.10</code>설치 실패<pre><code class="language-sh">sudo yum install compat-openssl10</code></pre>
</li>
</ul>
<p>확인 결과 <strong>Rocky 8</strong> 까지는 <strong>openssl10</strong>을 지원하지만 <strong>Rocky 9</strong>은 아직 공식 지원하지 않음.</p>
<p><a href="https://copr.fedorainfracloud.org/coprs/mroche/vfx-compatibility/">커스텀 리포지토리</a>로 <strong>Rocky9</strong>에서 컴파일된 <strong>openssl10</strong>을 설치.</p>
<pre><code class="language-sh"># Ways to enable via DNF (requires &#39;dnf-plugins-core&#39; to be installed)
sudo dnf copr enable mroche/vfx-compatibility epel-9-x86_64
sudo dnf config-manager --add-repo https://copr.fedorainfracloud.org/coprs/mroche/vfx-compatibility/repo/epel-9/mroche-vfx-compatibility-epel-9.repo

# Enable via curl
sudo curl -JLo /etc/yum.repos.d/mroche-vfx-compatibility.repo &quot;https://copr.fedorainfracloud.org/coprs/mroche/vfx-compatibility/repo/epel-9/mroche-vfx-compatibility-epel-9.repo&quot;</code></pre>
<h4 id="adlsdk_status_adls_not_found-에러-발생">ADLSDK_STATUS_ADLS_NOT_FOUND 에러 발생</h4>
<ul>
<li><p><a href="https://www.autodesk.com/support/technical/article/caas/tsarticles/ts/f5IhBc15i0kOwzBb8lcEN.html">라이센싱 시스템 설치</a>로 해결</p>
<pre><code class="language-sh">wget http://damassets.autodesk.net/content/dam/autodesk/external-assets/support-articles/autodesk-licensing-service-download/14-2-0-10911/AdskLicensingInstaller-linux-14.2.0.10911.tar.gz

tar -zxvf AdskLicensingInstaller-linux-14.2.0.10911.tar.gz

cd adsklicensinginstaller

sudo sh ./install.sh</code></pre>
</li>
</ul>
<h4 id="adlsdk_status_license_checkout_error-에러-발생">ADLSDK_STATUS_LICENSE_CHECKOUT_ERROR 에러 발생</h4>
<ul>
<li><a href="https://forums.autodesk.com/t5/maya-forum/maya-2023-rocky-linux-adlsdk-status-license-checkout-error/td-p/11266119">라이센스 파일 강제 인식</a>으로 해결</li>
<li>제품 코드: <code>maya 2022</code>(657N1), <code>maya 2023</code>(657O1)<pre><code class="language-sh">sudo /opt/Autodesk/AdskLicensing/Current/helper/AdskLicensingInstHelper register -pk 657N1 -pv 2022.0.0.F -el EN_US -cf /var/opt/Autodesk/Adlm/Maya2022/MayaConfig.pit</code></pre>
</li>
</ul>
<h4 id="libpng12so0-에러-발생"><code>libpng12.so.0</code> 에러 발생</h4>
<ul>
<li><code>libpng12</code> 설치<pre><code class="language-sh">sudo dnf install libpng12</code></pre>
<h4 id="빈-화면이-출력되는-에러-발생">빈 화면이 출력되는 에러 발생</h4>
</li>
<li>QT 관련 에러로 예상되어 <code>QT_DEBUG_PLUGINS</code> 환경변수 추가로 디버깅 출력<pre><code class="language-sh">export QT_DEBUG_PLUGINS=1</code></pre>
<pre><code>Cannot load library /usr/autodesk/maya2023/plugins/imageformats/libqmng.so: (libmng.so.1: 동적 오브젝트 파일을 열 수 없습니다: 그런 파일이나 디렉터리가 없습니다)
QLibraryPrivate::loadPlugin failed on &quot;/usr/autodesk/maya2023/plugins/imageformats/libqmng.so&quot; : &quot;Cannot load library /usr/autodesk/maya2023/plugins/imageformats/libqmng.so: (libmng.so.1: 동적 오브젝트 파일을 열 수 없습니다: 그런 파일이나 디렉터리가 없습니다)&quot;</code></pre></li>
<li><code>libmng.so.1</code> 에러가 원인인 것으로 확인</li>
<li>miniconda3 가상환경에 존재하지 않는 라이브러리임</li>
<li><strong>Rocky 9</strong>에서 사용하는 <code>libmng.so.2</code>와 충돌하므로 <strong>로컬에 설치 불가</strong></li>
</ul>
<h3 id="20232">2023.2</h3>
<h4 id="빈-화면이-출력되는-에러-발생-1">빈 화면이 출력되는 에러 발생</h4>
<ul>
<li><code>Maya 2022</code>와 같은 원인으로 확인</li>
<li><strong>Rocky 9</strong>에서 <code>Maya 2023</code> <strong>사용 불가</strong></li>
</ul>
<h3 id="20242">2024.2</h3>
<h4 id="설치-중-libnslso1-에러-발생">설치 중 <code>libnsl.so.1</code> 에러 발생</h4>
<ul>
<li><code>libnsl</code> 설치 후 해결<pre><code class="language-sh">sudo dnf install libnsl</code></pre>
</li>
<li>이후 <strong>정상 작동 확인</strong></li>
</ul>
<h2 id="mari-60v2">Mari 6.0v2</h2>
<p><strong>정상 작동 확인</strong></p>
<h2 id="houdini">Houdini</h2>
<h3 id="195805">19.5.805</h3>
<h4 id="실행-도중-houdini-종료됨">실행 도중 Houdini 종료됨</h4>
<ul>
<li>그래픽 드라이버 업데이트 후 <strong>정상 작동 확인</strong></li>
</ul>
<h3 id="200688">20.0.688</h3>
<ul>
<li>정상 실행은 되나, 라이센스 미보유로 <strong>내부 테스트 불가</strong></li>
</ul>
<h2 id="katana-60v4">Katana 6.0v4</h2>
<h4 id="glsl-에러-발생">GLSL 에러 발생</h4>
<ul>
<li>그래픽 드라이버 업데이트 및 재부팅 후 <strong>정상 작동 확인</strong></li>
</ul>
<h2 id="clarisse-5011">Clarisse 5.0.11</h2>
<h4 id="ix-환경변수-관련-에러-발생">IX 환경변수 관련 에러 발생</h4>
<p><code>miniconda3</code>의 <code>clarisse</code>환경에 설치된 라이브러리 경로가 <code>clarisse.env</code>에 추가되도록 스크립트 작성 후 <strong>정상 작동 확인</strong></p>
<h2 id="설치한-라이브러리-목록">설치한 라이브러리 목록</h2>
<h4 id="로컬에-설치">로컬에 설치</h4>
<ul>
<li><p><code>libssl.so.1.1</code></p>
</li>
<li><p><code>libGLU.so.1</code></p>
</li>
<li><p><code>libnsl.so.1</code></p>
</li>
<li><p><code>libssl.so.10</code></p>
</li>
<li><p>Maya 2022~2023 (<strong>Rocky9 에서 사용 불가</strong>)</p>
<ul>
<li><code>libpng15.so.15</code></li>
<li><code>libpng12.so.0</code></li>
</ul>
</li>
</ul>
<h4 id="miniconda3에서-연결">miniconda3에서 연결</h4>
<ul>
<li><code>libffi.so.6</code></li>
</ul>
<h2 id="daily">Daily</h2>
<h4 id="240603">24.06.03</h4>
<ul>
<li><p><code>Gnome-Terminal</code> 기반 OS에서 <code>ShotGrid Desktop App</code> 실행 후 <code>pin to menu</code> 기능 클릭 시 <code>ShotGrid Desktop App</code> UI가 사라지는 현상 발생.</p>
</li>
<li><p><code>ShotGrid Desktop App</code> 재설치 후에도 복구되지 않아 OS 재설치 진행.</p>
</li>
<li><p>해당 기능 비활성화 예정.</p>
</li>
</ul>
<h4 id="240604">24.06.04</h4>
<ul>
<li><p><code>Nuke 13.0v1</code> / <code>Nuke 13.2v2</code>: 라이브러리 설치 후 <strong>정상 작동 확인</strong></p>
</li>
<li><p><code>Katana 6.0v4</code>: 그래픽 드라이버 업데이트 후 <strong>정상 작동 확인</strong></p>
</li>
<li><p><code>Mari 6.0v2</code>: 별도 작업 진행 없이 <strong>정상 작동 확인</strong></p>
</li>
<li><p><code>Maya 2019</code> / <code>Maya 2019.2</code>: Python2 관련 에러로 Rocky 9에서 <strong>사용 불가.</strong> (Rocky 9은 Python3 지원)</p>
</li>
<li><p><code>Maya 2022.1</code> / <code>Maya 2022.4</code>: 라이브러리 문제 해결 후 정상 작동하였으나, 그래픽 드라이버 업데이트 후 <strong>빈 화면 출력</strong></p>
</li>
<li><p><code>Maya 2023</code>: <code>Arnold</code> import 관련 에러 및 기존 QT 에러 동일하게 발생</p>
</li>
<li><p><code>Houdini 19.0.805</code>(gcc9.3): 그래픽 드라이버 업데이트 후 <strong>정상 작동 확인</strong></p>
</li>
<li><p><code>Houdini 20.0.688</code>(gcc9.3): 라이센스 미보유로 <strong>내부 테스트 불가</strong></p>
</li>
<li><p><code>Clarisse 5.0.11</code>: miniconda3에서 &#39;clarisse&#39; 환경 생성 후 <strong>정상 작동 확인</strong></p>
</li>
</ul>
<h4 id="240605">24.06.05</h4>
<ul>
<li><p><strong>Rocky 9.4</strong> 재설치 후 <strong>그래픽 드라이버</strong> <code>NVIDIA-Linux-x86_64-550.78</code> 재설치</p>
</li>
<li><p><code>Maya</code> 제외한 DCC <strong>정상 작동 확인</strong></p>
</li>
<li><p><code>Maya 2024.2</code> 테스트 결과 <strong>정상 작동 확인</strong></p>
</li>
<li><p><code>Maya 2022</code> 및 <code>Maya 2023</code>의 경우 <code>libmng.so.1</code> 충돌 문제로 <strong>실행 불가</strong></p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[ShotGrid Toolkit에 엔진, 앱 등록하기]]></title>
            <link>https://velog.io/@td_dreamtree/ShotGrid-Toolkit-%EC%A0%9C%EC%9E%91</link>
            <guid>https://velog.io/@td_dreamtree/ShotGrid-Toolkit-%EC%A0%9C%EC%9E%91</guid>
            <pubDate>Fri, 31 May 2024 09:05:20 GMT</pubDate>
            <description><![CDATA[<p>이번엔 ShotGrid Toolkit에 엔진과 앱을 직접 등록하는 방법을 알아보자.</p>
<ul>
<li>이 글은 <a href="https://help.autodesk.com/view/SGDEV/ENU/?guid=SG_Supervisor_Artist_sa_integrations_sa_integrations_user_guide_html#installing-desktop"><strong>ShotGrid Desktop App</strong></a> 설치 및 <a href="https://help.autodesk.com/view/SGDEV/ENU/?guid=SGD_pg_developer_pg_getting_started_advanced_config_html"><strong>Advanced Project Setup</strong></a>이 마무리 된 것을 전제 조건으로 한다.</li>
<li>또한 ShotGrid의 Entity 구성과 Step에 대한 이해가 어느정도 필요하다.</li>
</ul>
<h2 id="목차">목차</h2>
<ul>
<li><a href="#shotgrid-toolkit%EC%9D%B4%EB%9E%80">ShotGrid Toolkit이란?</a></li>
<li><a href="#shotgrid-toolkit%EC%9D%84-%EC%82%AC%EC%9A%A9%ED%95%98%EB%8A%94-%EC%9D%B4%EC%9C%A0">ShotGrid Toolkit을 사용하는 이유</a></li>
<li><a href="#shotgrid-toolkit%EC%97%90-%EC%97%94%EC%A7%84-%EB%93%B1%EB%A1%9D">ShotGrid Toolkit에 엔진 등록</a><ul>
<li><a href="#tk-maya-tk-nuke-tk-houdini"><code>tk-maya</code>, <code>tk-nuke</code>, <code>tk-houdini</code></a></li>
<li><a href="#tk-mari"><code>tk-mari</code></a></li>
<li><a href="#tk-katana-tk-clarisse"><code>tk-katana</code>, <code>tk-clarisse</code></a></li>
<li><a href="#tank-cache_apps-%EC%8B%A4%ED%96%89"><code>./tank cache_apps</code> 실행</a></li>
</ul>
</li>
<li><a href="#shotgrid-toolkit%EC%97%90-%EC%95%B1-%EB%93%B1%EB%A1%9D">ShotGrid Toolkit에 앱 등록</a></li>
</ul>
<h2 id="shotgrid-toolkit이란">ShotGrid Toolkit이란?</h2>
<p>현재는 <strong>Flow Production Tracking</strong> 이라는 이름으로 변경되었으며 <code>Nuke</code>, <code>Maya</code>, <code>Houdini</code> 등 <strong>다양한 DCC와 통합</strong>하여 <strong>파이프라인을 효율적으로 관리</strong>할 수 있는 도구이다.</p>
<h2 id="shotgrid-toolkit을-사용하는-이유">ShotGrid Toolkit을 사용하는 이유</h2>
<p><strong>ShotGrid</strong>를 사용하다보면 일일이 <strong>사용자가 ShotGrid 페이지에 접속</strong>하여 Task를 등록 및 확인하거나 <strong>Publish할 데이터를 직접 업로드</strong>하게 된다.</p>
<p>그러나 <strong>Shotgrid Toolkit</strong>를 활용하면 <strong>프로젝트 파일 및 에셋의 Save, Open, Publish, Load와 같은 작업을 DCC 툴 내에서 수행</strong>할 수 있게 되므로 업무를 더 빠르고 효율적으로 처리할 수 있다.</p>
<h2 id="shotgrid-toolkit에-엔진-등록">ShotGrid Toolkit에 엔진 등록</h2>
<p><strong>ShotGrid Toolkit</strong>은 <strong>엔진</strong>과 <strong>앱</strong>을 등록 후 그들을 <code>.yml</code>파일에서 엮어서 사용하게 된다.
먼저, <strong>엔진</strong>을 등록하는 방법에 대해 알아보자.</p>
<h3 id="tk-maya-tk-nuke-tk-houdini">tk-maya, tk-nuke, tk-houdini</h3>
<p><code>tk-maya</code>, <code>tk-nuke</code>, <code>tk-houdini</code>와 같이 <strong>ShotGrid 자체에서 툴킷 엔진을 지원</strong>하는 경우, 비교적 간단하게 <strong>ShotGrid Toolkit</strong>에 <strong>엔진</strong>을 등록할 수 있다.</p>
<blockquote>
<p><em>추가적으로 공식 지원되는 엔진 목록은 <a href="https://help.autodesk.com/view/SGDEV/ENU/?guid=SGD_pc_toolkit_engines_html">여기</a>에서 확인할 수 있다.</em></p>
</blockquote>
<h4 id="1-환경변수-추가">1. 환경변수 추가</h4>
<p>먼저, 각 <strong>엔진</strong>에 포함된 <strong>시작 스크립트</strong>(<code>menu.py</code>, <code>userSetup.py</code>)를 터미널에서 인식할 수 있도록 <strong>환경변수를 추가</strong>한다.</p>
<blockquote>
<p><em>DCC마다 시작 시 호출하는 스크립트의 환경변수가 다르므로 해당 DCC의 공식 문서를 참조하는 것이 좋다.</em></p>
</blockquote>
<pre><code class="language-sh"># example - Nuke
export NUKE_PATH=&quot;/Your/Pipeline/Configuration/install/app_store/tk-nuke/v0.14.4/plugins/basic&quot;</code></pre>
<h4 id="2-engine_locationsyml에-엔진-추가">2. engine_locations.yml에 엔진 추가</h4>
<p><code>tk-maya</code>, <code>tk-nuke</code>, <code>tk-houdini</code>의 경우 app_store에서 <strong>엔진</strong>을 지원하므로, <code>config/env/includes/engine_locations.yml</code>에 각 <strong>엔진</strong>을 추가하여 등록할 수 있다.</p>
<pre><code class="language-yaml"># engine_locations.yml

engines.tk-maya.location:
  type: app_store
  name: tk-maya
  version: v0.12.0
engines.tk-nuke.location:
  type: app_store
  name: tk-nuke
  version: v0.14.4
engines.tk-houdini.location:
  type: app_store
  name: tk-houdini
  version: v1.9.1</code></pre>
<h4 id="3-configenvincludessettings에-각-엔진-별-yml-파일-생성">3. <code>config/env/includes/settings</code>에 각 엔진 별 yml 파일 생성</h4>
<p>각 <strong>엔진</strong>에서 사용하고자 하는 <strong>앱</strong>을 <code>.yml</code> 파일에 작성한다.
주의해야 할 점은 <strong>앱</strong>의 경우 <strong>ShotGrid Context</strong> 별로 지정할 수 있기 때문에, <strong>Context 별로 설정을 만들어주어야 한다</strong>는 것이다.</p>
<p>아래는 <code>tk-nuke.yml</code>의 작성 예시이다.</p>
<pre><code class="language-yaml">includes:
#  - ../app_locations.yml
  - ../engine_locations.yml
#  - ./tk-multi-loader2.yml
#  - ./tk-multi-publish2.yml
  - ./tk-multi-workfiles2.yml

# asset_step
settings.tk-nuke.asset_step:
apps:
   # tk-multi-about:
   #   location: &quot;@apps.tk-multi-about.location&quot;
   # tk-multi-loader2: &quot;@settings.tk-multi-loader2.nuke&quot;
   # tk-multi-publish2: &quot;@settings.tk-multi-publish2.nuke.asset_step&quot;
   tk-multi-workfiles2: &quot;@settings.tk-multi-workfiles2.nuke.asset_step&quot;
menu_favourites:
  - {app_instance: tk-multi-workfiles2, name: File Open...}
  - {app_instance: tk-multi-workfiles2, name: File Save...}
#  - {app_instance: tk-multi-publish2, name: Publish...}
#  - {app_instance: tk-multi-loader2, name: Load}
location: &#39;@engines.tk-nuke.location&#39;</code></pre>
<h3 id="tk-mari">tk-mari</h3>
<h4 id="1-startuppy-파일-작성">1. startup.py 파일 작성</h4>
<p><code>tk-mari</code>의 경우, 다른 DCC와 다르게 <strong>엔진</strong> 디렉토리에 <code>startup.py</code>가 제공되지 않아 <strong>ShotGrid Desktop App</strong>에서 <code>Mari</code>를 실행할 수 없다.
그렇기 때문에 별도로 <code>startup.py</code>를 작성하여 <strong>ShotGrid Desktop App에서 Mari가 작동</strong>하도록 했다.</p>
<p><code>startup.py</code>의 경우 <code>tk-nuke</code>의 <code>startup.py</code>를 기반으로 작성했다.</p>
<ul>
<li><code>tk-mari</code> : <a href="https://github.com/junopark00/tk-mari">https://github.com/junopark00/tk-mari</a></li>
</ul>
<h4 id="2-환경변수-추가">2. 환경변수 추가</h4>
<p><code>Mari</code>를 시작할 때 <code>init.py</code>를 인식하도록 <code>MARI_SCRIPT_PATH</code> 환경변수에 해당 경로를 추가한다.</p>
<pre><code class="language-sh">export MARI_SCRIPT_PATH=&quot;$MARI_SCRIPT_PATH:/tk-mari/startup&quot;</code></pre>
<h4 id="3-engine_locationsyml에-엔진-추가">3. engine_locations.yml에 엔진 추가</h4>
<p><code>tk-mari</code>의 경우 커스텀 과정을 거쳤기 때문에 <code>app_store</code>에서 엔진을 가져오는 것이 아닌 <code>dev</code> 혹은 <code>git</code>을 통해 엔진을 등록한다.</p>
<p>이때 주의할 점은 <strong><code>engine.py</code>가 존재하는 디렉토리 경로</strong>를 <code>path</code>에 추가해야 한다.</p>
<pre><code class="language-yaml"># dev
engines.tk-mari.location:
  type: dev
  name: tk-mari
  path: &quot;/home/juno/workspace/engines/tk-mari&quot;

# git
engines.tk-mari.location:
  type: git
  name: tk-mari
  version: v1.4.1
  path: &quot;github.com/junopark00/tk-mari.git&quot;</code></pre>
<h4 id="4-configenvincludessettings에-각-엔진-별-yml-파일-생성">4. <code>config/env/includes/settings</code>에 각 엔진 별 yml 파일 생성</h4>
<p>위와 유사한 방법으로 <code>tk-mari.yml</code>을 작성한다. <code>Mari</code>의 경우 전용 <strong>앱</strong>인 <code>tk-mari-projectmanager</code>도 추가한다.</p>
<pre><code class="language-yaml">includes:
#- ../app_locations.yml
- ../engine_locations.yml
- ./tk-multi-loader2.yml
- ./tk-multi-publish2.yml
#- ./tk-multi-screeningroom.yml
#- ./tk-multi-shotgunpanel.yml
#- ./tk-multi-snapshot.yml
- ./tk-multi-workfiles2.yml
- ./tk-mari-project-manager.yml

# asset_step
settings.mari.asset_step:
  apps:
    tk-multi-loader2: &quot;@apps.tk-multi-loader2&quot;
    tk-multi-publish2: &quot;@apps.tk-multi-publish2&quot;
    tk-multi-workfiles2: &quot;@settings.tk-multi-workfiles2.mari&quot;
    tk-mari-projectmanager: &quot;@settings.tk-mari-projectmanager&quot;
  menu_favourites:
  - {app_instance: tk-multi-workfiles2, name: File Open...}
  - {app_instance: tk-multi-workfiles2, name: File Save...}
  - {app_instance: tk-multi-loader2, name: Load}
  - {app_instance: tk-multi-publish2, name: Publish...}
  location: &quot;@engines.tk-mari.location&quot;</code></pre>
<h3 id="tk-katana-tk-clarisse">tk-katana, tk-clarisse</h3>
<h4 id="1-엔진-개발">1. 엔진 개발</h4>
<p><code>Katana</code>와 <code>Clarisse</code>처럼 <strong>ShotGrid</strong>에서 <strong>공식적으로 엔진을 지원하지 않는 경우</strong> <strong>엔진</strong>을 <strong>직접 개발</strong>하거나 기존에 다른 사람이 <strong>만들어 둔 엔진을 이용</strong>해야 한다.</p>
<p><strong>엔진</strong>을 구성하는 스크립트에는 <code>startup.py</code>, <code>menu_generation.py</code>, <code>engine.py</code> 등이 있으며 특히 <code>menu_generation.py</code>의 경우 DCC 내에 ShotGrid Toolkit 메뉴를 생성하는 스크립트이므로 <strong>각 DCC의 메뉴와 관련된 API</strong>가 사용된다.</p>
<ul>
<li><code>tk-katana</code>: <a href="https://github.com/junopark00/tk-katana">https://github.com/junopark00/tk-katana</a></li>
<li><code>tk-clarisse</code>: <a href="https://github.com/junopark00/tk-clarisse">https://github.com/junopark00/tk-clarisse</a></li>
</ul>
<h4 id="2-환경변수-추가-1">2. 환경변수 추가</h4>
<ul>
<li><p><code>Katana</code>
<code>Katana</code>의 경우 <code>resource</code> 디렉토리를 인식할 수 있도록 <code>KATANA_RESOURCES</code> 환경 변수에 해당 경로를 추가한다.</p>
<pre><code class="language-sh">export KATANA_RESOURCES=&quot;$KATANA_RESOURCES:/tk-katana/resources/Katana&quot;</code></pre>
</li>
<li><p><code>Clarisse</code>
<code>Clarisse</code>는 <code>export</code>를 통해 환경 변수를 등록하는 것이 아닌 <code>~/.isotropix</code> 디렉토리에 존재하는 <code>clarisse.env</code> 파일에 환경변수들을 등록해야 한다.</p>
<p>해당 <strong>엔진</strong>의 경우 Python3 기반으로 작성되었으므로 <code>IX_PYTHON3HOME</code>, <code>IX_PYTHON3PATH</code>를 추가하면 된다.</p>
<pre><code class="language-env"> # ~/.isotropix/clarisse/5.0/clarisse.env

 IX_PYTHON2HOME=/usr/local:/usr
 IX_PYTHON2PATH=/your/python2/libs/path
 IX_PYTHON3HOME=/usr/local:/usr
 IX_PYTHON3PATH=/your/python3/libs/path
 ILISE_SERVER=Your_IP:port
 IX_SHELF_CONFIG_FILE=$IX_PYTHON_API_PATH/shelves/shelf.cfg
 IX_SHELF_SCRIPT_PATH=
 IX_SHELF_ICON_PATH=
 IX_MENU_CONFIG_FILE=$IX_PYTHON_API_PATH/menus/init_menus.py
 IX_MENU_SCRIPT_PATH=
 IX_MENU_ICON_PATH=
 IX_RESOLUTION_PRESET=
 CLARISSE_STARTUP_SCRIPT=
 # Add LD_LIBRARY_PATH if you need.</code></pre>
</li>
</ul>
<h4 id="3-engine_locationsyml에-엔진-추가-1">3. engine_locations.yml에 엔진 추가</h4>
<p><code>tk-katana</code>와 <code>tk-clarisse</code> 또한 <strong>직접 작성된 엔진</strong>이므로 <code>dev</code> 혹은 <code>git</code>을 통해 엔진을 등록한다.</p>
<pre><code class="language-yaml"># dev
engines.tk-katana.location:
  type: dev
  name: tk-katana
  path: &quot;/home/juno/workspace/engines/tk-katana&quot;

# git
engines.tk-clarisse.location:
  type: git
  name: tk-clarisse
  version: v1.4.1
  path: &quot;github.com/junopark00/tk-clarisse.git&quot;</code></pre>
<h4 id="4-configenvincludessettings에-각-엔진-별-yml-파일-생성-1">4. <code>config/env/includes/settings</code>에 각 엔진 별 yml 파일 생성</h4>
<p>위와 유사한 방법으로 <code>tk-katana.yml</code>와 <code>tk-clarisse.yml</code>을 작성한다.</p>
<pre><code class="language-yaml"># tk-katana.yml

includes:
#  - ../app_locations.yml
  - ../engine_locations.yml
#  - ./tk-multi-loader2.yml
#  - ./tk-multi-publish2.yml
  - ./tk-multi-workfiles2.yml

# asset_step
settings.tk-katana.asset_step:
apps:
   # tk-multi-about:
   #   location: &quot;@apps.tk-multi-about.location&quot;
   # tk-multi-loader2: &quot;@settings.tk-multi-loader2.katana&quot;
   # tk-multi-publish2: &quot;@settings.tk-multi-publish2.katana.asset_step&quot;
   tk-multi-workfiles2: &quot;@settings.tk-multi-workfiles2.katana.asset_step&quot;
menu_favourites:
  - {app_instance: tk-multi-workfiles2, name: File Open...}
  - {app_instance: tk-multi-workfiles2, name: File Save...}
#  - {app_instance: tk-multi-publish2, name: Publish...}
#  - {app_instance: tk-multi-loader2, name: Load}
location: &#39;@engines.tk-katana.location&#39;

# tk-clarisse.yml

includes:
#  - ../app_locations.yml
  - ../engine_locations.yml
#  - ./tk-multi-loader2.yml
#  - ./tk-multi-publish2.yml
  - ./tk-multi-workfiles2.yml

# asset_step
settings.tk-clarisse.asset_step:
apps:
   # tk-multi-about:
   #   location: &quot;@apps.tk-multi-about.location&quot;
   # tk-multi-loader2: &quot;@settings.tk-multi-loader2.clarisse&quot;
   # tk-multi-publish2: &quot;@settings.tk-multi-publish2.clarisse.asset_step&quot;
   tk-multi-workfiles2: &quot;@settings.tk-multi-workfiles2.clarisse.asset_step&quot;
menu_favourites:
  - {app_instance: tk-multi-workfiles2, name: File Open...}
  - {app_instance: tk-multi-workfiles2, name: File Save...}
#  - {app_instance: tk-multi-publish2, name: Publish...}
#  - {app_instance: tk-multi-loader2, name: Load}
location: &#39;@engines.tk-clarisse.location&#39;</code></pre>
<h3 id="tank-cache_apps-실행">./tank cache_apps 실행</h3>
<p>엔진 등록과 관련된 파일 작성이 끝나면 <code>pipeline_configuraion</code> 최상단 디렉토리에 존재하는 <code>tank</code> 명령어를 통해 <strong>엔진을 로드 혹은 다운로드</strong> 할 수 있다.</p>
<blockquote>
<p><em>다양한 <code>Tank</code> 명령어에 대한 정보는 <a href="https://help.autodesk.com/view/SGDEV/KOR/?guid=SGD_pg_integrations_admin_guides_advanced_toolkit_administration_html#tank">여기</a>에서 확인</em></p>
</blockquote>
<pre><code class="language-sh">$] ./tank cache_apps

...

Engine tk-katana - OK!
App tk-multi-workfiles2 (Engine tk-katana) - OK!
Engine tk-clarisse - OK!
App tk-multi-workfiles2 (Engine tk-clarisse) - OK!
Framework tk-framework-widget_v0.2.x - OK!
Framework tk-framework-adobe_v1.x.x - OK!
Framework tk-framework-qtwidgets_v2.x.x - OK!
Framework tk-framework-shotgunutils_v4.x.x - OK!
Framework tk-framework-shotgunutils_v5.x.x - OK!
Framework tk-framework-adminui_v0.x.x - OK!
Framework tk-framework-widget_v1.x.x - OK!
Framework tk-framework-desktopserver_v1.x.x - OK!
Framework tk-framework-desktopclient_v0.x.x - OK!
Framework tk-framework-alias_v1.x.x - OK!
Framework tk-framework-aliastranslations_v0.x.x - OK!
Framework tk-framework-lmv_v0.x.x - OK!
Framework tk-framework-lmv_v1.x.x - OK!

Cache apps completed! 0 items downloaded.</code></pre>
<h2 id="shotgrid-toolkit에-앱-등록">ShotGrid Toolkit에 앱 등록</h2>
<p><strong>ShotGrid</strong>에서 기본적으로 제공하는 앱에는 <code>tk-multi-workfiles2</code>와 같이 DCC의 구분없이 사용할 수 있는 <strong>멀티 앱</strong>,
<code>tk-mari-projectmanager</code>와 같이 특정 DCC에 적용할 수 있는 <strong>전용 앱</strong>이 있다.</p>
<blockquote>
<p>필요하다면 직접 앱을 제작하여 적용할 수도 있다.</p>
</blockquote>
<h4 id="1-app_locationsyml-작성">1. app_locations.yml 작성</h4>
<p><strong>ShotGrid Toolkit</strong>에 <strong>앱</strong>을 등록하기 위해서는 <code>app_locations.yml</code>에 <strong>해당 앱에 대한 위치를 지정</strong>해주어야 한다.</p>
<ul>
<li><strong>기본적으로 제공되는 앱</strong>: <code>type</code>이 <code>app_store</code>로 설정하고 <code>version</code> 정보를 작성해야 한다.</li>
<li><strong>로컬 경로에 존재하는 앱</strong>:  <code>type</code>을 <code>dev</code>로 설정하고 <code>path</code>에 앱 경로를 작성하면 된다.</li>
<li><strong>GitHub 리포지토리의 앱</strong>: <code>type</code>을 <code>git</code>으로 설정하고 <code>path</code>에 리포지토리 주소를 작성하면 된다.</li>
</ul>
<p>아래는 <code>app_locations.yml</code>의 예시이다.</p>
<pre><code class="language-yaml"># app_store
apps.tk-multi-launchapp.location:
  type: app_store
  name: tk-multi-launchapp
  version: v0.13.0

# dev
apps.tk-custom-app.location:
  type: dev
  name: tk-custom-app
  path: &quot;/home/juno/workspace/tk-custom-app&quot;

# git
apps.tk-custom-app/location:
  type: git
  name: tk-custom-app
  path: &quot;https://github.com/junopark00/tk-custom-app&quot;</code></pre>
<h4 id="2-configcoretemplatesyml-작성">2. config/core/templates.yml 작성</h4>
<p><strong>ShotGrid Toolkit</strong>은 <code>templates.yml</code>이라는 파일에 의해 DCC에서 작업을 진행하거나 앱에서 사용될 디렉토리를 생성하게 된다.
이에 따라 디렉토리 <strong>스키마</strong>를 구성하고 디렉토리 경로에 대한 정보를 <code>templates.yml</code>에 추가함으로써 앱이 로컬 경로를 활용할 수 있게 된다.</p>
<blockquote>
<p><em>스키마 구성에 대한 자세한 정보는 <a href="https://help.autodesk.com/view/SGDEV/KOR/?guid=SGD_pg_integrations_admin_guides_advanced_toolkit_administration_html">디스크에 폴더 만들기</a> 부분을 참고</em></p>
</blockquote>
<p>아래는 <code>templates.yml</code> 예시의 일부분이다.</p>
<pre><code class="language-yaml">keys:
    Sequence:
        type: str
    Shot:
        type: str
    Step:
        type: str
    sg_asset_type:
        type: str
    Asset:
        type: str
    name:
        type: str
        filter_by: alphanumeric
    iteration:
        type: int
    version:
        type: int
        format_spec: &quot;03&quot;
    version_four:
       type: int
       format_spec: &quot;04&quot;
       alias: version
    timestamp:
        type: str
    width:
        type: int
    height:
        type: int
    segment_name:
        type: str

    # (중략)

paths:
    shot_root: sequences/{Sequence}/{Shot}/{Step}
    asset_root: assets/{sg_asset_type}/{Asset}/{Step}
    sequence_root: sequences/{Sequence}

    #
    # Nuke
    #

    # define the location of a work area
    shot_work_area_nuke:
        definition: &#39;@shot_root/work/nuke&#39;
    # define the location of a publish area
    shot_publish_area_nuke:
        definition: &#39;@shot_root/publish/nuke&#39;
    # The location of WIP script files
    nuke_shot_work:
        definition: &#39;@shot_root/work/nuke/{name}.v{version}.nk&#39;
    # The location of backups of WIP files
    # The location of published nuke script files
    nuke_shot_publish:
        definition: &#39;@shot_root/publish/nuke/{name}.v{version}.nk&#39;

    # (하략)</code></pre>
<p>이처럼 <code>templates.py</code>에는 어떤 경로에 어떤 확장자명으로 파일을 저장 혹은 내보낼지 결정하는 <strong>중요한 내용이 포함</strong>되어 있으므로,
<strong>스키마와 일치하는지, 오타는 없는지</strong> 검토할 필요가 있다.</p>
<p><em><code>templates.py</code> 작성에 대한 자세한 내용은 <a href="https://help.autodesk.com/view/SGDEV/KOR/?guid=SGD_pg_integrations_admin_guides_advanced_toolkit_administration_html">템플릿 구성</a> 부분을 참고</em></p>
<h4 id="3-configenvincludessettings에-설정-파일-작성">3. config/env/includes/settings에 설정 파일 작성</h4>
<p>대부분의 앱에서는 <strong>DCC</strong>와 <strong>Context</strong>에 따라 다른 정보를 받아오기 때문에, 이와 관련된 설정들을 <code>.yml</code>파일에 작성해야 한다.
<code>config/env/includes/settings</code> 디렉토리에 <code>tk-multi-workfiles2.yml</code>, <code>tk-multi-publish2.yml</code>와 같은 파일을 추가하고,
각 DCC에 부여된 <code>Task</code>에서 사용하는 <strong>Context</strong>마다 설정을 추가해야한다.</p>
<blockquote>
<p><em>이에 대한 자세한 내용은 <a href="https://help.autodesk.com/view/SGDEV/KOR/?guid=SGD_pg_integrations_admin_guides_advanced_toolkit_administration_html">앱 및 엔진 구성</a> 부분을 참고</em></p>
</blockquote>
<pre><code class="language-yaml">includes:
- ../app_locations.yml

settings.tk-multi-workfiles2: &amp;settings_tk-multi-workfiles2
  entities:
  - caption: Assets
    entity_type: Asset
    hierarchy: [sg_asset_type, code]
    filters:
    sub_hierarchy:
      entity_type: Task
      filters:
      link_field: entity
      hierarchy: [step]
  - caption: Shots
    entity_type: Shot
    filters:
    hierarchy: [sg_sequence, code]
    sub_hierarchy:
      entity_type: Task
      filters:
      link_field: entity
      hierarchy: [step]
  location: &quot;@apps.tk-multi-workfiles2.location&quot;

# launches at startup.
settings.tk-multi-workfiles2.launch_at_startup:
  launch_at_startup: true
  entities:
  - caption: Assets
    entity_type: Asset
    hierarchy: [sg_asset_type, code]
    filters:
    sub_hierarchy:
      entity_type: Task
      filters:
      link_field: entity
      hierarchy: [step]
  - caption: Shots
    entity_type: Shot
    filters:
    hierarchy: [sg_sequence, code]
    sub_hierarchy:
      entity_type: Task
      filters:
      link_field: entity
      hierarchy: [step]
  location: &quot;@apps.tk-multi-workfiles2.location&quot;

# ---- nuke

# asset_step
settings.tk-multi-workfiles2.nuke.asset_step:
  template_publish: nuke_asset_publish
  template_publish_area: asset_publish_area_nuke
  template_work: nuke_asset_work
  template_work_area: asset_work_area_nuke
  entities:
  - caption: Assets
    entity_type: Asset
    hierarchy: [sg_asset_type, code]
    filters:
    sub_hierarchy:
      entity_type: Task
      filters:
      link_field: entity
      hierarchy: [step]
  - caption: Shots
    entity_type: Shot
    filters:
    hierarchy: [sg_sequence, code]
    sub_hierarchy:
      entity_type: Task
      filters:
      link_field: entity
      hierarchy: [step]
  location: &quot;@apps.tk-multi-workfiles2.location&quot;

# (하략)</code></pre>
<p>이처럼 앱 설정 파일에는 <strong>각 DCC 및 Context 별로</strong> 앱에 어떤 형식으로 정보를 전달할지가 작성되어야 한다.</p>
<h4 id="4-tank-cache_apps-실행">4. ./tank cache_apps 실행</h4>
<p>작성이 끝나면 <code>pipeline_configuraion</code> 최상단 디렉토리에 존재하는 <code>tank</code> 명령어를 통해 <strong>앱을 로드 혹은 다운로드</strong> 할 수 있다.</p>
<blockquote>
<p><em>다양한 <code>Tank</code> 명령어에 대한 정보는 <a href="https://help.autodesk.com/view/SGDEV/KOR/?guid=SGD_pg_integrations_admin_guides_advanced_toolkit_administration_html#tank">여기</a>에서 확인</em></p>
</blockquote>
<pre><code class="language-sh">$] ./tank cache_apps

...

Engine tk-katana - OK!
App tk-multi-workfiles2 (Engine tk-katana) - OK!
Engine tk-clarisse - OK!
App tk-multi-workfiles2 (Engine tk-clarisse) - OK!
Framework tk-framework-widget_v0.2.x - OK!
Framework tk-framework-adobe_v1.x.x - OK!
Framework tk-framework-qtwidgets_v2.x.x - OK!
Framework tk-framework-shotgunutils_v4.x.x - OK!
Framework tk-framework-shotgunutils_v5.x.x - OK!
Framework tk-framework-adminui_v0.x.x - OK!
Framework tk-framework-widget_v1.x.x - OK!
Framework tk-framework-desktopserver_v1.x.x - OK!
Framework tk-framework-desktopclient_v0.x.x - OK!
Framework tk-framework-alias_v1.x.x - OK!
Framework tk-framework-aliastranslations_v0.x.x - OK!
Framework tk-framework-lmv_v0.x.x - OK!
Framework tk-framework-lmv_v1.x.x - OK!

Cache apps completed! 0 items downloaded.</code></pre>
<h2 id="마무리">마무리</h2>
<p>지금까지 <strong>ShotGrid Toolkit</strong>에 <strong>엔진</strong>과 <strong>앱</strong>을 등록하는 방법을 간단하게나마 정리해보았다.</p>
<p>엔진이나 앱 개발의 자세한 내용은 <strong>추후 추가 예정</strong>이다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Papago API 활용해보기]]></title>
            <link>https://velog.io/@td_dreamtree/Naver-API-%ED%99%9C%EC%9A%A9%ED%95%B4%EB%B3%B4%EA%B8%B0</link>
            <guid>https://velog.io/@td_dreamtree/Naver-API-%ED%99%9C%EC%9A%A9%ED%95%B4%EB%B3%B4%EA%B8%B0</guid>
            <pubDate>Fri, 31 May 2024 05:55:14 GMT</pubDate>
            <description><![CDATA[<h2 id="papago-translation-api">Papago Translation API</h2>
<p><strong>Papago Translation API</strong>는 입력된 텍스트를 <strong>다국어로 번역</strong>해주는 기계 번역 API이다.</p>
<p>또한 단순 텍스트 번역 뿐만 아니라 <strong>웹 번역</strong>, <strong>문서 번역</strong>, <strong>언어 인식 기능</strong>도 갖추고 있다.</p>
<h2 id="api-이용-신청하기">API 이용 신청하기</h2>
<p><strong>Papago Translation API</strong>의 경우 <a href="https://www.ncloud.com/">네이버 클라우드 플랫폼</a>에 가입 후 이용할 수 있는데, 무료 API도 다양하게 지원하고있다.</p>
<p>가입이 완료 되었다면 <a href="https://www.ncloud.com/product/aiService/papagoTranslation">Papago Translation 이용 신청하기</a>를 통해 API 이용 신청을 완료한다.</p>
<p>이후 <strong>Console</strong> 탭으로 가보면 
<img src="https://velog.velcdn.com/images/td_dreamtree/post/d0f45b94-a8e7-4fb2-9975-5731d43ba87a/image.png" alt="">
이처럼 <strong>Papago Translation</strong>이 추가되어 있을 것이다.</p>
<p>이제 <code>Application 등록</code>을 눌러 API 권한을 부여받아야 하는데,
<img src="https://velog.velcdn.com/images/td_dreamtree/post/95b260ff-e521-4f07-920b-a304c45163cf/image.png" alt="">
본인이 원하는 Application 이름을 입력하고 어떤 API를 사용하고 싶은지 선택하면 된다.</p>
<p>이후 <code>등록</code>버튼을 누르면 API 권한이 부여된 Application을 확인할 수 있다.
<img src="https://velog.velcdn.com/images/td_dreamtree/post/9b0cb588-281b-4066-aac8-a189456f60cf/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/td_dreamtree/post/9f5ad780-63cb-46ef-bef6-6277f295088f/image.png" alt="">
<code>Client ID</code>와 <code>Client Secret</code>이 정상적으로 발급되었다면 API를 이용할 수 있다.</p>
<h2 id="api-활용하기">API 활용하기</h2>
<p>이제 직접 <a href="https://api.ncloud-docs.com/docs/ai-naver-papagonmt-translation">Papago Translation API 예제</a>를 참고하여 텍스트 번역을 해보자.</p>
<p>다음은 파이썬으로 작성된 Papago Text Translation 예제이다.</p>
<pre><code class="language-python"># Papago Text Translation API

client_id = &quot;본인의 client ID&quot;
client_secret = &quot;본인의 client secret&quot;

def translate(source_text: str, source_language=&quot;ko&quot;, target_language=&quot;en&quot;) -&gt; str:
    &quot;&quot;&quot;
    Translate text by papago api.

    Args:
        source_text (str): original text to translate.
        source_language (str, optional): source language to translate. Defaults to &quot;ko&quot;.
        target_language (str, optional): target language to translate. Defaults to &quot;en&quot;.

    Returns:
        str: translated text.
    &quot;&quot;&quot;
    try:
        url = &quot;https://naveropenapi.apigw.ntruss.com/nmt/v1/translation&quot;

        encText = urllib.parse.quote(source_text)

        data = &quot;source=%s&amp;target=%s&amp;text=%s&quot; % (source_language, target_language, encText)
        request = urllib.request.Request(url)
        request.add_header(&quot;X-NCP-APIGW-API-KEY-ID&quot;, client_id)
        request.add_header(&quot;X-NCP-APIGW-API-KEY&quot;, client_secret)
        response = urllib.request.urlopen(request, data=data.encode(&quot;utf-8&quot;))
        rescode = response.getcode()

        if rescode == 200:
            response_body = response.read()
            result_json = json.loads(response_body.decode(&quot;utf-8&quot;))
            translated_text = result_json[&#39;message&#39;][&#39;result&#39;][&#39;translatedText&#39;]
            return translated_text
        elif rescode == 401:
            self.__error_message(&quot;Unauthorized&quot;, &quot;Check your API key or client ID.&quot;)
            return
        elif rescode == 403:
            self.__error_message(&quot;Forbidden&quot;, &quot;You don&#39;t have permission to access the server.&quot;)
            return
    except HTTPError as e:
        pass
    except Exception as e:
        self.__error_message(&quot;Error&quot;, str(e))
        return</code></pre>
<pre><code class="language-python">print(translate(&quot;안녕하세요&quot;))

&gt;&gt;&gt; &quot;Hello&quot;</code></pre>
<p>이처럼 <code>url</code>, <code>client ID</code>, <code>client secret</code>, <code>lang_code</code>가 모두 올바르게 입력됐다면 정상적으로 번역 결과가 출력된다.</p>
<h2 id="simple_translator">Simple_Translator</h2>
<p>다음은 <strong>Papago Text Translation</strong>과 <strong>Papago Language Detection</strong>을 종합해서 제작한 간단한 번역기 앱이다.</p>
<blockquote>
<p><a href="https://github.com/junopark00/Naver_API/tree/master/Simple_Translator">github.com/junopark00/Naver_API/Simple_Translator</a></p>
</blockquote>
<p>앱을 사용하기 위해서는 <code>model.py</code>에 <code>client ID</code>와 <code>client secret</code>을 입력해주어야 한다.
<img src="https://velog.velcdn.com/images/td_dreamtree/post/84dcc535-c30a-4c6c-9d27-9de304a656c4/image.png" alt=""></p>
<p>입력 후 <code>controller.py</code>를 실행하면 정상적으로 앱이 작동한다.</p>
<p><img src="https://velog.velcdn.com/images/td_dreamtree/post/cbe3fcdb-b853-4abb-805a-b6a106273c71/image.png" alt=""></p>
<p>이 앱은 <strong>Language Detection</strong>(언어 감지)를 통해 사용자가 입력한 언어를 자동으로 감지해 한국어로 번역한다.
또한 <code>QTimer</code>를 활용해 <code>Translate</code> 버튼을 누르지 않아도 입력 후 일정 시간이 지나면 자동으로 번역이 실행되게 했다.</p>
<p>자세한 내용은 GitHub 링크의 README를 참고.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[rez 패키지 빌드하기]]></title>
            <link>https://velog.io/@td_dreamtree/rez-%ED%8C%A8%ED%82%A4%EC%A7%80-%EB%B9%8C%EB%93%9C%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@td_dreamtree/rez-%ED%8C%A8%ED%82%A4%EC%A7%80-%EB%B9%8C%EB%93%9C%ED%95%98%EA%B8%B0</guid>
            <pubDate>Fri, 31 May 2024 05:20:26 GMT</pubDate>
            <description><![CDATA[<p>rez를 사용해 환경을 구성할 때, 특정 패키지 안에 모듈이나 소프트웨어를 추가하고 환경 변수를 관리하는 방법에 대해 알아보자.</p>
<h2 id="목차">목차</h2>
<ul>
<li><a href="#rez-%ED%8C%A8%ED%82%A4%EC%A7%80%EB%9E%80">rez 패키지란?</a></li>
<li><a href="#rez-%ED%8C%A8%ED%82%A4%EC%A7%80%EC%9D%98-%EC%86%8D%EC%84%B1">rez 패키지의 속성</a></li>
<li><a href="#rez-%ED%8C%A8%ED%82%A4%EC%A7%80-%EB%B9%8C%EB%93%9C%ED%95%98%EA%B8%B0">rez 패키지 빌드하기</a></li>
<li><a href="#rez-%ED%8C%A8%ED%82%A4%EC%A7%80-%ED%85%8C%EC%8A%A4%ED%8A%B8">rez 패키지 테스트</a></li>
</ul>
<h2 id="rez-패키지란">rez 패키지란?</h2>
<p><code>rez-build</code> 명령어의 패키지 빌드는 일반적으로 <code>package.py</code>와 이에 의해 호출되는 파일에 의해 이루어진다.
<code>rez-env</code> 명령어로 환경이 구성될 때 호출되는 패키지에 의해 <strong>각종 환경 변수와 모듈, 소프트웨어 등을 해당 환경에 포함</strong>하게 한다.</p>
<p>다음은 <code>package.py</code>의 예시이다.</p>
<pre><code class="language-python">name = &quot;my_pyside2_package&quot;
version = &quot;1.0.0&quot;
description = &quot;A sample package that includes PySide2.&quot;
authors = [&quot;Your Name&quot;]
requires = [&quot;python-3.7+&quot;, &quot;pyside2&quot;]

build_command = &quot;python {root}/build.py&quot;

def commands():
    env.PYTHONPATH.append(&quot;{root}/python&quot;)
    # 추가적인 환경 변수 설정이 필요할 경우 여기에 추가할 수 있습니다.</code></pre>
<h2 id="rez-패키지의-속성">rez 패키지의 속성</h2>
<p>다음은 위의 예시에서 사용된 <code>package.py</code>에서 <strong>사용자가 직접 정의할 수 있는 속성</strong>들이다.
패키지 속성에 대한 자세한 내용은 <a href="https://rez.readthedocs.io/en/stable/package_definition.html">여기</a>에서 확인할 수 있다.</p>
<ul>
<li><h4 id="name-str">name: str</h4>
<ul>
<li><strong>패키지의 이름</strong></li>
<li>영문과 숫자, 밑줄을 인식하며 대소문자를 구분함<pre><code class="language-python">name = &quot;my_pyside2_package&quot;</code></pre>
</li>
</ul>
</li>
<li><h4 id="version-str">version: str</h4>
<ul>
<li><strong>패키지의 버전</strong></li>
<li>자세한 정보는 <a href="https://rez.readthedocs.io/en/stable/basic_concepts.html#versions-concept">여기</a><pre><code class="language-python">version = &quot;1.0.0&quot;</code></pre>
</li>
</ul>
</li>
<li><h4 id="description-str">description: str</h4>
<ul>
<li><strong>패키지에 대한 설명</strong></li>
<li>패키지의 특정 버전에 대한 세부 정보를 언급해서는 안 됨<pre><code class="language-python">description = &quot;A sample package that includes PySide2.&quot;</code></pre>
</li>
</ul>
</li>
<li><h4 id="author-liststr">author: list[str]</h4>
<ul>
<li>패키지 작성자, 기여자 등의 이름<pre><code class="language-python">authors = [&quot;Your Name&quot;]</code></pre>
</li>
</ul>
</li>
<li><h4 id="requires-liststr">requires: list[str]</h4>
<ul>
<li>이 패키지가 의존하는 다른 패키지의 목록</li>
<li><strong>필요한 모든 패키지가 포함</strong>되어야 하며, <strong>종속성을 가진 패키지도 포함</strong></li>
<li><code>+</code>, <code>&lt;</code>등을 이용해 <strong>버전을 다양하게 설정</strong>할 수 있다<pre><code class="language-python">requires = [&quot;python-3.7+&quot;, &quot;pyside2&quot;]</code></pre>
</li>
</ul>
</li>
<li><h4 id="build_command-str">build_command: str</h4>
<ul>
<li>패키지 빌드 시 실행되는 명령어</li>
<li><strong>소스 파일을 컴파일</strong> 하거나 필요한 <strong>파일을 적절한 위치로 복사</strong>하는 등의 작업을 한다</li>
<li><code>{root}</code>는 <code>package.py</code>가 존재하는 디렉토리의 경로이다<pre><code class="language-python">build_command = &quot;python {root}/build.py&quot;</code></pre>
</li>
</ul>
</li>
<li><h4 id="commands---none">commands() -&gt; None</h4>
<ul>
<li>이 <strong>패키지를 사용할 수 있도록 환경을 구성</strong>하는 코드 블록</li>
<li>패키지가 <code>rez</code>환경으로 전환될 때 실행됨</li>
<li>각종 <strong>환경변수를 설정, 해제 혹은 추가하거나 별칭을 설정</strong>할 수 있음</li>
<li>스크립트를 불러올 수 있음<pre><code class="language-python">def commands():
env.PYTHONPATH.append(&quot;{root}/python&quot;)
env.PATH.prepend(&quot;{root}/bin&quot;)
# 추가적인 환경 변수 설정이 필요할 경우 여기에 추가할 수 있습니다.</code></pre>
</li>
</ul>
</li>
</ul>
<h2 id="rez-패키지-빌드하기">rez 패키지 빌드하기</h2>
<p><code>package.py</code>와 그에 의해 호출되는 <code>build.py</code>를 통해 <code>ffmpeg</code> 프레임워크를 포함한 패키지를 구성해보자.</p>
<h4 id="packagepy">package.py</h4>
<p><code>&quot;ffmpeg&quot;</code> 패키지에서 파이썬 3.7.x 버전을 사용하도록 <code>&quot;python-3.7&quot;</code>을 <code>requires</code>에 추가했고,
<code>build.py</code>에서 패키지 경로에 복사 될 <code>ffmpeg</code>를 사용할 수 있도록 <code>PATH</code> 환경변수를 추가했다.</p>
<p>또한 python3로 <code>build.py</code>를 실행하기 위해 <code>build_command</code>에 해당 명령어를 작성했다.</p>
<pre><code class="language-python"># package.py

name = &quot;ffmpeg&quot;
version = &quot;1.0.0&quot;
description = &quot;env for ffmpeg framework and python3&quot;
authors = [&quot;username&quot;]
requires = [&quot;python-3.7&quot;]

build_command = &quot;python3 {root}/build.py&quot;

def commands():
    env.PATH.append(&quot;/home/username/packages/ffmpeg/1.0.0&quot;)</code></pre>
<h4 id="buildpy">build.py</h4>
<p><code>build.py</code>에서는 <code>ffmpeg</code>를 특정 경로에 다운로드 및 압축을 해제하여 <strong>패키지로 복사하는 작업</strong>을 하도록 했다.</p>
<pre><code class="language-python"># build.py

import os

def get_ffmpeg()
    tar_path = &quot;/home/username/Downloads/ffmpeg-4.2.4.tar.xz&quot;
    extracted_tar = &quot;/home/username/Downloads/ffmpeg-4.2.4&quot;
    save_path = &quot;/home/username/Downloads&quot;
    ffmpeg_path = &quot;/home/username/packages/ffmpeg/1.0.0/ffmpeg&quot;

    if not os.path.exists(save_path):
        os.makedirs(save_path)

    if not os.path.exists(ffmpeg_path):

        if not os.path.exists(tar_path):
            # ffmpeg-4.2.4를 특정 경로에 다운로드
            os.system(f&quot;wget https://ffmpeg.org/releases/ffmpeg-4.2.4.tar.xz -P /home/username/Downloads&quot;)

        if not os.path.exists(extracted_tar):
            # 파일 압축 해제
            os.system(f&quot;cd /home/username/Downloads &amp;&amp; tar -xf {tar_path}&quot;)

        # ffmpeg 파일을 패키지로 복사
        os.system(f&quot;cd {extracted_tar} &amp;&amp; cp ./ffmpeg /home/username/packages/ffmpeg/1.0.0&quot;)
    else:
        # ffmpeg가 이미 존재하는 경우 pass
        print(&quot;ffmpeg already exists&quot;)
        pass

get_ffmpeg()</code></pre>
<h4 id="rez-build---install">rez-build --install</h4>
<p>작성이 완료된 <code>packge.py</code>와 <code>build.py</code>가 존재하는 디렉토리에서 <code>rez-build --install</code> 명령어를 실행하여 <strong>패키지를 로컬에 설치</strong>한다.
<code>-p PATH</code> 또는 <code>--prefix PATH</code> 옵션으로 패키지가 설치될 경로를 지정할 수도 있다.</p>
<pre><code class="language-sh">]$ rez-build --install
================================================================================
Building ffmpeg-1.0.0...
================================================================================
Resolving build environment: python
resolved by username@workstation.local, on Fri May 31 14:07:38 2024, using Rez v3.1.1

requested packages:
python            
~platform==linux  (implicit)
~arch==x86_64     (implicit)
~os==rocky-9.4    (implicit)

resolved packages:
arch-x86_64     /RAPA/packages/arch/x86_64
os-rocky-9.4    /RAPA/packages/os/rocky-9.4
platform-linux  /RAPA/packages/platform/linux
python-3.9.18   /RAPA/packages/python/3.9.18/platform-linux/arch-x86_64/os-rocky-9.4

Invoking custom build system...
Running build command: python /home/username/builder/ffmpeg/build.py install 
python /home/username/builder/ffmpeg/build.py

================================================================================
Build Summary
================================================================================

All 1 build(s) were successful.
</code></pre>
<h2 id="rez-패키지-테스트">rez 패키지 테스트</h2>
<p><code>rez-build</code>로 설치된 패키지를 <code>rez</code>환경으로 전환하여 <code>ffmpeg</code>를 정상적으로 불러오는지 확인해보자.</p>
<pre><code class="language-sh">]$ rez-env ffmpeg

You are now in a rez-configured environment.

resolved by username@workstation.local, on Fri May 31 14:12:42 2024, using Rez v3.1.1

requested packages:
ffmpeg            
~platform==linux  (implicit)
~arch==x86_64     (implicit)
~os==rocky-9.4    (implicit)

resolved packages:
arch-x86_64     /home/username/packages/arch/x86_64                                            
ffmpeg-1.0.0    /home/username/packages/ffmpeg/1.0.0                                           
os-rocky-9.4    /home/username/packages/os/rocky-9.4                                           
platform-linux  /home/username/packages/platform/linux                                         
python-3.9.18   /home/username/packages/python/3.9.18/platform-linux/arch-x86_64/os-rocky-9.4  
</code></pre>
<pre><code class="language-sh">&gt; ]$ which ffmpeg
/home/username/packages/ffmpeg/1.0.0/ffmpeg</code></pre>
<p>패키지 내부의 <code>ffmpeg</code>를 정상적으로 인식한 것을 알 수 있다.
이처럼 사용자가 <strong>자유롭게 패키지를 구성 및 수정</strong>할 수 있는 것이 <code>rez</code>의 장점 중 하나이다.</p>
<blockquote>
</blockquote>
<p>이미지 출처: <a href="https://rez.readthedocs.io/en/stable/">rez 3.1.1 documentation</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[rez 가상 환경 시작하기]]></title>
            <link>https://velog.io/@td_dreamtree/Rez-%EA%B0%80%EC%83%81%ED%99%98%EA%B2%BD-%EC%84%A4%EC%A0%95%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@td_dreamtree/Rez-%EA%B0%80%EC%83%81%ED%99%98%EA%B2%BD-%EC%84%A4%EC%A0%95%ED%95%98%EA%B8%B0</guid>
            <pubDate>Tue, 28 May 2024 06:02:18 GMT</pubDate>
            <description><![CDATA[<p>개발을 하다보면 특정 라이브러리나 모듈을 설치해서 사용하던 중 충돌이 발생하는 경우가 있다.
이러한 충돌을 막기 위해 주로 가상 환경을 이용한다.
가상 환경의 일종인 <code>rez</code>란 무엇인지, 어떻게 활용되는지 알아보자.</p>
<h2 id="목차">목차</h2>
<ul>
<li><a href="#rez%EB%9E%80">rez란?</a></li>
<li><a href="#rez%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%98%EB%8A%94-%EC%9D%B4%EC%9C%A0">rez를 사용하는 이유</a></li>
<li><a href="#rez-%EC%84%A4%EC%B9%98%ED%95%98%EA%B8%B0">rez 설치하기</a></li>
<li><a href="#rez-%EC%8B%9C%EC%9E%91%ED%95%98%EA%B8%B0">rez 시작하기</a></li>
</ul>
<h2 id="rez란">rez란?</h2>
<p>소스 코드 개발에 사용되는 패키지 관리자의 일종. 만들어진 패키지를 기반으로 독립적인 개발 환경을 구축하기 위해 사용한다.</p>
<h2 id="rez를-사용하는-이유">rez를 사용하는 이유</h2>
<p>기존의 패키지 관리자의 경우 가상 환경을 먼저 만들고 해당 <strong>가상 환경 내에 패키지를 설치</strong>한다. 
ex) Anaconda</p>
<p>이러한 방식은 간편하지만 가상 환경을 만들 때 수많은 라이브러리와 모듈이 함께 설치되기 때문에
<strong>가상 환경 자체가 무거워지고</strong> 패키지 간에 <strong>충돌이 발생하는 경우</strong>가 많아진다.</p>
<p>그러나 <code>rez</code>의 경우 <strong>중앙 저장소</strong>(<code>REZ_PACKAGES_PATH</code>)<strong>에 모든 패키지를 설치</strong>하고, 가상 환경을 실행할 때 <strong>필요한 패키지만 선택하여 호출</strong>하는 방식으로 작동한다.</p>
<p>이러한 방식은 <strong>가상 환경의 생성 속도를 빠르게</strong> 하고, <strong>독립적으로 실행</strong>되게 한다.</p>
<p>다음은 기존 패키지 관리자와 <code>rez</code>의 작동 방식을 나타낸 그림이다.</p>
<p><em>기존 패키지 관리자:</em>
<img src="https://velog.velcdn.com/images/td_dreamtree/post/a6fca79b-f4dd-4633-83d9-09894a6b5a9a/image.png" alt=""></p>
<p><em>rez:</em>
<img src="https://velog.velcdn.com/images/td_dreamtree/post/b937c6b1-532c-4b79-a376-b85b77e873f4/image.png" alt=""></p>
<h2 id="rez-설치하기">rez 설치하기</h2>
<h4 id="요구사항">요구사항</h4>
<ul>
<li>Python 3.7이상 (~ Python 3.11)</li>
</ul>
<h4 id="git-clone">Git Clone</h4>
<p><code>rez</code>의 <a href="https://github.com/AcademySoftwareFoundation/rez">Git Repository</a>를 Clone하거나 최신 릴리즈를 다운로드한다.</p>
<pre><code class="language-sh">]$ git clone https://github.com/AcademySoftwareFoundation/rez</code></pre>
<h4 id="설치파일-실행">설치파일 실행</h4>
<p>다운로드한 디렉토리로 이동하여 <code>install.py</code>를 실행한다.</p>
<pre><code class="language-sh">]$ python ./install.py</code></pre>
<h2 id="rez-시작하기">rez 시작하기</h2>
<h4 id="필수-패키지-설치">필수 패키지 설치</h4>
<p><code>rez</code>를 설치한 후에는 필수 패키지를 설치해야 한다. <code>rez-bind</code>를 통해 <strong>이미 설치된 소프트웨어를 참조</strong>하는 <code>rez</code> 패키지를 생성한다.
일반적으로는 <code>--quickstart</code> 옵션으로 <strong>표준 패키지를 바인딩</strong>한다.</p>
<ul>
<li>만약 <code>rez</code> 명령어를 인식하지 못할 경우 <code>rez</code> 실행 파일의 경로가 <code>PATH</code> 환경 변수에 추가되었는지 확인한다.<pre><code class="language-sh">export PATH=&quot;/path/to/rez&quot;:$PATH</code></pre>
</li>
</ul>
<pre><code class="language-sh">]$ rez-bind --quickstart

Binding platform into /home/username/packages...
Binding arch into /home/username/packages...
Binding os into /home/username/packages...
Binding python into /home/username/packages...
Binding rez into /home/username/packages...
Binding rezgui into /home/username/packages...
Binding setuptools into /home/username/packages...
Binding pip into /home/username/packages...

Successfully converted the following software found on the current system into Rez packages:

PACKAGE     URI
-------     ---
arch        /home/username/packages/arch/x86_64/package.py
os          /home/username/packages/os/osx-10.11.5/package.py
pip         /home/username/packages/pip/8.0.2/package.py
platform    /home/username/packages/platform/osx/package.py
python      /home/username/packages/python/2.7.11/package.py
rez         /home/username/packages/rez/2.0.rc1.44/package.py
rezgui      /home/username/packages/rezgui/2.0.rc1.44/package.py
setuptools  /home/username/packages/setuptools/19.4/package.py</code></pre>
<p>정상적으로 바인딩이 종료된 경우 <code>rez-env</code> 명령어로 새로운 가상환경을 실행할 수 있다.</p>
<pre><code class="language-sh">]$ rez-env python

You are now in a rez-configured environment.

resolved by west@localhost.localdomain, on Tue May 28 14:16:03 2024, using Rez v3.1.1

requested packages:
python                
~platform==linux      (implicit)
~arch==x86_64         (implicit)
~os==CentOS-7.9.2009  (implicit)

resolved packages:
arch-x86_64         /home/username/packages/arch/x86_64                                                 
os-CentOS-7.9.2009  /home/username/packages/os/CentOS-7.9.2009                                          
platform-linux      /home/username/packages/platform/linux                                              
python-3.7.7      </code></pre>
<h4 id="패키지-빌드하기">패키지 빌드하기</h4>
<p><code>rez-build</code> 명령어를 통해 사용자가 임의의 <code>package.py</code>로 패키지를 빌드할 수 있다.
<code>package.py</code>를 통해 <strong>특정 소프트웨어 혹은 모듈을 패키지에 포함</strong>시키거나, <strong>환경변수를 추가</strong>하는 등의 작업을 할 수 있다.</p>
<p><code>-i</code> 또는 <code>--install</code> 옵션을 통해 빌드된 패키지를 로컬에 설치한다.
<code>-p</code> 또는 <code>--prefix</code> 옵션을 통해 패키지를 특정 경로에 설치한다.</p>
<p><code>rez-build</code> 명령어는 아래의 조건을 만족했을 때 유효하다.</p>
<ul>
<li><code>rez-build</code> 명령어를 실행하는 디렉토리에 <code>package.py</code>가 존재하는 경우</li>
<li><code>cmake</code> 도구가 사용 가능한 경우 (일반적으로 활성화 됨)</li>
</ul>
<pre><code class="language-sh">]$ rez-build --install

--------------------------------------------------------------------------------
Building hello_world-1.0.0...
--------------------------------------------------------------------------------
Resolving build environment: python
resolved by username@workstation.local, on Tue May 28 14:16:03 2024, using Rez v3.1.1

requested packages:
python
~platform==osx    (implicit)
~arch==x86_64     (implicit)
~os==osx-10.11.5  (implicit)

resolved packages:
arch-x86_64     /home/username/packages/arch/x86_64                                            (local)
os-osx-10.11.5  /home/username/packages/os/osx-10.11.5                                         (local)
platform-osx    /home/username/packages/platform/osx                                           (local)
python-3.7.7   /home/username/packages/python/3.7.7/platform-osx/arch-x86_64/os-osx-10.11.5  (local)

Invoking cmake build system...
Executing: /usr/local/bin/cmake -d /home/username/workspace/rez/example_packages/hello_world -Wno-dev -DCMAKE_ECLIPSE_GENERATE_SOURCE_PROJECT=TRUE -D_ECLIPSE_VERSION=4.3 --no-warn-unused-cli -DCMAKE_INSTALL_PREFIX=/home/username/packages/hello_world/1.0.0 -DCMAKE_MODULE_PATH=${CMAKE_MODULE_PATH} -DCMAKE_BUILD_TYPE=Release -DREZ_BUILD_TYPE=local -DREZ_BUILD_INSTALL=1 -G Unix Makefiles
Not searching for unused variables given on the command line.
-- Could NOT find PkgConfig (missing:  PKG_CONFIG_EXECUTABLE)
-- Configuring done
-- Generating done
-- Build files have been written to: /home/username/workspace/rez/example_packages/hello_world/build

Executing: make -j4
[100%] Built target py

Executing: make -j4 install
[100%] Built target py
Install the project...
-- Install configuration: &quot;Release&quot;
-- Installing: /home/username/packages/hello_world/1.0.0/./python/hello_world.py
-- Installing: /home/username/packages/hello_world/1.0.0/./python/hello_world.pyc
-- Installing: /home/username/packages/hello_world/1.0.0/./bin/hello

All 1 build(s) were successful.</code></pre>
<p><code>package.py</code>를 이용한 커스텀 패키지에 대해서는 다음 글에서 다루도록 하겠다.</p>
<h4 id="패키지-테스트">패키지 테스트</h4>
<p>성공적으로 빌드가 진행된 패키지는 <code>rez-env</code>를 통해 호출할 수 있다.</p>
<pre><code class="language-sh">]$ rez-env hello_world

You are now in a rez-configured environment.

resolved by username@workstation.local, on Tue May 28 14:16:03 2024, using Rez v3.1.1

requested packages:
hello_world
~platform==osx    (implicit)
~arch==x86_64     (implicit)
~os==osx-10.11.5  (implicit)

resolved packages:
arch-x86_64        /home/username/packages/arch/x86_64                                            (local)
hello_world-1.0.0  /home/username/packages/hello_world/1.0.0                                      (local)
os-osx-10.11.5     /home/username/packages/os/osx-10.11.5                                         (local)
platform-osx       /home/username/packages/platform/osx                                           (local)
python-3.7.7      /home/username/packages/python/3.7.7/platform-osx/arch-x86_64/os-osx-10.11.5  (local)

&gt; ]$ █</code></pre>
<p><code>rez-env</code> 명령이 성공적으로 실행되면 프롬프트 앞에 <code>&gt;</code> 표시가 나타난다.
해당 표시는 현재 쉘이 <code>rez</code> 환경 내에서 실행되고 있음을 알려주는 <strong>시각적 신호</strong>이다.
<code>rez</code>는 현재 환경을 업데이트하는 것이 아닌 <strong>하위 쉘을 구성</strong>하고 그 안에 <strong>사용자를 배치</strong>한다.</p>
<p><code>rez</code>쉘 내에 있고 현재 사용 가능한 패키지의 목록을 다시 보고 싶다면 <code>rez-context</code> 명령어를 사용한다.
<code>rez-env</code> 명령어를 사용했을 때와 동일한 정보가 출력된다.</p>
<pre><code class="language-sh">]$ rez-context
resolved by username@workstation.local, on Tue May 28 14:16:03 2024, using Rez v3.1.1

requested packages:
hello_world
~platform==osx    (implicit)
~arch==x86_64     (implicit)
~os==osx-10.11.5  (implicit)

resolved packages:
arch-x86_64        /home/username/packages/arch/x86_64                                            (local)
hello_world-1.0.0  /home/username/packages/hello_world/1.0.0                                      (local)
os-osx-10.11.5     /home/username/packages/os/osx-10.11.5                                         (local)
platform-osx       /home/username/packages/platform/osx                                           (local)
python-3.7.7      /home/username/packages/python/3.7.7/platform-osx/arch-x86_64/os-osx-10.11.5  (local)

&gt; ]$ █</code></pre>
<p>구성된 환경의 종료는 <code>exit</code> 명령어를 입력하거나 <code>Ctrl+D</code> 단축키로 가능하다.</p>
<p>구성된 환경을 생성하고 일회성으로 명령을 실행하고 싶다면 <code>rez-env</code>로 가상 환경을 생성할 때
<code>-- (명령어)</code>를 추가하면 된다.</p>
<pre><code class="language-sh">]$ rez-env hello_world -- hello
Hello world!
]$ █</code></pre>
<blockquote>
</blockquote>
<p>이미지 출처: <a href="https://rez.readthedocs.io/en/stable/">rez 3.1.1 documentation</a></p>
]]></description>
        </item>
    </channel>
</rss>