Last active
January 2, 2024 03:18
-
-
Save antonkudin/9722629305c4ec9a834faab05921b504 to your computer and use it in GitHub Desktop.
Nudge selection in a direction
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
bl_info = { | |
"name": "Move Vertices by Direction", | |
"blender": (2, 80, 0), | |
"category": "Mesh", | |
} | |
import bpy | |
from bpy.types import Operator | |
from bpy.props import FloatProperty, BoolProperty | |
from mathutils import Vector, Matrix | |
class OBJECT_OT_move_vertices_by_direction(Operator): | |
bl_idname = "object.move_vertices_by_direction" | |
bl_label = "Move Vertices by Direction" | |
bl_options = {'REGISTER', 'UNDO'} | |
distance: FloatProperty( | |
name="Distance", | |
description="Distance to move vertices", | |
default=1.0, | |
precision=2, | |
) | |
horizontal: BoolProperty( | |
name="Horizontal", | |
description="Move horizontally (X or Y axis) instead of vertically (Z axis)", | |
default=False, | |
) | |
@classmethod | |
def poll(cls, context): | |
return context.active_object is not None | |
def execute(self, context): | |
# Get the active object | |
obj = context.active_object | |
# Check if there is an active object | |
if obj: | |
# Check for zero vertices | |
if not obj.data.vertices: | |
self.report({'ERROR'}, "No vertices.") | |
return {'CANCELLED'} | |
# remember current mode | |
currentMode = bpy.context.object.mode | |
bpy.ops.object.mode_set(mode='OBJECT') | |
# move direction in camera space: distance is right if horisontal, up if vertical | |
# horisontal: x = 1, y = 0, z = 0, vertical: x = 0, y = 0, z = 1 | |
baseVect = Vector((1, 0, 0)) if self.horizontal else Vector((0, 1, 0)) | |
# get camera rotation | |
camera_rotation = context.region_data.view_rotation | |
# asuming vect is in camera space, rotate it to world space | |
vect = camera_rotation @ baseVect | |
# get object rotation | |
obj_rotation = obj.matrix_world.to_quaternion() | |
# invert | |
obj_rotation.invert() | |
# rotate vect to object space | |
vect = obj_rotation @ vect | |
#limit vect to biggest axis | |
if abs(vect.x) > abs(vect.y) and abs(vect.x) > abs(vect.z): | |
vect.y = 0 | |
vect.z = 0 | |
elif abs(vect.y) > abs(vect.x) and abs(vect.y) > abs(vect.z): | |
vect.x = 0 | |
vect.z = 0 | |
else: | |
vect.x = 0 | |
vect.y = 0 | |
# normalize vect | |
vect.normalize() | |
# scale vect by distance and by current measurement unit | |
vect *= self.distance / bpy.context.scene.unit_settings.scale_length | |
# pixelVect = Vector((0, 0)) | |
# if(self.horizontal): | |
# pixelVect.x = self.distance | |
# else: | |
# pixelVect.y = self.distance | |
# uvmap = None | |
# get uv editor space, even if it is not active | |
# for area in bpy.context.screen.areas: | |
# if area.type == 'IMAGE_EDITOR': | |
# space = area.spaces.active | |
# # get image | |
# image = space.image | |
# # check if image is valid | |
# if image and image.size[0] > 0 and image.size[1] > 0: | |
# # get uvmap | |
# uvmap = obj.data.uv_layers.active.data | |
# # get pixel size in uv space | |
# pixelVect.x /= image.size[0] | |
# pixelVect.y /= image.size[1] | |
# # get uv editor space | |
# break | |
# break | |
# if object mode, just move object | |
if currentMode == 'OBJECT': | |
# rotate vect to world space | |
vect = obj.matrix_world.to_quaternion() @ vect | |
# to all selected objects | |
for obj in bpy.context.selected_objects: | |
obj.location += vect | |
return {'FINISHED'} | |
# count | |
count = 0 | |
# Move vertices along the specified axis | |
for v in obj.data.vertices: | |
if v.select: | |
v.co += vect | |
# move selected vertices in uv editor | |
# if uvmap: | |
# uvmap[count].uv += pixelVect | |
count += 1 | |
# restore mode | |
bpy.ops.object.mode_set(mode=currentMode) | |
return {'FINISHED'} | |
else: | |
self.report({'ERROR'}, "No active object.") | |
return {'CANCELLED'} | |
def get_selection_center(self, obj): | |
# Get the center of the selected vertices | |
center = Vector() | |
count = 0 | |
for v in obj.data.vertices: | |
if v.select: | |
center += v.co | |
count += 1 | |
if count == 0: | |
self.report({'ERROR'}, "No selected vertices.") | |
return {'CANCELLED'} | |
center /= count | |
return center # it is in object space | |
def get_movement_direction(vect, self, context, obj): | |
# Get the camera and object matrices | |
camera_matrix = context.region_data.view_matrix | |
obj_matrix = obj.matrix_world | |
# Calculate the inverse matrices | |
inv_camera_matrix = camera_matrix.inverted() | |
inv_obj_matrix = obj_matrix.inverted() | |
# Get the camera's forward vector in world space | |
camera_vect = inv_camera_matrix @ vect | |
# Get the camera's forward vector in object space | |
camera_forward_obj_space = inv_obj_matrix @ camera_vect | |
return camera_forward_obj_space.normalized() | |
def menu_func(self, context): | |
self.layout.operator(OBJECT_OT_move_vertices_by_direction.bl_idname) | |
def draw_func(self, context): | |
layout = self.layout | |
layout.separator() | |
layout.operator(OBJECT_OT_move_vertices_by_direction.bl_idname) | |
def register(): | |
bpy.utils.register_class(OBJECT_OT_move_vertices_by_direction) | |
bpy.types.VIEW3D_MT_edit_mesh_context_menu.append(menu_func) | |
bpy.types.VIEW3D_MT_edit_mesh.append(draw_func) | |
def unregister(): | |
bpy.utils.unregister_class(OBJECT_OT_move_vertices_by_direction) | |
bpy.types.VIEW3D_MT_edit_mesh_context_menu.remove(menu_func) | |
bpy.types.VIEW3D_MT_edit_mesh.remove(draw_func) | |
if __name__ == "__main__": | |
register() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Assign to directional keys (option/alt so it doesn't conflict)