薬剤搬送ロボットの記事を表紙で伝える ― サムネイル9本を4コマと記事の画像に

この記事の素材(コード・ログ・データ 1ファイル・まとめてダウンロードあり)
AIで読む ChatGPT・Claude などで、この記事に質問できます

薬剤搬送ロボットのシリーズ(pixelstomotors.com/series1 )の記事サムネイル9枚を、白黒の英字の見出しを組んだ表紙から、4コママンガ『ハコブくん』の絵・記事に載せた画像・記事の図解に替え、記事の札の名前を「HRI」から「薬剤搬送ロボット」にした。 同じシリーズの古い記事は、絵や画面を全面に置いた文字のない表紙で並んでいて、新しい9本だけが別のシリーズのように見えていたためだ。替えるときは、同じ種類の表紙が一覧で続かないよう、同じ種類は2〜3枚までに抑えた。

替えたことで、次のことが変わった。

  • シリーズのページを開いた人が、9本の記事を、ほかの回と同じ「絵と画面の表紙」の並びの中で見られる。前は、9本だけが英字の見出しの表紙で、古い記事とは別のシリーズに見えていた
  • 記事の札が「HRI」から「薬剤搬送ロボット」になり、57本の記事のカードで、何のシリーズの記事かが日本語で分かる。HRI(Human-Robot Interaction、人とロボットの関わり)という言葉を知らない人には、札の意味が伝わっていなかった
  • 9枚は、スクリプト1本(make_hri_covers.py)で作り直せる。2コアの CPU で、素材の取得を含めて約10秒、取得済みなら約7秒。前の表紙を作るのにかかった時間は測っていない

サムネイルは、記事一覧のカードの上に出る絵のこと。薬剤搬送ロボットのシリーズは、病院で薬を運ぶロボット「ハコブくん」を題材に、人とロボットの関わりを設計してきた記録で、古い回(LOG1〜LOG171)の表紙は、ハコブくんのイラスト、画面の設計図、Unity の場面などを全面に置いた画像だった。ところが、7月以降に書いた9本(LOG267〜LOG322)だけは、ほかのシリーズと同じ、英字の見出しと図を組んだ白黒の表紙になっていた。

作り直す前のシリーズのページの記録の一覧。LOG322・321・311・308・307・305 の表紙が、白黒の地に英字の見出しを組んだ形で並んでいる。札は HRI
作り直す前の一覧の下の段。LOG303・269・267 の表紙も、白黒の英字の見出しと図の形

やったことの全体は次のとおり。

順やったことなぜ結果
1
9本の表紙を、記事の画像と、記事の図解を斜めに置いた画像に替えた。札を「薬剤搬送ロボット」にした
古い回の表紙は、文字のない全面の画像だった
斜めの図解が5枚、Fusion の暗い画面が3枚になり、似た表紙が並んだ
2
斜めの図解を2枚に絞って寄せ、3枚を4コマの扉絵に替えた。LOG308 はハコブくんの三面図にした
「同じようなサムネイルはNG」「2個くらいならOK」「斜めならもっとアップに」と指示を受けた
三面図は NG になった
3
LOG308 を、記事の中の Fusion の画面(最初に出てきた形)にした
3つの案を出したが、記事の中の画面を使うよう指示を受けた
9枚を公開した

この表紙の替え方には、4つの考えがある。

  1. 素材は、その記事に載っているものか、同じ世界の4コマから取る。描き起こさない
  2. 同じ種類の表紙は、一覧で2〜3枚までにする。カードは3列で並ぶので、同じ種類が続くと、どれも同じに見える
  3. 斜めに置く図解は、紙の外の余白を見せない。余白が見えると、画面の写真ではなく、置いた紙に見える
  4. 表紙に見出しの文字を入れない。古い回と同じく、絵と画面に語らせる

ここから下は、作業の記録になる。

作業ログ ― 薬剤搬送ロボットの古い記事の表紙を確かめた

依頼は次のとおり。

薬剤搬送ロボットハコブくん | Drug Delivery Robotページのこの白黒サムネイルを他のHRI記事のサムネイルのデザインに合わせてください。

HRIというカテゴリータグを「薬剤搬送ロボット」に変更

サイトのデータを見ると、シリーズの値が HRI の記事は57本あった。そのうち、表紙が HTML(/thumbs/r-*.html)なのは9本だけで、残りの48本は、Studio の時代に作った画像の表紙(cover)だった。48本の表紙を並べて見た。

