Files
blender-easy-patch/loop.py
小煜 ab91b120e6 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配置文件
2026-03-03 19:24:57 +08:00

319 lines
9.7 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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