SHARE
TWEET

mesh_movecorner.py

a guest Sep 15th, 2013 57 Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. bl_info = {
  2.     "name": "Move corner",
  3.     "author": "Alexei Kireev",
  4.     "version": (0, 1),
  5.     "blender": (2, 66, 0),
  6.     "api": 54697,
  7.     "location": "View3D > Toolbar and View3D > Specials (W-key)",
  8.     "warning": "Alpha quality software. It CAN crash Blender at any moment (and it probably will), so save your work before using!",
  9.     "description": "Move corner vertices while preserving adjacent face orientation",
  10.     "category": "Mesh"}
  11.  
  12. """
  13. Usage: launch from the specials menu (default hotkey - W)
  14. e-mail: al.kireev {at} gmail {dot} com
  15. """
  16.    
  17. import bpy
  18. import mathutils
  19. import blf
  20.  
  21. # -- Debuging code --
  22. #tmp_call_cnt = 0
  23.  
  24. # --------------------------------------------------------------
  25. def draw_callback(self, context):
  26.     # draw instructions
  27.     font_id = 0
  28.     txt = "[Enter] to finish"
  29.     blf.size(font_id, 15, 72)
  30.     dim = blf.dimensions(font_id, txt)
  31.     w = context.region.width
  32.     blf.position(font_id, w - dim[0] - 30, 20, 0)
  33.     blf.draw(font_id, txt)
  34.  
  35.     # -- Debuging code --
  36.     #global tmp_call_cnt
  37.     #if tmp_call_cnt > 0:
  38.     #    return
  39.     #tmp_call_cnt += 1
  40.     #
  41.  
  42.     # ------------------------------------
  43.     obj = context.active_object
  44.     bpy.ops.object.mode_set(mode='OBJECT')
  45.  
  46.     dx = 0.0
  47.     dy = 0.0
  48.     dz = 0.0
  49.  
  50.     moved = False
  51.     i = -1
  52.     for sel_v_idx in self.sel_vert_idx:
  53.         i += 1
  54.         if (obj.data.vertices[sel_v_idx].co.x != self.sel_vert_pos[i].x or
  55.             obj.data.vertices[sel_v_idx].co.y != self.sel_vert_pos[i].y or
  56.             obj.data.vertices[sel_v_idx].co.z != self.sel_vert_pos[i].z):
  57.             moved = True
  58.             dx = obj.data.vertices[sel_v_idx].co.x - self.sel_vert_pos[i].x
  59.             dy = obj.data.vertices[sel_v_idx].co.y - self.sel_vert_pos[i].y
  60.             dz = obj.data.vertices[sel_v_idx].co.z - self.sel_vert_pos[i].z
  61.             break
  62.  
  63.     # -- Debuging code --
  64.     #if tmp_call_cnt > 0:
  65.     #    moved = True
  66.     #    dx = 0.4
  67.     #    dy = 0.0
  68.     #    dz = 0.0
  69.     #
  70.  
  71.     if moved:
  72.         for v_i in range(len(self.v_to_move)):
  73.             self.v_to_move[v_i][2] = calc_v_pos_line2plane(self, obj, v_i, dx, dy, dz)
  74.        
  75.         # Apply position changes calculated by calc_v_pos
  76.         for v_i in range(len(self.v_to_move)):
  77.             if self.v_to_move[v_i][2]: # If the vertex was actually moved
  78.                 obj.data.vertices[self.v_to_move[v_i][0]].co.x = self.v_to_move[v_i][1].x
  79.                 obj.data.vertices[self.v_to_move[v_i][0]].co.y = self.v_to_move[v_i][1].y
  80.                 obj.data.vertices[self.v_to_move[v_i][0]].co.z = self.v_to_move[v_i][1].z
  81.  
  82.         # Save positions of the selected vertices for the next frame
  83.         for v_i in range(len(self.sel_vert_idx)):
  84.             self.sel_vert_pos[i].x = obj.data.vertices[sel_v_idx].co.x
  85.             self.sel_vert_pos[i].y = obj.data.vertices[sel_v_idx].co.y
  86.             self.sel_vert_pos[i].z = obj.data.vertices[sel_v_idx].co.z
  87.  
  88.     bpy.ops.object.mode_set(mode='EDIT')
  89.  
  90.  
  91. # --------------------------------------------------------------
  92. def calc_v_pos_line2plane(self, obj, v_i, dx, dy, dz):
  93.     # --------------------------------------------------
  94.     # Find the middle point of the selected vertices connected to this vertex
  95.     # --------------------------------------------------
  96.     m_count = 0
  97.     sel_mid_point = mathutils.Vector((0.0, 0.0, 0.0))
  98.     # At first, try to find selected vertices that share the one edge with our vertex
  99.     for e in obj.data.edges:
  100.         if self.v_to_move[v_i][0] in e.vertices: # Index of our vertex
  101.             for v_idx in e.vertices:
  102.                 if obj.data.vertices[v_idx].select: # Assume our vertex is not selected
  103.                     sel_mid_point += obj.data.vertices[v_idx].co
  104.                     m_count += 1
  105.  
  106.     if m_count > 0:
  107.         sel_mid_point /= m_count
  108.     else:
  109.         # The edge search has failed, so try to find selected vertices that share one face with our vertex
  110.         for f in obj.data.polygons:
  111.            if self.v_to_move[v_i][0] in f.vertices: # Index of our vertex
  112.                for v_idx in f.vertices:
  113.                    if obj.data.vertices[v_idx].select:
  114.                        sel_mid_point += obj.data.vertices[v_idx].co
  115.                        m_count += 1
  116.         if m_count > 0:
  117.            sel_mid_point /= m_count
  118.         else:
  119.             return False
  120.    
  121.     # --------------------------------------------------
  122.     # Find the one plane (face) in group B that contains
  123.     # our vertex, OR, if there's two, find their intersection,
  124.     # Which is a line. If there's more than two, we can't
  125.     # move the vertex. Sorry.
  126.     # --------------------------------------------------
  127.    
  128.     # TODO: this should be checked near the beginning of the function
  129.     # to improve performance.
  130.     f_idx = -1
  131.     f_list = []
  132.     for f in obj.data.polygons:
  133.         f_idx += 1
  134.         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
  135.             f_list.append(f_idx)
  136.             if len(f_list) > 2:
  137.                 return False
  138.  
  139.     res_pt = None
  140.     if len(f_list) == 0:
  141.         return False
  142.     elif len(f_list) == 1:
  143.         # Use the only face found
  144.         delta_vector = mathutils.Vector((dx, dy, dz))
  145.         moved_v_pos = obj.data.vertices[self.v_to_move[v_i][0]].co + delta_vector
  146.         moved_sel_mid_pos = sel_mid_point + delta_vector
  147.         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)
  148.         if res_pt is None:
  149.             return False
  150.         self.v_to_move[v_i][1] = res_pt
  151.     else: # len(f_list) == 2
  152.         # Use the intersection of the faces we have found
  153.         v_co = obj.data.vertices[self.v_to_move[v_i][0]].co
  154.         # TODO: What if it's the same plance twice?
  155.         # !!! -- intersect_plane_plane may return bad results (if planes are parallel, etc.), but not None -- !!!
  156.         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)
  157.         if res_line is None:
  158.             return False
  159.  
  160.         # Now find the intersection between the driving line and the rail line we have found above
  161.         delta_vector = mathutils.Vector((dx, dy, dz))
  162.         moved_v_pos = v_co + delta_vector
  163.         moved_sel_mid_pos = sel_mid_point + delta_vector
  164.         res_pt = mathutils.geometry.intersect_line_line(moved_sel_mid_pos, moved_v_pos, res_line[0], res_line[0] + res_line[1])
  165.  
  166.         if res_pt is None:
  167.             return False
  168.         self.v_to_move[v_i][1] = res_pt[1]
  169.  
  170.     return True
  171.  
  172. # --------------------------------------------------------------
  173. def IsFaceInGroupA(obj, face):
  174.     for v_idx in face.vertices:
  175.         if obj.data.vertices[v_idx].select:
  176.             return True
  177.     return False
  178.  
  179. # --------------------------------------------------------------
  180. class MoveCorner(bpy.types.Operator):
  181.     bl_idname = "mesh.move_corner"
  182.     bl_label = "Move corner"
  183.     bl_description = "Preserve orientation of adjacent faces while moving the selected vertices"
  184.     bl_options = {'REGISTER', 'UNDO'}
  185.  
  186.     sel_vert_idx = []
  187.     sel_vert_pos = []
  188.     v_to_move = []
  189.  
  190.     def __init__(self):
  191.         print("__init__")
  192.  
  193.     def __del__(self):
  194.         print("__del__")
  195.  
  196.     def invoke(self, context, event):
  197.         print("invoke")
  198.         if context.area.type == 'VIEW_3D':
  199.             context.window_manager.modal_handler_add(self)
  200.  
  201.             # Add the r3d OpenGL drawing callback
  202.             # draw in view space with 'POST_VIEW' and 'PRE_VIEW'
  203.             #self._handle = context.region.callback_add(draw_callback, (self, context), 'POST_PIXEL')
  204.  
  205.             # -- They have changed it in 2.66 --
  206.             # To store the handle, the class should be used rather than the class instance (you may use self.__class__),
  207.             # so we can access the variable from outside, e.g. to remove the drawback on unregister()
  208.             self.__class__._handle = bpy.types.SpaceView3D.draw_handler_add(draw_callback, (self, context), 'WINDOW', 'POST_PIXEL')
  209.  
  210.             obj = context.active_object
  211.             bpy.ops.object.mode_set(mode='OBJECT')
  212.             #bpy.ops.object.editmode_toggle()
  213.             # ----------------------
  214.             self.sel_vert_idx = [] # Indices of the selected vertices to iterate upon easily
  215.             self.sel_vert_pos = [] # "Previous" positions of the selected vertices
  216.             v_to_move_idx = [] # List of the vertices to be moved. Used to compose self.v_to_move
  217.             for f in obj.data.polygons:
  218.                 for v_idx in f.vertices:
  219.                     v = obj.data.vertices[v_idx]
  220.                     if v.select: # Add the vertex to the list of selected vertices and save its coordinates
  221.                         if not v_idx in self.sel_vert_idx:
  222.                             self.sel_vert_idx.append(v_idx)
  223.                             self.sel_vert_pos.append(mathutils.Vector((v.co.x, v.co.y, v.co.z)))
  224.                         # If the face contains one selected vertex or more, ..
  225.                         for v2_idx in f.vertices:
  226.                             if not obj.data.vertices[v2_idx].select and not v2_idx in v_to_move_idx:
  227.                                 v_to_move_idx.append(v2_idx) # .. then add non-selected vertices to the list to be moved
  228.             print("Selected vertices:")
  229.             print(self.sel_vert_idx)
  230.             print("Idx of vertices to move:")
  231.             print(v_to_move_idx)
  232.  
  233.             self.v_to_move = []
  234.             # [[v1_idx, v1_pos, new pos calculated flag, [e1_idx, e2_idx, .., eN1_idx], f1_idx, f2_idx, .., fN1_idx],
  235.             #  [v2_idx, v2_pos, new pos calculated flag, [e1_idx, e2_idx, .., eN2_idx], f1_idx, f2_idx, .., fN2_idx],
  236.             #  ..,
  237.             #  [vM_idx, vM_pos, new pos calculated flag, [e1_idx, e2_idx, .., eNM_idx], f1_idx, f2_idx, .., fNM_idx]]
  238.             # where v1_idx, v2_idx, etc. - indices of the vertices to be moved;
  239.             #       v1_pos, v2_pos, etc. - new position of the vertex, as calculated by the function
  240.             #       new pos calculated flag - boolean flag - whether the new position was calculated or the vertex is not moved
  241.             #       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;
  242.             #       f1_idx, f2_idx, etc. - indices of the faces this particular vertex belongs to
  243.             for v_idx in v_to_move_idx:
  244.                 v_faces = [v_idx, mathutils.Vector(obj.data.vertices[v_idx].co), False]
  245.  
  246.                 # e1_idx, e2_idx, etc.
  247.                 e_idx = -1
  248.                 v_edges = []
  249.                 for e in obj.data.edges:
  250.                     e_idx += 1
  251.                     if v_idx in e.vertices:
  252.                         edge_OK = True
  253.                         if obj.data.vertices[v2_idx].select:
  254.                             edge_OK = False
  255.  
  256.                         if edge_OK:
  257.                             v_edges.append(e_idx)
  258.                 v_faces.append(v_edges)
  259.  
  260.                 # f1_idx, f2_idx, etc.
  261.                 f_idx = -1
  262.                 for f in obj.data.polygons:
  263.                     f_idx += 1
  264.                     if v_idx in f.vertices:
  265.                       v_faces.append(f_idx)
  266.                 self.v_to_move.append(v_faces)
  267.  
  268.             # ----------------------
  269.             bpy.ops.object.mode_set(mode='EDIT')
  270.             #bpy.ops.object.editmode_toggle()
  271.  
  272.             return {'RUNNING_MODAL'}
  273.         else:
  274.             self.report({'WARNING'}, "view3d not found, cannot run operator (%s)" % context.area.type)
  275.             return {'CANCELLED'}
  276.  
  277.     def modal(self, context, event):
  278.         context.area.tag_redraw()
  279.  
  280.         if event.type in ('NUMPAD_ENTER', 'RET'):
  281.             #context.region.callback_remove(self._handle)
  282.             # -- They have changed it in 2.66 --
  283.             bpy.types.SpaceView3D.draw_handler_remove(self.__class__._handle, 'WINDOW')
  284.             return {'FINISHED'}
  285.  
  286.         return {'PASS_THROUGH'}
  287. #        return {'RUNNING_MODAL'}
  288.  
  289.     @classmethod
  290.     def poll(cls, context):
  291.         ob = context.active_object
  292.         return (ob and ob.type == 'MESH' and context.mode == 'EDIT_MESH')
  293.  
  294.  
  295. # --------------------------------------------------------------
  296. # Integration with Blender and whatnot
  297. # --------------------------------------------------------------
  298. def menu_func(self, context):
  299.     self.layout.operator_context = "INVOKE_REGION_WIN"
  300.     self.layout.operator(MoveCorner.bl_idname, text="Move corner")
  301.     self.layout.separator()
  302.  
  303. # define classes for registration
  304. classes = [MoveCorner]
  305.  
  306. # Registration and menu integration
  307. def register():
  308.     if int(bpy.app.build_revision[0:5]) < 54697:
  309.         print("Error in Move Corner:")
  310.         print("Please download Blender 2.66 or newer.")
  311.         return {'ERROR'}
  312.  
  313.     for c in classes:
  314.         bpy.utils.register_class(c)
  315.  
  316.     bpy.types.VIEW3D_MT_edit_mesh_specials.prepend(menu_func)
  317.  
  318.  
  319. # unregistering and removing menus
  320. def unregister():
  321.     for c in classes:
  322.         bpy.utils.unregister_class(c)
  323.  
  324.     bpy.types.VIEW3D_MT_edit_mesh_specials.remove(menu_func)
  325.  
  326.  
  327. if __name__ == "__main__":
  328.     register()
RAW Paste Data
We use cookies for various purposes including analytics. By continuing to use Pastebin, you agree to our use of cookies as described in the Cookies Policy. OK, I Understand
Not a member of Pastebin yet?
Sign Up, it unlocks many cool features!
 
Top