import bpy
from .. icons import get_icon_id, icons
from math import radians, degrees
from .. utility import addon, modifier, geometry_nodes
from .. utils.blender_ui import get_dpi_factor
from .. utility.collections import turn_on_parent_collections
from bpy.props import BoolProperty, StringProperty
presets = {
"Width": [0.01, 0.02, 0.1],
"Segments": [1, 3, 4, 6, 12],
"Profile": [0.3, 0.5, 0.7],
"Angle": [30, 45, 60]}
class HOPS_OT_bevel_helper(bpy.types.Operator):
bl_idname = 'hops.bevel_helper'
bl_description = 'Display HOps Bevel Helper'
bl_label = 'HOps Bevel Helper'
label: bool = False
@classmethod
def poll(cls, context):
return any(o.type in {'MESH'} for o in context.selected_objects)
def check(self, context):
return True
def invoke(self, context, event):
preference = addon.preference().ui
for obj in context.selected_objects:
for mod in obj.modifiers:
if modifier.mod_type(mod) in {'BEVEL', 'BOOLEAN'}:
mod.show_expanded = False
mod_object = modifier.bool_object_get(mod)
if mod_object:
current_view = mod_object.hide_viewport
mod_object.hops.is_user_triggered = False
mod_object.hops.bevel_helper_hide = current_view
if context.space_data.local_view: # Check if we are in local view
mod_object.local_view_set(context.space_data, True)
if preference.use_bevel_helper_popup:
self.label = True
return context.window_manager.invoke_popup(self, width=int(240 * get_dpi_factor(force=False)))
else:
return context.window_manager.invoke_props_dialog(self, width=int(240 * get_dpi_factor(force=False)))
def execute(self, context):
return {'FINISHED'}
def draw(self, context):
preference = addon.preference().ui
layout = self.layout
if self.label:
layout.label(text='HOps Bevel Helper')
row = layout.row(align=True)
row.prop(preference, "show_bevel_in_bevel_helper", toggle=True, icon='MOD_BEVEL')
row.prop(preference, "show_boolean_in_bevel_helper", toggle=True, icon='MOD_BOOLEAN')
for obj in context.selected_objects:
if obj.type == 'MESH':
bevelmods = [mod for mod in obj.modifiers if mod.type == "BEVEL"]
booleanmods = [mod for mod in obj.modifiers if modifier.mod_type(mod) == "BOOLEAN"]
if bevelmods or booleanmods:
layout.label(text=obj.name, icon='OBJECT_DATA')
if preference.show_bevel_in_bevel_helper:
if bevelmods:
for mod in bevelmods:
self.draw_bevel(context, obj, mod)
if preference.show_boolean_in_bevel_helper:
if booleanmods:
layout.separator()
# Draw toggle all buttons
split = layout.split(factor=0.5)
row = split.row(align=True)
row.operator('hops.bevel_helper_hide_all_bools', text='Show All', icon='HIDE_OFF').obj_name = obj.name
if bpy.app.version > (2, 90, 0):
split = split.split()
row = split.row(align=True)
# row.alignment = 'LEFT'
for itm in bpy.types.BooleanModifier.bl_rna.properties['solver'].enum_items:
solver = row.operator('hops.bevel_helper_boolean_solver', text=itm.identifier[:1])
solver.obj_name = obj.name
solver.solver = itm.identifier
row = split.row(align=True)
# row.alignment = 'RIGHT'
row.operator('hops.bevel_helper_unhide_all_bools', text='Hide All', icon='HIDE_ON').obj_name = obj.name
for mod in booleanmods:
self.draw_boolean(context, obj, mod)
layout.separator()
def draw_boolean(self, context, obj, mod):
layout = self.layout
split = layout.split(factor=0.5)
row = split.row()
row.alignment = 'LEFT'
prop_data = geometry_nodes.NodeBoolean.prop_draw_data
wrapper = geometry_nodes.NodeBoolean.new(mod)
# row.prop(mod, 'show_expanded', text='', emboss=False)
row.alert = True
mod_object = wrapper.object if wrapper else mod.object
if mod_object:
name = mod_object.name
row.alert = False
row.separator()
operation = wrapper.operation if wrapper else mod.operation
op_icon = {
'INTERSECT': 'orange',
'UNION': 'green',
'DIFFERENCE': 'red',
'KNIFE': 'blue',
'INSET': 'purple',
}
icon = get_icon_id(op_icon.get(operation, 'red'))
swapoperator = row.operator('hops.bevel_helper_bool_swap', text='', icon_value=icon, emboss=False)
swapoperator.obj_name = obj.name
swapoperator.mod_name = mod.name
row.prop(*prop_data(mod, 'name'), text='', emboss=False)
row.separator()
row.prop(*prop_data(mod, 'show_viewport'), text='', emboss=False)
if bpy.app.version > (2, 90, 0):
split = split.row(align=True).split(factor=0.3)
row = split.row(align=True)
row.alignment = 'LEFT'
row.prop(*prop_data(mod, 'solver'), text='', emboss=True)
row = split.row(align=True)
row.alignment = 'RIGHT'
if mod_object:
row.label(text=name)
row.separator()
icon = 'HIDE_ON' if mod_object.hops.bevel_helper_hide else 'HIDE_OFF'
row.prop(mod_object.hops, 'bevel_helper_hide', text='', icon=icon, emboss=False)
row.separator()
if mod_object.hops.bevel_helper_hide:
row.label(text='')
else:
icon = 'RESTRICT_SELECT_OFF' if mod_object.select_get() else 'RESTRICT_SELECT_ON'
selectoperator = row.operator('hops.bevel_helper_bool_select', text='', icon=icon, emboss=False)
selectoperator.obj_name = obj.name
selectoperator.mod_name = mod.name
# column.separator()
def draw_bevel(self, context, obj, mod):
preference = addon.preference().property
layout = self.layout
split = layout.split(factor=0.5)
row = split.row()
row.alignment = 'LEFT'
row.prop(mod, 'show_expanded', text='', emboss=False)
row.prop(mod, 'name', text='', icon='MOD_BEVEL', emboss=False)
row = split.row(align=True)
row.alignment = 'RIGHT'
# if index == 0:
row.prop(preference, 'show_presets', text='', icon=F'RESTRICT_SELECT_O{"FF" if preference.show_presets else "N"}', emboss=False)
row.prop(mod, 'show_viewport', text='', emboss=False)
split = layout.split(factor=0.1)
column = split.column()
column.separator()
column = split.column(align=True)
if mod.show_expanded:
self.expanded(context, column, obj, mod)
else:
self.label_row(context, column, obj, mod, 'width', label='Width')
self.label_row(context, column, obj, mod, 'segments', label='Segments')
if bpy.app.version < (2, 90, 0):
column.row().prop(mod, "use_custom_profile")
row = column.row()
row.enabled = mod.use_custom_profile
if mod.use_custom_profile:
column.template_curveprofile(mod, "custom_profile")
row2 = column.row(align=True)
op = row2.operator('hops.save_bevel_profile', text='Save Profile')
op.obj, op.mod = context.object.name, mod.name
op = row2.operator('hops.load_bevel_profile', text='Load Profile')
op.obj, op.mod = context.object.name, mod.name
else:
column.row().prop(mod, "profile_type", expand=True)
if mod.profile_type == 'CUSTOM':
column.template_curveprofile(mod, "custom_profile")
col = column.row(align=True)
op = col.operator('hops.save_bevel_profile', text='Save Profile')
op.obj, op.mod = context.object.name, mod.name
op = col.operator('hops.load_bevel_profile', text='Load Profile')
op.obj, op.mod = context.object.name, mod.name
def expanded(self, context, layout, obj, mod):
self.label_row(context, layout, obj, mod, 'width', label='Width')
self.label_row(context, layout, obj, mod, 'segments', label='Segments')
self.label_row(context, layout, obj, mod, 'profile', label='Profile')
layout.separator()
self.label_row(context, layout, obj, mod, 'limit_method', label='Method')
if mod.limit_method == 'ANGLE':
self.label_row(context, layout, obj, mod, 'angle_limit', label='Angle')
layout.separator()
elif mod.limit_method == 'VGROUP':
self.label_row(context, layout, obj, mod, 'vertex_group', label='Vertex Group')
layout.separator()
self.label_row(context, layout, obj, mod, 'offset_type', label='Width Method')
layout.separator()
self.label_row(context, layout, obj, mod, 'miter_outer', label='Outer')
self.label_row(context, layout, obj, mod, 'miter_inner', label='Inner')
self.label_row(context, layout, obj, mod, 'spread', label='Spread')
layout.separator()
self.label_row(context, layout, obj, mod, 'use_clamp_overlap', label='Clamp Overlap')
self.label_row(context, layout, obj, mod, 'harden_normals', label='Harden Normals')
layout.separator()
def label_row(self, context, layout, obj, path, prop, label='Label'):
preference = addon.preference().property
column = layout.column(align=True)
split = column.split(factor=0.5, align=True)
split.label(text=label)
row = split.row(align=True)
if label == 'Width':
width = obj.modifiers[path.name].width
sub = row.row(align=True)
sub.scale_x = 0.3
ot = sub.operator('wm.context_set_float', text='/')
ot.data_path = F'active_object.modifiers["{path.name}"].width'
ot.value = width / 2
row.prop(path, prop, text='')
sub = row.row(align=True)
sub.scale_x = 0.3
ot = sub.operator('wm.context_set_float', text='*')
ot.data_path = F'active_object.modifiers["{path.name}"].width'
ot.value = width * 2
else:
row.prop(path, prop, text='')
if label in {'Width', 'Segments', 'Profile', 'Angle'} and preference.show_presets:
split = column.split(factor=0.5, align=True)
split.separator()
row = split.row(align=True)
for preset in presets[label]:
ot = row.operator(
F'wm.context_set_{"int" if label not in {"Width", "Profile", "Angle"} else "float"}', text=str(preset))
ot.data_path = F'active_object.modifiers["{path.name}"].{prop}'
ot.value = preset if label != 'Angle' else radians(preset)
if label not in {'Profile', 'Angle'}:
column.separator()
class HOPS_OT_bevel_helper_hide_false(bpy.types.Operator):
bl_idname = 'hops.bevel_helper_hide_false'
bl_description = 'Hide for bevel helper'
bl_label = 'HOps Bevel Helper'
obj_name: StringProperty(name="obj name")
def execute(self, context):
obj = bpy.data.objects[self.obj_name]
turn_on_parent_collections(obj, context.scene.collection)
obj.hide_set(False)
bpy.ops.hops.draw_wire_mesh_launcher('INVOKE_DEFAULT', target='NONE', object_name=obj.name)
return {'FINISHED'}
class HOPS_OT_bevel_helper_hide_true(bpy.types.Operator):
bl_idname = 'hops.bevel_helper_hide_true'
bl_description = 'Hide for bevel helper'
bl_label = 'HOps Bevel Helper'
obj_name: StringProperty(name="obj name")
def execute(self, context):
obj = bpy.data.objects[self.obj_name]
turn_on_parent_collections(obj, context.scene.collection)
obj.hide_set(True)
return {'FINISHED'}
class HOPS_OT_bevel_helper_hide_all_bools(bpy.types.Operator):
bl_idname = 'hops.bevel_helper_hide_all_bools'
bl_description = 'Hide all boolean objects'
bl_label = 'HOps Bevel Helper'
obj_name: StringProperty(name="obj name")
def execute(self, context):
obj = bpy.data.objects[self.obj_name]
toggle_all_bools(context, obj, on=True)
return {'FINISHED'}
class HOPS_OT_bevel_helper_unhide_all_bools(bpy.types.Operator):
bl_idname = 'hops.bevel_helper_unhide_all_bools'
bl_description = 'Unhide all boolean objects'
bl_label = 'HOps Bevel Helper'
obj_name: StringProperty(name="obj name")
def execute(self, context):
obj = bpy.data.objects[self.obj_name]
toggle_all_bools(context, obj, on=False)
return {'FINISHED'}
class HOPS_OT_bevel_helper_boolean_solver(bpy.types.Operator):
bl_idname = 'hops.bevel_helper_boolean_solver'
bl_description = 'Set solver for booleans'
bl_label = 'HOps Bevel Helper'
obj_name: StringProperty(name="obj name")
solver: StringProperty(name="FAST")
def execute(self, context):
obj = bpy.data.objects[self.obj_name]
if hasattr(obj, 'modifiers'):
for mod in obj.modifiers:
if mod.type == 'BOOLEAN':pass
elif geometry_nodes.NodeBoolean.is_valid_modifier(mod):
mod = geometry_nodes.NodeBoolean.new_unchecked(mod)
else:
continue
mod.solver = self.solver
return {'FINISHED'}
def toggle_all_bools(context, obj, on=True):
'''Hide / Unhide all objects referenced by the mods of the object.'''
if hasattr(obj, 'modifiers'):
bools = set()
for mod in obj.modifiers:
mod_object = modifier.bool_object_get(mod)
if mod_object is not None:
bools.add(mod_object)
elif mod.type == 'NODES':
for node in mod.node_group.nodes:
if (node.type == 'OBJECT_INFO') and (node.inputs['Object'].default_value is not None):
bools.add(node.inputs['Object'].default_value)
for obj in bools:
if isinstance(obj, bpy.types.Object):
turn_on_parent_collections(obj, context.scene.collection)
obj.hide_set(not on)
obj.hops.bevel_helper_hide = not on
class HOPS_OT_bevel_helper_bool_swap(bpy.types.Operator):
bl_idname = 'hops.bevel_helper_bool_swap'
bl_description = 'Swap boolean'
bl_label = 'HOps Bevel Helper'
obj_name: StringProperty(name="obj name")
mod_name: StringProperty(name="mod name")
def execute(self, context):
mod = bpy.data.objects[self.obj_name].modifiers[self.mod_name]
wrapper = geometry_nodes.NodeBoolean.new(mod)
if wrapper:
mod = wrapper
ops = [val for val in geometry_nodes.node_boolean.OPERATION_INPUT if type(val) == str]
else:
ops = [itm.identifier for itm in bpy.types.BooleanModifier.bl_rna.properties['operation'].enum_items]
index = ops.index(mod.operation)
mod.operation = ops[(index + 1) % len(ops)]
return {'FINISHED'}
class HOPS_OT_bevel_helper_bool_select(bpy.types.Operator):
bl_idname = 'hops.bevel_helper_bool_select'
bl_description = 'Select boolean'
bl_label = 'HOps Bevel Helper'
obj_name: StringProperty(name="obj name")
mod_name: StringProperty(name="mod name")
def execute(self, context):
mod = bpy.data.objects[self.obj_name].modifiers[self.mod_name]
if mod.object:
if mod.object.select_get():
mod.object.select_set(False)
else:
mod.object.select_set(True)
return {'FINISHED'}