Skip to content

Instantly share code, notes, and snippets.

@antonkudin
Last active January 2, 2024 03:18
Show Gist options
  • Save antonkudin/9722629305c4ec9a834faab05921b504 to your computer and use it in GitHub Desktop.
Save antonkudin/9722629305c4ec9a834faab05921b504 to your computer and use it in GitHub Desktop.
Nudge selection in a direction
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()
@antonkudin
Copy link
Author

Assign to directional keys (option/alt so it doesn't conflict) image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment