1 # SPDX-FileCopyrightText: 2016-2022 Blender Foundation
3 # SPDX-License-Identifier: GPL-2.0-or-later
10 from . import cad_module
as cm
13 'SHARED_VERTEX': 'Shared Vertex, no intersection possible',
14 'PARALLEL_EDGES': 'Edges Parallel, no intersection possible',
15 'NON_PLANAR_EDGES': 'Non Planar Edges, no clean intersection point'
19 def add_edges(bm
, pt
, idxs
, fdp
):
21 this function is a disaster --
22 index updates and ensure_lookup_table() are called before this function
23 and after, and i've tried doing this less verbose but results tend to be
24 less predictable. I'm obviously a terrible coder, but can only spend so
25 much time figuring out this stuff.
30 bm
.verts
.ensure_lookup_table()
31 bm
.edges
.ensure_lookup_table()
32 bm
.verts
.index_update()
36 bm
.edges
.index_update()
38 bm
.edges
.new((v1
, v2
))
40 bm
.edges
.index_update()
41 bm
.verts
.ensure_lookup_table()
42 bm
.edges
.ensure_lookup_table()
44 except Exception as err
:
45 print('some failure: details')
49 sys
.stderr
.write('ERROR: %s\n' % str(err
))
50 print(sys
.exc_info()[-1].tb_frame
.f_code
)
51 print('Error on line {}'.format(sys
.exc_info()[-1].tb_lineno
))
54 def remove_earmarked_edges(bm
, earmarked
):
55 edges_select
= [e
for e
in bm
.edges
if e
.index
in earmarked
]
56 bmesh
.ops
.delete(bm
, geom
=edges_select
, context
='EDGES')
59 def perform_vtx(bm
, pt
, edges
, pts
, vertex_indices
):
60 idx1
, idx2
= edges
[0].index
, edges
[1].index
61 fdp
= pt
, edges
, pts
, vertex_indices
63 # this list will hold those edges that pt lies on
64 edges_indices
= cm
.find_intersecting_edges(bm
, pt
, idx1
, idx2
)
65 mode
= 'VTX'[len(edges_indices
)]
68 cl_vert1
= cm
.closest_idx(pt
, edges
[0])
69 cl_vert2
= cm
.closest_idx(pt
, edges
[1])
70 add_edges(bm
, pt
, [cl_vert1
, cl_vert2
], fdp
)
73 to_edge_idx
= edges_indices
[0]
74 from_edge_idx
= idx1
if to_edge_idx
== idx2
else idx2
76 cl_vert
= cm
.closest_idx(pt
, bm
.edges
[from_edge_idx
])
77 to_vert1
, to_vert2
= cm
.vert_idxs_from_edge_idx(bm
, to_edge_idx
)
78 add_edges(bm
, pt
, [cl_vert
, to_vert1
, to_vert2
], fdp
)
81 add_edges(bm
, pt
, vertex_indices
, fdp
)
83 # final refresh before returning to user.
85 remove_earmarked_edges(bm
, edges_indices
)
87 bm
.edges
.index_update()
91 def do_vtx_if_appropriate(bm
, edges
):
92 vertex_indices
= cm
.get_vert_indices_from_bmedges(edges
)
94 # test 1, are there shared vers? if so return non-viable
95 if not len(set(vertex_indices
)) == 4:
96 return {'SHARED_VERTEX'}
98 # test 2, is parallel?
99 p1
, p2
, p3
, p4
= [bm
.verts
[i
].co
for i
in vertex_indices
]
100 point
= cm
.get_intersection([p1
, p2
], [p3
, p4
])
102 return {'PARALLEL_EDGES'}
104 # test 3, coplanar edges?
105 coplanar
= cm
.test_coplanar([p1
, p2
], [p3
, p4
])
107 return {'NON_PLANAR_EDGES'}
109 # point must lie on an edge or the virtual extension of an edge
110 bm
= perform_vtx(bm
, point
, edges
, (p1
, p2
, p3
, p4
), vertex_indices
)
114 class TCAutoVTX(bpy
.types
.Operator
):
115 '''Weld intersecting edges, project converging edges towards their intersection'''
116 bl_idname
= 'tinycad.autovtx'
117 bl_label
= 'VTX autoVTX'
120 def poll(cls
, context
):
121 obj
= context
.active_object
122 return bool(obj
) and obj
.type == 'MESH'
124 def cancel_message(self
, msg
):
126 self
.report({"WARNING"}, msg
)
129 def execute(self
, context
):
131 # final attempt to enter unfragmented bm/mesh
132 # ghastly, but what can I do? it works with these
134 bpy
.ops
.object.mode_set(mode
='OBJECT')
135 bpy
.ops
.object.mode_set(mode
='EDIT')
137 obj
= context
.active_object
140 bm
= bmesh
.from_edit_mesh(me
)
141 bm
.verts
.ensure_lookup_table()
142 bm
.edges
.ensure_lookup_table()
144 edges
= [e
for e
in bm
.edges
if e
.select
and not e
.hide
]
147 message
= do_vtx_if_appropriate(bm
, edges
)
148 if isinstance(message
, set):
149 msg
= messages
.get(message
.pop())
150 return self
.cancel_message(msg
)
153 return self
.cancel_message('select two edges!')
155 bm
.verts
.index_update()
156 bm
.edges
.index_update()
157 bmesh
.update_edit_mesh(me
, loop_triangles
=True)