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:
2026-03-03 19:24:57 +08:00
commit ab91b120e6
44 changed files with 17551 additions and 0 deletions

318
loop.py Normal file
View File

@@ -0,0 +1,318 @@
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