古い回の表紙48枚を並べた一覧。ハコブくんのイラスト、4コマの扉絵、Figma の画面、色の見本、Unity の場面、FigJam の流れ図などが、文字のない全面の画像で並んでいる

古い回の表紙は、どれも文字を入れていない。ハコブくんのイラスト、斜めに置いた画面の設計図、Unity の場面のように、その回で作ったものが全面に出ている。9本の表紙もこの形にそろえることにした。

9本の記事のうち、記事の中に画像があるのは4本だった。Autodesk Fusion(3D CAD のソフト)の回3本には Fusion の画面、アバターの回の LOG322 にはロボット「ピカリくん」の三面図がある。残りの5本(LOG267・269・303・305・321)は、記事の中に画像がなく、HTML の図解だけだった。iCloud の作業フォルダと、サイトのアーカイブ(site_archive)も探したが、この5本はもともと HTML の表紙で始まっていて、画像の表紙は見つからなかった。

そこで、画像のない5本は、記事の図解をブラウザで描いて、斜めに置いた画像にした。古い回にも、Figma の画面を斜めに置いた表紙がある。

画像は、Cloudflare R2 に置く。Cloudflare R2 は、ファイルを置いておくクラウドの置き場所で、このサイトの記事の画像と動画はすべてここにある(GitHub には入れない)。記事の図解は、Playwright(ブラウザをプログラムから動かす道具)でページを開き、画面を撮って画像にした。

作業ログ ― 1回目:斜めの図解が5枚並び、NG になった

1回目は、記事の画像がある4本は記事の画像を切り出し、ない5本は図解を斜め -16° に回して置いた。札の値も HRI から薬剤搬送ロボットに替えて公開した。

公開した一覧を見て、次の指示を受けた。

この辺の同じようなサムネイルはNGです

2個くらいならOK

1回目に公開した一覧。LOG308・307 は Fusion の暗い画面、LOG305・303・269・267 は白っぽい図解を斜めに置いた画像で、似た表紙が6枚続いている

斜めの図解が5枚、Fusion の暗い画面が3枚になっていた。1枚ずつ見れば記事の中身だが、一覧では、白っぽい斜めの紙と、暗い画面が続いて、同じ種類の表紙が並んで見える。

ねらい:同じ種類の表紙を2〜3枚までにして、一覧で同じ見た目が続かないようにする。 変わったこと:斜めの図解を5枚から2枚に、残りの3枚を4コマの扉絵にした。 見込める効果:シリーズのページで、カードの並びが古い回と同じ「いろいろな絵と画面」の並びになる(見込み。並びは公開後のページの画面で確かめた)。

続けて、次の指示を受けた。

斜めならもっとアップにして余白がないようにしてください

斜めにした図解は、紙の外の灰色が四隅に見えていた。残す2枚(LOG303・305)は、倍率を 1.7〜1.9 から 3.0〜3.3 に上げ、灰色が出ないところまで寄せた。

作業ログ ― 4コママンガの扉絵から、記事の中身に近い3枚を選んだ

画像のない3本(LOG267・269・321)は、4コママンガ『ハコブくん』の扉絵から選んだ。4コマは同じ病院の世界の話で、扉絵は 1080×1350 の縦長の絵になっている。143話分の扉絵を並べて選んだ。

4コマの扉絵143枚の一覧。ハコブくんと看護師、患者、アバターのピカリくんが白黒の漫画の絵で描かれている
記事記事の中身選んだ扉絵
LOG267
病院の夜の搬送をデジタルツインで組む
第100話「夜の運び方」:夜の廊下を走るハコブくん
LOG269
予測と実際の差を見て、判断を直す仕組み
第80話「過去最高の配送件数」:タブレットを見る人と、「過去最高デス」と言うハコブくん
LOG321遠くにいる人が操るアバター
第110話「すぐ戻ります」:VR のゴーグルで操縦する人

縦長の絵から 16:9 の帯を切ると、題名の文字か、人の顔のどちらかが切れやすい。題名の文字を外し、顔が入る高さを1枚ずつ探した(第80話は上から330px、第110話は300px、第100話は560px)。

LOG308 は、記事の中にあるハコブくんの三面図に替えた。暗い Fusion の画面を3枚から2枚に減らすためだった。

作業ログ ― LOG308 の表紙を、記事の中の Fusion の画面にした

2回目を公開すると、LOG308 について次の指示を受けた。

これはNG。他の案を出して

NG になった LOG308 の表紙。白い地に、ハコブくんを前・横・後ろから描いた三面図

