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:
318
loop.py
Normal file
318
loop.py
Normal 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
|
||||
Reference in New Issue
Block a user