Removed bundled brush pack
[blender-addons.git] / mesh_tiny_cad / XALL.py
bloba240c56f006384cd3e7722d3fa0bddf92ba69d0d
1 # ##### BEGIN GPL LICENSE BLOCK #####
3 # This program is free software; you can redistribute it and/or
4 # modify it under the terms of the GNU General Public License
5 # as published by the Free Software Foundation; either version 2
6 # of the License, or (at your option) any later version.
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
13 # You should have received a copy of the GNU General Public License
14 # along with this program; if not, write to the Free Software Foundation,
15 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 # ##### END GPL LICENSE BLOCK #####
19 # <pep8 compliant>
22 import bpy
23 import bmesh
24 from mathutils.geometry import intersect_line_line as LineIntersect
26 import itertools
27 from collections import defaultdict
28 from . import cad_module as cm
31 def order_points(edge, point_list):
32 ''' order these edges from distance to v1, then
33 sandwich the sorted list with v1, v2 '''
34 v1, v2 = edge
36 def dist(co):
37 return (v1 - co).length
38 point_list = sorted(point_list, key=dist)
39 return [v1] + point_list + [v2]
42 def remove_permutations_that_share_a_vertex(bm, permutations):
43 ''' Get useful Permutations '''
44 final_permutations = []
45 for edges in permutations:
46 raw_vert_indices = cm.vertex_indices_from_edges_tuple(bm, edges)
47 if cm.duplicates(raw_vert_indices):
48 continue
50 # reaches this point if they do not share.
51 final_permutations.append(edges)
53 return final_permutations
56 def get_valid_permutations(bm, edge_indices):
57 raw_permutations = itertools.permutations(edge_indices, 2)
58 permutations = [r for r in raw_permutations if r[0] < r[1]]
59 return remove_permutations_that_share_a_vertex(bm, permutations)
62 def can_skip(closest_points, vert_vectors):
63 '''this checks if the intersection lies on both edges, returns True
64 when criteria are not met, and thus this point can be skipped'''
65 if not closest_points:
66 return True
67 if not isinstance(closest_points[0].x, float):
68 return True
69 if cm.num_edges_point_lies_on(closest_points[0], vert_vectors) < 2:
70 return True
72 # if this distance is larger than than VTX_PRECISION, we can skip it.
73 cpa, cpb = closest_points
74 return (cpa - cpb).length > cm.CAD_prefs.VTX_PRECISION
77 def get_intersection_dictionary(bm, edge_indices):
79 bm.verts.ensure_lookup_table()
80 bm.edges.ensure_lookup_table()
82 permutations = get_valid_permutations(bm, edge_indices)
84 k = defaultdict(list)
85 d = defaultdict(list)
87 for edges in permutations:
88 raw_vert_indices = cm.vertex_indices_from_edges_tuple(bm, edges)
89 vert_vectors = cm.vectors_from_indices(bm, raw_vert_indices)
91 points = LineIntersect(*vert_vectors)
93 # some can be skipped. (NaN, None, not on both edges)
94 if can_skip(points, vert_vectors):
95 continue
97 # reaches this point only when an intersection happens on both edges.
98 [k[edge].append(points[0]) for edge in edges]
100 # k will contain a dict of edge indices and points found on those edges.
101 for edge_idx, unordered_points in k.items():
102 tv1, tv2 = bm.edges[edge_idx].verts
103 v1 = bm.verts[tv1.index].co
104 v2 = bm.verts[tv2.index].co
105 ordered_points = order_points((v1, v2), unordered_points)
106 d[edge_idx].extend(ordered_points)
108 return d
111 def update_mesh(bm, d):
112 ''' Make new geometry (delete old first) '''
114 oe = bm.edges
115 ov = bm.verts
117 new_verts = []
118 collect = new_verts.extend
119 for old_edge, point_list in d.items():
120 num_edges_to_add = len(point_list)-1
121 for i in range(num_edges_to_add):
122 a = ov.new(point_list[i])
123 b = ov.new(point_list[i+1])
124 oe.new((a, b))
125 bm.normal_update()
126 collect([a, b])
128 bmesh.ops.delete(bm, geom=[edge for edge in bm.edges if edge.select], context='EDGES')
130 #bpy.ops.mesh.remove_doubles(
131 # threshold=cm.CAD_prefs.VTX_DOUBLES_THRSHLD,
132 # use_unselected=False)
134 bmesh.ops.remove_doubles(bm, verts=new_verts, dist=cm.CAD_prefs.VTX_DOUBLES_THRSHLD)
137 def unselect_nonintersecting(bm, d_edges, edge_indices):
138 if len(edge_indices) > len(d_edges):
139 reserved_edges = set(edge_indices) - set(d_edges)
140 for edge in reserved_edges:
141 bm.edges[edge].select = False
142 print("unselected {}, non intersecting edges".format(reserved_edges))
145 class TCIntersectAllEdges(bpy.types.Operator):
146 '''Adds a vertex at the intersections of all selected edges'''
147 bl_idname = 'tinycad.intersectall'
148 bl_label = 'XALL intersect all edges'
149 bl_options = {'REGISTER', 'UNDO'}
151 @classmethod
152 def poll(cls, context):
153 obj = context.active_object
154 return obj is not None and obj.type == 'MESH' and obj.mode == 'EDIT'
156 def execute(self, context):
157 # must force edge selection mode here
158 bpy.context.tool_settings.mesh_select_mode = (False, True, False)
160 obj = context.active_object
161 if obj.mode == "EDIT":
162 bm = bmesh.from_edit_mesh(obj.data)
164 selected_edges = [edge for edge in bm.edges if edge.select]
165 edge_indices = [i.index for i in selected_edges]
167 d = get_intersection_dictionary(bm, edge_indices)
169 unselect_nonintersecting(bm, d.keys(), edge_indices)
170 update_mesh(bm, d)
172 bmesh.update_edit_mesh(obj.data)
173 else:
174 print('must be in edit mode')
176 return {'FINISHED'}