Initial commit
This commit is contained in:
388
skills/addon/commands/modifier.py
Normal file
388
skills/addon/commands/modifier.py
Normal file
@@ -0,0 +1,388 @@
|
||||
"""
|
||||
Modifier Operations
|
||||
모디파이어 관리 명령 핸들러
|
||||
"""
|
||||
|
||||
import bpy
|
||||
from typing import Dict, List, Any, Optional
|
||||
from ..utils.logger import get_logger
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
|
||||
def add_modifier(object_name: str, modifier_type: str, name: Optional[str] = None) -> Dict[str, Any]:
|
||||
"""오브젝트에 모디파이어 추가
|
||||
|
||||
Args:
|
||||
object_name: 대상 오브젝트 이름
|
||||
modifier_type: 모디파이어 타입 (SUBSURF, MIRROR, ARRAY, BEVEL, etc.)
|
||||
name: 모디파이어 이름 (optional)
|
||||
|
||||
Supported modifier types:
|
||||
- SUBSURF: Subdivision Surface
|
||||
- MIRROR: Mirror
|
||||
- ARRAY: Array
|
||||
- BEVEL: Bevel
|
||||
- BOOLEAN: Boolean
|
||||
- SOLIDIFY: Solidify
|
||||
- WIREFRAME: Wireframe
|
||||
- SKIN: Skin
|
||||
- ARMATURE: Armature
|
||||
- LATTICE: Lattice
|
||||
- CURVE: Curve
|
||||
- SIMPLE_DEFORM: Simple Deform
|
||||
- CAST: Cast
|
||||
- DISPLACE: Displace
|
||||
- HOOK: Hook
|
||||
- LAPLACIANDEFORM: Laplacian Deform
|
||||
- MESH_DEFORM: Mesh Deform
|
||||
- SHRINKWRAP: Shrinkwrap
|
||||
- WAVE: Wave
|
||||
- OCEAN: Ocean
|
||||
- PARTICLE_SYSTEM: Particle System
|
||||
- CLOTH: Cloth
|
||||
- COLLISION: Collision
|
||||
- DYNAMIC_PAINT: Dynamic Paint
|
||||
- EXPLODE: Explode
|
||||
- FLUID: Fluid
|
||||
- SOFT_BODY: Soft Body
|
||||
"""
|
||||
logger.info(f"Adding modifier '{modifier_type}' to '{object_name}'")
|
||||
|
||||
obj = bpy.data.objects.get(object_name)
|
||||
if not obj:
|
||||
raise ValueError(f"Object '{object_name}' not found")
|
||||
|
||||
mod = obj.modifiers.new(name or modifier_type, modifier_type)
|
||||
|
||||
return {
|
||||
'name': mod.name,
|
||||
'type': mod.type,
|
||||
'show_viewport': mod.show_viewport,
|
||||
'show_render': mod.show_render
|
||||
}
|
||||
|
||||
|
||||
def apply_modifier(object_name: str, modifier_name: str) -> Dict[str, str]:
|
||||
"""모디파이어 적용"""
|
||||
logger.info(f"Applying modifier '{modifier_name}' on '{object_name}'")
|
||||
|
||||
obj = bpy.data.objects.get(object_name)
|
||||
if not obj:
|
||||
raise ValueError(f"Object '{object_name}' not found")
|
||||
|
||||
mod = obj.modifiers.get(modifier_name)
|
||||
if not mod:
|
||||
raise ValueError(f"Modifier '{modifier_name}' not found on '{object_name}'")
|
||||
|
||||
# 모디파이어 적용은 Edit 모드에서는 할 수 없음
|
||||
if bpy.context.mode != 'OBJECT':
|
||||
bpy.ops.object.mode_set(mode='OBJECT')
|
||||
|
||||
# 오브젝트 선택 및 활성화
|
||||
bpy.context.view_layer.objects.active = obj
|
||||
obj.select_set(True)
|
||||
|
||||
# 모디파이어 적용
|
||||
bpy.ops.object.modifier_apply(modifier=modifier_name)
|
||||
|
||||
return {'status': 'success', 'message': f"Applied modifier '{modifier_name}' to '{object_name}'"}
|
||||
|
||||
|
||||
def list_modifiers(object_name: str) -> List[Dict[str, Any]]:
|
||||
"""오브젝트의 모디파이어 목록 조회"""
|
||||
logger.info(f"Listing modifiers for '{object_name}'")
|
||||
|
||||
obj = bpy.data.objects.get(object_name)
|
||||
if not obj:
|
||||
raise ValueError(f"Object '{object_name}' not found")
|
||||
|
||||
modifiers = []
|
||||
for mod in obj.modifiers:
|
||||
mod_info = {
|
||||
'name': mod.name,
|
||||
'type': mod.type,
|
||||
'show_viewport': mod.show_viewport,
|
||||
'show_render': mod.show_render,
|
||||
}
|
||||
|
||||
# 타입별 특화 속성 추가
|
||||
if mod.type == 'SUBSURF':
|
||||
mod_info['levels'] = mod.levels
|
||||
mod_info['render_levels'] = mod.render_levels
|
||||
elif mod.type == 'MIRROR':
|
||||
mod_info['use_axis'] = [mod.use_axis[0], mod.use_axis[1], mod.use_axis[2]]
|
||||
mod_info['use_bisect_axis'] = [mod.use_bisect_axis[0], mod.use_bisect_axis[1], mod.use_bisect_axis[2]]
|
||||
elif mod.type == 'ARRAY':
|
||||
mod_info['count'] = mod.count
|
||||
mod_info['use_relative_offset'] = mod.use_relative_offset
|
||||
mod_info['relative_offset_displace'] = list(mod.relative_offset_displace)
|
||||
elif mod.type == 'BEVEL':
|
||||
mod_info['width'] = mod.width
|
||||
mod_info['segments'] = mod.segments
|
||||
mod_info['limit_method'] = mod.limit_method
|
||||
elif mod.type == 'BOOLEAN':
|
||||
mod_info['operation'] = mod.operation
|
||||
mod_info['object'] = mod.object.name if mod.object else None
|
||||
elif mod.type == 'SOLIDIFY':
|
||||
mod_info['thickness'] = mod.thickness
|
||||
mod_info['offset'] = mod.offset
|
||||
elif mod.type == 'ARMATURE':
|
||||
mod_info['object'] = mod.object.name if mod.object else None
|
||||
mod_info['use_vertex_groups'] = mod.use_vertex_groups
|
||||
elif mod.type == 'LATTICE':
|
||||
mod_info['object'] = mod.object.name if mod.object else None
|
||||
elif mod.type == 'CURVE':
|
||||
mod_info['object'] = mod.object.name if mod.object else None
|
||||
elif mod.type == 'SIMPLE_DEFORM':
|
||||
mod_info['deform_method'] = mod.deform_method
|
||||
mod_info['factor'] = mod.factor
|
||||
elif mod.type == 'CAST':
|
||||
mod_info['cast_type'] = mod.cast_type
|
||||
mod_info['factor'] = mod.factor
|
||||
elif mod.type == 'DISPLACE':
|
||||
mod_info['strength'] = mod.strength
|
||||
mod_info['direction'] = mod.direction
|
||||
elif mod.type == 'WAVE':
|
||||
mod_info['time_offset'] = mod.time_offset
|
||||
mod_info['height'] = mod.height
|
||||
|
||||
modifiers.append(mod_info)
|
||||
|
||||
return modifiers
|
||||
|
||||
|
||||
def remove_modifier(object_name: str, modifier_name: str) -> Dict[str, str]:
|
||||
"""모디파이어 제거"""
|
||||
logger.info(f"Removing modifier '{modifier_name}' from '{object_name}'")
|
||||
|
||||
obj = bpy.data.objects.get(object_name)
|
||||
if not obj:
|
||||
raise ValueError(f"Object '{object_name}' not found")
|
||||
|
||||
mod = obj.modifiers.get(modifier_name)
|
||||
if not mod:
|
||||
raise ValueError(f"Modifier '{modifier_name}' not found on '{object_name}'")
|
||||
|
||||
obj.modifiers.remove(mod)
|
||||
|
||||
return {'status': 'success', 'message': f"Removed modifier '{modifier_name}' from '{object_name}'"}
|
||||
|
||||
|
||||
def toggle_modifier(object_name: str, modifier_name: str,
|
||||
viewport: Optional[bool] = None,
|
||||
render: Optional[bool] = None) -> Dict[str, Any]:
|
||||
"""모디파이어 활성화/비활성화
|
||||
|
||||
Args:
|
||||
object_name: 대상 오브젝트 이름
|
||||
modifier_name: 모디파이어 이름
|
||||
viewport: 뷰포트 표시 on/off (None이면 토글)
|
||||
render: 렌더 표시 on/off (None이면 토글)
|
||||
"""
|
||||
logger.info(f"Toggling modifier '{modifier_name}' on '{object_name}'")
|
||||
|
||||
obj = bpy.data.objects.get(object_name)
|
||||
if not obj:
|
||||
raise ValueError(f"Object '{object_name}' not found")
|
||||
|
||||
mod = obj.modifiers.get(modifier_name)
|
||||
if not mod:
|
||||
raise ValueError(f"Modifier '{modifier_name}' not found on '{object_name}'")
|
||||
|
||||
if viewport is not None:
|
||||
mod.show_viewport = viewport
|
||||
else:
|
||||
mod.show_viewport = not mod.show_viewport
|
||||
|
||||
if render is not None:
|
||||
mod.show_render = render
|
||||
else:
|
||||
mod.show_render = not mod.show_render
|
||||
|
||||
return {
|
||||
'name': mod.name,
|
||||
'show_viewport': mod.show_viewport,
|
||||
'show_render': mod.show_render
|
||||
}
|
||||
|
||||
|
||||
def modify_modifier_properties(object_name: str, modifier_name: str, **properties) -> Dict[str, Any]:
|
||||
"""모디파이어 속성 수정
|
||||
|
||||
Args:
|
||||
object_name: 대상 오브젝트 이름
|
||||
modifier_name: 모디파이어 이름
|
||||
**properties: 수정할 속성들 (key=value 형태)
|
||||
|
||||
Example properties by modifier type:
|
||||
SUBSURF: levels, render_levels
|
||||
MIRROR: use_axis, use_bisect_axis, mirror_object
|
||||
ARRAY: count, relative_offset_displace
|
||||
BEVEL: width, segments, limit_method
|
||||
BOOLEAN: operation, object
|
||||
SOLIDIFY: thickness, offset
|
||||
ARMATURE: object, use_vertex_groups
|
||||
SIMPLE_DEFORM: deform_method, factor, angle
|
||||
CAST: cast_type, factor, radius
|
||||
DISPLACE: strength, direction
|
||||
"""
|
||||
logger.info(f"Modifying properties of '{modifier_name}' on '{object_name}'")
|
||||
|
||||
obj = bpy.data.objects.get(object_name)
|
||||
if not obj:
|
||||
raise ValueError(f"Object '{object_name}' not found")
|
||||
|
||||
mod = obj.modifiers.get(modifier_name)
|
||||
if not mod:
|
||||
raise ValueError(f"Modifier '{modifier_name}' not found on '{object_name}'")
|
||||
|
||||
updated_properties = {}
|
||||
for key, value in properties.items():
|
||||
if hasattr(mod, key):
|
||||
# 특수 처리가 필요한 속성들
|
||||
if key in ['use_axis', 'use_bisect_axis'] and isinstance(value, list):
|
||||
# Mirror 모디파이어의 axis는 boolean 배열
|
||||
for i, v in enumerate(value):
|
||||
if i < 3:
|
||||
getattr(mod, key)[i] = v
|
||||
elif key == 'relative_offset_displace' and isinstance(value, list):
|
||||
# Array 모디파이어의 offset은 Vector
|
||||
for i, v in enumerate(value):
|
||||
if i < 3:
|
||||
mod.relative_offset_displace[i] = v
|
||||
elif key == 'object' and isinstance(value, str):
|
||||
# 오브젝트 참조는 문자열로 받아서 변환
|
||||
target_obj = bpy.data.objects.get(value)
|
||||
if target_obj:
|
||||
setattr(mod, key, target_obj)
|
||||
else:
|
||||
logger.warn(f"Target object '{value}' not found for property '{key}'")
|
||||
continue
|
||||
else:
|
||||
# 일반 속성
|
||||
setattr(mod, key, value)
|
||||
|
||||
updated_properties[key] = value
|
||||
else:
|
||||
logger.warn(f"Property '{key}' not found on modifier '{modifier_name}'")
|
||||
|
||||
return {
|
||||
'name': mod.name,
|
||||
'type': mod.type,
|
||||
'updated_properties': updated_properties
|
||||
}
|
||||
|
||||
|
||||
def get_modifier_info(object_name: str, modifier_name: str) -> Dict[str, Any]:
|
||||
"""특정 모디파이어의 상세 정보 조회"""
|
||||
logger.info(f"Getting info for modifier '{modifier_name}' on '{object_name}'")
|
||||
|
||||
obj = bpy.data.objects.get(object_name)
|
||||
if not obj:
|
||||
raise ValueError(f"Object '{object_name}' not found")
|
||||
|
||||
mod = obj.modifiers.get(modifier_name)
|
||||
if not mod:
|
||||
raise ValueError(f"Modifier '{modifier_name}' not found on '{object_name}'")
|
||||
|
||||
# 모든 읽기 가능한 속성을 추출
|
||||
info = {
|
||||
'name': mod.name,
|
||||
'type': mod.type,
|
||||
'show_viewport': mod.show_viewport,
|
||||
'show_render': mod.show_render,
|
||||
}
|
||||
|
||||
# 타입별 모든 관련 속성 추가
|
||||
if mod.type == 'SUBSURF':
|
||||
info.update({
|
||||
'levels': mod.levels,
|
||||
'render_levels': mod.render_levels,
|
||||
'subdivision_type': mod.subdivision_type,
|
||||
'use_limit_surface': mod.use_limit_surface
|
||||
})
|
||||
elif mod.type == 'MIRROR':
|
||||
info.update({
|
||||
'use_axis': [mod.use_axis[0], mod.use_axis[1], mod.use_axis[2]],
|
||||
'use_bisect_axis': [mod.use_bisect_axis[0], mod.use_bisect_axis[1], mod.use_bisect_axis[2]],
|
||||
'use_bisect_flip_axis': [mod.use_bisect_flip_axis[0], mod.use_bisect_flip_axis[1], mod.use_bisect_flip_axis[2]],
|
||||
'mirror_object': mod.mirror_object.name if mod.mirror_object else None,
|
||||
'use_clip': mod.use_clip,
|
||||
'use_mirror_merge': mod.use_mirror_merge,
|
||||
'merge_threshold': mod.merge_threshold
|
||||
})
|
||||
elif mod.type == 'ARRAY':
|
||||
info.update({
|
||||
'count': mod.count,
|
||||
'use_constant_offset': mod.use_constant_offset,
|
||||
'use_relative_offset': mod.use_relative_offset,
|
||||
'use_object_offset': mod.use_object_offset,
|
||||
'constant_offset_displace': list(mod.constant_offset_displace),
|
||||
'relative_offset_displace': list(mod.relative_offset_displace),
|
||||
'offset_object': mod.offset_object.name if mod.offset_object else None
|
||||
})
|
||||
elif mod.type == 'BEVEL':
|
||||
info.update({
|
||||
'width': mod.width,
|
||||
'segments': mod.segments,
|
||||
'limit_method': mod.limit_method,
|
||||
'offset_type': mod.offset_type,
|
||||
'profile': mod.profile,
|
||||
'material': mod.material
|
||||
})
|
||||
elif mod.type == 'BOOLEAN':
|
||||
info.update({
|
||||
'operation': mod.operation,
|
||||
'object': mod.object.name if mod.object else None,
|
||||
'solver': mod.solver
|
||||
})
|
||||
elif mod.type == 'SOLIDIFY':
|
||||
info.update({
|
||||
'thickness': mod.thickness,
|
||||
'offset': mod.offset,
|
||||
'use_rim': mod.use_rim,
|
||||
'use_even_offset': mod.use_even_offset,
|
||||
'material_offset': mod.material_offset
|
||||
})
|
||||
|
||||
return info
|
||||
|
||||
|
||||
def reorder_modifier(object_name: str, modifier_name: str, direction: str) -> Dict[str, Any]:
|
||||
"""모디파이어 순서 변경
|
||||
|
||||
Args:
|
||||
object_name: 대상 오브젝트 이름
|
||||
modifier_name: 모디파이어 이름
|
||||
direction: 'UP' 또는 'DOWN'
|
||||
"""
|
||||
logger.info(f"Moving modifier '{modifier_name}' {direction} on '{object_name}'")
|
||||
|
||||
obj = bpy.data.objects.get(object_name)
|
||||
if not obj:
|
||||
raise ValueError(f"Object '{object_name}' not found")
|
||||
|
||||
mod = obj.modifiers.get(modifier_name)
|
||||
if not mod:
|
||||
raise ValueError(f"Modifier '{modifier_name}' not found on '{object_name}'")
|
||||
|
||||
# 오브젝트 선택 및 활성화
|
||||
bpy.context.view_layer.objects.active = obj
|
||||
obj.select_set(True)
|
||||
|
||||
if direction.upper() == 'UP':
|
||||
bpy.ops.object.modifier_move_up(modifier=modifier_name)
|
||||
elif direction.upper() == 'DOWN':
|
||||
bpy.ops.object.modifier_move_down(modifier=modifier_name)
|
||||
else:
|
||||
raise ValueError(f"Invalid direction '{direction}'. Use 'UP' or 'DOWN'")
|
||||
|
||||
# 현재 순서 반환
|
||||
modifiers = [m.name for m in obj.modifiers]
|
||||
|
||||
return {
|
||||
'status': 'success',
|
||||
'modifier': modifier_name,
|
||||
'new_order': modifiers
|
||||
}
|
||||
Reference in New Issue
Block a user