Newer
Older
# SPDX-License-Identifier: GPL-2.0-or-later
from mathutils import (
Vector,
Matrix,
from mathutils.geometry import intersect_point_line
from .common_utilities import (
convert_distance,
get_units_info,
location_3d_to_region_2d,
class SnapNavigation():
__slots__ = (
'use_ndof',
'_rotate',
'_move',
'_zoom',
'_ndof_all',
'_ndof_orbit',
'_ndof_orbit_zoom',
'_ndof_pan')
@staticmethod
def debug_key(key):
for member in dir(key):
print(member, getattr(key, member))
@staticmethod
def convert_to_flag(shift, ctrl, alt):
return (shift << 0) | (ctrl << 1) | (alt << 2)
def __init__(self, context, use_ndof):
# TO DO:
# 'View Orbit', 'View Pan', 'NDOF Orbit View', 'NDOF Pan View'
self.use_ndof = use_ndof and context.preferences.inputs.use_ndof
self._rotate = set()
self._move = set()
self._zoom = set()
if self.use_ndof:
self._ndof_all = set()
self._ndof_orbit = set()
self._ndof_orbit_zoom = set()
self._ndof_pan = set()
for key in context.window_manager.keyconfigs.user.keymaps['3D View'].keymap_items:
if key.idname == 'view3d.rotate':
self._rotate.add((self.convert_to_flag(
key.shift, key.ctrl, key.alt), key.type, key.value))
elif key.idname == 'view3d.move':
self._move.add((self.convert_to_flag(
key.shift, key.ctrl, key.alt), key.type, key.value))
elif key.idname == 'view3d.zoom':
if key.type == 'WHEELINMOUSE':
self._zoom.add((self.convert_to_flag(
key.shift, key.ctrl, key.alt), 'WHEELUPMOUSE', key.value, key.properties.delta))
elif key.type == 'WHEELOUTMOUSE':
self._zoom.add((self.convert_to_flag(
key.shift, key.ctrl, key.alt), 'WHEELDOWNMOUSE', key.value, key.properties.delta))
self._zoom.add((self.convert_to_flag(
key.shift, key.ctrl, key.alt), key.type, key.value, key.properties.delta))
elif self.use_ndof:
if key.idname == 'view3d.ndof_all':
self._ndof_all.add((self.convert_to_flag(
key.shift, key.ctrl, key.alt), key.type))
elif key.idname == 'view3d.ndof_orbit':
self._ndof_orbit.add((self.convert_to_flag(
key.shift, key.ctrl, key.alt), key.type))
elif key.idname == 'view3d.ndof_orbit_zoom':
self._ndof_orbit_zoom.add(
(self.convert_to_flag(key.shift, key.ctrl, key.alt), key.type))
elif key.idname == 'view3d.ndof_pan':
self._ndof_pan.add((self.convert_to_flag(
key.shift, key.ctrl, key.alt), key.type))
def run(self, context, event, snap_location):
evkey = (self.convert_to_flag(event.shift, event.ctrl,
event.alt), event.type, event.value)
if evkey in self._rotate:
mano-wii
committed
if snap_location:
bpy.ops.view3d.rotate_custom_pivot(
'INVOKE_DEFAULT', pivot=snap_location)
mano-wii
committed
else:
bpy.ops.view3d.rotate('INVOKE_DEFAULT', use_cursor_init=True)
return True
if evkey in self._move:
bpy.ops.view3d.move('INVOKE_DEFAULT')
return True
for key in self._zoom:
if evkey == key[0:3]:
if key[3]:
if snap_location:
bpy.ops.view3d.zoom_custom_target(
'INVOKE_DEFAULT', delta=key[3], target=snap_location)
else:
bpy.ops.view3d.zoom('INVOKE_DEFAULT', delta=key[3])
bpy.ops.view3d.zoom('INVOKE_DEFAULT')
return True
if self.use_ndof:
ndofkey = evkey[:2]
if ndofkey in self._ndof_all:
bpy.ops.view3d.ndof_all('INVOKE_DEFAULT')
return True
if ndofkey in self._ndof_orbit:
bpy.ops.view3d.ndof_orbit('INVOKE_DEFAULT')
return True
if ndofkey in self._ndof_orbit_zoom:
bpy.ops.view3d.ndof_orbit_zoom('INVOKE_DEFAULT')
return True
if ndofkey in self._ndof_pan:
bpy.ops.view3d.ndof_pan('INVOKE_DEFAULT')
return True
return False
class CharMap:
__slots__ = (
'unit_system',
'uinfo',
'length_entered',
'length_entered_value',
'line_pos')
ascii = {
".", ",", "-", "+", "1", "2", "3",
"4", "5", "6", "7", "8", "9", "0",
"c", "m", "d", "k", "h", "a",
" ", "/", "*", "'", "\""
# "="
type = {
'BACK_SPACE', 'DEL',
'LEFT_ARROW', 'RIGHT_ARROW'
def __init__(self, context):
scale = context.scene.unit_settings.scale_length
separate_units = context.scene.unit_settings.use_separate
self.unit_system = context.scene.unit_settings.system
self.uinfo = get_units_info(scale, self.unit_system, separate_units)
self.clear()
def modal_(self, context, event):
if event.value == 'PRESS':
type = event.type
ascii = event.ascii
if (type in self.type) or (ascii in self.ascii):
if ascii:
pos = self.line_pos
if ascii == ",":
ascii = "."
self.length_entered = self.length_entered[:pos] + \
ascii + self.length_entered[pos:]
self.line_pos += 1
if self.length_entered:
pos = self.line_pos
if type == 'BACK_SPACE':
self.length_entered = self.length_entered[:pos -
1] + self.length_entered[pos:]
self.line_pos -= 1
elif type == 'DEL':
self.length_entered = self.length_entered[:pos] + \
self.length_entered[pos + 1:]
self.line_pos = (
pos - 1) % (len(self.length_entered) + 1)
self.line_pos = (
pos + 1) % (len(self.length_entered) + 1)
self.length_entered_value = bpy.utils.units.to_value(
self.unit_system, 'LENGTH', self.length_entered)
self.length_entered_value = 0.0 # invalid
# self.report({'INFO'}, "Operation not supported yet")
else:
self.length_entered_value = 0.0
return False
def get_converted_length_str(self, length):
if self.length_entered:
pos = self.line_pos
ret = self.length_entered[:pos] + '|' + self.length_entered[pos:]
else:
ret = convert_distance(length, self.uinfo)
def clear(self):
self.length_entered = ''
self.length_entered_value = 0.0
self.line_pos = 0
mano-wii
committed
self.last_type = None
self.last_vec = None
self.rotMat = None
trans_orient = scene.transform_orientation_slots[0]
self.orientation = [None, None]
if trans_orient.type == 'LOCAL':
self.orientation[0] = obj.matrix_world.to_3x3().transposed()
self.orientation[1] = Matrix.Identity(3)
else:
self.orientation[0] = Matrix.Identity(3)
self.orientation[1] = obj.matrix_world.to_3x3().transposed()
self.orientation_id = 0
self.center = Vector((0.0, 0.0, 0.0))
self.center_2d = Vector((0.0, 0.0))
self.projected_vecs = Matrix(([0.0, 0.0], [0.0, 0.0], [0.0, 0.0]))
def _constrain_set(self, mcursor):
vec = (mcursor - self.center_2d)
vec.normalize()
dot_x = abs(vec.dot(self.projected_vecs[0]))
dot_y = abs(vec.dot(self.projected_vecs[1]))
dot_z = abs(vec.dot(self.projected_vecs[2]))
if dot_x > dot_y and dot_x > dot_z:
vec = self.orientation[self.orientation_id][0]
type = 'X'
elif dot_y > dot_x and dot_y > dot_z:
vec = self.orientation[self.orientation_id][1]
type = 'Y'
else: # dot_z > dot_y and dot_z > dot_x:
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
vec = self.orientation[self.orientation_id][2]
type = 'Z'
return vec, type
def modal(self, event, shift_callback):
type = event.type
if self.last_type == type:
self.orientation_id += 1
if type == 'X':
if self.orientation_id < 2:
self.last_vec = self.orientation[self.orientation_id][0]
else:
self.orientation_id = 0
self.last_vec = type = None
elif type == 'Y':
if self.orientation_id < 2:
self.last_vec = self.orientation[self.orientation_id][1]
else:
self.orientation_id = 0
self.last_vec = type = None
elif type == 'Z':
if self.orientation_id < 2:
self.last_vec = self.orientation[self.orientation_id][2]
else:
self.orientation_id = 0
self.last_vec = type = None
elif shift_callback and type in {'RIGHT_SHIFT', 'LEFT_SHIFT'}:
if self.orientation_id < 1:
type = 'shift'
self.last_vec = shift_callback()
else:
self.orientation_id = 0
self.last_vec = type = None
else:
return False
self.preferences.auto_constrain = False
self.last_type = type
return True
if self.preferences.auto_constrain:
self.orientation_id = (self.orientation_id + 1) % 2
self.preferences.auto_constrain = self.orientation_id != 0
else:
self.preferences.auto_constrain = True
def update(self, region, rv3d, mcursor, center):
if rv3d.view_matrix != self.rotMat or self.center != center:
self.rotMat = rv3d.view_matrix.copy()
self.center_2d = location_3d_to_region_2d(
region, rv3d, self.center)
vec = self.center + self.orientation[self.orientation_id][0]
self.projected_vecs[0] = location_3d_to_region_2d(
region, rv3d, vec) - self.center_2d
vec = self.center + self.orientation[self.orientation_id][1]
self.projected_vecs[1] = location_3d_to_region_2d(
region, rv3d, vec) - self.center_2d
vec = self.center + self.orientation[self.orientation_id][2]
self.projected_vecs[2] = location_3d_to_region_2d(
region, rv3d, vec) - self.center_2d
self.projected_vecs[0].normalize()
self.projected_vecs[1].normalize()
self.projected_vecs[2].normalize()
return self._constrain_set(mcursor)
"""
__slots__ = (
"sctx",
"draw_cache",
"outer_verts",
"unit_system",
"rd",
"obj",
"bm",
"geom",
"type",
"location",
"preferences",
"normal",
"snap_vert",
"snap_edge",
"snap_face",
"incremental",
)
"""
'X': Vector((1, 0, 0)),
'Y': Vector((0, 1, 0)),
'Z': Vector((0, 0, 1)),
'RIGHT_SHIFT': 'shift',
'LEFT_SHIFT': 'shift',
snapwidgets = []
constrain = None
@staticmethod
def set_contrain(context, key):
widget = SnapUtilities.snapwidgets[-1] if SnapUtilities.snapwidgets else None
if SnapUtilities.constrain == key:
SnapUtilities.constrain = None
if hasattr(widget, "get_normal"):
widget.get_normal(context)
if hasattr(widget, "normal"):
if key == 'shift':
import bmesh
if isinstance(widget.geom, bmesh.types.BMEdge):
verts = widget.geom.verts
widget.normal = verts[1].co - verts[0].co
widget.normal.normalise()
else:
return
else:
widget.normal = SnapUtilities.constrain_keys[key]
SnapUtilities.constrain = key
def snap_context_update_and_return_moving_objects(self, context):
moving_objects = set()
moving_snp_objects = set()
children = set()
for obj in context.view_layer.objects.selected:
moving_objects.add(obj)
temp_children = set()
for obj in context.visible_objects:
temp_children.clear()
while obj.parent is not None:
temp_children.add(obj)
parent = obj.parent
if parent in moving_objects:
children.update(temp_children)
temp_children.clear()
obj = parent
del temp_children
moving_objects.difference_update(children)
self.sctx.clear_snap_objects(True)
for obj in context.visible_objects:
is_moving = obj in moving_objects or obj in children
snap_obj = self.sctx.add_obj(obj, obj.matrix_world)
if is_moving:
moving_snp_objects.add(snap_obj)
if obj.instance_type == 'COLLECTION':
mat = obj.matrix_world.copy()
for ob in obj.instance_collection.objects:
snap_obj = self.sctx.add_obj(ob, mat @ ob.matrix_world)
if is_moving:
moving_snp_objects.add(snap_obj)
del children
return moving_objects, moving_snp_objects
def snap_context_update(self, context):
def visible_objects_and_duplis():
if self.preferences.outer_verts:
for obj in context.visible_objects:
yield (obj, obj.matrix_world)
if obj.instance_type == 'COLLECTION':
mat = obj.matrix_world.copy()
for ob in obj.instance_collection.objects:
yield (ob, mat @ ob.matrix_world)
else:
for obj in context.objects_in_mode_unique_data:
yield (obj, obj.matrix_world)
self.sctx.clear_snap_objects(True)
for obj, matrix in visible_objects_and_duplis():
self.sctx.add_obj(obj, matrix)
def snap_context_init(self, context, snap_edge_and_vert=True):
from .snap_context_l import global_snap_context_get
# Create Snap Context
self.sctx = global_snap_context_get(
context.evaluated_depsgraph_get(), context.region, context.space_data)
ui_scale = context.preferences.system.ui_scale
self.sctx.set_pixel_dist(12 * ui_scale)
if SnapUtilities.snapwidgets:
widget = SnapUtilities.snapwidgets[-1]
mano-wii
committed
self.obj = widget.snap_obj.data[0] if widget.snap_obj else context.active_object
self.bm = widget.bm
self.geom = widget.geom
self.type = widget.type
self.location = widget.location
self.preferences = widget.preferences
self.draw_cache = widget.draw_cache
if hasattr(widget, "normal"):
self.normal = widget.normal
# init these variables to avoid errors
mano-wii
committed
self.obj = context.active_object
self.geom = None
self.type = 'OUT'
self.location = Vector()
preferences = context.preferences.addons[__package__].preferences
self.preferences = preferences
self.draw_cache = SnapDrawn(
preferences.out_color,
preferences.face_color,
preferences.edge_color,
preferences.vert_color,
preferences.center_color,
preferences.perpendicular_color,
preferences.constrain_shift_color,
tuple(
context.preferences.themes[0].user_interface.axis_x) + (1.0,),
tuple(
context.preferences.themes[0].user_interface.axis_y) + (1.0,),
tuple(
context.preferences.themes[0].user_interface.axis_z) + (1.0,),
self.sctx.rv3d,
ui_scale)
self.snap_vert = self.snap_edge = snap_edge_and_vert
shading = context.space_data.shading
self.snap_face = not (snap_edge_and_vert and (
shading.show_xray or shading.type == 'WIREFRAME'))
self.sctx.set_snap_mode(self.snap_vert, self.snap_edge, self.snap_face)
unit_system = context.scene.unit_settings.system
scale = context.scene.unit_settings.scale_length
scale /= context.space_data.overlay.grid_scale
self.rd = bpy.utils.units.to_value(
unit_system, 'LENGTH', str(1 / scale))
self.incremental = bpy.utils.units.to_value(
unit_system, 'LENGTH', str(self.preferences.incremental))
def snap_to_grid(self):
if self.type == 'OUT' and self.preferences.increments_grid:
loc = self.location / self.rd
self.location = Vector((round(loc.x),
round(loc.y),
round(loc.z))) * self.rd
def snap_context_free(self):
self.sctx = None
del self.sctx
del self.bm
del self.draw_cache
del self.geom
del self.location
del self.rd
del self.snap_face
del self.snap_obj
del self.type
del self.preferences
SnapUtilities.constrain = None