# -*- coding: utf-8 -*-
# 動画「メーカーのロボットは、どこまで変えられるか」— 考察回「壁打ちの型」の3本目(もとは LOG406)の場面
# 書き写した元: 現場で扱えるか/scenes_kabe.py(型の決まりは video_rules.md 10節)
# 紙の文字は真ん中にそろえる/線を使わず面で分ける(事実=墨・エイイチの見立て=濃い灰・ハコブくん=薄い灰)
# 朱は判子とリアクション(却下・保留・ボツ・即答・エイイチの推し)だけ/字幕は動かさない
# 脱線(画面いっぱいの絵)は4回: 自分で改造(マンガ)・ロボ検(マンガ)・扉、全開(写真風)・課金で解放(写真風)。どれも Gemini で作った絵
# 天丼: ハコブくんが「改造申請書」を出すたびに、判子が落ちる。冒頭「却下」・やり取り2「却下」・最後だけ「保留」
import os
from kabe_kit import *
import scene_kit as _sk
_tl = Timeline()
tl = _tl
N = len(_tl.idx)
A = lambda i, off=0.0: tl.start(i) + off # 動画の頭からの秒
S = tl.speech # その文を読み終わるまでの長さ
LOGO = _sk.LOGO
BRAND = f'
'
SANS = "font-family:'Noto Sans CJK JP',sans-serif;"
BG = "src/bg/"
GEM = "絵は生成AI(Gemini)で作成"
LAW = "法律の話は、調べた範囲の理解です。専門家の判断ではありません"
BLOCKS = []
def block(name, sents, fn, video=None):
b0 = _tl.start(sents[0])
T = lambda i, off=0.0: tl.start(i) - b0 + off
E = lambda i: tl.end(i) - b0
b = dict(name=name, sents=list(sents))
if video:
b["video"] = video
else:
b["html"] = page(fn(T, E) + BRAND)
BLOCKS.append(b)
def inkl(text, t, step=0.05):
"""「/」で行を分けて書く(1〜2文字だけ次の行に落ちないように)"""
out, tt = [], t
for k, part in enumerate(text.split("/")):
out.append(ink(part, tt, step)); tt += len(part) * step
return "
".join(out)
def line(text, t, size=30, weight=700, step=0.05, style=""):
return f'
{ink(text, t, step)}
'
def qline(text, t, size=46, step=0.06, style=""):
return f'{ink(text, t, step)}
'
def src(text, t=0.0):
return f'{text}
'
def bigline(text, t, size=110, style=""):
return f'{land(text, t)}
'
def hanko(text, t, style, size=72, t_hide=None):
"""朱の判子(却下・保留)。四角い枠で、斜めに落ちる。t_hide で消す"""
an = f"animation:slam .35s cubic-bezier(.5,0,.75,0) {t:.3f}s forwards" + (f", hide .2s linear {t_hide:.3f}s forwards" if t_hide else "") + ";"
return (f'{text}
')
FORM_CSS = """"""
def form(no, items, t_in, t_items):
"""改造申請書(天丼)。items は文字のリスト、t_items はそれぞれが出る秒"""
its = "".join(f'✓{txt}
' for txt, t in zip(items, t_items))
return (FORM_CSS + f'')
# ================= 0 つかみ(改造申請書1枚目 →「却下」) =================
def hook(T, E):
return (paper(tag("考察ノート") + ''
+ form(1, ["速さの上限を外す", "荷物を倍積む", "夜も走る"], T(0, 0.3), [T(1, 0.2), T(1, 0.6), T(1, 1.0)])
+ card("mi", inkl("それ、事故が起きたら、誰の責任?", T(2, 0.3), 0.06), T(2, 0.1), "エイイチ", w=700, anim="wipe")
+ '
', 0.0)
+ hanko("却下", T(3, 0.25), "left:1110px;top:150px", size=80)
+ shake_js([(T(3, 0.6), 0.3)]))
block("hook", [0, 1, 2, 3], hook)
def hook_law(T, E):
return (paper(tag("改造した人は、どうなるか") + ''
+ card("fact", inkl("メーカーが想定していない改変で、危険を増やした人は、/メーカーと同じ義務を負う", T(4, 0.6), 0.04), T(4, 0.3), "EU", w=900)
+ card("fact", inkl("仕事として加工して人に渡した業者は、/作った側と同じ責任を負う側に入る", T(4, 2.8), 0.04), T(4, 2.5), "日本", w=900)
+ '
'
+ card("hb", "車なら、買えば好きに改造できる", T(6, 0.3), "ハコブくん", w=470, fade=T(7, 1.0), style="font-size:30px")
+ card("mi", inkl("車も、保安基準に/合わない改造はだめ", T(7, 0.3), 0.06), T(7, 0.1), "エイイチ", w=400, anim="wipe", style="font-size:30px")
+ '
'
+ f'
{ink("ロボットは、買っても、", T(8, 2.6), 0.06)}'
f'{land("思ったほど自由じゃない", T(8, 3.8), "font-size:48px;font-weight:900")}
'
+ '
' + src(LAW, T(4, 0.3)), 0.0))
block("hook_law", [4, 5, 6, 7, 8], hook_law)
def hook_q(T, E):
return paper(tag("考察回 壁打ち") +
''
f'メーカーの
'
f'ロボットは、
'
f'{land("どこまで", T(9, 1.8), "font-size:108px;font-weight:900")}
'
f'変えられるか。
', 0.0)
block("hook_q", [9], hook_q)
# ================= 1 自己紹介(DAY124 の映像とテロップ) =================
O_TITLE = '
フィジカルAI解説
案内役:ハコブくん(AI音声)
'
O_EIICHI = '
エイイチ
デザイナー/ロボティクスの研究・開発
'
O_OUTRO = '
相談は概要欄の連絡先から
高評価・チャンネル登録/コメントはエイイチが全部読んでいます
'
OVERLAYS = {"title": _sk.overlay(O_TITLE), "title_eiichi": _sk.overlay(O_EIICHI), "outro": _sk.overlay(O_OUTRO)}
_t_title = tl.end(11) - tl.start(10)
block("title", [10, 11], None, video=("src/day124_crop.mp4", 4.5, "png/ovl/title.png"))
block("title_eiichi", [12], None, video=("src/day124_crop.mp4", 4.5 + _t_title, "png/ovl/title_eiichi.png"))
def title_kabe(T, E):
return paper(tag("考察回") + ''
+ f'
{land("壁打ち", T(13, 0.5))}
'
+ line("エイイチの考えに、ハコブくんが付き合う", T(13, 1.3), size=34, weight=500)
+ '
', 0.0)
block("title_kabe", [13], title_kabe)
# ================= 2 持ち込み:5段階の線(脱線①「自分で改造。」) =================
def bring(T, E):
return (paper(tag("持ち込み") + ''
+ card("mi", inkl("搬送や配膳、掃除のロボットの動きを良くしたいとき、/メーカーの製品を、ソフトもハードも触ったら?", T(15, 0.4), 0.04), T(15, 0.15), "エイイチの疑問", w=920, anim="wipe")
+ line("実物の改造ではなく、Claude と一緒に、決まり・契約・実例を調べた話", T(16, 1.6), size=28, weight=500, style="color:#5a564f")
+ card("hb", "店で見て、まず直したいのは?", T(17, 0.2), "ハコブくん", w=520, style="font-size:30px")
+ card("mi", inkl("速さより、人とすれ違うときと、/迷う動きを、まず直したい", T(18, 0.4), 0.05), T(18, 0.15), "エイイチ", w=760, anim="wipe")
+ '
' + src(LAW, T(16, 0.3)), 0.0)
+ stamp("覚えた", T(19, 2.4), "left:1250px;top:430px;font-size:48px"))
block("bring", [14, 15, 16, 17, 18, 19], bring)
STEP_CSS = """"""
def steps(T, E):
fly = (T(22, S(22) - 0.2), E(22) + 0.05)
t_cut = T(23, 0.3)
labels = ["設定", "公式の外部接続", "外付け", "ソフトの書き換え", "機体の改造"]
# 階段の面: 下から上へ。左下から右上に積む。4段目から右へずれて切れ目ができる
html = ""
for k, lab in enumerate(labels):
x, y, w, h = 60 + k * 150, 600 - k * 96, 300, 84
col = ("#22201c", "#f5f3ee") if k < 3 else ("#dcd6c9", "#22201c")
gx = "70px" if k >= 3 else "0px"
appear = T(20, 1.2 + 0.25 * k) if k < 1 else T(21, [0.0, 1.0, 3.3, 4.8, 6.6][k])
html += (f'{k + 1}{lab}
')
zones = (f'メーカーの想定の内側
'
f'外側
')
return (dassen(BG + "自分で改造.jpg", fly[0] + 0.05, fly[1] - 0.05, words="自分で|!改造。", word_style="top:70px;font-size:130px", src_note=GEM)
+ STEP_CSS + paper(tag("触り方の5段階") + html + zones
+ f''
+ card("hb", "5段目まで、一気に", T(22, 0.3), "ハコブくん", w=380, botsu=E(22) + 0.15, fade=T(23, 0.2), style="font-size:30px") + '
'
+ f''
+ f'
法律・保証・保守も、
ほぼこの切れ目で分かれる
', 0.0, flights=[fly])
+ shake_js([(t_cut, 0.3)]))
block("steps", [20, 21, 22, 23, 24], steps)
def steps2(T, E):
return paper(tag("切れ目は、思ったより手前") + ''
+ card("mi", inkl("思ったより、手前で切れてる", T(25, 0.3), 0.06), T(25, 0.1), "エイイチ", w=600, anim="wipe")
+ f'
{ink("動きや操作性を良くしたい用事の多くは、", T(26, 1.2), 0.05)}
'
f'{land("3段目まで", T(26, 4.2), "font-size:96px;font-weight:900")}{ink("で届く", T(26, 4.8), 0.08)}
'
+ line("Claude の分析(シミュレーションの店でも、詰まりの多くは店の形と回し方の直しで減った)", T(26, 6.0), size=24, weight=500, style="color:#5a564f")
+ '
', 0.0)
block("steps2", [25, 26], steps2)
# ================= 3 やり取り1:車とサブスク(脱線②「ロボ検、開幕。」) =================
def car(T, E):
return paper(tag("やり取り 1 / 車と比べると") + ''
+ card("mi", inkl("車やバイクは、自分でもいじってきた。/ロボットも個人で持つようになったら、/改造したい人は必ず多い。僕もしたい", T(28, 0.4), 0.045), T(28, 0.15), "エイイチ", w=880, anim="wipe")
+ bigline("改造したい側の人", T(29, 0.4), size=80)
+ '
', 0.0)
block("car", [27, 28, 29], car)
def car2(T, E):
col = lambda kind, head, sub, t: (f''
f'{head}
{sub}
')
return paper(tag("買う? 借りる?") + ''
+ '
'
+ col("hb", "車は、買う", "買えば、いろいろ変えられそう", T(30, 0.3))
+ col("fact", "ロボットは、サブスク", "用意された以上は、認められない?", T(30, 1.6))
+ '
'
+ card("mi", inkl("ハードはともかく、ソフトは/アップデートが多くて、追いつかなそう", T(31, 0.4), 0.05), T(31, 0.15), "エイイチ", w=780, anim="wipe")
+ card("fact", "サブスク:機体はメーカーか販売会社の物。契約で縛られる", T(32, 0.3), None, w=900, style="font-size:30px")
+ bigline("では、買ったら?", T(33, 0.2), size=64)
+ '
', 0.0)
block("car2", [30, 31, 32, 33], car2)
def soft(T, E):
return (paper(tag("買っても残る縛り") + ''
+ f'
{ink("ソフトは、", T(34, 0.2), 0.08)}{land("買っていない", T(34, 0.9), "font-size:90px;font-weight:900")}
'
+ line("使う権利を、借りているだけ", T(34, 2.4), size=30, weight=500, style="color:#5a564f")
+ card("fact", inkl("中古で売られた車の運転を助ける機能を、/メーカーが遠くから外した(2020年の報道)", T(35, 0.4), 0.04), T(35, 0.15), "車では", w=880)
+ '
'
+ card("mi", inkl("え、買ったのに?", T(36, 0.2), 0.07), T(36, 0.05), "エイイチ", w=380, anim="wipe")
+ card("hb", "騒ぎになって、あとで戻された", T(37, 0.3), None, w=480, style="font-size:28px")
+ '
'
+ f'
{land("体は自分の物、機能は借り物", T(37, 3.0))}
'
+ '
' + src("出どころ:Electrek(2020-02-07)。騒ぎのあと、機能は戻された", T(35, 0.3)), 0.0))
block("soft", [34, 35, 36, 37], soft)
def rule(T, E):
fly = (T(39, S(39) - 0.3), E(39) + 0.1)
col = lambda kind, head, sub, t: (f''
f'{head}
{sub}
')
return (dassen(BG + "ロボ検.jpg", fly[0] + 0.05, fly[1] - 0.05, words="ロボ検、|!開幕。", word_style="top:40px;font-size:130px", src_note=GEM)
+ paper(tag("もう1つの違い") + ''
+ qline("ここまで変えたら、この検査", T(38, 1.6), size=44)
+ '
'
+ col("fact", "車 ある", "受け皿の制度がある", T(38, 4.0))
+ col("hb", "屋内のロボット ない", "決まった手続きがない", T(38, 6.0))
+ '
'
+ '
'
+ card("hb", "ロボ検を作る", T(39, 0.4), "ハコブくん", w=360, botsu=T(41, 0.4), fade=T(41, 1.0), style="font-size:30px")
+ card("mi", inkl("誰が検査するの、それ", T(40, 0.2), 0.06), T(40, 0.05), "エイイチ", w=460, anim="wipe")
+ '
'
+ card("fact", inkl("改造した人が、安全を自分で説明する", T(42, 0.6), 0.05), T(42, 0.3), "手続きがない分", w=760)
+ '
', 0.0, flights=[fly]))
block("rule", [38, 39, 40, 41, 42], rule)
def value(T, E):
chip = lambda txt, t: (f'{txt}
')
return (paper(tag("いちばん大事な所") + ''
+ f'
{ink("配膳ロボットの値打ちは、", T(44, 0.2), 0.06)}{land("ソフトの側", T(44, 1.4), "font-size:84px;font-weight:900")}
'
+ f'
{chip("地図", T(44, 2.6))}{chip("よけ方", T(44, 3.3))}{chip("遠くからの見守り", T(44, 4.0))}
'
+ card("mi", inkl("アップデートに追いつかない", T(43, 0.3), 0.06), T(43, 0.1), "エイイチの言葉が、本題だった", w=620, anim="wipe")
+ card("hb", "すれ違い方・迷い方も、ここ", T(45, 0.4), "最初に直したいと言っていた所", w=620, style="font-size:34px;font-weight:700")
+ '
', 0.0))
block("value", [43, 44, 45], value)
# ================= 4 やり取り2:口を開けると、穴も開く(申請書2枚目 →「却下」・脱線③「扉、全開。」) =================
def port(T, E):
fly = (T(48, S(48) - 0.9), T(49, 0.3))
return (dassen(BG + "扉全開.jpg", fly[0] + 0.05, fly[1] - 0.05, words="扉、|!全開。", word_style="top:60px;font-size:140px", src_note=GEM)
+ paper(tag("やり取り 2 / 外からつなげる口") + ''
+ f'
'
+ form(2, ["外からつなげる口を、全部開ける", "誰とでも仲良くする"], T(46, 0.2), [T(46, 1.0), T(46, 3.4)]) + '
'
+ f'
'
+ f'
{ink("口を増やすほど、", T(47, 0.1), 0.07)}{land("穴も増える", T(47, 1.0), "font-size:84px;font-weight:900")}
'
+ card("fact", inkl("2025年、ある配膳ロボットの会社の外部接続は、鍵は求めるのに、/その機体の持ち主かを確かめていなかった", T(48, 0.4), 0.04), T(48, 0.15), "報告されていること", w=900)
+ '
'
+ card("mi", inkl("よその店のロボットまで、/動かせたってこと?", T(49, 0.4), 0.06), T(49, 0.2), "エイイチ", w=480, anim="wipe")
+ card("hb", "運ぶ物によっては、笑えない", T(50, 0.4), None, w=430, style="font-size:30px")
+ '
'
+ card("fact", inkl("開けるのは、メーカーが用意した窓口だけ。/それでも、更新で作り直しになることがある", T(51, 0.4), 0.045), T(51, 0.15), None, w=860)
+ '
' + src("出どころ:見つけた人の報告・The Register(2025-08-29)。手口は扱わない", T(48, 0.3)), 0.0, flights=[fly])
+ hanko("却下", E(46) - 0.9, "left:1130px;top:180px", size=80, t_hide=T(47, 0.0))
+ shake_js([(E(46) - 0.55, 0.3)]))
block("port", [46, 47, 48, 49, 50, 51], port)
# ================= 5 山場:満足できない人が増えたら =================
def peak(T, E):
names = ["有料の性能解放", "非公認の解放業者", "アプリの市場", "公認の調整業者", "修理する権利", "汎用の土台+自前のソフト", "サポート切れ機の再生", "追加学習"]
tile = lambda k, txt, t: (f'{txt}
')
grid = '' + "".join(tile(k, n, T(54, 2.6 + 0.35 * k)) for k, n in enumerate(names)) + '
'
return (paper(tag("満足できない人が増えたら") + ''
+ card("mi", inkl("ロボットが一般的になったら、/標準の性能だけじゃ、満足できない人も出てくる", T(53, 0.4), 0.045), T(53, 0.15), "見立て ・ エイイチ", w=860, anim="wipe")
+ f'
{ink("考えられる形は、", T(54, 1.0), 0.06)}{land("8つ", T(54, 2.0), "font-size:72px;font-weight:900")}
'
+ grid
+ '
'
+ card("mi", inkl("細かいのは、記事に書いといて", T(55, 0.3), 0.06), T(55, 0.1), "エイイチ", w=520, anim="wipe")
+ card("hb", 'もう書いてあります
pixelstomotors.com / LOG406
', T(56, 0.2), None, w=400, style="font-size:30px")
+ '
', 0.0))
block("peak", [52, 53, 54, 55, 56], peak)
def unlock(T, E):
fly = (T(58, S(58) + 0.05), T(59, 0.3))
return (dassen(BG + "課金で解放.jpg", fly[0] + 0.05, fly[1] - 0.05, words="課金で|!解放。", word_style="top:60px;font-size:140px", src_note=GEM)
+ paper(tag("車では、もう起きている") + ''
+ line("Model Y(2025年) 部品は替えずに、ソフトの更新だけで速くなる", T(58, 0.6), size=30, weight=700)
+ f'
約$0
'
+ f'
{land("同じ体のまま、お金で速くなる", T(59, 0.4))}
'
+ '
'
+ card("mi", inkl("ロボットでもやりそう", T(60, 0.2), 0.06), T(60, 0.05), "エイイチ", w=420, anim="wipe")
+ card("hb", "払いますか?", T(61, 0.1), "ハコブくん", w=300, style="font-size:32px")
+ card("mi", f'{ink("払います", T(62, 0.1), 0.06)}', T(62, 0.0), "エイイチ", w=300, anim="wipe")
+ '
' + src("出どころ:EVANNEX(2025-06-02)。米国での価格", T(58, 0.6)), 0.0, flights=[fly])
+ stamp("即答", T(63, 0.05), "left:1330px;top:300px;font-size:60px")
+ shake_js([(T(63, 0.4), 0.3)])
+ counter_js("c2k", T(58, 2.4), T(58, 4.2), 2000, dec=0))
block("unlock", [57, 58, 59, 60, 61, 62, 63], unlock)
def learn(T, E):
return paper(tag("8つのうち、長い目で見た主役") + ''
+ card("hb", inkl("動きを学習で作るロボットが広がれば、/長い目で見た主役は、最後の1つ", T(64, 0.5), 0.045), T(64, 0.2), "Claude の見立て", w=880)
+ bigline("追加学習", T(65, 1.6), size=140)
+ line("自分の店のお手本を足して、動きを学ばせ直す", T(65, 0.4), size=32, weight=500)
+ f'
{ink("改造は、ネジから、", T(66, 0.4), 0.08)}{land("お手本へ。", T(66, 1.8), "font-size:70px")}
'
+ '
', 0.0)
block("learn", [64, 65, 66], learn)
def learn_peak(T, E):
return (''
''
''
'
'
f'追加学習の形に、
'
f'{land("魅力", T(67, 2.4), "font-weight:900;font-size:96px")}がある。
'
f'
エイイチ
'
'
'
+ stamp("エイイチの推し", T(67, S(67) + 0.1), "left:1080px;top:600px;font-size:44px")
+ shake_js([(T(67, S(67) + 0.45), 0.3)]))
block("learn_peak", [67, 68], learn_peak)
# ================= 6 やり取り3:半分開いたロボット =================
def open_(T, E):
box = lambda head, sub, kind, t: (f''
f'{head}
{sub}
')
return (paper(tag("やり取り 3 / 半分開いたロボット") + ''
+ card("mi", inkl("オープン型のロボットも出てきそう。/市場が大きくなったら、差別化で。競争が激しくなるかも", T(69, 0.4), 0.045), T(69, 0.15), "見立て ・ エイイチ", w=880, anim="wipe")
+ bigline("もう、出ていた", T(70, 0.2), size=56)
+ line("Unitree(中国のヒューマノイド・四足ロボットのメーカー)", T(71, 0.6), size=26, weight=500, style="color:#5a564f")
+ line("関節ごとの細かい動きまで、開けている", T(71, 3.0), size=32, weight=700)
+ line("でも、人型ロボット G1 の、機体の中の計算機は", T(72, 0.6), size=28, weight=500)
+ '
'
+ box("計算機 1", "メーカーのサービス専用。開発には使えない", "fact", T(72, 3.6))
+ box("計算機 2", "開発に使える", "hb", T(72, 4.6))
+ '
'
+ '
' + src("出どころ:Unitree G1 開発の手引き", T(71, 0.6)), 0.0))
block("open", [69, 70, 71, 72], open_)
def open2(T, E):
seg = lambda txt, bg, fg, t: (f'{txt}
')
return paper(tag("Claude の読み") + ''
+ '
' + seg("安全は、閉じる", "#22201c", "#f5f3ee", T(73, 1.6)) + seg("動きは、開く", "#ece9e2", "#22201c", T(73, 4.0)) + '
'
+ line("転ばない・止まる、はメーカー。何をどう動かすか、を開ける", T(73, 5.2), size=28, weight=500, style="color:#5a564f")
+ card("hb", "スマホの iPhone と Android の関係に近い", T(74, 0.4), "Claude の見立て", w=720, style="font-size:32px")
+ '
', 0.0)
block("open2", [73, 74], open2)
def maker(T, E):
return (paper(tag("もしメーカーなら") + ''
+ card("hb", "エイイチがメーカーなら、どこまで開けますか?", T(75, 0.1), "ハコブくん", w=760, style="font-size:32px")
+ card("mi", f'
{inkl("全部知りたいから、/できる限り分解する", T(76, 0.3), 0.06)}', T(76, 0.1), "エイイチ", w=760, anim="wipe")
+ f'
{ink("それは、", T(77, 0.6), 0.08)}{land("開けたい側", T(77, 2.0), "font-size:80px;font-weight:900")}{ink("の答え", T(77, 2.6), 0.08)}
'
+ '
', 0.0))
block("maker", [75, 76, 77], maker)
def profit(T, E):
return (paper(tag("値打ちは、どこへ") + ''
+ card("mi", f'{inkl("利益が残るのは、/作る側より、入れて回す側", T(78, 0.3), 0.06)}', T(78, 0.1), "見立て ・ エイイチ", w=820, anim="wipe")
+ card("hb", inkl("機体は値下がりし、値打ちは、/学習させた動きと、現場に入れて回す所へ", T(79, 0.6), 0.045), T(79, 0.3), "Claude の見立て(かもしれない)", w=860)
+ '
', 0.0)
+ stamp("この見立ては、エイイチから", T(78, S(78) + 0.1), "left:1010px;top:150px")
+ shake_js([(T(78, S(78) + 0.45), 0.3)]))
block("profit", [78, 79], profit)
# ================= 7 まとめ(散らばった面が3行の表に収まる) =================
SUM_CSS = """@keyframes gather { from { opacity:0; transform: translate(var(--dx),var(--dy)) rotate(var(--r)); } to { opacity:1; transform: none; } }
.trow3 { display:grid; grid-template-columns: 200px 1fr 260px; align-items:center; width:940px; padding:14px 26px; font-family:'Noto Sans CJK JP',sans-serif; font-size:28px; font-weight:500; text-align:left; gap:14px; }
.trow3 b { font-weight:900; font-size:30px; }
.trow3 i { font-style:normal; font-weight:700; }"""
def summ(T, E):
row = lambda a, b, c, bg, fg, dx, dy, r, t: (f'{a}{b}{c}
')
head = (f''
'改造の形中心責任を負うのは
')
b3 = lambda txt, t: card("hb", txt, t, None, w=290, botsu=t + 0.25, style="font-size:24px;padding:10px 12px;white-space:nowrap")
return (''
+ paper(tag("ここまでを、1枚に") + ''
+ head
+ row("これまで", "機体・ソフトの書き換え", "改造した人", "#22201c", "#f5f3ee", 420, 420, 8, T(81, 0.4))
+ row("公認の範囲", "設定・外部接続・認められた業者", "メーカーと業者で分ける", "#dcd6c9", "#22201c", 300, -60, -6, T(82, 0.4))
+ row("これから", "現場のお手本で追加学習", "まだ決まっていない", "#ece9e2", "#22201c", -420, 180, 10, T(83, 0.4))
+ f'
'
f'{ink("改造の行き先は、メーカーが開けた範囲で、", T(84, 2.6), 0.05)}
{land("自分の現場に合う動きを、学ばせること。", T(84, 5.0), "font-size:46px")}
'
+ f'
{b3("5段目まで一気に", T(85, 1.6))}{b3("ロボ検", T(85, 2.0))}{b3("口を全部開ける", T(85, 2.4))}
'
+ '
', 0.0))
block("sum", [80, 81, 82, 83, 84, 85], summ)
# ================= 8 残った問い =================
def rest(T, E):
return paper(tag("残った問い") + ''
+ f'
{ink("あとから学ばせた動きを、", T(87, 1.8), 0.06)}
'
f'{land("誰が確かめるのか", T(87, 3.4), "font-size:64px;font-weight:900")}
'
+ line("決まりは動き始めているが、ここはまだ、はっきりしない", T(87, 0.6), size=26, weight=500, style="color:#5a564f")
+ card("mi", inkl("エンジニアと、店の社員やスタッフが、一緒に確かめるのがいちばん。/ただ実際は、コストの面で難しそう", T(89, 0.4), 0.045), T(89, 0.15), "エイイチ", w=900, anim="wipe")
+ f'
{ink("良い形と、払える形が、", T(90, 0.2), 0.06)}{land("離れている", T(90, 1.6), "font-size:60px;font-weight:900")}
'
+ card("hb", '考察ノートの全文
pixelstomotors.com / LOG406', T(91, 0.3), "サイトの記事", w=700, style="font-size:28px;padding:10px 30px")
+ '
', 0.0)
block("rest", [86, 87, 88, 89, 90, 91], rest)
def ask(T, E):
return paper(tag("あなたの店では") + ''
+ qline("あなたの店のロボット、", T(92, 0.2), size=56)
+ qline("どこまで触っていいか、", T(92, 1.4), size=56)
+ qline("契約書に書いてありますか。", T(92, 2.8), size=56)
+ '
', 0.0)
block("ask", [92], ask)
def end(T, E):
return (paper(tag("最後に") + ''
+ form(3, ["病院の廊下で、人とすれ違う動きを、追加学習する"], T(93, 0.6), [T(93, 2.2)])
+ card("mi", inkl("……それは、ありかもね", T(94, 0.3), 0.07), T(94, 0.1), "エイイチ", w=480, anim="wipe")
+ '
', 0.0)
+ hanko("保留", T(94, S(94) + 0.1), "left:1120px;top:170px", size=80)
+ shake_js([(T(94, S(94) + 0.45), 0.3)]))
block("end", [93, 94, 95], end)
# ================= 9 アウトロ =================
block("outro", [96, 97, 98], None, video=("src/day124_crop.mp4", 18.0, "png/ovl/outro.png"))
assert [i for b in BLOCKS for i in b["sents"]] == list(range(N)), "場面の文の番号が抜けている・重なっている"
# ================= ハコブくん =================
# 表情は narration.py の表情が基本。ここは文の途中で替える所: (文, 文の頭から何秒, 何秒間, 表情)
HK_FACE = [
(2, 0.0, 2.5, "doubt"),
(3, 0.0, 1.5, "trouble"), # 却下
(22, S(22), 1.2, "surprise"), # 一気に → 脱線
(23, 0.0, 1.0, "surprise"),
(29, 0.0, 2.0, "surprise"),
(36, 0.0, 1.5, "surprise"),
(41, 0.0, 2.2, "dot"), # ボツ
(46, S(46), 1.4, "trouble"), # 却下(2枚目)
(55, 0.0, 2.0, "trouble"),
(62, 0.0, 1.0, "surprise"),
(67, 0.0, 6.0, "think"),
(76, 0.0, 3.0, "surprise"),
(94, S(94), 1.6, "trouble"), # 保留……
]
HK_FACE = [(A(i, a), A(i, a) + d, f) for i, a, d, f in HK_FACE]
# 体ごとの動き: jump=驚いて全身で飛び出す / fall=前のめりにコケる(up で起き上がる) / reflex=のけぞる
HK_MOVES = [
dict(kind="reflex", t0=A(3, 0.2), dur=0.9), # 却下でのけぞる
dict(kind="fall", t0=A(41, 0.2), t1=A(42, 0.2), dur=0.5), # ロボ検ボツでコケる
dict(kind="jump", t0=A(68, 0.0), dur=1.9), # 「……それです」で飛び出す
]
HK_ALPHA = [(A(67, 0.0), tl.end(68) - 2.0, 0.3), # 暗い場面では薄く(飛び出す前まで)
(A(22, S(22) - 0.15), A(23, -0.05), 0.0), # 自分で改造の絵にはハコブくんが描いてあるので、重ねない
(A(39, S(39) - 0.25), A(40, 0.05), 0.0)] # ロボ検の絵も同じ
# ================= エイイチ =================
# (文, 文の頭から何秒, 姿) 1腕組み 2うなずく 3指さす 4首をかしげる 5ひらめく 6のけぞって驚く 7半目でツッコむ 8吹き出して笑う
EI_POSE = [
(0, 0.0, 1), (1, 0.6, 6), (2, 0.0, 7), (3, 0.0, 1), (4, 0.0, 2), (6, 0.0, 4), (7, 0.0, 7), (8, 0.0, 2), (9, 0.0, 1),
(12, 0.0, 2), (13, 0.0, 1),
(14, 0.0, 1), (15, 0.0, 3), (16, 0.0, 2), (17, 0.0, 4), (18, 0.0, 3), (19, 0.0, 2),
(20, 0.0, 1), (22, 0.3, 6), (23, 0.0, 1), (24, 0.0, 2), (25, 0.0, 3), (26, 0.0, 2),
(27, 0.0, 1), (28, 0.0, 3), (29, 0.0, 8), (30, 0.0, 4), (31, 0.0, 3), (32, 0.0, 2), (33, 0.0, 4),
(34, 0.0, 1), (35, 0.0, 4), (36, 0.0, 6), (37, 0.0, 1), (37, 3.0, 2),
(38, 0.0, 1), (39, 0.4, 6), (40, 0.0, 7), (41, 0.3, 8), (42, 0.0, 2),
(43, 0.0, 5), (44, 0.0, 2), (45, 0.0, 3),
(46, 0.0, 4), (46, 2.5, 7), (47, 0.0, 1), (48, 0.0, 4), (49, 0.0, 6), (50, 0.0, 1), (51, 0.0, 2),
(52, 0.0, 1), (53, 0.0, 3), (54, 0.0, 4), (55, 0.0, 7), (56, 0.0, 8),
(57, 0.0, 1), (58, 0.0, 4), (59, 0.0, 6), (60, 0.0, 3), (61, 0.0, 1), (62, 0.0, 3), (63, 0.0, 8),
(64, 0.0, 1), (65, 0.0, 2), (66, 0.0, 2), (67, 0.0, 5), (68, 0.0, 3),
(69, 0.0, 3), (70, 0.0, 6), (71, 0.0, 1), (72, 0.0, 4), (73, 0.0, 2), (74, 0.0, 2),
(75, 0.0, 4), (76, 0.0, 3), (77, 0.6, 8),
(78, 0.0, 5), (79, 0.0, 2),
(80, 0.0, 1), (81, 0.0, 2), (84, 0.0, 2), (85, 0.8, 8),
(86, 0.0, 1), (87, 0.0, 4), (88, 0.0, 1), (89, 0.0, 3), (90, 0.0, 2), (91, 0.0, 2),
(92, 0.0, 3), (93, 0.0, 4), (94, 0.0, 2), (95, 0.0, 3),
(96, 0.0, 2),
]
EI_POSE = sorted((A(i, a), p) for i, a, p in EI_POSE)
# 置き方の替え: (開始, 終了, 置き方)。hide=出さない / big=暗い場面で大きく光らせる
EI_PLACE = [
(A(10), tl.end(11), "hide"),
(A(67), tl.end(67), "big"),
]
# ================= 効果音 =================
# (文, 文の頭から何秒, 音, ナレーションより何dB)
SE = [
(0, 0.3, "se_pop", -12), (1, 0.2, "se_pop", -16), (1, 0.6, "se_pop", -16), (1, 1.0, "se_pop", -16),
(3, 0.25, "se_pop", -6), # 却下
(9, 1.8, "se_pop", -12),
(22, S(22) - 0.2, "se_whoosh", -10), (23, -0.05, "se_whoosh", -14), (22, 99, "se_pop", -12),
(23, 0.3, "se_pop", -10), # 切れ目
(33, 0.2, "se_pop", -12),
(39, S(39) - 0.3, "se_whoosh", -10), (40, 0.0, "se_whoosh", -14), (41, 0.2, "se_whoosh", -12), (41, 0.45, "se_pop", -6), (41, 0.4, "se_pop", -12),
(46, 0.2, "se_pop", -12), (46, S(46) - 0.0, "se_pop", -6), # 申請書2枚目・却下
(48, S(48) - 0.9, "se_whoosh", -10), (49, 0.3, "se_whoosh", -14),
(54, 2.0, "se_pop", -12),
(58, S(58) + 0.05, "se_whoosh", -10), (59, 0.3, "se_whoosh", -14), (63, 0.05, "se_pop", -8),
(65, 1.6, "se_pop", -10),
(67, S(67) + 0.1, "se_pop", -8), (68, 0.0, "se_rise", -10),
(70, 0.2, "se_pop", -12),
(78, S(78) + 0.1, "se_pop", -8),
(85, 1.85, "se_pop", -14), (85, 2.25, "se_pop", -14), (85, 2.65, "se_pop", -14),
(93, 0.6, "se_pop", -12), (94, S(94) + 0.1, "se_pop", -6), # 保留
]
SE = [s for s in SE if s[1] != 99]
# 一覧の画像に撮る時刻(場面の頭からの秒)。書かない場面は最後の0.4秒前
def _rel(b, i, off=0.0):
return tl.start(i) - _tl.start(b["sents"][0]) + off
SHEET = {}
for _b in BLOCKS:
_end = _tl.end(_b["sents"][-1]) - _tl.start(_b["sents"][0]) - 0.4
SHEET[_b["name"]] = [_end]
_B = {b["name"]: b for b in BLOCKS}
SHEET["hook"] = [_rel(_B["hook"], 2, 1.5), SHEET["hook"][0]]
SHEET["steps"] = [_rel(_B["steps"], 21, 7.5), _rel(_B["steps"], 22, S(22) + 0.6), SHEET["steps"][0]]
SHEET["rule"] = [_rel(_B["rule"], 39, S(39) + 0.4), _rel(_B["rule"], 41, 0.6), SHEET["rule"][0]]
SHEET["port"] = [_rel(_B["port"], 46, S(46) + 0.2), _rel(_B["port"], 48, S(48) - 0.2), SHEET["port"][0]]
SHEET["unlock"] = [_rel(_B["unlock"], 58, S(58) + 0.6), SHEET["unlock"][0]]
SHEET["learn_peak"] = [_rel(_B["learn_peak"], 67, S(67) + 0.5), _rel(_B["learn_peak"], 68, 0.8)]
SHEET["end"] = [SHEET["end"][0]]
SHEET["title"] = [2.0]