import bpy, time
from .... utility import addon, modifier, geometry_nodes
from .... ui_framework.master import Master
from .... ui_framework import form_ui as form
from .... ui_framework.utils.mods_list import get_mods_list
from .... utility.base_modal_controls import Base_Modal_Controls, confirm_events, increment_maps, decrement_maps
from .... utils.toggle_view3d_panels import collapse_3D_view_panels
from .... utility import method_handler
from ... meshtools.applymod import apply_mod
from .... utils.blender_ui import get_dpi_factor
from .... ui.hops_helper.mods_data import DATA_PT_modifiers
from . import States, Auto_Scroll, update_local_view, mods_exit_options, turn_on_coll, get_mod_object
from . mod_tracker import Mod_Tracker
from . bool_tracker import Bool_Tracker
from . child_tracker import Child_Tracker
from . coll_tracker import Coll_Tracker
from . popups import popup_generator
def max_rows():
return addon.preference().ui.Hops_modal_mod_count_fast_ui
DESC = """Ever Scroll V2\n
LMB - Booleans
LMB + SHIFT - Modifiers
LMB + CTRL - Child Objects
LMB + ALT - Smart Apply
"""
def state_update(self, context):
op = HOPS_OT_Ever_Scroll_V2.operator
if not op: return
if op.popup_active:
op.set_state(self.b_state, context)
class HOPS_OT_Ever_Scroll_V2(bpy.types.Operator):
bl_idname = "hops.ever_scroll_v2"
bl_label = "Ever Scroll V2"
bl_description = DESC
bl_options = {"REGISTER", "UNDO", "BLOCKING"}
entry_state: bpy.props.EnumProperty(
name="State",
items=(
("NONE", "NONE", "NONE"),
("BOOL", "BOOL", "BOOL"),
("MOD", "MOD", "MOD"),
("CHILD", "CHILD", "CHILD"),
("COLL", "COLL", "COLL")),
default="NONE")
b_state: bpy.props.EnumProperty(
name="State",
items=(
# ("NONE", "NONE", "NONE"),
("Modifiers", "MODIFIERS", "MOD"),
("Booleans", "BOOLEANS", "BOOL"),
("Children", "CHILDREN", "CHILD"),
# ("COLL", "COLL", "COLL")
),
update=state_update,
default="Modifiers")
dot_open: bpy.props.BoolProperty(default=False)
operator = None
popup_active = False
def invoke(self, context, event):
def check_type(obj):
if type(obj) == bpy.types.Object:
if type(obj.data) == bpy.types.Mesh:
return True
return False
self.obj = context.active_object if check_type(context.active_object) else None
if self.obj == None:
for obj in context.selected_objects:
if check_type(obj):
self.obj = obj
break
if self.obj == None:
bpy.ops.hops.display_notification(info="Select an Object")
return {'CANCELLED'}
# Smart apply option
if event.alt:
if check_type(self.obj):
apply_mod(self, self.obj, clear_last=False)
bpy.ops.hops.display_notification(info='Smart Apply')
bpy.ops.hops.draw_wire_mesh_launcher('INVOKE_DEFAULT', target='SELECTED')
self.report({'INFO'}, F'Smart Applied')
return {'FINISHED'}
self.__class__.operator = self
# States
self.state = States.BOOL
# Entry
if self.entry_state == 'NONE':
# Modifiers
if event.shift:
self.state = States.MOD
# Children
elif event.ctrl:
self.state = States.CHILD
elif self.entry_state == 'BOOL':
self.state = States.BOOL
elif self.entry_state == 'MOD':
self.state = States.MOD
elif self.entry_state == 'CHILD':
self.state = States.CHILD
elif self.entry_state == 'COLL':
self.state = States.COLL
self.display_state_notification()
# Trackers
self.mod_tracker = Mod_Tracker()
self.bool_tracker = Bool_Tracker()
self.child_tracker = Child_Tracker()
self.coll_tracker = Coll_Tracker()
self.update_tracker_data(context)
# Auto Scroll
self.auto_scroll = Auto_Scroll(context)
# Prefs
if addon.preference().property.ever_scroll_dot_open == 'DOT':
self.dot_open = True
self.popup_style = addon.preference().property.in_tool_popup_style
# Hops Dots
self.hops_dots_running = False
self.hops_dots_op_used = False
# Form
self.late_update = False
self.form_exit = False
self.form_exit_option = ''
self.auto_scroll_form_label = None
self.form = None
self.setup_form(context, event)
# Systems
self.master = Master(context)
self.master.only_use_fast_ui = True
self.draw_handle_2D = bpy.types.SpaceView3D.draw_handler_add(self.safe_draw_2D, (context,), 'WINDOW', 'POST_PIXEL') if self.popup_style == 'DEFAULT' else None
self.original_tool_shelf, self.original_n_panel = collapse_3D_view_panels()
self.base_controls = Base_Modal_Controls(context, event)
context.window_manager.modal_handler_add(self)
return {'RUNNING_MODAL'}
def modal(self, context, event):
if event.ctrl and event.type == 'LEFTMOUSE':
print()
# Form late update
if self.late_update:
self.late_update = False
self.alter_form_layout(context)
# --- Base Systems --- #
self.master.receive_event(event=event)
self.base_controls.update(context, event)
self.auto_scroll_update(context, event)
form_active = False
if self.popup_style == 'DEFAULT':
self.form.update(context, event, return_on_timer=not self.auto_scroll.active)
form_active = self.form.active()
# --- Hops Dots --- #
ret = self.hardflow(context, event)
if ret: return ret
# --- Base Controls --- #
if self.popup_active:
self.draw_FAS(context)
context.area.tag_redraw()
return {'RUNNING_MODAL'}
if self.base_controls.pass_through:
if not form_active:
return {'PASS_THROUGH'}
if self.base_controls.cancel:
if not form_active:
return self.cancel_exit(context)
elif self.base_controls.confirm:
if not form_active:
if not (event.type == 'SPACE' and event.value == 'PRESS' and event.shift):
return self.confirm_exit(context, event)
if self.form_exit:
return self.exit_action(context, event)
if event.type == 'TAB' and event.value == 'PRESS':
if self.popup_style == 'BLENDER':
if event.shift:
self.toggle_state(context)
else:
bpy.ops.hops.ever_scroll_v2_popup()
else:
if event.shift:
self.toggle_state(context)
elif self.form.is_dot_open():
self.form.close_dot()
else:
self.form.open_dot()
# --- Actions --- #
if not form_active and not self.auto_scroll.active:
self.actions(context, event)
self.draw_FAS(context)
context.area.tag_redraw()
return {'RUNNING_MODAL'}
# --- ACTIONS --- #
def actions(self, context, event):
scroll = self.base_controls.scroll
# Scrolling
if scroll and not event.shift and not event.ctrl:
self.scroll_trackers(context, scroll)
# Tracker Keys
if self.state == States.MOD:
self.mod_tracker.event_update(self, context, event, self.obj)
elif self.state == States.CHILD:
self.child_tracker.event_update(self, context, event, self.obj)
elif self.state == States.BOOL:
self.bool_tracker.event_update(self, context, event, self.obj)
elif self.state == States.COLL:
self.coll_tracker.event_update(self, context, event)
def hardflow(self, context, event):
if self.hops_dots_running:
if context.window_manager.hardflow.running:
self.draw_FAS(context)
context.area.tag_redraw()
return {'RUNNING_MODAL'}
else:
self.hops_dots_running = False
self.obj.select_set(True)
context.view_layer.objects.active = self.obj
self.draw_FAS(context)
context.area.tag_redraw()
return {'RUNNING_MODAL'}
if not self.form.active() and self.state == States.BOOL:
if event.ctrl and not event.shift:
self.hops_dots_running = True
self.hops_dots_op_used = True
bpy.ops.hardflow_om.display('INVOKE_DEFAULT', use_operations=False)
self.draw_FAS(context)
context.area.tag_redraw()
return {'RUNNING_MODAL'}
return False
# --- INTERFACE --- #
def draw_FAS(self, context):
self.master.setup()
if not self.master.should_build_fast_ui(): return
# Main
win_list = []
tracker_data = []
if self.state == States.MOD:
tracker_data = self.mod_tracker.FAS_data()
elif self.state == States.BOOL:
tracker_data = self.bool_tracker.FAS_data()
elif self.state == States.CHILD:
tracker_data = self.child_tracker.FAS_data()
elif self.state == States.COLL:
tracker_data = []
if tracker_data:
for entry in tracker_data:
win_list.append(entry)
# 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", "Dot UI"),
("Shift TAB", "Change Mode"),
("Ctrl S", "Toggle Auto Scroll")]
h_append = help_items["STANDARD"].append
if self.auto_scroll.active:
h_append(("Scroll", "Control Auto Scroll Speed"))
if self.state == States.MOD:
help_items["STANDARD"].extend(self.mod_tracker.help)
elif self.state == States.BOOL:
help_items["STANDARD"].extend(self.bool_tracker.help)
elif self.state == States.CHILD:
help_items["STANDARD"].extend(self.child_tracker.help)
elif self.state == States.COLL:
tracker_data = []
help_items["STANDARD"].reverse()
# Mods
active_mod = ""
mods_list = []
if not self.form.is_dot_open():
if self.state == States.MOD:
active_mod = self.mod_tracker.current_mod.name if self.mod_tracker.current_mod else ""
mods_list = get_mods_list(mods=self.obj.modifiers)
elif self.state == States.BOOL:
# Recursive Mode
if self.bool_tracker.recursive_active:
# Since the mod names can be the same in recursive mode the active mod highlight doesnt know which one to use
# This area will alter the mod names by adding (1) mod name, (2) mod name... to the front of the mod name
mods = []
group = self.bool_tracker.recursive.active_group()
if group:
active_mod = group.active_bool_data().real_mod()
index = None
for i, bool_data in enumerate(group.bool_datas):
mod = bool_data.real_mod()
if mod:
mods.append(mod)
if mod == active_mod:
index = i
mods_list = get_mods_list(mods=mods)
names = []
for mod_item in mods_list:
if mod_item[0] in names:
names.append(mod_item[0])
name = f'({names.count(mod_item[0]) - 1}) {mod_item[0]}'
mod_item[0] = name
else:
names.append(mod_item[0])
if index:
active_mod = mods_list[index][0]
else:
active_mod = group.active_mod()
mods_list.reverse()
else:
active_mod = self.bool_tracker.current_mod.name if self.bool_tracker.current_mod else ""
mods_list = get_mods_list(mods=self.obj.modifiers)
elif self.state == States.CHILD:
active_mod = self.child_tracker.current_obj.name if self.child_tracker.current_obj else ""
mods_list = [[c.name, c.type] for c in self.child_tracker.children if c]
elif self.state == States.COLL:
active_mod = ""
mods_list = []
self.master.receive_fast_ui(win_list=win_list, help_list=help_items, image="Booleans", mods_list=mods_list, active_mod_name=active_mod)
self.master.finished()
def setup_form(self, context, event):
# MAX WIDTH : 220px
self.form = form.Form(context, event, dot_open=self.dot_open)
# Load Image Group
form.setup_image_group(img_names=['play', 'pause', 'eyecon_open', 'eyecon_closed'])
def spacer(height=10):
row = self.form.row()
row.add_element(form.Spacer(height=height))
self.form.row_insert(row)
row = self.form.row()
modes = ["Modifiers", "Booleans", "Children"] # "Collections"
row.add_element(form.Dropdown(width=80, options=modes, callback=self.set_state, update_hook=self.state_hook, additional_args=(context,)))
row.add_element(form.Spacer(width=30))
self.auto_scroll_form_label = form.Label(text="", width=60)
row.add_element(self.auto_scroll_form_label)
self.play_button = form.Button(glob_img_key='play', width=20, tips=["Auto Scroll"], callback=self.toggle_auto_scroll, pos_args=(context,))
row.add_element(self.play_button)
row.add_element(form.Spacer(width=10))
self.exit_button = form.Button(text="✓", shift_text="D", ctrl_text="A", alt_text="X", width=20, tips=self.exit_button_tips_updater(),
callback=self.exit_button_func, pos_args=('',), neg_args=('DUPLICATE',),
ctrl_callback=self.exit_button_func, ctrl_args=('APPLY',),
alt_callback=self.exit_button_func, alt_args=('CANCEL',))
self.set_exit_button_modifier_key_text()
tips = self.exit_button.tips
tips.update_func = self.exit_button_tips_updater
row.add_element(self.exit_button)
self.form.row_insert(row)
spacer()
# --- Mods --- #
row = self.form.row()
group = self.mod_group(context)
self.mod_box = form.Scroll_Box(width=220, height=self.mods_box_height(), scroll_group=group, view_scroll_enabled=True)
row.add_element(self.mod_box)
self.form.row_insert(row, label="MODS", active=True if self.state == States.MOD else False)
# --- Bools --- #
row = self.form.row()
row.add_element(form.Button(text="End", tips=["End Recursive Scroll"], callback=self.bool_tracker.stop_recursive, pos_args=(context, self)))
self.form.row_insert(row, label="BOOL_REC", active=True if self.state == States.BOOL and self.bool_tracker.recursive_active else False)
row = self.form.row()
group = self.bool_group(context)
self.bool_box = form.Scroll_Box(width=220, height=self.bool_box_height(), scroll_group=group, view_scroll_enabled=True)
row.add_element(self.bool_box)
self.form.row_insert(row, label="BOOL", active=True if self.state == States.BOOL else False)
# --- Child --- #
row = self.form.row()
group = self.child_group()
self.child_box = form.Scroll_Box(width=220, height=self.child_box_height(), scroll_group=group, view_scroll_enabled=True)
row.add_element(self.child_box)
self.form.row_insert(row, label="CHILD", active=True if self.state == States.CHILD else False)
self.form.build()
# --- FORM FUNCS --- #
def mod_group(self, context):
group = form.Scroll_Group()
for index, mod in enumerate(self.mod_tracker.mods):
row = group.row()
row.add_element(form.Label(text=str(index + 1), width=25, height=20))
tip = []
text = form.shortened_text(mod.name, width=100, font_size=12)
tip = tip if text == mod.name else [mod.name]
obj = get_mod_object(mod)
if obj: tip.append(f"Shift Click : Toggle Reveal {obj.name}")
msg, pop_up = popup_generator(self, mod, index + 1)
if pop_up: tip.append(msg)
if modifier.mod_type(mod) == 'NODES':
tip = ["Shift Click : Toggle Objects"]
row.add_element(form.Button(
scroll_enabled=False, text=text, tips=tip,
width=110, height=20, use_padding=False,
callback=self.mod_tracker.make_selected_active, pos_args=(context, index, False), neg_args=(context, index, True),
highlight_hook=self.mod_tracker.highlight, highlight_hook_args=(index,),
popup=pop_up, popup_modifier_key='CTRL'))
row.add_element(form.Button(
scroll_enabled=False, text="O", highlight_text="X", tips=["Toggle modifier visibility"],
width=20, height=20, use_padding=False,
callback=self.mod_tracker.mod_toggle_view, pos_args=(self.obj, mod.name),
highlight_hook=self.mod_tracker.mod_show_view_highlight, highlight_hook_args=(self.obj, mod.name)))
row.add_element(form.Button(
scroll_enabled=False, text="R", tips=["Toggle modifier render"],
width=20, height=20, use_padding=False,
callback=self.mod_tracker.mod_toggle_render, pos_args=(self.obj, mod.name),
highlight_hook=self.mod_tracker.mod_show_render_highlight, highlight_hook_args=(self.obj, mod.name)))
row.add_element(form.Button(
scroll_enabled=False, text="✓", tips=["Click : Apply modifier", "Shift Click : Apply modifiers up to"],
width=25, height=20, use_padding=False,
callback=self.mod_tracker.apply_mods_form, pos_args=(context, self, mod.name, False), neg_args=(context, self, mod.name, True)))
group.row_insert(row)
return group
def bool_group(self, context):
group = form.Scroll_Group()
# Keep the bools index inline while looping over all mods
bool_index = 0
# Recursive Mode
if self.bool_tracker.recursive_active:
mods = self.bool_tracker.recursive.active_mods()
for index, mod_name in enumerate(mods):
text = form.shortened_text(mod_name, width=115, font_size=12)
row = group.row()
row.add_element(form.Label(text=str(index + 1), width=25, height=20))
row.add_element(form.Button(
scroll_enabled=False, text=text, tips=["Toggle object visibility"],
width=135, height=20, use_padding=False,
callback=self.bool_tracker.recursive.activate_selected, pos_args=(index,),
highlight_hook=self.bool_tracker.recursive.active_highlight, highlight_hook_args=(index,)))
row.add_element(form.Button(
scroll_enabled=False, text="O", highlight_text="X", tips=["Toggle modifier visibility"],
width=20, height=20, use_padding=False,
callback=self.bool_tracker.recursive.bool_toggle, pos_args=(index,),
highlight_hook=self.bool_tracker.recursive.bool_show_view_highlight, highlight_hook_args=(index,)))
row.add_element(form.Button(
scroll_enabled=False, tips=["Lock modifier object visibility"],
width=20, height=20, use_padding=False,
callback=self.bool_tracker.recursive.selection_to_tracked, pos_args=(index,),
highlight_hook=self.bool_tracker.recursive.tracked_highlight, highlight_hook_args=(index,),
glob_img_key='eyecon_closed', glob_img_key_update_func=self.bool_tracker.recursive.tracked_img_key_update, glob_img_key_update_args=(index,)))
group.row_insert(row)
return group
# Standard Mode
for index, mod in enumerate(self.bool_tracker.all_mods):
if mod not in self.bool_tracker.bools:
text = form.shortened_text(mod.name, width=150, font_size=12)
row = group.row()
row.add_element(form.Label(text=str(index + 1), width=25, height=20))
row.add_element(form.Label(text=str(text), width=175, height=20))
group.row_insert(row)
continue
row = group.row()
if self.bool_tracker.has_recursive_mods(mod):
row.add_element(form.Button(
scroll_enabled=False, text=str(index + 1), tips=["Start Recursive Scroll"],
width=25, height=20, use_padding=False,
callback=self.bool_tracker.select_start_recursive, pos_args=(context, self, bool_index)))
else:
row.add_element(form.Label(text=str(index + 1), width=25, height=20))
text = form.shortened_text(mod.name, width=100, font_size=12)
_, pop_up = popup_generator(self, mod, index + 1, bool_tracker_mode=True)
button = form.Button(
scroll_enabled=False, text=text, tips=self.bool_tips_updater(mod.name),
width=110, height=20, use_padding=False,
callback=self.bool_tracker.make_selected_active, pos_args=(bool_index, False), neg_args=(bool_index, True),
highlight_hook=self.bool_tracker.highlight, highlight_hook_args=(bool_index,),
popup=pop_up, popup_modifier_key='CTRL')
tips = button.tips
if tips:
tips.update_func = self.bool_tips_updater
tips.update_args = (mod.name,)
row.add_element(button)
bool_index += 1
row.add_element(form.Button(
scroll_enabled=False, text="O", highlight_text="X", tips=["Toggle modifier visibility"],
width=20, height=20, use_padding=False,
callback=self.bool_tracker.bool_toggle, pos_args=(self.obj, mod.name),
highlight_hook=self.bool_tracker.bool_show_view_highlight, highlight_hook_args=(self.obj, mod.name)))
row.add_element(form.Button(
scroll_enabled=False, tips=["Lock modifier object visibility"],
width=20, height=20, use_padding=False,
callback=self.bool_tracker.add_selected_to_tracked, pos_args=(self.obj, mod.name),
highlight_hook=self.bool_tracker.bool_tracked_highlight, highlight_hook_args=(self.obj, mod.name),
glob_img_key='eyecon_closed', glob_img_key_update_func=self.bool_tracker.bool_tracked_img_key_update, glob_img_key_update_args=(self.obj, mod.name)))
row.add_element(form.Button(
scroll_enabled=False, text="✓", tips=["Click : Apply modifier", "Shift Click : Apply modifiers up to"],
width=25, height=20, use_padding=False,
callback=self.bool_tracker.apply_mods_form, pos_args=(context, self, mod.name, False), neg_args=(context, self, mod.name, True)))
group.row_insert(row)
return group
def child_group(self):
group = form.Scroll_Group()
for index, child in enumerate(self.child_tracker.children):
row = group.row()
text = form.shortened_text(child.name, width=100, font_size=12)
tip = [] if text == child.name else [child.name]
row.add_element(form.Label(text=str(index + 1), width=25, height=20))
row.add_element(form.Button(
scroll_enabled=False, text=text, tips=tip,
width=130, height=20, use_padding=False,
callback=self.child_tracker.make_selected_active, pos_args=(index,), neg_args=(index,),
highlight_hook=self.child_tracker.highlight, highlight_hook_args=(index,)))
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.child_tracker.hide_toggle, pos_args=(child,),
highlight_hook=self.child_tracker.hide_highlight, highlight_hook_args=(child,)))
row.add_element(form.Button(
scroll_enabled=False, text="A", highlight_text="R", tips=["Click : Append / Remove from visibility"],
width=25, height=20, use_padding=False,
callback=self.child_tracker.add_to_tracked, pos_args=(child,),
highlight_hook=self.child_tracker.tracked_highlight, highlight_hook_args=(child,)))
group.row_insert(row)
return group
def set_exit_button_modifier_key_text(self):
if self.state == States.MOD:
self.exit_button.shift_text = "D"
self.exit_button.ctrl_text = "A"
elif self.state == States.BOOL or self.state == States.CHILD:
self.exit_button.shift_text = ""
self.exit_button.ctrl_text = ""
def exit_button_func(self, option=''):
self.form_exit = True
# Additional exit options for Mod Mode
if self.state == States.MOD:
self.form_exit_option = option
else:
if option == 'CANCEL':
self.form_exit_option = option
def exit_action(self, context, event):
self.common_exit(context)
if self.form_exit_option == 'CANCEL':
return self.cancel_exit(context)
if self.state == States.MOD:
if self.form_exit_option == 'DUPLICATE':
self.mod_tracker.exit_tracker(context, event, self.obj, force_ctrl=False, force_shift=True)
elif self.form_exit_option == 'APPLY':
self.mod_tracker.exit_tracker(context, event, self.obj, force_ctrl=True, force_shift=False)
elif self.state == States.BOOL:
self.bool_tracker.exit_tracker(context)
elif self.state == States.CHILD:
self.child_tracker.exit_tracker(context)
elif self.state == States.COLL:
self.coll_tracker.exit_tracker(context, event)
return {'FINISHED'}
def set_state(self, opt, context):
preset = ''
if opt == "Modifiers":
self.state = States.MOD
preset = 'MODS'
elif opt == "Booleans":
self.state = States.BOOL
preset = 'BOOL'
elif opt == "Children":
self.state = States.CHILD
preset = 'CHILD'
elif opt == "Collections":
self.state = States.COLL
preset = 'COLL'
self.set_exit_button_modifier_key_text()
self.update_tracker_data(context)
self.alter_form_layout(context)
def state_hook(self):
if self.state == States.MOD:
return "Modifiers"
elif self.state == States.BOOL:
return "Booleans"
elif self.state == States.CHILD:
return "Children"
elif self.state == States.COLL:
return "Collections"
def alter_form_layout(self, context):
if self.state == States.MOD:
self.mod_box.scroll_group = self.mod_group(context)
self.mod_box.height = self.mods_box_height()
self.form.row_activation(label='MODS', active=True)
self.form.row_activation(label='BOOL_REC', active=False)
self.form.row_activation(label='BOOL', active=False)
self.form.row_activation(label='CHILD', active=False)
elif self.state == States.BOOL:
self.bool_box.scroll_group = self.bool_group(context)
self.bool_box.height = self.bool_box_height()
self.form.row_activation(label='MODS', active=False)
self.form.row_activation(label='BOOL_REC', active=self.bool_tracker.recursive_active)
self.form.row_activation(label='BOOL', active=True)
self.form.row_activation(label='CHILD', active=False)
elif self.state == States.CHILD:
self.child_box.scroll_group = self.child_group()
self.child_box.height = self.child_box_height()
self.form.row_activation(label='MODS', active=False)
self.form.row_activation(label='BOOL_REC', active=False)
self.form.row_activation(label='BOOL', active=False)
self.form.row_activation(label='CHILD', active=True)
self.form.build(preserve_top_left=True)
def mods_box_height(self):
height = 20 * max_rows()
if self.state == States.MOD:
mod_len = len(self.obj.modifiers)
if mod_len < max_rows():
height = 20 * mod_len
return height
def bool_box_height(self):
height = 20 * max_rows()
if self.state == States.BOOL:
if self.bool_tracker.recursive_active:
mod_len = len(self.bool_tracker.recursive.active_mods())
if mod_len < max_rows():
height = 20 * mod_len
return height
mod_len = len(self.obj.modifiers)
if mod_len < max_rows():
height = 20 * mod_len
return height
def child_box_height(self):
height = 20 * max_rows()
if self.state == States.CHILD:
child_len = Child_Tracker.child_count(self.obj)
if child_len < max_rows():
height = 20 * child_len
return height
def exit_button_tips_updater(self):
if self.state == States.MOD:
return [
"Click : Finalize / Exit",
"Shift Click : Smart Apply Clone",
"Ctrl Click : Apply Visible Modifiers",
"Alt Click : Cancel / Exit"]
elif self.state == States.BOOL or self.state == States.CHILD:
return [
"Click : Finalize / Exit",
"Alt Click : Cancel / Exit"]
def bool_tips_updater(self, mod_name=''):
tip = []
mod = None
if mod_name in self.obj.modifiers:
mod = self.obj.modifiers[mod_name]
if not mod: return ["No Modifier"]
try:
if not hasattr(mod, 'name'): return ["No Modifier"]
text = form.shortened_text(mod.name, width=100, font_size=12)
tip = tip if text == mod.name else [mod.name]
obj = get_mod_object(mod)
if obj:
tip.append(f"Click : Reveal : {obj.name}")
if obj in self.bool_tracker.tracked_bools:
tip.append("Shift Click : Remove from additive")
else:
tip.append("Shift Click : Add to additive")
else:
tip.append("No Object in Modifier")
except: pass
tip.append("Ctrl Click : Popup Menu")
return tip
# --- TRACKERS --- #
def update_tracker_data(self, context):
# Global
self.coll_tracker.set_collectons_back()
# Trackers
if self.state == States.MOD:
self.mod_tracker.update_data(context, self.obj)
elif self.state == States.BOOL:
self.bool_tracker.update_data(context, self.obj)
elif self.state == States.CHILD:
self.child_tracker.update_data(context, self.obj)
elif self.state == States.COLL:
self.coll_tracker.update_data(context, self.obj)
def scroll_trackers(self, context, step=0):
if self.state == States.MOD:
self.mod_tracker.cycle_mods(context, step)
elif self.state == States.CHILD:
self.child_tracker.cycle_children(context, step)
elif self.state == States.BOOL:
self.bool_tracker.cycle_bools(context, step)
elif self.state == States.COLL:
self.coll_tracker.cycle_coll(context, step)
# --- UTILS --- #
def toggle_state(self, context):
if self.state == States.BOOL:
self.state = States.MOD
elif self.state == States.MOD:
self.state = States.CHILD
elif self.state == States.CHILD:
self.state = States.BOOL
self.set_exit_button_modifier_key_text()
self.update_tracker_data(context)
self.alter_form_layout(context)
self.display_state_notification()
def display_state_notification(self):
if self.state == States.CHILD:
bpy.ops.hops.display_notification(info="Children")
elif self.state == States.BOOL:
bpy.ops.hops.display_notification(info="Booleans")
elif self.state == States.MOD:
bpy.ops.hops.display_notification(info="Modifiers")
elif self.state == States.COLL:
bpy.ops.hops.display_notification(info="Collections")
# --- EXIT --- #
def common_exit(self, context):
self.form.shut_down(context)
if self.auto_scroll.timer: context.window_manager.event_timer_remove(self.auto_scroll.timer)
self.remove_shaders()
collapse_3D_view_panels(self.original_tool_shelf, self.original_n_panel)
self.master.run_fade()
self.entry_state = 'NONE'
def confirm_exit(self, context, event):
self.common_exit(context)
if self.state == States.MOD:
self.mod_tracker.exit_tracker(context, event, self.obj)
elif self.state == States.BOOL:
self.bool_tracker.exit_tracker(context, select_all=self.hops_dots_op_used)
elif self.state == States.CHILD:
self.child_tracker.exit_tracker(context)
elif self.state == States.COLL:
self.coll_tracker.exit_tracker(context, event)
self.__class__.operator = None
return {'FINISHED'}
def cancel_exit(self, context):
self.common_exit(context)
bpy.ops.ed.undo_push()
bpy.ops.ed.undo()
self.__class__.operator = None
return {'CANCELLED'}
# --- AUTO SCROLL --- #
def toggle_auto_scroll(self, context):
self.auto_scroll.active = not self.auto_scroll.active
self.mod_tracker.looping = True
self.child_tracker.looping = True
if self.auto_scroll.active:
self.auto_scroll.timer = context.window_manager.event_timer_add(0.1, window=context.window)
self.auto_scroll.activated_time = time.time()
self.play_button.change_image_group_key('pause')
else:
self.play_button.change_image_group_key('play')
if self.auto_scroll.timer:
context.window_manager.event_timer_remove(self.auto_scroll.timer)
self.auto_scroll.timer = None
if self.form.is_dot_open():
if self.auto_scroll_form_label:
self.auto_scroll_form_label.text = ""
def auto_scroll_update(self, context, event):
s_key = event.type == 'S' and event.value == 'PRESS' and event.ctrl and not event.shift
space_key = event.type == 'SPACE' and event.value == 'PRESS' and event.shift
if s_key or space_key:
self.toggle_auto_scroll(context)
if not self.auto_scroll.active: return
if self.form.is_dot_open():
if self.auto_scroll_form_label:
self.auto_scroll_form_label.text = self.auto_scroll.display_msg()
self.time = self.auto_scroll.display_msg()
if self.auto_scroll.sequance_hold:
if time.time() - self.auto_scroll.sequance_hold_time > 1:
self.auto_scroll.activated_time = time.time()
self.auto_scroll.sequance_hold = False
else: return
if time.time() - self.auto_scroll.activated_time > addon.preference().property.auto_scroll_time_interval:
self.auto_scroll.activated_time = time.time()
# Make both left and right scroll same direction
step = 1
if addon.preference().property.modal_handedness == 'RIGHT': step *= -1
self.scroll_trackers(context, step)
# Pause looping on sequence restart
if self.auto_scroll.sequance_hold == False:
if self.state == States.MOD:
self.auto_scroll.sequance_hold = self.mod_tracker.auto_scroll_sequance_begin
self.auto_scroll.sequance_hold_time = time.time()
self.mod_tracker.auto_scroll_sequance_begin = False
elif self.state == States.CHILD:
self.auto_scroll.sequance_hold = self.child_tracker.auto_scroll_sequance_begin
self.auto_scroll.sequance_hold_time = time.time()
self.child_tracker.auto_scroll_sequance_begin = False
elif self.state == States.BOOL:
self.auto_scroll.sequance_hold = self.bool_tracker.auto_scroll_sequance_begin
self.auto_scroll.sequance_hold_time = time.time()
self.bool_tracker.auto_scroll_sequance_begin = False
# Adjust speed
if self.form.active(): return
if event.type in {'WHEELDOWNMOUSE', 'LEFT_BRACKET'} and event.value == 'PRESS':
self.auto_scroll.activated_time = time.time()
addon.preference().property.auto_scroll_time_interval -= .125
if addon.preference().property.auto_scroll_time_interval < .1:
addon.preference().property.auto_scroll_time_interval = .25
bpy.ops.hops.display_notification(info=f"Interval time set: {addon.preference().property.auto_scroll_time_interval:.3f}")
elif event.type in {'WHEELUPMOUSE', 'RIGHT_BRACKET'} and event.value == 'PRESS':
self.auto_scroll.activated_time = time.time()
addon.preference().property.auto_scroll_time_interval += .125
if addon.preference().property.auto_scroll_time_interval > 60:
addon.preference().property.auto_scroll_time_interval = 60
bpy.ops.hops.display_notification(info=f"Interval time set: {addon.preference().property.auto_scroll_time_interval:.3f}")
# --- SHADER --- #
def remove_shaders(self):
if self.draw_handle_2D:
self.draw_handle_2D = bpy.types.SpaceView3D.draw_handler_remove(self.draw_handle_2D, "WINDOW")
def safe_draw_2D(self, context):
method_handler(self.draw_shader_2D,
arguments = (context,),
identifier = 'EverScroll V2 Shader',
exit_method = self.remove_shaders)
def draw_shader_2D(self, context):
self.form.draw()
if self.form.is_dot_open(): return
self.auto_scroll.draw()
class HOPS_OT_Ever_Scroll_V2_Popup(bpy.types.Operator):
bl_idname = "hops.ever_scroll_v2_popup"
bl_label = ""
bl_description = ""
def __del__(self):
op = HOPS_OT_Ever_Scroll_V2.operator
if not op: return
op.popup_active = False
def execute(self, context):
op = HOPS_OT_Ever_Scroll_V2.operator
if not op: return {'CANCELLED'}
if op.state == States.MOD:
op.b_state = "Modifiers"
elif op.state == States.BOOL:
op.b_state = "Booleans"
elif op.state == States.CHILD:
op.b_state = "Children"
op.popup_active = True
return bpy.context.window_manager.invoke_popup(self, width=int(180 * get_dpi_factor(force=False)))
def draw(self, context):
layout = self.layout
op = HOPS_OT_Ever_Scroll_V2.operator
if not op: return
if op.form_exit:
layout.label(text='Finished')
return
row = layout.row()
row.prop(op, "b_state", text='')
row.operator("hops.ever_scroll_v2_scroll", text='', icon='PAUSE' if op.auto_scroll.active else 'PLAY')
if op.state == States.CHILD:
row.operator("hops.ever_scroll_v2_finishobj", text='', icon='CHECKMARK')
else:
row.operator("hops.ever_scroll_v2_finish", text='', icon='CHECKMARK')
def row_layout(row):
# row = row.split(factor=0.10, align=True)
# row.alignment = 'LEFT'
# row.label(text=str(i + 1))
row = row.row(align=True)
row.alignment = 'EXPAND'
return row
def mod_func(row, mod_name):
row.separator()
row.operator_context = 'INVOKE_DEFAULT'
row.prop(mod, 'show_viewport', text='')
row.prop(mod, 'show_render', text='')
props = row.operator('hops.ever_scroll_v2_modedit', text="", icon='PROPERTIES')
props.name = mod_name
apply = row.operator("hops.ever_scroll_v2_apply", text="", icon='CHECKMARK')
apply.mod_name = mod_name
if op.state == States.MOD:
for i, mod in enumerate(context.active_object.modifiers):
row = layout.row(align=True)
row = row_layout(row)
row.alert = op.mod_tracker.current_mod == mod
row.operator_context = 'INVOKE_DEFAULT'
op_btn = row.operator("hops.ever_scroll_v2_modbtn", text=mod.name)
op_btn.index = i
row = row.row(align=True)
row.alert = False
mod_func(row, mod.name)
elif op.state == States.BOOL:
for i, mod in enumerate(context.active_object.modifiers):
active = op.bool_tracker.current_mod == mod
row = layout.row(align=True)
row = row_layout(row)
if modifier.mod_type(mod) == 'BOOLEAN':
row.alert = active
op_btn = row.operator("hops.ever_scroll_v2_boolbtn", text=mod.name)
op_btn.index = i
row = row.row(align=True)
row.alert = False
row.separator()
row.operator_context = 'INVOKE_DEFAULT'
row.prop(mod, 'show_viewport', text='')
obj_vis = row.operator('hops.ever_scroll_v2_boolvisbtn', icon='LOCKED' if op.bool_tracker.bool_tracked_highlight(mod.id_data, mod.name) else 'UNLOCKED')
obj_vis.obj_name = mod.id_data.name
obj_vis.mod_name = mod.name
props = row.operator('hops.ever_scroll_v2_modedit', text="", icon='PROPERTIES')
props.name = mod.name
apply = row.operator("hops.ever_scroll_v2_apply", text="", icon='CHECKMARK')
apply.mod_name = mod.name
else:
row.label(text=mod.name)
row.alert = active
elif op.state == States.CHILD:
for i, obj in enumerate(op.child_tracker.children):
row = row_layout(layout)
row.alert = op.child_tracker.current_obj == obj
active = row.operator("hops.ever_scroll_v2_objbtn", text=obj.name)
active.index = i
row.separator()
row = row.row(align=True)
row.alert = False
vis = row.operator("hops.ever_scroll_v2_objvis", text='', icon="HIDE_ON" if obj.hide_get() else "HIDE_OFF")
vis.obj_name = obj.name
visadd = row.operator("hops.ever_scroll_v2_objvisadd", text='', icon='EVENT_R' if op.child_tracker.tracked_highlight(obj) else 'EVENT_A')
visadd.obj_name = obj.name
class HOPS_OT_Ever_Scroll_V2_Scroll(bpy.types.Operator):
bl_idname = "hops.ever_scroll_v2_scroll"
bl_label = "Auto Scroll"
bl_description = ""
def execute(self, context):
op = HOPS_OT_Ever_Scroll_V2.operator
if not op: return {'CANCELLED'}
op.toggle_auto_scroll(context)
return {'FINISHED'}
class HOPS_OT_Ever_Scroll_V2_Apply(bpy.types.Operator):
bl_idname = "hops.ever_scroll_v2_apply"
bl_label = "Apply"
bl_description = "Click : Apply modifier\nShift Click : Apply modifiers up to"
mod_name: bpy.props.StringProperty()
def invoke(self, context, event):
op = HOPS_OT_Ever_Scroll_V2.operator
if not op: return {'CANCELLED'}
if event.shift:
op.mod_tracker.apply_mods_form(context, op, self.mod_name, True)
else:
op.mod_tracker.apply_mods_form(context, op, self.mod_name, False)
return {'FINISHED'}
class HOPS_OT_Ever_Scroll_V2_Finish(bpy.types.Operator):
bl_idname = "hops.ever_scroll_v2_finish"
bl_label = "Finish"
bl_description = """Click : Finalize / Exit
Shift Click : Smart Apply Clone
Ctrl Click : Apply Visible Modifiers
Alt Click : Cancel / Exit"""
def invoke(self, context, event):
op = HOPS_OT_Ever_Scroll_V2.operator
if not op: return {'CANCELLED'}
val = ''
if event.shift:
val ='DUPLICATE'
elif event.ctrl:
val = 'CLONE'
elif event.alt:
val = 'CANCEL'
op.exit_button_func(val)
op.popup_active = False
return {'FINISHED'}
class HOPS_OT_Ever_Scroll_V2_FinishObj(bpy.types.Operator):
bl_idname = "hops.ever_scroll_v2_finishobj"
bl_label = "Finish"
bl_description = """Click : Finalize / Exit"
Alt Click : Cancel / Exit"""
def invoke(self, context, event):
op = HOPS_OT_Ever_Scroll_V2.operator
if not op: return {'CANCELLED'}
val = ''
if event.alt:
val = 'CANCEL'
op.exit_button_func(val)
op.popup_active = False
return {'FINISHED'}
class HOPS_OT_Ever_Scroll_V2_ModBtn(bpy.types.Operator):
bl_idname = "hops.ever_scroll_v2_modbtn"
bl_label = ""
bl_description = "Shift+Click: Reveal Mod object if applicable"
index: bpy.props.IntProperty()
def invoke (self, context, event):
op = HOPS_OT_Ever_Scroll_V2.operator
if not op: return {'CANCELLED'}
op.mod_tracker.make_selected_active(context, self.index, reveal_object=event.shift)
return {'FINISHED'}
class HOPS_OT_Ever_Scroll_V2_BoolBtn(bpy.types.Operator):
bl_idname = "hops.ever_scroll_v2_boolbtn"
bl_label = ""
bl_description = "Click: Reveal\nShift + Click: Additive reveal"
index: bpy.props.IntProperty()
def invoke (self, context, event):
op = HOPS_OT_Ever_Scroll_V2.operator
if not op: return {'CANCELLED'}
op.bool_tracker.make_selected_active(self.index, event.shift)
return {'FINISHED'}
class HOPS_OT_Ever_Scroll_V2_BoolVisBtn(bpy.types.Operator):
bl_idname = "hops.ever_scroll_v2_boolvisbtn"
bl_label = ""
bl_description = "Lock modifier object visibility"
obj_name: bpy.props.StringProperty()
mod_name: bpy.props.StringProperty()
def invoke (self, context, event):
op = HOPS_OT_Ever_Scroll_V2.operator
if not op: return {'CANCELLED'}
obj = bpy.data.objects[self.obj_name]
op.bool_tracker.add_selected_to_tracked(obj, self.mod_name)
return {'FINISHED'}
class HOPS_OT_Ever_Scroll_V2_ModEdit(bpy.types.Operator):
bl_idname = "hops.ever_scroll_v2_modedit"
bl_label = "Modifier Properties"
bl_description = "Edit Modifier Properties"
name: bpy.props.StringProperty()
def draw(self, context):
obj = context.active_object
mod = obj.modifiers[self.name]
getattr(DATA_PT_modifiers, mod.type)(DATA_PT_modifiers, self.layout, obj, mod)
def execute (self, context):
op = HOPS_OT_Ever_Scroll_V2.operator
if not op: return {'CANCELLED'}
return bpy.context.window_manager.invoke_popup(self, width=int(240 * get_dpi_factor(force=False)))
class HOPS_OT_Ever_Scroll_V2_ObjBtn(bpy.types.Operator):
bl_idname = "hops.ever_scroll_v2_objbtn"
bl_label = ""
bl_description = "Reveal"
index: bpy.props.IntProperty()
def execute(self, context):
op = HOPS_OT_Ever_Scroll_V2.operator
if not op: return {'CANCELLED'}
op.child_tracker.make_selected_active(self.index)
return {'FINISHED'}
class HOPS_OT_Ever_Scroll_V2_ObjVis(bpy.types.Operator):
bl_idname = "hops.ever_scroll_v2_objvis"
bl_label = ""
bl_description = "Toggle Visibility"
obj_name: bpy.props.StringProperty()
def invoke(self, context, event):
op = HOPS_OT_Ever_Scroll_V2.operator
if not op: return {'CANCELLED'}
obj = bpy.data.objects[self.obj_name]
op.child_tracker.hide_toggle(obj)
return {'FINISHED'}
class HOPS_OT_Ever_Scroll_V2_ObjVisAdd(bpy.types.Operator):
bl_idname = "hops.ever_scroll_v2_objvisadd"
bl_label = ""
bl_description = "Click : Append / Remove from visibility"
obj_name: bpy.props.StringProperty()
def invoke(self, context, event):
op = HOPS_OT_Ever_Scroll_V2.operator
if not op: return {'CANCELLED'}
obj = bpy.data.objects[self.obj_name]
op.child_tracker.add_to_tracked(obj)
return {'FINISHED'}