Files
2025-11-29 18:18:51 +08:00

553 lines
13 KiB
Python

"""
Geometry Operations
도형 생성, 수정, 삭제 등 기하학적 작업을 처리하는 명령 핸들러
"""
import bpy
import bmesh
from typing import Dict, List, Tuple, Optional, Any
from ..utils.logger import get_logger
logger = get_logger(__name__)
# ============================================================================
# Primitive Creation (기본 도형 생성)
# ============================================================================
def create_cube(
location: Tuple[float, float, float] = (0, 0, 0),
size: float = 2.0,
name: Optional[str] = None
) -> Dict[str, Any]:
"""
큐브 생성
Args:
location: 위치 (x, y, z)
size: 크기
name: 오브젝트 이름 (None이면 자동 생성)
Returns:
생성된 오브젝트 정보
"""
logger.info(f"Creating cube at {location} with size {size}")
bpy.ops.mesh.primitive_cube_add(size=size, location=location)
obj = bpy.context.active_object
if name:
obj.name = name
return {
'name': obj.name,
'type': obj.type,
'location': list(obj.location),
'vertices': len(obj.data.vertices),
'faces': len(obj.data.polygons)
}
def create_sphere(
location: Tuple[float, float, float] = (0, 0, 0),
radius: float = 1.0,
segments: int = 32,
ring_count: int = 16,
name: Optional[str] = None
) -> Dict[str, Any]:
"""
구(Sphere) 생성
Args:
location: 위치 (x, y, z)
radius: 반지름
segments: 세그먼트 수 (수평)
ring_count: 링 수 (수직)
name: 오브젝트 이름
Returns:
생성된 오브젝트 정보
"""
logger.info(f"Creating sphere at {location} with radius {radius}")
bpy.ops.mesh.primitive_uv_sphere_add(
radius=radius,
segments=segments,
ring_count=ring_count,
location=location
)
obj = bpy.context.active_object
if name:
obj.name = name
return {
'name': obj.name,
'type': obj.type,
'location': list(obj.location),
'vertices': len(obj.data.vertices),
'faces': len(obj.data.polygons)
}
def create_cylinder(
location: Tuple[float, float, float] = (0, 0, 0),
radius: float = 1.0,
depth: float = 2.0,
vertices: int = 32,
name: Optional[str] = None
) -> Dict[str, Any]:
"""
실린더 생성
Args:
location: 위치 (x, y, z)
radius: 반지름
depth: 높이
vertices: 버텍스 수
name: 오브젝트 이름
Returns:
생성된 오브젝트 정보
"""
logger.info(f"Creating cylinder at {location}")
bpy.ops.mesh.primitive_cylinder_add(
radius=radius,
depth=depth,
vertices=vertices,
location=location
)
obj = bpy.context.active_object
if name:
obj.name = name
return {
'name': obj.name,
'type': obj.type,
'location': list(obj.location),
'vertices': len(obj.data.vertices),
'faces': len(obj.data.polygons)
}
def create_plane(
location: Tuple[float, float, float] = (0, 0, 0),
size: float = 2.0,
name: Optional[str] = None
) -> Dict[str, Any]:
"""
평면(Plane) 생성
Args:
location: 위치 (x, y, z)
size: 크기
name: 오브젝트 이름
Returns:
생성된 오브젝트 정보
"""
logger.info(f"Creating plane at {location}")
bpy.ops.mesh.primitive_plane_add(size=size, location=location)
obj = bpy.context.active_object
if name:
obj.name = name
return {
'name': obj.name,
'type': obj.type,
'location': list(obj.location),
'vertices': len(obj.data.vertices),
'faces': len(obj.data.polygons)
}
def create_cone(
location: Tuple[float, float, float] = (0, 0, 0),
radius1: float = 1.0,
depth: float = 2.0,
vertices: int = 32,
name: Optional[str] = None
) -> Dict[str, Any]:
"""
원뿔(Cone) 생성
Args:
location: 위치 (x, y, z)
radius1: 아래 반지름
depth: 높이
vertices: 버텍스 수
name: 오브젝트 이름
Returns:
생성된 오브젝트 정보
"""
logger.info(f"Creating cone at {location}")
bpy.ops.mesh.primitive_cone_add(
radius1=radius1,
depth=depth,
vertices=vertices,
location=location
)
obj = bpy.context.active_object
if name:
obj.name = name
return {
'name': obj.name,
'type': obj.type,
'location': list(obj.location),
'vertices': len(obj.data.vertices),
'faces': len(obj.data.polygons)
}
def create_torus(
location: Tuple[float, float, float] = (0, 0, 0),
major_radius: float = 1.0,
minor_radius: float = 0.25,
major_segments: int = 48,
minor_segments: int = 12,
name: Optional[str] = None
) -> Dict[str, Any]:
"""
토러스(Torus) 생성
Args:
location: 위치 (x, y, z)
major_radius: 주 반지름
minor_radius: 부 반지름
major_segments: 주 세그먼트 수
minor_segments: 부 세그먼트 수
name: 오브젝트 이름
Returns:
생성된 오브젝트 정보
"""
logger.info(f"Creating torus at {location}")
bpy.ops.mesh.primitive_torus_add(
major_radius=major_radius,
minor_radius=minor_radius,
major_segments=major_segments,
minor_segments=minor_segments,
location=location
)
obj = bpy.context.active_object
if name:
obj.name = name
return {
'name': obj.name,
'type': obj.type,
'location': list(obj.location),
'vertices': len(obj.data.vertices),
'faces': len(obj.data.polygons)
}
# ============================================================================
# Object Operations (오브젝트 작업)
# ============================================================================
def delete_object(name: str) -> Dict[str, str]:
"""
오브젝트 삭제
Args:
name: 오브젝트 이름
Returns:
삭제 결과
"""
logger.info(f"Deleting object: {name}")
obj = bpy.data.objects.get(name)
if not obj:
raise ValueError(f"Object '{name}' not found")
bpy.data.objects.remove(obj, do_unlink=True)
return {'status': 'success', 'message': f"Object '{name}' deleted"}
def transform_object(
name: str,
location: Optional[Tuple[float, float, float]] = None,
rotation: Optional[Tuple[float, float, float]] = None,
scale: Optional[Tuple[float, float, float]] = None
) -> Dict[str, Any]:
"""
오브젝트 변형 (이동, 회전, 스케일)
Args:
name: 오브젝트 이름
location: 위치 (x, y, z)
rotation: 회전 (x, y, z) in radians
scale: 스케일 (x, y, z)
Returns:
변형된 오브젝트 정보
"""
logger.info(f"Transforming object: {name}")
obj = bpy.data.objects.get(name)
if not obj:
raise ValueError(f"Object '{name}' not found")
if location:
obj.location = location
if rotation:
obj.rotation_euler = rotation
if scale:
obj.scale = scale
return {
'name': obj.name,
'location': list(obj.location),
'rotation': list(obj.rotation_euler),
'scale': list(obj.scale)
}
def duplicate_object(
name: str,
new_name: Optional[str] = None,
location: Optional[Tuple[float, float, float]] = None
) -> Dict[str, Any]:
"""
오브젝트 복제
Args:
name: 원본 오브젝트 이름
new_name: 새 오브젝트 이름 (None이면 자동 생성)
location: 새 위치 (None이면 원본 위치)
Returns:
복제된 오브젝트 정보
"""
logger.info(f"Duplicating object: {name}")
obj = bpy.data.objects.get(name)
if not obj:
raise ValueError(f"Object '{name}' not found")
# 복제
new_obj = obj.copy()
new_obj.data = obj.data.copy()
if new_name:
new_obj.name = new_name
if location:
new_obj.location = location
# 씬에 추가
bpy.context.collection.objects.link(new_obj)
return {
'name': new_obj.name,
'type': new_obj.type,
'location': list(new_obj.location)
}
def list_objects(object_type: Optional[str] = None) -> List[Dict[str, Any]]:
"""
씬의 오브젝트 목록 조회
Args:
object_type: 오브젝트 타입 필터 (None이면 전체)
예: 'MESH', 'ARMATURE', 'CAMERA', 'LIGHT'
Returns:
오브젝트 목록
"""
logger.info(f"Listing objects (type: {object_type or 'ALL'})")
objects = []
for obj in bpy.data.objects:
if object_type and obj.type != object_type:
continue
objects.append({
'name': obj.name,
'type': obj.type,
'location': list(obj.location),
'rotation': list(obj.rotation_euler),
'scale': list(obj.scale)
})
return objects
# ============================================================================
# Vertex Operations (버텍스 작업)
# ============================================================================
def get_vertices(name: str) -> List[Dict[str, Any]]:
"""
오브젝트의 버텍스 정보 조회
Args:
name: 오브젝트 이름
Returns:
버텍스 목록
"""
logger.info(f"Getting vertices for object: {name}")
obj = bpy.data.objects.get(name)
if not obj or obj.type != 'MESH':
raise ValueError(f"Mesh object '{name}' not found")
vertices = []
for i, vert in enumerate(obj.data.vertices):
vertices.append({
'index': i,
'co': list(vert.co),
'normal': list(vert.normal)
})
return vertices
def move_vertex(
object_name: str,
vertex_index: int,
new_position: Tuple[float, float, float]
) -> Dict[str, Any]:
"""
버텍스 이동
Args:
object_name: 오브젝트 이름
vertex_index: 버텍스 인덱스
new_position: 새 위치 (x, y, z)
Returns:
수정된 버텍스 정보
"""
logger.info(f"Moving vertex {vertex_index} in object {object_name}")
obj = bpy.data.objects.get(object_name)
if not obj or obj.type != 'MESH':
raise ValueError(f"Mesh object '{object_name}' not found")
mesh = obj.data
if vertex_index >= len(mesh.vertices):
raise ValueError(f"Vertex index {vertex_index} out of range")
mesh.vertices[vertex_index].co = new_position
mesh.update()
return {
'object': object_name,
'vertex_index': vertex_index,
'position': list(mesh.vertices[vertex_index].co)
}
def subdivide_mesh(
name: str,
cuts: int = 1
) -> Dict[str, Any]:
"""
메쉬 세분화 (Subdivide)
Args:
name: 오브젝트 이름
cuts: 세분화 횟수
Returns:
세분화된 메쉬 정보
"""
logger.info(f"Subdividing mesh: {name} (cuts: {cuts})")
obj = bpy.data.objects.get(name)
if not obj or obj.type != 'MESH':
raise ValueError(f"Mesh object '{name}' not found")
# Edit 모드로 전환
bpy.context.view_layer.objects.active = obj
bpy.ops.object.mode_set(mode='EDIT')
# 모든 에지 선택
bpy.ops.mesh.select_all(action='SELECT')
# 세분화
bpy.ops.mesh.subdivide(number_cuts=cuts)
# Object 모드로 복귀
bpy.ops.object.mode_set(mode='OBJECT')
return {
'name': obj.name,
'vertices': len(obj.data.vertices),
'edges': len(obj.data.edges),
'faces': len(obj.data.polygons)
}
def extrude_face(
object_name: str,
face_index: int,
offset: float = 1.0
) -> Dict[str, Any]:
"""
페이스 돌출 (Extrude)
Args:
object_name: 오브젝트 이름
face_index: 페이스 인덱스
offset: 돌출 거리
Returns:
돌출 결과 정보
"""
logger.info(f"Extruding face {face_index} in object {object_name}")
obj = bpy.data.objects.get(object_name)
if not obj or obj.type != 'MESH':
raise ValueError(f"Mesh object '{object_name}' not found")
# BMesh를 사용한 extrude
mesh = obj.data
bm = bmesh.new()
bm.from_mesh(mesh)
# 페이스 선택
if face_index >= len(bm.faces):
bm.free()
raise ValueError(f"Face index {face_index} out of range")
face = bm.faces[face_index]
# Extrude
ret = bmesh.ops.extrude_face_region(bm, geom=[face])
extruded_verts = [v for v in ret['geom'] if isinstance(v, bmesh.types.BMVert)]
# 오프셋 적용
for v in extruded_verts:
v.co += face.normal * offset
# 메쉬 업데이트
bm.to_mesh(mesh)
bm.free()
mesh.update()
return {
'object': object_name,
'face_index': face_index,
'vertices': len(mesh.vertices),
'faces': len(mesh.polygons)
}