Files
NODEDC_MISSION_CORE/tools/rover-scene/export_rover.py
T

71 lines
5.3 KiB
Python

"""Evaluated v020 geometry, semantic PBR groups and UVs; never saves the source."""
import bpy, json, hashlib
from collections import defaultdict
from pathlib import Path
from mathutils import Vector, Matrix
root=Path.cwd();source=Path(bpy.data.filepath)
# Linear colors. Painted black is a dielectric coating, not bare silver.
specs={
'body':('Корпус и подвеска',(.025,.028,.031),.12,.53),
'rubber':('Резина гусениц и катков',(.014,.017,.019),0,.72),
'track-steel':('Нержавеющие проставки',(.55,.58,.61),1,.17),
'box':('Бортовой ящик',(.034,.038,.042),.08,.40),
'molle':('MOLLE панель',(.085,.094,.105),.30,.44),
'motor':('Алюминий моторов',(.30,.32,.33),.68,.38),
'hardware':('Чёрный крепёж',(.018,.021,.024),.45,.38),
'polymer':('Пластик и кожухи',(.018,.022,.026),0,.44),
'drive':('Ведущие звёзды',(.22,.13,.038),0,.46),
}
materials={}
for key,(label,color,metal,rough) in specs.items():
m=bpy.data.materials.new('rover/'+key);m.use_nodes=True;bs=m.node_tree.nodes.get('Principled BSDF')
bs.inputs['Base Color'].default_value=(*color,1);bs.inputs['Metallic'].default_value=metal;bs.inputs['Roughness'].default_value=rough;materials[key]=m
def classify(name,obj):
n=name.lower();o=obj.lower()
if 'stamped clips' in n or 'inter-window clips' in o:return 'track-steel'
if 'molle' in n:return 'molle'
if 'motor | cast' in n:return 'motor'
if 'polyurethane' in n:return 'drive'
if any(k in n for k in ['rubber','gasket','elastomer','jacket','coated roller']):return 'rubber'
if any(k in n for k in ['case | textured','case | dark raised']):return 'box'
if any(k in n+' '+o for k in ['bolt','screw','washer','nut','lock steel','zinc','stainless','connector brass','dark steel','black oxide']):return 'hardware'
if any(k in n for k in ['polymer','asa','recess','cavities']):return 'polymer'
return 'body'
bpy.context.scene.frame_set(1);dg=bpy.context.evaluated_depsgraph_get();instances=[];assignment=defaultdict(set)
for inst in dg.object_instances:
o=inst.object;src=o.original;cols=[c.name for c in src.users_collection]
if o.type not in {'MESH','CURVE'} or src.hide_render:continue
if any(c.hide_render for c in src.users_collection) or any(any(t in c for t in ['STUDIO','BOOLEAN','DATUM']) for c in cols):continue
if not inst.is_instance and not src.visible_get():continue
mesh=bpy.data.meshes.new_from_object(o,preserve_all_data_layers=True,depsgraph=dg)
for idx in range(len(mesh.materials)):
name=mesh.materials[idx].name if mesh.materials[idx] else '';key=classify(name,src.name)
mesh.materials[idx]=materials[key];assignment[key].add(src.name)
if not mesh.materials:mesh.materials.append(materials['body']);assignment['body'].add(src.name)
instances.append((src.name,mesh,inst.matrix_world.copy()))
scene=bpy.data.scenes.new('Mission Core rover export');bpy.context.window.scene=scene;objects=[]
for name,mesh,matrix in instances:
o=bpy.data.objects.new(name,mesh);scene.collection.objects.link(o);o.matrix_world=matrix;objects.append(o)
bpy.context.view_layer.update()
lo=Vector((min((o.matrix_world@Vector(v))[i] for o in objects for v in o.bound_box) for i in range(3)))
hi=Vector((max((o.matrix_world@Vector(v))[i] for o in objects for v in o.bound_box) for i in range(3)))
shift=Vector((-(lo.x+hi.x)/2,-(lo.y+hi.y)/2,-lo.z))
for o in objects:o.matrix_world=Matrix.Translation(shift)@o.matrix_world
count=len(objects)
bpy.ops.object.select_all(action='SELECT');bpy.context.view_layer.objects.active=objects[0];bpy.ops.object.join()
# Separate by material keeps picking exact while avoiding 1,426 draw calls.
bpy.ops.object.mode_set(mode='EDIT');bpy.ops.mesh.select_all(action='SELECT');bpy.ops.mesh.separate(type='MATERIAL');bpy.ops.object.mode_set(mode='OBJECT')
for o in list(scene.objects):
bpy.ops.object.select_all(action='DESELECT');o.select_set(True);bpy.context.view_layer.objects.active=o
key=o.data.materials[0].name.removeprefix('rover/');o.name=specs[key][0]
for uv in list(o.data.uv_layers):o.data.uv_layers.remove(uv)
bpy.ops.object.mode_set(mode='EDIT');bpy.ops.mesh.select_all(action='SELECT');bpy.ops.uv.cube_project(cube_size=.2);bpy.ops.object.mode_set(mode='OBJECT')
bpy.ops.object.select_all(action='SELECT')
out=root/'apps/control-station/public/rover-scene/dcd006-v020.glb'
bpy.ops.export_scene.gltf(filepath=str(out),export_format='GLB',use_selection=True,use_active_scene=True,export_animations=False,export_cameras=False,export_lights=False,export_extras=False,export_yup=True,export_apply=False,export_texcoords=True,export_attributes=False,export_vertex_color='NONE')
manifest={'source':source.name,'source_sha256':hashlib.sha256(source.read_bytes()).hexdigest(),'model':'DCD-006 v020 complete rover','revision':'materials-v2','source_object_count':count,'render_mesh_count':len(scene.objects),'original_bounds':[list(lo),list(hi)],'dimensions_m':list(hi-lo),'translation_m':list(shift),'glb_bytes':out.stat().st_size,'glb_sha256':hashlib.sha256(out.read_bytes()).hexdigest(),'source_saved':False,'uv':'cube projection, 0.2 m','materials':{k:{'name':v[0],'baseColorLinear':v[1],'metalness':v[2],'roughness':v[3],'sourceObjects':sorted(assignment[k])} for k,v in specs.items()}}
(out.parent/'dcd006-v020.json').write_text(json.dumps(manifest,ensure_ascii=False,indent=2)+'\n')
print('ROVER EXPORT',out.stat().st_size,'bytes;',len(scene.objects),'material groups')