1 # SPDX-License-Identifier: GPL-2.0-or-later
5 # ----------------------------------------------------------
7 # Modified by: Alan Odom (Clockmender) & Rune Morling (ermo)
8 # ----------------------------------------------------------
12 from mathutils
.geometry
import intersect_line_line
as LineIntersect
14 from collections
import defaultdict
15 from . import pdt_cad_module
as cm
16 from .pdt_functions
import oops
17 from .pdt_msg_strings
import (
22 def order_points(edge
, point_list
):
23 """Order these edges from distance to v1, then sandwich the sorted list with v1, v2."""
27 """Measure distance between two coordinates."""
29 return (v1
- coord
).length
31 point_list
= sorted(point_list
, key
=dist
)
32 return [v1
] + point_list
+ [v2
]
35 def remove_permutations_that_share_a_vertex(bm
, permutations
):
36 """Get useful Permutations.
40 permutations: Possible Intersection Edges as a list
46 final_permutations
= []
47 for edges
in permutations
:
48 raw_vert_indices
= cm
.vertex_indices_from_edges_tuple(bm
, edges
)
49 if len(set(raw_vert_indices
)) < 4:
52 # reaches this point if they do not share.
53 final_permutations
.append(edges
)
55 return final_permutations
58 def get_valid_permutations(bm
, edge_indices
):
59 """Get useful Permutations.
63 edge_indices: List of indices of Edges to consider
66 List of suitable Edges.
69 raw_permutations
= itertools
.permutations(edge_indices
, 2)
70 permutations
= [r
for r
in raw_permutations
if r
[0] < r
[1]]
71 return remove_permutations_that_share_a_vertex(bm
, permutations
)
74 def can_skip(closest_points
, vert_vectors
):
75 """Check if the intersection lies on both edges and return True
76 when criteria are not met, and thus this point can be skipped.
79 closest_points: List of Coordinates of points to consider
80 vert_vectors: List of Coordinates of vertices to consider
86 if not closest_points
:
88 if not isinstance(closest_points
[0].x
, float):
90 if cm
.num_edges_point_lies_on(closest_points
[0], vert_vectors
) < 2:
93 # if this distance is larger than than 1.0e-5, we can skip it.
94 cpa
, cpb
= closest_points
95 return (cpa
- cpb
).length
> 1.0e-5
98 def get_intersection_dictionary(bm
, edge_indices
):
99 """Return a dictionary of edge indices and points found on those edges.
103 edge_indices: List of Edge Indices
106 Dictionary of Vectors.
109 bm
.verts
.ensure_lookup_table()
110 bm
.edges
.ensure_lookup_table()
112 permutations
= get_valid_permutations(bm
, edge_indices
)
114 list_k
= defaultdict(list)
115 list_d
= defaultdict(list)
117 for edges
in permutations
:
118 raw_vert_indices
= cm
.vertex_indices_from_edges_tuple(bm
, edges
)
119 vert_vectors
= cm
.vectors_from_indices(bm
, raw_vert_indices
)
121 points
= LineIntersect(*vert_vectors
)
123 # some can be skipped. (NaN, None, not on both edges)
124 if can_skip(points
, vert_vectors
):
127 # reaches this point only when an intersection happens on both edges.
128 [list_k
[edge
].append(points
[0]) for edge
in edges
]
130 # list_k will contain a dict of edge indices and points found on those edges.
131 for edge_idx
, unordered_points
in list_k
.items():
132 tv1
, tv2
= bm
.edges
[edge_idx
].verts
133 v1
= bm
.verts
[tv1
.index
].co
134 v2
= bm
.verts
[tv2
.index
].co
135 ordered_points
= order_points((v1
, v2
), unordered_points
)
136 list_d
[edge_idx
].extend(ordered_points
)
141 def update_mesh(bm
, int_dict
):
142 """Make new geometry (delete old first).
146 int_dict: Dictionary of Indices of Vertices
156 collect
= new_verts
.extend
157 for _
, point_list
in int_dict
.items():
158 num_edges_to_add
= len(point_list
) - 1
159 for i
in range(num_edges_to_add
):
160 coord_a
= orig_v
.new(point_list
[i
])
161 coord_b
= orig_v
.new(point_list
[i
+ 1])
162 orig_e
.new((coord_a
, coord_b
))
164 collect([coord_a
, coord_b
])
166 bmesh
.ops
.delete(bm
, geom
=[edge
for edge
in bm
.edges
if edge
.select
], context
="EDGES")
167 bmesh
.ops
.remove_doubles(bm
, verts
=bm
.verts
, dist
=0.0001)
170 def unselect_nonintersecting(bm
, d_edges
, edge_indices
):
171 """Deselects Non-Intersection Edges.
175 d_edges: List of Intersecting Edges
176 edge_indices: List of Edge Indices to consider
182 if len(edge_indices
) > len(d_edges
):
183 reserved_edges
= set(edge_indices
) - set(d_edges
)
184 for edge
in reserved_edges
:
185 bm
.edges
[edge
].select
= False
188 def intersect_all(context
):
189 """Computes All intersections with Crossing Geometry.
192 Deletes original edges and replaces with new intersected edges
195 context: Blender bpy.context instance.
201 pg
= context
.scene
.pdt_pg
202 obj
= context
.active_object
203 if all([bool(obj
), obj
.type == "MESH", obj
.mode
== "EDIT"]):
204 # must force edge selection mode here
205 bpy
.context
.tool_settings
.mesh_select_mode
= (False, True, False)
208 if obj
.mode
== "EDIT":
209 bm
= bmesh
.from_edit_mesh(obj
.data
)
211 selected_edges
= [edge
for edge
in bm
.edges
if edge
.select
]
212 edge_indices
= [i
.index
for i
in selected_edges
]
214 int_dict
= get_intersection_dictionary(bm
, edge_indices
)
216 unselect_nonintersecting(bm
, int_dict
.keys(), edge_indices
)
217 update_mesh(bm
, int_dict
)
219 bmesh
.update_edit_mesh(obj
.data
)
221 pg
.error
= f
"{PDT_ERR_EDOB_MODE},{obj.mode})"
222 context
.window_manager
.popup_menu(oops
, title
="Error", icon
="ERROR")
227 pg
.error
= f
"{PDT_ERR_EDOB_MODE},{obj.mode})"
228 context
.window_manager
.popup_menu(oops
, title
="Error", icon
="ERROR")
231 class PDT_OT_IntersectAllEdges(bpy
.types
.Operator
):
232 """Cut Selected Edges at All Intersections"""
234 bl_idname
= "pdt.intersectall"
235 bl_label
= "Intersect All Edges"
236 bl_options
= {"REGISTER", "UNDO"}
239 def poll(cls
, context
):
240 """Check to see object is in correct condition.
243 context: Blender bpy.context instance.
248 obj
= context
.active_object
251 return obj
is not None and obj
.type == "MESH" and obj
.mode
== "EDIT"
253 def execute(self
, context
):
254 """Computes All intersections with Crossing Geometry.
257 Deletes original edges and replaces with new intersected edges
260 context: Blender bpy.context instance.
266 pg
= context
.scene
.pdt_pg
267 pg
.command
= f
"intall"