"""
Blender Modifier Handlers
This module contains handlers for adding various modifiers to mesh objects in Blender.
Includes Mirror, Shrinkwrap, Weighted Normal, Data Transfer, Decimate, and Laplacian Smooth modifiers.
"""
import bpy
def add_mirror_modifier(axis, use_clip, use_bisect, mirror_object):
"""Add a Mirror modifier to the active mesh"""
try:
active_obj = bpy.context.active_object
if not active_obj or active_obj.type != 'MESH':
return {"error": "No active mesh object selected"}
# Add Mirror modifier
modifier = active_obj.modifiers.new(name="Mirror", type='MIRROR')
# Set axis
modifier.use_axis[0] = (axis == 'X')
modifier.use_axis[1] = (axis == 'Y')
modifier.use_axis[2] = (axis == 'Z')
# Set options
modifier.use_clip = use_clip
modifier.use_bisect_axis[0] = use_bisect if axis == 'X' else False
modifier.use_bisect_axis[1] = use_bisect if axis == 'Y' else False
modifier.use_bisect_axis[2] = use_bisect if axis == 'Z' else False
# Set mirror object if provided
if mirror_object:
mirror_obj = bpy.data.objects.get(mirror_object)
if mirror_obj:
modifier.mirror_object = mirror_obj
else:
return {"error": f"Mirror object '{mirror_object}' not found"}
return {"success": True}
except Exception as e:
return {"error": f"Failed to add mirror modifier: {str(e)}"}
def add_shrinkwrap_modifier(target, method, offset, on_cage, cull_backfaces):
"""Add a Shrinkwrap modifier to project mesh onto target"""
try:
active_obj = bpy.context.active_object
if not active_obj or active_obj.type != 'MESH':
return {"error": "No active mesh object selected"}
# Find target object
target_obj = bpy.data.objects.get(target)
if not target_obj:
return {"error": f"Target object '{target}' not found"}
# Add Shrinkwrap modifier
modifier = active_obj.modifiers.new(name="Shrinkwrap", type='SHRINKWRAP')
modifier.target = target_obj
modifier.wrap_method = method
modifier.offset = offset
modifier.use_project_opposite = cull_backfaces
# Note: on_cage is typically used with PROJECT method in manual retopo
# but it's a different feature in Blender API
return {"success": True}
except Exception as e:
return {"error": f"Failed to add shrinkwrap modifier: {str(e)}"}
def add_weighted_normal_modifier(mode, weight, keep_sharp, face_influence):
"""Add a Weighted Normal modifier for better hard-surface shading"""
try:
active_obj = bpy.context.active_object
if not active_obj or active_obj.type != 'MESH':
return {"error": "No active mesh object selected"}
# Add Weighted Normal modifier
modifier = active_obj.modifiers.new(name="WeightedNormal", type='WEIGHTED_NORMAL')
modifier.mode = mode
modifier.weight = weight
modifier.keep_sharp = keep_sharp
modifier.use_face_influence = face_influence
return {"success": True}
except Exception as e:
return {"error": f"Failed to add weighted normal modifier: {str(e)}"}
def add_data_transfer_modifier(source, data_types, mapping, mix_factor):
"""Add a Data Transfer modifier to copy data from source"""
try:
active_obj = bpy.context.active_object
if not active_obj or active_obj.type != 'MESH':
return {"error": "No active mesh object selected"}
# Find source object
source_obj = bpy.data.objects.get(source)
if not source_obj:
return {"error": f"Source object '{source}' not found"}
# Add Data Transfer modifier
modifier = active_obj.modifiers.new(name="DataTransfer", type='DATA_TRANSFER')
modifier.object = source_obj
modifier.mix_mode = 'REPLACE'
modifier.mix_factor = mix_factor
# Enable data types
for data_type in data_types:
if data_type == 'CUSTOM_NORMAL':
modifier.use_loop_data = True
modifier.data_types_loops = {'CUSTOM_NORMAL'}
elif data_type == 'UV':
modifier.use_loop_data = True
modifier.data_types_loops = {'UV'}
elif data_type == 'VCOL':
modifier.use_loop_data = True
modifier.data_types_loops = {'VCOL'}
elif data_type == 'VGROUP_WEIGHTS':
modifier.use_vert_data = True
modifier.data_types_verts = {'VGROUP_WEIGHTS'}
return {"success": True}
except Exception as e:
return {"error": f"Failed to add data transfer modifier: {str(e)}"}
def add_decimate_modifier(mode, ratio, iterations):
"""Add a Decimate modifier to reduce polygon count"""
try:
active_obj = bpy.context.active_object
if not active_obj or active_obj.type != 'MESH':
return {"error": "No active mesh object selected"}
current_faces = len(active_obj.data.polygons)
# Add Decimate modifier
modifier = active_obj.modifiers.new(name="Decimate", type='DECIMATE')
modifier.decimate_type = mode
if mode == 'COLLAPSE':
modifier.ratio = ratio
predicted_faces = int(current_faces * ratio)
elif mode == 'UNSUBDIV':
modifier.iterations = iterations
predicted_faces = int(current_faces / (4 ** iterations))
else: # DISSOLVE
predicted_faces = current_faces # Can't predict for DISSOLVE
return {
"success": True,
"face_count": current_faces,
"predicted_faces": predicted_faces
}
except Exception as e:
return {"error": f"Failed to add decimate modifier: {str(e)}"}
def add_laplacian_smooth_modifier(factor, repeat, preserve_volume):
"""Add a Laplacian Smooth modifier to reduce noise"""
try:
active_obj = bpy.context.active_object
if not active_obj or active_obj.type != 'MESH':
return {"error": "No active mesh object selected"}
# Add Laplacian Smooth modifier
modifier = active_obj.modifiers.new(name="LaplacianSmooth", type='LAPLACIANSMOOTH')
modifier.lambda_factor = factor
modifier.iterations = repeat
modifier.use_volume_preserve = preserve_volume
return {"success": True}
except Exception as e:
return {"error": f"Failed to add laplacian smooth modifier: {str(e)}"}