- 添加Blender插件核心文件:__init__.py、ui.py、property.py、preference.py - 添加插件工具模块:g.py、loop.py、generate_loop.py、const.py、op.py - 添加翻译工具:utils/trans.py - 添加PuLP线性规划库及其依赖文件,包括CBC求解器二进制文件 - 添加.gitignore和VSCode配置文件
709 lines
25 KiB
Python
709 lines
25 KiB
Python
from __future__ import annotations
|
||
import bpy
|
||
import bmesh
|
||
from bpy_extras import view3d_utils
|
||
import time
|
||
import mathutils
|
||
|
||
|
||
from . import const, g
|
||
from .utils import functions
|
||
from .draw import draw
|
||
from . import boundary
|
||
from . import generate_loop
|
||
from .utils.trans import t
|
||
|
||
|
||
class EasyPatchModalOperator(bpy.types.Operator):
|
||
bl_idname = f"{const.addon_prefix}.start"
|
||
bl_label = "Start Patching"
|
||
bl_options = {"REGISTER", "UNDO"} # 不加UNDO撤回的话,会导致用户前一个步骤也撤回
|
||
|
||
def __init__(self, *args, **kwargs):
|
||
super().__init__(*args, **kwargs)
|
||
|
||
def modal(self, context, event):
|
||
try:
|
||
if context.area:
|
||
context.area.tag_redraw()
|
||
|
||
result = self.process_undo(context, event)
|
||
if result is not None:
|
||
return result
|
||
|
||
# start_timestamp = time.time()
|
||
# process drag
|
||
result = self.process_drawing_verts(context, event)
|
||
if result is not None:
|
||
return result
|
||
# drawing_verts_consume_time = time.time() - start_timestamp
|
||
|
||
# process stop
|
||
result = self.process_stop(context, event)
|
||
if result is not None:
|
||
return result
|
||
|
||
# start_timestamp = time.time()
|
||
result = self.process_running(context, event)
|
||
if result is not None:
|
||
return result
|
||
# running_consume_time = time.time() - start_timestamp
|
||
|
||
# start_timestamp = time.time()
|
||
result = self.process_loop(context, event)
|
||
if result is not None:
|
||
return result
|
||
# process_loop_consume_time = time.time() - start_timestamp
|
||
|
||
# start_timestamp = time.time()
|
||
update_hover(context, event)
|
||
# update_hover_consume_time = time.time() - start_timestamp
|
||
|
||
# last_echo_time = g.last_echo_time if g.last_echo_time else 0
|
||
# if time.time() - last_echo_time > 0.1:
|
||
# print(
|
||
# drawing_verts_consume_time,
|
||
# running_consume_time,
|
||
# process_loop_consume_time,
|
||
# update_hover_consume_time,
|
||
# )
|
||
# g.last_echo_time = time.time()
|
||
|
||
#
|
||
g.last_mouse_co_2d = mathutils.Vector(
|
||
(event.mouse_region_x, event.mouse_region_y)
|
||
)
|
||
return self.process_ui_block(context, event)
|
||
|
||
except Exception as e:
|
||
functions.safe_execute(self.clear, context)
|
||
functions.log(f"An expection raise when modal, exit modal. Error: {str(e)}")
|
||
self.report(
|
||
{"WARNING"},
|
||
f"An expection raise when modal, exit modal. Error: {str(e)}",
|
||
)
|
||
EasyPatchModalOperator.set_stop_flag()
|
||
raise e
|
||
|
||
return {"PASS_THROUGH"}
|
||
# return {"RUNNING_MODAL"}
|
||
|
||
def invoke(self, context, event):
|
||
if not g.is_running:
|
||
self.start(context)
|
||
return {"RUNNING_MODAL"}
|
||
else:
|
||
EasyPatchModalOperator.stop()
|
||
return {"FINISHED"}
|
||
|
||
def start(self, context):
|
||
functions.log("modal start")
|
||
|
||
g.clear_all_variables()
|
||
|
||
draw.start(self)
|
||
g.is_running = True
|
||
self._timer = context.window_manager.event_timer_add(0.1, window=context.window)
|
||
context.window_manager.modal_handler_add(self)
|
||
|
||
self.context = context
|
||
self.region = context.region
|
||
self.rv3d = context.region_data
|
||
|
||
obj = functions.get_edit_obj()
|
||
g.obj = obj
|
||
g.obj_me = obj.data
|
||
g.obj_bm = bmesh.from_edit_mesh(obj.data)
|
||
|
||
g.mode = g.Mode.NORMAL
|
||
g.drawing_verts_snap_vertex = None
|
||
g.drawing_path_boundary = None
|
||
|
||
g.snap_bvh_trees = functions.get_snap_bvh_trees()
|
||
g.obj_bvh_tree = functions.get_obj_bvh_tree()
|
||
|
||
def stop():
|
||
functions.log("modal stop")
|
||
EasyPatchModalOperator.set_stop_flag()
|
||
|
||
def clear(self, context, remove_mesh_flag=True):
|
||
functions.log("modal clear")
|
||
|
||
# 用户tab离开编辑模式,会导致bm被删除,所以会报很多错
|
||
|
||
if remove_mesh_flag:
|
||
functions.safe_execute(self.remove_mesh)
|
||
|
||
functions.safe_execute(context.window_manager.event_timer_remove, self._timer)
|
||
functions.safe_execute(EasyPatchModalOperator.set_stop_flag)
|
||
functions.safe_execute(draw.clear)
|
||
|
||
if context.object.mode == "EDIT":
|
||
bmesh.update_edit_mesh(g.obj_me)
|
||
|
||
g.clear_all_variables()
|
||
|
||
def set_stop_flag():
|
||
g.is_running = False
|
||
|
||
# 删除插件生成的网格
|
||
def remove_mesh(self):
|
||
if g.temp_bm_edges:
|
||
functions.remove_bm_edges(g.obj_bm, g.temp_bm_edges)
|
||
g.temp_bm_edges = set()
|
||
|
||
if g.temp_bm_verts:
|
||
functions.delete_bm_verts(g.obj_bm, g.temp_bm_verts)
|
||
g.temp_bm_verts = set()
|
||
|
||
for loop in g.loops:
|
||
if loop.pattern:
|
||
loop.pattern.remove_mesh()
|
||
|
||
try:
|
||
bmesh.update_edit_mesh(g.obj_me)
|
||
except Exception as e:
|
||
pass
|
||
|
||
def process_stop(self, context, event):
|
||
if (
|
||
not g.is_running
|
||
or event.type == "ESC"
|
||
or context.object.mode != "EDIT"
|
||
or g.obj_bm is None
|
||
):
|
||
self.clear(context)
|
||
return {"FINISHED"}
|
||
|
||
if event.type in ["RET", "NUMPAD_ENTER"]:
|
||
count_vertex = functions.get_created_verts_num()
|
||
|
||
self.clear(context, remove_mesh_flag=False)
|
||
self.report({"INFO"}, f"{count_vertex} {t('verts')} {t('created')}")
|
||
return {"FINISHED"}
|
||
|
||
if g.running_exception:
|
||
raise g.running_exception
|
||
|
||
def process_drawing_verts(self, context, event):
|
||
# TODO: update snap和hovering和append_from_2d加一个移动阈值才更新,记录上一个鼠标位置,超过阈值才进行,modal末尾更新,共用一个值
|
||
# TODO: loop加上一个屏蔽pattern生成的状态
|
||
functions.update_snap_vertex(event, is_snap_opposite=g.is_snap_opposite)
|
||
|
||
if g.mode == g.Mode.DRAWING:
|
||
# # update and clear snap vertex when ctrl in running
|
||
functions.update_solid_path(context, event)
|
||
|
||
# append路径
|
||
g.drawing_path_boundary.append_path_from_2d(
|
||
context,
|
||
(event.mouse_region_x, event.mouse_region_y),
|
||
is_snap_surface=g.is_snap_surface,
|
||
is_snap_opposite=g.is_snap_opposite,
|
||
)
|
||
|
||
# drawing中改变点数
|
||
if event.type == "WHEELUPMOUSE":
|
||
g.drawing_path_boundary.length += 1
|
||
return {"RUNNING_MODAL"}
|
||
if event.type == "WHEELDOWNMOUSE":
|
||
g.drawing_path_boundary.length -= 1
|
||
return {"RUNNING_MODAL"}
|
||
|
||
# stop drag
|
||
if event.type == "LEFTMOUSE" and event.value == "RELEASE":
|
||
# # 第六条强制闭合
|
||
# if len(self.patch.boundaries) == 5:
|
||
# g.drawing_verts_snap_vertex = self.patch.start_vertex
|
||
|
||
snap_vert = (
|
||
g.drawing_verts_snap_vertex
|
||
) # 确保一致性,先获取,防止中间修改
|
||
functions.log("stop drag vertex:" + str(snap_vert))
|
||
|
||
done_boundary = g.drawing_path_boundary.done_from_temp(snap_vert)
|
||
|
||
g.drawing_verts_snap_vertex = None
|
||
g.mode = g.Mode.NORMAL
|
||
|
||
# clear drag things
|
||
g.drawing_path_boundary = None
|
||
|
||
if not done_boundary:
|
||
return
|
||
|
||
if done_boundary.path[-1] != done_boundary.end_vertex.co:
|
||
# 不知道为什么,end_vertex是吸附的时候需要手动补
|
||
done_boundary.path.append(
|
||
g.obj.matrix_world @ done_boundary.end_vertex.co
|
||
)
|
||
|
||
generate_loop.regenerate_loops()
|
||
|
||
if not g.is_drawing_along_mirror_persist:
|
||
g.is_drawing_along_mirror = False # 每次画完都重置这个变量
|
||
|
||
# 删除temp boundary
|
||
if event.type == "RIGHTMOUSE" and event.value == "PRESS":
|
||
g.drawing_path_boundary = None
|
||
g.mode = g.Mode.NORMAL
|
||
|
||
return {"RUNNING_MODAL"}
|
||
|
||
# if event.value != "NOTHING":
|
||
# print(event.type, event.value)
|
||
|
||
# TODO: 以CLICK_DRAG,用户可能会以左键单击误触发,造成多余的线条,多一个ctrl+enter,以保留多余线条
|
||
# start drag
|
||
if event.type == "LEFTMOUSE" and event.value == "CLICK_DRAG":
|
||
functions.log("start drag vertex:" + str(g.drawing_verts_snap_vertex))
|
||
g.mode = g.Mode.DRAWING
|
||
|
||
# create temp boundary
|
||
if g.drawing_verts_snap_vertex:
|
||
g.drawing_path_boundary = boundary.Boundary.create_temp(
|
||
g.drawing_verts_snap_vertex
|
||
)
|
||
else:
|
||
g.drawing_path_boundary = boundary.Boundary.create_temp(None)
|
||
|
||
return {
|
||
"RUNNING_MODAL"
|
||
} # block drag ui operation, if not running_modal, release will not trigger
|
||
|
||
def process_undo(self, context, event):
|
||
if event.type == "Z" and event.value == "PRESS" and event.ctrl:
|
||
|
||
self.report({"WARNING"}, t("you_cant_undo_here"))
|
||
return {"RUNNING_MODAL"}
|
||
|
||
def process_ui_block(self, context, event):
|
||
# I键内插,会导致未知问题
|
||
if event.type in ["X", "I"]:
|
||
return {"RUNNING_MODAL"}
|
||
|
||
return {"PASS_THROUGH"}
|
||
|
||
def process_loop(self, context, event):
|
||
if not g.hovering_loop:
|
||
return
|
||
|
||
loop = g.hovering_loop
|
||
|
||
# R
|
||
if event.type == "R" and event.value == "PRESS":
|
||
loop.change_solver_constraint_rotation()
|
||
return {"RUNNING_MODAL"}
|
||
|
||
# T
|
||
if event.type == "T" and event.value == "PRESS":
|
||
loop.next_solver_constraint_pattern(is_auto_search=g.is_auto_search_pattern)
|
||
return {"RUNNING_MODAL"}
|
||
|
||
# invert normal
|
||
if event.type == "I" and event.value == "PRESS" and loop.pattern:
|
||
functions.faces_normal_toward_viewport(g.obj, g.obj_bm, loop.pattern.faces)
|
||
return {"RUNNING_MODAL"}
|
||
|
||
# 改变padding约束
|
||
if (
|
||
event.shift
|
||
and (event.type == "WHEELUPMOUSE" or event.type == "WHEELDOWNMOUSE")
|
||
and g.hovering_boundary
|
||
):
|
||
boundary_index = loop.get_boundary_index(g.hovering_boundary)
|
||
if event.type == "WHEELUPMOUSE":
|
||
loop.change_solver_constraint_list(boundary_index, 1)
|
||
if event.type == "WHEELDOWNMOUSE":
|
||
loop.change_solver_constraint_list(boundary_index, -1)
|
||
|
||
# X附加边流
|
||
if event.type == "X" and event.value == "PRESS" and event.shift:
|
||
loop.change_solver_constraint_list("x", 1)
|
||
return {"RUNNING_MODAL"}
|
||
if event.type == "X" and event.value == "PRESS" and event.ctrl:
|
||
loop.change_solver_constraint_list("x", -1)
|
||
return {"RUNNING_MODAL"}
|
||
|
||
# Y附加边流
|
||
if event.type == "Y" and event.value == "PRESS" and event.shift:
|
||
loop.change_solver_constraint_list("y", 1)
|
||
return {"RUNNING_MODAL"}
|
||
if event.type == "Y" and event.value == "PRESS" and event.ctrl:
|
||
loop.change_solver_constraint_list("y", -1)
|
||
return {"RUNNING_MODAL"}
|
||
|
||
# C 清除约束
|
||
if event.type == "C" and event.value == "PRESS":
|
||
if event.shift:
|
||
loop.init_solver_constraint(
|
||
is_init_list=True, is_init_rotation=False, is_init_pattern=False
|
||
)
|
||
elif event.ctrl:
|
||
loop.init_solver_constraint(
|
||
is_init_list=False, is_init_rotation=True, is_init_pattern=False
|
||
)
|
||
else:
|
||
loop.init_solver_constraint()
|
||
return {"RUNNING_MODAL"}
|
||
|
||
def process_running(self, context, event):
|
||
if g.mode != g.Mode.NORMAL:
|
||
return
|
||
# 边缘建成后,改变长度
|
||
if event.ctrl and (
|
||
event.type == "WHEELUPMOUSE" or event.type == "WHEELDOWNMOUSE"
|
||
):
|
||
# boundary = functions.get_nearest_boundary_from_mouse(event)
|
||
boundary = g.hovering_boundary
|
||
if boundary:
|
||
if event.type == "WHEELUPMOUSE":
|
||
boundary.length += 1
|
||
if event.type == "WHEELDOWNMOUSE":
|
||
boundary.length -= 1
|
||
return {"RUNNING_MODAL"}
|
||
|
||
# S
|
||
if event.type == "S" and event.value == "PRESS":
|
||
g.is_snap_surface = not g.is_snap_surface # 切换吸附状态
|
||
|
||
if g.hovering_loop and g.hovering_loop.pattern:
|
||
if g.is_snap_surface:
|
||
# 吸附曲面
|
||
functions.snap_verts_to_surface_along_normal(
|
||
g.obj,
|
||
g.obj_bm,
|
||
g.hovering_loop.pattern.interior_verts,
|
||
g.snap_bvh_trees,
|
||
allow_opposite_direction=g.is_snap_opposite,
|
||
)
|
||
else:
|
||
functions.smooth_verts_by_normal(
|
||
g.obj, g.obj_bm, g.hovering_loop.pattern.interior_verts
|
||
)
|
||
|
||
return {"RUNNING_MODAL"}
|
||
|
||
if event.type == "M" and event.value == "PRESS" and not event.ctrl:
|
||
g.is_drawing_along_mirror = (
|
||
not g.is_drawing_along_mirror
|
||
) # 切换镜像绘制状态
|
||
|
||
if not g.is_drawing_along_mirror:
|
||
g.is_drawing_along_mirror_persist = False
|
||
return {"RUNNING_MODAL"}
|
||
|
||
# 删除boundary
|
||
if (
|
||
event.type == "X"
|
||
and event.value == "PRESS"
|
||
and not event.shift
|
||
and not event.ctrl
|
||
and g.hovering_boundary
|
||
):
|
||
g.hovering_boundary.delete()
|
||
generate_loop.regenerate_loops()
|
||
return {"RUNNING_MODAL"}
|
||
|
||
if event.type == "M" and event.value == "PRESS" and event.ctrl:
|
||
g.is_drawing_along_mirror_persist = (
|
||
not g.is_drawing_along_mirror_persist
|
||
) # 切换镜像绘制状态
|
||
|
||
g.is_drawing_along_mirror = g.is_drawing_along_mirror_persist
|
||
return {"RUNNING_MODAL"}
|
||
|
||
# smooth
|
||
if event.shift:
|
||
tobe_smooth_loops = []
|
||
if event.ctrl:
|
||
tobe_smooth_loops = g.loops
|
||
else:
|
||
if g.hovering_loop:
|
||
tobe_smooth_loops = [g.hovering_loop]
|
||
|
||
for tobe_smooth_loop in tobe_smooth_loops:
|
||
if (
|
||
not tobe_smooth_loop.pattern
|
||
or not tobe_smooth_loop.pattern.interior_verts
|
||
):
|
||
continue
|
||
to_be_smooth_verts = tobe_smooth_loop.pattern.interior_verts
|
||
|
||
if event.type in ["ONE", "NUMPAD_1"]:
|
||
functions.smooth_verts_by_avg(g.obj, g.obj_bm, to_be_smooth_verts)
|
||
return {"RUNNING_MODAL"}
|
||
elif event.type in ["TWO", "NUMPAD_2"]:
|
||
functions.smooth_verts_by_normal(
|
||
g.obj,
|
||
g.obj_bm,
|
||
to_be_smooth_verts,
|
||
smooth_type=0,
|
||
)
|
||
return {"RUNNING_MODAL"}
|
||
elif event.type in ["THREE", "NUMPAD_3"]:
|
||
functions.smooth_verts_by_normal(
|
||
g.obj,
|
||
g.obj_bm,
|
||
to_be_smooth_verts,
|
||
smooth_type=1,
|
||
)
|
||
return {"RUNNING_MODAL"}
|
||
# self.is_snap_surface = False
|
||
|
||
# v snap oppose,目前只针对path绘制的时候应用
|
||
if event.type == "V" and event.value == "PRESS" and g.is_running:
|
||
if g.is_snap_opposite:
|
||
g.is_snap_opposite = False
|
||
else:
|
||
g.is_snap_opposite = True
|
||
return {"RUNNING_MODAL"}
|
||
|
||
if event.type == "B" and event.value == "PRESS":
|
||
g.is_auto_search_pattern = not g.is_auto_search_pattern
|
||
return {"RUNNING_MODAL"}
|
||
|
||
|
||
class EasyPatchSnapSelectedToSurfaceOperator(bpy.types.Operator):
|
||
bl_idname = f"{const.addon_prefix}.snap_selected_to_surface"
|
||
bl_label = "Snap Selected Vertices to Surface"
|
||
# bl_description = t("toggle_driver")
|
||
bl_options = {"REGISTER", "UNDO"} # 不加UNDO撤回的话,会导致用户前一个步骤也撤回
|
||
|
||
def execute(self, context):
|
||
obj = context.object
|
||
|
||
# 检查是否在编辑模式
|
||
if obj.mode != "EDIT":
|
||
self.report({"WARNING"}, "Please run this in edit mode")
|
||
return {"FINISHED"}
|
||
|
||
# 获取依赖图
|
||
depsgraph = context.evaluated_depsgraph_get()
|
||
|
||
# 获取网格数据
|
||
bm = bmesh.from_edit_mesh(obj.data)
|
||
bm.verts.ensure_lookup_table()
|
||
|
||
# 获取所有选中的顶点
|
||
selected_vertices = [v for v in bm.verts if v.select]
|
||
if not selected_vertices:
|
||
self.report({"WARNING"}, "No Vertices selected")
|
||
return {"FINISHED"}
|
||
|
||
# 获取所有可见的网格对象(排除当前对象)
|
||
visible_objects = [
|
||
o
|
||
for o in context.visible_objects
|
||
if o.type == "MESH" and o != obj and o.visible_get()
|
||
]
|
||
|
||
if not visible_objects:
|
||
self.report({"WARNING"}, "no_visible_target_mesh_to_snap")
|
||
return {"FINISHED"}
|
||
|
||
# 构建所有可见目标对象的 BVH 树(一次性操作)
|
||
bvh_trees = functions.get_snap_bvh_trees()
|
||
|
||
functions.snap_verts_to_surface_along_normal(
|
||
obj,
|
||
bm,
|
||
selected_vertices,
|
||
bvh_trees,
|
||
allow_opposite_direction=False,
|
||
)
|
||
|
||
# 将顶点更改应用到网格上,loop_triangles提高效率
|
||
bmesh.update_edit_mesh(obj.data, loop_triangles=True)
|
||
|
||
# self.report({"INFO"}, f"Successfully snap {snap_count} vertices")
|
||
return {"FINISHED"}
|
||
|
||
|
||
def get_nearest_boundary_and_loop_by_co_2d(context, co_2d):
|
||
nearest_boundary = None
|
||
nearest_loop = None
|
||
min_distance = float("inf")
|
||
|
||
# 获取当前视图区域和区域的3D视图
|
||
region = context.region
|
||
rv3d = context.space_data.region_3d
|
||
|
||
# # mid_co方案
|
||
# # 遍历boundary列表
|
||
# for boundary in g.boundaries:
|
||
# # 获取boundary的mid_co三维坐标
|
||
# mid_co = boundary.mid_co
|
||
|
||
# # 将三维坐标投影到2D屏幕坐标
|
||
# screen_coord = view3d_utils.location_3d_to_region_2d(region, rv3d, mid_co)
|
||
|
||
# # 如果投影成功
|
||
# if screen_coord:
|
||
# # 计算鼠标位置与投影点的距离
|
||
# dist = (screen_coord[0] - co_2d[0]) ** 2 + (screen_coord[1] - co_2d[1]) ** 2
|
||
# if dist < min_distance:
|
||
# min_distance = dist
|
||
# nearest_boundary = boundary
|
||
|
||
# TODO: 只有一个edge的用mid_co,不然重叠了捕获不到
|
||
# 遍历boundary列表
|
||
for boundary in g.boundaries:
|
||
coords = [v.co for v in boundary.verts] + [boundary.mid_co]
|
||
# 遍历boundary的每一个顶点
|
||
for vert_co in coords:
|
||
# 将三维坐标投影到2D屏幕坐标
|
||
screen_coord = view3d_utils.location_3d_to_region_2d(region, rv3d, vert_co)
|
||
|
||
# 如果投影成功
|
||
if screen_coord:
|
||
# 计算鼠标位置与投影点的距离
|
||
dist = (screen_coord[0] - co_2d[0]) ** 2 + (
|
||
screen_coord[1] - co_2d[1]
|
||
) ** 2
|
||
if dist < min_distance:
|
||
min_distance = dist
|
||
nearest_boundary = boundary
|
||
|
||
if nearest_boundary:
|
||
# 获取与nearest_boundary相关的loop
|
||
connected_loops = functions.get_loops_from_boundary(nearest_boundary)
|
||
# print(nearest_boundary, connected_loops)
|
||
|
||
if len(connected_loops) == 0:
|
||
nearest_loop = None
|
||
elif len(connected_loops) == 1:
|
||
nearest_loop = connected_loops[0]
|
||
else:
|
||
# 找到距离鼠标位置最近的loop
|
||
min_loop_distance = float("inf")
|
||
|
||
for loop in connected_loops:
|
||
# 获取loop的mid_co三维坐标
|
||
loop_mid_co = loop.mid_co
|
||
|
||
# 将loop的三维坐标投影到2D屏幕坐标
|
||
loop_screen_coord = view3d_utils.location_3d_to_region_2d(
|
||
region, rv3d, loop_mid_co
|
||
)
|
||
|
||
# 如果loop投影成功
|
||
if loop_screen_coord:
|
||
# 计算鼠标位置与loop的投影点的距离
|
||
loop_dist = (loop_screen_coord[0] - co_2d[0]) ** 2 + (
|
||
loop_screen_coord[1] - co_2d[1]
|
||
) ** 2
|
||
if loop_dist < min_loop_distance:
|
||
min_loop_distance = loop_dist
|
||
nearest_loop = loop
|
||
|
||
return nearest_boundary, nearest_loop
|
||
|
||
|
||
def get_nearest_boundary_and_loop_by_co_3d(co_3d):
|
||
nearest_boundary = None
|
||
nearest_loop = None
|
||
min_distance = float("inf")
|
||
|
||
# # mid_co方案
|
||
# # 遍历boundary列表
|
||
# for boundary in g.boundaries:
|
||
# mid_co = boundary.mid_co
|
||
|
||
# # 计算给定的3D坐标与boundary的mid_co之间的欧几里得距离
|
||
# distance = (mid_co - co_3d).length
|
||
|
||
# # 判断是否是最近的boundary
|
||
# if distance < min_distance:
|
||
# min_distance = distance
|
||
# nearest_boundary = boundary
|
||
|
||
# verts方案
|
||
for boundary in g.boundaries:
|
||
coords = [v.co for v in boundary.verts] + [boundary.mid_co]
|
||
# 遍历boundary的每一个顶点
|
||
for vert_co in coords:
|
||
|
||
# 计算给定的3D坐标与顶点坐标之间的欧几里得距离
|
||
distance = (vert_co - co_3d).length
|
||
|
||
# 判断是否是最近的boundary
|
||
if distance < min_distance:
|
||
min_distance = distance
|
||
nearest_boundary = boundary
|
||
|
||
if nearest_boundary:
|
||
connected_loops = functions.get_loops_from_boundary(nearest_boundary)
|
||
if len(connected_loops) == 0:
|
||
nearest_loop = None
|
||
elif len(connected_loops) == 1:
|
||
nearest_loop = connected_loops[0]
|
||
else:
|
||
# 多个loop的情况,计算每个loop的mid_co和目标co_3d之间的3D距离
|
||
min_loop_distance = float("inf")
|
||
|
||
for loop in connected_loops:
|
||
loop_mid_co = loop.mid_co
|
||
|
||
# 计算给定的3D坐标与loop的mid_co之间的欧几里得距离
|
||
loop_distance = (loop_mid_co - co_3d).length
|
||
|
||
# 判断是否是最近的loop
|
||
if loop_distance < min_loop_distance:
|
||
min_loop_distance = loop_distance
|
||
nearest_loop = loop
|
||
|
||
return nearest_boundary, nearest_loop
|
||
|
||
|
||
def update_hover(context, event):
|
||
if g.is_changing_mesh:
|
||
g.hovering_boundary = None
|
||
g.hovering_loop = None
|
||
|
||
g.is_redraw_hovering = True
|
||
return
|
||
|
||
current_timestamp = time.time()
|
||
if current_timestamp - g.last_update_hovering_timestamp < (1 / 10):
|
||
return
|
||
|
||
# 两个方案,1.boundary的mid_co投影到2d,获取最近的,然后loop挑出b相邻的,再根据mid_co取2d最近的 2.ray_cast,没结果则用方案一,有结果,取距离该点最近的boundary的mid_co,然后再相邻取loop
|
||
|
||
region = context.region
|
||
rv3d = context.region_data
|
||
mouse_co_2d = (event.mouse_region_x, event.mouse_region_y)
|
||
ray_origin = view3d_utils.region_2d_to_origin_3d(region, rv3d, mouse_co_2d)
|
||
ray_direction = view3d_utils.region_2d_to_vector_3d(region, rv3d, mouse_co_2d)
|
||
|
||
closest_co = None
|
||
distance_min = float("inf")
|
||
for bvh_tree in g.snap_bvh_trees:
|
||
ray_cast_co, _, _, distance = bvh_tree.ray_cast(ray_origin, ray_direction)
|
||
if distance and distance < distance_min:
|
||
distance_min = distance
|
||
closest_co = ray_cast_co
|
||
|
||
hovering_boundary = None
|
||
hovering_loop = None
|
||
if closest_co:
|
||
hovering_boundary, hovering_loop = get_nearest_boundary_and_loop_by_co_3d(
|
||
closest_co
|
||
)
|
||
|
||
else:
|
||
# boundary
|
||
hovering_boundary, hovering_loop = get_nearest_boundary_and_loop_by_co_2d(
|
||
context, mouse_co_2d
|
||
)
|
||
|
||
g.hovering_boundary = hovering_boundary
|
||
g.hovering_loop = hovering_loop
|
||
|
||
g.last_update_hovering_timestamp = current_timestamp
|
||
|
||
g.is_redraw_hovering = True
|
||
|
||
|
||
classes = (EasyPatchModalOperator, EasyPatchSnapSelectedToSurfaceOperator)
|