Private
Public Access
Blender-Scanvorschau und Gruppenfoto ergänzen
This commit is contained in:
1 parent
f15ab1d399
commit
4adf927773
21 files changed
+453
-4
No files matched your search
@@ -0,0 +1,51 @@
|
||||
"""Fictional scan/database HUD for synthetic 3D visitors; no biometric processing."""
|
||||
import bpy, os
|
||||
ROOT=os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
OUT=os.path.join(ROOT,'preview','v4')
|
||||
bpy.ops.wm.read_factory_settings(use_empty=True);s=bpy.context.scene
|
||||
s.render.resolution_x=1280;s.render.resolution_y=720;s.render.resolution_percentage=100;s.render.fps=24;s.frame_start=1;s.frame_end=480
|
||||
s.view_settings.view_transform='Standard';s.sequence_editor_create();seq=s.sequence_editor.sequences
|
||||
seq.new_movie('Existing animation',os.path.join(OUT,'halloween_dark_groan.mp4'),channel=1,frame_start=1)
|
||||
seq.new_sound('Existing moan and atmosphere',os.path.join(OUT,'effects_with_groan.wav'),channel=2,frame_start=1)
|
||||
assets=os.path.join(OUT,'scan_assets')
|
||||
beam=seq.new_image('Grave headstone scan cone',os.path.join(assets,'cone_198.png'),channel=3,frame_start=198)
|
||||
for f in range(199,274):beam.elements.append('cone_%03d.png'%f)
|
||||
beam.frame_final_duration=76
|
||||
channel=4
|
||||
def text(name,body,start,end,x,y,size=24,color=(.4,1,.8,1)):
|
||||
global channel
|
||||
o=seq.new_effect(name,type='TEXT',channel=channel,frame_start=start,frame_end=end);channel+=1
|
||||
o.text=body;o.font_size=size;o.color=color;o.location=(x,y);o.align_x='LEFT';o.align_y='TOP';o.use_shadow=True;o.shadow_color=(0,0,0,1)
|
||||
return o
|
||||
def bar(name,start,end,x,y,w,h,color):
|
||||
global channel
|
||||
o=seq.new_effect(name,type='COLOR',channel=channel,frame_start=start,frame_end=end);channel+=1;o.color=color
|
||||
o.transform.scale_x=w/1280;o.transform.scale_y=h/720;o.transform.offset_x=x-640;o.transform.offset_y=y-360
|
||||
return o
|
||||
# Scan overlay on fictional figures: static stylized boxes, intentionally not a real detector.
|
||||
bar('Scan header',198,274,640,665,1280,90,(.006,.018,.025))
|
||||
text('Scan title','SIMULIERTER GESICHTSSCAN',198,274,.035,.96,30)
|
||||
text('Permanent simulation label','3D-DEMO / KEINE REALEN GESICHTSDATEN',198,274,.035,.90,18,(.9,.9,.9,1))
|
||||
for i,(x,y,w,h) in enumerate([(765,345,68,78),(819,334,68,78),(969,425,90,100),(1140,417,90,100)]):
|
||||
for xx,yy,ww,hh in [(x-w/2,y,2,h),(x+w/2,y,2,h),(x,y-h/2,w,2),(x,y+h/2,w,2)]:bar('Synthetic head frame',198,274,xx,yy,ww,hh,(.08,.6,.38))
|
||||
text('Synthetic figure label','DEMO %02d'%(i+1),198,274,(x-w/2)/1280,(y+h/2+22)/720,15)
|
||||
text('Scan progress','DEMO-BILDAUSSCHNITTE WERDEN VORBEREITET ...',198,246,.04,.13,22)
|
||||
text('Scan completed','4 FIKTIVE 3D-FIGUREN / DEMO-SCAN ABGESCHLOSSEN',246,274,.04,.13,22)
|
||||
# Later simulated database write, using invented records only.
|
||||
bar('Database panel',405,481,365,365,690,540,(.007,.019,.025))
|
||||
text('Database title','DEMO-DATENBANK',405,481,.045,.84,32)
|
||||
text('Database subtitle','Fiktive Scan-Datensaetze der 3D-Figuren',405,481,.045,.77,20,(.8,.85,.9,1))
|
||||
for i in range(4):
|
||||
f=410+i*10
|
||||
portrait=seq.new_image('Invented face '+str(i),os.path.join(assets,'face_%02d.png'%i),channel=channel,frame_start=f);channel+=1;portrait.frame_final_duration=481-f
|
||||
text('Record '+str(i),'ID: DEMO-%03d / GESPEICHERT'%(i+1),f,481,.11,.70-i*.11,23)
|
||||
text('Fictional portrait '+str(i),'FIKTIVES GESICHT | 3D-DEMO',f,481,.11,.66-i*.11,15,(.75,.85,.85,1))
|
||||
text('Write confirmation','SCHREIBVORGANG ABGESCHLOSSEN',451,481,.045,.22,22)
|
||||
text('Database simulation notice','SIMULATION: erfundene Gesichter und IDs.',405,481,.045,.16,17,(.85,.85,.85,1))
|
||||
s.render.image_settings.file_format='FFMPEG';s.render.ffmpeg.format='MPEG4';s.render.ffmpeg.codec='H264';s.render.ffmpeg.constant_rate_factor='HIGH';s.render.ffmpeg.audio_codec='AAC';s.render.ffmpeg.audio_bitrate=192
|
||||
s.render.filepath=os.path.join(OUT,'halloween_cone_demo.mp4')
|
||||
bpy.ops.wm.save_as_mainfile(filepath=os.path.join(OUT,'halloween_cone_demo.blend'))
|
||||
bpy.ops.render.render(animation=True)
|
||||
s.render.image_settings.file_format='PNG'
|
||||
for f in [225,465]:
|
||||
s.frame_set(f);s.render.filepath=os.path.join(OUT,'cone_demo_%03d.png'%f);bpy.ops.render.render(write_still=True)
|
||||
@@ -0,0 +1,99 @@
|
||||
"""Replace both old bass growls with a softly entering, breathy vocal moan."""
|
||||
import array
|
||||
import math
|
||||
import random
|
||||
import wave
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
OUT = ROOT / 'preview' / 'v4'
|
||||
rate = 24000
|
||||
random.seed(72)
|
||||
ambience = []
|
||||
lp = 0.0
|
||||
for i in range(rate * 20):
|
||||
t = i / rate
|
||||
lp = .90 * lp + .10 * random.uniform(-1, 1)
|
||||
env = (.5 + .5 * math.sin(t * 2.1)) ** 3
|
||||
value = .07 * lp * env if t < 10 or t > 17 else .006 * lp
|
||||
if 7.1 < t < 8.3:
|
||||
value += .13 * lp * (.5 + .5 * math.sin(t * 90))
|
||||
# The old 62 Hz modulated growl and rapid onset scratching are omitted.
|
||||
ambience.append(value)
|
||||
|
||||
random.seed(149)
|
||||
moan = [0.0] * len(ambience)
|
||||
phase = breath_low = breath_high = 0.0
|
||||
start, length = 13.04, 3.45
|
||||
for i in range(int(length * rate)):
|
||||
t = i / rate
|
||||
progress = t / length
|
||||
pitch = 108 - 26 * progress + 1.4 * math.sin(t * 8)
|
||||
phase += math.tau * pitch / rate
|
||||
voice = 0.0
|
||||
for harmonic in range(2, 23):
|
||||
frequency = harmonic * pitch
|
||||
resonance = (.8 * math.exp(-((frequency - 460) / 150) ** 2)
|
||||
+ .4 * math.exp(-((frequency - 950) / 220) ** 2)
|
||||
+ .18 * math.exp(-((frequency - 1450) / 260) ** 2))
|
||||
voice += resonance * math.sin(harmonic * phase + harmonic * .17) / math.sqrt(harmonic)
|
||||
noise = random.uniform(-1, 1)
|
||||
breath_low += .12 * (noise - breath_low)
|
||||
breath_high += .015 * (noise - breath_high)
|
||||
attack = min(1.0, t / .65)
|
||||
attack = attack * attack * (3 - 2 * attack)
|
||||
release = min(1.0, (length - t) / .9)
|
||||
release = release * release * (3 - 2 * release)
|
||||
envelope = attack * release * (.85 + .15 * math.sin(math.pi * progress))
|
||||
moan[round(start * rate) + i] = envelope * (.37 * voice + .13 * (breath_low - breath_high))
|
||||
|
||||
mixed = []
|
||||
peak = 0.0
|
||||
for i, ambient in enumerate(ambience):
|
||||
value = moan[i]
|
||||
for delay, gain in [(.17, .16), (.33, .08)]:
|
||||
index = i - round(delay * rate)
|
||||
if index >= 0:
|
||||
value += gain * moan[index]
|
||||
value += ambient
|
||||
peak = max(peak, abs(value))
|
||||
mixed.append(round(value * 32767))
|
||||
assert peak < .98, peak
|
||||
path = OUT / 'effects_with_groan_v2.wav'
|
||||
with wave.open(str(path), 'wb') as handle:
|
||||
handle.setparams((1, 2, rate, len(mixed), 'NONE', 'not compressed'))
|
||||
handle.writeframes(array.array('h', mixed).tobytes())
|
||||
print(f'Soft vocal onset at {start}s; audio peak {peak:.3f}, no clipping')
|
||||
|
||||
import bpy
|
||||
bpy.ops.wm.open_mainfile(filepath=str(OUT / 'halloween_cone_demo.blend'))
|
||||
scene = bpy.context.scene
|
||||
for strip in list(scene.sequence_editor.sequences):
|
||||
if strip.type == 'SOUND':
|
||||
scene.sequence_editor.sequences.remove(strip)
|
||||
scene.sequence_editor.sequences.new_sound('Soft breathy moan and ambience', str(path), channel=2, frame_start=1)
|
||||
bpy.ops.file.pack_all()
|
||||
bpy.ops.wm.save_as_mainfile(filepath=str(OUT / 'halloween_cone_demo_audio_v2.blend'))
|
||||
|
||||
# Reuse the complete latest picture, including the cone and fictional IDs.
|
||||
bpy.ops.wm.read_factory_settings(use_empty=True)
|
||||
scene = bpy.context.scene
|
||||
scene.render.resolution_x = 1280
|
||||
scene.render.resolution_y = 720
|
||||
scene.render.resolution_percentage = 100
|
||||
scene.render.fps = 24
|
||||
scene.frame_start = 1
|
||||
scene.frame_end = 480
|
||||
scene.view_settings.view_transform = 'Standard'
|
||||
scene.view_settings.look = 'None'
|
||||
scene.sequence_editor_create()
|
||||
scene.sequence_editor.sequences.new_movie('Latest complete picture', str(OUT / 'halloween_cone_demo.mp4'), channel=1, frame_start=1)
|
||||
scene.sequence_editor.sequences.new_sound('Revised audio', str(path), channel=2, frame_start=1)
|
||||
scene.render.image_settings.file_format = 'FFMPEG'
|
||||
scene.render.ffmpeg.format = 'MPEG4'
|
||||
scene.render.ffmpeg.codec = 'H264'
|
||||
scene.render.ffmpeg.constant_rate_factor = 'HIGH'
|
||||
scene.render.ffmpeg.audio_codec = 'AAC'
|
||||
scene.render.ffmpeg.audio_bitrate = 192
|
||||
scene.render.filepath = str(OUT / 'halloween_cone_demo_audio_v2.mp4')
|
||||
bpy.ops.render.render(animation=True)
|
||||
@@ -0,0 +1,42 @@
|
||||
"""Append a simulated saved group snapshot of the four synthetic visitors."""
|
||||
from pathlib import Path
|
||||
import array, math, random, wave
|
||||
from PIL import Image, ImageEnhance
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
OUT = ROOT / 'preview/v4'
|
||||
image = Image.open(OUT/'frames/review_185.png').convert('RGB')
|
||||
image = ImageEnhance.Brightness(image).enhance(1.7)
|
||||
image.save(OUT/'group_photo.png')
|
||||
image.save(OUT/'gruppenfoto_demo_001.png')
|
||||
import bpy
|
||||
bpy.ops.wm.open_mainfile(filepath=str(OUT/'halloween_cone_demo_audio_v2.blend'))
|
||||
s=bpy.context.scene
|
||||
seq=s.sequence_editor.sequences
|
||||
photo=seq.new_image('Group snapshot - four fictional visitors',str(OUT/'group_photo.png'),channel=30,frame_start=481)
|
||||
photo.frame_final_duration=72
|
||||
photo.transform.scale_x=.88;photo.transform.scale_y=.88
|
||||
for name,body,y,size,color in [
|
||||
('Photo title','GRUPPENFOTO GESPEICHERT',.96,30,(.4,1,.8,1)),
|
||||
('Photo file','gruppenfoto_demo_001.png | 4 fiktive Besucher',.085,20,(.9,.9,.9,1)),
|
||||
('Simulation','SIMULATION / 3D-FIGUREN',.045,17,(.65,.8,.8,1))]:
|
||||
strip=seq.new_effect(name,type='TEXT',channel=31+len([x for x in seq if x.name.startswith('Photo')]),frame_start=481,frame_end=553)
|
||||
strip.text=body;strip.font_size=size;strip.color=color;strip.location=(.5,y);strip.align_x='CENTER';strip.use_shadow=True
|
||||
flash=seq.new_effect('Camera flash',type='COLOR',channel=40,frame_start=481,frame_end=486)
|
||||
flash.color=(1,1,1);flash.blend_type='ALPHA_OVER'
|
||||
flash.blend_alpha=.75;flash.keyframe_insert('blend_alpha',frame=481)
|
||||
flash.blend_alpha=0;flash.keyframe_insert('blend_alpha',frame=485)
|
||||
rate=24000;random.seed(51);sound=[]
|
||||
for i in range(rate*3):
|
||||
t=i/rate
|
||||
click=.16*random.uniform(-1,1)*math.exp(-t*75)
|
||||
click+=.08*random.uniform(-1,1)*math.exp(-max(0,t-.11)*90) if t>=.11 else 0
|
||||
sound.append(round(click*32767))
|
||||
with wave.open(str(OUT/'group_photo_shutter.wav'),'wb') as w:
|
||||
w.setparams((1,2,rate,len(sound),'NONE','not compressed'));w.writeframes(array.array('h',sound).tobytes())
|
||||
seq.new_sound('Camera shutter',str(OUT/'group_photo_shutter.wav'),channel=41,frame_start=481)
|
||||
s.frame_end=552
|
||||
s.render.image_settings.file_format='FFMPEG';s.render.ffmpeg.format='MPEG4';s.render.ffmpeg.codec='H264';s.render.ffmpeg.constant_rate_factor='HIGH';s.render.ffmpeg.audio_codec='AAC';s.render.ffmpeg.audio_bitrate=192
|
||||
s.render.filepath=str(OUT/'halloween_group_photo.mp4')
|
||||
bpy.ops.file.pack_all();bpy.ops.wm.save_as_mainfile(filepath=str(OUT/'halloween_group_photo.blend'))
|
||||
bpy.ops.render.render(animation=True)
|
||||
s.render.image_settings.file_format='PNG';s.frame_set(510);s.render.filepath=str(OUT/'group_photo_end.png');bpy.ops.render.render(write_still=True)
|
||||
@@ -0,0 +1,48 @@
|
||||
"""Fictional scan/database HUD for synthetic 3D visitors; no biometric processing."""
|
||||
import bpy, os
|
||||
ROOT=os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
OUT=os.path.join(ROOT,'preview','v4')
|
||||
bpy.ops.wm.read_factory_settings(use_empty=True);s=bpy.context.scene
|
||||
s.render.resolution_x=1280;s.render.resolution_y=720;s.render.resolution_percentage=100;s.render.fps=24;s.frame_start=1;s.frame_end=480
|
||||
s.view_settings.view_transform='Standard';s.sequence_editor_create();seq=s.sequence_editor.sequences
|
||||
seq.new_movie('Existing animation',os.path.join(OUT,'halloween_dark_groan.mp4'),channel=1,frame_start=1)
|
||||
seq.new_sound('Existing moan and atmosphere',os.path.join(OUT,'effects_with_groan.wav'),channel=2,frame_start=1)
|
||||
channel=3
|
||||
def text(name,body,start,end,x,y,size=24,color=(.4,1,.8,1)):
|
||||
global channel
|
||||
o=seq.new_effect(name,type='TEXT',channel=channel,frame_start=start,frame_end=end);channel+=1
|
||||
o.text=body;o.font_size=size;o.color=color;o.location=(x,y);o.align_x='LEFT';o.align_y='TOP';o.use_shadow=True;o.shadow_color=(0,0,0,1)
|
||||
return o
|
||||
def bar(name,start,end,x,y,w,h,color):
|
||||
global channel
|
||||
o=seq.new_effect(name,type='COLOR',channel=channel,frame_start=start,frame_end=end);channel+=1;o.color=color
|
||||
o.transform.scale_x=w/1280;o.transform.scale_y=h/720;o.transform.offset_x=x-640;o.transform.offset_y=y-360
|
||||
return o
|
||||
# Scan overlay on fictional figures: static stylized boxes, intentionally not a real detector.
|
||||
bar('Scan header',198,274,640,665,1280,90,(.006,.018,.025))
|
||||
text('Scan title','SIMULIERTER GESICHTSSCAN',198,274,.035,.96,30)
|
||||
text('Permanent simulation label','3D-DEMO / KEINE REALEN GESICHTSDATEN',198,274,.035,.90,18,(.9,.9,.9,1))
|
||||
for i,(x,y,w,h) in enumerate([(765,345,68,78),(819,334,68,78),(969,425,90,100),(1140,417,90,100)]):
|
||||
for xx,yy,ww,hh in [(x-w/2,y,2,h),(x+w/2,y,2,h),(x,y-h/2,w,2),(x,y+h/2,w,2)]:bar('Synthetic head frame',198,274,xx,yy,ww,hh,(.08,.6,.38))
|
||||
text('Synthetic figure label','DEMO %02d'%(i+1),198,274,(x-w/2)/1280,(y+h/2+22)/720,15)
|
||||
line=bar('Scan sweep',198,246,640,475,1100,2,(.05,.6,.35))
|
||||
line.transform.offset_y=110;line.transform.keyframe_insert('offset_y',frame=198)
|
||||
line.transform.offset_y=-155;line.transform.keyframe_insert('offset_y',frame=245)
|
||||
text('Scan progress','DEMO-BILDAUSSCHNITTE WERDEN VORBEREITET ...',198,246,.04,.13,22)
|
||||
text('Scan completed','4 FIKTIVE 3D-FIGUREN / DEMO-SCAN ABGESCHLOSSEN',246,274,.04,.13,22)
|
||||
# Later simulated database write, using invented records only.
|
||||
bar('Database panel',405,481,365,365,690,540,(.007,.019,.025))
|
||||
text('Database title','DEMO-DATENBANK',405,481,.045,.84,32)
|
||||
text('Database subtitle','Fiktive Scan-Datensaetze der 3D-Figuren',405,481,.045,.77,20,(.8,.85,.9,1))
|
||||
for i in range(4):
|
||||
f=410+i*10
|
||||
text('Record '+str(i),'DEMO-%03d | 3D-Gesichtsausschnitt | GESPEICHERT'%(i+1),f,481,.045,.66-i*.07,19)
|
||||
text('Write confirmation','SCHREIBVORGANG ABGESCHLOSSEN',451,481,.045,.32,24)
|
||||
text('Database simulation notice','SIMULATION: keine Erkennung, keine echte Datenbank.',405,481,.045,.23,18,(.85,.85,.85,1))
|
||||
s.render.image_settings.file_format='FFMPEG';s.render.ffmpeg.format='MPEG4';s.render.ffmpeg.codec='H264';s.render.ffmpeg.constant_rate_factor='HIGH';s.render.ffmpeg.audio_codec='AAC';s.render.ffmpeg.audio_bitrate=192
|
||||
s.render.filepath=os.path.join(OUT,'halloween_scan_demo.mp4')
|
||||
bpy.ops.wm.save_as_mainfile(filepath=os.path.join(OUT,'halloween_scan_demo.blend'))
|
||||
bpy.ops.render.render(animation=True)
|
||||
s.render.image_settings.file_format='PNG'
|
||||
for f in [225,465]:
|
||||
s.frame_set(f);s.render.filepath=os.path.join(OUT,'scan_demo_%03d.png'%f);bpy.ops.render.render(write_still=True)
|
||||
@@ -0,0 +1,20 @@
|
||||
"""Replace the concluding photo with the rendered scanner viewpoint."""
|
||||
import bpy
|
||||
from pathlib import Path
|
||||
OUT=Path(__file__).resolve().parents[1]/'preview/v4'
|
||||
bpy.ops.wm.open_mainfile(filepath=str(OUT/'halloween_group_photo.blend'))
|
||||
s=bpy.context.scene
|
||||
for strip in s.sequence_editor.sequences:
|
||||
if strip.name.startswith('Group snapshot'):
|
||||
strip.directory=str(OUT)+'/'
|
||||
strip.elements[0].filename='gruppenfoto_scanner.png'
|
||||
if strip.name=='Photo file':
|
||||
strip.text='gruppenfoto_scanner.png | SCANNER IM GRABSTEIN'
|
||||
s.render.image_settings.file_format='FFMPEG'
|
||||
s.render.filepath=str(OUT/'halloween_scanner_group_photo.mp4')
|
||||
bpy.ops.file.pack_all()
|
||||
bpy.ops.wm.save_as_mainfile(filepath=str(OUT/'halloween_scanner_group_photo.blend'))
|
||||
bpy.ops.render.render(animation=True)
|
||||
s.render.image_settings.file_format='PNG';s.frame_set(510)
|
||||
s.render.filepath=str(OUT/'scanner_group_photo_end.png')
|
||||
bpy.ops.render.render(write_still=True)
|
||||
@@ -14,7 +14,7 @@ def boxes(data):
|
||||
|
||||
def child(data,name):return next(body for kind,body in boxes(data) if kind==name)
|
||||
|
||||
def inspect(path):
|
||||
def inspect(path, expected_seconds=20):
|
||||
moov=child(Path(path).read_bytes(),'moov');result=[]
|
||||
for kind,trak in boxes(moov):
|
||||
if kind!='trak':continue
|
||||
@@ -27,9 +27,9 @@ def inspect(path):
|
||||
item={'type':kind,'codec':entry[0],'duration_seconds':seconds,'samples':count}
|
||||
if kind=='vide':
|
||||
width,height=struct.unpack_from('>HH',entry[1],24);item.update(width=width,height=height,fps=count/seconds)
|
||||
assert (width,height,count)==(1280,720,480),item
|
||||
assert abs(seconds-20)<.01,item
|
||||
assert (width,height,count)==(1280,720,round(expected_seconds*24)),item
|
||||
assert abs(seconds-expected_seconds)<.01,item
|
||||
result.append(item)
|
||||
assert any(t['type']=='soun' for t in result),'Missing audio track'
|
||||
return result
|
||||
if __name__=='__main__':print(json.dumps(inspect(sys.argv[1]),indent=2))
|
||||
if __name__=='__main__':print(json.dumps(inspect(sys.argv[1],float(sys.argv[2]) if len(sys.argv)>2 else 20),indent=2))
|
||||
@@ -0,0 +1,32 @@
|
||||
"""Draw projected cone and four invented synthetic face avatars."""
|
||||
import json, math
|
||||
from pathlib import Path
|
||||
from PIL import Image, ImageDraw, ImageFilter
|
||||
root=Path(__file__).resolve().parents[1];out=root/'preview/v4/scan_assets'
|
||||
for record in json.loads((out/'projection.json').read_text()):
|
||||
layer=Image.new('RGBA',(1280,720));d=ImageDraw.Draw(layer)
|
||||
origin=tuple(record['origin']);ring=[tuple(p) for p in record['ring']]
|
||||
# Transparent beam facets and a softly luminous emitter; geometry follows the camera.
|
||||
for i in range(len(ring)):
|
||||
d.polygon([origin,ring[i],ring[(i+1)%len(ring)]],fill=(20,220,170,13))
|
||||
d.line(ring+[ring[0]],fill=(80,255,215,110),width=2)
|
||||
for i in [0,10,20,30]:d.line([origin,ring[i]],fill=(50,240,190,80),width=1)
|
||||
# A travelling cross-section makes the cone visibly sweep through the group.
|
||||
progress=((record['frame']-198)%32)/31
|
||||
scan=[(origin[0]+(p[0]-origin[0])*progress,origin[1]+(p[1]-origin[1])*progress) for p in ring]
|
||||
d.line(scan+[scan[0]],fill=(110,255,225,160),width=2)
|
||||
x,y=origin;d.ellipse((x-4,y-4,x+4,y+4),fill=(190,255,235,235))
|
||||
glow=layer.filter(ImageFilter.GaussianBlur(5));Image.alpha_composite(glow,layer).save(out/('cone_%03d.png'%record['frame']))
|
||||
for i in range(4):
|
||||
layer=Image.new('RGBA',(1280,720));d=ImageDraw.Draw(layer);x=56;y=205+i*79
|
||||
d.rounded_rectangle((x,y,x+64,y+66),radius=5,fill=(8,31,37,255),outline=(75,245,200,240),width=1)
|
||||
cx=x+32;cy=y+31;skin=[(158,107,79),(200,147,111),(129,90,68),(220,175,140)][i]
|
||||
d.ellipse((cx-17,cy-23,cx+17,cy+23),fill=(*skin,255),outline=(95,240,190,255))
|
||||
hair=[(31,24,21),(83,42,23),(18,19,25),(133,79,33)][i]
|
||||
d.pieslice((cx-18,cy-25,cx+18,cy+6),180,360,fill=(*hair,255))
|
||||
for sign in [-1,1]:
|
||||
ex=cx+sign*7;d.line((ex-3,cy-4,ex+3,cy-4),fill=(20,25,24,255),width=2)
|
||||
d.line((cx,cy-2,cx-2,cy+7,cx+2,cy+7),fill=(67,53,42,255),width=1)
|
||||
d.arc((cx-6,cy+8,cx+6,cy+16),0,180,fill=(54,32,25,255),width=1)
|
||||
for px,py in [(cx-8,cy-5),(cx+8,cy-5),(cx,cy+5),(cx-5,cy+15),(cx+5,cy+15)]:d.ellipse((px-1,py-1,px+1,py+1),fill=(80,255,190,255))
|
||||
layer.save(out/('face_%02d.png'%i))
|
||||
@@ -19,3 +19,7 @@ Hier liegen Pi-Transfer/Einrichtung und die reproduzierbaren Blender-Werkzeuge.
|
||||
Änderungen an Upload- und Installationsbefehlen in `UPLOAD.md` und
|
||||
`SOFTWARE.md` nachführen; Renderabläufe in den jeweiligen README-Dateien
|
||||
unter `preview/` dokumentieren.
|
||||
|
||||
Fiktive Scan-Videos nutzen nur synthetische Avatare und Demo-IDs.
|
||||
Kegelprojektion, Zeichnen der Bildsequenz und Videoschnitt sind getrennte
|
||||
Schritte; Reihenfolge im zugehörigen Vorschau-README dokumentieren.
|
||||
@@ -0,0 +1,15 @@
|
||||
"""Project fictional grave emitter into the existing animated camera."""
|
||||
import bpy, os, json, math
|
||||
from mathutils import Vector
|
||||
from bpy_extras.object_utils import world_to_camera_view
|
||||
root=os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
bpy.ops.wm.open_mainfile(filepath=os.path.join(root,'preview/v4/halloween_dark_groan.blend'))
|
||||
s=bpy.context.scene;origin=Vector((0,1.48,1.02));end=Vector((3.25,-.65,1.2));direction=(end-origin).normalized()
|
||||
u=direction.cross(Vector((0,0,1))).normalized();v=direction.cross(u).normalized();records=[]
|
||||
def project(p):
|
||||
c=world_to_camera_view(s,s.camera,p);return [c.x*1280,(1-c.y)*720]
|
||||
for f in range(198,274):
|
||||
s.frame_set(f);ring=[project(end+.72*(u*math.cos(i*math.tau/40)+v*math.sin(i*math.tau/40))) for i in range(40)]
|
||||
records.append({'frame':f,'origin':project(origin),'ring':ring})
|
||||
path=os.path.join(root,'preview/v4/scan_assets');os.makedirs(path,exist_ok=True)
|
||||
with open(os.path.join(path,'projection.json'),'w') as out:json.dump(records,out)
|
||||
@@ -0,0 +1,20 @@
|
||||
"""Render a genuine view from the same origin/direction as the demo scan cone."""
|
||||
import bpy
|
||||
from pathlib import Path
|
||||
from mathutils import Vector
|
||||
ROOT=Path(__file__).resolve().parents[1];OUT=ROOT/'preview/v4'
|
||||
bpy.ops.wm.open_mainfile(filepath=str(OUT/'halloween_dark.blend'))
|
||||
s=bpy.context.scene;s.frame_set(480)
|
||||
bpy.ops.object.camera_add(location=(0,1.48,1.02))
|
||||
cam=bpy.context.object;cam.name='Scanner camera at headstone emitter'
|
||||
cam.rotation_euler=(Vector((3.25,-.65,1.2))-cam.location).to_track_quat('-Z','Y').to_euler()
|
||||
cam.data.lens=20;cam.data.clip_start=.03;s.camera=cam
|
||||
# Soft illumination alongside the scanner represents its photo capture light.
|
||||
light=bpy.data.lights.new('Scanner photo fill','AREA');light.energy=110;light.color=(.72,.82,1);light.size=.55
|
||||
ob=bpy.data.objects.new('Scanner photo fill',light);s.collection.objects.link(ob)
|
||||
ob.location=cam.location+Vector((.05,-.05,.06));ob.rotation_euler=cam.rotation_euler
|
||||
s.render.use_sequencer=False;s.render.resolution_x=1280;s.render.resolution_y=720;s.render.resolution_percentage=100
|
||||
s.eevee.taa_render_samples=32;s.eevee.volumetric_samples=16
|
||||
s.render.image_settings.file_format='PNG';s.render.filepath=str(OUT/'gruppenfoto_scanner.png')
|
||||
bpy.ops.wm.save_as_mainfile(filepath=str(OUT/'scanner_photo_scene.blend'))
|
||||
bpy.ops.render.render(write_still=True)
|
||||
Reference in new issue
Block a user