Browse Source

Initial commit: add single_tools scripts

main
Ke Han 1 month ago
commit
195a3406e0
  1. 6
      .gitignore
  2. 1208
      create_depth_fast.py
  3. 547
      fill_all_empty_faces_v1.2.py
  4. 192
      generate_fbx.py

6
.gitignore vendored

@ -0,0 +1,6 @@
__pycache__/
*.pyc
*.pyo
*.pyd
*.swp
.DS_Store

1208
create_depth_fast.py

File diff suppressed because it is too large Load Diff

547
fill_all_empty_faces_v1.2.py

@ -0,0 +1,547 @@
import torch
import argparse
import numpy as np
import cv2
import time
from collections import defaultdict
import tqdm
from multiprocessing import Pool, cpu_count
from typing import List, Tuple, Dict # 如果还未导入
def read_vertices(obj_path):
vertices = []
with open(obj_path, 'r') as file:
lines = file.readlines()
for line in lines:
if line.startswith('v '): # 顶点坐标
vertices.append(list(map(float, line.split()[1:4])))
vertices = torch.tensor(vertices)
return vertices
def read_uvs(obj_path):
uv_coordinates = []
with open(obj_path, 'r') as file:
lines = file.readlines()
for line in lines:
if line.startswith('vt '): # UV 坐标
uv_coordinates.append(list(map(float, line.split()[1:3])))
uv_coordinates = torch.tensor(uv_coordinates)
return uv_coordinates
def read_faces(obj_path):
vertex_indices = []
uv_indices = []
with open(obj_path, 'r') as file:
lines = file.readlines()
for line in lines:
if line.startswith('f '): # 面
parts = line.split()[1:]
v_indices = []
uv_indices_temp = []
for face in parts:
v, vt = map(int, face.split('/')[:2])
v_indices.append(v - 1)
uv_indices_temp.append(vt - 1)
vertex_indices.append(v_indices)
uv_indices.append(uv_indices_temp)
vertex_indices = torch.tensor(vertex_indices)
uv_indices = torch.tensor(uv_indices)
return vertex_indices, uv_indices
def read_missing_faces(missing_faces_path):
with open(missing_faces_path, 'r') as file:
lines = file.readlines()
missing_color_faces = torch.tensor(
[int(line.strip()) for line in lines]
)
return missing_color_faces
def read_uv_map(input_texture_path):
uv_map = cv2.imread(input_texture_path)
uv_map = cv2.cvtColor(uv_map, cv2.COLOR_BGR2RGB)
uv_map = torch.from_numpy(uv_map)
return uv_map
def parse_obj_file_and_uv_map(obj_path, missing_faces_path, input_texture_path, device):
print(f"Reading OBJ file: {obj_path}")
# vertices = []
# uv_coordinates = []
# vertex_indices = []
# uv_indices = []
# multiprocessing.set_start_method('spawn', force=True)
# multiprocessing.freeze_support()
start_time = time.time()
p = Pool(5)
uv_map_result = p.apply_async(read_uv_map, (input_texture_path,))
vertices_result = p.apply_async(read_vertices, (obj_path,))
uv_coordinates_result = p.apply_async(read_uvs, (obj_path,))
faces_result = p.apply_async(read_faces, (obj_path,))
missing_faces_result = p.apply_async(read_missing_faces, (missing_faces_path,))
p.close()
p.join()
vertices = vertices_result.get()
uv_coordinates = uv_coordinates_result.get()
vertex_indices, uv_indices = faces_result.get()
missing_color_faces = missing_faces_result.get()
uv_map = uv_map_result.get()
vertices = vertices.to(device)
uv_coordinates = uv_coordinates.to(device)
vertex_indices = vertex_indices.to(device)
uv_indices = uv_indices.to(device)
missing_color_faces = missing_color_faces.to(device)
uv_map = uv_map.to(device)
end_time = time.time()
print(f"using: {end_time - start_time} seconds")
# exit()
print("Converting to tensors...")
return vertices, uv_coordinates, vertex_indices, uv_indices, missing_color_faces, uv_map
def write_obj_with_uv_coordinates(filename, vertices, uvs, vertex_indices, uv_indices):
"""
高性能OBJ文件写入函数
Parameters:
filename (str): 输出OBJ文件路径
vertices (np.ndarray): 顶点数组
uvs (np.ndarray): UV坐标数组
vertex_indices (np.ndarray): 面的顶点索引
uv_indices (np.ndarray): 面的UV索引
"""
# 估算数据大小(以字节为单位)
estimated_size = (
len(vertices) * 40 + # 每个顶点约40字节 (v x.xxxxxx y.xxxxxx z.xxxxxx\n)
len(uvs) * 30 + # 每个UV坐标约30字节 (vt x.xxxxxx y.xxxxxx\n)
len(vertex_indices) * 40 # 每个面约40字节 (f v1/vt1 v2/vt2 v3/vt3\n)
)
# 设置缓冲区大小为估算大小的1.2倍,最小256MB,最大1GB
buffer_size = min(max(int(estimated_size * 1.2), 256 * 1024 * 1024), 1024 * 1024 * 1024)
# 使用格式化字符串和列表推导式优化字符串生成
vertex_lines = ['v %.6f %.6f %.6f' % (v[0], v[1], v[2]) for v in vertices]
uv_lines = ['vt %.6f %.6f' % (uv[0], uv[1]) for uv in uvs]
# 优化face数据处理
face_lines = []
face_format = 'f %d/%d %d/%d %d/%d'
for v_idx, uv_idx in zip(vertex_indices, uv_indices):
face_lines.append(face_format % (
v_idx[0] + 1, uv_idx[0] + 1,
v_idx[1] + 1, uv_idx[1] + 1,
v_idx[2] + 1, uv_idx[2] + 1
))
# 使用join一次性构建完整内容
content = ['mtllib mesh.mtl'] + vertex_lines + [''] + uv_lines + [''] + ['usemtl material_0'] + face_lines
# 一次性写入所有数据
with open(filename, 'w', buffering=buffer_size) as f:
f.write('\n'.join(content))
def load_regions(filename):
regions = []
with open(filename, 'r') as file:
for line in file:
parts = line.split(";")
if len(parts) != 2:
continue # Skip any lines that don't have exactly two parts
first_set = set(int(x) for x in parts[0].strip().split())
second_set = set(int(x) for x in parts[1].strip().split())
regions.append((first_set, second_set))
return regions
def build_face_adjacency(vertices, faces):
"""
构建面的邻接关系基于共享边
Args:
vertices: 顶点数组
faces: 面片索引数组 (N x 3)
Returns:
dict: 面片邻接关系字典key为面片索引value为邻接面片索引列表
"""
# 将faces转换为numpy数组以加快处理速度
faces = np.asarray(faces)
num_faces = len(faces)
# 为每个面创建所有边 (Nx3x2)
edges = np.stack([
np.column_stack((faces[:, i], faces[:, (i + 1) % 3]))
for i in range(3)
], axis=1)
# 确保边的方向一致 (较小的顶点索引在前)
edges.sort(axis=2)
# 将边展平为 (Nx3, 2) 的形状
edges = edges.reshape(-1, 2)
# 创建边到面的映射
edge_faces = np.repeat(np.arange(num_faces), 3)
# 使用复合键对边进行排序
edge_keys = edges[:, 0] * vertices.shape[0] + edges[:, 1]
sort_idx = np.argsort(edge_keys)
edges = edges[sort_idx]
edge_faces = edge_faces[sort_idx]
# 找到重复的边(共享边)
same_edges = (edge_keys[sort_idx][1:] == edge_keys[sort_idx][:-1])
edge_start_idx = np.where(same_edges)[0]
# 构建邻接字典
face_adjacency = defaultdict(list)
for idx in edge_start_idx:
face1, face2 = edge_faces[idx], edge_faces[idx + 1]
face_adjacency[face1].append(face2)
face_adjacency[face2].append(face1)
return dict(face_adjacency)
def find_groups_and_subgroups(face_adjacency, missing_faces):
"""
找到相连的面组和它们的邻接面
返回:
regions: 列表每个元素是一个元组 (missing_faces_set, adjacent_faces_set)
load_regions() 函数返回格式保持一致
"""
missing_faces_set = set(missing_faces.cpu().numpy())
unused_faces = set(missing_faces.cpu().numpy())
regions = []
total_faces = len(unused_faces)
with tqdm.tqdm(total=total_faces, desc="Processing faces") as pbar:
while unused_faces:
start_face = unused_faces.pop()
current_group = {start_face}
current_subgroup = set()
stack = [start_face]
while stack:
face_idx = stack.pop()
for neighbor in face_adjacency.get(face_idx, []):
if neighbor in unused_faces:
current_group.add(neighbor)
unused_faces.remove(neighbor)
stack.append(neighbor)
elif neighbor not in missing_faces_set:
current_subgroup.add(neighbor)
regions.append((current_group, current_subgroup))
pbar.update(total_faces - len(unused_faces) - pbar.n)
# 输出统计信息
print(f"\nTotal regions: {len(regions)}")
print(f"Average missing faces group size: {sum(len(g[0]) for g in regions)/len(regions):.2f}")
print(f"Largest missing faces group size: {max(len(g[0]) for g in regions)}")
print(f"Smallest missing faces group size: {min(len(g[0]) for g in regions)}")
# 检查每个组是否都有邻接面
for i, (group, subgroup) in enumerate(regions):
if not subgroup:
print(f"Warning: Region {i} with {len(group)} missing faces has no adjacent faces!")
return regions
def compute_regions_face_colors(
regions: List[Tuple[set, set]],
uv_map: torch.Tensor,
uvs: torch.Tensor,
face_uv_indices: torch.Tensor,
device: str
) -> Dict[int, torch.Tensor]:
"""
根据每个区域的边缘面UV坐标计算加权平均的颜色
当无有效采样时更新对应face_uv_indices
参数:
regions (List[Tuple[set, set]]): 每个区域为 (缺失面集合, 邻接面集合)
uv_map (torch.Tensor): 原始纹理贴图RGB格式
uvs (torch.Tensor): 原始UV坐标
face_uv_indices (torch.Tensor): 每个面对应的UV索引
device (str): 使用的设备("cuda""cpu")
返回:
Dict[int, torch.Tensor]: 键为区域索引值为该区域加权平均计算得到的颜色uint8
"""
regions_face_color: Dict[int, torch.Tensor] = {}
for r_index, region in enumerate(tqdm.tqdm(regions, desc="Processing regions")):
region_faces_indexes = torch.tensor(list(region[0]), device=device)
region_edge_faces_indexes = torch.tensor(list(region[1]), device=device)
if len(region_edge_faces_indexes) == 0:
continue
# 获取边缘面的UV索引
edge_face_uv_indices = face_uv_indices[region_edge_faces_indexes]
# 使用三角形的质心UV坐标来采样颜色
triangle_uvs = uvs[edge_face_uv_indices] # shape: [num_faces, 3, 2]
centroid_uvs = triangle_uvs.mean(dim=1) # shape: [num_faces, 2]
# 将UV坐标转换为像素坐标
scale_tensor = torch.tensor([uv_map.shape[1] - 1, uv_map.shape[0] - 1], device=device)
pixel_coords = torch.round(centroid_uvs * scale_tensor)
pixel_coords[:, 1] = uv_map.shape[0] - 1 - pixel_coords[:, 1]
pixel_coords = pixel_coords.long().clamp(0, uv_map.shape[0] - 1)
# 直接采样质心位置的颜色
colors = uv_map[pixel_coords[:, 1], pixel_coords[:, 0]] # shape: [num_faces, 3]
# 使用面积加权平均来计算最终颜色
areas = torch.abs(
(triangle_uvs[:, 1, 0] - triangle_uvs[:, 0, 0]) * (triangle_uvs[:, 2, 1] - triangle_uvs[:, 0, 1]) -
(triangle_uvs[:, 2, 0] - triangle_uvs[:, 0, 0]) * (triangle_uvs[:, 1, 1] - triangle_uvs[:, 0, 1])
) * 0.5
if len(colors) > 0:
weighted_color = (colors.float() * areas.unsqueeze(1)).sum(dim=0) / areas.sum()
regions_face_color[r_index] = weighted_color.round().clamp(0, 255).to(torch.uint8)
else:
# 如果没有有效的采样点,使用第一个相邻面的UV坐标更新face_uv_indices
face_uv_indices[region_faces_indexes] = face_uv_indices[region_edge_faces_indexes[0]].unsqueeze(dim=0).clone()
return regions_face_color
def update_uv_map_and_indices(
uv_map: torch.Tensor,
uvs: torch.Tensor,
face_uv_indices: torch.Tensor,
regions: List[Tuple[set, set]],
regions_face_color: Dict[int, torch.Tensor],
device: str
) -> Tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
"""
根据计算得到的区域颜色更新UV贴图及对应的UV坐标并批量更新face_uv_indices
参数:
uv_map (torch.Tensor): 原始纹理贴图RGB格式
uvs (torch.Tensor): 原始UV坐标
face_uv_indices (torch.Tensor): 原始面的UV索引
regions (List[Tuple[set, set]]): 每个区域为 (缺失面集合, 邻接面集合)
regions_face_color (Dict[int, torch.Tensor]): 每个区域计算得到的颜色
device (str): 使用的设备("cuda""cpu")
返回:
Tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
new_uv_map: 更新后的UV贴图
uvs_updated: 更新后的UV坐标拼接上新计算的UV
face_uv_indices: 更新后的face UV索引
"""
total_regions = len(regions_face_color)
grid_size = uv_map.shape[1] // 3
all_c = torch.div(torch.arange(total_regions, device=device), grid_size, rounding_mode='floor')
all_r = torch.remainder(torch.arange(total_regions, device=device), grid_size)
# 创建新的颜色UV贴图
color_uv_map = torch.full((int(uv_map.shape[0] / 2), uv_map.shape[1], 3),
255, dtype=torch.uint8, device=device)
# 调整原始uvs的纵坐标
uvs[:, 1] = uvs[:, 1] * (2 / 3) + 1 / 3
# 批量创建所有颜色块的坐标
c_indices = all_c.unsqueeze(1).repeat(1, 9) * 3 + torch.tensor([0, 1, 2, 0, 1, 2, 0, 1, 2],
device=device).unsqueeze(0)
r_indices = all_r.unsqueeze(1).repeat(1, 9) * 3 + torch.tensor([0, 0, 0, 1, 1, 1, 2, 2, 2],
device=device).unsqueeze(0)
# 批量设置颜色
colors = torch.stack([color for _, color in sorted(regions_face_color.items(), key=lambda x: x[0])])
colors_repeated = colors.unsqueeze(1).repeat(1, 9, 1)
color_uv_map[c_indices.flatten(), r_indices.flatten()] = colors_repeated.reshape(-1, 3)
# 批量计算新的UV坐标
pixels = torch.stack([
all_r * 3 + 1,
uv_map.shape[0] + all_c * 3 + 1
], dim=1).to(device)
u_new = pixels[:, 0].float() / (uv_map.shape[1] - 1)
new_height = int(uv_map.shape[0] + uv_map.shape[0] / 2)
v_new = (new_height - 1 - pixels[:, 1].float()) / (new_height - 1)
new_uvs = torch.stack([u_new, v_new], dim=1)
# 更新UV坐标:拼接新计算的UV
uvs_updated = torch.cat([uvs, new_uvs], dim=0)
uv_coordinates_start = uvs_updated.shape[0] - total_regions
# 批量更新face_uv_indices
for i, (region_index, _) in enumerate(sorted(regions_face_color.items(), key=lambda x: x[0])):
region_faces_indexes = torch.tensor(list(regions[region_index][0]), device=device)
face_uv_indices[region_faces_indexes] = torch.full((1, 3), uv_coordinates_start + i, device=device)
# 合并原始UV贴图和新的颜色UV贴图
new_uv_map = torch.cat((uv_map, color_uv_map), dim=0)
return new_uv_map, uvs_updated, face_uv_indices
def group_regions_by_y_axis(
regions: List[Tuple[set, set]],
vertices: torch.Tensor,
triangle_vertex_indices: torch.Tensor,
device: str,
interval_size: float = 0.1
) -> Dict[int, List[int]]:
"""
将区域按照y轴高度分组
Args:
regions: 区域列表每个区域为(缺失面集合, 邻接面集合)的元组
vertices: 顶点坐标张量
triangle_vertex_indices: 三角形顶点索引张量
device: 计算设备 ('cuda' 'cpu')
interval_size: y轴分组的间隔大小默认为0.1
Returns:
Dict[int, List[int]]: 以y轴区间为键区域索引列表为值的字典
"""
y_intervals = defaultdict(list)
for r_index, region in enumerate(regions):
region_faces_indexes = torch.tensor(list(region[0]), device=device)
# 计算面组的平均y轴位置
face_vertices = vertices[triangle_vertex_indices[region_faces_indexes]]
avg_y = face_vertices[:, :, 1].mean(dim=(0, 1))
# 根据y轴位置分配到对应区间
interval_key = int(avg_y // interval_size)
y_intervals[interval_key].append(r_index)
return dict(y_intervals)
def align_regions_colors(
regions_face_color: Dict[int, torch.Tensor],
y_intervals: Dict[int, List[int]],
regions: List[Tuple[set, set]]
) -> Dict[int, torch.Tensor]:
"""
对齐区间内的颜色
Args:
regions_face_color: 每个区域的颜色
y_intervals: 每个y轴区间的区域索引列表
Returns:
Dict[int, torch.Tensor]: 以y轴区间为键颜色为值的字典
"""
# aligned_regions_face_color = {}
large_group_threshold_min = 5000
large_group_threshold_max = 100000
for interval_key, region_indices in y_intervals.items():
large_groups = []
# normal_groups = []
for r_index in region_indices:
region = regions[r_index]
if len(region[0]) >= large_group_threshold_min and len(region[0]) <= large_group_threshold_max:
large_groups.append((r_index, len(region[0]), regions_face_color[r_index]))
# 查找 large_groups 中 len(region[0]) 最大的组,并获取其颜色
if large_groups:
largest_group = max(large_groups, key=lambda x: x[1])
color: torch.Tensor = largest_group[2]
for large_group in large_groups:
regions_face_color[large_group[0]] = color
return regions_face_color
def process(input_obj_path, input_texture_path, missing_faces_path, output_obj_path, output_texture_path):
start_time = time.time()
device = 'cuda' if torch.cuda.is_available() else 'cpu'
vertices, uvs, triangle_vertex_indices, face_uv_indices, missing_color_faces, uv_map = parse_obj_file_and_uv_map(
input_obj_path, missing_faces_path, input_texture_path, device=device)
# 构建面的邻接关系和找到区域
start_face_adjacency_time = time.time()
face_adjacency = build_face_adjacency(vertices.cpu().numpy(), triangle_vertex_indices.cpu().numpy())
end_face_adjacency_time = time.time()
print(f"face_adjacency using: {end_face_adjacency_time - start_face_adjacency_time} seconds")
start_find_groups_time = time.time()
regions = find_groups_and_subgroups(face_adjacency, missing_color_faces)
end_find_groups_time = time.time()
print(f"find_groups_and_subgroups using: {end_find_groups_time - start_find_groups_time} seconds")
start_texture_map_time = time.time()
# 使用新封装的函数计算每个区域的加权平均颜色
regions_face_color = compute_regions_face_colors(regions, uv_map, uvs, face_uv_indices, device)
end_texture_map_time = time.time()
print(f"texture_mapping_to_triangle using: {end_texture_map_time - start_texture_map_time} seconds")
# 按y轴区间分组
y_intervals = group_regions_by_y_axis(
regions,
vertices,
triangle_vertex_indices,
device
)
# 对齐区间内的颜色
regions_face_color = align_regions_colors(regions_face_color, y_intervals, regions)
# 更新UV贴图和面索引
start_color_map_time = time.time()
new_uv_map, uvs, face_uv_indices = update_uv_map_and_indices(uv_map, uvs, face_uv_indices, regions,
regions_face_color, device)
end_color_map_time = time.time()
print(f"color_mapping_to_triangle using: {end_color_map_time - start_color_map_time} seconds")
end_time = time.time()
print(f"using: {end_time - start_time} seconds")
# 写入OBJ和纹理贴图
start_write_time = time.time()
vertices_cpu = vertices.cpu().numpy()
uvs_cpu = uvs.cpu().numpy()
triangle_vertex_indices_cpu = triangle_vertex_indices.cpu().numpy()
face_uv_indices_cpu = face_uv_indices.cpu().numpy()
new_uv_map_cpu = new_uv_map.cpu().numpy()
new_uv_map_bgr = cv2.cvtColor(new_uv_map_cpu, cv2.COLOR_RGB2BGR)
with Pool(2) as p:
# 异步执行OBJ和纹理图写入操作
obj_future = p.apply_async(write_obj_with_uv_coordinates,
(output_obj_path, vertices_cpu, uvs_cpu,
triangle_vertex_indices_cpu, face_uv_indices_cpu))
img_future = p.apply_async(cv2.imwrite,
(output_texture_path, new_uv_map_bgr,
[cv2.IMWRITE_PNG_COMPRESSION, 3]))
obj_future.get()
img_future.get()
end_write_time = time.time()
end_time = time.time()
print(f"Total file writing time: {end_write_time - start_write_time:.2f} seconds")
print(f"using: {end_time - start_time} seconds")
def main():
parser = argparse.ArgumentParser(description='Process OBJ files to fix missing color faces.')
parser.add_argument('--input_obj', type=str, required = True, help='Path to the input OBJ file')
parser.add_argument('--input_texture', type=str, required = True, help='Path to the texture file')
parser.add_argument('--missing_faces', type=str, required = True, help='Path to the file with indices of missing color faces')
parser.add_argument('--output_obj', type=str, required = True, help='Path to the output OBJ file')
parser.add_argument('--output_texture', type=str, required = True, help='Path to the texture file')
args = parser.parse_args()
process(args.input_obj, args.input_texture, args.missing_faces, args.output_obj, args.output_texture)
if __name__ == '__main__':
main()

192
generate_fbx.py

@ -0,0 +1,192 @@
import bpy
import os
import sys
import argparse
class ArgumentParserForBlender(argparse.ArgumentParser):
"""
This class is identical to its superclass, except for the parse_args
method (see docstring). It resolves the ambiguity generated when calling
Blender from the CLI with a python script, and both Blender and the script
have arguments. E.g., the following call will make Blender crash because
it will try to process the script's -a and -b flags:
>>> blender --python my_script.py -a 1 -b 2
To bypass this issue this class uses the fact that Blender will ignore all
arguments given after a double-dash ('--'). The approach is that all
arguments before '--' go to Blender, arguments after go to the script.
The following calls work fine:
>>> blender --python my_script.py -- -a 1 -b 2
>>> blender --python my_script.py --
"""
def _get_argv_after_doubledash(self):
"""
Given the sys.argv as a list of strings, this method returns the
sublist right after the '--' element (if present, otherwise returns
an empty list).
"""
try:
idx = sys.argv.index("--")
return sys.argv[idx+1:] # the list after '--'
except ValueError as e: # '--' not in the list:
return []
# overrides superclass
def parse_args(self):
"""
This method is expected to behave identically as in the superclass,
except that the sys.argv list will be pre-processed using
_get_argv_after_doubledash before. See the docstring of the class for
usage examples and details.
"""
return super().parse_args(args=self._get_argv_after_doubledash())
def find_pid_objname(pid):
for obj in bpy.data.objects:
if obj.type == 'MESH':
return obj.name
def obj2glb(input_path: str, output_path: str, human_num, face_num, is_reduce_face) -> None:
"""
将OBJ文件转换为GLB格式并保存
参数:
input_path (str): 输入OBJ文件的路径
output_path (str): 输出GLB文件的路径
human_num (int): 人物数量
face_num (int): 目标面数
is_reduce_face (str): 是否减面
img_quality (int): 图片质量
返回:
None
"""
if is_reduce_face == "True":
is_reduce_face = True
else:
is_reduce_face = False
if not os.path.exists(input_path):
raise FileNotFoundError(f"输入文件 {input_path} 不存在")
bpy.ops.object.delete(use_global=False, confirm=False)
bpy.ops.object.select_all(action="DESELECT")
bpy.ops.object.select_by_type(type="MESH")
bpy.ops.object.delete(use_global=False, confirm=False)
bpy.ops.wm.obj_import(filepath=input_path)
pid = os.path.splitext(os.path.basename(input_path))[0]
print(f'人物数量:{human_num},目标面数:{face_num}')
faces_dest = face_num * human_num
total_faces: int = 0
for obj in bpy.data.objects:
if obj.type == 'MESH':
# 获取对象的面数并累加
total_faces += len(obj.data.polygons)
print(f"所有对象的面数之和: {total_faces}")
if not is_reduce_face:
print("不减面")
faces_dest = total_faces
print(f"目标面数:{faces_dest} 当前面数:{total_faces}")
for obj in bpy.data.objects:
if obj.type == 'MESH':
# 取消选中所有对象
bpy.ops.object.select_all(action='DESELECT')
# 选中当前对象
obj.select_set(True)
bpy.context.view_layer.objects.active = obj
# 添加和应用修改器
modifier = obj.modifiers.new(name="Decimate", type='DECIMATE')
modifier.ratio = faces_dest / total_faces
# 应用修改器
bpy.ops.object.modifier_apply(modifier="Decimate")
pid_objname = find_pid_objname(pid)
obj = bpy.data.objects[pid_objname]
# scale = 90 / bpy.data.objects[pid_objname].dimensions.y
print(bpy.data.objects[pid_objname].dimensions)
# bpy.data.objects[pid_objname].scale = (scale, scale, scale)
# bpy.ops.object.transform_apply(scale=True)
bpy.ops.outliner.orphans_purge(do_recursive=True)
# 导出GLB文件
bpy.ops.export_scene.fbx(filepath=output_path,
use_selection=False, # 导出所有对象,若只导出选中对象可设为 True
object_types={'MESH'}, # 只导出网格对象
path_mode='RELATIVE', # 使用相对贴图路径
# embed_textures=False, # 不嵌入贴图(路径生效)
# apply_unit_scale=True, # 应用单位缩放
# apply_scale_options='FBX_SCALE_ALL', # 应用缩放
bake_space_transform=False) # 保持原始空间变换
# 清理所有资源的优化版本
def clean_data_blocks():
"""清理所有数据块"""
# 首先删除所有对象
bpy.ops.object.select_all(action='SELECT')
bpy.ops.object.delete()
# 按特定顺序清理数据块以避免依赖问题
data_blocks = [
(bpy.data.actions, "动作"),
(bpy.data.armatures, "骨骼"),
(bpy.data.cameras, "相机"),
(bpy.data.lights, "灯光"),
(bpy.data.meshes, "网格"),
(bpy.data.materials, "材质"),
(bpy.data.textures, "纹理"),
(bpy.data.images, "图片"),
(bpy.data.curves, "曲线"),
(bpy.data.lights, "灯光"),
(bpy.data.worlds, "世界"),
(bpy.data.collections, "集合")
]
# 循环清理每种类型的数据
for data_block, block_name in data_blocks:
try:
for item in data_block:
if item.users == 0:
data_block.remove(item)
else:
item.user_clear()
data_block.remove(item)
except Exception as e:
print(f"清理{block_name}时出错: {str(e)}")
# 强制执行垃圾回收
bpy.ops.outliner.orphans_purge(do_recursive=True)
import gc
gc.collect()
# 执行清理
clean_data_blocks()
if __name__ == "__main__":
parser = ArgumentParserForBlender()
parser.add_argument("--input_path", type=str, required=True)
parser.add_argument("--output_path", type=str, required=True)
parser.add_argument("--human_num", type=int, required=True)
parser.add_argument("--face_num", type=int, required=True)
parser.add_argument("--is_reduce_face", type=str, required=True)
args = parser.parse_args()
obj2glb(args.input_path, args.output_path, args.human_num, args.face_num, args.is_reduce_face)
Loading…
Cancel
Save