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 #####
19 from mathutils
import (
23 from mathutils
.geometry
import intersect_point_line
24 from .drawing_utilities
import SnapDrawn
25 from .common_utilities
import (
28 location_3d_to_region_2d
,
32 class SnapNavigation():
46 for member
in dir(key
):
47 print(member
, getattr(key
, member
))
50 def convert_to_flag(shift
, ctrl
, alt
):
51 return (shift
<< 0) |
(ctrl
<< 1) |
(alt
<< 2)
53 def __init__(self
, context
, use_ndof
):
55 # 'View Orbit', 'View Pan', 'NDOF Orbit View', 'NDOF Pan View'
56 self
.use_ndof
= use_ndof
and context
.preferences
.inputs
.use_ndof
63 self
._ndof
_all
= set()
64 self
._ndof
_orbit
= set()
65 self
._ndof
_orbit
_zoom
= set()
66 self
._ndof
_pan
= set()
68 for key
in context
.window_manager
.keyconfigs
.user
.keymaps
['3D View'].keymap_items
:
69 if key
.idname
== 'view3d.rotate':
70 self
._rotate
.add((self
.convert_to_flag(key
.shift
, key
.ctrl
, key
.alt
), key
.type, key
.value
))
71 elif key
.idname
== 'view3d.move':
72 self
._move
.add((self
.convert_to_flag(key
.shift
, key
.ctrl
, key
.alt
), key
.type, key
.value
))
73 elif key
.idname
== 'view3d.zoom':
74 if key
.type == 'WHEELINMOUSE':
75 self
._zoom
.add((self
.convert_to_flag(key
.shift
, key
.ctrl
, key
.alt
), 'WHEELUPMOUSE', key
.value
, key
.properties
.delta
))
76 elif key
.type == 'WHEELOUTMOUSE':
77 self
._zoom
.add((self
.convert_to_flag(key
.shift
, key
.ctrl
, key
.alt
), 'WHEELDOWNMOUSE', key
.value
, key
.properties
.delta
))
79 self
._zoom
.add((self
.convert_to_flag(key
.shift
, key
.ctrl
, key
.alt
), key
.type, key
.value
, key
.properties
.delta
))
82 if key
.idname
== 'view3d.ndof_all':
83 self
._ndof
_all
.add((self
.convert_to_flag(key
.shift
, key
.ctrl
, key
.alt
), key
.type))
84 elif key
.idname
== 'view3d.ndof_orbit':
85 self
._ndof
_orbit
.add((self
.convert_to_flag(key
.shift
, key
.ctrl
, key
.alt
), key
.type))
86 elif key
.idname
== 'view3d.ndof_orbit_zoom':
87 self
._ndof
_orbit
_zoom
.add((self
.convert_to_flag(key
.shift
, key
.ctrl
, key
.alt
), key
.type))
88 elif key
.idname
== 'view3d.ndof_pan':
89 self
._ndof
_pan
.add((self
.convert_to_flag(key
.shift
, key
.ctrl
, key
.alt
), key
.type))
92 def run(self
, context
, event
, snap_location
):
93 evkey
= (self
.convert_to_flag(event
.shift
, event
.ctrl
, event
.alt
), event
.type, event
.value
)
95 if evkey
in self
._rotate
:
97 bpy
.ops
.view3d
.rotate_custom_pivot('INVOKE_DEFAULT', pivot
=snap_location
)
99 bpy
.ops
.view3d
.rotate('INVOKE_DEFAULT', use_cursor_init
=True)
102 if evkey
in self
._move
:
103 bpy
.ops
.view3d
.move('INVOKE_DEFAULT')
106 for key
in self
._zoom
:
107 if evkey
== key
[0:3]:
110 bpy
.ops
.view3d
.zoom_custom_target('INVOKE_DEFAULT', delta
=key
[3], target
=snap_location
)
112 bpy
.ops
.view3d
.zoom('INVOKE_DEFAULT', delta
=key
[3])
114 bpy
.ops
.view3d
.zoom('INVOKE_DEFAULT')
119 if ndofkey
in self
._ndof
_all
:
120 bpy
.ops
.view3d
.ndof_all('INVOKE_DEFAULT')
122 if ndofkey
in self
._ndof
_orbit
:
123 bpy
.ops
.view3d
.ndof_orbit('INVOKE_DEFAULT')
125 if ndofkey
in self
._ndof
_orbit
_zoom
:
126 bpy
.ops
.view3d
.ndof_orbit_zoom('INVOKE_DEFAULT')
128 if ndofkey
in self
._ndof
_pan
:
129 bpy
.ops
.view3d
.ndof_pan('INVOKE_DEFAULT')
140 'length_entered_value',
144 ".", ",", "-", "+", "1", "2", "3",
145 "4", "5", "6", "7", "8", "9", "0",
146 "c", "m", "d", "k", "h", "a",
147 " ", "/", "*", "'", "\""
152 'LEFT_ARROW', 'RIGHT_ARROW'
155 def __init__(self
, context
):
156 scale
= context
.scene
.unit_settings
.scale_length
157 separate_units
= context
.scene
.unit_settings
.use_separate
158 self
.unit_system
= context
.scene
.unit_settings
.system
159 self
.uinfo
= get_units_info(scale
, self
.unit_system
, separate_units
)
163 def modal_(self
, context
, event
):
164 if event
.value
== 'PRESS':
167 if (type in self
.type) or (ascii
in self
.ascii
):
172 self
.length_entered
= self
.length_entered
[:pos
] + ascii
+ self
.length_entered
[pos
:]
175 if self
.length_entered
:
177 if type == 'BACK_SPACE':
178 self
.length_entered
= self
.length_entered
[:pos
- 1] + self
.length_entered
[pos
:]
182 self
.length_entered
= self
.length_entered
[:pos
] + self
.length_entered
[pos
+ 1:]
184 elif type == 'LEFT_ARROW':
185 self
.line_pos
= (pos
- 1) % (len(self
.length_entered
) + 1)
187 elif type == 'RIGHT_ARROW':
188 self
.line_pos
= (pos
+ 1) % (len(self
.length_entered
) + 1)
191 self
.length_entered_value
= bpy
.utils
.units
.to_value(self
.unit_system
, 'LENGTH', self
.length_entered
)
192 except: # ValueError:
193 self
.length_entered_value
= 0.0 #invalid
194 #self.report({'INFO'}, "Operation not supported yet")
196 self
.length_entered_value
= 0.0
202 def get_converted_length_str(self
, length
):
203 if self
.length_entered
:
205 ret
= self
.length_entered
[:pos
] + '|' + self
.length_entered
[pos
:]
207 ret
= convert_distance(length
, self
.uinfo
)
212 self
.length_entered
= ''
213 self
.length_entered_value
= 0.0
218 def __init__(self
, peferences
, scene
, obj
):
219 self
.last_type
= None
222 self
.preferences
= peferences
223 trans_orient
= scene
.transform_orientation_slots
[0]
224 self
.orientation
= [None, None]
225 if trans_orient
.type == 'LOCAL':
226 self
.orientation
[0] = obj
.matrix_world
.to_3x3().transposed()
227 self
.orientation
[1] = Matrix
.Identity(3)
229 self
.orientation
[0] = Matrix
.Identity(3)
230 self
.orientation
[1] = obj
.matrix_world
.to_3x3().transposed()
232 self
.orientation_id
= 0
233 self
.center
= Vector((0.0, 0.0, 0.0))
234 self
.center_2d
= Vector((0.0, 0.0))
235 self
.projected_vecs
= Matrix(([0.0, 0.0], [0.0, 0.0], [0.0, 0.0]))
237 def _constrain_set(self
, mcursor
):
238 vec
= (mcursor
- self
.center_2d
)
241 dot_x
= abs(vec
.dot(self
.projected_vecs
[0]))
242 dot_y
= abs(vec
.dot(self
.projected_vecs
[1]))
243 dot_z
= abs(vec
.dot(self
.projected_vecs
[2]))
245 if dot_x
> dot_y
and dot_x
> dot_z
:
246 vec
= self
.orientation
[self
.orientation_id
][0]
249 elif dot_y
> dot_x
and dot_y
> dot_z
:
250 vec
= self
.orientation
[self
.orientation_id
][1]
253 else: # dot_z > dot_y and dot_z > dot_x:
254 vec
= self
.orientation
[self
.orientation_id
][2]
259 def modal(self
, event
, shift_callback
):
261 if self
.last_type
== type:
262 self
.orientation_id
+= 1
265 if self
.orientation_id
< 2:
266 self
.last_vec
= self
.orientation
[self
.orientation_id
][0]
268 self
.orientation_id
= 0
269 self
.last_vec
= type = None
271 if self
.orientation_id
< 2:
272 self
.last_vec
= self
.orientation
[self
.orientation_id
][1]
274 self
.orientation_id
= 0
275 self
.last_vec
= type = None
277 if self
.orientation_id
< 2:
278 self
.last_vec
= self
.orientation
[self
.orientation_id
][2]
280 self
.orientation_id
= 0
281 self
.last_vec
= type = None
282 elif shift_callback
and type in {'RIGHT_SHIFT', 'LEFT_SHIFT'}:
283 if self
.orientation_id
< 1:
285 self
.last_vec
= shift_callback()
287 self
.orientation_id
= 0
288 self
.last_vec
= type = None
292 self
.preferences
.auto_constrain
= False
293 self
.last_type
= type
297 self
.rotMat
= None # update
298 if self
.preferences
.auto_constrain
:
299 self
.orientation_id
= (self
.orientation_id
+ 1) % 2
300 self
.preferences
.auto_constrain
= self
.orientation_id
!= 0
302 self
.preferences
.auto_constrain
= True
304 def update(self
, region
, rv3d
, mcursor
, center
):
305 if rv3d
.view_matrix
!= self
.rotMat
or self
.center
!= center
:
306 self
.rotMat
= rv3d
.view_matrix
.copy()
308 self
.center
= center
.copy()
309 self
.center_2d
= location_3d_to_region_2d(region
, rv3d
, self
.center
)
311 vec
= self
.center
+ self
.orientation
[self
.orientation_id
][0]
312 self
.projected_vecs
[0] = location_3d_to_region_2d(region
, rv3d
, vec
) - self
.center_2d
313 vec
= self
.center
+ self
.orientation
[self
.orientation_id
][1]
314 self
.projected_vecs
[1] = location_3d_to_region_2d(region
, rv3d
, vec
) - self
.center_2d
315 vec
= self
.center
+ self
.orientation
[self
.orientation_id
][2]
316 self
.projected_vecs
[2] = location_3d_to_region_2d(region
, rv3d
, vec
) - self
.center_2d
318 self
.projected_vecs
[0].normalize()
319 self
.projected_vecs
[1].normalize()
320 self
.projected_vecs
[2].normalize()
322 return self
._constrain
_set
(mcursor
)
348 'X': Vector((1,0,0)),
349 'Y': Vector((0,1,0)),
350 'Z': Vector((0,0,1)),
351 'RIGHT_SHIFT': 'shift',
352 'LEFT_SHIFT': 'shift',
359 def set_contrain(context
, key
):
360 widget
= SnapUtilities
.snapwidgets
[-1] if SnapUtilities
.snapwidgets
else None
361 if SnapUtilities
.constrain
== key
:
362 SnapUtilities
.constrain
= None
363 if hasattr(widget
, "get_normal"):
364 widget
.get_normal(context
)
367 if hasattr(widget
, "normal"):
370 if isinstance(widget
.geom
, bmesh
.types
.BMEdge
):
371 verts
= widget
.geom
.verts
372 widget
.normal
= verts
[1].co
- verts
[0].co
373 widget
.normal
.normalise()
377 widget
.normal
= SnapUtilities
.constrain_keys
[key
]
379 SnapUtilities
.constrain
= key
382 def snap_context_update_and_return_moving_objects(self
, context
):
383 moving_objects
= set()
384 moving_snp_objects
= set()
386 for obj
in context
.view_layer
.objects
.selected
:
387 moving_objects
.add(obj
)
389 temp_children
= set()
390 for obj
in context
.visible_objects
:
391 temp_children
.clear()
392 while obj
.parent
is not None:
393 temp_children
.add(obj
)
395 if parent
in moving_objects
:
396 children
.update(temp_children
)
397 temp_children
.clear()
402 moving_objects
.difference_update(children
)
404 self
.sctx
.clear_snap_objects(True)
406 for obj
in context
.visible_objects
:
407 is_moving
= obj
in moving_objects
or obj
in children
408 snap_obj
= self
.sctx
.add_obj(obj
, obj
.matrix_world
)
410 moving_snp_objects
.add(snap_obj
)
412 if obj
.instance_type
== 'COLLECTION':
413 mat
= obj
.matrix_world
.copy()
414 for ob
in obj
.instance_collection
.objects
:
415 snap_obj
= self
.sctx
.add_obj(ob
, mat
@ ob
.matrix_world
)
417 moving_snp_objects
.add(snap_obj
)
420 return moving_objects
, moving_snp_objects
423 def snap_context_update(self
, context
):
424 def visible_objects_and_duplis():
425 if self
.preferences
.outer_verts
:
426 for obj
in context
.visible_objects
:
427 yield (obj
, obj
.matrix_world
)
429 if obj
.instance_type
== 'COLLECTION':
430 mat
= obj
.matrix_world
.copy()
431 for ob
in obj
.instance_collection
.objects
:
432 yield (ob
, mat
@ ob
.matrix_world
)
434 for obj
in context
.objects_in_mode_unique_data
:
435 yield (obj
, obj
.matrix_world
)
437 self
.sctx
.clear_snap_objects(True)
439 for obj
, matrix
in visible_objects_and_duplis():
440 self
.sctx
.add_obj(obj
, matrix
)
443 def snap_context_init(self
, context
, snap_edge_and_vert
=True):
444 from .snap_context_l
import global_snap_context_get
447 self
.sctx
= global_snap_context_get(context
.evaluated_depsgraph_get(), context
.region
, context
.space_data
)
448 self
.sctx
.set_pixel_dist(12)
449 self
.sctx
.use_clip_planes(True)
451 if SnapUtilities
.snapwidgets
:
452 widget
= SnapUtilities
.snapwidgets
[-1]
454 self
.obj
= widget
.snap_obj
.data
[0] if widget
.snap_obj
else context
.active_object
456 self
.geom
= widget
.geom
457 self
.type = widget
.type
458 self
.location
= widget
.location
459 self
.preferences
= widget
.preferences
460 self
.draw_cache
= widget
.draw_cache
461 if hasattr(widget
, "normal"):
462 self
.normal
= widget
.normal
465 #init these variables to avoid errors
466 self
.obj
= context
.active_object
470 self
.location
= Vector()
472 preferences
= context
.preferences
.addons
[__package__
].preferences
473 self
.preferences
= preferences
475 self
.draw_cache
= SnapDrawn(
476 preferences
.out_color
,
477 preferences
.face_color
,
478 preferences
.edge_color
,
479 preferences
.vert_color
,
480 preferences
.center_color
,
481 preferences
.perpendicular_color
,
482 preferences
.constrain_shift_color
,
483 tuple(context
.preferences
.themes
[0].user_interface
.axis_x
) + (1.0,),
484 tuple(context
.preferences
.themes
[0].user_interface
.axis_y
) + (1.0,),
485 tuple(context
.preferences
.themes
[0].user_interface
.axis_z
) + (1.0,))
487 self
.snap_vert
= self
.snap_edge
= snap_edge_and_vert
489 shading
= context
.space_data
.shading
490 self
.snap_face
= not (snap_edge_and_vert
and (shading
.show_xray
or shading
.type == 'WIREFRAME'))
492 self
.sctx
.set_snap_mode(self
.snap_vert
, self
.snap_edge
, self
.snap_face
)
494 #Configure the unit of measure
495 unit_system
= context
.scene
.unit_settings
.system
496 scale
= context
.scene
.unit_settings
.scale_length
497 scale
/= context
.space_data
.overlay
.grid_scale
498 self
.rd
= bpy
.utils
.units
.to_value(unit_system
, 'LENGTH', str(1 / scale
))
500 self
.incremental
= bpy
.utils
.units
.to_value(unit_system
, 'LENGTH', str(self
.preferences
.incremental
))
502 def snap_to_grid(self
):
503 if self
.type == 'OUT' and self
.preferences
.increments_grid
:
504 loc
= self
.location
/ self
.rd
505 self
.location
= Vector((round(loc
.x
),
507 round(loc
.z
))) * self
.rd
509 def snap_context_free(self
):
524 SnapUtilities
.constrain
= None