feat: 初始化Easy Patch插件及依赖文件
- 添加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配置文件
This commit is contained in:
535
boundary.py
Normal file
535
boundary.py
Normal file
@@ -0,0 +1,535 @@
|
||||
from __future__ import annotations
|
||||
import mathutils
|
||||
import bmesh
|
||||
from bpy_extras import view3d_utils
|
||||
import bpy
|
||||
import numpy as np
|
||||
|
||||
from . import const
|
||||
from .utils import functions
|
||||
from . import g
|
||||
|
||||
|
||||
class Boundary:
|
||||
def __init__(self, start_vertex, end_vertex, path):
|
||||
self.index = -1
|
||||
self.verts = []
|
||||
self.path = path
|
||||
self.start_vertex = start_vertex
|
||||
self.end_vertex = end_vertex
|
||||
|
||||
self.temp_length = 3 # for uncreated boundary
|
||||
|
||||
self.mid_co = mathutils.Vector((0, 0, 0))
|
||||
self.solid_path = []
|
||||
self.is_editable = True
|
||||
self.is_hover = False
|
||||
|
||||
self.is_solid_start = (
|
||||
not not start_vertex
|
||||
and start_vertex not in self.get_boundaries_verts_except_self()
|
||||
)
|
||||
self.is_solid_end = None
|
||||
|
||||
@classmethod
|
||||
def create_temp(cls, start_vertex):
|
||||
instance = cls(
|
||||
start_vertex,
|
||||
None,
|
||||
[],
|
||||
)
|
||||
return instance
|
||||
|
||||
@property
|
||||
def length(self):
|
||||
if self.edges:
|
||||
return len(self.edges)
|
||||
else:
|
||||
return self.temp_length
|
||||
|
||||
@length.setter
|
||||
def length(self, length):
|
||||
# 固化边
|
||||
if not self.is_editable:
|
||||
return
|
||||
if length < 1:
|
||||
return
|
||||
|
||||
# TODO: 下面两个暂不用考虑,两个boundary为同一个loop并且boundary都为1了,则冲突了
|
||||
|
||||
# 建成和非建成改的长度不一样
|
||||
if self.edges:
|
||||
# TODO:特殊情况,两个boundary且都为1的时候冲突了
|
||||
# if (
|
||||
# length == 1
|
||||
# and len(self.op_instance.patch.boundaries) == 2
|
||||
# and any(
|
||||
# [
|
||||
# boundary.length == 1
|
||||
# for boundary in self.op_instance.patch.boundaries
|
||||
# ]
|
||||
# )
|
||||
# ):
|
||||
# return
|
||||
|
||||
# 特殊情况,已建成的边,如果头尾都吸附到了某个edge的两个顶点上(并且不是吸附路径),如果这个时候改变分段为1,则会报错创建已存在的边
|
||||
if length == 1:
|
||||
start_vertex_related_edges = self.start_vertex.link_edges
|
||||
for start_vertex_related_edge in start_vertex_related_edges:
|
||||
if self.end_vertex in start_vertex_related_edge.verts:
|
||||
return
|
||||
|
||||
self.change_length(length)
|
||||
else:
|
||||
# # TODO:特殊情况,两个边且都为1的时候
|
||||
# if (
|
||||
# length == 1
|
||||
# and len(self.op_instance.patch.boundaries) == 1
|
||||
# and self.op_instance.patch.boundaries[0].length == 1
|
||||
# ):
|
||||
# return
|
||||
|
||||
# 特殊情况,未建成的边,如果头尾都吸附到了某个edge的两个顶点上(并且不是吸附路径),如果这个时候改变分段为1,则会报错创建已存在的边,强制约束不能小于1
|
||||
if length <= 1:
|
||||
return
|
||||
|
||||
self.temp_length = length
|
||||
|
||||
def get_actual_verts_co_and_path(self):
|
||||
# 第一个是考虑了temp_path,第二个是真实的path
|
||||
if self.solid_path:
|
||||
path = self.solid_path
|
||||
vertex_co_along_path = path
|
||||
else:
|
||||
path = self.path
|
||||
vertex_co_along_path, _ = self.vertices_co_along_path()
|
||||
return vertex_co_along_path, path
|
||||
|
||||
def change_length(self, length):
|
||||
self.remove_mesh()
|
||||
self.create_mesh(length)
|
||||
|
||||
# self.op_instance.patch.update_after_boundary_change()
|
||||
functions.on_change_boundary_length(self)
|
||||
|
||||
def append_path_from_2d(
|
||||
self,
|
||||
context,
|
||||
co_2d,
|
||||
is_snap_surface=True,
|
||||
is_snap_opposite=False,
|
||||
):
|
||||
# 獲取base_co
|
||||
if len(self.path) != 0:
|
||||
base_co = self.path[-1]
|
||||
else:
|
||||
if self.start_vertex:
|
||||
# vertex.co获取的是局部坐标
|
||||
start_vertex = g.obj.matrix_world @ self.start_vertex.co
|
||||
base_co = start_vertex
|
||||
self.path.append(start_vertex)
|
||||
else:
|
||||
base_co = (0, 0, 0)
|
||||
|
||||
region = context.region
|
||||
rv3d = context.region_data
|
||||
|
||||
# 阈值
|
||||
if self.path:
|
||||
path_last_co_2d = view3d_utils.location_3d_to_region_2d(
|
||||
region, rv3d, self.path[-1]
|
||||
) # 视图外会返回None
|
||||
if not path_last_co_2d:
|
||||
return
|
||||
if (
|
||||
path_last_co_2d - mathutils.Vector(co_2d)
|
||||
).length < const.draw_path_threshold:
|
||||
# print("太小不记录")
|
||||
return
|
||||
|
||||
co_3d = []
|
||||
last_co = mathutils.Vector(base_co)
|
||||
|
||||
# Get the ray origin and direction
|
||||
ray_origin = view3d_utils.region_2d_to_origin_3d(region, rv3d, co_2d)
|
||||
ray_direction = view3d_utils.region_2d_to_vector_3d(region, rv3d, co_2d)
|
||||
|
||||
hit = None
|
||||
if is_snap_surface:
|
||||
snap_bvh_trees = g.snap_bvh_trees
|
||||
for snap_bvh_tree in snap_bvh_trees:
|
||||
location, normal, index, distance = snap_bvh_tree.ray_cast(
|
||||
ray_origin, ray_direction
|
||||
)
|
||||
if location:
|
||||
hit = True
|
||||
break
|
||||
|
||||
if hit:
|
||||
if (
|
||||
is_snap_opposite or normal.dot(ray_direction.normalized()) < 0
|
||||
): # 点乘大于0,反向
|
||||
co_3d = location
|
||||
|
||||
if not co_3d:
|
||||
# Project base point
|
||||
plane_center = last_co
|
||||
plane_normal = ray_direction
|
||||
|
||||
intersection_point = mathutils.geometry.intersect_line_plane(
|
||||
ray_origin, ray_origin + ray_direction, plane_center, plane_normal
|
||||
)
|
||||
if intersection_point:
|
||||
co_3d = intersection_point
|
||||
|
||||
self.path.append(co_3d)
|
||||
return
|
||||
|
||||
def create_mesh(self, length):
|
||||
if self.solid_path:
|
||||
self.is_editable = False
|
||||
# self.edges 和self.verts 已在update_solid中定义,并且不用创建实体
|
||||
|
||||
else:
|
||||
vertices_co_along_path_3d, self.verts_correspond_path_idxs = (
|
||||
self.vertices_co_along_path(length)
|
||||
)
|
||||
|
||||
# create vert
|
||||
vertices_along_path = []
|
||||
for i in range(len(vertices_co_along_path_3d)):
|
||||
coord = vertices_co_along_path_3d[i]
|
||||
if i == 0:
|
||||
# 开头
|
||||
if self.start_vertex:
|
||||
vertices_along_path.append(self.start_vertex)
|
||||
else:
|
||||
vertex = functions.create_temp_vertex(coord)
|
||||
vertices_along_path.append(vertex)
|
||||
self.start_vertex = vertex
|
||||
|
||||
elif i == len(vertices_co_along_path_3d) - 1:
|
||||
# 中间
|
||||
if self.end_vertex:
|
||||
vertices_along_path.append(self.end_vertex)
|
||||
else:
|
||||
vertex = functions.create_temp_vertex(coord)
|
||||
vertices_along_path.append(vertex)
|
||||
self.end_vertex = vertex
|
||||
else:
|
||||
# 结尾
|
||||
vertex = functions.create_temp_vertex(coord)
|
||||
vertices_along_path.append(vertex)
|
||||
|
||||
# create edge
|
||||
boundary_edges = []
|
||||
for i in range(len(vertices_along_path) - 1):
|
||||
edge = functions.create_temp_edge(
|
||||
vertices_along_path[i], vertices_along_path[i + 1]
|
||||
)
|
||||
boundary_edges.append(edge)
|
||||
bmesh.update_edit_mesh(g.obj_me)
|
||||
|
||||
self.edges = boundary_edges # ordered edges from start vert
|
||||
self.verts = vertices_along_path
|
||||
|
||||
self.update_mid_co()
|
||||
|
||||
@functions.set_block_before_done
|
||||
def done_from_temp(self, end_vertex):
|
||||
if (
|
||||
len(self.path) < 3
|
||||
): # path代表鼠标路径,即使吸附上了solid边,这个也是鼠标路径
|
||||
functions.log("too short, cancel")
|
||||
return
|
||||
|
||||
self.end_vertex = end_vertex
|
||||
self.is_solid_end = (
|
||||
not not end_vertex
|
||||
and end_vertex not in self.get_boundaries_verts_except_self()
|
||||
)
|
||||
|
||||
if (
|
||||
self.start_vertex
|
||||
and self.end_vertex
|
||||
and self.start_vertex == self.end_vertex
|
||||
):
|
||||
functions.log("start and end same, cancel")
|
||||
return
|
||||
|
||||
# 开始处理切分↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
|
||||
# 处理切分
|
||||
# boundary被切分,被切分的boundary的相关的loop要先删pattern及其网格,每切分一个boundary,boundary会被删除1条,新建3条,然后重建loop,然后相关的loop(即新建的boundary,被切分成2个的boundary)要重建网格
|
||||
|
||||
# 截断了另外的boundary,需要把那些boundary拆分并重建,两个可能:1.开始或结束点在boundary上 2.开启了自动截断模式(笔记,自动截断的情况,获取截断点的坐标,分割path,注意要先创建截断点vert(这里单独写个函数),把他赋予到对应boundary的start和end,再执行create_mesh)
|
||||
mid_verts_to_boundary = (
|
||||
{}
|
||||
) # 建立中间点(即除了开始和结束)到boundary的映射。考虑不是每次都建立,不然滚轮的时候会频繁创建(开销很小,不考虑)
|
||||
for b in g.boundaries:
|
||||
if b == self: # 排除自身
|
||||
continue
|
||||
for v in b.verts:
|
||||
if v == b.start_vertex or v == b.end_vertex:
|
||||
continue
|
||||
|
||||
# # 理论上来说中间点和boundary是一一对应的,不可能有重复的情况
|
||||
# if v in mid_verts_to_boundary:
|
||||
# raise "mid vert duplicated"
|
||||
|
||||
mid_verts_to_boundary[v] = b
|
||||
|
||||
# TODO: 暂时无法处理两个切分点都在同一个boundary的中间点上,返回None
|
||||
# TODO: 暂时无法处理两个boundary的情况
|
||||
# 下面这个约束同时过滤了上面这两个,实际上这两个issue要同时解决才能应用
|
||||
for b in g.boundaries:
|
||||
if b == self: # 跳过自身(没必要,因为这个都没创建)
|
||||
continue
|
||||
if self.start_vertex in b.verts and self.end_vertex in b.verts:
|
||||
functions.log("2 boundary case, cancel")
|
||||
return
|
||||
|
||||
# 被切割的boundary要先删pattern的mesh,等切割完了,再全部recreate
|
||||
to_be_seperate_boundary_by_end = None
|
||||
to_be_seperate_boundary_by_end_related_loops = []
|
||||
if self.end_vertex in mid_verts_to_boundary:
|
||||
to_be_seperate_boundary_by_end = mid_verts_to_boundary[self.end_vertex]
|
||||
for loop in functions.get_loops_from_boundary(
|
||||
to_be_seperate_boundary_by_end
|
||||
):
|
||||
# 删pattern
|
||||
loop.remove_pattern()
|
||||
to_be_seperate_boundary_by_end_related_loops.append(loop)
|
||||
|
||||
to_be_seperate_boundary_by_start = None
|
||||
to_be_seperate_boundary_by_start_related_loops = []
|
||||
if self.start_vertex in mid_verts_to_boundary:
|
||||
to_be_seperate_boundary_by_start = mid_verts_to_boundary[self.start_vertex]
|
||||
for loop in functions.get_loops_from_boundary(
|
||||
to_be_seperate_boundary_by_start
|
||||
):
|
||||
# 删pattern
|
||||
loop.remove_pattern()
|
||||
to_be_seperate_boundary_by_start_related_loops.append(loop)
|
||||
|
||||
# 从全局变量删loop
|
||||
to_be_seperate_boundary_related_loops = set(
|
||||
(
|
||||
to_be_seperate_boundary_by_end_related_loops
|
||||
+ to_be_seperate_boundary_by_start_related_loops
|
||||
)
|
||||
) # 取并集,因为有可能会有同一个loop的情况
|
||||
for (
|
||||
to_be_seperate_boundary_related_loop
|
||||
) in to_be_seperate_boundary_related_loops:
|
||||
g.loops.remove(to_be_seperate_boundary_related_loop)
|
||||
|
||||
# 从全局变量删boundary
|
||||
if to_be_seperate_boundary_by_end:
|
||||
g.boundaries.remove(to_be_seperate_boundary_by_end)
|
||||
if to_be_seperate_boundary_by_start:
|
||||
g.boundaries.remove(to_be_seperate_boundary_by_start)
|
||||
|
||||
# 切分
|
||||
if to_be_seperate_boundary_by_end:
|
||||
need_to_be_cut_boundary = mid_verts_to_boundary[self.end_vertex]
|
||||
seperate_boundary_1, seperate_boundary_2 = seperate_boundary_from_vert(
|
||||
need_to_be_cut_boundary, self.end_vertex
|
||||
)
|
||||
# 切分为两个的boundary加进全局变量
|
||||
for i in [seperate_boundary_1, seperate_boundary_2]:
|
||||
# 重建boundary网格
|
||||
i.create_mesh(3)
|
||||
# 附加到全局变量
|
||||
g.boundaries.add(i)
|
||||
if to_be_seperate_boundary_by_start:
|
||||
need_to_be_cut_boundary = mid_verts_to_boundary[self.start_vertex]
|
||||
seperate_boundary_1, seperate_boundary_2 = seperate_boundary_from_vert(
|
||||
need_to_be_cut_boundary, self.start_vertex
|
||||
)
|
||||
# 切分为两个的boundary加进全局变量
|
||||
for i in [seperate_boundary_1, seperate_boundary_2]:
|
||||
# 重建boundary网格
|
||||
i.create_mesh(3)
|
||||
# 附加到全局变量
|
||||
g.boundaries.add(i)
|
||||
|
||||
# 重建loop和pattern在外部进行了,因为是删loop而不是改loop所以可以自动识别到
|
||||
# 处理切分完毕↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑
|
||||
|
||||
# 如果开始结束都在某个loop的控制点中,则要把那个loop删掉,如:正方形,对角切
|
||||
for loop in g.loops:
|
||||
controll_verts = loop.controll_verts
|
||||
if (
|
||||
self.start_vertex in controll_verts
|
||||
and self.end_vertex in controll_verts
|
||||
):
|
||||
g.loops.remove(loop)
|
||||
loop.remove_pattern()
|
||||
|
||||
# 创建网格前,处理吸附到中轴线
|
||||
if self.is_editable and g.is_drawing_along_mirror:
|
||||
functions.process_path_to_mirror_axis(self.path)
|
||||
|
||||
self.create_mesh(self.length)
|
||||
g.boundaries.add(self)
|
||||
|
||||
g.is_redraw_boundary = True
|
||||
|
||||
return self
|
||||
|
||||
def remove_mesh(
|
||||
self, exclude_verts=[], is_force_remove_edges=False, is_remove_start_end=False
|
||||
):
|
||||
if is_force_remove_edges:
|
||||
functions.remove_bm_edges(
|
||||
g.obj_bm, self.edges
|
||||
) # using bmesh.ops.delete edges will also delete the island vert
|
||||
|
||||
if len(self.edges) == 1:
|
||||
# 特殊情况,
|
||||
functions.remove_bm_edges(
|
||||
g.obj_bm, [self.edges[0]]
|
||||
) # using bmesh.ops.delete edges will also delete the island vert
|
||||
|
||||
if len(self.edges) > 1:
|
||||
if is_remove_start_end:
|
||||
# 删除网格,包括开始和结束顶点,用于用户在modal的删除
|
||||
boundaries_verts_except_self = self.get_boundaries_verts_except_self()
|
||||
to_be_delete_verts = []
|
||||
remaining_verts = []
|
||||
for vert in self.verts_generator():
|
||||
if vert in exclude_verts:
|
||||
continue # 排除 exclude_verts 中的顶点
|
||||
|
||||
if self.is_solid_start and vert == self.start_vertex:
|
||||
remaining_verts.append(
|
||||
vert
|
||||
) # 如果是 solid_start, 跳过start_vertex
|
||||
continue
|
||||
if self.is_solid_end and vert == self.end_vertex:
|
||||
remaining_verts.append(vert) # 如果是 solid_end, 跳过end_vertex
|
||||
continue
|
||||
# 并且这个vert不能出现在其他boundary中
|
||||
if vert in boundaries_verts_except_self:
|
||||
remaining_verts.append(vert)
|
||||
continue
|
||||
|
||||
to_be_delete_verts.append(vert)
|
||||
functions.delete_bm_verts(g.obj_bm, to_be_delete_verts)
|
||||
|
||||
# 即使经过上面的条件,还是可能漏,如:三角形,外部画一根截断一个边,先删外部的那一根,然后删被截断的下面那根,然后删上面那根,中间会留一个顶点,因为一开始就把他当成solid_vert看了,但是随着boundary被删除,他就不是了
|
||||
to_be_delete_verts = []
|
||||
for vert in remaining_verts:
|
||||
if len(vert.link_edges) == 0:
|
||||
to_be_delete_verts.append(vert)
|
||||
functions.delete_bm_verts(g.obj_bm, to_be_delete_verts)
|
||||
|
||||
else:
|
||||
# 删除网格,除了开始和结束顶点,用于改变长度时的删除网格
|
||||
verts = [
|
||||
vert
|
||||
for vert in self.verts_generator()
|
||||
if vert != self.start_vertex
|
||||
and vert != self.end_vertex
|
||||
and vert not in exclude_verts
|
||||
]
|
||||
functions.delete_bm_verts(g.obj_bm, verts)
|
||||
|
||||
self.edges = []
|
||||
bmesh.update_edit_mesh(g.obj_me)
|
||||
|
||||
def get_boundaries_verts_except_self(self):
|
||||
boundaries_verts_except_self = set()
|
||||
for b in g.boundaries:
|
||||
if b == self:
|
||||
continue
|
||||
boundaries_verts_except_self.update(b.verts)
|
||||
return boundaries_verts_except_self
|
||||
|
||||
def verts_generator(self):
|
||||
# # find first edge
|
||||
# start_vertex = self.start_vertex
|
||||
# yield start_vertex
|
||||
# for edge in self.edges:
|
||||
# end_vertex = edge.other_vert(start_vertex)
|
||||
# yield end_vertex
|
||||
# start_vertex = end_vertex
|
||||
|
||||
for i in self.verts: # 搞不懂为什么一开始写成上面这样
|
||||
yield i
|
||||
|
||||
@property
|
||||
def vertices_co(self):
|
||||
return [temp.co for temp in list(self.verts_generator())]
|
||||
|
||||
def vertices_co_along_path(self, edge_length=None):
|
||||
if edge_length is None:
|
||||
edge_length = self.temp_length
|
||||
return functions.uniform_vertices_co_along_path(self.path, edge_length - 1)
|
||||
|
||||
def update_mid_co(self):
|
||||
midpoint_of_path = functions.get_midpoint_of_path(g.obj_bm, self.edges)
|
||||
|
||||
self.mid_co = g.obj.matrix_world @ midpoint_of_path
|
||||
return
|
||||
|
||||
@functions.set_block_before_done
|
||||
def delete(self):
|
||||
# boundary删除,loop可能会被删除,loop被删pattern也会被删
|
||||
related_loops = functions.get_loops_from_boundary(self)
|
||||
|
||||
# 删除网格
|
||||
for related_loop in related_loops:
|
||||
related_loop.remove_pattern()
|
||||
|
||||
if not self.solid_path:
|
||||
self.remove_mesh(is_remove_start_end=True)
|
||||
|
||||
# 从全局变量中删除loop和boundary
|
||||
g.boundaries.remove(self)
|
||||
for related_loop in related_loops:
|
||||
g.loops.remove(related_loop)
|
||||
|
||||
g.is_redraw_boundary = True
|
||||
g.is_redraw_pattern = True
|
||||
|
||||
return
|
||||
|
||||
|
||||
def seperate_from_path_idxs(path_idxs):
|
||||
# 从多个
|
||||
pass
|
||||
|
||||
|
||||
def seperate_boundary_from_vert(boundary, seperate_vert):
|
||||
# 从boundary已有的顶点,拆分boundary
|
||||
# 如果path不够长,则只有头尾直线
|
||||
|
||||
# # 首先要删除boundaries列表(外部删了)
|
||||
# g.boundaries.remove(boundary)
|
||||
|
||||
seperate_vert_co_world = g.obj.matrix_world @ seperate_vert.co
|
||||
|
||||
# 删除boundary网格(不包括开始和结束点和vert)
|
||||
boundary.remove_mesh(exclude_verts=[seperate_vert], is_force_remove_edges=True)
|
||||
|
||||
# 拆分
|
||||
ori_start_vert = boundary.start_vertex
|
||||
ori_end_vert = boundary.end_vertex
|
||||
|
||||
# 通过verts_correspond_path_idxs,来获取已有顶点到path索引的映射,因为path是不均匀的,所以要在计算均匀顶点的时候顺便计算
|
||||
vert_idx_in_verts = boundary.verts.index(seperate_vert)
|
||||
vert_idx_in_path = boundary.verts_correspond_path_idxs[
|
||||
vert_idx_in_verts
|
||||
] # 如果path 10个,则范围是0~9,tou是0,尾是9,中间是0~8
|
||||
if len(boundary.path) < vert_idx_in_path + 2: # 理论不会发生
|
||||
raise f"vert_idx_in_path exceed path length: {len(boundary.path)} {vert_idx_in_path}"
|
||||
path_1 = boundary.path[0 : vert_idx_in_path + 1] + [seperate_vert_co_world]
|
||||
path_2 = [seperate_vert_co_world] + boundary.path[
|
||||
vert_idx_in_path + 1 : len(boundary.path)
|
||||
]
|
||||
|
||||
boundary_1 = Boundary(ori_start_vert, seperate_vert, path_1)
|
||||
boundary_2 = Boundary(seperate_vert, ori_end_vert, path_2)
|
||||
|
||||
return boundary_1, boundary_2
|
||||
Reference in New Issue
Block a user