import os import time import glob import json import numpy as np import trimesh import argparse from scipy.spatial.transform import Rotation from PIL import Image, ImageDraw import math import trimesh.transformations as tf os.environ['PYOPENGL_PLATFORM'] = 'egl' import gradio as gr import spaces # 결과 저장 경로 LOG_PATH = './results/demo' os.makedirs(LOG_PATH, exist_ok=True) def create_textual_animation_gif(output_path, model_name, animation_type, duration=3.0, fps=30): """텍스트 기반의 간단한 애니메이션 GIF 생성 - 렌더링 실패 시 대체용""" try: # 간단한 프레임 시퀀스 생성 frames = [] num_frames = int(duration * fps) if num_frames > 60: # 너무 많은 프레임은 효율적이지 않음 num_frames = 60 for i in range(num_frames): t = i / (num_frames - 1) # 0~1 범위 angle = t * 360 # 전체 회전 # 새 이미지 생성 img = Image.new('RGB', (640, 480), color=(240, 240, 240)) draw = ImageDraw.Draw(img) # 정보 텍스트 draw.text((50, 50), f"Model: {os.path.basename(model_name)}", fill=(0, 0, 0)) draw.text((50, 100), f"Animation Type: {animation_type}", fill=(0, 0, 0)) draw.text((50, 150), f"Frame: {i+1}/{num_frames}", fill=(0, 0, 0)) # 애니메이션 유형에 따른 시각적 효과 center_x, center_y = 320, 240 if animation_type == 'rotate': # 회전하는 사각형 radius = 100 x = center_x + radius * math.cos(math.radians(angle)) y = center_y + radius * math.sin(math.radians(angle)) draw.rectangle((x-40, y-40, x+40, y+40), outline=(0, 0, 0), fill=(255, 0, 0)) elif animation_type == 'float': # 위아래로 움직이는 원 offset_y = 50 * math.sin(2 * math.pi * t) draw.ellipse((center_x-50, center_y-50+offset_y, center_x+50, center_y+50+offset_y), outline=(0, 0, 0), fill=(0, 0, 255)) elif animation_type == 'explode' or animation_type == 'assemble': # 바깥쪽/안쪽으로 움직이는 여러 도형 scale = t if animation_type == 'explode' else 1 - t for j in range(8): angle_j = j * 45 dist = 120 * scale x = center_x + dist * math.cos(math.radians(angle_j)) y = center_y + dist * math.sin(math.radians(angle_j)) if j % 3 == 0: draw.rectangle((x-20, y-20, x+20, y+20), outline=(0, 0, 0), fill=(255, 0, 0)) elif j % 3 == 1: draw.ellipse((x-20, y-20, x+20, y+20), outline=(0, 0, 0), fill=(0, 255, 0)) else: draw.polygon([(x, y-20), (x+20, y+20), (x-20, y+20)], outline=(0, 0, 0), fill=(0, 0, 255)) elif animation_type == 'pulse': # 크기가 변하는 원 scale = 0.5 + 0.5 * math.sin(2 * math.pi * t) radius = 100 * scale draw.ellipse((center_x-radius, center_y-radius, center_x+radius, center_y+radius), outline=(0, 0, 0), fill=(0, 255, 0)) elif animation_type == 'swing': # 좌우로 움직이는 삼각형 angle_offset = 30 * math.sin(2 * math.pi * t) points = [ (center_x + 100 * math.cos(math.radians(angle_offset)), center_y - 80), (center_x + 100 * math.cos(math.radians(120 + angle_offset)), center_y + 40), (center_x + 100 * math.cos(math.radians(240 + angle_offset)), center_y + 40) ] draw.polygon(points, outline=(0, 0, 0), fill=(255, 165, 0)) # 프레임 추가 frames.append(img) # GIF로 저장 frames[0].save( output_path, save_all=True, append_images=frames[1:], optimize=False, duration=int(1000 / fps), loop=0 ) print(f"Created textual animation GIF at {output_path}") return output_path except Exception as e: print(f"Error creating textual animation: {str(e)}") return None @spaces.GPU def process_3d_model(input_3d, animation_type, animation_duration, fps): """Process a 3D model and apply animation""" print(f"Processing: {input_3d} with animation type: {animation_type}") try: # 텍스트 기반 애니메이션 GIF 생성 (렌더링 실패를 우려하여 항상 생성) base_filename = os.path.basename(input_3d).rsplit('.', 1)[0] text_gif_path = os.path.join(LOG_PATH, f'text_animated_{base_filename}.gif') animated_gif_path = create_textual_animation_gif( text_gif_path, os.path.basename(input_3d), animation_type, animation_duration, fps ) # 원본 GLB 파일 복사 copy_glb_path = os.path.join(LOG_PATH, f'copy_{base_filename}.glb') import shutil try: shutil.copy(input_3d, copy_glb_path) animated_glb_path = copy_glb_path print(f"Copied original GLB to {copy_glb_path}") except Exception as e: print(f"Error copying GLB: {e}") animated_glb_path = input_3d # 메타데이터 생성 metadata = { "animation_type": animation_type, "duration": animation_duration, "fps": fps, "original_model": os.path.basename(input_3d), "created_at": time.strftime("%Y-%m-%d %H:%M:%S") } json_path = os.path.join(LOG_PATH, f'metadata_{base_filename}.json') with open(json_path, 'w') as f: json.dump(metadata, f, indent=4) return animated_glb_path, animated_gif_path, json_path except Exception as e: error_msg = f"Error processing file: {str(e)}" print(error_msg) return error_msg, None, None # Gradio 인터페이스 설정 with gr.Blocks(title="GLB 애니메이션 생성기") as demo: # 제목 섹션 gr.Markdown("""