Cleanup: Node Wrangler: preview_node operator
[blender-addons.git] / mesh_tiny_cad / XALL.py
blob403a25353233278675ac30d1fc1f8f2609f91e69
1 # SPDX-FileCopyrightText: 2016-2022 Blender Foundation
3 # SPDX-License-Identifier: GPL-2.0-or-later
6 import bpy
7 import bmesh
8 from mathutils.geometry import intersect_line_line as LineIntersect
10 import itertools
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 '''
18 v1, v2 = edge
20 def dist(co):
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):
32 continue
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:
50 return True
51 if not isinstance(closest_points[0].x, float):
52 return True
53 if cm.num_edges_point_lies_on(closest_points[0], vert_vectors) < 2:
54 return True
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)
68 k = defaultdict(list)
69 d = defaultdict(list)
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):
79 continue
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)
92 return d
95 def update_mesh(bm, d):
96 ''' Make new geometry (delete old first) '''
98 oe = bm.edges
99 ov = bm.verts
101 new_verts = []
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])
108 oe.new((a, b))
109 bm.normal_update()
110 collect([a, b])
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'}
135 @classmethod
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)
154 update_mesh(bm, d)
156 bmesh.update_edit_mesh(obj.data)
157 else:
158 print('must be in edit mode')
160 return {'FINISHED'}