三面図は、古い回の最初の表紙(LOG1)とほとんど同じ絵で、しかも、Fusion で形にしようとした「元の絵」だった。記事は「Claude の指示だけでは形にならなかった」話なので、元の絵では中身が伝わらない。

3つの案を作った。

LOG308 の表紙の3案。A は4コマ第55話の、画面に「同一指示3回目:確認シマス」と出たハコブくん。B は廊下でハコブくんとベッドがすれ違う寸法図。C は車椅子の人と並ぶ寸法図

これに対して、記事のページの画面写真と一緒に、次の指示を受けた。

この辺使いなよ

LOG308 の記事のページ。「出てきたのは、角の丸い箱に穴が開いたものだった。イラストのハコブくんとは似ていない」の文の下に、板2枚と箱と青い面が並んだ Fusion の画面がある

記事で最初に Fusion に出てきた形(板2枚・箱・青い面)の画面で、記事の「指示だけでは形にならなかった」がそのまま見える。ウィンドウの中の作業画面だけを、形が真ん中に来るように切り出した(中心 1400, 880・幅 1500px)。Fusion の暗い画面は3枚(LOG307・308・311)になったが、3枚とも形が違う(板・ばらばらの部品・ボディ)。

表紙の素材の種類を、回ごとに図にした。上のボタンで、前・1回目・公開した形を切り替えられる。

作り直した後のシリーズのページは次のとおり。

作り直した後のシリーズのページの記録の一覧。LOG322 はピカリくんの三面図、LOG321 は VR で操縦する人の4コマ、LOG311 は Fusion のボディ、LOG308 は Fusion の板と箱、LOG307 は Fusion の板、LOG305 は寄せた図解。札は薬剤搬送ロボット
一覧の下の段。LOG303 は寄せた色のトークンの図解、LOG269 はタブレットを見る人とハコブくんの4コマ、LOG267 は夜の廊下を走るハコブくんの4コマ

作業ログ ― 札の名前を HRI から薬剤搬送ロボットにした

記事の札に出る文字は、記事のデータのシリーズの値そのものになっている。そこで、値を HRI から「薬剤搬送ロボット」に替えた。替えた所は次のとおり。

