Files
sunrise/scripts/create-red-sheep.py

122 lines
5.6 KiB
Python

"""Run through Blender MCP. Separate scene, four colors, articulated rigid parts."""
import bpy, math, os
from mathutils import Vector
ROOT = 'D:/project/sunrise'
scene = bpy.data.scenes.new('Red Sheep Character')
bpy.context.window.scene = scene
palette = {'Red': 'D94B3D', 'Cream': 'FFF0D6', 'Ink': '352724', 'Gold': 'EAAF45'}
mats = {}
for name, color in palette.items():
m = bpy.data.materials.new('Sheep_' + name)
m.use_nodes = True
rgb = [int(color[i:i+2], 16) / 255 for i in (0, 2, 4)]
linear = [v / 12.92 if v < .04045 else ((v + .055) / 1.055) ** 2.4 for v in rgb]
m.diffuse_color = (*linear, 1)
node = next(n for n in m.node_tree.nodes if n.type == 'BSDF_PRINCIPLED')
node.inputs['Base Color'].default_value = (*linear, 1)
node.inputs['Roughness'].default_value = 1
mats[name] = m
def empty(name, loc, parent=None):
obj = bpy.data.objects.new(name, None)
scene.collection.objects.link(obj)
obj.parent = parent
obj.location = loc
return obj
root = empty('RedSheep', (0, 0, 0))
parts = {}
def ball(name, loc, scale, color, parent=root):
bpy.ops.mesh.primitive_uv_sphere_add(segments=20, ring_count=12, location=loc)
obj = bpy.context.object
obj.name = name
obj.scale = scale
bpy.ops.object.transform_apply(location=False, rotation=False, scale=True)
obj.data.materials.append(mats[color])
for p in obj.data.polygons: p.use_smooth = True
obj.parent = parent
parts.setdefault(parent, []).append(obj)
return obj
def stroke(name, coords, radius, color, parent=root):
curve = bpy.data.curves.new(name, 'CURVE')
curve.dimensions = '3D'
curve.bevel_depth = radius
curve.bevel_resolution = 2
spline = curve.splines.new('POLY')
spline.points.add(len(coords)-1)
for point, xyz in zip(spline.points, coords): point.co = (*xyz, 1)
obj = bpy.data.objects.new(name, curve)
scene.collection.objects.link(obj)
obj.data.materials.append(mats[color])
bpy.context.view_layer.objects.active = obj
obj.select_set(True)
bpy.ops.object.convert(target='MESH')
obj = bpy.context.object
obj.parent = parent
parts.setdefault(parent, []).append(obj)
obj.select_set(False)
return obj
body = empty('Body', (0, 0, 0), root)
ball('Wool body', (0, 0, 1.25), (.55, .37, .58), 'Red', body)
for x in (-.32, .32): ball('Hip wool', (x, 0, .96), (.28, .32, .29), 'Red', body)
ball('Tail', (0, .42, 1.02), (.22, .19, .22), 'Red', body)
for side, x in [('Left', -.27), ('Right', .27)]:
leg = empty('Leg' + side, (x, 0, .85), root)
ball('Cream leg', (0, 0, -.35), (.18, .18, .36), 'Cream', leg)
ball('Hoof', (0, -.055, -.70), (.21, .24, .15), 'Ink', leg)
arm = empty('Arm' + side, (x / .27 * .52, 0, 1.65), root)
ball('Sleeve', (x / .27 * .065, 0, -.18), (.21, .22, .27), 'Red', arm)
ball('Forearm', (x / .27 * .12, -.02, -.40), (.145, .15, .23), 'Cream', arm)
ball('Hand', (x / .27 * .14, -.04, -.58), (.17, .17, .17), 'Ink', arm)
head = empty('Head', (0, 0, 2.38), root)
ball('Head wool', (0, 0, 0), (.66, .43, .56), 'Red', head)
ball('Cream face', (0, -.315, -.05), (.47, .20, .39), 'Cream', head)
for x,z,s in [(-.44,.28,.25),(-.23,.46,.26),(0,.50,.27),(.25,.44,.25),(.46,.25,.24),(-.52,-.22,.19),(.52,-.22,.19)]:
ball('Wool tuft', (x, -.10, z), (s, .33, s), 'Red', head)
for sign in (-1, 1):
ear = ball('Ear', (sign*.70, -.035, -.12), (.30, .12, .15), 'Cream', head)
ear.rotation_euler.y = sign * .35
ball('Horn', (sign*.64, -.06, .25), (.24, .25, .27), 'Ink', head)
spiral = []
for i in range(32):
t = i / 31 * math.pi * 1.7
r = .16 - i / 31 * .11
spiral.append((sign*(.64 + math.cos(t)*r), -.305, .25+math.sin(t)*r))
stroke('Horn curl', spiral, .022, 'Cream', head)
ball('Eye', (sign*.18, -.503, -.035), (.048, .025, .065), 'Ink', head)
ball('Nose', (0, -.522, -.16), (.048, .025, .03), 'Ink', head)
stroke('Mouth', [(-.11,-.51,-.21),(-.075,-.527,-.24),(-.035,-.535,-.235),(0,-.537,-.20),(.035,-.535,-.235),(.075,-.527,-.24),(.11,-.51,-.21)], .015, 'Ink', head)
# Small flat sunrise badge, facing -Y.
verts = [(0, -.374, 1.43)] + [(.13*math.cos(i*math.pi/16), -.374, 1.43+.13*math.sin(i*math.pi/16)) for i in range(17)]
mesh = bpy.data.meshes.new('Sun badge')
mesh.from_pydata(verts, [], [(0,i+1,i+2) for i in range(16)])
badge = bpy.data.objects.new('Sun badge', mesh)
scene.collection.objects.link(badge)
badge.data.materials.append(mats['Gold'])
badge.parent = body
parts[body].append(badge)
for a in (0, math.pi/4, math.pi/2, 3*math.pi/4, math.pi):
stroke('Sun ray', [(r*math.cos(a), -.378, 1.43+r*math.sin(a)) for r in (.19,.25)], .018, 'Gold', body)
# Merge static pieces per joint so the runtime only needs six meshes plus outlines.
for pivot, objects in parts.items():
bpy.ops.object.select_all(action='DESELECT')
for obj in objects: obj.select_set(True)
bpy.context.view_layer.objects.active = objects[0]
bpy.ops.object.join()
obj = bpy.context.object
obj.name = pivot.name + 'Mesh'
# Joining preserves each mesh in the pivot's local coordinate system.
bpy.ops.object.select_all(action='DESELECT')
for obj in scene.objects: obj.select_set(True)
bpy.context.view_layer.objects.active = root
os.makedirs(ROOT + '/public/characters', exist_ok=True)
os.makedirs(ROOT + '/assets/characters', exist_ok=True)
bpy.ops.export_scene.gltf(filepath=ROOT+'/public/characters/red-sheep.glb', use_selection=True, use_active_scene=True, export_format='GLB', export_yup=True)
bpy.ops.wm.save_as_mainfile(filepath=ROOT+'/assets/characters/red-sheep.blend', copy=True)
print('Exported red-sheep.glb; meshes:', len([o for o in scene.objects if o.type == 'MESH']))