1 # SPDX-License-Identifier: GPL-2.0-or-later
6 from mathutils
.geometry
import intersect_line_line
as LineIntersect
9 from collections
import defaultdict
10 from . import cad_module
as cm
13 def order_points(edge
, point_list
):
14 ''' order these edges from distance to v1, then
15 sandwich the sorted list with v1, v2 '''
19 return (v1
- co
).length
20 point_list
= sorted(point_list
, key
=dist
)
21 return [v1
] + point_list
+ [v2
]
24 def remove_permutations_that_share_a_vertex(bm
, permutations
):
25 ''' Get useful Permutations '''
26 final_permutations
= []
27 for edges
in permutations
:
28 raw_vert_indices
= cm
.vertex_indices_from_edges_tuple(bm
, edges
)
29 if cm
.duplicates(raw_vert_indices
):
32 # reaches this point if they do not share.
33 final_permutations
.append(edges
)
35 return final_permutations
38 def get_valid_permutations(bm
, edge_indices
):
39 raw_permutations
= itertools
.permutations(edge_indices
, 2)
40 permutations
= [r
for r
in raw_permutations
if r
[0] < r
[1]]
41 return remove_permutations_that_share_a_vertex(bm
, permutations
)
44 def can_skip(closest_points
, vert_vectors
):
45 '''this checks if the intersection lies on both edges, returns True
46 when criteria are not met, and thus this point can be skipped'''
47 if not closest_points
:
49 if not isinstance(closest_points
[0].x
, float):
51 if cm
.num_edges_point_lies_on(closest_points
[0], vert_vectors
) < 2:
54 # if this distance is larger than than VTX_PRECISION, we can skip it.
55 cpa
, cpb
= closest_points
56 return (cpa
- cpb
).length
> cm
.CAD_prefs
.VTX_PRECISION
59 def get_intersection_dictionary(bm
, edge_indices
):
61 bm
.verts
.ensure_lookup_table()
62 bm
.edges
.ensure_lookup_table()
64 permutations
= get_valid_permutations(bm
, edge_indices
)
69 for edges
in permutations
:
70 raw_vert_indices
= cm
.vertex_indices_from_edges_tuple(bm
, edges
)
71 vert_vectors
= cm
.vectors_from_indices(bm
, raw_vert_indices
)
73 points
= LineIntersect(*vert_vectors
)
75 # some can be skipped. (NaN, None, not on both edges)
76 if can_skip(points
, vert_vectors
):
79 # reaches this point only when an intersection happens on both edges.
80 [k
[edge
].append(points
[0]) for edge
in edges
]
82 # k will contain a dict of edge indices and points found on those edges.
83 for edge_idx
, unordered_points
in k
.items():
84 tv1
, tv2
= bm
.edges
[edge_idx
].verts
85 v1
= bm
.verts
[tv1
.index
].co
86 v2
= bm
.verts
[tv2
.index
].co
87 ordered_points
= order_points((v1
, v2
), unordered_points
)
88 d
[edge_idx
].extend(ordered_points
)
93 def update_mesh(bm
, d
):
94 ''' Make new geometry (delete old first) '''
100 collect
= new_verts
.extend
101 for old_edge
, point_list
in d
.items():
102 num_edges_to_add
= len(point_list
)-1
103 for i
in range(num_edges_to_add
):
104 a
= ov
.new(point_list
[i
])
105 b
= ov
.new(point_list
[i
+1])
110 bmesh
.ops
.delete(bm
, geom
=[edge
for edge
in bm
.edges
if edge
.select
], context
='EDGES')
112 #bpy.ops.mesh.remove_doubles(
113 # threshold=cm.CAD_prefs.VTX_DOUBLES_THRSHLD,
114 # use_unselected=False)
116 bmesh
.ops
.remove_doubles(bm
, verts
=new_verts
, dist
=cm
.CAD_prefs
.VTX_DOUBLES_THRSHLD
)
119 def unselect_nonintersecting(bm
, d_edges
, edge_indices
):
120 if len(edge_indices
) > len(d_edges
):
121 reserved_edges
= set(edge_indices
) - set(d_edges
)
122 for edge
in reserved_edges
:
123 bm
.edges
[edge
].select
= False
124 print("unselected {}, non intersecting edges".format(reserved_edges
))
127 class TCIntersectAllEdges(bpy
.types
.Operator
):
128 '''Adds a vertex at the intersections of all selected edges'''
129 bl_idname
= 'tinycad.intersectall'
130 bl_label
= 'XALL intersect all edges'
131 bl_options
= {'REGISTER', 'UNDO'}
134 def poll(cls
, context
):
135 obj
= context
.active_object
136 return obj
is not None and obj
.type == 'MESH' and obj
.mode
== 'EDIT'
138 def execute(self
, context
):
139 # must force edge selection mode here
140 bpy
.context
.tool_settings
.mesh_select_mode
= (False, True, False)
142 obj
= context
.active_object
143 if obj
.mode
== "EDIT":
144 bm
= bmesh
.from_edit_mesh(obj
.data
)
146 selected_edges
= [edge
for edge
in bm
.edges
if edge
.select
]
147 edge_indices
= [i
.index
for i
in selected_edges
]
149 d
= get_intersection_dictionary(bm
, edge_indices
)
151 unselect_nonintersecting(bm
, d
.keys(), edge_indices
)
154 bmesh
.update_edit_mesh(obj
.data
)
156 print('must be in edit mode')