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 #####
24 from mathutils
.geometry
import intersect_line_line
as LineIntersect
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 '''
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
):
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
:
67 if not isinstance(closest_points
[0].x
, float):
69 if cm
.num_edges_point_lies_on(closest_points
[0], vert_vectors
) < 2:
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
)
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
):
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
)
111 def update_mesh(bm
, d
):
112 ''' Make new geometry (delete old first) '''
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])
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'}
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
)
172 bmesh
.update_edit_mesh(obj
.data
)
174 print('must be in edit mode')