場所中身数
src/data/records/*.json
Studio から移した記事1本ずつのデータの series
57本
src/data/cards.json
記事一覧のカードのデータの series
57件
src/lib/records.ts
/series1 のページが、どのシリーズの値の記事を並べるか(SERIES_PAGES)
1か所
src/components/MangaPage.astro
4コマのページの下に出す「関連する記録」の値
1か所

ページの紹介文の「人とロボットの関わり(HRI)」のような、文の中の HRI はそのままにした。過去の記事のコードやログの中に出てくる series: 'HRI'(LOG344・347・351・460 など)も、その時点の記録なので替えていない。

前の HTML の表紙(public/thumbs/r-*.html)は消さずに残した。データの cover を null に戻し、thumb を前の値に戻せば、前の表紙に戻る。

作業ログ ― 表紙を作って入れる手順

この記事のコード・ログ・データは、1ファイルずつ下に置いています。全文はリンク先にあります。

ファイルの一覧(1本)

01_make_hri_covers.py.txt ― make_hri_covers.py(全文)― 9枚の表紙を作る・3,798字

この記事のコード・ログ・データは、1ファイルずつ下に置いています。全文はリンク先にあります。

ファイルの一覧(1本)

01_make_hri_covers.py.txt ― make_hri_covers.py(全文)― 9枚の表紙を作る・3,798字

使った素材の場所

  • 記事の画像:pixelstomotors.com/media/ の4枚(Fusion の画面3枚・ピカリくんの三面図)
  • 4コマの扉絵:p2m-site の src/data/manga/ep100.json・ep80.json・ep110.json の cover
  • 記事の図解:p2m-site の public/embeds/figma-design-token-consolidation-CwxN66LU.html・does-a-delivery-robot-help-nurses-t7lznGbR.html
  • 作った表紙:iCloud の デジタルツイン学習R2R2R/HRI_サムネイル_20261108/(R2 の hri-thumb-LOG{番号}.webp。作り直した回は -v2・-v3 を付けた新しい名前で入れた。upload_media.py は同じ名前の上書きをしないため)

make_hri_covers.py(全文)― 9枚の表紙を作る

作業の途中では、1枚ずつ値を変えて切り出した。公開した9枚の値を、このスクリプト1本にまとめた。動かし直すと、LOG307・311・322 は公開した画像とバイト単位で同じものができた。

01_make_hri_covers.py.txt3,798字・92行・python
"""薬剤搬送ロボット(/series1)の記事サムネイル9枚を作る(LOG473)。

前は HTML のサムネイル(白黒・英字の見出し)だった9本を、記事の画像・4コマの絵・記事の図解から作った
1920x1080 の webp に替える。素材はすべて公開中のサイト(pixelstomotors.com/media/ と p2m-site の public/embeds/)から取る。

使い方(p2m-site のフォルダで動かす):
  python3 make_hri_covers.py <出力フォルダ>
斜めに置く図解(LOG303・LOG305)は、同じフォルダの tilt.js(Playwright)で描く。
別のタブで開く保存

tilt.js(全文)― 記事の図解を斜めに置いて撮る

const {chromium}=require('playwright');const fs=require('fs');
const S=process.env.S;const jobs=JSON.parse(process.argv[2]);
(async()=>{const b=await chromium.launch();const p=await b.newPage({viewport:{width:1920,height:1080},deviceScaleFactor:1});
for(const [f,out,rot,sc,tx,ty] of jobs){
 const html=`<html><body style="margin:0;width:1920px;height:1080px;overflow:hidden;background:#e6e6e6">
 <iframe src="file://${process.cwd()}/public/embeds/${f}" style="position:absolute;left:320px;top:40px;width:1280px;height:1000px;border:0;background:#fff;box-shadow:0 30px 60px rgba(0,0,0,.18);transform:translate(${tx}px,${ty}px) rotate(${rot}deg) scale(${sc});transform-origin:center"></iframe></body></html>`;
 fs.writeFileSync(S+'/w.html',html);await p.goto('file://'+S+'/w.html');await p.waitForTimeout(1200);
 await p.screenshot({path:S+'/out/'+out+'.png'});}
await b.close();})();

make_hri_covers.py を動かした出力

$ cd p2m-site && python3 make_hri_covers.py hri_covers
hri-thumb-LOG267-v2.webp (1920, 1080) 187264 bytes
hri-thumb-LOG269-v2.webp (1920, 1080) 173130 bytes
hri-thumb-LOG303-v2.webp (1920, 1080) 38134 bytes
hri-thumb-LOG305-v2.webp (1920, 1080) 80708 bytes
hri-thumb-LOG307.webp (1920, 1080) 63254 bytes
hri-thumb-LOG308-v3.webp (1920, 1080) 190294 bytes
hri-thumb-LOG311.webp (1920, 1080) 96758 bytes
hri-thumb-LOG321-v2.webp (1920, 1080) 269894 bytes
hri-thumb-LOG322.webp (1920, 1080) 198886 bytes

R2 に入れたときの出力(Mac で動かした)

1回目(9枚):

$ cd "$HOME/mnt/PixelstoMotors/デジタルツイン学習R2R2R/HRI_サムネイル_20261108" && python3 _tool/upload_media.py .
入れる 9・入っている 0・名前の重なり 0・合計 0.8MB
  9/9
おわり

2回目(作り直した6枚):

入れる 6・入っている 9・名前の重なり 0・合計 0.8MB
  6/6
おわり

3回目(LOG308):

入れる 1・入っている 15・名前の重なり 0・合計 0.2MB
  1/1
おわり

update_series1.py(全文)― 札の名前と表紙の欄を書き換える

実際には3回に分けて書き換えた(commit 84070ae・60a93b3・39501d2)。このスクリプトは、その3回を合わせた最後の形を1回で作る。書き換える前の commit で動かし、公開中の60ファイルと1文字も違わないことを確かめた。

"""/series1(薬剤搬送ロボット)の記事データを書き換える(LOG473)。

1. シリーズの値 HRI を 薬剤搬送ロボット に変える(記事の札に出る文字。records の json・cards.json・
   src/lib/records.ts の SERIES_PAGES・4コマのページの関連記録)
2. 9本のサムネイルを HTML(/thumbs/r-*.html)から画像(/media/hri-thumb-LOG{N}*.webp)に替える
   (cover に画像を入れ、thumb を null にする。前の HTML は public/thumbs/ に残す)

p2m-site のフォルダで動かす。実際には、作業の途中で3回に分けて書き換えた
(84070ae・60a93b3・39501d2)。このスクリプトは、その3回を合わせた最後の形を1回で作る。
"""
import glob
import json
import re

COVERS = {
    'hospital-operations-digital-twin-system-design': 'hri-thumb-LOG267-v2.webp',
    'predicted-vs-actual-correction-loop': 'hri-thumb-LOG269-v2.webp',
    'figma-design-token-consolidation': 'hri-thumb-LOG303-v2.webp',
    'does-a-delivery-robot-help-nurses': 'hri-thumb-LOG305-v2.webp',
    'autodesk-fusion-mcp-setup': 'hri-thumb-LOG307.webp',
    'claude-instructions-hit-a-wall': 'hri-thumb-LOG308-v3.webp',
    'from-drawing-to-solid': 'hri-thumb-LOG311.webp',
    'avatar-gitai': 'hri-thumb-LOG321-v2.webp',
    'avatar-gitai-pikari': 'hri-thumb-LOG322.webp',
}
OLD, NEW = 'HRI', '薬剤搬送ロボット'


def records():
    n = 0
    for f in glob.glob('src/data/records/*.json'):
        s = open(f, encoding='utf-8').read()
        d = json.loads(s)
        if d.get('series') != OLD:
            continue
        s2 = s.replace(f'"series": "{OLD}"', f'"series": "{NEW}"', 1)
        if d['slug'] in COVERS:
            old_t = json.dumps(d['thumb'], ensure_ascii=False)
            s2 = s2.replace('"cover": null, "thumb": ' + old_t, f'"cover": "/media/{COVERS[d["slug"]]}", "thumb": null', 1)
            assert COVERS[d['slug']] in s2, f
        json.loads(s2)
        open(f, 'w', encoding='utf-8').write(s2)
        n += 1
    print('records', n)


def cards():
    t = open('src/data/cards.json', encoding='utf-8').read()
    print('cards', t.count(f'"series": "{OLD}"'))
    t = t.replace(f'"series": "{OLD}"', f'"series": "{NEW}"')
    for slug, name in COVERS.items():
        pat = re.compile(r'("slug": "%s",\n"cover": )null(,\n"thumb": )"/thumbs/r-%s\.html"' % (re.escape(slug), re.escape(slug)))
        t, k = pat.subn(r'\1"/media/%s"\2null' % name, t)
        assert k == 1, (slug, k)
    json.loads(t)
    open('src/data/cards.json', 'w', encoding='utf-8').write(t)


def code():
    for f, a, b in [
        ('src/lib/records.ts', "series1: { series: 'HRI'", "series1: { series: '薬剤搬送ロボット'"),
        ('src/components/MangaPage.astro', "seriesCards('HRI')", "seriesCards('薬剤搬送ロボット')"),
        ('src/components/MangaPage.astro', 'Studio と同じく HRI の新しい6件', 'Studio と同じく 薬剤搬送ロボット(series1)の新しい6件'),
    ]:
        t = open(f, encoding='utf-8').read()
        assert a in t, (f, a)
        open(f, 'w', encoding='utf-8').write(t.replace(a, b))


if __name__ == '__main__':
    records()
    cards()
    code()
$ python3 update_series1.py
records 57
cards 57

shot_series1.js(全文)― 公開した一覧の画面を撮る

const {chromium}=require('playwright');
(async()=>{const b=await chromium.launch();const p=await b.newPage({viewport:{width:1440,height:1000},deviceScaleFactor:2});
await p.goto('https://pixelstomotors.com/series1?z=1');await p.waitForTimeout(1500);
await p.addStyleTag({content:'*{scroll-behavior:auto!important}'});
const cards=await p.$$('.rcard');
await cards[0].scrollIntoViewIfNeeded();
const y0=await p.evaluate(e=>e.getBoundingClientRect().top+window.scrollY,cards[0]);
for(const [i,y] of [[9,y0-90],[10,y0-90+520]]){await p.evaluate(v=>window.scrollTo(0,v),y);await p.waitForTimeout(2500);await p.evaluate(()=>{for(const e of document.querySelectorAll('body *')){const c=getComputedStyle(e);if(c.position==='fixed'&&!e.closest('header'))e.style.display='none'}});
await p.screenshot({path:process.env.W+`/LOG473_${String(i).padStart(2,'0')}_series1_after_${i==9?'upper':'lower'}.png`});}
await b.close();})();

ビルドと公開

$ npm run build
17:43:28 [@astrojs/sitemap] `sitemap-index.xml` created at `dist`
17:43:28 [build] 835 page(s) built in 30.48s
17:43:28 [build] Complete!
$ git log --oneline -3
39501d2 LOG308のサムネイルを記事の Fusion の画面(板・箱・青い面)に差し替え
60a93b3 series1: 似たサムネイルを差し替え(斜めの図解は2本に絞り余白なしに、残りは4コマの絵・三面図)
84070ae series1: 白黒サムネイル9本を他のHRI記事と同じ画像の形に、タグ「HRI」を「薬剤搬送ロボット」に