Newer
Older
### BEGIN GPL LICENSE BLOCK #####
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 3
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# ##### END GPL LICENSE BLOCK #####
import bpy
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
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
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))
else:
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)
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'
}
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
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:]
elif type == 'LEFT_ARROW':
self.line_pos = (pos - 1) % (len(self.length_entered) + 1)
elif type == 'RIGHT_ARROW':
self.line_pos = (pos + 1) % (len(self.length_entered) + 1)
try:
self.length_entered_value = bpy.utils.units.to_value(self.unit_system, 'LENGTH', self.length_entered)
except: # ValueError:
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
class Constrain:
def __init__(self, peferences, scene, obj):
self.last_type = None
self.last_vec = None
self.rotMat = None
self.preferences = peferences
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))
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
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
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:
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
self.rotMat = None # update
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 = center.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",
)
"""
constrain_keys = {
'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)
self.sctx.set_pixel_dist(12)
self.sctx.use_clip_planes(True)
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
#Init DrawCache
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)
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)
#Configure the unit of measure
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