"""Run with Blender MCP. Separate cartoon rain gear for the rounded v10 sheep.""" import bpy, bmesh, math, json, struct from mathutils import Vector, Matrix ROOT='D:/project/sunrise' with bpy.data.libraries.load(ROOT+'/assets/characters/ivory-lamb-v10.blend',link=False) as (src,dst): dst.scenes=src.scenes reference=dst.scenes[0] reference.view_layers[0].update() scene=bpy.data.scenes.new('Cartoon rain gear') bpy.context.window.scene=scene def material(name,hexcolor): mat=bpy.data.materials.new(name); mat.use_nodes=True rgb=[int(hexcolor[i:i+2],16)/255 for i in (0,2,4)] rgb=[v/12.92 if v<.04045 else ((v+.055)/1.055)**2.4 for v in rgb] mat.diffuse_color=(*rgb,1) nodes=mat.node_tree.nodes; nodes.clear() out=nodes.new('ShaderNodeOutputMaterial'); node=nodes.new('ShaderNodeEmission') node.inputs['Color'].default_value=(*rgb,1) mat.node_tree.links.new(node.outputs[0],out.inputs['Surface']) return mat yellow=material('Raincoat yellow','E9BE69') red=material('Umbrella red','BE6264') cream=material('Umbrella ivory','F5EDDD') ink=material('SheepV2_Ink_Gear','67564C') def empty(name,parent=None): obj=bpy.data.objects.new(name,None);scene.collection.objects.link(obj);obj.parent=parent return obj def mesh(name,vertices,faces,mat,parent): data=bpy.data.meshes.new(name);data.from_pydata(vertices,[],faces);data.update() obj=bpy.data.objects.new(name,data);scene.collection.objects.link(obj) obj.parent=parent;data.materials.append(mat) return obj def stroke(name,points,radius,mat,parent): curve=bpy.data.curves.new(name,'CURVE');curve.dimensions='3D' curve.bevel_depth=radius;curve.bevel_resolution=3 spline=curve.splines.new('POLY');spline.points.add(len(points)-1) for p,co in zip(spline.points,points):p.co=(*co,1) obj=bpy.data.objects.new(name,curve);scene.collection.objects.link(obj);obj.parent=parent curve.materials.append(mat) bpy.ops.object.select_all(action='DESELECT');obj.select_set(True);bpy.context.view_layer.objects.active=obj bpy.ops.object.convert(target='MESH') return bpy.context.object def sphere(name,location,scale,mat,parent): bpy.ops.mesh.primitive_uv_sphere_add(segments=24,ring_count=12,location=location) obj=bpy.context.object;obj.name=name;obj.scale=scale;obj.parent=parent obj.data.materials.append(mat) bpy.ops.object.transform_apply(location=False,rotation=False,scale=True) return obj umbrella=empty('Umbrella') # The grip is in the raised right hoof; the tilted shaft centers the canopy above the head. grip=Vector((.58,-.40,.82)); top=Vector((0,-.05,3.03)) stroke('Umbrella shaft',[grip,top],.022,ink,umbrella) for sector in range(8): vertices=[];faces=[] for row in range(9): theta=(math.pi/2)*row/8 for col in range(5): a=(sector+col/4)*math.pi/4 vertices.append((1.28*math.sin(theta)*math.cos(a),-.05+1.28*math.sin(theta)*math.sin(a),2.53+.50*math.cos(theta))) for row in range(8): for col in range(4): i=row*5+col;faces.append((i,i+1,i+6,i+5)) panel=mesh('Canopy panel',vertices,faces,red if sector%2==0 else cream,umbrella) bpy.context.view_layer.objects.active=panel solid=panel.modifiers.new('Canopy thickness','SOLIDIFY');solid.thickness=.018 bpy.ops.object.modifier_apply(modifier=solid.name) a=sector*math.pi/4 stroke('Canopy rib',[(1.28*math.sin(t)*math.cos(a),-.05+1.28*math.sin(t)*math.sin(a),2.54+.50*math.cos(t)) for t in [i*math.pi/32 for i in range(17)]],.009,ink,umbrella) stroke('Canopy rim',[(1.28*math.cos(a),-.05+1.28*math.sin(a),2.53) for a in [i*math.tau/96 for i in range(97)]],.016,ink,umbrella) sphere('Umbrella tip',(0,-.05,3.055),(.04,.04,.07),ink,umbrella) sphere('Grip',grip,(.052,.052,.12),red,umbrella) coat=empty('Raincoat') for joint_name in ('Body','ArmLeft','ArmRight'): pivot=next(o for o in reference.objects if o.name.split('.')[0]==joint_name) source=next(o for o in reference.objects if o.parent==pivot and o.type=='MESH' and not o.name.startswith('Preview') and not o.get('sheepOutline')==False) data=source.data.copy(); bm=bmesh.new();bm.from_mesh(data) dark=[f for f in bm.faces if 'Ink' in source.data.materials[f.material_index].name] bmesh.ops.delete(bm,geom=dark,context='FACES') if joint_name=='Body': # Keep the torso component; do not turn the wool tail into a coat bubble. unseen=set(bm.verts);components=[] while unseen: pending=[unseen.pop()];component=set(pending) while pending: v=pending.pop() for edge in v.link_edges: n=edge.other_vert(v) if n in unseen:unseen.remove(n);component.add(n);pending.append(n) components.append(component) keep=max(components,key=len) bmesh.ops.delete(bm,geom=[v for v in bm.verts if v not in keep],context='VERTS') bm.to_mesh(data);bm.free() data.materials.clear();data.materials.append(yellow) anchor=empty('Coat'+joint_name,coat) if joint_name!='Body':anchor.matrix_local=pivot.matrix_world.copy() obj=bpy.data.objects.new('Raincoat '+joint_name,data);scene.collection.objects.link(obj);obj.parent=anchor transform=source.matrix_world if joint_name=='Body' else pivot.matrix_world.inverted()@source.matrix_world center=Vector((0,0,.68)) if joint_name=='Body' else Vector((.08 if joint_name=='ArmRight' else -.08,0,-.20)) for v in data.vertices: v.co=center+(transform@v.co-center)*1.075 if joint_name=='Body': # A short raincoat hem leaves both wool legs and hoof tips exposed. bm=bmesh.new();bm.from_mesh(data) bmesh.ops.bisect_plane(bm,geom=list(bm.verts)+list(bm.edges)+list(bm.faces), plane_co=(0,0,.42),plane_no=(0,0,1),clear_inner=True) boundary=[edge for edge in bm.edges if edge.is_boundary] bmesh.ops.holes_fill(bm,edges=boundary,sides=0) for v in bm.verts: flare=1+.06*max(0,1-(v.co.z-.42)/.25) v.co.x*=flare;v.co.y*=flare bm.to_mesh(data);bm.free() for p in data.polygons:p.material_index=0 # A folded hood behind the neck, plus simple front fasteners. sphere('CoatFoldedHood',(0,.48,1.18),(.38,.20,.17),yellow,coat) hood=empty('CoatHood',coat) head_pivot=next(o for o in reference.objects if o.name.split('.')[0]=='Head') hood.matrix_local=head_pivot.matrix_world.copy() vertices=[];faces=[] def hood_point(theta,phi): x=.98*math.sin(theta)*math.cos(phi) y=-.85*math.cos(theta) z=.02+.96*math.sin(theta)*math.sin(phi) z+=.12*math.exp(-(x/.22)**2-((theta-1.5)/.35)**2)*max(0,math.sin(phi))**8 return (x,y,z) for row in range(17): theta=1.12+(math.pi-1.12)*row/16 for col in range(49):vertices.append(hood_point(theta,col*math.tau/48)) for row in range(16): for col in range(48): i=row*49+col;faces.append((i,i+1,i+50,i+49)) hood_shell=mesh('Rain hood shell',vertices,faces,yellow,hood) bpy.context.view_layer.objects.active=hood_shell solid=hood_shell.modifiers.new('Hood fabric','SOLIDIFY');solid.thickness=.025 bpy.ops.object.modifier_apply(modifier=solid.name) stroke('Hood opening rim',[hood_point(1.12,i*math.tau/96) for i in range(97)],.025,yellow,hood) stroke('Coat placket',[(0,-.72,z) for z in (.47,.65,.90,1.07)],.012,ink,coat) for z in (.54,.77,1.00):sphere('Coat button',(0,-.735,z),(.032,.022,.032),cream,coat) for obj in scene.objects: if obj.type!='MESH':continue bm=bmesh.new();bm.from_mesh(obj.data);bmesh.ops.recalc_face_normals(bm,faces=list(bm.faces));bm.to_mesh(obj.data);bm.free() for p in obj.data.polygons:p.use_smooth=True obj.data.update() old=obj.data.attributes.get('_outline_offset') if old:obj.data.attributes.remove(old) offsets=obj.data.attributes.new('_outline_offset','FLOAT_VECTOR','POINT') for v,item in zip(obj.data.vertices,offsets.data):item.vector=v.normal*.012 def export(root,path): bpy.ops.object.select_all(action='DESELECT') root.select_set(True) for obj in root.children_recursive:obj.select_set(True) bpy.context.view_layer.objects.active=root bpy.ops.export_scene.gltf(filepath=path,use_selection=True,use_active_scene=True,export_format='GLB',export_yup=True,export_extras=True,export_attributes=True) data=open(path,'rb').read();size=struct.unpack_from('