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