Export_3ds: Improved distance cue node search
[blender-addons.git] / mesh_snap_utilities_line / common_classes.py
blob2d36280439fd1c33d61c329421452a85ed0f769b
1 # SPDX-FileCopyrightText: 2018-2022 Blender Foundation
3 # SPDX-License-Identifier: GPL-2.0-or-later
5 import bpy
7 from mathutils import (
8 Vector,
9 Matrix,
11 from mathutils.geometry import intersect_point_line
12 from .drawing_utilities import SnapDrawn
13 from .common_utilities import (
14 convert_distance,
15 get_units_info,
16 location_3d_to_region_2d,
20 class SnapNavigation():
21 __slots__ = (
22 'use_ndof',
23 '_rotate',
24 '_move',
25 '_zoom',
26 '_ndof_all',
27 '_ndof_orbit',
28 '_ndof_orbit_zoom',
29 '_ndof_pan')
31 @staticmethod
32 def debug_key(key):
33 for member in dir(key):
34 print(member, getattr(key, member))
36 @staticmethod
37 def convert_to_flag(shift, ctrl, alt):
38 return (shift << 0) | (ctrl << 1) | (alt << 2)
40 def __init__(self, context, use_ndof):
41 # TO DO:
42 # 'View Orbit', 'View Pan', 'NDOF Orbit View', 'NDOF Pan View'
43 self.use_ndof = use_ndof and bpy.app.build_options.input_ndof
45 self._rotate = set()
46 self._move = set()
47 self._zoom = set()
49 if self.use_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))
69 else:
70 self._zoom.add((self.convert_to_flag(
71 key.shift, key.ctrl, key.alt), key.type, key.value, key.properties.delta))
73 elif self.use_ndof:
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:
92 if snap_location:
93 bpy.ops.view3d.rotate_custom_pivot(
94 'INVOKE_DEFAULT', pivot=snap_location)
95 else:
96 bpy.ops.view3d.rotate('INVOKE_DEFAULT', use_cursor_init=True)
97 return True
99 if evkey in self._move:
100 bpy.ops.view3d.move('INVOKE_DEFAULT')
101 return True
103 for key in self._zoom:
104 if evkey == key[0:3]:
105 if key[3]:
106 if snap_location:
107 bpy.ops.view3d.zoom_custom_target(
108 'INVOKE_DEFAULT', delta=key[3], target=snap_location)
109 else:
110 bpy.ops.view3d.zoom('INVOKE_DEFAULT', delta=key[3])
111 else:
112 bpy.ops.view3d.zoom('INVOKE_DEFAULT')
113 return True
115 if self.use_ndof:
116 ndofkey = evkey[:2]
117 if ndofkey in self._ndof_all:
118 bpy.ops.view3d.ndof_all('INVOKE_DEFAULT')
119 return True
120 if ndofkey in self._ndof_orbit:
121 bpy.ops.view3d.ndof_orbit('INVOKE_DEFAULT')
122 return True
123 if ndofkey in self._ndof_orbit_zoom:
124 bpy.ops.view3d.ndof_orbit_zoom('INVOKE_DEFAULT')
125 return True
126 if ndofkey in self._ndof_pan:
127 bpy.ops.view3d.ndof_pan('INVOKE_DEFAULT')
128 return True
130 return False
133 class CharMap:
134 __slots__ = (
135 'unit_system',
136 'uinfo',
137 'length_entered',
138 'length_entered_value',
139 'line_pos')
141 ascii = {
142 ".", ",", "-", "+", "1", "2", "3",
143 "4", "5", "6", "7", "8", "9", "0",
144 "c", "m", "d", "k", "h", "a",
145 " ", "/", "*", "'", "\""
146 # "="
148 type = {
149 'BACK_SPACE', 'DEL',
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)
159 self.clear()
161 def modal_(self, context, event):
162 if event.value == 'PRESS':
163 type = event.type
164 ascii = event.ascii
165 if (type in self.type) or (ascii in self.ascii):
166 if ascii:
167 pos = self.line_pos
168 if ascii == ",":
169 ascii = "."
170 self.length_entered = self.length_entered[:pos] + \
171 ascii + self.length_entered[pos:]
172 self.line_pos += 1
174 if self.length_entered:
175 pos = self.line_pos
176 if type == 'BACK_SPACE':
177 self.length_entered = self.length_entered[:pos -
178 1] + self.length_entered[pos:]
179 self.line_pos -= 1
181 elif type == 'DEL':
182 self.length_entered = self.length_entered[:pos] + \
183 self.length_entered[pos + 1:]
185 elif type == 'LEFT_ARROW':
186 self.line_pos = (
187 pos - 1) % (len(self.length_entered) + 1)
189 elif type == 'RIGHT_ARROW':
190 self.line_pos = (
191 pos + 1) % (len(self.length_entered) + 1)
193 try:
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")
199 else:
200 self.length_entered_value = 0.0
202 return True
204 return False
206 def get_converted_length_str(self, length):
207 if self.length_entered:
208 pos = self.line_pos
209 ret = self.length_entered[:pos] + '|' + self.length_entered[pos:]
210 else:
211 ret = convert_distance(length, self.uinfo)
213 return ret
215 def clear(self):
216 self.length_entered = ''
217 self.length_entered_value = 0.0
218 self.line_pos = 0
221 class Constrain:
222 def __init__(self, prefs, scene, obj):
223 self.last_type = None
224 self.last_vec = None
225 self.rotMat = 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)
232 else:
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)
243 vec.normalize()
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]
251 type = 'X'
253 elif dot_y > dot_x and dot_y > dot_z:
254 vec = self.orientation[self.orientation_id][1]
255 type = 'Y'
257 else: # dot_z > dot_y and dot_z > dot_x:
258 vec = self.orientation[self.orientation_id][2]
259 type = 'Z'
261 return vec, type
263 def modal(self, event, shift_callback):
264 type = event.type
265 if self.last_type == type:
266 self.orientation_id += 1
268 if type == 'X':
269 if self.orientation_id < 2:
270 self.last_vec = self.orientation[self.orientation_id][0]
271 else:
272 self.orientation_id = 0
273 self.last_vec = type = None
274 elif type == 'Y':
275 if self.orientation_id < 2:
276 self.last_vec = self.orientation[self.orientation_id][1]
277 else:
278 self.orientation_id = 0
279 self.last_vec = type = None
280 elif type == 'Z':
281 if self.orientation_id < 2:
282 self.last_vec = self.orientation[self.orientation_id][2]
283 else:
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:
288 type = 'shift'
289 self.last_vec = shift_callback()
290 else:
291 self.orientation_id = 0
292 self.last_vec = type = None
293 else:
294 return False
296 self.preferences.auto_constrain = False
297 self.last_type = type
298 return True
300 def toggle(self):
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
305 else:
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)
333 class SnapUtilities:
335 __slots__ = (
336 "sctx",
337 "draw_cache",
338 "outer_verts",
339 "unit_system",
340 "rd",
341 "obj",
342 "bm",
343 "geom",
344 "type",
345 "location",
346 "preferences",
347 "normal",
348 "snap_vert",
349 "snap_edge",
350 "snap_face",
351 "incremental",
355 constrain_keys = {
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',
363 snapwidgets = []
364 constrain = None
366 @staticmethod
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)
373 return
375 if hasattr(widget, "normal"):
376 if key == 'shift':
377 import bmesh
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()
382 else:
383 return
384 else:
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()
392 children = 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)
401 parent = obj.parent
402 if parent in moving_objects:
403 children.update(temp_children)
404 temp_children.clear()
405 obj = parent
407 del temp_children
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)
416 if is_moving:
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)
423 if is_moving:
424 moving_snp_objects.add(snap_obj)
426 del children
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)
439 else:
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
461 self.bm = widget.bm
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
470 else:
471 # init these variables to avoid errors
472 self.obj = context.active_object
473 self.bm = None
474 self.geom = None
475 self.type = 'OUT'
476 self.location = Vector()
478 preferences = context.preferences.addons[__package__].preferences
479 self.preferences = preferences
481 # Init DrawCache
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,
490 tuple(
491 context.preferences.themes[0].user_interface.axis_x) + (1.0,),
492 tuple(
493 context.preferences.themes[0].user_interface.axis_y) + (1.0,),
494 tuple(
495 context.preferences.themes[0].user_interface.axis_z) + (1.0,),
496 self.sctx.rv3d,
497 ui_scale)
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),
521 round(loc.y),
522 round(loc.z))) * self.rd
524 def snap_context_free(self):
525 self.sctx = None
526 del self.sctx
528 del self.bm
529 del self.draw_cache
530 del self.geom
531 del self.location
532 del self.rd
533 del self.snap_face
534 del self.snap_obj
535 del self.type
537 del self.preferences
539 SnapUtilities.constrain = None