import bpy
from bpy.props import BoolProperty
from . import infobar
from ... utility import addon
from ... utility import modifier
from ... utility.base_modal_controls import Base_Modal_Controls
from ... ui_framework.master import Master
from ... ui_framework.utils.mods_list import get_mods_list
# Cursor Warp imports
from ... utils.toggle_view3d_panels import collapse_3D_view_panels
from ... utils.modal_frame_drawing import draw_modal_frame
from ... utils.cursor_warp import mouse_warp
from ... utility import method_handler
class HOPS_OT_AdjustArrayOperator(bpy.types.Operator):
bl_idname = "hops.adjust_array"
bl_label = "Adjust Array"
bl_options = {"REGISTER", "UNDO", "BLOCKING"}
bl_description = """Classic Array Modal
Adds an array on the mesh. Supports multiple modifiers. Press H for help
*newer arrays can be opted into via the opt-in panel*
"""
x: BoolProperty(name="X Axis",
description="X Axis",
default=True)
y: BoolProperty(name="Y Axis",
description="Y Axis",
default=False)
z: BoolProperty(name="Z Axis",
description="Z Axis",
default=False)
is_relative: BoolProperty(name="Is relative",
description="Is relatives",
default=True)
@classmethod
def poll(cls, context):
return any(o.type == 'MESH' for o in context.selected_objects)
def invoke(self, context, event):
self.objects = [o for o in context.selected_objects if o.type == 'MESH']
self.object = context.active_object if context.active_object in context.selected_objects else self.objects[0]
self.modal_scale = addon.preference().ui.Hops_modal_scale
obj = bpy.context.active_object
if obj.dimensions[2] == 0 or obj.dimensions[1] == 0 or obj.dimensions[0] == 0:
self.x = False
self.y = False
self.z = True
if addon.preference().property.force_array_apply_scale_on_init:
for obj in self.objects:
bpy.ops.object.transform_apply(location=False, rotation=False, scale=True)
self.arrays = {}
self.get_array_modifier("Array")
self.using_array = "Array"
self.new_dir = "X"
for obj in self.arrays:
mods = self.arrays[obj]["arrays"]
for mod in mods:
relative = self.arrays[obj]["start_array_relative_offset_displace"]
constant = self.arrays[obj]["start_array_constant_offset_displace"]
if not relative:
self.arrays[obj]["start_array_count"] = mod.count
relative.update({mod.name: [i for i in mod.relative_offset_displace]})
constant.update({mod.name: [i for i in mod.constant_offset_displace]})
self.offset_x = 0
self.offset_y = 0
self.offset_z = 0
if addon.preference().property.force_array_reset_on_init:
for obj in self.arrays:
mods = self.arrays[obj]["arrays"]
mod.use_constant_offset = True
for mod in mods:
mod.relative_offset_displace = [0, 0, 0]
mod.constant_offset_displace = [0, 0, 0]
self.set_init_constant = False
if len(self.objects) > 1:
self.set_init_constant = True
for object in self.objects:
modifier.sort(object, types=['WEIGHTED_NORMAL'], last=True)
# 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')
context.window_manager.modal_handler_add(self)
infobar.initiate(self)
return {"RUNNING_MODAL"}
def get_array_modifier(self, name):
'''Create or get array modifier on all selected mesh objects.'''
for count, obj in enumerate(self.objects):
if obj not in self.arrays:
self.arrays.update({obj: {"arrays": [], "start_array_relative_offset_displace": {}, "start_array_constant_offset_displace": {}, "created_array_modifier": False, "start_array_count": 2}})
mods = obj.modifiers
if name in mods:
if obj.modifiers[name] not in self.arrays[obj]["arrays"]:
self.arrays[obj]["arrays"].append(obj.modifiers[name])
else:#if did not get one this iteration, create it
array_modifier = obj.modifiers.new(name, "ARRAY")
self.array = array_modifier
self.arrays[obj]["arrays"].append(array_modifier)
array_modifier.count = 2
if not obj.dimensions[2] == 0 or obj.dimensions[1] == 0 or obj.dimensions[0] == 0:
array_modifier.relative_offset_displace = [1, 0, 0]
relative = self.arrays[obj]["start_array_relative_offset_displace"]
relative.update({self.array.name: [i for i in self.array.relative_offset_displace]})
constant = self.arrays[obj]["start_array_constant_offset_displace"]
constant.update({self.array.name: [i for i in self.array.constant_offset_displace]})
self.arrays[obj]["created_array_modifier"] = True
def modal(self, context, event):
# Base Systems
self.master.receive_event(event=event)
self.base_controls.update(context, event)
mouse_warp(context, event)
if self.base_controls.pass_through:
return {'PASS_THROUGH'}
active_object = bpy.context.active_object
if addon.preference().property.modal_handedness == 'LEFT':
self.offset_x -= self.base_controls.mouse
self.offset_y -= self.base_controls.mouse
self.offset_z -= self.base_controls.mouse
else:
self.offset_x += self.base_controls.mouse
self.offset_y += self.base_controls.mouse
self.offset_z += self.base_controls.mouse
#Override For Planes
if active_object.dimensions[2] == 0 or active_object.dimensions[1] == 0 or active_object.dimensions[0] == 0:
for obj in self.arrays:
mods = self.arrays[obj]["arrays"]
for mod in mods:
self.array = mod
self.array.use_constant_offset = True
self.array.use_relative_offset = False
self.is_relative = False
for obj in self.arrays:
mods = self.arrays[obj]["arrays"]
for mod in mods:
self.array = mod
relative = self.arrays[obj]["start_array_relative_offset_displace"]
constant = self.arrays[obj]["start_array_constant_offset_displace"]
if self.array.name == self.using_array:
if self.is_relative:
if mod.name in relative:
if self.x:
self.array.relative_offset_displace[0] = float("{:.2f}".format(relative[mod.name][0] + self.offset_x))
if self.y:
self.array.relative_offset_displace[1] = float("{:.2f}".format(relative[mod.name][1] + self.offset_y))
if self.z:
self.array.relative_offset_displace[2] = float("{:.2f}".format(relative[mod.name][2] + self.offset_z))
else:
if mod.name in constant:
if self.x:
self.array.constant_offset_displace[0] = float("{:.2f}".format(constant[mod.name][0] + self.offset_x))
if self.y:
self.array.constant_offset_displace[1] = float("{:.2f}".format(constant[mod.name][1] + self.offset_y))
if self.z:
self.array.constant_offset_displace[2] = float("{:.2f}".format(constant[mod.name][2] + self.offset_z))
if event.type == "X" and event.value == "PRESS" or self.new_dir == "X":
if event.shift:
self.x = not self.x
else:
if self.is_relative:
self.array.relative_offset_displace = [1, 0, 0]
self.offset_x = 0
else:
self.array.constant_offset_displace = [0, 0, 0]
self.offset_x = 0
self.x = True
self.y = False
self.z = False
if event.type == "Y" and event.value == "PRESS" or self.new_dir == "Y":
if event.shift:
self.y = not self.y
else:
if self.is_relative:
self.array.relative_offset_displace = [0, 1, 0]
self.offset_y = -1
else:
self.array.constant_offset_displace = [0, 0, 0]
self.offset_y = 0
self.x = False
self.y = True
self.z = False
if event.type == "Z" and event.value == "PRESS" or self.new_dir == "Z":
if event.shift:
self.z = not self.z
else:
if self.is_relative:
self.array.relative_offset_displace = [0, 0, 1]
self.offset_z = -1
else:
self.array.constant_offset_displace = [0, 0, 0]
self.offset_z = 0
self.x = False
self.y = False
self.z = True
if self.base_controls.scroll:
if event.shift:
if self.base_controls.scroll == 1:
bpy.ops.object.modifier_move_up(modifier=self.array.name)
else:
bpy.ops.object.modifier_move_down(modifier=self.array.name)
else:
self.array.count += self.base_controls.scroll
self.report({'INFO'}, F'Count : {self.array.count}')
if event.type == "C" and event.value == "PRESS" or self.set_init_constant:
self.array.use_constant_offset = True
self.array.use_relative_offset = False
self.is_relative = False
self.array.constant_offset_displace = [0, 0, 0]
self.report({'INFO'}, F'Constant Offset')
if event.type == "Q" and event.value == "PRESS":
bpy.ops.object.modifier_move_up(modifier=self.array.name)
if event.type == "W" and event.value == "PRESS":
bpy.ops.object.modifier_move_down(modifier=self.array.name)
if event.type == "R" and event.value == "PRESS":
self.array.use_constant_offset = False
self.array.use_relative_offset = True
self.is_relative = True
self.array.relative_offset_displace = [1, 0, 0]
self.report({'INFO'}, F'Relative Offset')
self.set_init_constant = False
self.new_dir = ""
if event.type == "ONE" and event.value == "PRESS":
self.get_array_modifier("Array")
self.offset_x = 0
self.offset_y = 0
self.offset_z = 0
self.set_init_constant = True
self.using_array = "Array"
self.new_dir = "X"
if event.type == "TWO" and event.value == "PRESS":
self.get_array_modifier("Array1")
self.offset_x = 0
self.offset_y = 0
self.offset_z = 0
self.set_init_constant = True
self.using_array = "Array1"
self.new_dir = "Y"
if event.type == "THREE" and event.value == "PRESS":
self.get_array_modifier("Array2")
self.offset_x = 0
self.offset_y = 0
self.offset_z = 0
self.set_init_constant = True
self.using_array = "Array2"
self.new_dir = "Z"
if event.type == "S" and event.value == "PRESS":
bpy.ops.object.transform_apply(location=False, rotation=False, scale=True)
if self.base_controls.cancel:
self.remove_shader()
collapse_3D_view_panels(self.original_tool_shelf, self.original_n_panel)
self.restore()
self.master.run_fade()
infobar.remove(self)
return {'CANCELLED'}
if self.base_controls.confirm:
self.remove_shader()
collapse_3D_view_panels(self.original_tool_shelf, self.original_n_panel)
self.master.run_fade()
infobar.remove(self)
return {'FINISHED'}
self.draw_master(context=context)
context.area.tag_redraw()
return {"RUNNING_MODAL"}
def restore(self):
for obj in self.arrays:
mods = self.arrays[obj]["arrays"]
for mod in mods:
self.array = mod
self.array.count = self.arrays[obj]["start_array_count"]
self.array.relative_offset_displace = self.arrays[obj]["start_array_relative_offset_displace"][mod.name]
self.array.constant_offset_displace = self.arrays[obj]["start_array_constant_offset_displace"][mod.name]
if self.arrays[obj]["created_array_modifier"]:
if self.array.name in obj.modifiers:
obj.modifiers.remove(self.array)
def draw_master(self, context):
# Start
self.master.setup()
########################
# Fast UI
########################
if self.master.should_build_fast_ui():
# Main
win_list = []
self.array = self.objects[0].modifiers[self.using_array]
relative = self.array.relative_offset_displace
constant = self.array.constant_offset_displace
if addon.preference().ui.Hops_modal_fast_ui_loc_options != 1: #Fast Floating
win_list.append(str(self.array.count))
if self.is_relative:
win_list.append(" {:.2f} {:.2f} {:.2f} ".format(relative[0], relative[1], relative[2]))
else:
win_list.append(" {:.2f} {:.2f} {:.2f} ".format(constant[0], constant[1], constant[2]))
else:
win_list.append("Name : " + str(self.array.name))
win_list.append("Count : " + str(self.array.count))
if self.is_relative:
win_list.append(" X: {:.3f} Y: {:.3f} Z: {:.3f} ".format(relative[0], relative[1], relative[2]))
else:
win_list.append(" X: {:.3f} Y: {:.3f} Z: {:.3f} ".format(constant[0], constant[1], constant[2]))
# 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"] = [
("X", "set x axis"),
("Y", "set y axis"),
("Z", "set z axis"),
("shift + X", "on/off x axis"),
("shift + Y", "on/off y axis"),
("shift + Z", "on/off z axis"),
("Shift + Scroll", "Move mod up/down"),
("Scroll", "change array count"),
("R", "use relative offset"),
("C", "use constant offset"),
("1", "jump to 1st modifier"),
("2", "create/jump to 2nd modifier"),
("3", "create/jump to 3rd modifier"),
("S", "apply scale"),
("Q", "Move mod DOWN"),
("W", "Move mod UP")]
# Mods
active_mod = ""
if self.array != None:
active_mod = self.array.name
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="Array", mods_list=mods_list, active_mod_name=active_mod)
# Finished
self.master.finished()
####################################################
# CURSOR WARP
####################################################
def safe_draw_shader(self, context):
method_handler(self.draw_shader,
arguments = (context,),
identifier = 'UI Framework',
exit_method = self.remove_shader)
def remove_shader(self):
'''Remove shader handle.'''
if self.draw_handle:
self.draw_handle = bpy.types.SpaceView3D.draw_handler_remove(self.draw_handle, "WINDOW")
def draw_shader(self, context):
'''Draw shader handle.'''
draw_modal_frame(context)