Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- bl_info = {
- "name": "Move corner",
- "author": "Alexei Kireev",
- "version": (0, 1),
- "blender": (2, 66, 0),
- "api": 54697,
- "location": "View3D > Toolbar and View3D > Specials (W-key)",
- "warning": "Alpha quality software. It CAN crash Blender at any moment (and it probably will), so save your work before using!",
- "description": "Move corner vertices while preserving adjacent face orientation",
- "category": "Mesh"}
- """
- Usage: launch from the specials menu (default hotkey - W)
- e-mail: al.kireev {at} gmail {dot} com
- """
- import bpy
- import mathutils
- import blf
- # -- Debuging code --
- #tmp_call_cnt = 0
- # --------------------------------------------------------------
- def draw_callback(self, context):
- # draw instructions
- font_id = 0
- txt = "[Enter] to finish"
- blf.size(font_id, 15, 72)
- dim = blf.dimensions(font_id, txt)
- w = context.region.width
- blf.position(font_id, w - dim[0] - 30, 20, 0)
- blf.draw(font_id, txt)
- # -- Debuging code --
- #global tmp_call_cnt
- #if tmp_call_cnt > 0:
- # return
- #tmp_call_cnt += 1
- #
- # ------------------------------------
- obj = context.active_object
- bpy.ops.object.mode_set(mode='OBJECT')
- dx = 0.0
- dy = 0.0
- dz = 0.0
- moved = False
- i = -1
- for sel_v_idx in self.sel_vert_idx:
- i += 1
- if (obj.data.vertices[sel_v_idx].co.x != self.sel_vert_pos[i].x or
- obj.data.vertices[sel_v_idx].co.y != self.sel_vert_pos[i].y or
- obj.data.vertices[sel_v_idx].co.z != self.sel_vert_pos[i].z):
- moved = True
- dx = obj.data.vertices[sel_v_idx].co.x - self.sel_vert_pos[i].x
- dy = obj.data.vertices[sel_v_idx].co.y - self.sel_vert_pos[i].y
- dz = obj.data.vertices[sel_v_idx].co.z - self.sel_vert_pos[i].z
- break
- # -- Debuging code --
- #if tmp_call_cnt > 0:
- # moved = True
- # dx = 0.4
- # dy = 0.0
- # dz = 0.0
- #
- if moved:
- for v_i in range(len(self.v_to_move)):
- self.v_to_move[v_i][2] = calc_v_pos_line2plane(self, obj, v_i, dx, dy, dz)
- # Apply position changes calculated by calc_v_pos
- for v_i in range(len(self.v_to_move)):
- if self.v_to_move[v_i][2]: # If the vertex was actually moved
- obj.data.vertices[self.v_to_move[v_i][0]].co.x = self.v_to_move[v_i][1].x
- obj.data.vertices[self.v_to_move[v_i][0]].co.y = self.v_to_move[v_i][1].y
- obj.data.vertices[self.v_to_move[v_i][0]].co.z = self.v_to_move[v_i][1].z
- # Save positions of the selected vertices for the next frame
- for v_i in range(len(self.sel_vert_idx)):
- self.sel_vert_pos[i].x = obj.data.vertices[sel_v_idx].co.x
- self.sel_vert_pos[i].y = obj.data.vertices[sel_v_idx].co.y
- self.sel_vert_pos[i].z = obj.data.vertices[sel_v_idx].co.z
- bpy.ops.object.mode_set(mode='EDIT')
- # --------------------------------------------------------------
- def calc_v_pos_line2plane(self, obj, v_i, dx, dy, dz):
- # --------------------------------------------------
- # Find the middle point of the selected vertices connected to this vertex
- # --------------------------------------------------
- m_count = 0
- sel_mid_point = mathutils.Vector((0.0, 0.0, 0.0))
- # At first, try to find selected vertices that share the one edge with our vertex
- for e in obj.data.edges:
- if self.v_to_move[v_i][0] in e.vertices: # Index of our vertex
- for v_idx in e.vertices:
- if obj.data.vertices[v_idx].select: # Assume our vertex is not selected
- sel_mid_point += obj.data.vertices[v_idx].co
- m_count += 1
- if m_count > 0:
- sel_mid_point /= m_count
- else:
- # The edge search has failed, so try to find selected vertices that share one face with our vertex
- for f in obj.data.polygons:
- if self.v_to_move[v_i][0] in f.vertices: # Index of our vertex
- for v_idx in f.vertices:
- if obj.data.vertices[v_idx].select:
- sel_mid_point += obj.data.vertices[v_idx].co
- m_count += 1
- if m_count > 0:
- sel_mid_point /= m_count
- else:
- return False
- # --------------------------------------------------
- # Find the one plane (face) in group B that contains
- # our vertex, OR, if there's two, find their intersection,
- # Which is a line. If there's more than two, we can't
- # move the vertex. Sorry.
- # --------------------------------------------------
- # TODO: this should be checked near the beginning of the function
- # to improve performance.
- f_idx = -1
- f_list = []
- for f in obj.data.polygons:
- f_idx += 1
- if self.v_to_move[v_i][0] in f.vertices and not IsFaceInGroupA(obj, f): # self.v_to_move[v_i][0] is the index of our vertex
- f_list.append(f_idx)
- if len(f_list) > 2:
- return False
- res_pt = None
- if len(f_list) == 0:
- return False
- elif len(f_list) == 1:
- # Use the only face found
- delta_vector = mathutils.Vector((dx, dy, dz))
- moved_v_pos = obj.data.vertices[self.v_to_move[v_i][0]].co + delta_vector
- moved_sel_mid_pos = sel_mid_point + delta_vector
- res_pt = mathutils.geometry.intersect_line_plane(moved_sel_mid_pos, moved_v_pos, obj.data.vertices[self.v_to_move[v_i][0]].co, obj.data.polygons[f_list[0]].normal)
- if res_pt is None:
- return False
- self.v_to_move[v_i][1] = res_pt
- else: # len(f_list) == 2
- # Use the intersection of the faces we have found
- v_co = obj.data.vertices[self.v_to_move[v_i][0]].co
- # TODO: What if it's the same plance twice?
- # !!! -- intersect_plane_plane may return bad results (if planes are parallel, etc.), but not None -- !!!
- res_line = mathutils.geometry.intersect_plane_plane(v_co, obj.data.polygons[f_list[0]].normal, v_co, obj.data.polygons[f_list[1]].normal)
- if res_line is None:
- return False
- # Now find the intersection between the driving line and the rail line we have found above
- delta_vector = mathutils.Vector((dx, dy, dz))
- moved_v_pos = v_co + delta_vector
- moved_sel_mid_pos = sel_mid_point + delta_vector
- res_pt = mathutils.geometry.intersect_line_line(moved_sel_mid_pos, moved_v_pos, res_line[0], res_line[0] + res_line[1])
- if res_pt is None:
- return False
- self.v_to_move[v_i][1] = res_pt[1]
- return True
- # --------------------------------------------------------------
- def IsFaceInGroupA(obj, face):
- for v_idx in face.vertices:
- if obj.data.vertices[v_idx].select:
- return True
- return False
- # --------------------------------------------------------------
- class MoveCorner(bpy.types.Operator):
- bl_idname = "mesh.move_corner"
- bl_label = "Move corner"
- bl_description = "Preserve orientation of adjacent faces while moving the selected vertices"
- bl_options = {'REGISTER', 'UNDO'}
- sel_vert_idx = []
- sel_vert_pos = []
- v_to_move = []
- def __init__(self):
- print("__init__")
- def __del__(self):
- print("__del__")
- def invoke(self, context, event):
- print("invoke")
- if context.area.type == 'VIEW_3D':
- context.window_manager.modal_handler_add(self)
- # Add the r3d OpenGL drawing callback
- # draw in view space with 'POST_VIEW' and 'PRE_VIEW'
- #self._handle = context.region.callback_add(draw_callback, (self, context), 'POST_PIXEL')
- # -- They have changed it in 2.66 --
- # To store the handle, the class should be used rather than the class instance (you may use self.__class__),
- # so we can access the variable from outside, e.g. to remove the drawback on unregister()
- self.__class__._handle = bpy.types.SpaceView3D.draw_handler_add(draw_callback, (self, context), 'WINDOW', 'POST_PIXEL')
- obj = context.active_object
- bpy.ops.object.mode_set(mode='OBJECT')
- #bpy.ops.object.editmode_toggle()
- # ----------------------
- self.sel_vert_idx = [] # Indices of the selected vertices to iterate upon easily
- self.sel_vert_pos = [] # "Previous" positions of the selected vertices
- v_to_move_idx = [] # List of the vertices to be moved. Used to compose self.v_to_move
- for f in obj.data.polygons:
- for v_idx in f.vertices:
- v = obj.data.vertices[v_idx]
- if v.select: # Add the vertex to the list of selected vertices and save its coordinates
- if not v_idx in self.sel_vert_idx:
- self.sel_vert_idx.append(v_idx)
- self.sel_vert_pos.append(mathutils.Vector((v.co.x, v.co.y, v.co.z)))
- # If the face contains one selected vertex or more, ..
- for v2_idx in f.vertices:
- if not obj.data.vertices[v2_idx].select and not v2_idx in v_to_move_idx:
- v_to_move_idx.append(v2_idx) # .. then add non-selected vertices to the list to be moved
- print("Selected vertices:")
- print(self.sel_vert_idx)
- print("Idx of vertices to move:")
- print(v_to_move_idx)
- self.v_to_move = []
- # [[v1_idx, v1_pos, new pos calculated flag, [e1_idx, e2_idx, .., eN1_idx], f1_idx, f2_idx, .., fN1_idx],
- # [v2_idx, v2_pos, new pos calculated flag, [e1_idx, e2_idx, .., eN2_idx], f1_idx, f2_idx, .., fN2_idx],
- # ..,
- # [vM_idx, vM_pos, new pos calculated flag, [e1_idx, e2_idx, .., eNM_idx], f1_idx, f2_idx, .., fNM_idx]]
- # where v1_idx, v2_idx, etc. - indices of the vertices to be moved;
- # v1_pos, v2_pos, etc. - new position of the vertex, as calculated by the function
- # new pos calculated flag - boolean flag - whether the new position was calculated or the vertex is not moved
- # e1_idx, e2_idx, etc. - indices of the edges from face group B that don't also belong to group A and contain the vertex;
- # f1_idx, f2_idx, etc. - indices of the faces this particular vertex belongs to
- for v_idx in v_to_move_idx:
- v_faces = [v_idx, mathutils.Vector(obj.data.vertices[v_idx].co), False]
- # e1_idx, e2_idx, etc.
- e_idx = -1
- v_edges = []
- for e in obj.data.edges:
- e_idx += 1
- if v_idx in e.vertices:
- edge_OK = True
- if obj.data.vertices[v2_idx].select:
- edge_OK = False
- if edge_OK:
- v_edges.append(e_idx)
- v_faces.append(v_edges)
- # f1_idx, f2_idx, etc.
- f_idx = -1
- for f in obj.data.polygons:
- f_idx += 1
- if v_idx in f.vertices:
- v_faces.append(f_idx)
- self.v_to_move.append(v_faces)
- # ----------------------
- bpy.ops.object.mode_set(mode='EDIT')
- #bpy.ops.object.editmode_toggle()
- return {'RUNNING_MODAL'}
- else:
- self.report({'WARNING'}, "view3d not found, cannot run operator (%s)" % context.area.type)
- return {'CANCELLED'}
- def modal(self, context, event):
- context.area.tag_redraw()
- if event.type in ('NUMPAD_ENTER', 'RET'):
- #context.region.callback_remove(self._handle)
- # -- They have changed it in 2.66 --
- bpy.types.SpaceView3D.draw_handler_remove(self.__class__._handle, 'WINDOW')
- return {'FINISHED'}
- return {'PASS_THROUGH'}
- # return {'RUNNING_MODAL'}
- @classmethod
- def poll(cls, context):
- ob = context.active_object
- return (ob and ob.type == 'MESH' and context.mode == 'EDIT_MESH')
- # --------------------------------------------------------------
- # Integration with Blender and whatnot
- # --------------------------------------------------------------
- def menu_func(self, context):
- self.layout.operator_context = "INVOKE_REGION_WIN"
- self.layout.operator(MoveCorner.bl_idname, text="Move corner")
- self.layout.separator()
- # define classes for registration
- classes = [MoveCorner]
- # Registration and menu integration
- def register():
- if int(bpy.app.build_revision[0:5]) < 54697:
- print("Error in Move Corner:")
- print("Please download Blender 2.66 or newer.")
- return {'ERROR'}
- for c in classes:
- bpy.utils.register_class(c)
- bpy.types.VIEW3D_MT_edit_mesh_specials.prepend(menu_func)
- # unregistering and removing menus
- def unregister():
- for c in classes:
- bpy.utils.unregister_class(c)
- bpy.types.VIEW3D_MT_edit_mesh_specials.remove(menu_func)
- if __name__ == "__main__":
- register()
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement