Skip to content
{{ message }}

Instantly share code, notes, and snippets.

# zeffii/intersect_all.py

Last active Dec 17, 2015
intersect_all as an addon. seems ok! needs user testing
 ''' 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 2 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, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. END GPL LICENCE BLOCK ''' bl_info = { "name": "Edge tools : tinyCAD X ALL", "author": "zeffii", "version": (0, 0, 4), "blender": (2, 6, 7), "category": "Mesh", "location": "View3D > EditMode > (w) Specials", "warning": "Still under development", "wiki_url": "", "tracker_url": "" } import bpy import bmesh from mathutils import Vector from mathutils.geometry import intersect_line_line as LineIntersect import itertools from collections import defaultdict VTX_PRECISION = 1.0e-5 # Epsilon. or 1.0e-6 ..if you need VTX_DOUBLES_THRSHLD = 0.0001 ''' helpers ''' def mDist(A, B): ''' returns distance between two given points ''' return (A-B).length def isPointOnEdge(point, A, B): ''' returns True / False if a point happens to lie on an edge ''' eps = ((mDist(A, B) - mDist(point,B)) - mDist(A,point)) if abs(eps) < VTX_PRECISION: return True return False def CountPointOnEdges(point, edges): ''' returns the number of edges that a point lies on. ''' v1, v2, v3, v4 = edges count = 0 if(isPointOnEdge(point, v1, v2)): count+=1 if(isPointOnEdge(point, v3, v4)): count+=1 return count def vertex_indices_from_edges_tuple(bm, edge): ''' find the vertex indices of a 2-tuple of edges ''' k = lambda v, w: bm.edges[edge[v]].verts[w].index return [k(i>>1, i%2)for i in range(4)] def vector_from_indices(bm, raw_vert_indices): return [bm.verts[i].co for i in raw_vert_indices] def order_points(edge, point_list): ''' order these edges from distance to v1, then sandwich the sorted list with v1, v2 ''' v1, v2 = edge dist = lambda co: (v1-co).length point_list = sorted(point_list, key=dist) return [v1] + point_list + [v2] def has_shared_vertex(vert_indices): if len(set(vert_indices)) < len(vert_indices): return True def remove_permutations_that_share_a_vertex(bm, permutations): ''' Get useful Permutations ''' final_permutations = [] for edges in permutations: raw_vert_indices = vertex_indices_from_edges_tuple(bm, edges) if has_shared_vertex(raw_vert_indices): continue # reaches this point if they do not share. final_permutations.append(edges) return final_permutations def get_valid_permutations(bm, edge_indices): raw_permutations = itertools.permutations(edge_indices, 2) permutations = [result for result in raw_permutations if result < result] return remove_permutations_that_share_a_vertex(bm, permutations) def can_skip(closest_points, vert_vectors): '''this checks if the intersection lies on both edges, returns True when criteria are not met, and thus this point can be skipped''' if not closest_points: return True if not isinstance(closest_points.x, float): return True if CountPointOnEdges(closest_points, vert_vectors) < 2: return True # if this distance is larger than than VTX_PRECISION, we can skip it. if mDist(closest_points, closest_points) > VTX_PRECISION: return True return False def get_intersection_dictionary(bm, edge_indices): permutations = get_valid_permutations(bm, edge_indices) k = defaultdict(list) d = defaultdict(list) for edges in permutations: raw_vert_indices = vertex_indices_from_edges_tuple(bm, edges) vert_vectors = vector_from_indices(bm, raw_vert_indices) points = LineIntersect(*vert_vectors) # some can be skipped. (NaN, None, not on both edges) if can_skip(points, vert_vectors): continue # reaches this point only when an intersection happens on both edges. [k[edge].append(points) for edge in edges] # k will contain a dict of edge indices and points found on those edges. for edge_idx, unordered_points in k.items(): tv1, tv2 = bm.edges[edge_idx].verts v1 = bm.verts[tv1.index].co v2 = bm.verts[tv2.index].co ordered_points = order_points((v1,v2), unordered_points) d[edge_idx].extend(ordered_points) return d def update_mesh(obj, d): ''' Make new geometry (delete old first) ''' bpy.ops.mesh.delete(type='EDGE') bpy.ops.object.editmode_toggle() oe = obj.data.edges ov = obj.data.vertices vert_count = len(ov) edge_count = len(oe) for old_edge, point_list in d.items(): num_points = len(point_list) num_edges_to_add = num_points-1 for i in range(num_edges_to_add): oe.add(1) ov.add(2) ov[vert_count].co = point_list[i] ov[vert_count+1].co = point_list[i+1] oe[edge_count].vertices = [vert_count, vert_count+1] vert_count = len(ov) edge_count = len(oe) # set edit mode bpy.ops.object.editmode_toggle() bpy.ops.mesh.remove_doubles(threshold=VTX_DOUBLES_THRSHLD, use_unselected=False) def unselect_nonintersecting(bm, d_edges, edge_indices): if len(edge_indices) > len(d_edges): reserved_edges = set(edge_indices) - set(d_edges) for edge in reserved_edges: bm.edges[edge].select = False print("unselected {}, non intersecting edges".format(reserved_edges)) class IntersectAllEdges(bpy.types.Operator): bl_idname = 'mesh.intersectall' bl_label = 'Edge tools : tinyCAD X ALL' # bl_options = {'REGISTER', 'UNDO'} @classmethod def poll(self, context): obj = context.active_object return obj != None and obj.type == 'MESH' and obj.mode == 'EDIT' def execute(self, context): # must force edge selection mode here bpy.context.tool_settings.mesh_select_mode = (False, True, False) obj = context.active_object if obj.mode == "EDIT": bm = bmesh.from_edit_mesh(obj.data) selected_edges = [edge for edge in bm.edges if edge.select] edge_indices = [i.index for i in selected_edges] d = get_intersection_dictionary(bm, edge_indices) unselect_nonintersecting(bm, d.keys(), edge_indices) update_mesh(obj, d) else: print('must be in edit mode') return {'FINISHED'} def menu_func(self, context): self.layout.operator(IntersectAllEdges.bl_idname, text="Intersect selected edges") def register(): bpy.utils.register_module(__name__) bpy.types.VIEW3D_MT_edit_mesh_specials.append(menu_func) def unregister(): bpy.utils.unregister_module(__name__) bpy.types.VIEW3D_MT_edit_mesh_specials.remove(menu_func) if __name__ == "__main__": register()
to join this conversation on GitHub. Already have an account? Sign in to comment