- 添加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配置文件
319 lines
9.7 KiB
Python
319 lines
9.7 KiB
Python
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
|