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 3
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, see <http://www.gnu.org/licenses/>.
16 # ##### END GPL LICENSE BLOCK #####
21 from mathutils
import Vector
22 from mathutils
.geometry
import intersect_point_line
24 from .drawing_utilities
import SnapDrawn
25 from .common_utilities
import snap_utilities
26 from .common_classes
import (
35 __package__
= "mesh_snap_utilities_line"
38 def get_closest_edge(bm
, point
, dist
):
43 # Test the BVH (AABB) first
46 isect
= v1
[i
] - dist
<= point
[i
] <= v2
[i
] + dist
48 isect
= v2
[i
] - dist
<= point
[i
] <= v1
[i
] + dist
53 ret
= intersect_point_line(point
, v1
, v2
)
62 new_dist
= (point
- tmp
).length
70 def get_loose_linked_edges(bmvert
):
71 linked
= [e
for e
in bmvert
.link_edges
if not e
.link_faces
]
73 linked
+= [le
for v
in e
.verts
if not v
.link_faces
for le
in v
.link_edges
if le
not in linked
]
77 def make_line(self
, bm_geom
, location
):
78 obj
= self
.main_snap_obj
.data
[0]
82 update_edit_mesh
= False
85 vert
= bm
.verts
.new(location
)
86 self
.list_verts
.append(vert
)
87 update_edit_mesh
= True
89 elif isinstance(bm_geom
, bmesh
.types
.BMVert
):
90 if (bm_geom
.co
- location
).length_squared
< .001:
91 if self
.list_verts
== [] or self
.list_verts
[-1] != bm_geom
:
92 self
.list_verts
.append(bm_geom
)
94 vert
= bm
.verts
.new(location
)
95 self
.list_verts
.append(vert
)
96 update_edit_mesh
= True
98 elif isinstance(bm_geom
, bmesh
.types
.BMEdge
):
99 self
.list_edges
.append(bm_geom
)
100 ret
= intersect_point_line(location
, bm_geom
.verts
[0].co
, bm_geom
.verts
[1].co
)
102 if (ret
[0] - location
).length_squared
< .001:
104 vert
= bm_geom
.verts
[0]
106 vert
= bm_geom
.verts
[1]
108 edge
, vert
= bmesh
.utils
.edge_split(bm_geom
, bm_geom
.verts
[0], ret
[1])
109 update_edit_mesh
= True
111 if self
.list_verts
== [] or self
.list_verts
[-1] != vert
:
112 self
.list_verts
.append(vert
)
113 self
.geom
= vert
# hack to highlight in the drawing
114 # self.list_edges.append(edge)
116 else: # constrain point is near
117 vert
= bm
.verts
.new(location
)
118 self
.list_verts
.append(vert
)
119 update_edit_mesh
= True
121 elif isinstance(bm_geom
, bmesh
.types
.BMFace
):
122 split_faces
.add(bm_geom
)
123 vert
= bm
.verts
.new(location
)
124 self
.list_verts
.append(vert
)
125 update_edit_mesh
= True
127 # draw, split and create face
128 if len(self
.list_verts
) >= 2:
129 v1
, v2
= self
.list_verts
[-2:]
130 edge
= bm
.edges
.get([v1
, v2
])
132 self
.list_edges
.append(edge
)
134 if not v2
.link_edges
:
135 edge
= bm
.edges
.new([v1
, v2
])
136 self
.list_edges
.append(edge
)
138 v1_link_faces
= v1
.link_faces
139 v2_link_faces
= v2
.link_faces
140 if v1_link_faces
and v2_link_faces
:
141 split_faces
.update(set(v1_link_faces
).intersection(v2_link_faces
))
145 faces
= v1_link_faces
148 faces
= v2_link_faces
152 if bmesh
.geometry
.intersect_face_point(face
, co2
):
153 co
= co2
- face
.calc_center_median()
154 if co
.dot(face
.normal
) < 0.001:
155 split_faces
.add(face
)
158 edge
= bm
.edges
.new([v1
, v2
])
159 self
.list_edges
.append(edge
)
160 ed_list
= get_loose_linked_edges(v2
)
161 for face
in split_faces
:
162 facesp
= bmesh
.utils
.face_split_edgenet(face
, ed_list
)
166 facesp
= bmesh
.ops
.connect_vert_pair(bm
, verts
=[v1
, v2
], verts_exclude
=bm
.verts
)
168 if not self
.intersect
or not facesp
['edges']:
169 edge
= bm
.edges
.new([v1
, v2
])
170 self
.list_edges
.append(edge
)
172 for edge
in facesp
['edges']:
173 self
.list_edges
.append(edge
)
174 update_edit_mesh
= True
178 ed_list
= set(self
.list_edges
)
179 for edge
in v2
.link_edges
:
180 if edge
not in ed_list
and edge
.other_vert(v2
) in self
.list_verts
:
184 ed_list
.update(get_loose_linked_edges(v2
))
186 bmesh
.ops
.edgenet_fill(bm
, edges
=list(ed_list
))
187 update_edit_mesh
= True
188 # print('face created')
191 obj
.data
.update_gpu_tag()
192 obj
.data
.update_tag()
193 obj
.update_from_editmode()
195 bmesh
.update_edit_mesh(obj
.data
)
196 self
.sctx
.tag_update_drawn_snap_object(self
.main_snap_obj
)
197 #bm.verts.index_update()
199 bpy
.ops
.ed
.undo_push(message
="Undo draw line*")
201 return [obj
.matrix_world
@ v
.co
for v
in self
.list_verts
]
204 class SnapUtilitiesLine(SnapUtilities
, bpy
.types
.Operator
):
205 """Make Lines. Connect them to split faces"""
206 bl_idname
= "mesh.snap_utilities_line"
207 bl_label
= "Line Tool"
208 bl_options
= {'REGISTER'}
210 wait_for_input
: bpy
.props
.BoolProperty(name
="Wait for Input", default
=True)
212 def _exit(self
, context
):
213 #avoids unpredictable crashes
214 del self
.main_snap_obj
218 del self
.list_verts_co
220 bpy
.types
.SpaceView3D
.draw_handler_remove(self
._handle
, 'WINDOW')
221 context
.area
.header_text_set(None)
222 self
.snap_context_free()
224 #Restore initial state
225 context
.tool_settings
.mesh_select_mode
= self
.select_mode
226 context
.space_data
.overlay
.show_face_center
= self
.show_face_center
228 def _init_snap_line_context(self
, context
):
229 self
.prevloc
= Vector()
232 self
.list_verts_co
= []
233 self
.bool_update
= True
234 self
.vector_constrain
= ()
237 if not (self
.bm
and self
.obj
):
238 self
.obj
= context
.edit_object
239 self
.bm
= bmesh
.from_edit_mesh(self
.obj
.data
)
241 self
.main_snap_obj
= self
.snap_obj
= self
.sctx
._get
_snap
_obj
_by
_obj
(self
.obj
)
242 self
.main_bm
= self
.bm
244 def _shift_contrain_callback(self
):
245 if isinstance(self
.geom
, bmesh
.types
.BMEdge
):
246 mat
= self
.main_snap_obj
.mat
247 verts_co
= [mat
@ v
.co
for v
in self
.geom
.verts
]
248 return verts_co
[1] - verts_co
[0]
250 def modal(self
, context
, event
):
251 if self
.navigation_ops
.run(context
, event
, self
.prevloc
if self
.vector_constrain
else self
.location
):
252 return {'RUNNING_MODAL'}
254 context
.area
.tag_redraw()
256 if event
.ctrl
and event
.type == 'Z' and event
.value
== 'PRESS':
258 if not self
.wait_for_input
:
266 bpy
.ops
.object.mode_set(mode
='EDIT') # just to be sure
267 bpy
.ops
.mesh
.select_all(action
='DESELECT')
268 context
.tool_settings
.mesh_select_mode
= (True, False, True)
269 context
.space_data
.overlay
.show_face_center
= True
271 self
.snap_context_update(context
)
272 self
._init
_snap
_line
_context
(context
)
273 self
.sctx
.update_all()
275 return {'RUNNING_MODAL'}
277 is_making_lines
= bool(self
.list_verts_co
)
279 if (event
.type == 'MOUSEMOVE' or self
.bool_update
) and self
.charmap
.length_entered_value
== 0.0:
280 mval
= Vector((event
.mouse_region_x
, event
.mouse_region_y
))
282 if self
.rv3d
.view_matrix
!= self
.rotMat
:
283 self
.rotMat
= self
.rv3d
.view_matrix
.copy()
284 self
.bool_update
= True
285 snap_utilities
.cache
.clear()
287 self
.bool_update
= False
289 self
.snap_obj
, self
.prevloc
, self
.location
, self
.type, self
.bm
, self
.geom
, self
.len = snap_utilities(
293 constrain
=self
.vector_constrain
,
294 previous_vert
=(self
.list_verts
[-1] if self
.list_verts
else None),
295 increment
=self
.incremental
)
299 if is_making_lines
and self
.preferences
.auto_constrain
:
300 loc
= self
.list_verts_co
[-1]
301 vec
, type = self
.constrain
.update(self
.sctx
.region
, self
.sctx
.rv3d
, mval
, loc
)
302 self
.vector_constrain
= [loc
, loc
+ vec
, type]
304 if event
.value
== 'PRESS':
305 if is_making_lines
and self
.charmap
.modal_(context
, event
):
306 self
.bool_update
= self
.charmap
.length_entered_value
== 0.0
308 if not self
.bool_update
:
309 text_value
= self
.charmap
.length_entered_value
310 vector
= (self
.location
- self
.list_verts_co
[-1]).normalized()
311 self
.location
= self
.list_verts_co
[-1] + (vector
* text_value
)
314 elif self
.constrain
.modal(event
, self
._shift
_contrain
_callback
):
315 self
.bool_update
= True
316 if self
.constrain
.last_vec
:
317 if self
.list_verts_co
:
318 loc
= self
.list_verts_co
[-1]
322 self
.vector_constrain
= (loc
, loc
+ self
.constrain
.last_vec
, self
.constrain
.last_type
)
324 self
.vector_constrain
= None
326 elif event
.type in {'LEFTMOUSE', 'RET', 'NUMPAD_ENTER'}:
327 if event
.type == 'LEFTMOUSE' or self
.charmap
.length_entered_value
!= 0.0:
328 if not is_making_lines
and self
.bm
:
329 self
.main_snap_obj
= self
.snap_obj
330 self
.main_bm
= self
.bm
332 mat_inv
= self
.main_snap_obj
.mat
.inverted_safe()
333 point
= mat_inv
@ self
.location
338 if self
.vector_constrain
:
339 geom2
= get_closest_edge(self
.main_bm
, point
, .001)
341 self
.list_verts_co
= make_line(self
, geom2
, point
)
343 self
.vector_constrain
= None
349 elif event
.type == 'F8':
350 self
.vector_constrain
= None
351 self
.constrain
.toogle()
353 elif event
.type in {'RIGHTMOUSE', 'ESC'}:
354 if not self
.wait_for_input
or not is_making_lines
or event
.type == 'ESC':
356 self
.geom
.select
= True
360 snap_utilities
.cache
.clear()
361 self
.vector_constrain
= None
364 self
.list_verts_co
= []
369 a
= 'length: ' + self
.charmap
.get_converted_length_str(self
.len)
371 context
.area
.header_text_set(text
= "hit: %.3f %.3f %.3f %s" % (*self
.location
, a
))
373 if True or is_making_lines
:
374 return {'RUNNING_MODAL'}
376 return {'PASS_THROUGH'}
378 def draw_callback_px(self
):
380 self
.draw_cache
.draw_elem(self
.snap_obj
, self
.bm
, self
.geom
)
381 self
.draw_cache
.draw(self
.type, self
.location
, self
.list_verts_co
, self
.vector_constrain
, self
.prevloc
)
383 def invoke(self
, context
, event
):
384 if context
.space_data
.type == 'VIEW_3D':
385 self
.snap_context_init(context
)
386 self
.snap_context_update(context
)
388 self
.constrain
= Constrain(self
.preferences
, context
.scene
, self
.obj
)
390 self
.intersect
= self
.preferences
.intersect
391 self
.create_face
= self
.preferences
.create_face
392 self
.navigation_ops
= SnapNavigation(context
, True)
393 self
.charmap
= CharMap(context
)
395 self
._init
_snap
_line
_context
(context
)
397 # print('name', __name__, __package__)
400 self
.select_mode
= context
.tool_settings
.mesh_select_mode
[:]
401 self
.show_face_center
= context
.space_data
.overlay
.show_face_center
403 #Modify the current state
404 bpy
.ops
.mesh
.select_all(action
='DESELECT')
405 context
.tool_settings
.mesh_select_mode
= (True, False, True)
406 context
.space_data
.overlay
.show_face_center
= True
408 #Store values from 3d view context
409 self
.rv3d
= context
.region_data
410 self
.rotMat
= self
.rv3d
.view_matrix
.copy()
411 # self.obj_glmatrix = bgl.Buffer(bgl.GL_FLOAT, [4, 4],
412 # self.obj_matrix.transposed())
415 context
.window_manager
.modal_handler_add(self
)
417 if not self
.wait_for_input
:
418 mat_inv
= self
.obj
.matrix_world
.inverted_safe()
419 point
= mat_inv
@ self
.location
420 self
.list_verts_co
= make_line(self
, self
.geom
, point
)
422 self
._handle
= bpy
.types
.SpaceView3D
.draw_handler_add(self
.draw_callback_px
, (), 'WINDOW', 'POST_VIEW')
424 return {'RUNNING_MODAL'}
426 self
.report({'WARNING'}, "Active space must be a View3d")
431 bpy
.utils
.register_class(SnapUtilitiesLine
)
433 if __name__
== "__main__":