diff --git a/mesh_snap_utilities_line.py b/mesh_snap_utilities_line.py new file mode 100644 index 0000000000000000000000000000000000000000..2e07fa858d0862f2bb3ddc0e5bd975166ab7a66f --- /dev/null +++ b/mesh_snap_utilities_line.py @@ -0,0 +1,1031 @@ +### BEGIN GPL LICENSE BLOCK ##### +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 3 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# +# ##### END GPL LICENSE BLOCK ##### + +# Contact for more information about the Addon: +# Email: germano.costa@ig.com.br +# Twitter: wii_mano @mano_wii + +bl_info = { + "name": "Snap_Utilities_Line", + "author": "Germano Cavalcante", + "version": (5, 7, 1), + "blender": (2, 75, 0), + "location": "View3D > TOOLS > Snap Utilities > snap utilities", + "description": "Extends Blender Snap controls", + "wiki_url" : "http://blenderartists.org/forum/showthread.php?363859-Addon-CAD-Snap-Utilities", + "tracker_url": "https://developer.blender.org/maniphest/task/edit/form/2/", + "category": "Mesh"} + +import bpy, bgl, bmesh +from mathutils import Vector +from mathutils.geometry import ( + intersect_point_line, + intersect_line_line, + intersect_line_plane, + intersect_ray_tri) + +def get_units_info(scale, unit_system, separate_units): + if unit_system == 'METRIC': + scale_steps = ((1000, 'km'), (1, 'm'), (1 / 100, 'cm'), + (1 / 1000, 'mm'), (1 / 1000000, '\u00b5m')) + elif unit_system == 'IMPERIAL': + scale_steps = ((5280, 'mi'), (1, '\''), + (1 / 12, '"'), (1 / 12000, 'thou')) + scale /= 0.3048 # BU to feet + else: + scale_steps = ((1, ' BU'),) + separate_units = False + + return (scale, scale_steps, separate_units) + +def convert_distance(val, units_info, precision = 5): + scale, scale_steps, separate_units = units_info + sval = val * scale + idx = 0 + while idx < len(scale_steps) - 1: + if sval >= scale_steps[idx][0]: + break + idx += 1 + factor, suffix = scale_steps[idx] + sval /= factor + if not separate_units or idx == len(scale_steps) - 1: + dval = str(round(sval, precision)) + suffix + else: + ival = int(sval) + dval = str(round(ival, precision)) + suffix + fval = sval - ival + idx += 1 + while idx < len(scale_steps): + fval *= scale_steps[idx - 1][0] / scale_steps[idx][0] + if fval >= 1: + dval += ' ' \ + + ("%.1f" % fval) \ + + scale_steps[idx][1] + break + idx += 1 + return dval + +def location_3d_to_region_2d(region, rv3d, coord): + prj = rv3d.perspective_matrix * Vector((coord[0], coord[1], coord[2], 1.0)) + width_half = region.width / 2.0 + height_half = region.height / 2.0 + return Vector((width_half + width_half * (prj.x / prj.w), + height_half + height_half * (prj.y / prj.w), + prj.z / prj.w + )) + +def fac_nearest_to_segment_2d(co2v, v2d0, v2d1): + u = v2d1.xy - v2d0.xy + h = co2v.xy - v2d0.xy + return u.dot(h) / u.length_squared + +def region_2d_to_orig_and_view_vector(region, rv3d, coord, clamp=None): + viewinv = rv3d.view_matrix.inverted() + persinv = rv3d.perspective_matrix.inverted() + + dx = (2.0 * coord[0] / region.width) - 1.0 + dy = (2.0 * coord[1] / region.height) - 1.0 + + if rv3d.is_perspective: + origin_start = viewinv.translation.copy() + + out = Vector((dx, dy, -0.5)) + + w = out.dot(persinv[3].xyz) + persinv[3][3] + + view_vector = ((persinv * out) / w) - origin_start + else: + view_vector = -viewinv.col[2].xyz + + origin_start = ((persinv.col[0].xyz * dx) + + (persinv.col[1].xyz * dy) + + viewinv.translation) + + if clamp != 0.0: + if rv3d.view_perspective != 'CAMERA': + # this value is scaled to the far clip already + origin_offset = persinv.col[2].xyz + if clamp is not None: + if clamp < 0.0: + origin_offset.negate() + clamp = -clamp + if origin_offset.length > clamp: + origin_offset.length = clamp + + origin_start -= origin_offset + + view_vector.normalize() + return origin_start, view_vector + +def out_Location(rv3d, region, orig, vector): + view_matrix = rv3d.view_matrix + v1 = Vector((int(view_matrix[0][0]*1.5),int(view_matrix[0][1]*1.5),int(view_matrix[0][2]*1.5))) + v2 = Vector((int(view_matrix[1][0]*1.5),int(view_matrix[1][1]*1.5),int(view_matrix[1][2]*1.5))) + + hit = intersect_ray_tri(Vector((1,0,0)), Vector((0,1,0)), Vector(), (vector), (orig), False) + if hit == None: + hit = intersect_ray_tri(v1, v2, Vector(), (vector), (orig), False) + if hit == None: + hit = intersect_ray_tri(v1, v2, Vector(), (-vector), (orig), False) + if hit == None: + hit = Vector() + return hit + +class SnapCache(): + bvert = None + vco = None + + bedge = None + v0 = None + v1 = None + vmid = None + vperp = None + v2d0 = None + v2d1 = None + v2dmid = None + v2dperp = None + + bface = None + fmid = None + fnor = None + + out_obj = None + out_obmat = None + out_obimat = None + + +def snap_utilities( + cache, context, obj_matrix_world, + bm, mcursor, + outer_verts = False, + constrain = None, + previous_vert = None, + ignore_obj = None, + increment = 0.0): + + rv3d = context.region_data + region = context.region + scene = context.scene + is_increment = False + r_loc = None + r_type = None + r_len = 0.0 + bm_geom = None + + if bm.select_history: + bm.select_history[0].select = False + bm.select_history.clear() + + bpy.ops.view3d.select(location = (int(mcursor.x), int(mcursor.y))) + + if bm.select_history: + bm_geom = bm.select_history[0] + + if isinstance(bm_geom, bmesh.types.BMVert): + r_type = 'VERT' + + if cache.bvert != bm_geom: + cache.bvert = bm_geom + cache.vco = obj_matrix_world * cache.bvert.co + #cache.v2d = location_3d_to_region_2d(region, rv3d, cache.vco) + + if constrain: + location = intersect_point_line(cache.vco, constrain[0], constrain[1]) + #factor = location[1] + r_loc = location[0] + else: + r_loc = cache.vco + + elif isinstance(bm_geom, bmesh.types.BMEdge): + if cache.bedge != bm_geom: + cache.bedge = bm_geom + cache.v0 = obj_matrix_world * bm_geom.verts[0].co + cache.v1 = obj_matrix_world * bm_geom.verts[1].co + cache.vmid = 0.5 * (cache.v0 + cache.v1) + cache.v2d0 = location_3d_to_region_2d(region, rv3d, cache.v0) + cache.v2d1 = location_3d_to_region_2d(region, rv3d, cache.v1) + cache.v2dmid = location_3d_to_region_2d(region, rv3d, cache.vmid) + + if previous_vert and previous_vert not in bm_geom.verts: + pvert_co = obj_matrix_world * previous_vert.co + perp_point = intersect_point_line(pvert_co, cache.v0, cache.v1) + cache.vperp = perp_point[0] + #factor = point_perpendicular[1] + cache.v2dperp = location_3d_to_region_2d(region, rv3d, perp_point[0]) + + #else: cache.v2dperp = None + + if constrain: + location = intersect_line_line(constrain[0], constrain[1], cache.v0, cache.v1) + if location == None: + is_increment = True + orig, view_vector = region_2d_to_orig_and_view_vector(region, rv3d, mcursor) + end = orig + view_vector + location = intersect_line_line(constrain[0], constrain[1], orig, end) + r_loc = location[0] + + elif cache.v2dperp and\ + abs(cache.v2dperp[0] - mcursor[0]) < 10 and abs(cache.v2dperp[1] - mcursor[1]) < 10: + r_type = 'PERPENDICULAR' + r_loc = cache.vperp + + elif abs(cache.v2dmid[0] - mcursor[0]) < 10 and abs(cache.v2dmid[1] - mcursor[1]) < 10: + r_type = 'CENTER' + r_loc = cache.vmid + + else: + if increment and previous_vert in cache.bedge.verts: + is_increment = True + + r_type = 'EDGE' + fac = fac_nearest_to_segment_2d(mcursor, cache.v2d0, cache.v2d1) + fac *= cache.v2d0.z / cache.v2d1.z #convert to fac3d + r_loc = cache.v0 + fac * (cache.v1 - cache.v0) + + elif isinstance(bm_geom, bmesh.types.BMFace): + is_increment = True + r_type = 'FACE' + + if cache.bface != bm_geom: + cache.bface = bm_geom + cache.fmid = obj_matrix_world*bm_geom.calc_center_median() + cache.fnor = bm_geom.normal*obj_matrix_world.inverted() + + orig, view_vector = region_2d_to_orig_and_view_vector(region, rv3d, mcursor) + end = orig + view_vector + r_loc = intersect_line_plane(orig, end, cache.fmid, cache.fnor, False) + + if constrain: + is_increment = False + r_loc = intersect_point_line(r_loc, constrain[0], constrain[1])[0] + + else: #OUT + is_increment = True + r_type = 'OUT' + + orig, view_vector = region_2d_to_orig_and_view_vector(region, rv3d, mcursor) + + face_index = -1 + if cache.out_obj == None: + result, r_loc, normal, face_index, cache.out_obj, cache.out_obmat = scene.ray_cast(orig, view_vector) + if result: + r_type = 'FACE' + cache.out_obimat = cache.out_obmat.inverted() + else: + face_index = -1 + r_loc = None + + if cache.out_obj and cache.out_obj != ignore_obj: + if not r_loc or outer_verts: + location = None + if face_index == -1: + # get the ray relative to the cache.out_obj + ray_origin_obj = cache.out_obimat * orig + end = orig + view_vector * 1000 + ray_target_obj = cache.out_obimat * end + result, location, normal, face_index = cache.out_obj.ray_cast(ray_origin_obj, ray_target_obj) + + if face_index == -1: + cache.out_obj = None + + elif outer_verts: + vloc = None + try: + me = cache.out_obj.data + verts = me.polygons[face_index].vertices + v_dist = 100 + + for i in verts: + v_co = cache.out_obmat * me.vertices[i].co + v_2d = location_3d_to_region_2d(region, rv3d, v_co) + dist = (Vector(mcursor) - v_2d.xy).length_squared + if dist < v_dist: + v_dist = dist + vloc = v_co + except Exception as e: + print('Fail', e) + + if vloc: + is_increment = False + r_type = 'VERT' + r_loc = vloc + + if not r_loc: + r_type = 'FACE' + r_loc = cache.out_obmat * location + + if constrain: + if r_loc: + is_increment = False + r_loc = intersect_point_line(r_loc, constrain[0], constrain[1])[0] + else: + r_loc = intersect_line_line(constrain[0], constrain[1], orig, end)[0] + + elif not r_loc: + r_loc = out_Location(rv3d, region, orig, view_vector) + + if previous_vert: + pv_co = obj_matrix_world * previous_vert.co + vec = r_loc - pv_co + if is_increment and increment: + r_len = round((1 / increment) * vec.length) * increment + r_loc = r_len * vec.normalized() + pv_co + else: + r_len = vec.length + + return r_loc, r_type, bm_geom, r_len + +def get_isolated_edges(bmvert): + linked = [e for e in bmvert.link_edges if not e.link_faces] + for e in linked: + linked += [le for v in e.verts if not v.link_faces for le in v.link_edges if le not in linked] + return linked + +def draw_line(self, obj, Bmesh, bm_geom, location): + if not hasattr(self, 'list_verts'): + self.list_verts = [] + + if not hasattr(self, 'list_edges'): + self.list_edges = [] + + if not hasattr(self, 'list_faces'): + self.list_faces = [] + + if bm_geom == None: + vertices = (bmesh.ops.create_vert(Bmesh, co=(location))) + self.list_verts.append(vertices['vert'][0]) + + elif isinstance(bm_geom, bmesh.types.BMVert): + if (bm_geom.co - location).length < .01: + if self.list_verts == [] or self.list_verts[-1] != bm_geom: + self.list_verts.append(bm_geom) + else: + vertices = bmesh.ops.create_vert(Bmesh, co=(location)) + self.list_verts.append(vertices['vert'][0]) + + elif isinstance(bm_geom, bmesh.types.BMEdge): + self.list_edges.append(bm_geom) + vector_p0_l = (bm_geom.verts[0].co-location) + vector_p1_l = (bm_geom.verts[1].co-location) + cross = vector_p0_l.cross(vector_p1_l)/bm_geom.calc_length() + + if cross < Vector((0.001,0,0)): # or round(vector_p0_l.angle(vector_p1_l), 2) == 3.14: + factor = vector_p0_l.length/bm_geom.calc_length() + vertex0 = bmesh.utils.edge_split(bm_geom, bm_geom.verts[0], factor) + self.list_verts.append(vertex0[1]) + #self.list_edges.append(vertex0[0]) + + else: # constrain point is near + vertices = bmesh.ops.create_vert(Bmesh, co=(location)) + self.list_verts.append(vertices['vert'][0]) + + elif isinstance(bm_geom, bmesh.types.BMFace): + self.list_faces.append(bm_geom) + vertices = (bmesh.ops.create_vert(Bmesh, co=(location))) + self.list_verts.append(vertices['vert'][0]) + + # draw, split and create face + if len(self.list_verts) >= 2: + V1 = self.list_verts[-2] + V2 = self.list_verts[-1] + #V2_link_verts = [x for y in [a.verts for a in V2.link_edges] for x in y if x != V2] + for edge in V2.link_edges: + if V1 in edge.verts: + self.list_edges.append(edge) + break + else: #if V1 not in V2_link_verts: + if not V2.link_edges: + edge = Bmesh.edges.new([V1, V2]) + self.list_edges.append(edge) + else: + link_two_faces = V1.link_faces and V2.link_faces + if link_two_faces: + self.list_faces = [f for f in V2.link_faces if f in V1.link_faces] + + elif not self.list_faces: + faces, co2 = (V1.link_faces, V2.co.copy()) if V1.link_faces else (V2.link_faces, V1.co.copy()) + for face in faces: + if bmesh.geometry.intersect_face_point(face, co2): + co = co2 - face.calc_center_median() + if co.dot(face.normal) < 0.001: + self.list_faces.append(face) + + if self.list_faces: + edge = Bmesh.edges.new([V1, V2]) + self.list_edges.append(edge) + ed_list = get_isolated_edges(V2) + for face in set(self.list_faces): + facesp = bmesh.utils.face_split_edgenet(face, list(set(ed_list))) + self.list_faces = [] + else: + if self.intersect: + facesp = bmesh.ops.connect_vert_pair(Bmesh, verts = [V1, V2], verts_exclude=Bmesh.verts) + #print(facesp) + if not self.intersect or not facesp['edges']: + edge = Bmesh.edges.new([V1, V2]) + self.list_edges.append(edge) + else: + for edge in facesp['edges']: + self.list_edges.append(edge) + bmesh.update_edit_mesh(obj.data, tessface=True, destructive=True) + + # create face + if self.create_face: + ed_list = self.list_edges.copy() + for edge in V2.link_edges: + for vert in edge.verts: + if vert in self.list_verts: + ed_list.append(edge) + for edge in get_isolated_edges(V2): + if edge not in ed_list: + ed_list.append(edge) + bmesh.ops.edgenet_fill(Bmesh, edges = list(set(ed_list))) + bmesh.update_edit_mesh(obj.data, tessface=True, destructive=True) + break + #print('face created') + + return [obj.matrix_world*a.co for a in self.list_verts] + +class CharMap: + ascii = { + ".", ",", "-", "+", "1", "2", "3", + "4", "5", "6", "7", "8", "9", "0", + "c", "m", "d", "k", "h", "a", + " ", "/", "*", "'", "\"" + #"=" + } + type = { + 'BACK_SPACE', 'DEL', + 'LEFT_ARROW', 'RIGHT_ARROW' + } + + @staticmethod + def modal(self, context, event): + c = event.ascii + if c: + if c == ",": + c = "." + self.length_entered = self.length_entered[:self.line_pos] + c + self.length_entered[self.line_pos:] + self.line_pos += 1 + if self.length_entered: + if event.type == 'BACK_SPACE': + self.length_entered = self.length_entered[:self.line_pos-1] + self.length_entered[self.line_pos:] + self.line_pos -= 1 + + elif event.type == 'DEL': + self.length_entered = self.length_entered[:self.line_pos] + self.length_entered[self.line_pos+1:] + + elif event.type == 'LEFT_ARROW': + self.line_pos = (self.line_pos - 1) % (len(self.length_entered)+1) + + elif event.type == 'RIGHT_ARROW': + self.line_pos = (self.line_pos + 1) % (len(self.length_entered)+1) + +class SnapUtilitiesLine(bpy.types.Operator): + """ Draw edges. Connect them to split faces.""" + bl_idname = "mesh.snap_utilities_line" + bl_label = "Line Tool" + bl_options = {'REGISTER', 'UNDO'} + + constrain_keys = { + 'X': Vector((1,0,0)), + 'Y': Vector((0,1,0)), + 'Z': Vector((0,0,1)), + 'RIGHT_SHIFT': 'shift', + 'LEFT_SHIFT': 'shift', + } + + @classmethod + def poll(cls, context): + preferences = context.user_preferences.addons[__name__].preferences + return (context.mode in {'EDIT_MESH', 'OBJECT'} and + preferences.create_new_obj or + (context.object is not None and + context.object.type == 'MESH')) + + def modal_navigation(self, context, event): + #TO DO: + #'View Orbit', 'View Pan', 'NDOF Orbit View', 'NDOF Pan View' + rv3d = context.region_data + if not hasattr(self, 'navigation_cache'): # or self.navigation_cache == False: + #print('update navigation') + self.navigation_cache = True + self.keys_rotate = set() + self.keys_move = set() + self.keys_zoom = set() + for key in context.window_manager.keyconfigs.user.keymaps['3D View'].keymap_items: + if key.idname == 'view3d.rotate': + #self.keys_rotate[key.id]={'Alt': key.alt, 'Ctrl': key.ctrl, 'Shift':key.shift, 'Type':key.type, 'Value':key.value} + self.keys_rotate.add((key.alt, key.ctrl, key.shift, key.type, key.value)) + if key.idname == 'view3d.move': + self.keys_move.add((key.alt, key.ctrl, key.shift, key.type, key.value)) + if key.idname == 'view3d.zoom': + self.keys_zoom.add((key.alt, key.ctrl, key.shift, key.type, key.value, key.properties.delta)) + if key.type == 'WHEELINMOUSE': + self.keys_zoom.add((key.alt, key.ctrl, key.shift, 'WHEELDOWNMOUSE', key.value, key.properties.delta)) + if key.type == 'WHEELOUTMOUSE': + self.keys_zoom.add((key.alt, key.ctrl, key.shift, 'WHEELUPMOUSE', key.value, key.properties.delta)) + + evkey = (event.alt, event.ctrl, event.shift, event.type, event.value) + if evkey in self.keys_rotate: + bpy.ops.view3d.rotate('INVOKE_DEFAULT') + elif evkey in self.keys_move: + if event.shift and self.vector_constrain and self.vector_constrain[2] in {'RIGHT_SHIFT', 'LEFT_SHIFT', 'shift'}: + self.vector_constrain = None + bpy.ops.view3d.move('INVOKE_DEFAULT') + else: + for key in self.keys_zoom: + if evkey == key[0:5]: + delta = key[5] + if delta == 0: + bpy.ops.view3d.zoom('INVOKE_DEFAULT') + else: + rv3d.view_distance += delta*rv3d.view_distance/6 + rv3d.view_location -= delta*(self.location - rv3d.view_location)/6 + break + + def draw_callback_px(self, context): + # draw 3d point OpenGL in the 3D View + bgl.glEnable(bgl.GL_BLEND) + bgl.glDisable(bgl.GL_DEPTH_TEST) + + if self.vector_constrain: + vc = self.vector_constrain + if hasattr(self, 'preloc') and self.type in {'VERT', 'FACE'}: + bgl.glColor4f(1.0,1.0,1.0,0.5) + bgl.glPointSize(5) + bgl.glBegin(bgl.GL_POINTS) + bgl.glVertex3f(*self.preloc) + bgl.glEnd() + if vc[2] == 'X': + Color4f = (self.axis_x_color + (1.0,)) + elif vc[2] == 'Y': + Color4f = (self.axis_y_color + (1.0,)) + elif vc[2] == 'Z': + Color4f = (self.axis_z_color + (1.0,)) + else: + Color4f = self.constrain_shift_color + else: + if self.type == 'OUT': + Color4f = self.out_color + elif self.type == 'FACE': + Color4f = self.face_color + elif self.type == 'EDGE': + Color4f = self.edge_color + elif self.type == 'VERT': + Color4f = self.vert_color + elif self.type == 'CENTER': + Color4f = self.center_color + elif self.type == 'PERPENDICULAR': + Color4f = self.perpendicular_color + + bgl.glColor4f(*Color4f) + bgl.glPointSize(10) + bgl.glBegin(bgl.GL_POINTS) + bgl.glVertex3f(*self.location) + bgl.glEnd() + + # draw 3d line OpenGL in the 3D View + bgl.glEnable(bgl.GL_DEPTH_TEST) + bgl.glDepthRange(0,0.9999) + bgl.glColor4f(1.0, 0.8, 0.0, 1.0) + bgl.glLineWidth(2) + bgl.glEnable(bgl.GL_LINE_STIPPLE) + bgl.glBegin(bgl.GL_LINE_STRIP) + for vert_co in self.list_verts_co: + bgl.glVertex3f(*vert_co) + bgl.glVertex3f(*self.location) + bgl.glEnd() + + # restore opengl defaults + bgl.glDepthRange(0,1) + bgl.glPointSize(1) + bgl.glLineWidth(1) + bgl.glDisable(bgl.GL_BLEND) + bgl.glDisable(bgl.GL_LINE_STIPPLE) + bgl.glColor4f(0.0, 0.0, 0.0, 1.0) + + def modal(self, context, event): + context.area.tag_redraw() + + if event.ctrl and event.type == 'Z' and event.value == 'PRESS': + bpy.ops.ed.undo() + self.vector_constrain = None + self.list_verts_co = [] + self.list_verts = [] + self.list_edges = [] + self.list_faces = [] + self.obj = bpy.context.active_object + self.obj_matrix = self.obj.matrix_world.copy() + self.bm = bmesh.from_edit_mesh(self.obj.data) + return {'RUNNING_MODAL'} + + if event.type == 'MOUSEMOVE' or self.bool_update: + if self.rv3d.view_matrix != self.rotMat: + self.rotMat = self.rv3d.view_matrix.copy() + self.bool_update = True + self.cache.bedge = None + else: + self.bool_update = False + + mval = Vector((event.mouse_region_x, event.mouse_region_y)) + + if self.list_verts != []: + previous_vert = self.list_verts[-1] + else: + previous_vert = None + + outer_verts = self.outer_verts and not self.keytab + self.location, self.type, self.geom, self.len = snap_utilities( + self.cache, context, self.obj_matrix, + self.bm, mval, + outer_verts = self.outer_verts, + constrain = self.vector_constrain, + previous_vert = previous_vert, + ignore_obj = self.obj, + increment = self.incremental + ) + if self.snap_to_grid and self.type == 'OUT': + loc = self.location/self.rd + self.location = Vector((round(loc.x), + round(loc.y), + round(loc.z)))*self.rd + + if self.keyf8 and self.list_verts_co: + lloc = self.list_verts_co[-1] + orig, view_vec = region_2d_to_orig_and_view_vector(self.region, self.rv3d, mval) + location = intersect_point_line(lloc, orig, (orig+view_vec)) + vec = (location[0] - lloc) + ax, ay, az = abs(vec.x),abs(vec.y),abs(vec.z) + vec.x = ax > ay > az or ax > az > ay + vec.y = ay > ax > az or ay > az > ax + vec.z = az > ay > ax or az > ax > ay + if vec == Vector(): + self.vector_constrain = None + else: + vc = lloc+vec + try: + if vc != self.vector_constrain[1]: + type = 'X' if vec.x else 'Y' if vec.y else 'Z' if vec.z else 'shift' + self.vector_constrain = [lloc, vc, type] + except: + type = 'X' if vec.x else 'Y' if vec.y else 'Z' if vec.z else 'shift' + self.vector_constrain = [lloc, vc, type] + + if event.value == 'PRESS': + if self.list_verts_co and (event.ascii in CharMap.ascii or event.type in CharMap.type): + CharMap.modal(self, context, event) + + elif event.type in self.constrain_keys: + self.bool_update = True + if self.vector_constrain and self.vector_constrain[2] == event.type: + self.vector_constrain = () + + else: + if event.shift: + if isinstance(self.geom, bmesh.types.BMEdge): + if self.list_verts: + loc = self.list_verts_co[-1] + self.vector_constrain = (loc, loc + self.geom.verts[1].co - self.geom.verts[0].co, event.type) + else: + self.vector_constrain = [self.obj_matrix * v.co for v in self.geom.verts]+[event.type] + else: + if self.list_verts: + loc = self.list_verts_co[-1] + else: + loc = self.location + self.vector_constrain = [loc, loc + self.constrain_keys[event.type]]+[event.type] + + elif event.type == 'LEFTMOUSE': + # SNAP 2D + snap_3d = self.location + Lsnap_3d = self.obj_matrix.inverted()*snap_3d + Snap_2d = location_3d_to_region_2d(self.region, self.rv3d, snap_3d) + if self.vector_constrain and isinstance(self.geom, bmesh.types.BMVert): # SELECT FIRST + bpy.ops.view3d.select(location=(int(Snap_2d[0]), int(Snap_2d[1]))) + try: + geom2 = self.bm.select_history[0] + except: # IndexError or AttributeError: + geom2 = None + else: + geom2 = self.geom + self.vector_constrain = None + self.list_verts_co = draw_line(self, self.obj, self.bm, geom2, Lsnap_3d) + bpy.ops.ed.undo_push(message="Undo draw line*") + + elif event.type == 'TAB': + self.keytab = self.keytab == False + if self.keytab: + context.tool_settings.mesh_select_mode = (False, False, True) + else: + context.tool_settings.mesh_select_mode = (True, True, True) + + elif event.type == 'F8': + self.vector_constrain = None + self.keyf8 = self.keyf8 == False + + elif event.value == 'RELEASE': + if event.type in {'RET', 'NUMPAD_ENTER'}: + if self.length_entered != "" and self.list_verts_co: + try: + text_value = bpy.utils.units.to_value(self.unit_system, 'LENGTH', self.length_entered) + vector = (self.location-self.list_verts_co[-1]).normalized() + location = (self.list_verts_co[-1]+(vector*text_value)) + G_location = self.obj_matrix.inverted()*location + self.list_verts_co = draw_line(self, self.obj, self.bm, self.geom, G_location) + self.length_entered = "" + self.vector_constrain = None + + except:# ValueError: + self.report({'INFO'}, "Operation not supported yet") + + elif event.type in {'RIGHTMOUSE', 'ESC'}: + if self.list_verts_co == [] or event.type == 'ESC': + bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW') + context.tool_settings.mesh_select_mode = self.select_mode + context.area.header_text_set() + context.user_preferences.view.use_rotate_around_active = self.use_rotate_around_active + if not self.is_editmode: + bpy.ops.object.editmode_toggle() + return {'FINISHED'} + else: + self.vector_constrain = None + self.list_verts = [] + self.list_verts_co = [] + self.list_faces = [] + + a = "" + if self.list_verts_co: + if self.length_entered: + pos = self.line_pos + a = 'length: '+ self.length_entered[:pos] + '|' + self.length_entered[pos:] + else: + length = self.len + length = convert_distance(length, self.uinfo) + a = 'length: '+ length + context.area.header_text_set("hit: %.3f %.3f %.3f %s" % (self.location[0], self.location[1], self.location[2], a)) + + self.modal_navigation(context, event) + return {'RUNNING_MODAL'} + + def invoke(self, context, event): + if context.space_data.type == 'VIEW_3D': + #print('name', __name__, __package__) + preferences = context.user_preferences.addons[__name__].preferences + create_new_obj = preferences.create_new_obj + if context.mode == 'OBJECT' and (create_new_obj or context.object == None or context.object.type != 'MESH'): + + mesh = bpy.data.meshes.new("") + obj = bpy.data.objects.new("", mesh) + context.scene.objects.link(obj) + context.scene.objects.active = obj + + #bgl.glEnable(bgl.GL_POINT_SMOOTH) + self.is_editmode = bpy.context.object.data.is_editmode + bpy.ops.object.mode_set(mode='EDIT') + context.space_data.use_occlude_geometry = True + + self.scale = context.scene.unit_settings.scale_length + self.unit_system = context.scene.unit_settings.system + self.separate_units = context.scene.unit_settings.use_separate + self.uinfo = get_units_info(self.scale, self.unit_system, self.separate_units) + + grid = context.scene.unit_settings.scale_length/context.space_data.grid_scale + relative_scale = preferences.relative_scale + self.scale = grid/relative_scale + self.rd = bpy.utils.units.to_value(self.unit_system, 'LENGTH', str(1/self.scale)) + + incremental = preferences.incremental + self.incremental = bpy.utils.units.to_value(self.unit_system, 'LENGTH', str(incremental)) + + self.use_rotate_around_active = context.user_preferences.view.use_rotate_around_active + context.user_preferences.view.use_rotate_around_active = True + + self.select_mode = context.tool_settings.mesh_select_mode[:] + context.tool_settings.mesh_select_mode = (True, True, True) + + self.region = context.region + self.rv3d = context.region_data + self.rotMat = self.rv3d.view_matrix.copy() + self.obj = bpy.context.active_object + self.obj_matrix = self.obj.matrix_world.copy() + self.bm = bmesh.from_edit_mesh(self.obj.data) + self.cache = SnapCache() + + self.location = Vector() + self.list_verts = [] + self.list_verts_co = [] + self.bool_update = False + self.vector_constrain = () + self.keytab = False + self.keyf8 = False + self.type = 'OUT' + self.len = 0 + self.length_entered = "" + self.line_pos = 0 + + self.out_color = preferences.out_color + self.face_color = preferences.face_color + self.edge_color = preferences.edge_color + self.vert_color = preferences.vert_color + self.center_color = preferences.center_color + self.perpendicular_color = preferences.perpendicular_color + self.constrain_shift_color = preferences.constrain_shift_color + + self.axis_x_color = tuple(context.user_preferences.themes[0].user_interface.axis_x) + self.axis_y_color = tuple(context.user_preferences.themes[0].user_interface.axis_y) + self.axis_z_color = tuple(context.user_preferences.themes[0].user_interface.axis_z) + + self.intersect = preferences.intersect + self.create_face = preferences.create_face + self.outer_verts = preferences.outer_verts + self.snap_to_grid = preferences.increments_grid + + self._handle = bpy.types.SpaceView3D.draw_handler_add(self.draw_callback_px, (context,), 'WINDOW', 'POST_VIEW') + context.window_manager.modal_handler_add(self) + return {'RUNNING_MODAL'} + else: + self.report({'WARNING'}, "Active space must be a View3d") + return {'CANCELLED'} + +class PanelSnapUtilities(bpy.types.Panel) : + bl_space_type = "VIEW_3D" + bl_region_type = "TOOLS" + #bl_context = "mesh_edit" + bl_category = "Snap Utilities" + bl_label = "Snap Utilities" + + @classmethod + def poll(cls, context): + preferences = context.user_preferences.addons[__name__].preferences + return (context.mode in {'EDIT_MESH', 'OBJECT'} and + preferences.create_new_obj or + (context.object is not None and + context.object.type == 'MESH')) + + def draw(self, context): + layout = self.layout + TheCol = layout.column(align = True) + TheCol.operator("mesh.snap_utilities_line", text = "Line", icon="GREASEPENCIL") + + addon_prefs = context.user_preferences.addons[__name__].preferences + expand = addon_prefs.expand_snap_settings + icon = "TRIA_DOWN" if expand else "TRIA_RIGHT" + + box = layout.box() + box.prop(addon_prefs, "expand_snap_settings", icon=icon, + text="Settings:", emboss=False) + if expand: + #box.label(text="Snap Items:") + box.prop(addon_prefs, "outer_verts") + box.prop(addon_prefs, "incremental") + box.prop(addon_prefs, "increments_grid") + if addon_prefs.increments_grid: + box.prop(addon_prefs, "relative_scale") + box.label(text="Line Tool:") + box.prop(addon_prefs, "intersect") + box.prop(addon_prefs, "create_face") + box.prop(addon_prefs, "create_new_obj") + +def update_panel(self, context): + try: + bpy.utils.unregister_class(PanelSnapUtilities) + except: + pass + PanelSnapUtilities.bl_category = context.user_preferences.addons[__name__].preferences.category + bpy.utils.register_class(PanelSnapUtilities) + +class SnapAddonPreferences(bpy.types.AddonPreferences): + # this must match the addon name, use '__package__' + # when defining this in a submodule of a python package. + bl_idname = __name__ + + intersect = bpy.props.BoolProperty( + name="Intersect", + description="intersects created line with the existing edges, even if the lines do not intersect.", + default=True) + + create_new_obj = bpy.props.BoolProperty( + name="Create a new object", + description="If have not a active object, or the active object is not in edit mode, it creates a new object.", + default=False) + + create_face = bpy.props.BoolProperty( + name="Create faces", + description="Create faces defined by enclosed edges.", + default=False) + + outer_verts = bpy.props.BoolProperty( + name="Snap to outer vertices", + description="The vertices of the objects are not activated also snapped.", + default=True) + + expand_snap_settings = bpy.props.BoolProperty( + name="Expand", + description="Expand, to display the settings", + default=False) + + increments_grid = bpy.props.BoolProperty( + name="Increments of Grid", + description="Snap to increments of grid", + default=False) + + category = bpy.props.StringProperty( + name="Category", + description="Choose a name for the category of the panel", + default="Snap Utilities", + update=update_panel) + + incremental = bpy.props.FloatProperty( + name="Incremental", + description="Snap in defined increments", + default=0, + min=0, + step=1, + precision=3) + + relative_scale = bpy.props.FloatProperty( + name="Relative Scale", + description="Value that divides the global scale.", + default=1, + min=0, + step=1, + precision=3) + + out_color = bpy.props.FloatVectorProperty(name="OUT", default=(0.0, 0.0, 0.0, 0.5), size=4, subtype="COLOR", min=0, max=1) + face_color = bpy.props.FloatVectorProperty(name="FACE", default=(1.0, 0.8, 0.0, 1.0), size=4, subtype="COLOR", min=0, max=1) + edge_color = bpy.props.FloatVectorProperty(name="EDGE", default=(0.0, 0.8, 1.0, 1.0), size=4, subtype="COLOR", min=0, max=1) + vert_color = bpy.props.FloatVectorProperty(name="VERT", default=(1.0, 0.5, 0.0, 1.0), size=4, subtype="COLOR", min=0, max=1) + center_color = bpy.props.FloatVectorProperty(name="CENTER", default=(1.0, 0.0, 1.0, 1.0), size=4, subtype="COLOR", min=0, max=1) + perpendicular_color = bpy.props.FloatVectorProperty(name="PERPENDICULAR", default=(0.1, 0.5, 0.5, 1.0), size=4, subtype="COLOR", min=0, max=1) + constrain_shift_color = bpy.props.FloatVectorProperty(name="SHIFT CONSTRAIN", default=(0.8, 0.5, 0.4, 1.0), size=4, subtype="COLOR", min=0, max=1) + + def draw(self, context): + layout = self.layout + + layout.label(text="Snap Colors:") + split = layout.split() + + col = split.column() + col.prop(self, "out_color") + col.prop(self, "constrain_shift_color") + col = split.column() + col.prop(self, "face_color") + col = split.column() + col.prop(self, "edge_color") + col = split.column() + col.prop(self, "vert_color") + col = split.column() + col.prop(self, "center_color") + col = split.column() + col.prop(self, "perpendicular_color") + + row = layout.row() + + col = row.column() + col.label(text="Category:") + col.prop(self, "category", text="") + #col.label(text="Snap Items:") + col.prop(self, "incremental") + col.prop(self, "increments_grid") + if self.increments_grid: + col.prop(self, "relative_scale") + + col.prop(self, "outer_verts") + row.separator() + + col = row.column() + col.label(text="Line Tool:") + col.prop(self, "intersect") + col.prop(self, "create_face") + col.prop(self, "create_new_obj") + +def register(): + print('Addon', __name__, 'registered') + bpy.utils.register_class(SnapAddonPreferences) + bpy.utils.register_class(SnapUtilitiesLine) + update_panel(None, bpy.context) + #bpy.utils.register_class(PanelSnapUtilities) + +def unregister(): + bpy.utils.unregister_class(PanelSnapUtilities) + bpy.utils.unregister_class(SnapUtilitiesLine) + bpy.utils.unregister_class(SnapAddonPreferences) + +if __name__ == "__main__": + __name__ = "mesh_snap_utilities_line" + register()