import bpy, math
from math import degrees
from . import operator
from ... utils.objects import set_active
from ... utility import modifier
from ... utility import addon
from ...ui_framework.operator_ui import Master
operations = (# ("KNIFE", "KNIFE", "KNIFE"),
("INSET", "Inset", "Inset"),
("SLASH", "Slash", "Slash"),
("INTERSECT", "Intersect", "Intersect"),
("UNION", "Union", "Union"),
("DIFFERENCE", "Difference", "Difference")
)
operations_nodes=(
("KNIFE", "Knife", "knife"),
("INSET", "Inset", "Inset"),
("SLASH", "Slash", "Slash"),
("INTERSECT", "Intersect", "Intersect"),
("UNION", "Union", "Union"),
("DIFFERENCE", "Difference", "Difference")
)
def operator_items(self, context):
return operations_nodes if addon.preference().property.workflow_modifier == 'NODES' else operations
class HOPS_BOOL_OPERATOR():
operator: bpy.props.EnumProperty(name="Mode",
items=operator_items)
boolshape: bpy.props.BoolProperty(
name="Boolshape",
description='Add boolshape Status',
default=True)
sort: bpy.props.BoolProperty(
name="Sort",
description="Modifier sorting for this bool operation",
default=True)
bstep: bpy.props.BoolProperty(
name="Up Level",
description="Add a new bevel modifier after this boolean operation",
default=False)
outset: bpy.props.BoolProperty(
name="Outset",
description="Set Inset to Outset",
default=False)
thickness: bpy.props.FloatProperty(
name="Thickness",
description="How deep the inset should cut",
default=0.50,
min=0.00,
soft_max=10.0,
step=1,
precision=3)
nodes_thickness: bpy.props.FloatProperty(
name="Thickness",
description="How deep the inset should cut",
default=0.1)
keep_bevels: bpy.props.BoolProperty(
name="Keep Bevels",
description="Keep Bevel modifiers on inset objects enabled if they don't use vertex groups or bevel weight",
default=False)
inset_slice: bpy.props.BoolProperty(
name="Inset Slice",
description="Create Slice from inset volume",
default=False)
transfer_normals: bpy.props.BoolProperty(
name="Transfer Normals",
description="Write current normals of inputs as Custom Normals",
default=False)
weld: bpy.props.BoolProperty(
name="Weld",
description="Weld duplicate vertices after boolean",
default=False)
merge_threshold: bpy.props.FloatProperty(
name="Merge Threshold",
description="Distance below whcih vertices are merged",
default=0.0001,
min=0.00,
precision=4)
stitch_distance: bpy.props.FloatProperty(
name="Stitch Distance",
description="Distance below whcih vertices of new edges are stitched",
default=0.0001,
min=0.00,
precision=4)
non_manifold: bpy.props.BoolProperty(
name="Non-manifold",
description="Makes cutter double-sided enabling use of non-manifold geometry.",
default=False)
called_ui = False
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
HOPS_BOOL_OPERATOR.called_ui = False
self.inset_slice = False
@classmethod
def poll(cls, context):
obj = context.active_object
return obj and obj.mode == 'OBJECT' and obj.type == 'MESH'
def operation(self):
return "DIFFERENCE"
def draw(self, context):
box = self.layout.box()
if bpy.app.version > (2, 83, 0):
row = box.row()
row.prop(addon.preference().property, "boolean_solver", text = 'Solver')
box.row().prop(self, "operator", text='')
self.layout.separator()
if addon.preference().property.workflow_modifier == 'NODES':
if self.operator == 'INSET':
row = self.layout.row()
row.prop(self, "inset_slice")
self.layout.prop(self, "nodes_thickness")
self.layout.separator()
elif self.operator == 'KNIFE':
row = self.layout.row()
row.prop(self, "non_manifold")
row.prop(self, "stitch_distance")
self.layout.separator()
row = self.layout.row()
row.prop(self, "transfer_normals")
row = self.layout.row()
row.prop(self, "weld")
row = row.split()
row.enabled = self.weld
row.prop(self, "merge_threshold")
else:
if self.operator == 'INSET':
row = self.layout.row()
row.prop(self, "outset")
row.prop(self, "keep_bevels")
row.prop(self, "inset_slice")
self.layout.prop(self, "thickness")
self.layout.separator()
# workflow shared
row = self.layout.row()
row.prop(self, "boolshape", text = 'Set Cutter To Boolshape')
row = self.layout.row()
row.prop(self, 'sort', text='Sort Modifiers')
row = self.layout.row()
#row.prop(self, 'bstep', text='Bevel Step')
row.prop(addon.preference().property, "parent_boolshapes", text='Parent To Target')
def invoke(self, context, event):
self.operator = self.operation()
self.boolshape = not event.shift
self.sort = not event.ctrl
self.bstep = event.ctrl and addon.preference().property.bool_bstep
return self.execute(context)
def execute(self, context):
if self.bstep:
active = context.active_object
old_objects = set(context.scene.objects)
bpy.ops.object.shade_smooth()
if len(context.selected_objects) == 1:
if all(obj.hops.status == 'BOOLSHAPE'for obj in context.selected_objects):
operator.shift(context, self.operator, boolshape=self.boolshape, sort=self.sort, outset=self.outset, thickness=self.thickness, keep_bevels=self.keep_bevels, parent=addon.preference().property.parent_boolshapes, inset_slice=self.inset_slice)
return {'FINISHED'}
else:
return {'CANCELLED'}
elif len(context.selected_objects) <= 1:
return {'CANCELLED'}
nodes = addon.preference().property.workflow_modifier == 'NODES'
thickness = self.thickness
if nodes:
thickness = self.nodes_thickness
operator.add(context, self.operator, boolshape=self.boolshape, sort=self.sort, outset=self.outset, thickness=thickness, keep_bevels=self.keep_bevels, parent=addon.preference().property.parent_boolshapes, inset_slice=self.inset_slice, transfer_normals=self.transfer_normals, weld=self.weld, merge_threshold=self.merge_threshold, stitch_distance=self.stitch_distance)
if self.bstep:
set_active(active, only_select=True)
new_objects = set(context.scene.objects)
objects = new_objects - old_objects
objects.add(active)
for obj in objects:
set_active(obj, select=True)
bpy.ops.hops.bevel_half_add()
modifier.user_sort(obj)
bpy.ops.hops.adjust_bevel('INVOKE_DEFAULT', ignore_ctrl=True)
return {'CANCELLED'}
extra_title = ''
if self.bstep:
extra_title = ' w/ Bstep'
elif self.sort != True and self.bstep != True:
extra_title = ' w/ SortBypass'
else:
if bpy.app.version > (2, 83, 0):
extra_title = f' ('+ addon.preference().property.boolean_solver +')'
else:
extra_title = ' '
# Operator UI
if not HOPS_BOOL_OPERATOR.called_ui:
HOPS_BOOL_OPERATOR.called_ui = True
ui = Master()
draw_data = [
[self.operator + extra_title],
["Parent Shapes - ", (addon.preference().property.parent_boolshapes)],
["Sort Modifiers - ", (self.sort)],
["Workflow - ", (addon.preference().property.workflow)],
["Boolean Operation complete"]]
if self.outset:
draw_data[0][0] = "OUTSET" + extra_title
draw_data.insert(2, ["Outset - ", "ON"])
if bpy.app.version > (2, 83, 0):
draw_data.insert(1, ["2.9X Solver - ", (addon.preference().property.boolean_solver)])
ui.receive_draw_data(draw_data=draw_data)
ui.draw(draw_bg=addon.preference().ui.Hops_operator_draw_bg, draw_border=addon.preference().ui.Hops_operator_draw_border)
return {'FINISHED'}
class HOPS_OT_BoolDifference(HOPS_BOOL_OPERATOR, bpy.types.Operator):
bl_idname = "hops.bool_difference"
bl_label = "Hops Difference Boolean"
bl_options = {'REGISTER', 'UNDO'}
bl_description = """Difference Boolean
Cuts mesh using Difference Boolean
LMB - Boolean Object (DEFAULT)
LMB + Shift - Extract
LMB + CTRL - Bypass Sort / Bstep (Add Bevel)
"""
def operation(self):
return "DIFFERENCE"
class HOPS_OT_BoolDifference_hotkey(HOPS_BOOL_OPERATOR, bpy.types.Operator):
bl_idname = "hops.bool_difference_hotkey"
bl_label = "Hops Difference Boolean"
bl_options = {'REGISTER', 'UNDO'}
bl_description = """Cuts mesh using Difference Boolean
LMB - Boolean Object (DEFAULT)
LMB + Shift - Extract
LMB + CTRL - Bypass Sort / Bstep (Add Bevel)
"""
def invoke(self, context, event):
self.operator = "DIFFERENCE"
self.boolshape = True
return self.execute(context)
class HOPS_OT_BoolUnion(HOPS_BOOL_OPERATOR, bpy.types.Operator):
bl_idname = "hops.bool_union"
bl_label = "Hops Union Boolean"
bl_options = {'REGISTER', 'UNDO'}
bl_description = """Union Boolean
Merges mesh using Union Boolean
LMB - Boolean Object (DEFAULT)
LMB + Shift - Extract
LMB + CTRL - Bypass Sort / Bstep (Add Bevel)
"""
def operation(self):
return "UNION"
class HOPS_OT_BoolUnion_hotkey(HOPS_BOOL_OPERATOR, bpy.types.Operator):
bl_idname = "hops.bool_union_hotkey"
bl_label = "Hops Union Boolean"
bl_options = {'REGISTER', 'UNDO'}
bl_description = """Union Boolean
Merges mesh using Union Boolean
LMB - Boolean Object (DEFAULT)
LMB + Shift - Extract
LMB + CTRL - Bypass Sort / Bstep (Add Bevel)
"""
def invoke(self, context, event):
self.operator = "UNION"
self.boolshape = True
return self.execute(context)
class HOPS_OT_BoolIntersect(HOPS_BOOL_OPERATOR, bpy.types.Operator):
bl_idname = "hops.bool_intersect"
bl_label = "Hops Intersect Boolean"
bl_options = {'REGISTER', 'UNDO'}
bl_description = """Intersect Boolean
Cuts mesh using Intersect Boolean
LMB - Boolean Object (DEFAULT)
LMB + Shift - Extract
LMB + CTRL - Bypass Sort / Bstep (Add Bevel)
"""
def operation(self):
return "INTERSECT"
class HOPS_OT_BoolIntersect_hotkey(HOPS_BOOL_OPERATOR, bpy.types.Operator):
bl_idname = "hops.bool_intersect_hotkey"
bl_label = "Hops Intersect Boolean"
bl_options = {'REGISTER', 'UNDO'}
bl_description = """Intersect Boolean
Cuts mesh using Intersect Boolean
LMB - Boolean Object (DEFAULT)
LMB + Shift - Extract
LMB + CTRL - Bypass Sort / Bstep (Add Bevel)
"""
def invoke(self, context, event):
self.operator = "INTERSECT"
self.boolshape = True
return self.execute(context)
class HOPS_OT_Slash(HOPS_BOOL_OPERATOR, bpy.types.Operator):
bl_idname = "hops.slash"
bl_label = "Hops Slash Boolean"
bl_options = {"REGISTER", "UNDO"}
bl_description = """Slash Boolean
Splits the primary mesh using the secondary mesh.
LMB - Boolean Object (DEFAULT)
LMB + Shift - Extract
LMB + CTRL - Bypass Sort / Bstep (Add Bevel)
"""
def operation(self):
return "SLASH"
class HOPS_OT_Slash_hotkey(HOPS_BOOL_OPERATOR, bpy.types.Operator):
bl_idname = "hops.slash_hotkey"
bl_label = "Hops Slash Boolean"
bl_options = {"REGISTER", "UNDO"}
bl_description = """Slash Boolean
Splits the primary mesh using the secondary mesh.
LMB - Boolean Object (DEFAULT)
LMB + Shift - Extract
LMB + CTRL - Bypass Sort / Bstep (Add Bevel)
"""
def invoke(self, context, event):
self.operator = "SLASH"
self.boolshape = True
return self.execute(context)
class HOPS_OT_BoolInset(HOPS_BOOL_OPERATOR, bpy.types.Operator):
bl_idname = "hops.bool_inset"
bl_label = "Hops Inset Boolean"
bl_options = {'REGISTER', 'UNDO'}
bl_description = """Inset/Outset Boolean
Cuts mesh using Inset/Outset Boolean
LMB - Boolean Object (DEFAULT)
LMB + Shift - Outset
LMB + CTRL - Bypass Sort / Bstep (Add Bevel)
"""
def operation(self):
return "INSET"
def invoke(self, context, event):
self.operator = self.operation()
self.outset = event.shift and not event.alt
self.keep_bevels = event.ctrl and not event.alt
self.sort = not event.ctrl
return self.execute(context)
class HOPS_OT_BoolKnife(bpy.types.Operator, HOPS_BOOL_OPERATOR):
bl_idname = "hops.bool_knife"
bl_label = "Hops Knife Boolean"
bl_options = {'REGISTER', 'UNDO'}
bl_description = """Knife Boolean
Cuts mesh using Knife Boolean
LMB - Boolean Knife (DEFAULT)
LMB + Shift - Knife Project
"""
knife_project: bpy.props.BoolProperty(
name="Knife Project",
description="Use knife project instead of boolean intersect",
default=False)
cut_through: bpy.props.BoolProperty(
name="Cut Through",
description="Use Cut Through Mesh",
default=True)
projection: bpy.props.EnumProperty(
name="Projection",
description="Object Projection",
items = [
("VIEW", "VIEW", "Project from view"),
("Z-", "Z-", "Project for Z- side of the object"),
("Z+", "Z+", "Project for Z+ side of the object"),
("X-", "X-", "Project for X- side of the object"),
("X+", "X+", "Project for X+ side of the object"),
("Y-", "Y-", "Project for Y- side of the object"),
("Y+", "Y+", "Project for Y+ side of the object"),
],
default = 'VIEW',
)
@classmethod
def poll(cls, context):
obj = context.active_object
return obj and obj.mode == 'OBJECT' and obj.type == 'MESH'
def draw(self, context):
row = self.layout.row()
row.prop(self, "knife_project")
if self.knife_project:
row.prop(self, "cut_through")
self.layout.row().prop(self, "projection")
return
if addon.preference().property.workflow_modifier == 'NODES':
HOPS_BOOL_OPERATOR.draw(self, context)
def invoke(self, context, event):
self.knife_project = event.shift
if addon.preference().property.workflow_modifier == 'NODES':
self.operator = 'KNIFE'
return self.execute(context)
def execute(self, context):
if self.knife_project:
bpy.ops.hops.display_notification(info='Knife Project Used', name="Knife Project")
return operator.knife(context, self.knife_project, material_cut=True, cut_through=self.cut_through, projection=self.projection)
if addon.preference().property.workflow_modifier == 'NODES':
operator.add(context, self.operator, boolshape=self.boolshape, sort=self.sort, outset=self.outset, thickness=self.nodes_thickness, keep_bevels=self.keep_bevels, parent=addon.preference().property.parent_boolshapes, inset_slice=self.inset_slice, transfer_normals=self.transfer_normals, weld=self.weld, merge_threshold=self.merge_threshold, stitch_distance=self.stitch_distance)
bpy.ops.hops.display_notification(info='Knife Modifier Added', name="Knife Modifier")
return {'FINISHED'}
bpy.ops.hops.display_notification(info='Knife Intersect Used', name="Knife Intersect")
return operator.knife(context, self.knife_project, material_cut=True, cut_through=self.cut_through, projection=self.projection)