# -*- coding: utf-8 -*- """国忠3D模型导出工具。3ds Max:脚本 > 运行脚本,选择此文件。 导出可见静态网格、基础颜色和贴图,不更改或保存原 MAX 场景。 文字请先转网格。特殊渲染材质请在上传前预览核对。 """ import os, json, math, base64, tempfile, traceback from pymxs import runtime as rt def export_scene(destination): warnings, nodes, cache = [], [], {} factor = 1.0 / float(rt.units.decodeValue('1mm')) def xyz(p): return [float(p.x)*factor, float(p.y)*factor, float(p.z)*factor] def color(c): return [max(0,min(1,float(c.r)/255)),max(0,min(1,float(c.g)/255)),max(0,min(1,float(c.b)/255))] def prop(m,names): for name in names: try: value=rt.getProperty(m,name) if value is not None:return value except:pass return None def material(m,fallback): if m is None:return {'color':fallback} try:key=int(rt.getHandleByAnim(m)) except:key=str(m.name) if key in cache:return cache[key] try:subs=list(m.materialList) except:subs=[] if subs: result={'submaterials':[material(x,fallback) for x in subs]} cache[key]=result return result result={'name':str(m.name),'color':fallback} c=prop(m,['diffuse','base_color','baseColor']) try:result['color']=color(c) except:warnings.append(str(m.name)+':使用构件颜色,请核对材质') mp=prop(m,['diffuseMap','base_color_map','baseColorMap']) enabled=prop(m,['diffuseMapEnable','base_color_map_on']) if mp is not None and enabled is not False: try: with tempfile.TemporaryDirectory(prefix='guozhong-texture-') as folder: bitmap=rt.renderMap(mp,size=rt.Point2(2048,2048),filter=True) filename=os.path.join(folder,'texture.png') bitmap.filename=filename rt.save(bitmap);rt.close(bitmap) with open(filename,'rb') as f:result['texture']='data:image/png;base64,'+base64.b64encode(f.read()).decode('ascii') result['color']=[1,1,1] except:warnings.append(str(m.name)+':贴图烘焙失败,已保留基础颜色') cache[key]=result return result faces_total=0 for obj in rt.geometry: if obj.isHidden:continue mesh=rt.snapshotAsMesh(obj) if mesh.numfaces==0:continue faces_total+=int(mesh.numfaces) if faces_total>300000:raise RuntimeError('超过30万三角面,请简化模型后再导出。') n={'id':len(nodes),'name':str(obj.name),'vertices':[xyz(rt.getVert(mesh,i)) for i in range(1,mesh.numverts+1)],'faces':[],'materialIds':[],'material':material(obj.material,color(obj.wirecolor))} for i in range(1,mesh.numfaces+1): f=rt.getFace(mesh,i);n['faces'].append([int(f.x)-1,int(f.y)-1,int(f.z)-1]);n['materialIds'].append(int(rt.getFaceMatID(mesh,i))-1) if mesh.numtverts: n['uvs']=[[float(rt.getTVert(mesh,i).x),float(rt.getTVert(mesh,i).y)] for i in range(1,mesh.numtverts+1)] n['uvfaces']=[] for i in range(1,mesh.numfaces+1): f=rt.getTVFace(mesh,i);n['uvfaces'].append([int(f.x)-1,int(f.y)-1,int(f.z)-1]) nodes.append(n) if not nodes:raise RuntimeError('没有可见的网格构件。文字或样条请先转为网格。') if len(nodes)>300:raise RuntimeError('构件超过300个,请适当合并,以便手机流畅查看。') lo=[min(v[i] for n in nodes for v in n['vertices']) for i in range(3)] hi=[max(v[i] for n in nodes for v in n['vertices']) for i in range(3)] center=[(lo[0]+hi[0])/2,(lo[1]+hi[1])/2,lo[2]] for n in nodes:n['vertices']=[[round((v[0]-center[0])/1000,8),round((v[2]-center[2])/1000,8),round(-(v[1]-center[1])/1000,8)] for v in n['vertices']] data={'format':'guozhong-3d-v1','name':os.path.splitext(os.path.basename(destination))[0],'unit':'mm','dimensions':[hi[i]-lo[i] for i in range(3)],'nodes':nodes,'warnings':list(set(warnings))} payload=json.dumps(data,ensure_ascii=False,separators=(',',':'),allow_nan=False).encode('utf-8') if len(payload)>20*1024*1024:raise RuntimeError('文件超过20 MB,请减少贴图或简化模型。') with open(destination,'wb') as f:f.write(payload) return data if __name__=='__main__': destination=os.environ.get('GZ3D_EXPORT_PATH') batch=bool(destination) if not destination:destination=rt.getSaveFileName(caption='导出国忠手机模型',types='国忠3D模型 (*.gz3d)|*.gz3d|',filename='我的模型.gz3d') if destination: try: if not str(destination).lower().endswith('.gz3d'):destination=str(destination)+'.gz3d' result=export_scene(str(destination)) message='导出完成!\n'+str(destination)+'\n整体尺寸:'+' × '.join(str(round(x,1)) for x in result['dimensions'])+' 毫米\n请登录平台,上传并核对预览。' if result['warnings']:message+='\n需要核对:\n'+'\n'.join(result['warnings']) if batch:print(message) else:rt.messageBox(message,title='国忠3D建模算量工作室') except Exception as exc: if batch:raise rt.messageBox('导出未完成:\n'+str(exc),title='国忠3D导出工具')