# -*- coding: utf-8 -*- # 動画「ハコブくんの3D」(3Dを作る順番の7手順)の場面。2026-11-13 1版 PATTERN = 2 # 大文字型(4-4c)。2026-11-13 エイイチが決定(大文字型+動きあり) MOVE = True # 背景の映像は Unity の場面の寄り(KIOXIA service_sim/unity/closeup/ 依頼162・166・171)と、病院の1日(hospital161 1便の通し) # 出どころの札は使わない(全部エイイチの記録) import os as _os, sys as _sys, json as _json, base64 as _b64 _here = _os.path.dirname(_os.path.abspath(__file__)) for _c in (_os.environ.get("P2M_BASE", ""), _os.path.join(_here, "..", "base"), _os.path.join(_here, "..", "_制作ベース")): if _c and _os.path.exists(_os.path.join(_c, "scene_kit.py")): _sys.path.insert(0, _c); break from scene_kit import * from scene_kit import _p2page from motion_kit import use_motion # --- 場面ごとの、文の始まりの秒(声の長さから)--- _idx = _json.load(open(_os.path.join(_here, 'tts/index.json'))) if _os.path.exists(_os.path.join(_here, 'tts/index.json')) else [] _LINES = {} for _o in _idx: _d = _LINES.setdefault(_o['scene'], []) _t0 = (_d[-1][0] + _d[-1][1] + _d[-1][2]) if _d else 0.0 _d.append((_t0, _o['dur'], _o['pause'])) def at(scene, k=0, f=0.0): """その場面の k 番目の文の、読み上げの f 割の所(秒)""" L = _LINES.get(scene) if not L: return 0.3 k = min(k, len(L) - 1) return round(L[k][0] + L[k][1] * f, 2) def scene_len(scene): L = _LINES.get(scene) return (L[-1][0] + L[-1][1] + L[-1][2]) if L else 4.0 # --- 出どころの札(左上)--- FUDA_CSS = """ .fuda{position:absolute;left:140px;top:96px;display:flex;align-items:center;gap:14px;font-size:30px;font-weight:700;color:#0D0D0D} .fuda .k{padding:6px 18px;border-radius:999px;font-size:28px;font-weight:900} .fuda .k.hap{background:#0D0D0D;color:#fff} .fuda .k.news{background:#1A6C98;color:#fff} .fuda .k.ken{border:3px solid #888888;color:#555} .fuda .k.ei{background:#C4471F;color:#fff} .fuda .k.clip{background:#0D0D0D;color:#fff} .fuda .k.chosa{background:#5A5A5A;color:#fff} """ FUDA_NAME = {"hap": "会社の発表", "news": "ニュース", "ken": "論文や本", "ei": "エイイチの記録と見立て", "clip": "公開されている実物の動画", "chosa": "調査会社の推計"} def fuda(kind, who): return f'
{FUDA_NAME[kind]}{who}
' # --- エイイチ(ドット絵)--- def _uri(p): return 'data:image/png;base64,' + _b64.b64encode(open(_os.path.join(_here, p), 'rb').read()).decode() import ei_talk as _talk EI = {k: (_talk.base_uri(k), _talk.base_uri(k, True)) for k in ('udegumi', 'unazuku', 'yubisasu', 'hirameku')} # エイイチが話す文(narration.py の EI_LINES)→ (場面, 場面の中で何番目の文か, 文番号) import importlib.util as _ilu _sp = _ilu.spec_from_file_location('_narr_here', _os.path.join(_here, 'narration.py')) _nm = _ilu.module_from_spec(_sp); _sp.loader.exec_module(_nm) _EIL = getattr(_nm, 'EI_LINES', []) _EI_SPEAK = {} _cnt = {} for _o in _idx: _k = _cnt.get(_o['scene'], 0); _cnt[_o['scene']] = _k + 1 if _o['i'] in _EIL: _EI_SPEAK.setdefault(_o['scene'], []).append((_k, _o['i'])) EI_CSS = """ .eiw{position:absolute;right:10px;bottom:0px;width:420px;height:420px;overflow:hidden} .eiw img{position:absolute;left:0;top:8px;width:420px;height:420px;image-rendering:pixelated} .ein{position:absolute;right:40px;bottom:346px;width:300px;text-align:center;font-size:30px;font-weight:900;color:#0D0D0D} .ein span{display:inline-block;background:#F5F5F0;border:3px solid #D6D6D1;border-radius:999px;padding:4px 20px} """ def _blinks(start, end): t, out = start, [] while t < end - 0.4: out.append(round(t, 2)); t += 3.3 return out def ei_wipe(pose, scene, pop=None, name=None, poses=None): """右下の小窓。pop=秒 で、ぽんと出る。poses=[(秒, 絵)] で途中で絵を替える(指さしは大事な所だけ。2026-10-28 エイイチの指示)。 話している間は口と目を声に合わせて動かし、話していない間はまばたきだけする。 絵ごとにまとまり(div)を作り、替える時刻に前のまとまりを消して(out)、次を出す(fade)。絵の透明な所から前の絵が見えないように""" seq_p = sorted(poses or [(0, pose)]) if seq_p[0][0] > 0: seq_p = [(0, pose)] + seq_p L = scene_len(scene) + 1 segs = [(t, seq_p[k + 1][0] if k + 1 < len(seq_p) else L, p) for k, (t, p) in enumerate(seq_p)] spans, mouth, blinks = [], [], [] for k, i in _EI_SPEAK.get(scene, []): t0 = at(scene, k, 0.0) sq, end = _talk.mouth_seq(i, t0) spans.append((t0, end)); mouth += sq blinks += [(b, 0.13) for b in _talk.blink_times(t0, end)] mouth.sort() blinks += [(b, 0.15) for b in _blinks((pop or 0) + 1.3, scene_len(scene)) if not any(a0 - 0.2 <= b <= a1 + 0.2 for a0, a1 in spans)] out = '' for s0, s1, p in segs: ev = [] lv = ([l for (t, l) in mouth if t <= s0] or [1])[-1] if lv != 1 and p in _talk.MOUTH: ev.append((s0, f'')) for t, l in mouth: if s0 < t < s1 and p in _talk.MOUTH: ev.append((t, f'')) e = _talk.eye_patches(p) if p != 'unazuku' else None for b, d in blinks: if e and s0 <= b and b + d < s1: ev.append((b, f'')) ev.append((round(b + d, 2), f'')) ev.sort(key=lambda x: x[0]) inner = f'' + ''.join(h for _, h in ev) if s0 > 0: inner = f'
{inner}
' if s1 < L: inner = f'
{inner}
' out += inner a = f' data-a="pop" data-t="{pop}" data-d="0.35"' if pop is not None else '' nm = f'
{name}
' if name else '' return f'{nm}
{out}
' def add(page_html, *parts): return page_html.replace('', ''.join(parts) + '', 1) S = {} MOTION = {} O = {} VIDEO = {} # --- 背景の映像の上にカード(4-4e)--- HL, WARN = "#C4471F", "#62BFF5" CARD_CSS = f""" .cw{{position:absolute;left:0;right:0;top:215px;bottom:300px;display:flex;align-items:center;justify-content:center}} .cd{{background:rgba(8,8,8,.86);border-radius:30px;padding:46px 84px 52px;text-align:center;color:#fff;max-width:1400px;box-shadow:0 10px 40px rgba(0,0,0,.35)}} .cd .k{{font-size:42px;font-weight:700;color:#BDBDBD;margin-bottom:22px}} .cd .b{{font-weight:900;line-height:1.16}} .cd .s{{font-size:44px;font-weight:700;color:#D0D0D0;margin-top:26px;line-height:1.35}} .cd .hl{{color:{HL}}} .cd .wn{{color:{WARN}}} .cap{{position:absolute;left:140px;right:auto;top:110px;background:rgba(8,8,8,.72);color:#fff;font-size:30px;font-weight:700;padding:10px 22px;border-radius:10px}} .ff{{position:absolute;right:120px;top:170px;background:#C4471F;color:#fff;font-size:64px;font-weight:900;padding:10px 34px;border-radius:16px;letter-spacing:.04em}} """ def card(inner, cap="", pop=0.05): c = f'
{cap}
' if cap else "" return f'{c}
{inner}
' def C_word(lines, kicker="", sub="", size=130): k = f'
{kicker}
' if kicker else "" s = f'
{sub}
' if sub else "" return f'{k}
{"
".join(lines)}
{s}' def bg_card(name, file, ss, inner=None, cap="", ei_pose=None, dim=0.5, slow=1.0, ff=False, poses=None, img_first=None): """背景に映像を流し、上にカード(inner が None なら映像だけ・左上に何の映像か)。 img_first=割合:画像を見せたい場面。はじめはカードを出さず、背景を暗くしないで絵を見せ、場面のその割合の所でカードを出す (2026-11-13 エイイチの指示「画像の内容を見せたい時はカードを一時的に非表示に。センターから右にずらさない」)""" if inner: pop = 0.05 if img_first is not None: pop = round(scene_len(name) * img_first, 2) dim = 1.0 parts = card(inner, cap=cap, pop=pop) else: parts = f'' + (f'
{cap}
' if cap else '') if ff: parts += '
▶▶ 早送り
' if ei_pose: parts += ei_wipe(ei_pose, name, poses=poses) html = overlay(parts) MOTION[name] = dict(type="html", bg=dict(file=file, ss=ss, dim=dim, slow=slow), html=html) S[name] = html def ei(name, page_html, pose='udegumi', pop=None, poses=None): S[name] = add(page_html, ei_wipe(pose, name, pop=pop, poses=poses)) MOTION[name] = dict(type='html', html=S[name]) def img(name, file, title, w=1100, top=230, tag=None, credit=""): """画像を出し、上に題(ゴシックの太字。明朝・手書き風は使わない 2026-11-13 エイイチの指示)。画像は y 830 より上・x 400 より右に収める""" src = 'file://' + _os.path.join(_here, file) left = (1920 - w) // 2 body = (f'
' f'{title}
' f'') S[name] = page(body, tag=tag) MOTION[name] = dict(type='html', html=S[name]) L166 = "src/closeup166/closeup166_load.mp4" # ① 薬を積む(ハコブくんは新しい形) R171 = "src/closeup171/closeup171_receive.mp4" # ② 受け取る(本番のカメラ) R171S = "src/closeup171/closeup171_receive_side_half.mp4" # ② 受け取る(横のカメラ・0.5倍の速さ) R162 = "src/closeup162/closeup162_receive.mp4" D1 = "src/day1_blue.mp4" # 病院の1日(1便の通し・5倍速) CAP3 = "Unity|場面の寄り(3DCG)" TAGS = {n: f"{n}{w}" for n, w in ((1, "使い道"), (2, "絵と大きさ"), (3, "数字で形"), (4, "並べて直す"), (5, "骨と顔"), (6, "ポーズで確認"), (7, "Unity で動かす"))} STEPS7 = [("1", "使い道"), ("2", "絵と大きさ"), ("3", "数字で形"), ("4", "並べて直す"), ("5", "骨と顔"), ("6", "ポーズで確認"), ("7", "Unityで動かす")] # 0 何に使うためか bg_card("hook_use", L166, 4, C_word(['薬の受け渡しを', '近くで見せたい'], "病院の搬送ロボット", "", 120), cap=CAP3) img("hook_use2", "src/LOG496_01_handover_scene.png", "ハコブくん × 大城さん", w=1000, top=262) S["hook_joke"] = p2_word(['体が', 'できた!'], kicker="ハコブくん、ついに", kc="muted", size=200, sub="……腕は、まだない") bg_card("hook_why", D1, 8, C_word(['人は青い筒', 'ロボットは白い箱'], "これまでの病院のシミュレーション", "", 110), cap="Unity|病院の1日(5倍速)", dim=0.6) bg_card("hook_why2", D1, 24, C_word(['どう受け取るかは', '見えない'], "何回止まったかは、数えられる", "", 120), cap="Unity|病院の1日(5倍速)", dim=0.6) S["hook_joke2"] = p2_word(['筒から', '箱へ'], kicker="薬の受け渡し、これまでは", kc="muted", size=200, sub="絵としては、ちょっと寂しい") S["hook_today"] = p2_word(['作る順番', '7つ'], kicker="そのまま真似できる形で", kc="muted", size=200) # 1 自己紹介 O.update({ "title": overlay('
フィジカルAI解説
案内役:ハコブくん(AI音声)
'), "title_eiichi": overlay('
エイイチ
デザイナー/ロボティクスの研究・開発
'), "outro": overlay('
相談は概要欄の連絡先から
高評価・チャンネル登録/コメントはエイイチが全部読んでいます
'), }) VIDEO.update({ "title": ("src/day124_crop.mp4", 4.5, False), "title_eiichi": ("src/day124_crop.mp4", None, False), "outro": ("src/day124_crop.mp4", 18.0, False), }) S["toc"] = p2_word(['7つの', '手順'], kicker="3Dを作る順番", kc="muted", size=200) S["toc2"] = p2_list(STEPS7, kicker="7つの手順", size=62) S["toc_note"] = p2_word(['ぜんぶ', '3DCG'], kicker="ハコブくんはマンガのロボット", kc="muted", size=200, sub="実物は、ありません") # 手順1 S["s1"] = chapter2("手順1", "使い道を決める", "見た目の決まりも、先に") S["s1_use"] = p2_list([("1", "試算"), ("2", "病院の1日"), ("3", "場面を近くで"), ("4", "本物で撮る")], kicker="病院の搬送ロボットを見せる、4つの段", size=96, tag=TAGS[1]) ei("ei_steps", p2_word(['4つが', 'つながれば'], kicker="エイイチの言葉", kc="muted", size=170, sub="より伝わりやすくなる", tag=TAGS[1]), pop=0.1) S["s1_look"] = p2_pair("粘土の人形", "しすぎない", da="簡略化した見た目", db="デフォルメ", ca="blue", cb="red", mid="+", kicker="見た目の決まり", sub="人とロボットを、同じ決まりで", size=120, tag=TAGS[1]) S["s1_tip"] = p2_word(['なんか違う、で', '迷子にならない'], kicker="先に決めるコツ", kc="muted", size=120, tag=TAGS[1]) # 手順2 S["s2"] = chapter2("手順2", "絵と大きさ", "元の絵と、大きさをそろえる") img("s2_ref", "src/LOG496_04_oshiro_ref.png", "マンガの三面図", w=450, top=262, tag=TAGS[2]) S["s2_size"] = p2_pair("1.15m", "1.65m", da="ハコブくん(高さ)", db="大城さん(身長)", ca="blue", cb="red", mid="/", kicker="実寸で作る", sub="頭は、身長の約7分の1", size=190, tag=TAGS[2]) S["s2_joke"] = p2_word(['50センチ', '小さい'], kicker="ハコブくん、大城さんより", kc="muted", size=190, sub="見上げるのには、慣れています", tag=TAGS[2]) # 手順3 S["s3"] = chapter2("手順3", "数字で形を作る", "形を作るのは Blender") S["s3_split"] = p2_pair("Blender", "Unity", da="形を作る", db="動かして撮る", ca="blue", cb="red", mid="→", kicker="道具の役わけ", size=170, tag=TAGS[3]) S["s3_tool"] = p2_word(['画面を開かず', 'コードで'], kicker="Blender を Python で動かす", kc="muted", size=170, sub="コードを書いたのは、AI の Claude", tag=TAGS[3]) img("s3_robot", "src/hakobu_compare_v2_20261113.png", "Blender で、三面図から箱を組む", w=840, top=262, tag=TAGS[3]) img("s3_human", "src/nurse_oshiro_3d_preview.png", "Python で形を足し、Blender へ", w=1300, top=340, tag=TAGS[3]) S["s3_why"] = p2_word(['首の長さも', 'ただの数字'], kicker="形は、ぜんぶコードの中", kc="muted", size=170, sub="首が長い → 数字を1つ変えるだけ", tag=TAGS[3]) S["s3_joke"] = p2_word(['人間の首は', '縮まない'], kicker="ちなみに", kc="muted", size=170, sub="3Dの、いいところ", tag=TAGS[3]) # 手順4 S["s4"] = chapter2("手順4", "並べて直す", "絵に描き出して、見て、直す") img("s4_render", "src/LOG496_03_hakobu_preview.png", "確認用の絵も Blender で", w=520, top=262, tag=TAGS[4]) bg_card("s4_side", "src/bg_hakobu_before.mp4", 0, C_word(['1人だけ', '切り抜いた絵'], "2人を並べると、ハコブくんの角だけ硬い", "", 130), cap="直す前のハコブくん(三面図の角のまま)", dim=0.55, img_first=0.5) img("s4_fix", "src/LOG496_02_hakobu_before_after.png", "体の角を丸めた", w=840, top=262, tag=TAGS[4]) S["s4_tip"] = p2_word(['1人ずつより', '並べて見る'], kicker="手順4のコツ", kc="muted", size=170, tag=TAGS[4]) img("s4_oshiro", "src/oshiro_first.png", "大城さん、最初の形", w=560, top=262, tag=TAGS[4]) bg_card("ei_first", "src/bg_oshiro_first.mp4", 0, C_word(['首長すぎ', '肩いかつすぎ'], "エイイチの最初のひと言", "", 140), cap="大城さん、最初の形", ei_pose="udegumi", dim=0.5, img_first=0.4) bg_card("ei_first2", "src/bg_oshiro_first.mp4", 6, C_word(['バランス', '悪っ!'], "正直な感想", "", 200), cap="大城さん、最初の形", ei_pose="udegumi", dim=0.5, img_first=0.35, poses=[(at('ei_first2', 1, 0.1), 'yubisasu'), (round(at('ei_first2', 1, 0.1) + 3.0, 2), 'udegumi')]) # 早送り(最初の形と直した形を、目・肩・首に寄りながら切り替える。2026-11-13 エイイチの指示「目とか肩とかアップにして動きを」) bg_card("s4_ff", "src/ff_oshiro.mp4", 0, None, cap="最初の形 ⇄ 直した形", dim=1.0, ff=True) S["s4_ff2"] = p2_num("15", "回", kicker="作り直し", sub="首・肩・腕・目・Vネック", tag=TAGS[4]) S["s4_tip2"] = p2_pair("人", "AI", da="言葉で言う", db="数字に直す", ca="blue", cb="red", mid="→", kicker="直す所の伝え方", size=200, tag=TAGS[4]) # 手順5 S["s5"] = chapter2("手順5", "骨と顔", "動かすための見えない棒") S["s5_bone"] = p2_num("45", "本", kicker="大城さんの骨(見えない棒)", sub="Blender で入れる", tag=TAGS[5]) S["s5_name"] = p2_word(['骨の名前は', '決まりどおり'], kicker="ポイント|Unity の人型の決まり", kc="muted", size=160, sub="よその歩く動きを、そのまま当てはめられる", tag=TAGS[5]) img("s5_face", "src/LOG496_08_talk_scene.png", "目・眉・口5つ", w=1000, top=262, tag=TAGS[5]) S["s5_joke"] = p2_word(['眉は', 'ありません'], kicker="ハコブくんの顔は、画面", kc="muted", size=200, sub="困った顔は、気持ちでがんばる", tag=TAGS[5]) # 手順6 S["s6"] = chapter2("手順6", "ポーズで確認", "今日いちばん手こずった所") img("s6_pose", "src/LOG496_06_nurse_poses.png", "8つのポーズ", w=1040, top=262, tag=TAGS[6]) bg_card("ei_sleeve", "src/bg_torn1.mp4", 0, C_word(['袖が', '切れてるよ'], "エイイチから", "", 120), cap="袖が裂けた所(直す前のコードで描き出し直した絵)", ei_pose="udegumi", dim=0.85, img_first=0.5) S["s6_try1"] = p2_item("つなぎ目が裂けた", "袖を、別の部品に", no="1回目", size=130, tag=TAGS[6]) S["s6_try2"] = p2_item("中の肌が飛び出た", "袖の中の肌は、作らない", no="2回目", size=130, tag=TAGS[6]) S["s6_joke"] = p2_word(['見えない所は', '作らない'], kicker="3Dの世界", kc="muted", size=170, sub="なかなか、割り切っています", tag=TAGS[6]) bg_card("s6_try3", "src/bg_torn3.mp4", 0, C_word(['3回目', '肩がゆがむ'], "腕を上げると", "", 140), cap="3回目(直す前のコードで描き出した絵)", dim=0.85, img_first=0.5) img("s6_fix", "src/LOG496_07_sleeve_fixed.png", "袖をまるごと腕の骨に", w=1100, top=290, tag=TAGS[6]) S["s6_tip"] = p2_word(['ポーズの確認を', '飛ばさない'], kicker="動かすと、止まっている時には見えない所が壊れる", kc="muted", size=150, tag=TAGS[6]) S["s6_hakobu"] = p2_word(['袖の悩み', 'なし'], kicker="服を着ていないハコブくん", kc="muted", size=200, tag=TAGS[6]) # 手順7 S["s7"] = chapter2("手順7", "Unityで動かす", "Blender から Unity へ") S["s7_fbx"] = p2_pair("Blender", "Unity", da="形と骨", db="読み込んで動かす", ca="blue", cb="red", mid="FBX", kicker="バトンタッチ", size=170, tag=TAGS[7]) bg_card("ei_unity", L166, 1, C_word(['熱いっ!'], "Unity でここまでできるの", "", 200), cap=CAP3, ei_pose='udegumi', poses=[(at('ei_unity', 1, 0.1), 'hirameku'), (round(at('ei_unity', 1, 0.1) + 3.0, 2), 'udegumi')]) S["s7_mixamo"] = p2_num("33", "本", kicker="Mixamo から借りた動き", sub="歩く・うなずく・おじぎ など|ファイルは配らない", tag=TAGS[7]) bg_card("s7_scene", L166, 8, None, cap="Unity|薬剤部で、薬を積む(3DCG)", dim=1.0) bg_card("s7_scene2", R171S, 2, None, cap="Unity|届け先で、カードをかざして受け取る(3DCG・横から)", dim=1.0) bg_card("s7_ff", R171, 0, None, cap="Unity|細かい直し", dim=1.0, slow=0.3, ff=True) bg_card("s7_end", R162, 20, C_word(['手と顔が', '出てきた'], "筒と箱だった世界に", "", 150), cap=CAP3) # まとめ・これから S["sum"] = p2_word(['7つの手順', 'もう一度'], kicker="まとめ", kc="muted", size=180) S["sum2"] = p2_list(STEPS7, kicker="3Dを作る順番", size=62) S["sum_code"] = p2_word(['形のコードは', '記事に'], kicker="2人の形の作り方", kc="muted", size=170, sub="同じコードを動かせば、同じ形ができる") S["next"] = p2_word(['この3Dで', '何を見せる?'], kicker="これから", kc="muted", size=170) ei("ei_next", p2_word(['渡す・受け取る', 'トラブル・会話'], kicker="エイイチの言葉", kc="muted", size=140), pop=0.1) S["next2"] = p2_word(['作りはじめた', 'ばかり'], kicker="まだ", kc="muted", size=180, sub="本物の看護師さんに近いかは、これから確かめる") S["next_joke"] = p2_word(['廊下で', 'すれ違う'], kicker="次の場面", kc="muted", size=190, sub="ぶつからないように、がんばります") bg_card("site", R171S, 8, C_word(['pixelstomotors.com'], "作り方のコードと記録を、毎日公開", "", 120), cap=CAP3) # --- 白い紙の場面を減らす(2026-11-13 エイイチの指示「なるべく白背景の時間を減らして(意味ある形で)」)--- # 話していることに合う映像や3Dの絵を後ろに流し、文字はカードに載せる def C_list(items, kicker="", size=64, tool=None): """tool={番号: 道具} があれば、右に道具の札(準備・Blender・Unity)を出し、同じ道具の行をまとめて見せる""" TC = {"準備": "#8A8A8A", "Blender": "#E87D0D", "Unity": "#62BFF5"} rows = "" for n, t in items: tg = "" if tool: tl = tool.get(n, "") tg = f'{tl}' rows += f'
{n}{t}{tg}
' k = f'
{kicker}
' if kicker else "" return f'{k}
{rows}
' TOOL7 = {"1": "準備", "2": "準備", "3": "Blender", "4": "Blender", "5": "Blender", "6": "Blender", "7": "Unity"} def chap(name, no, word, sub, file, ss): bg_card(name, file, ss, C_word([f'{word}'], no, sub, 150), dim=0.45) HDIAG, HFRONT, HAND, TALK = "src/bg_hakobu_diag.mp4", "src/bg_hakobu_front.mp4", "src/bg_handover.mp4", "src/bg_talk.mp4" HCMP, OFIN, SFIX, POSES, FF = "src/bg_hakobu_cmp.mp4", "src/bg_oshiro_final.mp4", "src/bg_sleeve_fixed.mp4", "src/bg_poses.mp4", "src/ff_oshiro.mp4" bg_card("hook_joke", HDIAG, 0, C_word(['体が', 'できた!'], "ハコブくん、ついに", "……腕は、まだない", 170), img_first=0.3) bg_card("hook_joke2", D1, 40, C_word(['筒から', '箱へ'], "薬の受け渡し、これまでは", "絵としては、ちょっと寂しい", 170), cap="Unity|病院の1日(5倍速)", dim=0.6) bg_card("hook_today", R162, 2, C_word(['作る順番', '7つ'], "そのまま真似できる形で", "", 170), cap=CAP3) bg_card("toc", L166, 12, C_word(['7つの', '手順'], "3Dを作る順番", "", 170), cap=CAP3) bg_card("toc2", L166, 18, C_list(STEPS7, "7つの手順と、使う道具", 40, TOOL7), cap=CAP3) bg_card("toc_note", R171S, 0, C_word(['ぜんぶ', '3DCG'], "ハコブくんはマンガのロボット", "実物は、ありません", 170), cap=CAP3) chap("s1", "手順1|準備", "使い道を決める", "見た目の決まりも、先に", HAND, 0) bg_card("s1_use", D1, 15, C_list([("1", "試算"), ("2", "病院の1日"), ("3", '場面を近くで'), ("4", "本物で撮る")], "病院の搬送ロボットを見せる、4つの段", 80), cap="Unity|病院の1日(5倍速)", dim=0.55) bg_card("ei_steps", D1, 30, C_word(['4つが', 'つながれば'], "エイイチの言葉", "より伝わりやすくなる", 150), cap="Unity|病院の1日(5倍速)", ei_pose="udegumi", dim=0.55) bg_card("s1_look", HAND, 3, C_word(['粘土の人形', 'デフォルメしすぎない'], "見た目の決まり", "人とロボットを、同じ決まりで", 110), img_first=0.35) bg_card("s1_tip", HAND, 8, C_word(['なんか違う、で', '迷子にならない'], "先に決めるコツ", "", 120)) chap("s2", "手順2|準備", "絵と大きさ", "元の絵と、大きさをそろえる", TALK, 0) bg_card("s2_size", TALK, 3, C_word(['1.15m / 1.65m'], "実寸で作る|ハコブくん(高さ)・大城さん(身長)", "頭は、身長の約7分の1", 150), img_first=0.35) bg_card("s2_joke", TALK, 8, C_word(['50センチ', '小さい'], "ハコブくん、大城さんより", "見上げるのには、慣れています", 160)) chap("s3", "手順3|Blender", "数字で形を作る", "道具は Blender(形)と Unity(動き)", HCMP, 0) bg_card("s3_split", L166, 4, C_word(['Blender → Unity'], "道具の役わけ", "形を作る → 動かして撮る", 120), cap=CAP3) bg_card("s3_tool", HCMP, 4, C_word(['Blender', 'コードで動かす'], "3Dの形を作る無料のソフト", "画面は開かず Python で|コードは AI の Claude", 130), dim=0.45, img_first=0.35) bg_card("s3_why", OFIN, 0, C_word(['首の長さも', 'ただの数字'], "形は、ぜんぶコードの中", "首が長い → 数字を1つ変えるだけ", 150), dim=0.45) bg_card("s3_joke", OFIN, 8, C_word(['人間の首は', '縮まない'], "ちなみに", "3Dの、いいところ", 150), dim=0.45) chap("s4", "手順4|Blender", "並べて直す", "絵に描き出して、見て、直す", HAND, 4) bg_card("s4_tip", HAND, 10, C_word(['1人ずつより', '並べて見る'], "手順4のコツ", "", 150)) bg_card("s4_ff2", FF, 0, C_word(['15回'], "作り直し", "首・肩・腕・目・Vネック", 200), dim=0.45) bg_card("s4_tip2", OFIN, 4, C_word(['人 → AI'], "直す所の伝え方", "言葉で言う → 数字に直す", 170), dim=0.45) chap("s5", "手順5|Blender", "骨と顔", "動かすための見えない棒", POSES, 0) bg_card("s5_bone", POSES, 4, C_word(['45本'], "大城さんの骨(見えない棒)", "骨は Blender で入れる", 200), cap="8つのポーズ(骨で付けた姿勢)", dim=0.45, img_first=0.35) bg_card("s5_name", R162, 6, C_word(['骨の名前は', '決まりどおり'], "ポイント|Unity の人型の決まり", "よその歩く動きを、そのまま当てはめられる", 140), cap=CAP3) bg_card("s5_joke", HFRONT, 0, C_word(['眉は', 'ありません'], "ハコブくんの顔は、画面", "困った顔は、気持ちでがんばる", 170), img_first=0.35) chap("s6", "手順6|Blender", "ポーズで確認", "今日いちばん手こずった所", POSES, 6) bg_card("s6_try1", "src/bg_torn1.mp4", 0, C_word(['1回目', 'つなぎ目が裂けた'], "腕を前に出すと", "袖を、別の部品に", 130), cap="1回目(直す前のコードで描き出し直した絵)", dim=0.85, img_first=0.5) bg_card("s6_try2", "src/bg_torn2.mp4", 0, C_word(['2回目', '中の肌が飛び出た'], "腕を前に出すと", "袖の中の肌は、作らない", 130), cap="2回目(直す前のコードで描き出し直した絵)", dim=0.85, img_first=0.5) bg_card("s6_joke", SFIX, 0, C_word(['見えない所は', '作らない'], "3Dの世界", "なかなか、割り切っています", 150), dim=0.45, img_first=0.4) bg_card("s6_tip", POSES, 18, C_word(['ポーズの確認を', '飛ばさない'], "動かすと、止まっている時には見えない所が壊れる", "", 130), dim=0.45) bg_card("s6_hakobu", HFRONT, 6, C_word(['袖の悩み', 'なし'], "服を着ていないハコブくん", "", 170), img_first=0.35) chap("s7", "手順7|Unity", "Unityで動かす", "Blender から Unity へ", L166, 0) bg_card("s7_fbx", L166, 6, C_word(['Blender → Unity'], "バトンタッチ|FBX で渡す", "形と骨 → 読み込んで動かす", 120), cap=CAP3) bg_card("s7_mixamo", R162, 10, C_word(['33本'], "Mixamo から借りた動き", "歩く・うなずく・おじぎ など|ファイルは配らない", 200), cap=CAP3) bg_card("sum", R171S, 4, C_word(['7つの手順', 'もう一度'], "まとめ", "", 160), cap=CAP3) bg_card("sum2", R171S, 8, C_list(STEPS7, "3Dを作る順番と、使う道具", 40, TOOL7), cap=CAP3) bg_card("sum_code", HCMP, 8, C_word(['形のコードは', '記事に'], "2人の形の作り方", "同じコードを動かせば、同じ形ができる", 150), dim=0.45) bg_card("next", R162, 14, C_word(['この3Dで', '何を見せる?'], "これから", "", 150), cap=CAP3) bg_card("ei_next", R162, 18, C_word(['渡す・受け取る', 'トラブル・会話'], "エイイチの言葉", "", 130), cap=CAP3, ei_pose="udegumi") bg_card("next2", R171S, 12, C_word(['作りはじめた', 'ばかり'], "まだ", "本物の看護師さんに近いかは、これから確かめる", 150), cap=CAP3) bg_card("next_joke", R162, 24, C_word(['廊下で', 'すれ違う'], "次の場面", "ぶつからないように、がんばります", 160), cap=CAP3) use_motion(MOTION, VIDEO, O, _here, MOVE)