import platform
import time
import string
import bpy
import bmesh
from math import radians, sqrt
from statistics import median
from mathutils import Matrix, Vector
from itertools import combinations
from bpy.types import Operator
from .. shader import dots
from .. dots import update
from .....utility import view3d
from ..... utility import addon, modifier
from ..... utils.blender_ui import get_dpi_factor
descriptions = {
'screw_x': "Adjust Screw X",
'screw_y': "Adjust Screw Y",
'screw_z': "Adjust Screw Z",
'solidify_x': "Adjust Solidify",
'solidify_y': "Adjust Solidify",
'solidify_z': "Adjust Solidify",
'solidify_c': "Adjust Solidify",
'displace_x': "Adjust Displace X",
'displace_y': "Adjust Displace Y",
'displace_z': "Adjust Displace Z",
'simple_deform_x': "Adjust Deform X",
'simple_deform_y': "Adjust Deform Y",
'simple_deform_z': "Adjust Deform Z",
'array_x': "Adjust Array X",
'array_y': "Adjust Array Y",
'array_z': "Adjust Array Z",
'bevel_c': "Adjust Bevel",
'wireframe_c': "Adjust Wireframe Thickness",
'boolshape': "Display BoolShape",
'Grab': "Grab Manipulator",
'Cut': "Cut Shape",
'Union': "Union Shape",
'Slash': "Slash Shape",
'Inset': "Inset Shape",
'Intersect': "Intersect Shape",
'Knife': "Knife Shape"
}
class HARDFLOWOM_OT_display(Operator):
bl_idname = 'hardflow_om.display'
bl_label = 'ctrl'
bl_description = 'Display Hardflow Dots'
bl_options = {'REGISTER', 'UNDO'}
use_operations: bpy.props.BoolProperty(default=True)
@classmethod
def poll(cls, context):
return any(o.type == 'MESH' for o in context.selected_objects)
def invoke(self, context, event):
if not context.active_object or context.active_object.type != 'MESH':
return {'CANCELLED'}
self.system = 'WINLIN' if platform.system() != 'Darwin' else 'MAC'
self.unhide_collection = False
hardflow = bpy.context.window_manager.hardflow
hardflow.running = True
# self.bm = bmesh.from_edit_mesh(context.active_object.data)
# preference = addon.preference()
self.original_region = bpy.context.region
self.original_active = bpy.data.objects[bpy.context.active_object.name]
self.mouse = self.start_mouse = Vector((event.mouse_region_x, event.mouse_region_y))
self.release = False
self.start_time = 0.0
self.tooltip = False
context.window_manager.modal_handler_add(self)
# hardflow.dots.points.clear()
if addon.preference().behavior.display_gizmo:
bpy.context.space_data.show_gizmo_context = False
self.collect(context)
update(self, context, hardflow.dots, self.mouse)
dots.handler = bpy.types.SpaceView3D.draw_handler_add(dots, (self, context), 'WINDOW', 'POST_PIXEL')
HARDFLOWOM_OT_display.running = True
context.area.tag_redraw()
return {'RUNNING_MODAL'}
def modal(self, context, event):
hardflow = bpy.context.window_manager.hardflow
standard = set(string.ascii_uppercase + string.digits + string.punctuation)
if not hardflow.running or context.region != self.original_region:
self.exit(context)
return {'CANCELLED'}
if context.active_object != self.original_active:
self.original_active = bpy.data.objects[bpy.context.active_object.name]
self.collect(context)
if event.value == 'RELEASE' and not self.release:
self.release = True
self.collect(context)
if event.type == 'MOUSEMOVE':
self.mouse = Vector((event.mouse_region_x, event.mouse_region_y))
update(self, context, hardflow.dots, self.mouse)
held = event.ctrl or (event.oskey and self.system == 'MAC')
if held:
def launch_mod_edit():
self.release = False
hardflow.dots.points.clear()
if self.highlight:
self.start_time = time.time()
if event.shift:
self.step_mod()
else:
self.use_mod(context)
def launch_dot_settings():
if self.highlight:
popover_types = {'screw', 'array', 'solidify', 'displace', 'simple_deform', 'bevel', 'wireframe'}
if self.highlight_type[:-2] in popover_types:
bpy.ops.hops.dot_settings()
config = context.window_manager.keyconfigs[context.preferences.keymap.active_keyconfig]
left_click = 0
if config.preferences:
select = getattr(config.preferences, 'select_mouse', None)
if select:
for i, prop in enumerate(config.preferences.bl_rna.properties['select_mouse'].enum_items):
if select == prop.identifier:
left_click = i
if event.type == 'LEFTMOUSE':
if event.value == 'PRESS':
if left_click:
launch_dot_settings()
else:
launch_mod_edit()
elif event.type == 'RIGHTMOUSE':
if event.value == 'PRESS':
if left_click:
launch_mod_edit()
else:
launch_dot_settings()
elif event.type == 'MIDDLEMOUSE':
if event.value == 'PRESS':
self.release = False
hardflow.dots.points.clear()
elif event.type in standard:
self.exit(context)
hardflow.dots.points.clear()
return {'PASS_THROUGH'}
else:
if addon.preference().behavior.display_gizmo:
bpy.context.space_data.show_gizmo_context = True
self.exit(context)
hardflow.dots.points.clear()
return {'CANCELLED'}
context.area.tag_redraw()
return {'PASS_THROUGH'}
def exit(self, context):
# Reset any possible op call settings
HARDFLOWOM_OT_display.use_operations = True
self.use_operations = True
hardflow = context.window_manager.hardflow
hardflow.running = False
hardflow.dots.display = False
if dots.handler:
dots.remove(dots)
self.active_point = None
hardflow.dots.hit = False
hardflow.dots.location = (0.0, 0.0, 0.0)
hardflow.dots.normal = Vector()
hardflow.dots.object = None
hardflow.dots.mesh = None
hardflow.dots.index = int()
hardflow.dots.points.clear()
def unmodified_bounds(self, obj, exclude={'ARRAY'}):
disabled = []
for mod in obj.modifiers:
if mod.type in exclude and mod.show_viewport:
disabled.append(mod)
mod.show_viewport = False
bpy.context.view_layer.update()
bounds = [Vector(point[:]) for point in obj.bound_box[:]]
for mod in disabled:
mod.show_viewport = True
del disabled
return bounds
def collect(self, context):
hardflow = bpy.context.window_manager.hardflow
if not hardflow.running:
return
hardflow.dots.points.clear()
ob = context.active_object
Xgizmos = []
Ygizmos = []
Zgizmos = []
Cgizmos = []
offset = addon.preference().display.dot_side_offset / 100
offsetc = addon.preference().display.dot_corner_offset / 100
mod_types = {
'SCREW',
'SOLIDIFY',
'DISPLACE',
'SIMPLE_DEFORM',
'ARRAY',
'BEVEL',
'WIREFRAME'}
c_types = {'BEVEL', 'WIREFRAME'}
both_types = {'SOLIDIFY'}
if addon.preference().behavior.display_dots:
for mod in ob.modifiers:
if mod.type in mod_types and not mod.name.startswith('Hops array displace'):
axis = 'C' if mod.type in c_types else ''
# c type append
if axis:
Cgizmos.append(self.collect_c_types(context, ob, mod, offsetc, Cgizmos))
# by type
elif mod.type == 'ARRAY':
displace = mod.relative_offset_displace
displace_axis = [displace[i] != 0 for i in range(3)]
index = displace_axis.index(True) if True in displace_axis else 0
axis = 'XYZ'[index]
# by attr
elif hasattr(mod, 'axis'):
axis = mod.axis
elif hasattr(mod, 'direction'):
if mod.direction not in {'X', 'Y', 'Z'}:
axis = 'Z'
else:
axis = mod.direction
elif hasattr(mod, 'deform_axis'):
axis = mod.deform_axis
# both type fallthrough
elif mod.type in both_types and not axis:
if len([m for m in ob.modifiers if m.type == mod.type]) > 1:
axis = 'C'
Cgizmos.append(self.collect_c_types(context, ob, mod, offsetc, Cgizmos))
else:
axis = 'Z'
# axis type append
if axis and axis != 'C':
gizmos = locals()[F'{axis}gizmos']
_type = self.collect_axis_types(context, ob, mod, offset, axis, gizmos)
gizmos.append(_type)
if addon.preference().behavior.display_boolshapes:
if addon.preference().behavior.display_boolshapes_for_all:
boolshapes = [obj for obj in context.view_layer.objects if obj.hops.status == "BOOLSHAPE" and not obj.visible_get()]
else:
boolshapes = [modifier.bool_object_get(mod) for mod in ob.modifiers if modifier.mod_type(mod) == "BOOLEAN"]
boolshapes = [o for o in boolshapes if o and not o.visible_get()]
for obj in boolshapes:
self.collect_bbox_origin(context, obj, 'boolshape', 'blue', obj.name)
if addon.preference().behavior.display_operators and self.use_operations:
if len(context.selected_objects) > 1:
factor = get_dpi_factor()
if addon.preference().property.dots_snap == 'FIXED':
self.collect_origins(context, 'Grab', 'black', Vector((0.0, 0.0)))
self.collect_origins(context, 'Cut', 'red', Vector((-20.0 * factor, 0.0 * factor)))
self.collect_origins(context, 'Union', 'green', Vector((-11.0 * factor, 20.0 * factor)))
self.collect_origins(context, 'Slash', 'yellow', Vector((-11.0 * factor, -20.0 * factor)))
self.collect_origins(context, 'Knife', 'blue', Vector((11.0 * factor, -20.0 * factor)))
self.collect_origins(context, 'Intersect', 'orange', Vector((11.0 * factor, 20.0 * factor)))
self.collect_origins(context, 'Inset', 'purple', Vector((20.0 * factor, 0.0 * factor)))
def collect_c_types(self, context, ob, mod, offset, Cgizmos):
factor = 1 if bpy.context.space_data.region_3d.is_perspective else 6
v_to_origin = (context.region_data.view_matrix@ob.location).length * factor
_type = F'{mod.type.lower()}_c'
pos = (
-(len(Cgizmos) * offset * v_to_origin),
len(Cgizmos) * offset * v_to_origin,
len(Cgizmos) * offset * v_to_origin)
self.collect_bbox_cross(context, ob, _type, mod.name, Vector(pos), _type)
return mod.type.lower()
def collect_axis_types(self, context, ob, mod, offset, axis, gizmos):
factor = 1 if bpy.context.space_data.region_3d.is_perspective else 6
v_to_origin = (context.region_data.view_matrix@ob.location).length * factor
bbox_collect = F'collect_bbox_{axis.lower()}face'
_type = F'{mod.type.lower()}_{axis.lower()}'
offs = len(gizmos) * offset * v_to_origin
index = ['X', 'Y', 'Z'].index(axis)
pos = [0, 0, 0]
pos[index] = offs
getattr(self, bbox_collect)(context, ob, _type, mod.name, Vector(pos), _type)
return mod.type.lower()
# FACES
def collect_bbox_origin(self, context, ob, types, color, obj_name):
hardflow = context.window_manager.hardflow
bbox_corners = [(ob.matrix_world @ (Vector(corner))) for corner in ob.bound_box]
new = hardflow.dots.points.add()
new.location3d = median([bbox_corners[0], bbox_corners[1], bbox_corners[2], bbox_corners[3], bbox_corners[4], bbox_corners[5], bbox_corners[6], bbox_corners[7]])
new.type = types
new.color = color
new.name = obj_name
new.description = descriptions[types]
def collect_bbox_cross(self, context, ob, types, mod_name, offset=Vector((0, 0, 0)), color='main'):
hardflow = context.window_manager.hardflow
locmat = Matrix.Translation(Vector((0, 0, 0))) @ ob.rotation_euler.to_matrix().to_4x4() @ (Matrix.Scale(1, 4, (1, 0, 0)) @ Matrix.Scale(1, 4, (0, 1, 0)) @ Matrix.Scale(1, 4, (0, 0, 1)))
bbox_corners = [(ob.matrix_world @ (Vector(corner))) + (locmat @ offset) for corner in self.unmodified_bounds(ob)]
new = hardflow.dots.points.add()
new.location3d = bbox_corners[2]
new.type = types
new.color = color
new.name = mod_name
new.description = descriptions[types]
def collect_bbox_yface(self, context, ob, types, mod_name, offset=Vector((0, 0, 0)), color='main'):
hardflow = context.window_manager.hardflow
locmat = Matrix.Translation(Vector((0, 0, 0))) @ ob.rotation_euler.to_matrix().to_4x4() @ (Matrix.Scale(1, 4, (1, 0, 0)) @ Matrix.Scale(1, 4, (0, 1, 0)) @ Matrix.Scale(1, 4, (0, 0, 1)))
bbox_corners = [(ob.matrix_world @ (Vector(corner))) + (locmat @ offset) for corner in self.unmodified_bounds(ob)]
new = hardflow.dots.points.add()
new.location3d = median([bbox_corners[2], bbox_corners[3], bbox_corners[6], bbox_corners[7]])
new.type = types
new.color = color
new.name = mod_name
new.description = descriptions[types]
def collect_bbox_zface(self, context, ob, types, mod_name, offset=Vector((0, 0, 0)), color='main'):
hardflow = context.window_manager.hardflow
locmat = Matrix.Translation(Vector((0, 0, 0))) @ ob.rotation_euler.to_matrix().to_4x4() @ (Matrix.Scale(1, 4, (1, 0, 0)) @ Matrix.Scale(1, 4, (0, 1, 0)) @ Matrix.Scale(1, 4, (0, 0, 1)))
bbox_corners = [(ob.matrix_world @ (Vector(corner))) + (locmat @ offset) for corner in self.unmodified_bounds(ob)]
new = hardflow.dots.points.add()
new.location3d = median([bbox_corners[1], bbox_corners[2], bbox_corners[5], bbox_corners[6]])
new.type = types
new.color = color
new.name = mod_name
new.description = descriptions[types]
def collect_bbox_xface(self, context, ob, types, mod_name, offset=Vector((0, 0, 0)), color='main'):
hardflow = context.window_manager.hardflow
locmat = Matrix.Translation(Vector((0, 0, 0))) @ ob.rotation_euler.to_matrix().to_4x4() @ (Matrix.Scale(1, 4, (1, 0, 0)) @ Matrix.Scale(1, 4, (0, 1, 0)) @ Matrix.Scale(1, 4, (0, 0, 1)))
bbox_corners = [(ob.matrix_world @ (Vector(corner))) + (locmat @ offset) for corner in self.unmodified_bounds(ob)]
new = hardflow.dots.points.add()
new.location3d = median([bbox_corners[4], bbox_corners[5], bbox_corners[7], bbox_corners[6]])
new.type = types
new.color = color
new.name = mod_name
new.description = descriptions[types]
def collect_bbox_ybface(self, context, ob, types, mod_name, offset=Vector((0, 0, 0))):
hardflow = context.window_manager.hardflow
locmat = Matrix.Translation(Vector((0, 0, 0))) @ ob.rotation_euler.to_matrix().to_4x4() @ (Matrix.Scale(1, 4, (1, 0, 0)) @ Matrix.Scale(1, 4, (0, 1, 0)) @ Matrix.Scale(1, 4, (0, 0, 1)))
bbox_corners = [(ob.matrix_world @ (Vector(corner))) + (locmat @ offset) for corner in self.unmodified_bounds(ob)]
new = hardflow.dots.points.add()
new.location3d = median([bbox_corners[0], bbox_corners[1], bbox_corners[4], bbox_corners[5]])
new.type = types
new.name = mod_name
new.description = descriptions[types]
def collect_bbox_zbface(self, context, ob, types, mod_name, offset=Vector((0, 0, 0))):
hardflow = context.window_manager.hardflow
locmat = Matrix.Translation(Vector((0, 0, 0))) @ ob.rotation_euler.to_matrix().to_4x4() @ (Matrix.Scale(1, 4, (1, 0, 0)) @ Matrix.Scale(1, 4, (0, 1, 0)) @ Matrix.Scale(1, 4, (0, 0, 1)))
bbox_corners = [(ob.matrix_world @ (Vector(corner))) + (locmat @ offset) for corner in self.unmodified_bounds(ob)]
new = hardflow.dots.points.add()
new.location3d = median([bbox_corners[3], bbox_corners[4], bbox_corners[7], bbox_corners[0]])
new.type = types
new.name = mod_name
new.description = descriptions[types]
def collect_bbox_xbface(self, context, ob, types, mod_name, offset=Vector((0, 0, 0))):
hardflow = context.window_manager.hardflow
locmat = Matrix.Translation(Vector((0, 0, 0))) @ ob.rotation_euler.to_matrix().to_4x4() @ (Matrix.Scale(1, 4, (1, 0, 0)) @ Matrix.Scale(1, 4, (0, 1, 0)) @ Matrix.Scale(1, 4, (0, 0, 1)))
bbox_corners = [(ob.matrix_world @ (Vector(corner))) + (locmat @ offset) for corner in self.unmodified_bounds(ob)]
new = hardflow.dots.points.add()
new.location3d = median([bbox_corners[0], bbox_corners[1], bbox_corners[2], bbox_corners[3]])
new.type = types
new.name = mod_name
new.description = descriptions[types]
# EDGES
def collect_bbox_xedge(self, context, ob, types, mod_name, offset=Vector((0, 0, 0))):
hardflow = context.window_manager.hardflow
locmat = Matrix.Translation(Vector((0, 0, 0))) @ ob.rotation_euler.to_matrix().to_4x4() @ (Matrix.Scale(1, 4, (1, 0, 0)) @ Matrix.Scale(1, 4, (0, 1, 0)) @ Matrix.Scale(1, 4, (0, 0, 1)))
bbox_corners = [(ob.matrix_world @ (Vector(corner))) + (locmat @ offset) for corner in self.unmodified_bounds(ob)]
new = hardflow.dots.points.add()
new.location3d = median([bbox_corners[4], bbox_corners[7]])
new.type = types
new.name = mod_name
new.description = descriptions[types]
def collect_bbox_yedge(self, context, ob, types, mod_name, offset=Vector((0, 0, 0))):
hardflow = context.window_manager.hardflow
locmat = Matrix.Translation(Vector((0, 0, 0))) @ ob.rotation_euler.to_matrix().to_4x4() @ (Matrix.Scale(1, 4, (1, 0, 0)) @ Matrix.Scale(1, 4, (0, 1, 0)) @ Matrix.Scale(1, 4, (0, 0, 1)))
bbox_corners = [(ob.matrix_world @ (Vector(corner))) + (locmat @ offset) for corner in self.unmodified_bounds(ob)]
new = hardflow.dots.points.add()
new.location3d = median([bbox_corners[3], bbox_corners[7]])
new.type = types
new.name = mod_name
new.description = descriptions[types]
def collect_bbox_zedge(self, context, ob, types, mod_name, offset=Vector((0, 0, 0))):
hardflow = context.window_manager.hardflow
locmat = Matrix.Translation(Vector((0, 0, 0))) @ ob.rotation_euler.to_matrix().to_4x4() @ (Matrix.Scale(1, 4, (1, 0, 0)) @ Matrix.Scale(1, 4, (0, 1, 0)) @ Matrix.Scale(1, 4, (0, 0, 1)))
bbox_corners = [(ob.matrix_world @ (Vector(corner))) + (locmat @ offset) for corner in self.unmodified_bounds(ob)]
new = hardflow.dots.points.add()
new.location3d = median([bbox_corners[1], bbox_corners[2]])
new.type = types
new.name = mod_name
new.description = descriptions[types]
def collect_origins(self, context, types, color, offset):
hardflow = context.window_manager.hardflow
selected = context.selected_objects
origins = [obj.location for obj in selected]
if addon.preference().property.dots_snap == 'CURSOR':
d2 = Vector((addon.preference().property.dots_x_cursor + self.start_mouse[0], addon.preference().property.dots_y_cursor + self.start_mouse[1]))
elif addon.preference().property.dots_snap == 'FIXED':
if addon.preference().property.dots_x < 40:
addon.preference().property.dots_x = 40
elif addon.preference().property.dots_x > (context.region.width - 40):
addon.preference().property.dots_x = (context.region.width - 40)
if addon.preference().property.dots_y < 40:
addon.preference().property.dots_y = 40
elif addon.preference().property.dots_y > (context.region.height - 40):
addon.preference().property.dots_y = (context.region.height - 40)
d2 = Vector((addon.preference().property.dots_x, addon.preference().property.dots_y))
elif addon.preference().property.dots_snap == 'ORIGIN':
d2 = view3d.location3d_to_location2d(median(origins))
d2 = d2 + offset
d3 = view3d.location2d_to_location3d(d2[0], d2[1], median(origins))
new = hardflow.dots.points.add()
new.location3d = d3
new.type = types
new.color = color
new.description = descriptions[types]
def collect_bbox_verts(self, context, ob):
hardflow = context.window_manager.hardflow
self.unmodified_bounds(ob)
for v in ob.bound_box:
new = hardflow.dots.points.add()
new.location3d = v
def step_mod(self):
step_types = {'bevel', 'screw', 'array'}
axis = self.highlight_type[-1].upper()
_type = self.highlight_type[:-2]
if _type not in step_types:
return
ot = F'mods_{_type}_step'
getattr(bpy.ops.hops, ot)('INVOKE_DEFAULT', axis=axis, modname=self.highlight_modname)
def use_mod(self, context):
types = {'array', 'screw', 'solidify', 'displace', 'simple_deform', 'bevel', 'wireframe'}
hardflow = bpy.context.window_manager.hardflow
axis = self.highlight_type[-1].upper()
_type = self.highlight_type[:-2]
# booleans
if addon.preference().property.dots_snap == 'FIXED':
if self.highlight_type == "Grab":
bpy.ops.hops.dots_grab('INVOKE_DEFAULT')
none_mesh_count = len([o for o in context.selected_objects if o.type != 'MESH'])
if none_mesh_count == 0:
if self.highlight_type == "Cut":
bpy.ops.hops.bool_difference_hotkey('INVOKE_DEFAULT')
elif self.highlight_type == "Union":
bpy.ops.hops.bool_union_hotkey('INVOKE_DEFAULT')
elif self.highlight_type == "Slash":
bpy.ops.hops.slash_hotkey('INVOKE_DEFAULT')
elif self.highlight_type == "Inset":
bpy.ops.hops.bool_inset('INVOKE_DEFAULT')
elif self.highlight_type == "Intersect":
bpy.ops.hops.bool_intersect_hotkey('INVOKE_DEFAULT')
elif self.highlight_type == "Knife":
bpy.ops.hops.bool_knife('INVOKE_DEFAULT')
if _type not in types and _type[:4] != 'bool':
return
if _type[:4] not in {'bool'}:
ot = F'mods_{_type}'
getattr(bpy.ops.hops, ot)('INVOKE_DEFAULT', axis=axis, modname=self.highlight_modname)
return
elif self.highlight_type == 'wireframe_c':
bpy.ops.hops.mods_wireframe('INVOKE_DEFAULT', modname=self.highlight_modname)
elif self.highlight_type == "boolshape":
bpy.ops.hops.select_boolshape(obj_name=self.highlight_modname)
return
hardflow.dots.points.clear()