1 # SPDX-License-Identifier: GPL-2.0-or-later
5 from mathutils
import (
9 from mathutils
.geometry
import intersect_point_line
10 from .drawing_utilities
import SnapDrawn
11 from .common_utilities
import (
14 location_3d_to_region_2d
,
18 class SnapNavigation():
31 for member
in dir(key
):
32 print(member
, getattr(key
, member
))
35 def convert_to_flag(shift
, ctrl
, alt
):
36 return (shift
<< 0) |
(ctrl
<< 1) |
(alt
<< 2)
38 def __init__(self
, context
, use_ndof
):
40 # 'View Orbit', 'View Pan', 'NDOF Orbit View', 'NDOF Pan View'
41 self
.use_ndof
= use_ndof
and context
.preferences
.inputs
.use_ndof
48 self
._ndof
_all
= set()
49 self
._ndof
_orbit
= set()
50 self
._ndof
_orbit
_zoom
= set()
51 self
._ndof
_pan
= set()
53 for key
in context
.window_manager
.keyconfigs
.user
.keymaps
['3D View'].keymap_items
:
54 if key
.idname
== 'view3d.rotate':
55 self
._rotate
.add((self
.convert_to_flag(
56 key
.shift
, key
.ctrl
, key
.alt
), key
.type, key
.value
))
57 elif key
.idname
== 'view3d.move':
58 self
._move
.add((self
.convert_to_flag(
59 key
.shift
, key
.ctrl
, key
.alt
), key
.type, key
.value
))
60 elif key
.idname
== 'view3d.zoom':
61 if key
.type == 'WHEELINMOUSE':
62 self
._zoom
.add((self
.convert_to_flag(
63 key
.shift
, key
.ctrl
, key
.alt
), 'WHEELUPMOUSE', key
.value
, key
.properties
.delta
))
64 elif key
.type == 'WHEELOUTMOUSE':
65 self
._zoom
.add((self
.convert_to_flag(
66 key
.shift
, key
.ctrl
, key
.alt
), 'WHEELDOWNMOUSE', key
.value
, key
.properties
.delta
))
68 self
._zoom
.add((self
.convert_to_flag(
69 key
.shift
, key
.ctrl
, key
.alt
), key
.type, key
.value
, key
.properties
.delta
))
72 if key
.idname
== 'view3d.ndof_all':
73 self
._ndof
_all
.add((self
.convert_to_flag(
74 key
.shift
, key
.ctrl
, key
.alt
), key
.type))
75 elif key
.idname
== 'view3d.ndof_orbit':
76 self
._ndof
_orbit
.add((self
.convert_to_flag(
77 key
.shift
, key
.ctrl
, key
.alt
), key
.type))
78 elif key
.idname
== 'view3d.ndof_orbit_zoom':
79 self
._ndof
_orbit
_zoom
.add(
80 (self
.convert_to_flag(key
.shift
, key
.ctrl
, key
.alt
), key
.type))
81 elif key
.idname
== 'view3d.ndof_pan':
82 self
._ndof
_pan
.add((self
.convert_to_flag(
83 key
.shift
, key
.ctrl
, key
.alt
), key
.type))
85 def run(self
, context
, event
, snap_location
):
86 evkey
= (self
.convert_to_flag(event
.shift
, event
.ctrl
,
87 event
.alt
), event
.type, event
.value
)
89 if evkey
in self
._rotate
:
91 bpy
.ops
.view3d
.rotate_custom_pivot(
92 'INVOKE_DEFAULT', pivot
=snap_location
)
94 bpy
.ops
.view3d
.rotate('INVOKE_DEFAULT', use_cursor_init
=True)
97 if evkey
in self
._move
:
98 bpy
.ops
.view3d
.move('INVOKE_DEFAULT')
101 for key
in self
._zoom
:
102 if evkey
== key
[0:3]:
105 bpy
.ops
.view3d
.zoom_custom_target(
106 'INVOKE_DEFAULT', delta
=key
[3], target
=snap_location
)
108 bpy
.ops
.view3d
.zoom('INVOKE_DEFAULT', delta
=key
[3])
110 bpy
.ops
.view3d
.zoom('INVOKE_DEFAULT')
115 if ndofkey
in self
._ndof
_all
:
116 bpy
.ops
.view3d
.ndof_all('INVOKE_DEFAULT')
118 if ndofkey
in self
._ndof
_orbit
:
119 bpy
.ops
.view3d
.ndof_orbit('INVOKE_DEFAULT')
121 if ndofkey
in self
._ndof
_orbit
_zoom
:
122 bpy
.ops
.view3d
.ndof_orbit_zoom('INVOKE_DEFAULT')
124 if ndofkey
in self
._ndof
_pan
:
125 bpy
.ops
.view3d
.ndof_pan('INVOKE_DEFAULT')
136 'length_entered_value',
140 ".", ",", "-", "+", "1", "2", "3",
141 "4", "5", "6", "7", "8", "9", "0",
142 "c", "m", "d", "k", "h", "a",
143 " ", "/", "*", "'", "\""
148 'LEFT_ARROW', 'RIGHT_ARROW'
151 def __init__(self
, context
):
152 scale
= context
.scene
.unit_settings
.scale_length
153 separate_units
= context
.scene
.unit_settings
.use_separate
154 self
.unit_system
= context
.scene
.unit_settings
.system
155 self
.uinfo
= get_units_info(scale
, self
.unit_system
, separate_units
)
159 def modal_(self
, context
, event
):
160 if event
.value
== 'PRESS':
163 if (type in self
.type) or (ascii
in self
.ascii
):
168 self
.length_entered
= self
.length_entered
[:pos
] + \
169 ascii
+ self
.length_entered
[pos
:]
172 if self
.length_entered
:
174 if type == 'BACK_SPACE':
175 self
.length_entered
= self
.length_entered
[:pos
-
176 1] + self
.length_entered
[pos
:]
180 self
.length_entered
= self
.length_entered
[:pos
] + \
181 self
.length_entered
[pos
+ 1:]
183 elif type == 'LEFT_ARROW':
185 pos
- 1) % (len(self
.length_entered
) + 1)
187 elif type == 'RIGHT_ARROW':
189 pos
+ 1) % (len(self
.length_entered
) + 1)
192 self
.length_entered_value
= bpy
.utils
.units
.to_value(
193 self
.unit_system
, 'LENGTH', self
.length_entered
)
194 except: # ValueError:
195 self
.length_entered_value
= 0.0 # invalid
196 # self.report({'INFO'}, "Operation not supported yet")
198 self
.length_entered_value
= 0.0
204 def get_converted_length_str(self
, length
):
205 if self
.length_entered
:
207 ret
= self
.length_entered
[:pos
] + '|' + self
.length_entered
[pos
:]
209 ret
= convert_distance(length
, self
.uinfo
)
214 self
.length_entered
= ''
215 self
.length_entered_value
= 0.0
220 def __init__(self
, prefs
, scene
, obj
):
221 self
.last_type
= None
224 self
.preferences
= prefs
225 trans_orient
= scene
.transform_orientation_slots
[0]
226 self
.orientation
= [None, None]
227 if trans_orient
.type == 'LOCAL':
228 self
.orientation
[0] = obj
.matrix_world
.to_3x3().transposed()
229 self
.orientation
[1] = Matrix
.Identity(3)
231 self
.orientation
[0] = Matrix
.Identity(3)
232 self
.orientation
[1] = obj
.matrix_world
.to_3x3().transposed()
234 self
.orientation_id
= 0
235 self
.center
= Vector((0.0, 0.0, 0.0))
236 self
.center_2d
= Vector((0.0, 0.0))
237 self
.projected_vecs
= Matrix(([0.0, 0.0], [0.0, 0.0], [0.0, 0.0]))
239 def _constrain_set(self
, mcursor
):
240 vec
= (mcursor
- self
.center_2d
)
243 dot_x
= abs(vec
.dot(self
.projected_vecs
[0]))
244 dot_y
= abs(vec
.dot(self
.projected_vecs
[1]))
245 dot_z
= abs(vec
.dot(self
.projected_vecs
[2]))
247 if dot_x
> dot_y
and dot_x
> dot_z
:
248 vec
= self
.orientation
[self
.orientation_id
][0]
251 elif dot_y
> dot_x
and dot_y
> dot_z
:
252 vec
= self
.orientation
[self
.orientation_id
][1]
255 else: # dot_z > dot_y and dot_z > dot_x:
256 vec
= self
.orientation
[self
.orientation_id
][2]
261 def modal(self
, event
, shift_callback
):
263 if self
.last_type
== type:
264 self
.orientation_id
+= 1
267 if self
.orientation_id
< 2:
268 self
.last_vec
= self
.orientation
[self
.orientation_id
][0]
270 self
.orientation_id
= 0
271 self
.last_vec
= type = None
273 if self
.orientation_id
< 2:
274 self
.last_vec
= self
.orientation
[self
.orientation_id
][1]
276 self
.orientation_id
= 0
277 self
.last_vec
= type = None
279 if self
.orientation_id
< 2:
280 self
.last_vec
= self
.orientation
[self
.orientation_id
][2]
282 self
.orientation_id
= 0
283 self
.last_vec
= type = None
284 elif shift_callback
and type in {'RIGHT_SHIFT', 'LEFT_SHIFT'}:
285 if self
.orientation_id
< 1:
287 self
.last_vec
= shift_callback()
289 self
.orientation_id
= 0
290 self
.last_vec
= type = None
294 self
.preferences
.auto_constrain
= False
295 self
.last_type
= type
299 self
.rotMat
= None # update
300 if self
.preferences
.auto_constrain
:
301 self
.orientation_id
= (self
.orientation_id
+ 1) % 2
302 self
.preferences
.auto_constrain
= self
.orientation_id
!= 0
304 self
.preferences
.auto_constrain
= True
306 def update(self
, region
, rv3d
, mcursor
, center
):
307 if rv3d
.view_matrix
!= self
.rotMat
or self
.center
!= center
:
308 self
.rotMat
= rv3d
.view_matrix
.copy()
310 self
.center
= center
.copy()
311 self
.center_2d
= location_3d_to_region_2d(
312 region
, rv3d
, self
.center
)
314 vec
= self
.center
+ self
.orientation
[self
.orientation_id
][0]
315 self
.projected_vecs
[0] = location_3d_to_region_2d(
316 region
, rv3d
, vec
) - self
.center_2d
317 vec
= self
.center
+ self
.orientation
[self
.orientation_id
][1]
318 self
.projected_vecs
[1] = location_3d_to_region_2d(
319 region
, rv3d
, vec
) - self
.center_2d
320 vec
= self
.center
+ self
.orientation
[self
.orientation_id
][2]
321 self
.projected_vecs
[2] = location_3d_to_region_2d(
322 region
, rv3d
, vec
) - self
.center_2d
324 self
.projected_vecs
[0].normalize()
325 self
.projected_vecs
[1].normalize()
326 self
.projected_vecs
[2].normalize()
328 return self
._constrain
_set
(mcursor
)
354 'X': Vector((1, 0, 0)),
355 'Y': Vector((0, 1, 0)),
356 'Z': Vector((0, 0, 1)),
357 'RIGHT_SHIFT': 'shift',
358 'LEFT_SHIFT': 'shift',
365 def set_contrain(context
, key
):
366 widget
= SnapUtilities
.snapwidgets
[-1] if SnapUtilities
.snapwidgets
else None
367 if SnapUtilities
.constrain
== key
:
368 SnapUtilities
.constrain
= None
369 if hasattr(widget
, "get_normal"):
370 widget
.get_normal(context
)
373 if hasattr(widget
, "normal"):
376 if isinstance(widget
.geom
, bmesh
.types
.BMEdge
):
377 verts
= widget
.geom
.verts
378 widget
.normal
= verts
[1].co
- verts
[0].co
379 widget
.normal
.normalise()
383 widget
.normal
= SnapUtilities
.constrain_keys
[key
]
385 SnapUtilities
.constrain
= key
387 def snap_context_update_and_return_moving_objects(self
, context
):
388 moving_objects
= set()
389 moving_snp_objects
= set()
391 for obj
in context
.view_layer
.objects
.selected
:
392 moving_objects
.add(obj
)
394 temp_children
= set()
395 for obj
in context
.visible_objects
:
396 temp_children
.clear()
397 while obj
.parent
is not None:
398 temp_children
.add(obj
)
400 if parent
in moving_objects
:
401 children
.update(temp_children
)
402 temp_children
.clear()
407 moving_objects
.difference_update(children
)
409 self
.sctx
.clear_snap_objects(True)
411 for obj
in context
.visible_objects
:
412 is_moving
= obj
in moving_objects
or obj
in children
413 snap_obj
= self
.sctx
.add_obj(obj
, obj
.matrix_world
)
415 moving_snp_objects
.add(snap_obj
)
417 if obj
.instance_type
== 'COLLECTION':
418 mat
= obj
.matrix_world
.copy()
419 for ob
in obj
.instance_collection
.objects
:
420 snap_obj
= self
.sctx
.add_obj(ob
, mat
@ ob
.matrix_world
)
422 moving_snp_objects
.add(snap_obj
)
425 return moving_objects
, moving_snp_objects
427 def snap_context_update(self
, context
):
428 def visible_objects_and_duplis():
429 if self
.preferences
.outer_verts
:
430 for obj
in context
.visible_objects
:
431 yield (obj
, obj
.matrix_world
)
433 if obj
.instance_type
== 'COLLECTION':
434 mat
= obj
.matrix_world
.copy()
435 for ob
in obj
.instance_collection
.objects
:
436 yield (ob
, mat
@ ob
.matrix_world
)
438 for obj
in context
.objects_in_mode_unique_data
:
439 yield (obj
, obj
.matrix_world
)
441 self
.sctx
.clear_snap_objects(True)
443 for obj
, matrix
in visible_objects_and_duplis():
444 self
.sctx
.add_obj(obj
, matrix
)
446 def snap_context_init(self
, context
, snap_edge_and_vert
=True):
447 from .snap_context_l
import global_snap_context_get
449 # Create Snap Context
450 self
.sctx
= global_snap_context_get(
451 context
.evaluated_depsgraph_get(), context
.region
, context
.space_data
)
452 ui_scale
= context
.preferences
.system
.ui_scale
453 self
.sctx
.set_pixel_dist(12 * ui_scale
)
455 if SnapUtilities
.snapwidgets
:
456 widget
= SnapUtilities
.snapwidgets
[-1]
458 self
.obj
= widget
.snap_obj
.data
[0] if widget
.snap_obj
else context
.active_object
460 self
.geom
= widget
.geom
461 self
.type = widget
.type
462 self
.location
= widget
.location
463 self
.preferences
= widget
.preferences
464 self
.draw_cache
= widget
.draw_cache
465 if hasattr(widget
, "normal"):
466 self
.normal
= widget
.normal
469 # init these variables to avoid errors
470 self
.obj
= context
.active_object
474 self
.location
= Vector()
476 preferences
= context
.preferences
.addons
[__package__
].preferences
477 self
.preferences
= preferences
480 self
.draw_cache
= SnapDrawn(
481 preferences
.out_color
,
482 preferences
.face_color
,
483 preferences
.edge_color
,
484 preferences
.vert_color
,
485 preferences
.center_color
,
486 preferences
.perpendicular_color
,
487 preferences
.constrain_shift_color
,
489 context
.preferences
.themes
[0].user_interface
.axis_x
) + (1.0,),
491 context
.preferences
.themes
[0].user_interface
.axis_y
) + (1.0,),
493 context
.preferences
.themes
[0].user_interface
.axis_z
) + (1.0,),
497 self
.snap_vert
= self
.snap_edge
= snap_edge_and_vert
499 shading
= context
.space_data
.shading
500 self
.snap_face
= not (snap_edge_and_vert
and (
501 shading
.show_xray
or shading
.type == 'WIREFRAME'))
503 self
.sctx
.set_snap_mode(self
.snap_vert
, self
.snap_edge
, self
.snap_face
)
505 # Configure the unit of measure
506 unit_system
= context
.scene
.unit_settings
.system
507 scale
= context
.scene
.unit_settings
.scale_length
508 scale
/= context
.space_data
.overlay
.grid_scale
509 self
.rd
= bpy
.utils
.units
.to_value(
510 unit_system
, 'LENGTH', str(1 / scale
))
512 self
.incremental
= bpy
.utils
.units
.to_value(
513 unit_system
, 'LENGTH', str(self
.preferences
.incremental
))
515 def snap_to_grid(self
):
516 if self
.type == 'OUT' and self
.preferences
.increments_grid
:
517 loc
= self
.location
/ self
.rd
518 self
.location
= Vector((round(loc
.x
),
520 round(loc
.z
))) * self
.rd
522 def snap_context_free(self
):
537 SnapUtilities
.constrain
= None