1 # SPDX-FileCopyrightText: 2016-2022 Blender Foundation
3 # SPDX-License-Identifier: GPL-2.0-or-later
8 from mathutils
.geometry
import intersect_line_line
as LineIntersect
11 from collections
import defaultdict
12 from . import cad_module
as cm
15 def order_points(edge
, point_list
):
16 ''' order these edges from distance to v1, then
17 sandwich the sorted list with v1, v2 '''
21 return (v1
- co
).length
22 point_list
= sorted(point_list
, key
=dist
)
23 return [v1
] + point_list
+ [v2
]
26 def remove_permutations_that_share_a_vertex(bm
, permutations
):
27 ''' Get useful Permutations '''
28 final_permutations
= []
29 for edges
in permutations
:
30 raw_vert_indices
= cm
.vertex_indices_from_edges_tuple(bm
, edges
)
31 if cm
.duplicates(raw_vert_indices
):
34 # reaches this point if they do not share.
35 final_permutations
.append(edges
)
37 return final_permutations
40 def get_valid_permutations(bm
, edge_indices
):
41 raw_permutations
= itertools
.permutations(edge_indices
, 2)
42 permutations
= [r
for r
in raw_permutations
if r
[0] < r
[1]]
43 return remove_permutations_that_share_a_vertex(bm
, permutations
)
46 def can_skip(closest_points
, vert_vectors
):
47 '''this checks if the intersection lies on both edges, returns True
48 when criteria are not met, and thus this point can be skipped'''
49 if not closest_points
:
51 if not isinstance(closest_points
[0].x
, float):
53 if cm
.num_edges_point_lies_on(closest_points
[0], vert_vectors
) < 2:
56 # if this distance is larger than than VTX_PRECISION, we can skip it.
57 cpa
, cpb
= closest_points
58 return (cpa
- cpb
).length
> cm
.CAD_prefs
.VTX_PRECISION
61 def get_intersection_dictionary(bm
, edge_indices
):
63 bm
.verts
.ensure_lookup_table()
64 bm
.edges
.ensure_lookup_table()
66 permutations
= get_valid_permutations(bm
, edge_indices
)
71 for edges
in permutations
:
72 raw_vert_indices
= cm
.vertex_indices_from_edges_tuple(bm
, edges
)
73 vert_vectors
= cm
.vectors_from_indices(bm
, raw_vert_indices
)
75 points
= LineIntersect(*vert_vectors
)
77 # some can be skipped. (NaN, None, not on both edges)
78 if can_skip(points
, vert_vectors
):
81 # reaches this point only when an intersection happens on both edges.
82 [k
[edge
].append(points
[0]) for edge
in edges
]
84 # k will contain a dict of edge indices and points found on those edges.
85 for edge_idx
, unordered_points
in k
.items():
86 tv1
, tv2
= bm
.edges
[edge_idx
].verts
87 v1
= bm
.verts
[tv1
.index
].co
88 v2
= bm
.verts
[tv2
.index
].co
89 ordered_points
= order_points((v1
, v2
), unordered_points
)
90 d
[edge_idx
].extend(ordered_points
)
95 def update_mesh(bm
, d
):
96 ''' Make new geometry (delete old first) '''
102 collect
= new_verts
.extend
103 for old_edge
, point_list
in d
.items():
104 num_edges_to_add
= len(point_list
)-1
105 for i
in range(num_edges_to_add
):
106 a
= ov
.new(point_list
[i
])
107 b
= ov
.new(point_list
[i
+1])
112 bmesh
.ops
.delete(bm
, geom
=[edge
for edge
in bm
.edges
if edge
.select
], context
='EDGES')
114 #bpy.ops.mesh.remove_doubles(
115 # threshold=cm.CAD_prefs.VTX_DOUBLES_THRSHLD,
116 # use_unselected=False)
118 bmesh
.ops
.remove_doubles(bm
, verts
=new_verts
, dist
=cm
.CAD_prefs
.VTX_DOUBLES_THRSHLD
)
121 def unselect_nonintersecting(bm
, d_edges
, edge_indices
):
122 if len(edge_indices
) > len(d_edges
):
123 reserved_edges
= set(edge_indices
) - set(d_edges
)
124 for edge
in reserved_edges
:
125 bm
.edges
[edge
].select
= False
126 print("unselected {}, non intersecting edges".format(reserved_edges
))
129 class TCIntersectAllEdges(bpy
.types
.Operator
):
130 '''Adds a vertex at the intersections of all selected edges'''
131 bl_idname
= 'tinycad.intersectall'
132 bl_label
= 'XALL intersect all edges'
133 bl_options
= {'REGISTER', 'UNDO'}
136 def poll(cls
, context
):
137 obj
= context
.active_object
138 return obj
is not None and obj
.type == 'MESH' and obj
.mode
== 'EDIT'
140 def execute(self
, context
):
141 # must force edge selection mode here
142 bpy
.context
.tool_settings
.mesh_select_mode
= (False, True, False)
144 obj
= context
.active_object
145 if obj
.mode
== "EDIT":
146 bm
= bmesh
.from_edit_mesh(obj
.data
)
148 selected_edges
= [edge
for edge
in bm
.edges
if edge
.select
]
149 edge_indices
= [i
.index
for i
in selected_edges
]
151 d
= get_intersection_dictionary(bm
, edge_indices
)
153 unselect_nonintersecting(bm
, d
.keys(), edge_indices
)
156 bmesh
.update_edit_mesh(obj
.data
)
158 print('must be in edit mode')