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 #####
21 # ----------------------------------------------------------
23 # Modified by: Alan Odom (Clockmender) & Rune Morling (ermo)
24 # ----------------------------------------------------------
28 from mathutils
.geometry
import intersect_line_line
as LineIntersect
30 from collections
import defaultdict
31 from . import pdt_cad_module
as cm
32 from .pdt_functions
import oops
33 from .pdt_msg_strings
import (
38 def order_points(edge
, point_list
):
39 """Order these edges from distance to v1, then sandwich the sorted list with v1, v2."""
43 """Measure distance between two coordinates."""
45 return (v1
- coord
).length
47 point_list
= sorted(point_list
, key
=dist
)
48 return [v1
] + point_list
+ [v2
]
51 def remove_permutations_that_share_a_vertex(bm
, permutations
):
52 """Get useful Permutations.
56 permutations: Possible Intersection Edges as a list
62 final_permutations
= []
63 for edges
in permutations
:
64 raw_vert_indices
= cm
.vertex_indices_from_edges_tuple(bm
, edges
)
65 if len(set(raw_vert_indices
)) < 4:
68 # reaches this point if they do not share.
69 final_permutations
.append(edges
)
71 return final_permutations
74 def get_valid_permutations(bm
, edge_indices
):
75 """Get useful Permutations.
79 edge_indices: List of indices of Edges to consider
82 List of suitable Edges.
85 raw_permutations
= itertools
.permutations(edge_indices
, 2)
86 permutations
= [r
for r
in raw_permutations
if r
[0] < r
[1]]
87 return remove_permutations_that_share_a_vertex(bm
, permutations
)
90 def can_skip(closest_points
, vert_vectors
):
91 """Check if the intersection lies on both edges and return True
92 when criteria are not met, and thus this point can be skipped.
95 closest_points: List of Coordinates of points to consider
96 vert_vectors: List of Coordinates of vertices to consider
102 if not closest_points
:
104 if not isinstance(closest_points
[0].x
, float):
106 if cm
.num_edges_point_lies_on(closest_points
[0], vert_vectors
) < 2:
109 # if this distance is larger than than 1.0e-5, we can skip it.
110 cpa
, cpb
= closest_points
111 return (cpa
- cpb
).length
> 1.0e-5
114 def get_intersection_dictionary(bm
, edge_indices
):
115 """Return a dictionary of edge indices and points found on those edges.
119 edge_indices: List of Edge Indices
122 Dictionary of Vectors.
125 bm
.verts
.ensure_lookup_table()
126 bm
.edges
.ensure_lookup_table()
128 permutations
= get_valid_permutations(bm
, edge_indices
)
130 list_k
= defaultdict(list)
131 list_d
= defaultdict(list)
133 for edges
in permutations
:
134 raw_vert_indices
= cm
.vertex_indices_from_edges_tuple(bm
, edges
)
135 vert_vectors
= cm
.vectors_from_indices(bm
, raw_vert_indices
)
137 points
= LineIntersect(*vert_vectors
)
139 # some can be skipped. (NaN, None, not on both edges)
140 if can_skip(points
, vert_vectors
):
143 # reaches this point only when an intersection happens on both edges.
144 [list_k
[edge
].append(points
[0]) for edge
in edges
]
146 # list_k will contain a dict of edge indices and points found on those edges.
147 for edge_idx
, unordered_points
in list_k
.items():
148 tv1
, tv2
= bm
.edges
[edge_idx
].verts
149 v1
= bm
.verts
[tv1
.index
].co
150 v2
= bm
.verts
[tv2
.index
].co
151 ordered_points
= order_points((v1
, v2
), unordered_points
)
152 list_d
[edge_idx
].extend(ordered_points
)
157 def update_mesh(bm
, int_dict
):
158 """Make new geometry (delete old first).
162 int_dict: Dictionary of Indices of Vertices
172 collect
= new_verts
.extend
173 for _
, point_list
in int_dict
.items():
174 num_edges_to_add
= len(point_list
) - 1
175 for i
in range(num_edges_to_add
):
176 coord_a
= orig_v
.new(point_list
[i
])
177 coord_b
= orig_v
.new(point_list
[i
+ 1])
178 orig_e
.new((coord_a
, coord_b
))
180 collect([coord_a
, coord_b
])
182 bmesh
.ops
.delete(bm
, geom
=[edge
for edge
in bm
.edges
if edge
.select
], context
="EDGES")
183 bmesh
.ops
.remove_doubles(bm
, verts
=bm
.verts
, dist
=0.0001)
186 def unselect_nonintersecting(bm
, d_edges
, edge_indices
):
187 """Deselects Non-Intersection Edges.
191 d_edges: List of Intersecting Edges
192 edge_indices: List of Edge Indices to consider
198 if len(edge_indices
) > len(d_edges
):
199 reserved_edges
= set(edge_indices
) - set(d_edges
)
200 for edge
in reserved_edges
:
201 bm
.edges
[edge
].select
= False
204 def intersect_all(context
):
205 """Computes All intersections with Crossing Geometry.
208 Deletes original edges and replaces with new intersected edges
211 context: Blender bpy.context instance.
217 pg
= context
.scene
.pdt_pg
218 obj
= context
.active_object
219 if all([bool(obj
), obj
.type == "MESH", obj
.mode
== "EDIT"]):
220 # must force edge selection mode here
221 bpy
.context
.tool_settings
.mesh_select_mode
= (False, True, False)
224 if obj
.mode
== "EDIT":
225 bm
= bmesh
.from_edit_mesh(obj
.data
)
227 selected_edges
= [edge
for edge
in bm
.edges
if edge
.select
]
228 edge_indices
= [i
.index
for i
in selected_edges
]
230 int_dict
= get_intersection_dictionary(bm
, edge_indices
)
232 unselect_nonintersecting(bm
, int_dict
.keys(), edge_indices
)
233 update_mesh(bm
, int_dict
)
235 bmesh
.update_edit_mesh(obj
.data
)
237 pg
.error
= f
"{PDT_ERR_EDOB_MODE},{obj.mode})"
238 context
.window_manager
.popup_menu(oops
, title
="Error", icon
="ERROR")
243 pg
.error
= f
"{PDT_ERR_EDOB_MODE},{obj.mode})"
244 context
.window_manager
.popup_menu(oops
, title
="Error", icon
="ERROR")
247 class PDT_OT_IntersectAllEdges(bpy
.types
.Operator
):
248 """Cut Selected Edges at All Intersections"""
250 bl_idname
= "pdt.intersectall"
251 bl_label
= "Intersect All Edges"
252 bl_options
= {"REGISTER", "UNDO"}
255 def poll(cls
, context
):
256 """Check to see object is in correct condition.
259 context: Blender bpy.context instance.
264 obj
= context
.active_object
267 return obj
is not None and obj
.type == "MESH" and obj
.mode
== "EDIT"
269 def execute(self
, context
):
270 """Computes All intersections with Crossing Geometry.
273 Deletes original edges and replaces with new intersected edges
276 context: Blender bpy.context instance.
282 pg
= context
.scene
.pdt_pg
283 pg
.command
= f
"intall"