import bpy, os, json, math, struct from mathutils import Matrix, Vector from mathutils.bvhtree import BVHTree OUT=os.path.dirname(os.path.abspath(__file__)) DEST=os.path.abspath(os.path.join(OUT,'../dist')) bpy.ops.wm.open_mainfile(filepath=os.path.join(OUT,'holiday_ghost_vtuber_rig.blend')) rig=bpy.data.objects['RIG | pose in Pose Mode'] rig.matrix_world=Matrix.Identity(4) for prop in ['Head Yaw','Head Pitch','Head Roll','Gaze X','Gaze Y','Blink L','Blink R']:rig[prop]=0.0 rig.update_tag();bpy.context.view_layer.update() sheet=bpy.data.objects['Ghost sheet | continuous quad drape'] # Independent deformation rings spread the head's pull over the entire drape. # The lowest ring stays planted; the top blends into the existing head bone. ring_heights=[.12,.50,.90,1.25,1.60,1.85] bpy.ops.object.select_all(action='DESELECT');rig.select_set(True);bpy.context.view_layer.objects.active=rig bpy.ops.object.mode_set(mode='EDIT') for i,z in enumerate(ring_heights): b=rig.data.edit_bones.new('sheet.%02d'%i) b.head=(.14*(z/3.02)**1.3,0,z);b.tail=b.head+Vector((0,0,.14)) b.parent=rig.data.edit_bones['root'] bpy.ops.object.mode_set(mode='OBJECT') for g in list(sheet.vertex_groups):sheet.vertex_groups.remove(g) ring_names=['sheet.%02d'%i for i in range(len(ring_heights))]+['head'] groups=[sheet.vertex_groups.new(name=n) for n in ring_names] levels=ring_heights+[2.05] for v in sheet.data.vertices: z=v.co.z if z<=levels[0]:groups[0].add([v.index],1,'REPLACE');continue if z>=levels[-1]:groups[-1].add([v.index],1,'REPLACE');continue i=next(i for i in range(len(levels)-1) if levels[i]<=z=levels[-1]:fgroups[-1].add([v.index],1,'REPLACE');continue i=next(i for i in range(len(levels)-1) if levels[i]<=z3000: for i in range(len(o.data.splines)-1,-1,-1): if i%8:o.data.splines.remove(o.data.splines[i]) o.data.bevel_resolution=0;o.data.resolution_u=2 if o.type=='MESH': report['objects'].append({'name':o.name,'vertices':len(o.data.vertices)}) for mod in o.modifiers: if mod.type=='SUBSURF':mod.levels=0;mod.render_levels=0 # Real-time PBR interpretation of the procedural Cycles materials. for slot in o.material_slots: m=slot.material if not m:continue if m.get('realtime_prepared'):continue old=m.node_tree.nodes if m.use_nodes else [] bs=next((n for n in old if n.type=='BSDF_PRINCIPLED'),None) base=tuple(bs.inputs['Base Color'].default_value) if bs else tuple(m.diffuse_color) rough=bs.inputs['Roughness'].default_value if bs else .5 metal=bs.inputs['Metallic'].default_value if bs else 0 name=m.name.lower() if 'ivory' in name or 'cotton' in name or 'sheet' in name:base=(.81,.755,.64,1);rough=.9 if 'hat' in name and ('cloth' in name or 'red' in name or 'felt' in name):base=(.37,.009,.017,1);rough=.88 if 'eye' in name:base=(.006,.004,.007,1);rough=.47 m.use_nodes=True;m.node_tree.nodes.clear() p=m.node_tree.nodes.new('ShaderNodeBsdfPrincipled');p.inputs['Base Color'].default_value=base;p.inputs['Roughness'].default_value=rough;p.inputs['Metallic'].default_value=metal output=m.node_tree.nodes.new('ShaderNodeOutputMaterial');m.node_tree.links.new(p.outputs['BSDF'],output.inputs['Surface']) m.diffuse_color=base;m['realtime_prepared']=True os.makedirs(DEST,exist_ok=True) bpy.ops.object.select_all(action='DESELECT') for o in objects:o.select_set(True) bpy.context.view_layer.objects.active=rig bpy.ops.export_scene.gltf(filepath=os.path.join(DEST,'ghost.glb'),export_format='GLB',use_selection=True,export_apply=False,export_animations=False,export_skins=True,export_morph=True,export_yup=True,export_extras=True) with open(os.path.join(DEST,'ghost.glb'),'rb') as f: f.seek(12);n,t=struct.unpack('