1 # SPDX-FileCopyrightText: 2018-2022 Blender Foundation
3 # SPDX-License-Identifier: GPL-2.0-or-later
7 from mathutils
import (
11 from mathutils
.geometry
import intersect_point_line
12 from .drawing_utilities
import SnapDrawn
13 from .common_utilities
import (
16 location_3d_to_region_2d
,
20 class SnapNavigation():
33 for member
in dir(key
):
34 print(member
, getattr(key
, member
))
37 def convert_to_flag(shift
, ctrl
, alt
):
38 return (shift
<< 0) |
(ctrl
<< 1) |
(alt
<< 2)
40 def __init__(self
, context
, use_ndof
):
42 # 'View Orbit', 'View Pan', 'NDOF Orbit View', 'NDOF Pan View'
43 self
.use_ndof
= use_ndof
and bpy
.app
.build_options
.input_ndof
50 self
._ndof
_all
= set()
51 self
._ndof
_orbit
= set()
52 self
._ndof
_orbit
_zoom
= set()
53 self
._ndof
_pan
= set()
55 for key
in context
.window_manager
.keyconfigs
.user
.keymaps
['3D View'].keymap_items
:
56 if key
.idname
== 'view3d.rotate':
57 self
._rotate
.add((self
.convert_to_flag(
58 key
.shift
, key
.ctrl
, key
.alt
), key
.type, key
.value
))
59 elif key
.idname
== 'view3d.move':
60 self
._move
.add((self
.convert_to_flag(
61 key
.shift
, key
.ctrl
, key
.alt
), key
.type, key
.value
))
62 elif key
.idname
== 'view3d.zoom':
63 if key
.type == 'WHEELINMOUSE':
64 self
._zoom
.add((self
.convert_to_flag(
65 key
.shift
, key
.ctrl
, key
.alt
), 'WHEELUPMOUSE', key
.value
, key
.properties
.delta
))
66 elif key
.type == 'WHEELOUTMOUSE':
67 self
._zoom
.add((self
.convert_to_flag(
68 key
.shift
, key
.ctrl
, key
.alt
), 'WHEELDOWNMOUSE', key
.value
, key
.properties
.delta
))
70 self
._zoom
.add((self
.convert_to_flag(
71 key
.shift
, key
.ctrl
, key
.alt
), key
.type, key
.value
, key
.properties
.delta
))
74 if key
.idname
== 'view3d.ndof_all':
75 self
._ndof
_all
.add((self
.convert_to_flag(
76 key
.shift
, key
.ctrl
, key
.alt
), key
.type))
77 elif key
.idname
== 'view3d.ndof_orbit':
78 self
._ndof
_orbit
.add((self
.convert_to_flag(
79 key
.shift
, key
.ctrl
, key
.alt
), key
.type))
80 elif key
.idname
== 'view3d.ndof_orbit_zoom':
81 self
._ndof
_orbit
_zoom
.add(
82 (self
.convert_to_flag(key
.shift
, key
.ctrl
, key
.alt
), key
.type))
83 elif key
.idname
== 'view3d.ndof_pan':
84 self
._ndof
_pan
.add((self
.convert_to_flag(
85 key
.shift
, key
.ctrl
, key
.alt
), key
.type))
87 def run(self
, context
, event
, snap_location
):
88 evkey
= (self
.convert_to_flag(event
.shift
, event
.ctrl
,
89 event
.alt
), event
.type, event
.value
)
91 if evkey
in self
._rotate
:
93 bpy
.ops
.view3d
.rotate_custom_pivot(
94 'INVOKE_DEFAULT', pivot
=snap_location
)
96 bpy
.ops
.view3d
.rotate('INVOKE_DEFAULT', use_cursor_init
=True)
99 if evkey
in self
._move
:
100 bpy
.ops
.view3d
.move('INVOKE_DEFAULT')
103 for key
in self
._zoom
:
104 if evkey
== key
[0:3]:
107 bpy
.ops
.view3d
.zoom_custom_target(
108 'INVOKE_DEFAULT', delta
=key
[3], target
=snap_location
)
110 bpy
.ops
.view3d
.zoom('INVOKE_DEFAULT', delta
=key
[3])
112 bpy
.ops
.view3d
.zoom('INVOKE_DEFAULT')
117 if ndofkey
in self
._ndof
_all
:
118 bpy
.ops
.view3d
.ndof_all('INVOKE_DEFAULT')
120 if ndofkey
in self
._ndof
_orbit
:
121 bpy
.ops
.view3d
.ndof_orbit('INVOKE_DEFAULT')
123 if ndofkey
in self
._ndof
_orbit
_zoom
:
124 bpy
.ops
.view3d
.ndof_orbit_zoom('INVOKE_DEFAULT')
126 if ndofkey
in self
._ndof
_pan
:
127 bpy
.ops
.view3d
.ndof_pan('INVOKE_DEFAULT')
138 'length_entered_value',
142 ".", ",", "-", "+", "1", "2", "3",
143 "4", "5", "6", "7", "8", "9", "0",
144 "c", "m", "d", "k", "h", "a",
145 " ", "/", "*", "'", "\""
150 'LEFT_ARROW', 'RIGHT_ARROW'
153 def __init__(self
, context
):
154 scale
= context
.scene
.unit_settings
.scale_length
155 separate_units
= context
.scene
.unit_settings
.use_separate
156 self
.unit_system
= context
.scene
.unit_settings
.system
157 self
.uinfo
= get_units_info(scale
, self
.unit_system
, separate_units
)
161 def modal_(self
, context
, event
):
162 if event
.value
== 'PRESS':
165 if (type in self
.type) or (ascii
in self
.ascii
):
170 self
.length_entered
= self
.length_entered
[:pos
] + \
171 ascii
+ self
.length_entered
[pos
:]
174 if self
.length_entered
:
176 if type == 'BACK_SPACE':
177 self
.length_entered
= self
.length_entered
[:pos
-
178 1] + self
.length_entered
[pos
:]
182 self
.length_entered
= self
.length_entered
[:pos
] + \
183 self
.length_entered
[pos
+ 1:]
185 elif type == 'LEFT_ARROW':
187 pos
- 1) % (len(self
.length_entered
) + 1)
189 elif type == 'RIGHT_ARROW':
191 pos
+ 1) % (len(self
.length_entered
) + 1)
194 self
.length_entered_value
= bpy
.utils
.units
.to_value(
195 self
.unit_system
, 'LENGTH', self
.length_entered
)
196 except: # ValueError:
197 self
.length_entered_value
= 0.0 # invalid
198 # self.report({'INFO'}, "Operation not supported yet")
200 self
.length_entered_value
= 0.0
206 def get_converted_length_str(self
, length
):
207 if self
.length_entered
:
209 ret
= self
.length_entered
[:pos
] + '|' + self
.length_entered
[pos
:]
211 ret
= convert_distance(length
, self
.uinfo
)
216 self
.length_entered
= ''
217 self
.length_entered_value
= 0.0
222 def __init__(self
, prefs
, scene
, obj
):
223 self
.last_type
= None
226 self
.preferences
= prefs
227 trans_orient
= scene
.transform_orientation_slots
[0]
228 self
.orientation
= [None, None]
229 if trans_orient
.type == 'LOCAL':
230 self
.orientation
[0] = obj
.matrix_world
.to_3x3().transposed()
231 self
.orientation
[1] = Matrix
.Identity(3)
233 self
.orientation
[0] = Matrix
.Identity(3)
234 self
.orientation
[1] = obj
.matrix_world
.to_3x3().transposed()
236 self
.orientation_id
= 0
237 self
.center
= Vector((0.0, 0.0, 0.0))
238 self
.center_2d
= Vector((0.0, 0.0))
239 self
.projected_vecs
= Matrix(([0.0, 0.0], [0.0, 0.0], [0.0, 0.0]))
241 def _constrain_set(self
, mcursor
):
242 vec
= (mcursor
- self
.center_2d
)
245 dot_x
= abs(vec
.dot(self
.projected_vecs
[0]))
246 dot_y
= abs(vec
.dot(self
.projected_vecs
[1]))
247 dot_z
= abs(vec
.dot(self
.projected_vecs
[2]))
249 if dot_x
> dot_y
and dot_x
> dot_z
:
250 vec
= self
.orientation
[self
.orientation_id
][0]
253 elif dot_y
> dot_x
and dot_y
> dot_z
:
254 vec
= self
.orientation
[self
.orientation_id
][1]
257 else: # dot_z > dot_y and dot_z > dot_x:
258 vec
= self
.orientation
[self
.orientation_id
][2]
263 def modal(self
, event
, shift_callback
):
265 if self
.last_type
== type:
266 self
.orientation_id
+= 1
269 if self
.orientation_id
< 2:
270 self
.last_vec
= self
.orientation
[self
.orientation_id
][0]
272 self
.orientation_id
= 0
273 self
.last_vec
= type = None
275 if self
.orientation_id
< 2:
276 self
.last_vec
= self
.orientation
[self
.orientation_id
][1]
278 self
.orientation_id
= 0
279 self
.last_vec
= type = None
281 if self
.orientation_id
< 2:
282 self
.last_vec
= self
.orientation
[self
.orientation_id
][2]
284 self
.orientation_id
= 0
285 self
.last_vec
= type = None
286 elif shift_callback
and type in {'RIGHT_SHIFT', 'LEFT_SHIFT'}:
287 if self
.orientation_id
< 1:
289 self
.last_vec
= shift_callback()
291 self
.orientation_id
= 0
292 self
.last_vec
= type = None
296 self
.preferences
.auto_constrain
= False
297 self
.last_type
= type
301 self
.rotMat
= None # update
302 if self
.preferences
.auto_constrain
:
303 self
.orientation_id
= (self
.orientation_id
+ 1) % 2
304 self
.preferences
.auto_constrain
= self
.orientation_id
!= 0
306 self
.preferences
.auto_constrain
= True
308 def update(self
, region
, rv3d
, mcursor
, center
):
309 if rv3d
.view_matrix
!= self
.rotMat
or self
.center
!= center
:
310 self
.rotMat
= rv3d
.view_matrix
.copy()
312 self
.center
= center
.copy()
313 self
.center_2d
= location_3d_to_region_2d(
314 region
, rv3d
, self
.center
)
316 vec
= self
.center
+ self
.orientation
[self
.orientation_id
][0]
317 self
.projected_vecs
[0] = location_3d_to_region_2d(
318 region
, rv3d
, vec
) - self
.center_2d
319 vec
= self
.center
+ self
.orientation
[self
.orientation_id
][1]
320 self
.projected_vecs
[1] = location_3d_to_region_2d(
321 region
, rv3d
, vec
) - self
.center_2d
322 vec
= self
.center
+ self
.orientation
[self
.orientation_id
][2]
323 self
.projected_vecs
[2] = location_3d_to_region_2d(
324 region
, rv3d
, vec
) - self
.center_2d
326 self
.projected_vecs
[0].normalize()
327 self
.projected_vecs
[1].normalize()
328 self
.projected_vecs
[2].normalize()
330 return self
._constrain
_set
(mcursor
)
356 'X': Vector((1, 0, 0)),
357 'Y': Vector((0, 1, 0)),
358 'Z': Vector((0, 0, 1)),
359 'RIGHT_SHIFT': 'shift',
360 'LEFT_SHIFT': 'shift',
367 def set_contrain(context
, key
):
368 widget
= SnapUtilities
.snapwidgets
[-1] if SnapUtilities
.snapwidgets
else None
369 if SnapUtilities
.constrain
== key
:
370 SnapUtilities
.constrain
= None
371 if hasattr(widget
, "get_normal"):
372 widget
.get_normal(context
)
375 if hasattr(widget
, "normal"):
378 if isinstance(widget
.geom
, bmesh
.types
.BMEdge
):
379 verts
= widget
.geom
.verts
380 widget
.normal
= verts
[1].co
- verts
[0].co
381 widget
.normal
.normalise()
385 widget
.normal
= SnapUtilities
.constrain_keys
[key
]
387 SnapUtilities
.constrain
= key
389 def snap_context_update_and_return_moving_objects(self
, context
):
390 moving_objects
= set()
391 moving_snp_objects
= set()
393 for obj
in context
.view_layer
.objects
.selected
:
394 moving_objects
.add(obj
)
396 temp_children
= set()
397 for obj
in context
.visible_objects
:
398 temp_children
.clear()
399 while obj
.parent
is not None:
400 temp_children
.add(obj
)
402 if parent
in moving_objects
:
403 children
.update(temp_children
)
404 temp_children
.clear()
409 moving_objects
.difference_update(children
)
411 self
.sctx
.clear_snap_objects(True)
413 for obj
in context
.visible_objects
:
414 is_moving
= obj
in moving_objects
or obj
in children
415 snap_obj
= self
.sctx
.add_obj(obj
, obj
.matrix_world
)
417 moving_snp_objects
.add(snap_obj
)
419 if obj
.instance_type
== 'COLLECTION':
420 mat
= obj
.matrix_world
.copy()
421 for ob
in obj
.instance_collection
.objects
:
422 snap_obj
= self
.sctx
.add_obj(ob
, mat
@ ob
.matrix_world
)
424 moving_snp_objects
.add(snap_obj
)
427 return moving_objects
, moving_snp_objects
429 def snap_context_update(self
, context
):
430 def visible_objects_and_duplis():
431 if self
.preferences
.outer_verts
:
432 for obj
in context
.visible_objects
:
433 yield (obj
, obj
.matrix_world
)
435 if obj
.instance_type
== 'COLLECTION':
436 mat
= obj
.matrix_world
.copy()
437 for ob
in obj
.instance_collection
.objects
:
438 yield (ob
, mat
@ ob
.matrix_world
)
440 for obj
in context
.objects_in_mode_unique_data
:
441 yield (obj
, obj
.matrix_world
)
443 self
.sctx
.clear_snap_objects(True)
445 for obj
, matrix
in visible_objects_and_duplis():
446 self
.sctx
.add_obj(obj
, matrix
)
448 def snap_context_init(self
, context
, snap_edge_and_vert
=True):
449 from .snap_context_l
import global_snap_context_get
451 # Create Snap Context
452 self
.sctx
= global_snap_context_get(
453 context
.evaluated_depsgraph_get(), context
.region
, context
.space_data
)
454 ui_scale
= context
.preferences
.system
.ui_scale
455 self
.sctx
.set_pixel_dist(12 * ui_scale
)
457 if SnapUtilities
.snapwidgets
:
458 widget
= SnapUtilities
.snapwidgets
[-1]
460 self
.obj
= widget
.snap_obj
.data
[0] if widget
.snap_obj
else context
.active_object
462 self
.geom
= widget
.geom
463 self
.type = widget
.type
464 self
.location
= widget
.location
465 self
.preferences
= widget
.preferences
466 self
.draw_cache
= widget
.draw_cache
467 if hasattr(widget
, "normal"):
468 self
.normal
= widget
.normal
471 # init these variables to avoid errors
472 self
.obj
= context
.active_object
476 self
.location
= Vector()
478 preferences
= context
.preferences
.addons
[__package__
].preferences
479 self
.preferences
= preferences
482 self
.draw_cache
= SnapDrawn(
483 preferences
.out_color
,
484 preferences
.face_color
,
485 preferences
.edge_color
,
486 preferences
.vert_color
,
487 preferences
.center_color
,
488 preferences
.perpendicular_color
,
489 preferences
.constrain_shift_color
,
491 context
.preferences
.themes
[0].user_interface
.axis_x
) + (1.0,),
493 context
.preferences
.themes
[0].user_interface
.axis_y
) + (1.0,),
495 context
.preferences
.themes
[0].user_interface
.axis_z
) + (1.0,),
499 self
.snap_vert
= self
.snap_edge
= snap_edge_and_vert
501 shading
= context
.space_data
.shading
502 self
.snap_face
= not (snap_edge_and_vert
and (
503 shading
.show_xray
or shading
.type == 'WIREFRAME'))
505 self
.sctx
.set_snap_mode(self
.snap_vert
, self
.snap_edge
, self
.snap_face
)
507 # Configure the unit of measure
508 unit_system
= context
.scene
.unit_settings
.system
509 scale
= context
.scene
.unit_settings
.scale_length
510 scale
/= context
.space_data
.overlay
.grid_scale
511 self
.rd
= bpy
.utils
.units
.to_value(
512 unit_system
, 'LENGTH', str(1 / scale
))
514 self
.incremental
= bpy
.utils
.units
.to_value(
515 unit_system
, 'LENGTH', str(self
.preferences
.incremental
))
517 def snap_to_grid(self
):
518 if self
.type == 'OUT' and self
.preferences
.increments_grid
:
519 loc
= self
.location
/ self
.rd
520 self
.location
= Vector((round(loc
.x
),
522 round(loc
.z
))) * self
.rd
524 def snap_context_free(self
):
539 SnapUtilities
.constrain
= None