尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

从PDF到自动化:三维模型制作规范落地检查实践

从PDF到自动化:三维模型制作规范落地检查实践 简介这份三维模型制作规范以PDF电子文档形式收录面向使用3ds Max进行建筑与场景建模的模型师、数字城市项目成员和游戏美术人员提供从软件版本、单位设置到贴图输出的一整套可执行标准。内容包括总体要求、模型制作注意事项、建筑局部标准尺寸以及贴图制作规范重点强调删除重叠面、控制网格分段、避免破面闪烁并给出居民楼层高、女儿墙、台阶、门高等具体数值。贴图部分详细规定tif格式、尺寸需为2的幂次方、最大不超过512像素、文字贴图与栏杆Alpha通道制作方法可直接用于项目内部质检与新人快速上手。包内为单个PDF文件大小约1.93MB无需安装即可查阅已有301人浏览学习适合需要统一建模标准、减少返工并提升场景表现效率的团队或个人参考。1. 当一份规范 PDF 被挂到墙上模型就开始失控了多数团队都有一份《三维模型制作规范.pdf》写法大同小异先讲单位用米、轴向用 Y 向上再讲不允许多边面、材质要按「类型_部位_用途」命名最后附几张截图和两张表格。文件躺在共享盘里新人入职读一遍过后再没人看。等到外包模型进引擎光照炸了、面数超限、骨骼名字对不上翻出 PDF 才发现规范里其实都写了——只是没人把文字变成能落地的约定。问题不在规范本身而在规范的表达方式。文字和截图天然适合描述意图不适合约束行为。一份能真正管住外包、拦截返工、让程序和美术不吵架的规范必须拆成「可量化的字段、可脚本检查的阈值、可自动生成的命名约束」。这篇文章就以「三维模型制作规范.pdf」为起点讲怎么把文档里的每一句话拆成能执行的检查规则再写成脚本挂到 DCC 工具或 CI 流程上。适用对象是技术美术、DCC 工具开发者以及被模型质量逼到想做工具链的美术组长。2. 把规范文档拆成可量化字段再落成数据格式2.1 一份规范 PDF 通常包含哪几类内容先遍历一遍手头的规范 PDF剔除掉「要有美感」「注意氛围」这类主观条款剩下能约束的几乎全落在六个维度上拓扑结构、单位与坐标系、命名规则、材质与贴图、层级与节点、导出格式。每个维度背后对应的是下游工具会真实报错或失真的事。比如拓扑里的多边面进了引擎的碰撞网格生成或 UV 展开算法里轻则警告重则崩单位不一致模型从 Maya 导到 UE 会直接放大 100 倍命名不遵守程序那边写死的查找节点路径全部失效。常见做法是把每个维度拆成检查项每个检查项带四个属性检查目标哪个文件或哪个节点、判定条件正则或数值比较、错误级别Error / Warning / Info、修复建议人工处理或脚本自动修。比如「不允许出现四边以上的多边形」判定条件就是「三角面数 ! 0 且四边面数 ! 0」错误级别是 Error修复建议是「选中该网格使用 Mesh Cleanup 或 Quadrangulate」。这样写出来的规范不再是段落而是一张张检查表后续写自动化工具时只需要把表里的每一行翻译成代码。2.1.1 允许和禁止之间要留 Warning 的缓冲把每一条规则都设成 Error 的规范活不过第一周。实际项目里遇到的模型肯定有例外高模转低模的接缝处偶尔出现五边面是正常的完全禁止反而逼美术把所有五边面硬塞成多边形顶点数量上去了效果没变好。所以拆规范时要分级。直接影响引擎加载、物理碰撞、骨骼绑定的规则进 Error影响质量但可后续处理或需要人工把关的进 Warning纯风格建议比如保持布线均匀进 Info不在自动检查里拦截。拿我之前处理过的建筑可视化外包流程举例。规范里有一条「模型原点必须在物体底部中心」这在组装阶段是一条硬规则因为程序要用原点对齐放置建筑物。但有的构件是弧形面片底部中心数值是近似值硬设成 Error 会导致一批本来能用的模型被 AI 拒收。最后改法是把「原点在底部中心」设成 Warning同时加一条下游约束「项目原点必须与 CAD 底图重合」为 Error两套规则配合才没有把外包逼疯。2.2 把 PDF 拆成 YAML 检查清单让规则本身可版本化规范文档的第二个问题是更新难追踪。PDF 里改一个阈值新旧两版混在一起外包可能按旧版做、组长按新版审往返耗一周。更工程化的做法是让规范文档退居为「解释性文本」真正被执行的是一个 YAML 或 JSON 格式的检查清单。清单文件进 Git每次修改都有 diff 记录谁改了什么阈值一目了然。美术和 TA 看的还是 PDF 的解释但脚本读的是 YAML。rules: - id: TOPO_003 name: 不允许多边面 target: mesh condition: type: face_count allowed: [tri, quad] level: Error message: 检测到 {count} 个多边面位于 {mesh_name} auto_fix: 选中该网格执行 Mesh Cleanup勾选 Faces with more than 4 sides - id: UNIT_001 name: 单位必须为米 target: scene condition: type: linear_unit expected: meter level: Error message: 场景单位不是米当前为 {unit}这套 YAML 的好处是检查逻辑和规则数据分离。写流程代码的人只需要实现一个通用的规则解释器项目组想加规则时直接改 YAMLTA 不用为每一条新规则重写一段检查函数。常见的执行方式是每个字段对应 Python 里的一个校验函数族比如face_count对应遍历网格面的函数linear_unit对应读取文件头信息的函数。规则多了以后还可以给每个id建索引检查报告里按id聚合错误问题发散到某个外包商时只看 TOPO_ 前缀的统计就够了。2.2.1 阈值设定要参考引擎和 DCC 的真实上限拆字段时容易犯的错是照抄引擎文档的硬上限。UE 顶点缓存最大是 65536 个顶点16 位索引于是规范写成「单模型顶点数不得超过 65536」。真这么做的话一批合理的场景模型都会挂掉因为顶点数爆的是单网格、单材质不是整个模型。正确做法是把单位统一然后留 20% 余量。比如「单网格顶点数不超过 60000单网格三角面不超过 100000」。这个阈值落在引擎跑得动、美术又不至于频繁触线的位置。单位规则也要配合检查脚本的执行时机。有的团队只在导出前跑一次检查但模型在制作中就可能改了单位。我一般建议给 DCC 工具加一个文件保存时自动触发的钩子保存前跑一遍 Unit 规则的快速检查只读文件头不遍历几何体全场景遍历留给提交前的完整检查。这样规则拆得细但执行时有轻重缓急团队才不会因为每次保存都卡几秒而偷偷禁用插件。3. 用 Python 把规范字段变成可执行的模型检查脚本3.1 从读文件元数据开始单位、轴、命名的检查规范落地的第一步是让脚本能打开模型文件并读出关键信息。无论模型存在 Maya 场景.ma/.mb、FBX 还是 glTF 里Python 生态里都有对应的库。最常见的组合是trimesh读几何、fbx或assimp读格式、re做命名匹配。先实现最小可用版本输入一个文件路径输出一个包含单位、轴、面数、顶点数、网格名的 JSON 报告。import json import re import trimesh def inspect_model(filepath): scene trimesh.load(filepath, forceNone) report { file: filepath, unit: meter if scene.units m else str(scene.units), up_axis: detect_up_axis(scene), mesh_count: len(scene.geometry) if hasattr(scene, geometry) else 1, vertices: 0, faces: 0, bad_faces: [], meshes: [], } geoms scene.geometry.values() if hasattr(scene, geometry) else [scene] for g in geoms: report[vertices] len(g.vertices) report[faces] len(g.faces) # 找出多边形数 4 的面 if hasattr(g, faces) and len(g.faces) 0: face_sizes [len(f) for f in g.faces] report[bad_faces].extend( [i for i, s in enumerate(face_sizes) if s 4] ) report[meshes].append({ name: g.metadata.get(name, unnamed), vertices: len(g.vertices), faces: len(g.faces), }) return report def detect_up_axis(scene): # 常见的 Y-up 或 Z-up 判定读 scene 的 up_axis 元数据无则默认 Y return scene.metadata.get(up_axis, Y)这段代码检查了三类高频违规项。scene.units对应 YAML 里的 UNIT_001bad_faces对应 TOPO_003。注意trimesh在加载某些早期版本 FBX 时会把单位识别成毫米因为它读的是文件头里UnitScaleFactor字段这个字段在不少 DCC 工具里默认是1.0而不是明确的100.0单位检查的误报率主要来自这里。如果发现误报可以在 read 时传forcefbx让解析器走专用通道再自己读UnitScaleFactor做换算。命名检查是另一个高频需求做法是对物体名做正则匹配def check_naming(mesh_name, pattern): if re.match(pattern, mesh_name): return True return False PATTERNS { SM_: r^SM_[A-Z]{2,5}_[a-zA-Z0-9_]$, # 静态网格 SK_: r^SK_[A-Z]{2,5}_[a-zA-Z0-9_]$, # 骨骼网格 UI_: r^UI_[a-zA-Z0-9_]\.(png|jpg|tga)$, # UI 贴图 } mesh_name SM_Props_Barrel_01 print(check_naming(mesh_name, PATTERNS[SM_])) # True命名的正则建议放在 YAML 规则表里而不是写死在代码中因为不同项目的前缀体系差别很大。有的项目要求SM_后面跟资产类型再跟关卡名再跟编号有的项目允许不加类型直接写SM_House。这些差异不应该靠 TA 改代码来适应。我在规则表里加一个pattern字段检查脚本启动时读 YAML 并编译所有 pattern匹配失败的网格输出到命名违规清单由人工决定是改名字还是改规则。3.2 把 YAML 规则表翻译成循环检查器写完单个检查函数下一步是把规则表驱动起来。规则解释器的核心设计是注册表模式每个condition.type对应一个注册好的检查函数规则表新增条目时解释器用类型名查找函数并执行。import yaml # 检查函数注册表 CHECKERS {} def register(condition_type): def decorator(fn): CHECKERS[condition_type] fn return fn return decorator register(face_count) def check_face_count(mesh, rule): allowed rule[condition][allowed] bad [] if hasattr(mesh, faces): for i, f in enumerate(mesh.faces): if (len(f) 3 and tri not in allowed) or \ (len(f) 4 and quad not in allowed) or \ (len(f) 4): bad.append(i) return bad register(linear_unit) def check_unit(scene, rule): expected rule[condition][expected] current getattr(scene, units, None) return [] if current expected else [{unit: current}] def run_rules(filepath, rules_yaml): with open(rules_yaml, r, encodingutf-8) as f: rules yaml.safe_load(f)[rules] report inspect_model(filepath) issues [] for rule in rules: checker CHECKERS.get(rule[condition][type]) if not checker: continue for mesh in report[meshes]: result checker(mesh, rule) if result: issues.append({ id: rule[id], level: rule[level], message: rule[message].format( countlen(result), mesh_namemesh[name], ), auto_fix: rule.get(auto_fix, ), }) return issues if __name__ __main__: issues run_rules(models/SM_Props_Barrel_01.fbx, model_rules.yaml) for item in issues: print(f[{item[level]}] {item[id]}: {item[message]})这段代码把「读规则表 → 遍历模型 → 收集违规」串成了完整链路。注意run_rules里每个规则都对所有网格跑一遍规则多了之后是 O(规则数 × 网格数) 的复杂度。对单个模型来说没压力但要批量跑整个关卡时就该考虑用functools.lru_cache把inspect_model的结果做成缓存或者改成先导出所有模型文件再并行检查。参数说明要留意rule[message].format(count...)这行——YAML 规则里的{count}占位符必须和代码里传入的kwarg名字对得上。auto_fix字段目前只是返回建议文本往下走可以扩展成真正的自动修复脚本比如多边面直接调用mesh.merge_vertices()或导出前的清理函数但自动修复有风险一般只处理顶点合并这类无损操作涉及拓扑重拓的操作绝不自动执行。3.3 报告输出不只是打日志按级别聚合和导出脚本输出到命令行只能满足自查。多人在一个项目里工作时检查结果应该汇总成统一格式的报告方便管理层和外包对接。常见的做法是在run_rules的结果上按level做聚合统计再输出成 Markdown 或 JSON 文件。Markdown 适合直接贴到团队群JSON 适合被 CI 系统消费。还有一个重要细节是输出结果要带模型文件的相对路径而不是网格名。因为不同文件的网格名可能重名只看网格名会让美术找不到具体是哪个文件出了问题。def aggregate_report(all_file_issues): stats {Error: 0, Warning: 0, Info: 0} by_file {} for file_result in all_file_issues: for issue in file_result: stats[issue[level]] 1 by_file.setdefault(issue[id], []).append(issue) return { stats: stats, top_issues: sorted( by_file.items(), keylambda x: len(x[1]), reverseTrue, )[:10], }top_issues按出现次数排序后能快速找出外包团队最容易踩的坑。比如统计后发现 TOPO_003 出现 200 次、UNIT_001 出现 5 次那下周的对接会上就该重点讲拓扑清理而不是反复强调单位。报告缓存的时机也值得注意不要每次打开文件都跑全量规则把最近一次的报告缓存为.report.json只有模型文件变更才重新生成这样编辑器集成时的体验会好很多。4. 把检查脚本挂到 Maya 插件和 CI 流程里让规范自动卡人4.1 Maya 内嵌检查面板保存时卡一次、提审前卡一次脚本写好后如果不进美术的工作环境就仍然只是一堆命令行工具。Maya 是很多团队的主战场所以要做的是把这些规则包装成 Maya 菜单或窗口。常见做法是用maya.cmds写一个轻量 UI上面两个按钮「快速检查」和「提交检查」。快速检查只跑单位、命名、文件路径合法性这几条在保存时由scriptJob自动触发提交检查则跑全部规则并生成 Markdown 报告导出到项目里约定的提交目录。from maya import cmds, mel def run_quick_check(): filepath cmds.file(queryTrue, sceneNameTrue) if not filepath: return # 只跑快速规则避免阻塞保存 issues run_rules(filepath, model_rules_quick.yaml) if any(i[level] Error for i in issues): result cmds.confirmDialog( title规范检查未通过, message存在 Error 级问题是否仍然保存, button[保存, 取消], defaultButton取消, cancelButton保存, dismissString保存, ) if result 取消: mel.eval(catchCancel) for issue in issues: print(f[{issue[level]}] {issue[id]}: {issue[message]}) cmds.scriptJob(event[SceneSaved, run_quick_check])这段代码里scriptJob是 Maya 的核心机制。event[SceneSaved, run_quick_check]表示场景保存动作完成后自动触发run_quick_check函数。注意这里用的是SceneSaved而不是SceneSave区别是前者在保存后触发后者在保存前触发——如果要在保存前拦截需要把cmds.confirmDialog的判断逻辑提到保存动作之前但那样会导致 Maya 原生保存流程被截断。我一般建议用「保存后提示 允许撤销」的流程而不是「阻止保存」因为被硬拦截的美术会想尽办法绕过检查比如直接改脚本。catchCancel这段 mel 是 Maya 在处理取消事件时防止报错的标准写法。实际部署时还要处理一个边界首次打开场景、还没有保存路径时cmds.file(queryTrue, sceneNameTrue)返回空字符串代码里的if not filepath: return就是为此加的保护。4.2 提审目录加文件监听检查报告自动生成到约定位置保存时触发是一道防线提交到提审目录是第二道防线。不少团队规定外包交付时把模型放到约定的共享目录然后由 TA 人工跑一遍检查再放行。这个流程可以自动化用一个 Python 脚本监听目录检测到新文件或修改时间变化的文件后对该文件跑完整检查把报告写到和模型同名的.md文件。import os import time WATCH_DIR r//server/share/incoming REPORT_EXT .md def handle_new_file(filepath): if not filepath.lower().endswith((.fbx, .obj, .gltf, .glb)): return issues run_rules(filepath, model_rules_full.yaml) report_path os.path.splitext(filepath)[0] REPORT_EXT with open(report_path, w, encodingutf-8) as f: f.write(f# 模型规范检查报告\n\n) f.write(f文件{filepath} \n) f.write(f检查时间{time.strftime(%Y-%m-%d %H:%M:%S)} \n\n) f.write(## 问题列表\n\n) for issue in issues: f.write(f- [{issue[level]}] {issue[id]} {issue[message]}\n) if not issues: f.write(无违规项。\n) def watch_incoming(): seen set() while True: for root, _, files in os.walk(WATCH_DIR): for fn in files: if not fn.lower().endswith((.fbx, .obj, .gltf, .glb)): continue filepath os.path.join(root, fn) mtime os.path.getmtime(filepath) key (filepath, mtime) if key not in seen: seen.add(key) handle_new_file(filepath) time.sleep(30)这种轮询方案实现简单但要注意两个坑。一个是seen集合只存了(路径, 修改时间)文件被多次覆盖且修改时间不同会重复生成报告。另一个是网络驱动器遍历的性能——外包同时交付几十个文件时每个文件跑一次全量检查可能让脚本卡几分钟建议加线程池来控制并发。生产级做法其实是用watchdog库的Observer做增量监听但在网络盘上watchdog常因文件锁定事件丢事件轮询反而是更稳的办法。4.3 在自动化构建里用命令行跑检查彻底解放人工比目录监听更进一步的是把规范检查嵌入自动化构建的一部分。团队使用 Jenkins 或 GitLab CI 时可以加一个阶段专门跑模型规范检查。步骤一般是从制品库拉取最新模型包 → 解压 → 遍历所有模型文件 → 执行检查脚本 → 把报告归档 → 根据 Error 数量决定构建是否失败。stages: - validate validate_models: stage: validate script: - python scripts/validate_models.py --input ./models --output ./report - python scripts/gate_check.py --report ./report/aggregated.json --max_error 0 artifacts: paths: - report/ when: alwaysgate_check.py的逻辑是读聚合报告统计 Error 数量超过阈值就返回非零退出码CI 流程即判定为失败。这里--max_error加了个缓冲空间小项目可以设 0大项目刚接入规范时可以先设 20逐步收紧。另一个细节是artifacts里when: always——即使构建失败也要保留报告不然美术看不到失败原因。管线类 CI 的失败要定位到具体文件聚合报告里除了规则 ID 和错误信息最好附上os.path.relpath计算出的相对路径方便开发者在制品库里直接跳转定位。5. 规范文档的「接口化」从死 PDF 变成可以查询的数据源整套流程跑通后回看最开始那份《三维模型制作规范.pdf》会发现它已经不是一个读完就忘的文本而是一个可以被程序读取、被 CI 拦截、被团队协作的工具。此时最后一个值得做的事是把规范升格为「可查询的接口」。做法有二。一是给规范里的每一条规则配上唯一的 ID如 TO_PO、UNI_T这个 ID 在 YAML 里、检查脚本里、报告输出里、Maya 插件提示里都保持一致。美术看到检查报告里的TOPO_003可以直接到 PDF 的附录里搜这个 ID 找到详细解释和修复截图。这样规范和检查工具互为索引AI 生成的建议和人工编写的说明在同一个编号体系下对齐。二是把规范版本号和检查脚本版本绑定。model_rules.yaml的头部加version字段脚本运行时输出这个版本号报告文件名也带上版本如report_v3.2.md。当收到外包反馈「我按规范做了为什么报错」第一件事就是核对双方版本是否一致九成问题都是外包手上的 PDF 是旧版、YAML 规则已经更新。在我经历的合作流程里靠这个版本对齐手段把返工沟通次数直接从每周 5 次降到了每月 1 次。最后把规范中常用的几个阈值做成常量写在 YAML 的constants段里规则条目直接引用常量名避免同一数值在十条规则里各写一遍漏改一处就产生一次线上事故。本文还有配套的精品资源点击获取
返回列表