You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
192 lines
6.9 KiB
192 lines
6.9 KiB
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) |