59 lines
4.7 KiB
Python
59 lines
4.7 KiB
Python
"""Hand-drawn, pressure-varied Hanja pigment on the existing stone atlas; no fonts."""
|
|
import bpy,numpy as np,math
|
|
s=bpy.data.scenes['Summit slab and marker'];bpy.context.window.scene=s
|
|
body=bpy.data.objects['Photo matched summit boulder'];mat=body.data.materials[0];nodes=mat.node_tree.nodes;links=mat.node_tree.links
|
|
N=2048;mask=np.zeros((N,N),dtype=np.float32)
|
|
# Each control point is x, y (down), brush width. Sweeps and hooked ends are individually drawn.
|
|
冠=[[(.13,.17,.045),(.10,.26,.078),(.08,.34,.018)],[(.12,.20,.035),(.48,.17,.062),(.87,.14,.085),(.83,.30,.015)],[(.22,.38,.025),(.39,.35,.055),(.54,.36,.018)],[(.16,.53,.022),(.35,.49,.065),(.56,.48,.027)],[(.29,.53,.072),(.26,.70,.052),(.18,.81,.032),(.11,.85,.007)],[(.45,.51,.073),(.42,.72,.062),(.46,.87,.047),(.66,.88,.066),(.87,.83,.076),(.90,.69,.010)],[(.57,.43,.025),(.74,.41,.055),(.91,.40,.012)],[(.77,.29,.032),(.78,.47,.069),(.76,.69,.048),(.69,.73,.014)],[(.59,.54,.024),(.65,.58,.070),(.67,.62,.012)]]
|
|
岳=[[(.73,.10,.025),(.52,.16,.072),(.29,.21,.027)],[(.28,.22,.074),(.26,.40,.063),(.24,.56,.036)],[(.30,.35,.020),(.50,.34,.057),(.72,.31,.021)],[(.68,.34,.059),(.66,.46,.068),(.65,.56,.027)],[(.12,.58,.017),(.43,.56,.066),(.86,.54,.020)],[(.49,.64,.035),(.50,.77,.080),(.48,.91,.037)],[(.22,.72,.035),(.23,.91,.072),(.45,.92,.049),(.78,.88,.044)],[(.79,.68,.029),(.80,.77,.074),(.77,.93,.018)]]
|
|
山=[[(.49,.12,.024),(.50,.32,.083),(.48,.56,.069),(.49,.82,.045)],[(.18,.39,.035),(.19,.62,.078),(.20,.86,.072),(.43,.85,.043),(.80,.81,.063)],[(.82,.34,.032),(.81,.56,.083),(.80,.78,.061),(.76,.87,.012)]]
|
|
def brush(points,cx,top,w,h,lean,seed):
|
|
rng=np.random.default_rng(seed)
|
|
controls=np.array(points)
|
|
# Catmull-Rom interpolation produces continuous hand motion rather than straight font stems.
|
|
padded=np.vstack([controls[0],controls,controls[-1]])
|
|
samples=[]
|
|
for i in range(1,len(padded)-2):
|
|
a,b,c,d=padded[i-1:i+3]
|
|
for t in np.linspace(0,1,28,endpoint=False):
|
|
samples.append(.5*((2*b)+(-a+c)*t+(2*a-5*b+4*c-d)*t*t+(-a+3*b-3*c+d)*t*t*t))
|
|
samples.append(controls[-1])
|
|
for k,(x,y,width) in enumerate(samples):
|
|
px=cx+(x-.5)*w+lean*(.5-y);pz=top-y*h
|
|
u=(.02+.46*(px+1.3)/2.6)*(N-1);v=(.02+.96*(pz+.2)/2.3)*(N-1)
|
|
rx=max(.45,width*w*.46/2.6*N*.5);ry=max(.6,width*h*.96/2.3*N*.5)
|
|
rx*=1+.08*math.sin(k*.73+seed)
|
|
x0=max(0,int(u-rx-2));x1=min(N,int(u+rx+3));y0=max(0,int(v-ry-2));y1=min(N,int(v+ry+3))
|
|
X,Y=np.meshgrid(np.arange(x0,x1)+.5,np.arange(y0,y1)+.5)
|
|
radius=np.sqrt(((X-u)/rx)**2+((Y-v)/ry)**2)
|
|
dab=np.clip((1.10-radius)*5,0,1)
|
|
mask[y0:y1,x0:x1]=np.maximum(mask[y0:y1,x0:x1],dab)
|
|
for char,cx,top,w,h,lean,seed in [(冠,.18,1.78,.48,.45,.025,13),(岳,.15,1.29,.43,.41,-.012,31),(山,.20,.83,.47,.40,.018,58)]:
|
|
for i,stroke in enumerate(char):brush(stroke,cx,top,w,h,lean,seed+i*7)
|
|
# Small mineral-shaped losses, sparse chips and uneven pigment opacity; retain legibility.
|
|
rng=np.random.default_rng(194);Y,X=np.indices(mask.shape)
|
|
wear=.89+.07*np.sin(X*.19+np.sin(Y*.27))+.035*rng.random(mask.shape)
|
|
chips=rng.random(mask.shape)<.025
|
|
mask*=np.where(chips,.12,wear)
|
|
for _ in range(55):
|
|
occupied=np.argwhere(mask>.7)
|
|
y,x=occupied[rng.integers(len(occupied))];rx=rng.uniform(.6,1.7);ry=rng.uniform(.8,2.5)
|
|
x0=max(0,x-4);x1=min(N,x+5);y0=max(0,y-5);y1=min(N,y+6)
|
|
yy,xx=np.indices((y1-y0,x1-x0));patch=((xx+x0-x)/rx)**2+((yy+y0-y)/ry)**2<1
|
|
mask[y0:y1,x0:x1]*=np.where(patch,.25,1)
|
|
image_mask=bpy.data.images['Hanja pigment mask'];rgba=np.ones((N,N,4),dtype=np.float32);rgba[:,:,:3]=mask[:,:,None]
|
|
image_mask.pixels.foreach_set(rgba.ravel());image_mask.update();image_mask.pack()
|
|
bs=next(n for n in nodes if n.type=='BSDF_PRINCIPLED')
|
|
paint=next(n for n in nodes if n.type=='MIX_RGB' and n.inputs[0].is_linked)
|
|
target=next(n for n in nodes if n.type=='TEX_IMAGE' and n.image and n.image.name=='Painted Hanja on granite')
|
|
links.new(paint.outputs[0],bs.inputs['Base Color']);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
|
|
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'))
|
|
target.image.save();target.image.pack();links.new(target.outputs['Color'],bs.inputs['Base Color'])
|
|
body['inscriptionStyle']='Hand-drawn brush paths, pressure variation and worn pigment; no font rasterization'
|
|
s.render.filepath='D:/project/sunrise/assets/summit/blender-plateau-study/marker-brush-painted.png'
|
|
bpy.ops.render.render(write_still=True)
|
|
print('Painted 20 hand-drawn brush paths with tapered hooks and sparse pigment wear.')
|