import bpy import bmesh import collections from . import utilities_uv from .utilities_bbox import BBox from mathutils import Vector class op(bpy.types.Operator): bl_idname = "uv.textools_align" bl_label = "Align" bl_description = "Align vertices, edges or shells" bl_options = {'REGISTER', 'UNDO'} direction: bpy.props.StringProperty(name="Direction", default="top", options={'HIDDEN'}) @classmethod def poll(cls, context): if not bpy.context.active_object: return False if bpy.context.active_object.mode != 'EDIT': return False return True def execute(self, context): sync = bpy.context.scene.tool_settings.use_uv_select_sync all_groups = [] # islands, bboxes, uv_layer or corners, uv_layer update_obj = [] general_bbox = BBox() bmeshes_refcount_safe = [] selected_objs = utilities_uv.selected_unique_objects_in_mode_with_uv() align_mode = bpy.context.scene.texToolsSettings.align_mode _is_island_mode = is_island_mode() for obj in selected_objs: bm = bmesh.from_edit_mesh(obj.data) uv_layer = bm.loops.layers.uv.verify() if _is_island_mode: islands = utilities_uv.get_selected_islands(bm, uv_layer, selected=True) if not islands: continue for island in islands: bbox = BBox.calc_bbox_uv(island, uv_layer) general_bbox.union(bbox) all_groups.append((island, bbox, uv_layer)) bmeshes_refcount_safe.append(bm) update_obj.append(obj) else: if sync: corners = [luv for f in bm.faces if f.select for luv in f.loops] else: corners = [luv for f in bm.faces if f.select for luv in f.loops if luv[uv_layer].select] if not corners: continue if align_mode == 'SELECTION': bbox = BBox.calc_bbox_uv(corners, uv_layer, are_loops=True) general_bbox.union(bbox) all_groups.append((corners, uv_layer)) bmeshes_refcount_safe.append(bm) update_obj.append(obj) if not update_obj: self.report({'ERROR'}, "No object for manipulate") return {'CANCELLED'} if align_mode == 'SELECTION' and not general_bbox.is_valid: self.report({'ERROR'}, "Zero Area") return {'CANCELLED'} general_bbox = recalc_general_bbox_from_align_mode(align_mode, self.direction, general_bbox) if is_island_mode(): align_islands(all_groups, self.direction, general_bbox) else: # Vertices or Edges UV selection mode align_corners(all_groups, self.direction, general_bbox) for obj in update_obj: bmesh.update_edit_mesh(obj.data) return {'FINISHED'} def align_islands(groups, direction, general_bbox): for island, bounds, uv_layer in groups: center = bounds.center if direction == 'bottom': delta = Vector((0, (general_bbox.min - bounds.min).y)) elif direction == 'top': delta = Vector((0, (general_bbox.max - bounds.max).y)) elif direction == 'left': delta = Vector(((general_bbox.min - bounds.min).x, 0)) elif direction == 'right': delta = Vector(((general_bbox.max - bounds.max).x, 0)) elif direction == 'center': delta = Vector((general_bbox.center - center)) elif direction == 'horizontal': delta = Vector((0, (general_bbox.center - center).y)) elif direction == 'vertical': delta = Vector(((general_bbox.center - center).x, 0)) elif direction == 'bottomleft': delta = general_bbox.min - bounds.min elif direction == 'topright': delta = general_bbox.max - bounds.max elif direction == 'topleft': delta_x = general_bbox.min - bounds.min delta_y = general_bbox.max - bounds.max delta = Vector((delta_x.x, delta_y.y)) elif direction == 'bottomright': delta_x = general_bbox.max - bounds.max delta_y = general_bbox.min - bounds.min delta = Vector((delta_x.x, delta_y.y)) else: raise NotImplemented if delta != Vector((0, 0)): utilities_uv.translate_island(island, uv_layer, delta) def align_corners(groups, direction, general_bbox): for luvs, uv_layer in groups: if direction in {'left', 'right', 'vertical'}: if direction == 'left': destination = general_bbox.min.x elif direction == 'right': destination = general_bbox.max.x else: destination = general_bbox.center.x for luv in luvs: luv[uv_layer].uv[0] = destination elif direction in {'top', 'bottom', 'horizontal'}: if direction == 'top': destination = general_bbox.max.y elif direction == 'bottom': destination = general_bbox.min.y else: destination = general_bbox.center.y for luv in luvs: luv[uv_layer].uv[1] = destination else: if direction == 'center': destination = general_bbox.center elif direction == 'bottomleft': destination = general_bbox.min elif direction == 'topright': destination = general_bbox.max elif direction == 'topleft': destination = Vector((general_bbox.min.x, general_bbox.max.y)) elif direction == 'bottomright': destination = Vector((general_bbox.max.x, general_bbox.min.y)) else: raise NotImplemented for luv in luvs: luv[uv_layer].uv = destination def is_island_mode(): scene = bpy.context.scene if scene.tool_settings.use_uv_select_sync: selection_mode = 'FACE' if scene.tool_settings.mesh_select_mode[2] else 'VERTEX' else: selection_mode = scene.tool_settings.uv_select_mode return selection_mode in ('FACE', 'ISLAND') def recalc_general_bbox_from_align_mode(align_mode, direction, general_bbox): bb = collections.namedtuple('BBox', ['min', 'max', 'center']) if align_mode == 'SELECTION': general_bbox = bb(general_bbox.min, general_bbox.max, general_bbox.center) elif align_mode == 'CURSOR': cursor = Vector(bpy.context.space_data.cursor_location.copy()) general_bbox = bb(cursor, cursor, cursor) else: # CANVAS _, column, row = utilities_uv.get_UDIM_tile_coords(bpy.context.active_object) if direction in {'bottom', 'left', 'bottomleft'}: canvas = Vector((column, row)) elif direction in {'top', 'topleft'}: canvas = Vector((column, row + 1)) elif direction in {'right', 'topright'}: canvas = Vector((column + 1, row + 1)) elif direction == 'bottomright': canvas = Vector((column + 1, row)) elif direction in {'horizontal', 'vertical', 'center'}: canvas = Vector((column + 0.5, row + 0.5)) else: raise NotImplemented general_bbox = bb(canvas, canvas, canvas) return general_bbox bpy.utils.register_class(op)