1 # SPDX-FileCopyrightText: 2016-2022 Blender Foundation
3 # SPDX-License-Identifier: GPL-2.0-or-later
6 # ----------------------------------------------------------
8 # Modified by: Alan Odom (Clockmender) & Rune Morling (ermo)
9 # ----------------------------------------------------------
13 from mathutils
.geometry
import intersect_line_line
as LineIntersect
15 from collections
import defaultdict
16 from . import pdt_cad_module
as cm
17 from .pdt_functions
import oops
18 from .pdt_msg_strings
import (
23 def order_points(edge
, point_list
):
24 """Order these edges from distance to v1, then sandwich the sorted list with v1, v2."""
28 """Measure distance between two coordinates."""
30 return (v1
- coord
).length
32 point_list
= sorted(point_list
, key
=dist
)
33 return [v1
] + point_list
+ [v2
]
36 def remove_permutations_that_share_a_vertex(bm
, permutations
):
37 """Get useful Permutations.
41 permutations: Possible Intersection Edges as a list
47 final_permutations
= []
48 for edges
in permutations
:
49 raw_vert_indices
= cm
.vertex_indices_from_edges_tuple(bm
, edges
)
50 if len(set(raw_vert_indices
)) < 4:
53 # reaches this point if they do not share.
54 final_permutations
.append(edges
)
56 return final_permutations
59 def get_valid_permutations(bm
, edge_indices
):
60 """Get useful Permutations.
64 edge_indices: List of indices of Edges to consider
67 List of suitable Edges.
70 raw_permutations
= itertools
.permutations(edge_indices
, 2)
71 permutations
= [r
for r
in raw_permutations
if r
[0] < r
[1]]
72 return remove_permutations_that_share_a_vertex(bm
, permutations
)
75 def can_skip(closest_points
, vert_vectors
):
76 """Check if the intersection lies on both edges and return True
77 when criteria are not met, and thus this point can be skipped.
80 closest_points: List of Coordinates of points to consider
81 vert_vectors: List of Coordinates of vertices to consider
87 if not closest_points
:
89 if not isinstance(closest_points
[0].x
, float):
91 if cm
.num_edges_point_lies_on(closest_points
[0], vert_vectors
) < 2:
94 # if this distance is larger than than 1.0e-5, we can skip it.
95 cpa
, cpb
= closest_points
96 return (cpa
- cpb
).length
> 1.0e-5
99 def get_intersection_dictionary(bm
, edge_indices
):
100 """Return a dictionary of edge indices and points found on those edges.
104 edge_indices: List of Edge Indices
107 Dictionary of Vectors.
110 bm
.verts
.ensure_lookup_table()
111 bm
.edges
.ensure_lookup_table()
113 permutations
= get_valid_permutations(bm
, edge_indices
)
115 list_k
= defaultdict(list)
116 list_d
= defaultdict(list)
118 for edges
in permutations
:
119 raw_vert_indices
= cm
.vertex_indices_from_edges_tuple(bm
, edges
)
120 vert_vectors
= cm
.vectors_from_indices(bm
, raw_vert_indices
)
122 points
= LineIntersect(*vert_vectors
)
124 # some can be skipped. (NaN, None, not on both edges)
125 if can_skip(points
, vert_vectors
):
128 # reaches this point only when an intersection happens on both edges.
129 [list_k
[edge
].append(points
[0]) for edge
in edges
]
131 # list_k will contain a dict of edge indices and points found on those edges.
132 for edge_idx
, unordered_points
in list_k
.items():
133 tv1
, tv2
= bm
.edges
[edge_idx
].verts
134 v1
= bm
.verts
[tv1
.index
].co
135 v2
= bm
.verts
[tv2
.index
].co
136 ordered_points
= order_points((v1
, v2
), unordered_points
)
137 list_d
[edge_idx
].extend(ordered_points
)
142 def update_mesh(bm
, int_dict
):
143 """Make new geometry (delete old first).
147 int_dict: Dictionary of Indices of Vertices
157 collect
= new_verts
.extend
158 for _
, point_list
in int_dict
.items():
159 num_edges_to_add
= len(point_list
) - 1
160 for i
in range(num_edges_to_add
):
161 coord_a
= orig_v
.new(point_list
[i
])
162 coord_b
= orig_v
.new(point_list
[i
+ 1])
163 orig_e
.new((coord_a
, coord_b
))
165 collect([coord_a
, coord_b
])
167 bmesh
.ops
.delete(bm
, geom
=[edge
for edge
in bm
.edges
if edge
.select
], context
="EDGES")
168 bmesh
.ops
.remove_doubles(bm
, verts
=bm
.verts
, dist
=0.0001)
171 def unselect_nonintersecting(bm
, d_edges
, edge_indices
):
172 """Deselects Non-Intersection Edges.
176 d_edges: List of Intersecting Edges
177 edge_indices: List of Edge Indices to consider
183 if len(edge_indices
) > len(d_edges
):
184 reserved_edges
= set(edge_indices
) - set(d_edges
)
185 for edge
in reserved_edges
:
186 bm
.edges
[edge
].select
= False
189 def intersect_all(context
):
190 """Computes All intersections with Crossing Geometry.
193 Deletes original edges and replaces with new intersected edges
196 context: Blender bpy.context instance.
202 pg
= context
.scene
.pdt_pg
203 obj
= context
.active_object
204 if all([bool(obj
), obj
.type == "MESH", obj
.mode
== "EDIT"]):
205 # must force edge selection mode here
206 bpy
.context
.tool_settings
.mesh_select_mode
= (False, True, False)
209 if obj
.mode
== "EDIT":
210 bm
= bmesh
.from_edit_mesh(obj
.data
)
212 selected_edges
= [edge
for edge
in bm
.edges
if edge
.select
]
213 edge_indices
= [i
.index
for i
in selected_edges
]
215 int_dict
= get_intersection_dictionary(bm
, edge_indices
)
217 unselect_nonintersecting(bm
, int_dict
.keys(), edge_indices
)
218 update_mesh(bm
, int_dict
)
220 bmesh
.update_edit_mesh(obj
.data
)
222 pg
.error
= f
"{PDT_ERR_EDOB_MODE},{obj.mode})"
223 context
.window_manager
.popup_menu(oops
, title
="Error", icon
="ERROR")
228 pg
.error
= f
"{PDT_ERR_EDOB_MODE},{obj.mode})"
229 context
.window_manager
.popup_menu(oops
, title
="Error", icon
="ERROR")
232 class PDT_OT_IntersectAllEdges(bpy
.types
.Operator
):
233 """Cut Selected Edges at All Intersections"""
235 bl_idname
= "pdt.intersectall"
236 bl_label
= "Intersect All Edges"
237 bl_options
= {"REGISTER", "UNDO"}
240 def poll(cls
, context
):
241 """Check to see object is in correct condition.
244 context: Blender bpy.context instance.
249 obj
= context
.active_object
252 return obj
is not None and obj
.type == "MESH" and obj
.mode
== "EDIT"
254 def execute(self
, context
):
255 """Computes All intersections with Crossing Geometry.
258 Deletes original edges and replaces with new intersected edges
261 context: Blender bpy.context instance.
267 pg
= context
.scene
.pdt_pg
268 pg
.command
= f
"intall"