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

194
generate_loop.py Normal file
View File

@@ -0,0 +1,194 @@
from . import g
from .loop import Loop
from collections import defaultdict
# 因为要引用Loop patch,所以单独放了
def regenerate_loops(is_force_refresh=False):
boundaries = g.boundaries # 假设 'g' 有一个 'boundaries' 属性,它包含边界对象
graph = {}
# 构建无向图
for boundary in boundaries:
graph.setdefault(boundary.start_vertex, []).append(boundary.end_vertex)
graph.setdefault(boundary.end_vertex, []).append(boundary.start_vertex)
# 方向字典
boundary_direction = {
(boundary.start_vertex, boundary.end_vertex): (boundary, True)
for boundary in boundaries
}
boundary_direction.update(
{
(boundary.end_vertex, boundary.start_vertex): (boundary, False)
for boundary in boundaries
}
)
def dfs(node, start, path, visited, current_loops):
max_length = 6
if len(path) > max_length:
return
visited.add(node)
path.append(node)
for neighbor in graph[node]:
# 2条边在拓扑结构上是一摸一样的没办法通过拓扑办法识别最小圈
if neighbor == start and len(path) > 2:
# 找到一个环路创建对应的环路每条边是一个元组包含boundary和方向
loop = Loop()
for i in range(len(path) - 1):
start_vertex = path[i]
end_vertex = path[i + 1]
if (start_vertex, end_vertex) in boundary_direction:
boundary, direction = boundary_direction[
(start_vertex, end_vertex)
]
loop.add_edge(boundary, direction)
else:
boundary, direction = boundary_direction[
(end_vertex, start_vertex)
]
loop.add_edge(boundary, direction)
# 处理闭合边path[-1] 到 path[0]
start_vertex = path[-1]
end_vertex = path[0]
if (start_vertex, end_vertex) in boundary_direction:
boundary, direction = boundary_direction[(start_vertex, end_vertex)]
loop.add_edge(boundary, direction)
else:
boundary, direction = boundary_direction[(end_vertex, start_vertex)]
loop.add_edge(boundary, direction)
current_loops.append(loop)
elif neighbor not in visited:
dfs(neighbor, start, path, visited, current_loops)
path.pop()
visited.remove(node)
loops = [] # 所有环路
for node in graph:
current_loops = [] # 用来存储当前节点发现的环路
dfs(node, node, [], set(), current_loops)
loops.extend(current_loops) # 将当前节点的环路加入到结果中
# 去重环路
unique_loops = set(loops) # 使用 set 去重环路
# 删除长度超过6的
unique_loops = {loop for loop in unique_loops if len(loop.boundaries) <= 6}
# 如果一个环路,他有弦,则删掉
to_be_delete_loops = set()
# 获取连续1 2 3条boundary的映射并且要标记属于哪个loop的
start_v_and_end_v_to_loops_of_length = [
defaultdict(set) for _ in range(3)
] # 0是长度为1的1为长度为2的2为长度为3的元素为字典key是两个顶点value是相关的loop
for loop in unique_loops:
verts = loop.controll_verts
for vert_idx, vert in enumerate(verts):
for offset in range(1, 4): # 1~3
vert_1 = verts[vert_idx]
vert_2 = verts[(vert_idx + offset) % len(verts)]
sorted_start_v_and_end_v_tuple = tuple(
sorted([vert_1, vert_2], key=lambda v: v.index)
)
start_v_and_end_v_to_loops_of_length[offset - 1][
sorted_start_v_and_end_v_tuple
].add(loop)
# # debug形状
# print(len(start_v_and_end_v_to_loops_of_length))
# for i, start_v_and_end_v_to_loops in enumerate(
# start_v_and_end_v_to_loops_of_length
# ):
# print(f"长度{i+1}")
# for (vert_1, vert_2), loops in start_v_and_end_v_to_loops.items():
# print(len(loops))
for loop in unique_loops:
if loop.length <= 3: # 小于3不检查弦
continue
offset_range = loop.length - 3
loop_controll_verts = loop.controll_verts
for offset in range(1, offset_range + 1):
# 隔一个顶点就检查1条的隔两个顶点就检查1和2条的隔三个就检查123条的
# offset就是boundary的条数
# 遍历两两不相邻的顶点
for i in range(len(loop_controll_verts)):
for j in range(len(loop_controll_verts)):
# 确保 j 不等于 i并且 j 不是 i 的相邻边界
if (
i != j
and (j != (i + 1) % len(loop_controll_verts))
and (j != (i - 1) % len(loop_controll_verts))
):
vert_1 = loop_controll_verts[i]
vert_2 = loop_controll_verts[j]
sorted_start_v_and_end_v_tuple = tuple(
sorted([vert_1, vert_2], key=lambda v: v.index)
)
# 隔一个顶点就检查1条的2和3跳过
# 如果 i 和 j 隔了 1 个顶点,跳过 offset 为 1 和 2
distance = min(
abs(i - j), len(loop_controll_verts) - abs(i - j)
) # 0 1距离1
# print(loop.length, distance, offset)
if distance == 2 and (offset == 2 or offset == 3):
continue # 跳过 offset 为 1 或 2 的情况
# 如果 i 和 j 隔了 2 个顶点,跳过 offset 为 2
if distance == 3 and offset == 2:
continue # 跳过 offset 为 2 的情况
related_loops = start_v_and_end_v_to_loops_of_length[
offset - 1
].get(sorted_start_v_and_end_v_tuple)
if not related_loops:
continue
# 排除自身的影响,并且用-号而不用remove()这样搞不会导致start_v_and_end_v_to_loops_of_length被修改
remaining_loops = related_loops - {loop}
if remaining_loops:
to_be_delete_loops.add(loop)
# TODO:一旦被认为是弦则对该loop的循环就要退出了但是层数太多了
break
# # 从所有的loops中删除外层loop
unique_loops.difference_update(to_be_delete_loops)
# 防止重建网格
updated_loops = []
if not is_force_refresh:
loop_tobe_assign = set()
for loop in unique_loops:
if loop in g.loops:
continue
g.loops.append(loop)
updated_loops.append(loop)
else:
g.loops = unique_loops
updated_loops = unique_loops
# print(len(g.loops))
# for loop in g.loops:
# print("---")
# print(loop.shape)
# for b, d in loop.b_and_d_list:
# print(b.start_vertex.index, b.end_vertex.index, d)
for updated_loop in updated_loops:
updated_loop.recreate_pattern()
return updated_loops