Files
sunrise/scripts/paint-marker-inscription.py
T

69 lines
4.9 KiB
Python

"""Rasterize existing Hanja strokes into the stone's own albedo; no letter geometry exported."""
import bpy, numpy as np, math
from pathlib import Path
s=bpy.data.scenes['Summit slab and marker'];bpy.context.window.scene=s
body=bpy.data.objects['Photo matched summit boulder'];root=bpy.data.objects['summit-marker'];mesh=body.data
if body.get('paintedInscription'):raise RuntimeError('Already painted; edit the texture rather than baking it twice.')
mat=mesh.materials[0];nodes=mat.node_tree.nodes;links=mat.node_tree.links
bs=next(n for n in nodes if n.type=='BSDF_PRINCIPLED');old_color=bs.inputs['Base Color'].links[0].from_socket
old_uv=nodes.new('ShaderNodeUVMap');old_uv.uv_map='Granite grain UV'
for n in list(nodes):
if n.type=='TEX_IMAGE':links.new(old_uv.outputs['UV'],n.inputs['Vector'])
atlas=mesh.uv_layers.new(name='Painted monument atlas')
# Separate front and rear islands; preserve the existing stone geometry and normals.
xmin=-1.3;xspan=2.6;zmin=-.2;zspan=2.3
half=len(mesh.vertices)//2
for f in mesh.polygons:
rear=sum(1 for i in f.vertices if i>=half)>1
for li in f.loop_indices:
v=mesh.vertices[mesh.loops[li].vertex_index].co
atlas.data[li].uv=((.52 if rear else .02)+.46*(v.x-xmin)/xspan,.02+.96*(v.z-zmin)/zspan)
mesh.uv_layers.active=atlas
for layer in mesh.uv_layers:layer.active_render=(layer==atlas)
N=2048;mask=np.zeros((N,N),dtype=np.float32)
letters=[o for o in root.children if o.name.startswith('Carved ')]
for o in letters:
o.data.calc_loop_triangles()
for tri in o.data.loop_triangles:
p=[o.matrix_basis@o.data.vertices[i].co for i in tri.vertices]
xy=np.array([[(.02+.46*(v.x-xmin)/xspan)*(N-1),(.02+.96*(v.z-zmin)/zspan)*(N-1)] for v in p])
lo=np.maximum(0,np.floor(xy.min(axis=0)).astype(int));hi=np.minimum(N-1,np.ceil(xy.max(axis=0)).astype(int))
if np.any(hi<lo):continue
A,B,C=xy;den=(B[1]-C[1])*(A[0]-C[0])+(C[0]-B[0])*(A[1]-C[1])
if abs(den)<1e-8:continue
X,Y=np.meshgrid(np.arange(lo[0],hi[0]+1)+.5,np.arange(lo[1],hi[1]+1)+.5)
u=((B[1]-C[1])*(X-C[0])+(C[0]-B[0])*(Y-C[1]))/den
v=((C[1]-A[1])*(X-C[0])+(A[0]-C[0])*(Y-C[1]))/den
hit=(u>=0)&(v>=0)&(u+v<=1)
mask[lo[1]:hi[1]+1,lo[0]:hi[0]+1]=np.maximum(mask[lo[1]:hi[1]+1,lo[0]:hi[0]+1],hit)
# Slight pigment wear lets mineral grain remain visible through the painted strokes.
rng=np.random.default_rng(91);mask*=rng.uniform(.88,1,mask.shape)
rgba=np.ones((N,N,4),dtype=np.float32);rgba[:,:,:3]=mask[:,:,None]
im=bpy.data.images.new('Hanja pigment mask',width=N,height=N,alpha=False);im.pixels.foreach_set(rgba.ravel());im.update()
tex=nodes.new('ShaderNodeTexImage');tex.image=im
atlas_node=nodes.new('ShaderNodeUVMap');atlas_node.uv_map=atlas.name;links.new(atlas_node.outputs['UV'],tex.inputs['Vector'])
mix=nodes.new('ShaderNodeMixRGB');links.new(tex.outputs['Color'],mix.inputs[0]);links.new(old_color,mix.inputs[1]);mix.inputs[2].default_value=(.012,.014,.010,1);links.new(mix.outputs[0],bs.inputs['Base Color'])
image=bpy.data.images.new('Painted Hanja on granite',width=N,height=N,alpha=False)
target=nodes.new('ShaderNodeTexImage');target.image=image;nodes.active=target
for o in s.objects:o.select_set(False)
body.select_set(True);bpy.context.view_layer.objects.active=body
s.render.bake.use_pass_direct=False;s.render.bake.use_pass_indirect=False;s.render.bake.use_pass_color=True;s.render.bake.margin=8
bpy.ops.object.bake(type=next(i.identifier for i in bpy.ops.object.bake.get_rna_type().properties['type'].enum_items if i.identifier=='DIFFUSE'))
image.filepath_raw='D:/project/sunrise/public/terrain/materials/marker-painted-color.png'
image.file_format=next(i.identifier for i in image.bl_rna.properties['file_format'].enum_items if i.identifier=='PNG');image.save();image.pack()
links.new(atlas_node.outputs['UV'],target.inputs['Vector']);links.new(target.outputs['Color'],bs.inputs['Base Color'])
for o in letters:
world=o.matrix_world.copy();o.parent=None;o.matrix_world=world;o.hide_render=True;o.hide_set(True);o.name='Paint source - '+o.name
body['paintedInscription']=True
side=mat.copy();side.name='Unpainted stone edge'
edge_bs=next(n for n in side.node_tree.nodes if n.type=='BSDF_PRINCIPLED')
scan=next(n for n in side.node_tree.nodes if n.type=='TEX_IMAGE' and n.image and n.image.name.startswith('rock-color'))
for socket in [edge_bs.inputs['Base Color'],edge_bs.inputs['Normal']]:
for link in list(socket.links):side.node_tree.links.remove(link)
edge_bs.inputs['Base Color'].default_value=(.23,.215,.18,1);mesh.materials.append(side)
for p in mesh.polygons:
if any(i<half for i in p.vertices) and any(i>=half for i in p.vertices):p.material_index=1
s.render.filepath='D:/project/sunrise/assets/summit/blender-plateau-study/marker-painted.png'
bpy.ops.render.render(write_still=True)
print('Painted',int(np.count_nonzero(mask)),'texels. Export hierarchy contains only the stone; glyph meshes retained hidden as paint sources.')