import bpy, bmesh, enum, gpu, sys
from mathutils import Vector, Matrix, geometry
from bpy_extras.view3d_utils import region_2d_to_origin_3d, region_2d_to_vector_3d, location_3d_to_region_2d
from gpu_extras.batch import batch_for_shader
from ... utility import addon
from ... ui_framework.master import Master
from ... ui_framework.utils.mods_list import get_mods_list
from ... utility.base_modal_controls import Base_Modal_Controls
from ... utils.space_3d import ray_cast_objects
from ... utility.shader import dot_handler
from ... utils.blender_ui import get_dpi_factor
from ... utility.object import set_origin
from ... utility.math import coords_to_center, dimensions
from ... utils.toggle_view3d_panels import collapse_3D_view_panels
from ... ui_framework import form_ui as form
from ... utility import method_handler
class states(enum.Enum):
origin = 0
cursor = 1
empty = 2
class modes(enum.Enum):
none = enum.auto()
set = enum.auto()
gizmo = enum.auto()
class gizmo_modes(enum.Enum):
rot = 0
loc = 1
locrot = 2
class object_modes(enum.Enum):
VISIBLE = 0
SELECTION = 1
DESELECTED = 2
DESC = """Set Origin
Set origin of selected object(s) to a point or line on mesh surface \n
Shift - Copy origin's location and rotation from active object to selection
Ctrl + Shift - Copy origin's location from active object to selection
"""
class HOPS_OT_SET_ORIGIN(bpy.types.Operator):
bl_idname = "hops.set_origin"
bl_label = "Set Origin"
bl_options = {'REGISTER', 'UNDO'}
bl_description = DESC
_state = states.origin
_median_loc = False
axis_gizmo = [
Vector(),
Vector((1, 0, 0)),
Vector(),
Vector((0, 1, 0)),
Vector(),
Vector((0, 0, 1)),
]
axis_gizmo_colors = [
[1, 0, 0, 1],
[1, 0, 0, 1],
[0, 1, 0, 1],
[0, 1, 0, 1],
[0, 0, 1, 1],
[0, 0, 1, 1],
]
@property
def state(self):
return self._state
@state.setter
def state(self, val):
if val == self._state: return
if val == states.origin:
self.color = self.color_origin
self.color_high = self.color_origin_high
self.set_dot_colors()
elif val == states.cursor:
self.color = self.color_cursor
self.color_high = self.color_cursor_high
self.set_dot_colors()
else:
self.color = self.color_empty
self.color_high = self.color_empty_high
self.set_dot_colors()
self._state = val
@property
def median_loc(self):
return self._median_loc
@median_loc.setter
def median_loc(self, val):
if self._median_loc == val: return
self._median_loc = val
if self.final_matrix is None: return
if self.gizmo_mode != gizmo_modes.rot:
if val:
self.final_matrix.translation = (self.anchor_matrix.translation + self.pointer_vec) /2
else:
self.final_matrix.translation = self.anchor_matrix.translation
if self.origin_dot: self.origin_dot.location = self.final_matrix.translation
@classmethod
def poll(cls, context):
# for obj in context.selected_objects:
# if obj.type and obj.type == 'EMPTY':
# return False
return context.selected_objects
def invoke(self, context, event):
self.notify = lambda val: bpy.ops.hops.display_notification(info=val) if addon.preference().ui.Hops_extra_info else lambda val: None
self.object_name = ''
self.face_index = -1
preference = addon.preference()
self.init_cursor_matrix = context.scene.cursor.matrix.copy()
self.shift = event.shift
self.state = states.origin
self.mode = modes.none
self.gizmo_handler = None
self.intersect = Vector()
self.selected_meshes = [o for o in context.selected_objects if o.type == 'MESH']
self.visible_meshes = [o for o in context.visible_objects if o.type == 'MESH']
self.unselected = [o for o in self.visible_meshes if not o.select_get()]
self.object_mode = object_modes.SELECTION
self.objects = self.selected_meshes
self.gizmo_mode = gizmo_modes.locrot
self.median_loc_base = False
self.median_loc = False
self.empty = None
self.empties = []
self.object_lock = False
self.parent_to_empty = True
self.bounds_only = False
self.bounds_obj = None
self.bounds_obj_index = -1
if event.shift:
if not context.active_object:
msg ='No active object to copy origin from!'
self.notify(msg)
self.report({'INFO'}, msg)
return {'CANCELLED'}
active = context.active_object
selected = [o for o in context.selected_objects if o is not active]
if not selected:
msg ='No selected objects to set origin to!'
self.notify(msg)
self.report({'INFO'}, msg)
return {'CANCELLED'}
if event.ctrl:
origin = active.matrix_world.translation
msg = "Copied origin's location"
else:
origin = active.matrix_world
msg = "Copied origin's location and orientation"
for obj in selected:
set_origin(obj, origin)
self.report({'INFO'}, msg)
self.notify(msg)
return{'FINISHED'}
if not self.visible_meshes:
msg = 'There are no Mesh objects to work with'
self.notify(msg)
self.report({'INFO'}, msg)
return {"CANCELLED"}
#draw data
self.size = 15 * get_dpi_factor()
self.size_high = 1.5 * self.size
self.outline_color = [0, 0, 0, 0]
self.outline_color_high = [0, 0, 0 ,0]
self.outline_width = 0
self.outline_width_high = 0
self.color_origin = preference.color.dot
self.color_origin_high = preference.color.dot_highlight
self.color_cursor = (1, 0, 0, 0.8)
self.color_cursor_high = (1, 1, 0, 0.8)
self.color_empty = (0, 1, 0, 0.8)
self.color_empty_high = (0, 1, 1, 0.8)
self.color = self.color_origin
self.color_high = self.color_origin_high
self.gizmo_origin = Vector()
self.gizmo_pointer = Vector()
self.edges = []
self.final_matrix = None
self.gizmo_handler = bpy.types.SpaceView3D.draw_handler_add(self.draw_rot_gizmo, (context,), 'WINDOW', 'POST_VIEW')
self.dot_handler = dot_handler(context, handle_draw=True)
self.dot_handler.snap_radius = self.size / 2
self.anchor_matrix = Matrix()
self.pointer_vec = Vector()
self.active_dot = None
self.origin_dot = None
# Dot UI
self.form_obj = None
if context.active_object != None:
if context.active_object.type == 'MESH':
obj = context.active_object
if obj in self.selected_meshes:
if obj in self.visible_meshes:
self.form_obj = context.active_object
self.form = None
if self.form_obj and addon.preference().property.in_tool_popup_style == 'DEFAULT':
if len(self.form_obj.modifiers) > 0:
self.setup_form(context, event)
self.form_obj_mod_mapping = {}
if self.form_obj != None:
for mod in self.form_obj.modifiers:
self.form_obj_mod_mapping[mod.name] = mod.show_viewport
# Base Systems
self.master = Master(context=context)
self.master.only_use_fast_ui = True
self.base_controls = Base_Modal_Controls(context, event)
self.original_tool_shelf, self.original_n_panel = collapse_3D_view_panels()
self.draw_handle = bpy.types.SpaceView3D.draw_handler_add(self.safe_draw_shader, (context,), 'WINDOW', 'POST_PIXEL')
self.update_dots(context, event)
redraw_areas(context)
context.window_manager.modal_handler_add(self)
return {'RUNNING_MODAL'}
def modal(self, context, event):
# Base Systems
self.master.receive_event(event)
self.base_controls.update(context, event)
if self.form:
self.form.update(context, event)
if self.base_controls.pass_through or event.type.count('WHEEL'):
return {'PASS_THROUGH'}
self.median_loc = self.median_loc_base - event.alt #XOR in python
if event.type == 'MOUSEMOVE':
mouse = Vector((event.mouse_region_x, event.mouse_region_y))
if self.mode == modes.none:
self.update_dots(context, event)
self.active_dot = self.dot_handler.active_dot
elif self.mode == modes.set:
vec2d = location_3d_to_region_2d(context.region, context.space_data.region_3d, self.anchor_matrix.translation, default=mouse)
if (mouse - vec2d).length > self.dot_handler.snap_radius:
self.mode = modes.gizmo
self.setup_gizmo()
elif self.mode == modes.gizmo:
matrix = self.anchor_matrix.copy()
pointer_vec = self.intersect_tri(context, matrix, mouse)
self.update_dots(context, event)
if self.dot_handler.active_dot and self.dot_handler.active_dot is self.active_dot:
self.dot_handler.active_dot.highlit = False
else:
self.active_dot = self.dot_handler.active_dot
active_vec = self.active_dot.location if self.active_dot else None
if not active_vec and self.gizmo_mode != gizmo_modes.loc:
distance = 0.0
nearest = None
for edge in self.edges:
current, normalized_distance = geometry.intersect_point_line(pointer_vec, *edge)
if normalized_distance > 1:
current = edge[1]
elif normalized_distance < 0:
current = edge[0]
length = (current - pointer_vec).length
if length < distance or not distance:
distance = length
nearest = edge
active_vec, _ = geometry.intersect_point_line(self.anchor_matrix.translation, *nearest)
active_vec = active_vec if active_vec else matrix.translation
self.pointer_vec = active_vec
track_vec = matrix.inverted() @ active_vec
delta_angle = track_vec.to_2d().angle_signed(Vector((0, 1)), 0)
correction_matrix = Matrix.Rotation(delta_angle, 4, 'Z')
matrix @= correction_matrix
if self.median_loc:
matrix.translation = (self.anchor_matrix.translation + self.pointer_vec) / 2
if self.gizmo_mode == gizmo_modes.loc:
self.final_matrix.translation = matrix.translation
elif self.gizmo_mode == gizmo_modes.rot:
matrix.translation = self.final_matrix.translation
self.final_matrix = matrix
else:
self.final_matrix = matrix
self.create_origin_dot()
self.origin_dot.location = self.final_matrix.translation
for i in range(3):
self.gizmo_pointer[i] = active_vec[i]
elif event.type == 'A' and event.value == 'PRESS':
i = self.state.value
i = (i + 1) % len(states.__members__)
self.state = states(i)
if self.origin_dot:
color = [1 - c for c in self.color]
color[3] = 1
self.origin_dot.color = self.origin_dot.color_high = color
self.notify(f'{self.state.name.capitalize()} Mode')
elif event.type == 'B' and event.value == 'PRESS':
self.bounds_only = not self.bounds_only
self.object_name = ''
if self.bounds_obj is not None: self.bounds_obj.hide_set(not self.bounds_only)
self.update_dots(context, event)
msg = 'Bounds' if self.bounds_only else 'Mesh'
self.notify(msg)
elif event.type == 'C' and event.value == 'PRESS' and self.object_name:
self.object_lock = not self.object_lock
self.notify(f'Objects: {self.object_name if self.object_lock else self.object_mode.name.capitalize()}')
elif event.type == 'E' and event.value == 'PRESS' and self.state == states.empty:
self.parent_to_empty = not self.parent_to_empty
self.notify(f'Parent to empty: {self.parent_to_empty}')
elif event.type == 'S' and event.value == 'PRESS' and self.mode == modes.none and not self.object_lock:
i = self.object_mode.value
i = (i + 1) % len(object_modes.__members__)
self.object_mode = object_modes(i)
if self.object_mode == object_modes.VISIBLE:
self.objects = self.visible_meshes
elif self.object_mode == object_modes.SELECTION:
self.objects = self.selected_meshes
if self.object_name and not bpy.data.objects[self.object_name].select_get():
self.dot_handler.clear_dots()
self.object_name = ''
else:
self.objects = self.unselected
if self.object_name and bpy.data.objects[self.object_name].select_get():
self.dot_handler.clear_dots()
self.object_name = ''
self.update_dots(context, event)
self.notify(f'Objects: {self.object_mode.name.capitalize()}')
elif event.type == 'R' and event.value == 'PRESS':
i = self.gizmo_mode.value
i = (i + 1) % len(modes.__members__)
self.gizmo_mode = gizmo_modes(i)
if self.gizmo_mode == gizmo_modes.loc:
msg = 'Location'
elif self.gizmo_mode == gizmo_modes.rot:
msg = 'Rotation'
else:
msg = 'Loc & Rot'
self.notify(f'Gizmo mode: {msg}')
elif event.type == 'F' and event.value == 'PRESS':
self.median_loc_base = not self.median_loc_base
self.median_loc = not self.median_loc
if self.median_loc:
msg = 'Location: Median'
else:
msg = 'Location: First'
self.notify(msg)
elif event.type == 'LEFTMOUSE' and event.value == 'PRESS' and self.active_dot:
self.mode = modes.set
self.anchor_matrix = self.active_dot.matrix.copy()
matrix = self.anchor_matrix.copy()
if self.gizmo_mode == gizmo_modes.loc:
self.final_matrix.translation = matrix.translation
elif self.gizmo_mode == gizmo_modes.rot:
matrix.translation = self.final_matrix.translation
self.final_matrix = matrix
else:
self.final_matrix = matrix
elif event.type == 'LEFTMOUSE' and event.value == 'RELEASE' and self.mode != modes.none:
if self.state == states.cursor:
if self.mode == modes.set:
matrix = Matrix.Translation(self.final_matrix.translation)
context.scene.cursor.matrix = matrix
elif self.mode == modes.gizmo:
matrix = self.final_matrix.copy()
if self.gizmo_mode == gizmo_modes.loc:
matrix = Matrix.Translation(self.final_matrix.translation)
elif self.gizmo_mode == gizmo_modes.rot:
matrix.translation = context.scene.cursor.matrix.translation
context.scene.cursor.matrix = matrix
elif self.state == states.origin:
if self.mode == modes.set:
for obj in context.selected_objects:
for obj in context.selected_objects:
set_origin(obj, self.final_matrix.translation)
else:
for obj in context.selected_objects:
if self.gizmo_mode == gizmo_modes.loc:
set_origin(obj, self.final_matrix.translation)
elif self.gizmo_mode == gizmo_modes.rot:
matrix = self.final_matrix.copy()
matrix.translation = obj.matrix_world.translation
set_origin(obj, matrix)
else:
set_origin(obj, self.final_matrix)
elif self.state == states.empty:
self.empty = bpy.data.objects.new('Origin', None)
self.empties.append(self.empty)
self.empty.empty_display_type = 'SPHERE'
context.collection.objects.link(self.empty)
self.empty.matrix_world = self.final_matrix
if self.mode == modes.set or self.gizmo_mode == gizmo_modes.loc:
msg = 'Location'
elif self.gizmo_mode == gizmo_modes.rot:
msg = 'Rotation'
else:
msg = 'Location and Rotation'
self.notify(f'{self.state.name.capitalize()} set: {msg}')
if event.shift:
if self.mode == modes.set: self.create_origin_dot()
self.origin_dot.location = self.final_matrix.translation
self.origin_dot.matrices = [self.final_matrix.copy()]
self.mode = modes.none
else:
if self.state == states.empty:
if self.parent_to_empty:
inv_mat = self.empty.matrix_world.inverted()
for obj in context.selected_objects:
mat = obj.matrix_world.copy()
obj.parent = self.empty
obj.matrix_parent_inverse = inv_mat
obj.matrix_world = mat
self.exit(context)
self.select_empties(context)
self.report({'INFO'}, "FINISHED")
return {'FINISHED'}
elif self.base_controls.cancel:
#context.scene.cursor.matrix = self.init_cursor_matrix
self.exit(context)
self.report({'INFO'}, "CANCELLED")
bpy.ops.ed.undo_push()
bpy.ops.ed.undo()
return {'CANCELLED'}
elif event.type == 'SPACE':
self.exit(context)
self.select_empties(context)
self.report({'INFO'}, "FINISHED")
self.notify('FINISHED')
return {'FINISHED'}
elif event.type == 'TAB' and event.value == 'PRESS' and addon.preference().property.in_tool_popup_style == 'BLENDER':
bpy.ops.hops.modlist_popover(allow_removal=False)
self.draw_ui(context)
redraw_areas(context)
return {'RUNNING_MODAL'}
def exit(self, context):
if self.bounds_obj is not None:
me = self.bounds_obj.data
bpy.data.objects.remove(self.bounds_obj)
bpy.data.meshes.remove(me)
self.dot_handler.purge()
self.purge_gizmo()
collapse_3D_view_panels(self.original_tool_shelf, self.original_n_panel)
self.master.run_fade()
redraw_areas(context)
# Form
self.remove_shader()
if self.form:
self.form.shut_down(context)
if self.form_obj != None:
for mod_name, show in self.form_obj_mod_mapping.items():
if mod_name in self.form_obj.modifiers:
self.form_obj.modifiers[mod_name].show_viewport = show
def update_dots(self, context, event):
vec2d = event.mouse_region_x, event.mouse_region_y
origin = region_2d_to_origin_3d(context.region, context.space_data.region_3d, vec2d)
direction = region_2d_to_vector_3d(context.region, context.space_data.region_3d, vec2d)
objects = self.objects
rebuild = 0
if self.object_lock and self.object_name:
objects = [bpy.data.objects[self.object_name]]
hit, _, _, index, object, matrix = ray_cast_objects(context, origin, direction, objects, evaluated=True)
if hit and object.type == 'MESH':
if object.name != self.object_name:
rebuild += 1
self.object_name = object.name
self.create_bounds(object)
if index != self.face_index:
rebuild += 1
self.face_index = index
if self.bounds_only and self.bounds_obj:
rebuild = 0
self.face_index = -1
hit, _, _, index, object, matrix = self.bounds_cast(origin, direction)
#hit, _, _, index, object, matrix = ray_cast_objects(context, origin, direction, [self.bounds_obj], evaluated=False)
if hit and (index != self.bounds_obj_index):
rebuild = 1
object = self.bounds_obj
self.bounds_obj_index = index
if rebuild:
self.dot_handler.clear_dots()
self.origin_dot = None
if self.mode == modes.none:
self.edges.clear()
eval_mesh = object.evaluated_get(context.evaluated_depsgraph_get()).data
face = eval_mesh.polygons[index]
loc, rot, sca = matrix.decompose()
trans_mat = Matrix.Translation(loc)
rot_mat = rot.to_matrix().to_4x4()
scale_mat = Matrix.Diagonal((*sca, 1))
normal = (scale_mat.inverted().transposed() @ face.normal)
surface_matrix = normal.to_track_quat('Z', 'Y').to_matrix().to_4x4()
surface_matrix.translation = face.center
surface_matrix = trans_mat @ rot_mat @ surface_matrix
for vert in face.vertices:
vec = matrix @ eval_mesh.vertices[vert].co
dot = self.dot_handler.dot_create(vec, type='VERT', size=self.size, size_high=self.size_high, color=self.color, color_high=self.color_high, outline_color=self.outline_color, outline_color_high=self.outline_color_high, outline_width=self.outline_width, outline_width_high=self.outline_width_high)
mat = surface_matrix.copy()
mat.translation = vec
dot.matrices = [mat]
for vert1, vert2 in face.edge_keys:
v1 = matrix @ eval_mesh.vertices[vert1].co
v2 = matrix @ eval_mesh.vertices[vert2].co
vec = (v1 + v2) /2
if self.mode == modes.none:
self.edges.append((v1, v2))
dot = self.dot_handler.dot_create(vec, type='EDGE', size=self.size, size_high=self.size_high, color=self.color, color_high=self.color_high, outline_color=self.outline_color, outline_color_high=self.outline_color_high, outline_width=self.outline_width, outline_width_high=self.outline_width_high)
mat = surface_matrix.copy()
mat.translation = vec
dot.matrices = [mat]
vec = matrix @ face.center
dot = self.dot_handler.dot_create(vec, type='FACE', size=self.size, size_high=self.size_high, color=self.color, color_high=self.color_high, outline_color=self.outline_color, outline_color_high=self.outline_color_high, outline_width=self.outline_width, outline_width_high=self.outline_width_high)
mat = surface_matrix.copy()
mat.translation = vec
dot.matrices = [mat]
if self.mode == modes.none:
self.final_matrix = matrix.normalized()
self.final_matrix.translation = matrix.translation
self.create_origin_dot()
self.dot_handler.update(context, event)
def set_dot_colors(self,):
for dot in self.dot_handler.dots:
dot.color = self.color
dot.color_high = self.color_high
def draw_rot_gizmo(self, context):
if self.mode != modes.gizmo: return
#Enable(bgl.GL_LINE_SMOOTH)
gpu.state.line_width_set(3)
line = [self.gizmo_origin, self.gizmo_pointer]
built_in_shader = 'UNIFORM_COLOR' if bpy.app.version[0] >=4 else '3D_UNIFORM_COLOR'
shader = gpu.shader.from_builtin(built_in_shader)
batch = batch_for_shader(shader, 'LINES', {"pos": line})
shader.bind()
shader.uniform_float('color', self.color_high)
batch.draw(shader)
persp_matrix = context.region_data.view_matrix.copy()
persp_matrix.translation.z = context.region_data.view_distance
depth_factor = (persp_matrix @ self.origin_dot.location).z * 0.1
s = (0.5 if context.region_data.view_perspective == 'ORTHO' else 0.3 ) * abs(depth_factor)
sca = Matrix.Scale(s, 4)
matrix = self.final_matrix @ sca
matrix.translation = self.origin_dot.location
axes = [matrix @ v for v in self.axis_gizmo]
built_in_shader = 'FLAT_COLOR' if bpy.app.version[0] >=4 else '3D_FLAT_COLOR'
axes_shader = gpu.shader.from_builtin(built_in_shader)
axes_batch = batch_for_shader(axes_shader, 'LINES', {"pos": axes, 'color': self.axis_gizmo_colors})
axes_shader.bind()
axes_batch.draw(axes_shader)
#Disable(bgl.GL_LINE_SMOOTH)
gpu.state.line_width_set(1)
def setup_gizmo(self):
location = self.dot_handler.active_dot.location
for i in range(3):
self.gizmo_origin[i] = location[i]
def purge_gizmo(self):
if self.gizmo_handler:
bpy.types.SpaceView3D.draw_handler_remove(self.gizmo_handler, 'WINDOW')
self.gizmo_handler = None
def intersect_tri(self, context, matrix, vec2d):
v1 = matrix @ Vector(( 0, 1, 0))
v2 = matrix @ Vector(( 1,-1, 0))
v3 = matrix @ Vector((-1,-1, 0))
origin = region_2d_to_origin_3d(context.region, context.space_data.region_3d, vec2d)
direction = region_2d_to_vector_3d(context.region, context.space_data.region_3d, vec2d)
intersect = geometry.intersect_ray_tri(v1, v2, v3, direction, origin, False)
if not intersect:
intersect = geometry.intersect_ray_tri(v1, v2, v3, -direction, origin, False)
if not intersect:
intersect = self.intersect
self.intersect = intersect
return intersect
def bounds_cast(self, origin_world, direction_world):
mesh = self.bounds_obj.data
matrix = self.bounds_obj.matrix_world
inverted = matrix.inverted()
origin = inverted @ origin_world
direction = inverted @ (direction_world + origin_world) - origin
distance = sys.float_info.max
hit = False
vec = Vector()
normal = Vector((0, 0, -1))
index = -1
for p in mesh.polygons:
v1 = mesh.vertices[p.vertices[0]].co
v2 = mesh.vertices[p.vertices[1]].co
v3 = mesh.vertices[p.vertices[2]].co
point = geometry.intersect_ray_tri(v1, v2, v3, direction, origin, True)
if point is not None:
dist = (point - origin).magnitude
if dist < distance:
hit = True
vec = point
normal = p.normal
index = p.index
distance = dist
else:
v1 = mesh.vertices[p.vertices[2]].co
v2 = mesh.vertices[p.vertices[3]].co
v3 = mesh.vertices[p.vertices[0]].co
point = geometry.intersect_ray_tri(v1, v2, v3, direction, origin, True)
if point is not None:
dist = (point - origin).magnitude
if dist < distance:
hit = True
vec = point
normal = p.normal
index = p.index
distance = dist
return hit, vec, normal, index, self.bounds_obj, matrix
def create_origin_dot(self):
if self.origin_dot is None:
color = [1 - c for c in self.color]
color[3] = 1
self.origin_dot = self.dot_handler.dot_create(self.final_matrix.translation, type='ORIGIN', size=self.size*0.8, size_high=self.size_high, color=color, color_high=color, outline_color=self.outline_color, outline_color_high=self.outline_color_high, outline_width=self.outline_width, outline_width_high=self.outline_width_high)
self.origin_dot.matrices = [Matrix.Translation(self.final_matrix.translation)]
def select_empties(self, context):
if not self.empties: return
bpy.ops.object.select_all(action='DESELECT')
for obj in self.empties:
obj.select_set(True)
context.view_layer.objects.active = obj
def create_bounds(self, obj):
if self.bounds_obj is None:
me = bpy.data.meshes.new('SO_Proxy')
self.bounds_obj = bpy.data.objects.new('SO_Proxy', me)
bpy.context.collection.objects.link(self.bounds_obj)
self.bounds_obj.display_type = 'BOUNDS'
self.bounds_obj.hide_set(not self.bounds_only)
center = coords_to_center(obj.bound_box)
scale = dimensions(obj.bound_box)
bm = bmesh.new()
matrix = Matrix.Diagonal((*scale, 1))
matrix.translation = center
bmesh.ops.create_cube(bm, matrix=matrix)
bm.to_mesh(self.bounds_obj.data)
self.bounds_obj.data.update()
self.bounds_obj.matrix_world = obj.matrix_world
self.bounds_obj_index = -1
def draw_ui(self, context):
self.master.setup()
if not self.master.should_build_fast_ui(): return
# Main
win_list = []
if self.gizmo_mode == gizmo_modes.loc:
gmode = '[R] Location'
gmode_s = '[R] L'
elif self.gizmo_mode == gizmo_modes.rot:
gmode = '[R] Rotation'
gmode_s = '[R] R'
else:
gmode = '[R] Loc & Rot'
gmode_s = '[R] L&R'
if addon.preference().ui.Hops_modal_fast_ui_loc_options != 1:
win_list.append(self.state.name.capitalize()[0])
win_list.append(f"[S] {self.object_mode.name[0]}" if not self.object_lock else "L")
win_list.append(gmode_s)
win_list.append('F' if not self.median_loc else 'M')
if self.state == states.empty:
win_list.append(f'[E] {self.parent_to_empty}')
else:
win_list.append(self.state.name.capitalize())
win_list.append(f"[S] {self.object_mode.name.capitalize()}" if not self.object_lock else self.object_name)
win_list.append(gmode)
win_list.append('[F] First' if not self.median_loc else '[F] Median')
win_list.append('[B] Mesh' if self.bounds_only else '[B] Bounds')
if self.state == states.empty:
win_list.append(f'[E] Parent: {self.parent_to_empty}')
# Help
help_items = {"GLOBAL" : [], "STANDARD" : []}
help_items["GLOBAL"] = [
("M", "Toggle mods list"),
("H", "Toggle help"),
("~", "Toggle UI Display Type"),
("O", "Toggle viewport rendering")]
help_items["STANDARD"] = [
("TAB", f"Modifier list") if addon.preference().property.in_tool_popup_style == 'BLENDER' else ('', ''),
("B", f"Toggle {'Bounds' if not self.bounds_only else 'Mesh'} mode"),
("C", f"Object lock: {self.object_lock}"),
("F", f"Location : {'First' if not self.median_loc else 'Median'} [Alt]"),
("R", f"Gizmo Mode : {gmode}"),
("S", f"Objects: {self.object_mode.name.capitalize()}"),
("A", f"Change Mode {self.state.name.capitalize()}"),
("Shift+LMB", "Confirm without exit"),
("LMB ", f"Set {self.state.name.capitalize()}; Hold to rotate"),
("RMB ", "Cancel"),
("SPACE", "Confirm"),]
if self.state == states.empty:
help_items["STANDARD"] = [("E", f"Parent to empty: {self.parent_to_empty}")] + help_items["STANDARD"]
# Mods
mods_list = get_mods_list(mods=bpy.context.active_object.modifiers)
self.master.receive_fast_ui(win_list=win_list, help_list=help_items, image="", mods_list=mods_list)
self.master.finished()
# --- FORM FUNCS --- #
def setup_form(self, context, event):
# MAX WIDTH = 165
self.form = form.Form(context, event, dot_open=False)
# Mod Scroll Box
group = form.Scroll_Group()
for index, mod in enumerate(self.form_obj.modifiers):
row = group.row()
# Count
row.add_element(form.Label(text=str(index + 1), width=25, height=20))
# Mod name
text = form.shortened_text(mod.name, width=95, font_size=12)
row.add_element(form.Label(text=text, width=100, height=20))
# Visible
row.add_element(form.Button(
scroll_enabled=False, text="X", highlight_text="O", tips=["Toggle visibility"],
width=20, height=20, use_padding=False,
callback=self.rebuild_from_form, pos_args=(mod, context),
highlight_hook_obj=mod, highlight_hook_attr='show_viewport'))
group.row_insert(row)
row = self.form.row()
box_height = 160
if len(self.form_obj.modifiers) < 8:
box_height = 20 * len(self.form_obj.modifiers)
mod_box = form.Scroll_Box(width=165, height=box_height, scroll_group=group, view_scroll_enabled=True)
row.add_element(mod_box)
self.form.row_insert(row, active=True)
self.form.build()
def rebuild_from_form(self, mod, context):
mod.show_viewport = not mod.show_viewport
# --- SHADERS --- #
def safe_draw_shader(self, context):
method_handler(self.draw_shader,
arguments = (context,),
identifier = 'Set Origin 2D Shader',
exit_method = self.remove_shader)
def remove_shader(self):
if self.draw_handle:
self.draw_handle = bpy.types.SpaceView3D.draw_handler_remove(self.draw_handle, "WINDOW")
def draw_shader(self, context):
if self.form:
self.form.draw()
def redraw_areas(context):
for area in context.screen.areas:
if area.type != 'VIEW_3D': continue
area.tag_redraw()