import bmesh import mathutils import traceback from . import pattern from .pattern import pattern_list from . import g class Loop: def __init__(self): self.b_and_d_list = [] # 存储边界和方向的元组 (boundary, direction) self.bvh_tree = None self.pattern = None # create要调用到,没声明报错 self.mid_co = mathutils.Vector((0, 0, 0)) self.solver_msg = "" self.suggest_solution = [] self.solver_constraint_list = [ None for _ in range(8) ] # 丑陋储存法,用None作占位符,现存的pattern最多有八个约束(6个padding 2个x y 附加边流) self.solver_constraint_rotation = None self.solver_constraint_pattern = None def add_edge(self, boundary, direction): self.b_and_d_list.append((boundary, direction)) self.update_mid_co() def update_mid_co(self): self.mid_co = sum( [b.mid_co for b in self.boundaries], mathutils.Vector() ) / len(self.boundaries) def normalize(self): """标准化环路,将每条边的顶点索引进行排序,忽略边的方向""" return sorted( [ (id(boundary), boundary) # Sort by the unique id of the boundary object for boundary, _ in self.b_and_d_list ] ) def __eq__(self, other): """重载 '==' 运算符,用来进行环路set的去重""" return self.normalize() == other.normalize() def __hash__(self): """重载 '__hash__' 使其能够作为字典的键进行去重""" return hash(tuple(self.normalize())) def __repr__(self): return f"Loop({self.b_and_d_list})" @property def length(self): return len(self.b_and_d_list) @property def shape(self): # 这个是指边数 return [len(b.verts) - 1 for b, _ in self.b_and_d_list] @property def boundaries(self): return [b for b, _ in self.b_and_d_list] @property def boundary_verts_walk(self): # 有重复,用于createmesh的时候walk,即每个boundary的头尾都是重叠的 verts = [] for b, d in self.b_and_d_list: if d: boundary_verts = b.verts else: boundary_verts = b.verts[::-1] for v in boundary_verts: verts.append(v) return verts @property def verts(self): # 无重复顶点 verts = [] seen = set() # 用来跟踪已访问的顶点 for b, d in self.b_and_d_list: if d: boundary_verts = b.verts # 如果方向为真,按顺序获取顶点 else: boundary_verts = b.verts[::-1] # 如果方向为假,反转顶点顺序 # 遍历当前边界的顶点 for v in boundary_verts: if v not in seen: # 如果顶点未被访问过 verts.append(v) seen.add(v) # 标记该顶点为已访问 return verts @property def controll_verts(self): # 即start和end的列表,无重复顶点 controll_verts = [] for idx, (b, d) in enumerate(self.b_and_d_list): if d: vert_1 = b.start_vertex vert_2 = b.end_vertex else: vert_1 = b.end_vertex vert_2 = b.start_vertex controll_verts.append(vert_1) return controll_verts @property def edges(self): edges = [] for b, d in self.b_and_d_list: if d: boundary_edges = b.edges else: boundary_edges = b.edges[::-1] for e in boundary_edges: edges.append(e) return edges # @property # def controll_verts(self): # # 控制点,即每个boundary的start end # return set(v for b in self.boundaries for v in b.verts) @property def current_solution(self): if self.pattern and self.pattern.solution: return self.pattern.solution return None def create_bvh_tree(self): temp_bm = bmesh.new() temp_bm_verts = [] for b in self.boundaries: for vert in b.verts: vert = temp_bm.verts.new(vert.co) temp_bm_verts.append(vert) # 确保新创建的顶点的索引是正确的 temp_bm.verts.index_update() temp_bm.verts.ensure_lookup_table() temp_bm.faces.new(temp_bm_verts) # ngon bvh_tree = mathutils.bvhtree.BVHTree.FromBMesh(temp_bm) self.bvh_tree = bvh_tree temp_bm.free() def recreate_pattern(self): # 尝试创建pattern self.remove_pattern() try: pattern_instance = pattern.recreate_pattern(self) except Exception as e: pattern_instance = None error_info = traceback.format_exc() g.running_exception = e bmesh.update_edit_mesh(g.obj_me) self.pattern = pattern_instance g.is_redraw_pattern = True return self.pattern def remove_pattern(self): if self.pattern: self.pattern.remove_mesh() self.pattern = None def get_pattern_constraint(self): # 获取当前pattern,如果有约束则返回约束,无约束则返回当前pattern实例,如无解则返回None if self.solver_constraint_pattern: return self.solver_constraint_pattern elif self.pattern: # 不应返回pattern实例,后面还要copy,有些属性无法复制 for i in pattern.pattern_list: if i.name == self.pattern.name: return i return None def change_solver_constraint_rotation(self): current_pattern = self.get_pattern_constraint() if not current_pattern: return rotation = self.solver_constraint_rotation if rotation is None: if self.pattern: new_rotation = self.pattern.rotation + 1 else: new_rotation = 0 else: new_rotation = rotation + 1 new_rotation = new_rotation % len(self.boundaries) self.solver_constraint_rotation = new_rotation self.solver_constraint_pattern = current_pattern self.recreate_pattern() def next_solver_constraint_pattern(self, is_auto_search=True): current_pattern = self.get_pattern_constraint() # 初始化约束,设置pattern约束 boundary_num = len(self.boundaries) pattern_list_current = [ i for i in pattern_list if boundary_num == i.boundary_num ] self.init_solver_constraint() if current_pattern is None: loop_start_idx = 0 else: loop_start_idx = next( i for i, obj in enumerate(pattern_list_current) if obj.name == current_pattern.name ) for pattern in ( pattern_list_current[loop_start_idx + 1 :] + pattern_list_current[:loop_start_idx] ): # 不跳过第一个,会导致无法切换到下一个 self.solver_constraint_pattern = pattern result = self.recreate_pattern() if not is_auto_search: # 不是自动模式,则拿到第一个就溜 return if result is not None: return # 找不到,回到原处 self.solver_constraint_pattern = current_pattern self.recreate_pattern() def init_solver_constraint( self, is_init_list=True, is_init_rotation=True, is_init_pattern=True ): # 另外两个约束不能单独保留 if is_init_pattern: is_init_list = True is_init_rotation = True if is_init_list: self.solver_constraint_list = [None for _ in range(len(self.shape) + 2)] if is_init_rotation: self.solver_constraint_rotation = None if is_init_pattern: self.solver_constraint_pattern = None self.recreate_pattern() def change_solver_constraint_list(self, key, change): current_pattern = self.get_pattern_constraint() if not current_pattern: return if not self.solver_constraint_pattern and not self.pattern: return if isinstance(key, int): solution_idx = key else: edge_var_count = len(current_pattern.additional_edge_flow_info) if key == "x" and edge_var_count >= 1: solution_idx = len(self.shape) - 1 + 1 elif key == "y" and edge_var_count >= 2: solution_idx = len(self.shape) - 1 + 2 elif key == "z" and edge_var_count >= 3: solution_idx = len(self.shape) - 1 + 3 else: return # 是否有约束,没有再找solution solver_constraint_list = self.solver_constraint_list old_value = solver_constraint_list[solution_idx] if old_value is None: current_solution = self.current_solution if current_solution: old_value = current_solution[solution_idx] if old_value is None: old_value = 0 new_value = old_value + change if new_value < 0: return upper = max(self.shape) if new_value > upper: return self.solver_constraint_list[solution_idx] = new_value self.solver_constraint_pattern = current_pattern self.recreate_pattern() def get_boundary_index(self, boundary_input): for idx, b in enumerate(self.boundaries): if b == boundary_input: return idx