---
title: "サービスロボット導入シミュレーション Log#46 | 看護師と搬送ロボットの受け渡しを、すり抜けない動きに"
slug: nurse-hospital-robot-handover-without-clipping
date: 2026-11-13
day: 499
meta_description: "Unity の3DCGの病院で、看護師が搬送ロボットのハコブくんから薬を受け取る場面を、画面で認証し、引き出しが自分で開き、斜め横から上から取る形にして、すり抜けずに見せた。"
tags: [サービスロボット導入シミュレーション, 搬送ロボット, 病院, 看護師, 3Dモデル, Unity, Blender, 受け渡し, シミュレーション]
series: "導入シミュレーション"
---

Unity の3DCGの病院で、看護師が搬送ロボット「ハコブくん」から薬を受け取る場面は、画面で認証し、引き出しが自分で開き、看護師が引き出しの斜め横に立って上から取る形にすると、手や脚が引き出しにすり抜けずに見せられた。**どちらも実物のロボットや人ではなく、シミュレーションの中の3DCGで、引き出しに体が入ったコマは0になった。**

この回でできるようになったことです。

- 搬送ロボットとの受け渡しの一連を、2本の動画で見せられるようになった。①薬剤部で積む（番号で認証→引き出しが自分で開く→上から入れる→自分で閉まる）と、②病棟で受け取る（カードで認証→自分で開く→上から取る→自分で閉まる）
- 看護師の体が引き出しに入っていないかを、毎コマ数えて確かめられるようになった。入ったコマは赤く光らせて動画に出せる
- ハコブくんの3Dを、横に貼った板をやめて三面図に近い形にした。画面のまわりの丸いふちと、車輪の間の灰色の板も足した
- 時間：この回は Unity への依頼を10本出し、朝9時から夕方5時すぎまでかかった。うち3本（依頼167・168・170）は、撮る前に止まって動画が出なかった。前のやり方と比べられる時間は測っていない

困っていたのは、前の回（[LOG496](https://pixelstomotors.com/record/hakobu-nurse-3d-models-for-handover-closeups)）で作った3Dの看護師とハコブくんを、最初に Unity で動かした動画でした。看護師は深くしゃがみ、引き出しの横から手を入れて、腕が引き出しの板を突き抜けていました。実際のハコブくんは、画面で認証しないと引き出しが開きません。開くのも自動です。見た目は整っていても、動きが現場と違えば、受け渡しの場面を見せる意味がありません。

> ハコブくんのドロワーを受け取る前に、画面で認証が必要です（認証カードを顔あたりにかざす動作のことです。もしくは認証番号を画面に入力）。また、ドロワーに入れる、取り出す時に人がしゃがみすぎです。箱の上からモノを入れたり、取る動作が必要です。

| 何を | なぜ | どうなったか |
|---|---|---|
| 看護師の3Dの面の向き・袖・パンツの重みを直した | Unity で全部の面が裏返っていた。しゃがむとパンツの付け根が折れた | 面は全部外向き。袖は腕を上げても形が変わらない |
| ハコブくんの横の板をやめ、胴の横をゆるくふくらませた | 横がパネルを貼ったように見えた | 三角形 5,804 から 6,792。車輪は前に決めた太いタイヤのまま |
| Mixamo の歩きを入れた（依頼163） | 歩く動きの元がなかった | 前の手づくりの歩きのほうが良かったので、戻した |
| 画面で認証し、引き出しが自分で開いて閉まる形にした（依頼166・167） | 実際のハコブくんがそう動く | ①は番号、②はカード。受け取って何もしなければ3秒で閉まる |
| 看護師を引き出しの斜め横に立たせ、上から取らせた（依頼168〜171） | 出てくる引き出しが看護師の右手とひざに入った | 当たり0コマ。股関節40°・ひざ22°、手首は25°以内 |
| ①積むも同じやり方にした（依頼172） | ②と同じ決まりで見せるため | 当たり0コマ。①と②がそろった |

【図解】LOG499_request_trail.html

病院の搬送ロボットのほかに、配膳ロボットを自分の店に入れたらどうなるかは、このシリーズの試算をもとにした別のページで試せます。

【バナー】serving-robot-one-less-staff


## 作業ログ ― 依頼162 で、はじめて看護師とハコブくんを Unity で動かした

サービスロボット導入シミュレーションは、病院で検体や薬を運ぶ搬送ロボットの1日を Unity で動かしてきました。前の回では、場面を切り取って近くで見せる「寄りの場面」のために、ハコブくんと看護師の大城さんを3Dにしました。この回は、その2人を Unity の場面に置いて、実際に動かすところからです。

Unity への作業は、Claude（このチャット）が依頼文を書き、Mac の上の Claude Code が受け取って実装し、動画と報告を返す流れです。依頼文は iCloud の「依頼箱」というフォルダに置くと、Mac の見張り係が1本ずつ Claude Code に回します。この回では、依頼162〜172 のうち10本を回しました（164 は病院の1日の別の依頼）。

### 依頼162 の結果：手は取っ手に合ったが、看護師は深くしゃがんだ

依頼162 は、薬剤部で看護師がハコブくんに薬を積む場面（①）と、病棟で受け取る場面（②）を、それぞれ30秒前後の動画にするものです。確かめの数字は全部合いました。

| 確かめ | ① 積む | ② 受け取る |
|---|---|---|
| 開ける時の手のひらと取っ手の距離（コマごとの最大） | 0.80 cm | 0.80 cm |
| 箱の底とトレーの面の差 | 0.00 cm | ― |
| 引き出し | 0〜0.26 m で開けて閉めた | 同じ |
| ハコブくんの胴を突き抜けたコマ | 0 | 0 |

【画像】LOG499_03_stills162.jpg 依頼162 の静止画6枚。上の2枚と左下が、引き出しの前で深くしゃがむ看護師。右上の2枚の真ん中が、看護師を正面と横から見た所。右下は話しかける顔

【動画】LOG499_12_v162_receive.mp4 依頼162 の②受け取る（32秒）。看護師は歩いて来て深くしゃがみ、取っ手を引いて引き出しを開け、箱を取る。この時はまだ認証も、引き出しが自分で開く動きもない

報告には「決めてほしいこと」が6つありました。大きいのは2つです。看護師の FBX を Unity で読むと、全部の部品の面が裏返っていたこと。取っ手（床から 0.61〜0.66 m）とトレー（0.435 m）が看護師の腕には低く、ひざを曲げて深くしゃがむ形になったことです。


## 作業ログ ― 看護師の3Dの面の向き・袖・パンツの重みを直した

### 面が裏返っていたのは、作る側で三角形の向きを逆にしていたから

看護師の形は、部品ごとに「表面からの距離」を返す式で書き、scikit-image の [marching_cubes](https://scikit-image.org/docs/stable/api/skimage.measure.html#skimage.measure.marching_cubes) で三角形の面にしています（前の回の LOG496 で説明）。三角形には表と裏があり、3つの頂点を回る向きで決まります。前のコードは、marching_cubes が返した向きを逆にしてから Blender に渡していました。

半径0.6の球で確かめると、marching_cubes の向きのままなら面の囲む体積が正（外向き）になり、逆にすると負（内向き）になりました。Blender の絵は面の両側を描くので気づかず、Unity は表側だけを描くので、全部の面が裏返って見えていました。向きを直して作り直すと、13の部品すべての体積が正になりました。

```text
raw 6680.0156 reversed(as in script) -6680.014
```

```text
VOL Nurse_Brows 1.8 cm3
VOL Nurse_Eyes 13.2 cm3
VOL Nurse_Hair 4412.6 cm3
VOL Nurse_Mouth_flat 0.4 cm3
VOL Nurse_Mouth_smile 0.5 cm3
VOL Nurse_Mouth_surprised 1.0 cm3
VOL Nurse_Mouth_talk 0.8 cm3
VOL Nurse_Mouth_worried 0.4 cm3
VOL Nurse_Pants 26478.5 cm3
VOL Nurse_Shoes 2064.5 cm3
VOL Nurse_Skin 11449.8 cm3
VOL Nurse_Sleeves 548.6 cm3
VOL Nurse_Top 3740.6 cm3
```

依頼162 では、Claude Code が Unity 側で裏返しを打ち消す材質（NurseInsideOut.shader）を足して撮っていました。直した FBX に差し替えた依頼163 で、この手当ては外しました。

### 袖はまるごと上腕の骨に、パンツは腰と太ももの境目をなだらかに

袖は、前の回の終わりに直してあった版（付け根を肩の骨と分け合うのをやめ、まるごと上腕の骨に付ける）を、依頼162 が終わるのを待って main に入れました。

パンツは、深くしゃがむと太ももの付け根の面が細かく折れていました。骨ごとの重み（その頂点がどの骨にどれだけついて動くか）は、骨の受け持つ形までの距離で決めていて、境目の広さを `tau` という値で調整しています。パンツの腰と太ももだけ、`tau` を 0.018 から 0.045 に広げました。

- ねらい：しゃがんだ時に、パンツの付け根が折れて見えるのを減らす
- 変わったこと：腰（Hips）と太もも（UpperLeg）の重みの境目を、約2.5倍の幅でなだらかにした
- 見込める効果：付け根の折れは少し和らいだ（下の絵の右2枚）。深くしゃがむと、ひざと付け根に面の少なさから来るしわは残る。依頼167 から上体を倒す形に変えたので、深くしゃがむ場面は今はない

【画像】LOG499_04_pants_squat.jpg Blender でしゃがませた看護師。左2枚が直す前、右2枚が腰と太ももの重みの境目を広げた後。太ももの付け根の細かい折れが少し和らいだ

直した看護師は p2m-site の 83d0ef7 に入れました（FBX の md5 0b940c20468ee8bf953cbafed7ca0ab6・三角形 52,592）。


## 作業ログ ― ハコブくんの横の板をやめ、三面図に寄せた

### 三面図と並べると、横の板が耳のように出ていた

依頼162 の動画を見て、ハコブくんの細部をもう一度見たいと言われました。横がパネルのようになっている、という指摘です。三面図と、いまの3Dを並べました。

【画像】LOG499_01_hakobu_before.png 上がマンガの三面図（正面・横・後ろ）、下が直す前の3D。右は斜めから。下の正面と後ろで、横に貼った板が胴の外へ耳のように出ている

並べて分かったずれは4つです。

1. 横の板：前の回で、マンガ（ep147）の横のふくらみを表そうと、厚さ2cmの板を胴の横に貼っていた。三面図の横は胴そのものの面で、板はない。正面と後ろから見ると、板が耳のように出ていた
2. 横の前のふち：三面図は頭の下から前へ流れる曲線で、輪のある面につながる。3Dは板の四角い角になっていた
3. 足まわり：三面図は車輪の間にへこんだ灰色の板があり、前後の角を濃い灰色の部品が包む
4. 画面：三面図の画面はタブレットのような黒い板で、まわりを薄い灰色の丸いふちが囲む。3Dは黒い板だけだった

### 横の板をやめ、胴の横の面の真ん中だけを1cm外へ出した

板を外し、胴の横の面そのものを、真ん中ほど外へふくらませました。最初は「前後の角へ行くほど内へ寄せる」形にしましたが、後ろから見ると角に段ができました。角は動かさず、横の直線の真ん中だけを外へ出す（4f(1−f)、f は直線の上の位置）形に変えました。ふくらみの大きさは 1cm、高さは胴（床から高さの15〜66%）だけで、頭と台の近くでは0に戻します。

- ねらい：横がパネルを貼ったように見えないようにする
- 変わったこと：厚さ2cmの板（幅 0.674 m）をやめ、胴の横の面が真ん中で1cmふくらむ（幅 0.673 m）。輪はふくらんだ面の、輪の中心の所に乗せた
- 見込める効果：正面と後ろから見て、横に出っ張りがなくなった（下の絵）。Unity の中でも、輪の光る面は横の面から 2〜12 mm 外にあり、沈みはなかった（依頼166 の報告）

画面のまわりには、画面より2cm大きく角の丸みが4.2cmの、薄い灰色のふちを足しました。初めは頭と同じに近い色で見えなかったので、色を (184, 186, 186) に濃くしました。車輪の間の灰色の板は、形ではなく色の画像で描いています（三角形を増やさないため）。

### 車輪が脚のように見えたのは、台の底を丸めすぎていたから

途中で、車輪を三面図どおりの小さい車輪（半径 0.034 m）にしました。すると、台の下から脚のように出て見え、前に決めた形から戻ってしまったと指摘を受けました。前の回で、太いタイヤ（半径 0.046 m・幅 0.052 m）の上半分を台の中に隠す形に決めていたからです。

タイヤを前の形に戻しても、正面から見ると脚のように見えました。原因は、台の底の角を 5.6 cm（0.016 m × 丸めの倍率 3.5）も丸めていて、台の底が車輪の上で細っていたことでした。底の丸みを 1.2 cm にすると、横から見て、台の下から丸いタイヤが半分のぞく形になりました。

【画像】LOG499_02_hakobu_after.png 上が三面図、下が直した3D。横の板がなくなり、横の面がゆるくふくらむ。画面のまわりに灰色の丸いふち、横の下に車輪の間の灰色の板。タイヤは上半分を台の中に隠した太い形

直したハコブくんは p2m-site の cab3977 に入れました（FBX の md5 dfd43653e3d176e19aae2f288a68c5bd・三角形 6,792）。


## 作業ログ ― Mixamo の歩きを入れ、手づくりの歩きに戻した（依頼163）

### Mixamo は、人型の3Dに付けられる動きを借りられる Adobe のサービス

[Mixamo](https://www.mixamo.com/) は、歩く・走る・うなずくといった人の動きを、人型の3Dに付けて書き出せる Adobe のサービスです。前の回で、歩く・曲がる・うなずく・話す・おじぎなど33本を借りてありました。Adobe の決まりで、動きのファイルそのものは公開しません。置き場所は Unity のプロジェクトの中だけです。

| 言葉 | 意味 |
|---|---|
| Humanoid | Unity の人型の決まり。骨の名前をそろえると、別の人型の動きをそのまま付けられる |
| Root Motion | 動きのファイルの中の「体全体がどれだけ進んだか」で、人を歩かせる仕組み |
| IK | 手や足の先の位置を決めると、ひじやひざの曲がりを逆算する仕組み |

依頼163 では、33本を Unity に読み込み、歩き出し・歩く・止まる・曲がる、積んだ後のうなずき、受け取った後の話しかけとおじぎを、Mixamo の動きに置き換えました。足のすべりは 1.49 cm で基準（3 cm）の中でした。

【動画】LOG499_13_v163_receive.mp4 依頼163 の②受け取る（35秒）。歩き方・立ち方・おじぎを Mixamo の動きにした版。この版の歩きは使わず、依頼162 の手づくりの歩きに戻した

動画を見て、エイイチの判断ははっきりしていました。

> 看護師の体の歩き方や立ち方は前の方が良かったね。直してほしいところはハコブくんとのやり取りで他のところです。

依頼163 は、前の回で決めたとおり歩きまで Mixamo の動きにしました。けれど、直したかったのはハコブくんとのやり取り（認証・入れる・取る）だけで、歩きと立ち方は手づくりのほうが合っていました。


## 作業ログ ― 画面での認証と、自分で開く引き出しを足した（依頼165〜167）

### 依頼165 は、始める条件で止まり何も作らなかった

認証と、上から入れる・取る形を作る依頼165 を出しましたが、1分で止まりました。依頼165 には「依頼163 の判定が合格であること」を始める条件に書いていて、依頼163 は「止めた（決めてほしいことあり）」で終わっていたからです。中身は依頼166 にまとめ直しました。

### 依頼166：ハコブくんを差し替え、①は番号・②はカードで認証

依頼166 では、ハコブくんを新しい形に差し替え、看護師の歩きを依頼162 に戻し、認証と上から取る形を作りました。①は右手の人さし指で画面の数字を4回押し、②は胸ポケットのカードを画面の前にかざします。認証が通ると、画面にチェックの印が出ます。

動画は、エイイチの指示で②受け取るの1本だけを完璧にすることにしました。

> 動画を作るのはとりあえず一つでいいです。まずは一つだけ完璧にしてください。

> 勝手に指示していない動作を修正するのはやめてください。

ここから先の依頼には、「変えてよいのは指示した所だけ。それ以外は前の依頼のまま」と書き、前の依頼との骨のずれが 1 mm 以内であることを確かめさせるようにしました。

【画像】LOG499_05_stills166.jpg 依頼166 の静止画。上はカードを画面にかざす所・「認証しました」の印・上体を倒して取る所（横から）。下は新しいハコブくんの正面・横・斜め

【動画】LOG499_14_v166_receive.mp4 依頼166 の②受け取る（39.5秒）。カードで認証してから取っ手を引いて開け、上から箱を取る。上体がほぼ水平まで倒れ、引き出しの横から手を入れている

判定は合格でしたが、問題が3つ残りました。

- ひざを20°以内に抑えたまま 0.435 m のトレーに手を届かせたので、上体がほぼ水平（88°）まで倒れた
- Claude が依頼文に「依頼162 におじぎがなければ足す」と書いたため、指示にないおじぎが入った（Claude の誤り）
- 依頼162 のままの歩きでは足が最大 43 cm すべっていたので、Claude Code が足を床に留める形を足した

動画を見たエイイチからは、別の所の指摘がありました。

> ドロワーは認証したら自動で開きます。まだ看護師はドロワーの横からモノを取っていてドロワーをすり抜けています。

引き出しを取っ手で引く形は、実際のハコブくんと違っていました。看護師は引き出しの横から手を入れていて、腕が引き出しの板を突き抜けていました。閉まり方は、看護師が受け取ったあと何もしなければ自動で閉まる、と決まりました。おじぎは外します。

### 依頼167：引き出しが自分で出てきて、看護師の右手とひざに入った

依頼167 では、引き出しがチェックの印で自分で開き（1.25秒で 0.26 m）、手が離れて10cm上がってから3秒で自分で閉まる形にしました。看護師は引き出しの正面に立ち、開いた口の真上から取ります。すり抜け（取る間に体と箱が引き出しに入ったコマ）は 0 になりました。

ところが、引き出しが出てくる間に、看護師の体と当たりました。看護師はカードをかざすため、ハコブくんから 0.74 m の所に立っています。開いた引き出しの前の板は 0.6155 m まで出てくるので、前かがみの右手とひざに届きます（38コマ中13コマ）。確かめが合わなかったので、依頼167 は撮らずに止まりました。

エイイチの答えは、引き出しの斜め横に立つ、でした。


## 作業ログ ― 止まり続けた原因は、Claude が書いた数字だった（依頼168〜171）

### 依頼168：斜め横の6か所は、どれも「5cm」を満たせなかった

依頼168 では、看護師を引き出しの斜め横（看護師から見て右）に立たせました。ここで Claude は、依頼文に「体と引き出しを 5 cm 以上離す」「カードは画面の前 3〜5 cm」と書いていました。どちらもエイイチの指示ではなく、Claude が決めた数字です。

Claude Code が立つ所を6か所測ると、カードが画面に届く所では、ひざと下ろした左手が引き出しの角から 5 cm 以内に入りました。依頼168 も撮らずに止まりました。

### 依頼169：いまの形を、当たる所を赤くして撮った

止まった報告だけが続いて、エイイチには何が起きているのか見えていませんでした。

> 今の状況を動画で出して、何が問題かわからないです。

依頼169 では、何も直さずに依頼167 の形のまま撮り、看護師の体が引き出しに入ったコマだけ、入った所を赤く光らせました。

| 部位 | 入った部品 | 時刻 | コマ |
|---|---|---|---|
| 右手 | 前の板 | 15.567〜16.233 秒 | 21 |
| ひざ | 前の板 | 15.867〜16.367 秒 | 16 |
| もも・すね | 前の板 | 15.900〜16.367 秒 | 各 15 |
| ひざ・すね | 底 | 16.233〜16.300 秒 | 3 |
| ひざ・もも | 取っ手 | 16.267〜16.300 秒 | 2 |

【画像】LOG499_06_stills167.jpg 依頼167 の確かめの静止画（横から）。左上は認証の直後で、出てくる引き出しが看護師の右手とひざに届く。右上は開いた所。左下と右下は、ひざを伸ばしたまま上体を倒して取る所

【画像】LOG499_07_hits169.jpg 依頼169 の横からの動画の4コマ。右上と左下で、出てくる引き出しの前の板に入った右手・もも・ひざ・すねが赤く光る。右下は取る所で、ひざが伸びたまま上体がほぼ水平まで倒れている

【動画】LOG499_15_v169_side_half.mp4 依頼169 の横からの動画（0.5倍の速さ・22秒）。5〜7秒のあたりで、出てくる引き出しが看護師の脚と右手に入り、入った所が赤く光る

### 依頼170：手首と腰は直ったが、また止まった

静止画を見たエイイチからは、姿勢よりも手首が気になるという指摘がありました。取る時に、手首が前腕に対して大きく折れていたからです。腰はもう少し起こすことになりました。

依頼170 では、股関節の倒しを 50° から 40° 前後に減らしてひざを曲げ、手首の曲がりを前腕に対して25°以内にしました。手首は右22°・左12°、股関節40〜45°・ひざ22°でできました。

それでも止まりました。今度は、Claude が書いた「頭と画面を 15 cm 以上」と「カード 3〜5 cm」が、当たらない立つ所と両立しませんでした。

### 依頼171：Claude の数字をゆるめ、合わなくても必ず撮るようにした

依頼167・168・170 が止まった原因を並べると、エイイチの指示（当たらない・真上から取る）ではなく、Claude が依頼に書いた数字が厳しすぎたことでした。図解の「止めた条件」に、条件ごとにまとめています。依頼171 では、Claude が決めた数字を次のようにゆるめ、確かめが合わなくても必ず撮る形にしました。

| 条件 | 依頼170 まで | 依頼171 | だれが決めたか |
|---|---|---|---|
| カードと画面の距離 | 3〜5 cm | 1〜8 cm | Claude |
| 頭と画面の距離 | 15 cm 以上 | 5 cm 以上 | Claude |
| 体と引き出しの距離 | 5 cm 以上（168） | 1 cm 以上 | Claude |
| 引き出しに当たらない・すり抜けない | 0 コマ | 0 コマ（ゆるめない） | エイイチ |
| 開いた口の真上から取る | 必ず | 必ず（ゆるめない） | エイイチ |
| 確かめが合わない時 | 撮らずに止める | 撮って、合わない所を報告に書く | Claude |

- ねらい：エイイチが動画で見て判断できるようにし、Claude が決めた数字で作業を止めない
- 変わったこと：依頼167・168・170 は撮る前に止まり、動画が出なかった。依頼171 は、65通りの立つ所とかがみを試したうえで撮り、合格した
- 見込める効果：確かめの数字は、撮るかどうかを決めるものではなく、エイイチが動画を見るときの材料になる。依頼172 も止まらずに撮れた

依頼171 の結果です。

| 確かめ | 値 |
|---|---|
| 引き出しとの当たり | 0 コマ（場面全体） |
| 体と引き出しの一番近い距離 | 出てくる間 3.04 cm・向きを変える間 2.02 cm・取る間 1.52 cm |
| 右手が入る向き | 開いた口の真上から |
| かがみ | 股関節の倒し 40.0°・ひざの曲げ 21.7°・頭と画面 10.85 cm |
| 手首 | 右 22.5°・左 12.1° |
| カード | 6.99 cm で止まった（一番近い所 3.93 cm・画面に触れない） |

斜め横のままでは右腕が引き出しの壁に当たるので、Claude Code はカードをかざした後に、斜め横のまま箱のほうへ向き直る3歩を入れました。指示にない動きですが、立つ所を変えたことに伴う動きとして入れたものです。エイイチは、この形で②受け取るを完成にしました。

【画像】LOG499_08_stills171.jpg 依頼171 の静止画。左上はカードを画面にかざす所（横から）、右上は斜め横から上体を倒して取る所（横から）、左下は手元の寄りで、手首がまっすぐのまま箱をつかむ所、右下は取る所

【画像】LOG499_09_side171.jpg 依頼171 の横からの動画の4コマ。カードをかざし、引き出しが自分で出てきても看護師に当たらず、上体を倒して上から取る

【動画】LOG499_16_v171_side_half.mp4 依頼171 の②受け取るを横から（0.5倍の速さ・22秒）。当たったコマがないので、赤い表示は出ない

【動画】LOG499_17_v171_receive.mp4 依頼171 の②受け取る（本番のカメラ・39.5秒）。カードをかざす所と取る所は、看護師の背中に隠れて見えにくい


## 作業ログ ― ①積むも同じやり方にした（依頼172）

②と同じ決まりで、①積むを直しました。番号で認証し、引き出しが自分で開き、看護師は斜め横から上へ回って、開いた口の真上から箱を入れます。箱を持つ手は依頼166 と同じ左手で、持ち替えはしていません。

| 確かめ | 結果 | 基準 |
|---|---|---|
| 当たったコマ（開く・向き直る・入れる・閉まる間） | 0 | 0 |
| 体と引き出しの一番近い距離 | 1.10 cm | 1 cm 以上 |
| 股関節の倒し・ひざの曲げ | 44.0°・29.3° | 35〜45°・30°以内 |
| 頭と画面 | 5.59 cm | 5 cm 以上 |
| 左手首の曲がり | 23.1° | 25°以内 |
| 番号 | 4回とも、指先とボタンの中心 0.45 cm | 1 cm 以内 |
| 手が離れてから閉まり始めるまで | 3.000 秒 | 3.0 秒 |
| 箱の底とトレーの上の面の差 | ＋0.18 cm | 1 cm 以内 |

【画像】LOG499_10_stills172.jpg 依頼172 の静止画。左上は番号を押す所（横から）、右上は斜め横から上体を倒して箱を入れる所（横から）、左下は手元の寄りで、手のひらを下にして箱を上から持つ所、右下は入れる所

【画像】LOG499_11_side172.jpg 依頼172 の横からの動画の6コマ。番号を押し、引き出しが出てきてから向き直り、上体を倒して上から入れ、体を起こす

【動画】LOG499_18_v172_side_half.mp4 依頼172 の①積むを横から（0.5倍の速さ・25秒）

【動画】LOG499_19_v172_load.mp4 依頼172 の①積む（本番のカメラ・34秒）。カウンターで箱を取り、番号で認証して上から入れ、引き出しが閉まってからうなずいて見送る

エイイチの判断は「細かい所に問題はあるが、いったんよい」でした。これで、①積むと②受け取るの2本がそろいました。

### 残っていること

- 本番のカメラ（依頼166 の値のまま）では、カードをかざす所と取る・入れる所が、看護師の背中や頭に隠れる
- ①で番号を押す間だけ、上体を股関節から18°倒している（依頼166 は背中だけ6°）
- 受け取った後に閉まるまでの3秒と、話しかけの後の頭の戻し（1.0秒）は、依頼167 の値のまま
- Mixamo の動きのファイルを、Unity のクラウドの保管（Plastic）に入れるかは決めていない。いまは歩きに使っていない


## 作業ログ ― 依頼文と報告の全文

依頼箱に置いた依頼文と、Claude Code の報告（見張り係が結果のフォルダに書いたもの）を、依頼の順に全文で載せます。報告の中の「止められた操作」は、Claude Code が試して止められたコマンドの記録です。

### 依頼162 の依頼文（20261113_162_病院の場面の寄りを作り看護師がハコブくんに薬を積む場面と受け取る場面を撮る.md）

````markdown
---
作業フォルダ: /Users/shimazueiichi/Robotics_UX/ServiceSim
上限時間: 300
---
# 依頼162：病院の「場面の寄り」を新しく作る。看護師がハコブくんに薬を積む場面と、届け先で受け取る場面を撮る

## ねらい
サービスロボット導入シミュレーションは、これから次の4段で見せる（エイイチ、2026-11-13）：
**試算 → 全体のシミュレーション（今の病院の1日）→ 場面の細部（今回から）→ 実際のロボットの動画**。
今回はその3段目の最初。今の病院の1日の場面とは別に、**現場の具体的な一場面を短く切り取って見せる場面**を作る。人とロボットのやり取り（渡す・受け取る、あとでトラブルの時の動き・話しかける姿）が、近くで見て分かることがねらい。
今回撮るのは2つ：**① 薬剤部で看護師がハコブくんの引き出しに薬を積む ② 届け先でハコブくんが待ち、看護師が引き出しから受け取る**。

**病院の1日の場面（Assets/ServiceSim/Hospital/）・配膳の場面・それぞれの実行ファイルと台帳・共通の部品には触らない。** 新しく作るのは Assets/ServiceSim/Closeups/ の中と、モデルを写す Assets/ServiceSim/Models/ の中だけ。Packages（manifest.json）にも触らない。
**エイイチは見ていない。人に聞かないと進めない所に当たったら、その項目は止めて報告に書き、ほかは続ける。**

## 正（読むもの）
p2m-site main（e3daa10）
- ハコブくん：`tools/sim_robots/hakobu/`（README.md を読む）
  - hakobu.fbx（md5 364baa19a7809321d3f635c7fe17fa94）・hakobu_albedo.png（397877afe090d0d7e5c591b0454fb26f）・hakobu_emission.png（4dc8ec34c9af0c3003ba00be6fe250b3）
  - 実寸 幅0.653・奥行0.760・高さ1.150m。根は床の中心、顔は +Z。部品：Body・Face・Ring・Drawer・Tray_1〜4。引き出しは前（+Z）へ 0.26m まで出る。Tray_1〜4 は FBX では根の直下にあるので、プレハブで Drawer の子にする
  - 取っ手の高さは床から約 0.61〜0.66m、引き出しの中のトレーの上の面は約 0.43m
- 看護師（大城さん）：`tools/sim_people/nurse_oshiro/`（README.md を読む）
  - nurse_oshiro.fbx（md5 09c296c50f88d39156fbcd62223b7d3e）。身長1.65m。根は床の中心、顔は +Z
  - 骨は Unity の Humanoid の名前（Hips・Spine・Chest・Neck・Head・左右の Shoulder〜Hand・指の Proximal と Intermediate・UpperLeg〜Toes）。作った時の姿勢は腕を下ろした形
  - 顔の骨 Eye_L/R（上下につぶすとまばたき）・Brow_L/R は Humanoid に入れない
  - 口は Nurse_Mouth_smile／flat／worried／talk／surprised の5つの部品。表示を切り替えて使う（既定は smile）
  - 確認用の絵：iCloud の …/サービスロボット導入シミュレーション/hospital/people_3d/nurse_oshiro_poses.png（骨で姿勢を付けた8つ。この形に近い姿勢が Unity でも出ればよい）

## 始める前に確かめること（違えば作らずに報告して終わる）
作業場所の保留 0・Unity と回し係が動いていない・KIOXIA がマウント済み・上の md5

## 1. 読み込む
- 2つの FBX と画像を Assets/ServiceSim/Models/Hakobu/・Assets/ServiceSim/Models/NurseOshiro/ に写す
- ハコブくん：材質に albedo と emission を付ける（目・口・輪・背中の帯が光る）。プレハブを作り、Tray_1〜4 を Drawer の子にする
- 看護師：Rig を Humanoid にして、骨の割り当てを確かめる（自動で全部割り当たるはず。割り当たらない骨があれば報告に書く）。T ポーズに直して Avatar を作る
- 大きさの確かめ：ハコブくんの高さ 1.15m・看護師 1.642m が Unity の目盛りで合うこと

## 2. 作る：場面の寄り（Closeups）
- 新しい場面 `Assets/ServiceSim/Closeups/Closeup_Handover.unity`。床・壁・薬剤部のカウンター（腰の高さ 0.9m・奥行 0.6m）・廊下の突き当たりの扉くらいの簡単な部屋でよい。色は依頼153 の病院の色に合わせる
- 動きは動画用の台本で決める（時刻と動きの表を JSON で Closeups/ に置き、そこを読む。値は決め打ちにしない）
- 看護師の手は Animator の IK（OnAnimatorIK）で、取っ手・トレー・荷物に合わせる。かがむのは Spine と Chest を前へ（取っ手が低いので腰を曲げる）。指はつかむ時に曲げる（指の骨の Proximal・Intermediate）
- 荷物：小さい薬の箱（約 0.12×0.08×0.05m・白に色の帯）と、検体の袋。トレーに置く
- **① 積む（約 25〜35秒）**：ハコブくんが薬剤部のカウンターの前に止まっている → 看護師がカウンターの薬の箱を手に取る → ハコブくんの前に来て少しかがむ → 取っ手を持って引き出しを開ける → 箱をトレーに置く → 引き出しを閉める → 体を起こして、ハコブくんに軽くうなずく → ハコブくんが走り出す
- **② 受け取る（約 25〜35秒）**：届け先（病棟のナースステーションの前）にハコブくんが来て止まる → 看護師が気づいて歩いて来る → かがんで引き出しを開ける → 箱を取り出す → 閉める → 体を起こしてハコブくんに口を talk にして話しかける → ハコブくんが戻っていく
- 看護師が歩く所：今回は**数歩だけ**（1〜2m）。歩く動きの元がないので、足を交互に出す簡単な動き（Humanoid の筋肉の値を時間で動かす）で作る。不自然なら、歩く所を短くして、カメラで足元を外して撮る。歩き方の元をどうするか（ほかから借りるか）は「決めてほしいこと」に書く
- まばたき：3〜5秒に1回、Eye_L/R を縦に 0.1 までつぶして戻す（0.15秒）
- 服や肌が裂ける所が出たら、骨の重みは触らずに報告に書く（直すのは Claude が作る側でやる）

## 3. 確かめ（違えば 4 は撮らずに止める）
- (a) 引き出しを開ける時：看護師の手のひらの中心と取っ手の中心の距離（コマごとの最大）が 2cm 以内
- (b) 箱をトレーに置いた時、箱がトレーの面から浮いていない・めり込んでいない（1cm 以内）
- (c) 引き出しの開き：0〜0.26m の間で、開けて閉めること
- (d) 看護師の手や体がハコブくんの体を突き抜けていないこと（引き出しの前の板を除く）。突き抜けたコマがあれば、その時刻と場所
- (e) 静止画を目で見て、袖・首元・指が裂けたりつぶれたりしていないか

## 4. 撮る
- 動画：①と②を1本ずつ。1920×1080・30fps。カメラは3つくらいを切り替える（全体・手元の寄り・ハコブくんの顔と看護師の顔が入る斜め）。字幕・パネルは入れない
- 静止画：①で引き出しを開けた瞬間・箱を置いた瞬間、②で箱を取り出した瞬間・話しかけている顔の寄り。ほかに看護師が立った全身（正面・横）
- 出力：/Volumes/KIOXIA/r2r2r-data/service_sim/unity/closeup/20261113_closeup162/。動画と静止画は iCloud の …/サービスロボット導入シミュレーション/hospital/closeup162/ にも写し、md5 を確かめる
- check-in（保留0）。実行ファイルは作らない

## 報告
claude_code_reports/20261113_closeup_162.txt に：始める前の確かめ、1 の読み込み（Humanoid の割り当ての結果）、2 で作ったもの、3 の確かめ（表）、4 のカメラの値と動画の長さ、出力の場所、check-in の番号、決めてほしいこと（直し方の候補は作らない）。
**報告の最後の1行は必ず次のどれか**：「判定：合格」／「判定：止めた（決めてほしいことあり）」／「判定：失敗」
````

### 依頼162 の報告（20261113_162_病院の場面の寄りを作り看護師がハコブくんに薬を積む場面と受け取る場面を撮る_20261113_0749.md）

````markdown
# 結果：20261113_162_病院の場面の寄りを作り看護師がハコブくんに薬を積む場面と受け取る場面を撮る

- 状態：完了
- 始めた時刻：2026-11-13 07:49
- 終わった時刻：2026-11-13 08:58（69分）
- 作業フォルダ：/Users/shimazueiichi/Robotics_UX/ServiceSim
- この続きを Claude Code で話すとき：作業フォルダで `claude --resume aafc469f-bdca-4d5c-b87c-5c9b252572e5`

## Claude Code の報告

依頼162 はもう終わっています。指定されたファイルは途中の試し（try17）を撮った時の記録で、その後に直して撮り直し、check-in と報告まで済ませました。

やったこと
- 看護師とハコブくんを Models/ に読み込み、場面の寄り Closeup_Handover.unity を作って、① 積む と ② 受け取る を撮りました。
- 撮った絵を見て、2か所を直してから撮り直しました。
  - かがんだ時に、手を使っていない腕が後ろへ振れていました。
  - 体を起こす時に、取っ手から離す途中の腕が外へ広がっていました。
- check-in（cs:128）と報告まで済ませました。

結果
| 確かめ | ① 積む | ② 受け取る |
|---|---|---|
| (a) 開ける時の手のひらと取っ手の距離（コマごとの最大） | 0.80 cm | 0.80 cm |
| (b) 箱の底とトレーの面の差 | 0.00 cm | ― |
| (c) 引き出し | 0〜0.26 m で開けて閉めた | 同じ |
| (d) ハコブくんの胴を突き抜けたコマ | 0 | 0 |
| (e) 袖・首元・指（静止画を目で見た） | 裂け・つぶれなし | 裂け・つぶれなし |

- 動画は2本です。① 30.0 秒、② 32.0 秒で、どちらも 1920×1080・30 コマ/秒です。静止画は6枚です。
- iCloud の hospital/closeup162/ に写した8ファイルは、md5 が KIOXIA と同じでした。
- check-in は cs:128 で、保留は 0 です。
- 判定：合格

変えたファイル
- Assets/ServiceSim/Closeups/ と Assets/ServiceSim/Models/ の中だけで、.meta を含めて 98 ファイルです。病院の場面・配膳の場面・共通の部品・Packages・実行ファイルは変えていません。
- 報告は claude_code_reports/20261113_closeup_162.txt に置き、同じものを KIOXIA の closeup/20261113_closeup162/report162.txt にも置きました。

決めてほしいこと
1. 歩き方の元を、ほかから借りるかどうか。
2. 看護師の FBX は Unity で読むと全部の部品の面が裏返っています。作る側で直すかどうか。
3. 取っ手とトレーが低く「少しかがむ」では届かないので、ひざを曲げて深くしゃがむ形にしました。この形でよいか。
4. ハコブくんの高さが Unity では 1.145 m で、README の 1.150 m より 5 mm 低いです。このままにするか。
5. 検体の袋の使い方（今ははじめからトレーに載せたままです）。
6. 作る側で骨の重みを直すかどうか（背中の肌が服から出る所と、しゃがんだ時のパンツのおしり・ひざの形）。
````

### 依頼163 の依頼文（20261113_163_場面の寄りにMixamoの動きを入れ看護師を歩かせて積む受け取るを撮り直す.md）

````markdown
---
作業フォルダ: /Users/shimazueiichi/Robotics_UX/ServiceSim
上限時間: 300
---
# 依頼163：場面の寄りに Mixamo の動きを入れ、看護師を歩かせて①積む・②受け取るを撮り直す。直した看護師に差し替える

依頼162 の報告（claude_code_reports/20261113_closeup_162.txt）を読んでから始める。162 で作った場面（Assets/ServiceSim/Closeups/Closeup_Handover.unity）と台本の JSON を土台にする。

エイイチと決めたこと（2026-11-13）：
- 人の動きは Mixamo から借りる（エイイチの Adobe のアカウントで借りた）。**Adobe の決まりで、Mixamo の FBX はそのまま公開しない**。置き場所は ServiceSim の Assets/ServiceSim/Models/Animations/ だけ（MASTER の保存先テーブル）。GitHub・iCloud・KIOXIA には写さない
- 看護師のモデルを直した版に差し替える：袖をまるごと上腕の骨に付けた（腕を上げた時に袖の山がゆがんでいた）・面の向きを外向きに直した（依頼162 の決めてほしいこと2）・パンツの腰と太ももの重みの境目を広げた（同6の一部）
- 人とハコブくんの動きは、エイイチが動画で見てから次へ進む。この依頼は撮って報告するところまで

依頼162 の決めてほしいことへの答え：
1. 歩き方の元 → Mixamo から借りる（下の「正」）
2. 面の裏返し → 作る側で直した。Unity 側で裏返しを打ち消す手当て（両面の材質・面を返すスクリプトなど）を入れていたら外す
3. 深くしゃがむ形 → 今回は Picking Up Object を土台にし、取っ手・トレーに手が届く深さまでひざを曲げる。どのくらい深くしゃがんだか（腰の高さの一番低い値）を報告に書く。形はエイイチが動画で見て決める
4. ハコブくんの高さ 5mm の差 → このままでよい
5. 検体の袋 → 今のまま（はじめからトレーに載せたまま）
6. 骨の重み → パンツの付け根は少し直した。背中の肌が服から出る所と、深くしゃがんだ時のひざ・付け根のしわは今回は直さない。見えた所を報告に書く

**病院の1日の場面（Assets/ServiceSim/Hospital/）・配膳の場面・それぞれの実行ファイルと台帳・共通の部品には触らない。** 触るのは Assets/ServiceSim/Closeups/・Assets/ServiceSim/Models/ の中だけ。Packages にも触らない。
**エイイチは見ていない。人に聞かないと進めない所に当たったら、その項目は止めて報告に書き、ほかは続ける。**

## 正（読むもの）
- 看護師：p2m-site main（83d0ef7）の `tools/sim_people/nurse_oshiro/nurse_oshiro.fbx`（md5 0b940c20468ee8bf953cbafed7ca0ab6）。骨の名前・作りは依頼162 と同じ
- 動き：Assets/ServiceSim/Models/Animations/Mixamo_20261113/ に Claude が写した 33 本（元は Mac の ~/Downloads/mixamo_20261113/。md5 の一覧は同じフォルダの md5.txt。2026-11-13 に 33 本とも一致を確かめて写した）
  - locomotion/：walking・idle・left turn・right turn・left strafe walk・right strafe walk ほか（Female Locomotion Pack）
  - gestures/：head nod yes・lengthy head nod・acknowledging・thoughtful head shake・shaking head no・relieved sigh・look away gesture・weight shift ほか（Gestures Pack Basic）
  - single/：Female Start Walking・Female Stop Walking・Talking（ふつうの会話）・Quick Formal Bow・Picking Up Object・Pointing Forward・Nervously Look Around・Waving Gesture
  - どれも骨だけ（肌なし）・30fps・Mixamo の骨の名前（mixamorig:）

## 始める前に確かめること（違えば作らずに報告して終わる）
作業場所の保留 0・Unity と回し係が動いていない・KIOXIA がマウント済み・看護師の md5（GitHub の main から取ってきたものが上の値と同じ）・動きの md5（md5.txt と合う）・依頼162 の判定が合格

## 1. 読み込む
- 看護師の FBX を Assets/ServiceSim/Models/NurseOshiro/ で差し替える（Avatar の設定は依頼162 と同じ）
- 動きの FBX：Rig を Humanoid、Avatar は「このモデルから作る」。Root Transform の向き・高さは元の動きに合わせる（Bake Into Pose の設定は報告に書く）
- walking・idle・talking・weight shift はループにする

## 2. 場面を直す（台本の JSON の動きを、Mixamo の動きに置きかえる）
- 歩く：Female Start Walking → walking（ループ）→ Female Stop Walking。曲がる所は left turn・right turn。歩く距離は台本の位置から決め、足がすべらないこと（Root Motion で歩かせる）
- 立って待つ：idle と weight shift
- かがんで引き出しを開ける・箱を置く／取る：Picking Up Object を土台にして、手は依頼162 の IK で取っ手・トレー・箱に合わせる
- ① 積み終わったら head nod yes で軽くうなずく。② 受け取ったら Talking で話しかけ（口は talk と smile を交互に出す）、最後に Quick Formal Bow で軽くおじぎ
- ハコブくんの動き（来る・止まる・引き出し）は依頼162 のまま
- まばたきは依頼162 のまま
- 一場面の長さは 25〜40秒

## 3. 確かめ（違えば 4 は撮らずに止める）
- 依頼162 の (a)〜(e) と同じ
- (f) 歩く所：足が床をすべった長さ（足が床に着いている間に動いた距離の最大）が 3cm 以内。足が床に沈む・浮くが 2cm 以内
- (g) 袖：腕を上げた所の静止画を目で見て、袖がゆがんだり裂けたりしていないか
- (h) 面：どの部品も裏返って見えないか（静止画を目で見る）

## 4. 撮る
- 依頼162 と同じカメラ・大きさで、①と②を1本ずつ撮り直す。静止画も依頼162 と同じ種類に、歩いている全身（横から）を足す
- 出力：/Volumes/KIOXIA/r2r2r-data/service_sim/unity/closeup/20261113_closeup163/。動画と静止画は iCloud の …/サービスロボット導入シミュレーション/hospital/closeup163/ にも写し、md5 を確かめる（Mixamo の FBX は写さない）
- check-in（保留0）。実行ファイルは作らない

## 報告
claude_code_reports/20261113_closeup_163.txt に：始める前の確かめ、1 の読み込み（Humanoid の割り当て・Root Motion の設定）、2 で直したもの、3 の確かめ（表）、しゃがんだ時の腰の高さの一番低い値、4 のカメラの値と動画の長さ、出力の場所、check-in の番号、決めてほしいこと（直し方の候補は作らない）。
**報告の最後の1行は必ず次のどれか**：「判定：合格」／「判定：止めた（決めてほしいことあり）」／「判定：失敗」
````

### 依頼163 の報告（20261113_163_場面の寄りにMixamoの動きを入れ看護師を歩かせて積む受け取るを撮り直す_20261113_0919.md）

````markdown
# 結果：20261113_163_場面の寄りにMixamoの動きを入れ看護師を歩かせて積む受け取るを撮り直す

- 状態：完了
- 始めた時刻：2026-11-13 09:19
- 終わった時刻：2026-11-13 10:01（42分）
- 作業フォルダ：/Users/shimazueiichi/Robotics_UX/ServiceSim
- この続きを Claude Code で話すとき：作業フォルダで `claude --resume 57cbc597-aa65-4335-973c-a8a925a09a73`

## Claude Code の報告

**やったこと**
- 始める前の確かめをした。看護師の md5（0b940c20…、main 83d0ef7）と動きの md5（33 本）はどちらも合っていた。Unity と回し係は動いておらず、KIOXIA はマウント済み、依頼162 の判定も合格だった。保留は、この依頼のために置かれた Mixamo のフォルダ（38 件）だけだったので、止めずに始めた。
- 看護師の FBX を差し替えた。13 部品とも面が外向きになったので、依頼162 の裏返しの手当て（NurseInsideOut.shader）は外し、材質を Standard に戻した。使わなくなった Closeup_Nurse.controller も外した。
- Mixamo の 33 本を Humanoid で読み込んだ。
  - 自動の割り当てが目とあごに髪・帽子の骨を入れていたので外した。
  - 骨だけの Mixamo の FBX は、基準の形がファイルごとに動きの 1 コマ目になっていた。そのまま Avatar を作ると、足が床に 2〜5 cm 沈んだ。看護師と同じように骨組みを T ポーズに直してから Avatar を作ったら、そろった。
  - Root Transform は向き・高さ・前後左右とも Original。Bake Into Pose は、向きは曲がる 2 本以外を焼き、高さは全部焼き、前後左右は歩く 3 本と曲がる 2 本以外を焼いた。ループは walking・idle・Talking・weight shift。
- 場面を直した（台本は closeup163_script.json）。
  - 2 回曲がり、歩き始めから歩き止めへつないで歩く。
  - かがむ所は Picking Up Object を土台にし、体を下げて前へ傾け、手は依頼162 の IK で合わせた。
  - ①は積んだ後に head nod yes でうなずく。②は Talking で話しかけ（口は talk と smile を交互）、Quick Formal Bow でおじぎする。
  - 足がすべらないように、床に着いた足を留める IK を入れた。根の動きは、動きごとに記録した通り道から足している（Unity の Root Motion のままだと途中で飛んだため）。
- ①と②を撮り直した。動画と静止画を iCloud に写した。

**結果**
- 確かめはすべて基準の中だった。

| 項目 | ① 積む | ② 受け取る | 基準 |
|---|---|---|---|
| (a) 手のひらと取っ手の距離の最大 | 0.80 cm | 0.80 cm | 2 cm 以内 |
| (b) 箱の底とトレーの面の差 | 0.00 cm | ―（置かない） | 1 cm 以内 |
| (c) 引き出し | 0〜0.26 m、開けて閉めた | 同じ | 合う |
| (d) ハコブくんの胴を突き抜けたコマ | 0 | 0 | 0 |
| (f) 歩く所のすべり | 1.49 cm | 1.49 cm | 3 cm 以内 |
| (f) 床に沈んだ最大 | 0.17 cm | 0.17 cm | 2 cm 以内 |
| (f) 両足とも浮いた最大 | 1.50 cm | 1.50 cm | 2 cm 以内 |

- (e)(g)(h) は静止画で見て、袖のゆがみ・裂けも、裏返った部品もなかった。
- 腰（Hips の骨）の一番低い値は 0.577 m（立った時は 0.88 m）。
- 動画は ① 30.0 秒（900 コマ）、② 35.0 秒（1050 コマ）。カメラは依頼162 と同じで、歩く全身を横から撮るカメラを足した。
- iCloud に写した 9 ファイルは、md5 がすべて KIOXIA と同じだった。
- 見えた所（直していない）：
  - 深くしゃがむと、パンツのひざの裏と付け根に角ばったしわが出る。
  - 立っている時、かかとが 1〜2 cm 浮く。
  - 背中の肌は服から出ていなかった。
- check-in は cs:129。ただし保留は 0 になっていない（残りは Mixamo のフォルダだけ）。

**変えたファイル**
- Closeups/
  - CloseupNurse.cs
  - CloseupDirector.cs
  - Editor/CloseupShoot.cs
  - Editor/CloseupImport.cs
  - Editor/CloseupProbe.cs（新しく作った）
  - closeup163_script.json（新しく作った）
  - Closeup_Handover.unity
  - Materials/C162_*.mat
  - Closeup_Nurse.controller（外した）
- Models/
  - NurseOshiro/nurse_oshiro.fbx と .meta、材質 13 個、プレハブ
  - NurseOshiro/NurseInsideOut.shader（外した）
  - Hakobu の材質とプレハブ（読み込み直しで書き直された）
- 報告：claude_code_reports/20261113_closeup_163.txt（KIOXIA にも report163.txt として写した）

**決めてほしいこと**
1. Mixamo の FBX を Plastic（クラウド）に check-in してよいか。保留 0 にはこれが要るが、Adobe の決まりに当たるかを自分では決められないので入れていない。入れないと、Avatar と Root Motion の設定（FBX の .meta）が Plastic に残らない。
2. 台本の位置から歩く長さは 1.38 m しかない。歩き始めと歩き止めだけで 2.9 m あるので途中でつないでおり、walking のループは 0 周になっている。
3. Picking Up Object は、立ったまま前上へ手を伸ばす動きだった。かがむ深さ（腰 0.577 m）は、すべて足したひざの曲げから来ている。形は動画で見てほしい。
4. 曲がる動き（left turn）は足を引きずるので、3 cm 上げて小さく踏みかえる形にした。見え方を動画で見てほしい。

判定：止めた（決めてほしいことあり）
````

### 依頼165 の依頼文（20261113_165_受け渡しの前に画面で認証しドロワーへは上から入れ取る形に撮り直す.md）

````markdown
---
作業フォルダ: /Users/shimazueiichi/Robotics_UX/ServiceSim
上限時間: 300
---
# 依頼165：場面の寄りで、引き出しを開ける前にハコブくんの画面で認証する動きを足し、しゃがまずに上から入れる・取る形にして①積む・②受け取るを撮り直す

依頼163 の報告（claude_code_reports/20261113_closeup_163.txt）を読んでから始める。163 で作った場面（Assets/ServiceSim/Closeups/Closeup_Handover.unity・台本の JSON・Mixamo の動き）を土台にする。

エイイチの指摘（2026-11-13、依頼162 の動画を見て）：
1. **引き出しを開ける前に、画面で認証が要る。** 認証カードをハコブくんの顔（画面）のあたりにかざす。または認証番号を画面に入力する
2. **人がしゃがみすぎ。** 引き出しには、上からモノを入れる・上から取る動きにする

**病院の1日の場面（Assets/ServiceSim/Hospital/）・配膳の場面・それぞれの実行ファイルと台帳・共通の部品には触らない。** 触るのは Assets/ServiceSim/Closeups/・Assets/ServiceSim/Models/ の中だけ。Packages にも触らない。Mixamo の FBX はどこにも写さない（Adobe の決まり）。
**エイイチは見ていない。人に聞かないと進めない所に当たったら、その項目は止めて報告に書き、ほかは続ける。** ハコブくんと看護師の形（FBX）は変えない。

## 始める前に確かめること（違えば作らずに報告して終わる）
作業場所の保留 0・Unity と回し係が動いていない・KIOXIA がマウント済み・依頼163 の判定が「合格」（「止めた」「失敗」なら、この依頼は作らずに、163 の何が残っているかを報告に書いて終わる）

## 1. 認証の動き（引き出しを開ける前に必ず入れる）
- ① 積む（薬剤部）は **認証番号を画面に入力**、② 受け取る（病棟）は **認証カードを画面にかざす**。2つのやり方を1本ずつ見せる
- 画面の表示（ハコブくんの顔の画面の上に重ねる簡単な表示でよい。Unity の中で作る。ハコブくんの FBX と色の画像は変えない）：
  - ハコブくんが止まったら、顔のまま、画面の下に「認証してください」の小さな帯
  - ① 番号：看護師が画面の前に立つと、画面が数字のボタン（3×4）に変わる。右手の人さし指で4回押す（1回ごとにボタンが一瞬明るくなる・入力欄に「●」が1つずつ増える）
  - ② カード：看護師が胸ポケットから白いカード（0.086×0.054×0.001 m・ミントの帯）を左手で出し、画面の前 3〜5 cm にかざして 1 秒止める
  - 通ったら：画面に大きめのチェックの印を 1 秒 → 顔に戻る。横の輪の光が 1 回明るく点く。引き出しの鍵が開いた合図としてここから開けられる
- 指先・カードは画面に触れてよいが、画面の中に入らないこと（めり込み 0）
- 押す・かざす間の頭と体：画面を見る（首を少し下げる）。背中はまっすぐに近く立ったまま

## 2. 引き出しには上から入れる・上から取る（しゃがまない）
- 寸法（依頼162 の報告）：取っ手の中心 0.61〜0.66 m、トレーの上の面 0.435 m
- ひざは軽く曲げるだけ（太ももの曲げ 20° まで）。深くしゃがまない。かわりに **股関節から上体を前に倒す**（低い台車から物を取る時の姿勢）。曲げは Spine と Hips（股関節）に分け、背中の上の肌が服から出ないこと
- 開ける：立ったまま上体を軽く倒し、取っ手を持って引く（162 と同じく引きながら少し下がってよい）
- 入れる（①）：開いた引き出しの **真上** から箱を下ろす。手は箱の上側を持ち、手の甲が上。箱をトレーの上 10 cm まで上から下ろして置く
- 取る（②）：開いた引き出しの真上から手を下ろし、箱を上からつかんで持ち上げる
- 体を起こしてから閉める（閉めるのは手で押すだけでよい、ひざを曲げない）
- 確かめの値：看護師の腰の高さ（Hips の骨）の一番低い値が、立った時の 80% 以上。報告に書く

## 3. 台本
- 163 の流れ（歩いて来る → 止まる → …）に、引き出しの前で「認証 → 開ける → 上から入れる／取る → 閉める」を入れる。うなずき（①）・話しかけとおじぎ（②）は 163 のまま
- 一場面の長さは 30〜45 秒

## 4. 確かめ（違えば 5 は撮らずに止める）
- 163 の (a)〜(h) と同じ（(a) は開ける時の手と取っ手、(b) は箱の底とトレー）
- (i) 認証：① 押した回数 4・押すごとに指先と押したボタンの中心の距離が 1 cm 以内。② カードと画面の距離が 3〜5 cm の間で 1 秒止まっている
- (j) しゃがみ：腰の高さの一番低い値が立った時の 80% 以上・太ももの曲げが 20° 以内
- (k) 上から：入れる・取る間、手首が箱より上にある
- (l) 頭がハコブくんの画面・体に当たるコマ 0

## 5. 撮る
- 163 と同じカメラ・大きさで ①・② を1本ずつ。認証の所がよく見える寄りの静止画（① 番号を押す所・② カードをかざす所・チェックの印）と、上から入れる所・取る所の横からの静止画を足す
- 出力：/Volumes/KIOXIA/r2r2r-data/service_sim/unity/closeup/20261113_closeup165/。動画と静止画は iCloud の …/サービスロボット導入シミュレーション/hospital/closeup165/ にも写し、md5 を確かめる
- check-in（保留0）。実行ファイルは作らない

## 報告
claude_code_reports/20261113_closeup_165.txt に：始める前の確かめ、1〜3 で作った・直したもの（画面の表示の作り方・カードの作り方・腰と股関節の曲げの値）、4 の確かめ（表）、5 のカメラの値と動画の長さ、出力の場所、check-in の番号、決めてほしいこと（直し方の候補は作らない）。
**報告の最後の1行は必ず次のどれか**：「判定：合格」／「判定：止めた（決めてほしいことあり）」／「判定：失敗」
````

### 依頼165 の報告（20261113_165_受け渡しの前に画面で認証しドロワーへは上から入れ取る形に撮り直す_20261113_1002.md）

````markdown
# 結果：20261113_165_受け渡しの前に画面で認証しドロワーへは上から入れ取る形に撮り直す

- 状態：完了
- 始めた時刻：2026-11-13 10:02
- 終わった時刻：2026-11-13 10:03（1分）
- 作業フォルダ：/Users/shimazueiichi/Robotics_UX/ServiceSim
- この続きを Claude Code で話すとき：作業フォルダで `claude --resume 264e3075-00c6-4ad3-a6ee-ec63fb417087`

## Claude Code の報告

**やったこと**
- 作り始める前に、依頼で決められた点を確かめました。依頼163の判定が「合格」ではなかったので、依頼のとおりこの依頼は作らずに止めました。
- 報告を claude_code_reports/20261113_closeup_165.txt に書きました。

**結果**
- 依頼163の判定は「止めた（決めてほしいことあり）」で、合格ではありません。
- 作業場所の保留は78件で、0ではありません。78件はすべて Models/Animations/ の中のもので、依頼163で check-in しなかった Mixamo の FBX 33本とその .meta などです。いまの最新は cs:129 です。
- Unity と回し係は動いていません。KIOXIA はマウント済みです。
- 認証の画面とカード、上から入れる・取る形、台本、確かめ、撮影は、どれも手を付けていません。場面・モデル・Plastic・KIOXIA・iCloud も変えていません。check-in もしていません。

**変えたファイル**
- 報告ファイル（claude_code_reports/20261113_closeup_165.txt）だけ

**決めてほしいこと**
1. 依頼163で決めてほしいこととして出した4つに答えてほしいです。
   - Mixamo の FBX を Plastic に check-in してよいか（いちばん大事なところです）
   - walking のループが0周になっていること
   - Picking Up Object では深くかがまないこと
   - 曲がる所で足を小さく踏みかえる形にしていること

   答えが出て163が「合格」になれば、この依頼をそのまま始められます。
2. 163が「止めた」のままでもこの依頼を進めてよいか。進めてよい場合、保留0の条件から Mixamo のフォルダを除いてよいか。

判定：止めた（決めてほしいことあり）
````

### 依頼166 の依頼文（20261113_166_ハコブくんを新しい形に差し替え看護師の歩きと立ち方を依頼162に戻して撮り直す.md）

````markdown
---
作業フォルダ: /Users/shimazueiichi/Robotics_UX/ServiceSim
上限時間: 300
---
# 依頼166：ハコブくんを新しい形に差し替え、看護師の歩き・立ち方を依頼162 に戻し、ハコブくんとのやり取り（認証・上から入れる／取る）だけを作り直して、①積む・②受け取るを撮り直す

依頼162・163・165 の報告（claude_code_reports/20261113_closeup_162.txt・_163.txt・_165.txt）と、**依頼165 の依頼文の全文**（iCloud の 依頼箱/済み/20261113_165_受け渡しの前に画面で認証しドロワーへは上から入れ取る形に撮り直す.md）を読んでから始める。165 は始める条件（163 が合格）で止まり、何も作っていない。165 の 1（認証）・2（上から入れる・取る）・4 の確かめ (i)〜(l) は、この依頼でそのまま作る。

依頼163 の決めてほしいことへの答え（2026-11-13）：
1. Mixamo の FBX の Plastic への check-in → まだ決めていない。しない。保留に Mixamo のフォルダ（Models/Animations/）が残るのはかまわない
2〜4. 歩き・かがみ・曲がる所 → Mixamo の動きは使わず、依頼162 の作りに戻す（下の 2）。Mixamo のフォルダは消さずに残す

エイイチの指摘（2026-11-13、依頼163 の動画を見て）：
- **看護師の歩き方・立ち方は、前（依頼162）のほうが良かった。** 直してほしかったのはハコブくんとのやり取りの所だけで、ほかの所ではない
- **ハコブくんが直っていない。** 新しい形（横の板をやめた版）に差し替える

**病院の1日の場面（Assets/ServiceSim/Hospital/）・配膳の場面・それぞれの実行ファイルと台帳・共通の部品には触らない。** 触るのは Assets/ServiceSim/Closeups/・Assets/ServiceSim/Models/ の中だけ。Packages にも触らない。
Mixamo の FBX はどこにも写さない。Plastic への check-in もしない（エイイチがまだ決めていない）。
**エイイチは見ていない。人に聞かないと進めない所に当たったら、その項目は止めて報告に書き、ほかは続ける。**

## 正（読むもの）
- ハコブくん：p2m-site main（cab3977）の `tools/sim_robots/hakobu/`。hakobu.fbx（md5 dfd43653e3d176e19aae2f288a68c5bd）・hakobu_albedo.png（md5 148dfd59ae628e814342e6acd6d15816）・hakobu_emission.png（md5 4dc8ec34c9af0c3003ba00be6fe250b3）。部品の名前は前と同じ（Hakobu_Body・Face・Ring・Drawer・Tray_1〜4）。横の板（Pods）はなくなり、横の面がゆるくふくらみ、画面のまわりに丸いふちが付いた。大きさは前と同じ実寸
- 看護師：p2m-site main の `tools/sim_people/nurse_oshiro/nurse_oshiro.fbx`（依頼163 と同じ md5 0b940c20468ee8bf953cbafed7ca0ab6）

## 始める前に確かめること（違えば作らずに報告して終わる）
作業場所の保留は Mixamo のフォルダ（Models/Animations/）だけ・Unity と回し係が動いていない・KIOXIA がマウント済み・3つの md5（違えば作らずに報告して終わる）。依頼163・165 の判定が「止めた」なのは分かっているので、それでは止めない

## 1. ハコブくんを差し替える
- Models/Hakobu/ の FBX と画像2枚を差し替える。プレハブで Tray_1〜4 を Drawer の子にする作りと、引き出しが 0.26 m 出る作りは前のまま
- 認証の画面の表示（認証の帯・数字のボタン・チェックの印）は、新しい画面の板の上に乗せる（画面の板は前より 2 mm 前に出た）
- 横の輪の光る面が、ふくらんだ横の面から浮いたり沈んだりしていないかを静止画で見る

## 2. 看護師の動きを分ける
- **依頼162 に戻す所**：歩く・立って待つ・向きを変える・振り向く・見送る。依頼162 の台本（closeup162_script.json）と CloseupNurse.cs の動き方（Mixamo を使わない作り）に戻す。足を留める IK などで 163 に足した物は、歩きに使わないなら外してよい
- **新しく作る所（依頼165 の依頼文の 1・2 のとおり）**：ハコブくんの前に止まってから離れるまで（認証 → 開ける → 上から入れる／取る → 閉める）。① は番号を画面に入力、② はカードを画面にかざす。ひざの曲げは 20° まで、股関節から上体を倒し、引き出しの真上から入れる・取る。作り方は 162 と同じ（Mixamo を使わず、骨の回しと手の IK）
- うなずき（①）・話しかけ（②）・おじぎ（②）：依頼162 の作りに戻す。162 におじぎがなければ、162 の作り方（Spine・Chest・首を足す）で軽いおじぎを足す
- 162 の歩きから新しいやり取りへ、やり取りから 162 の立ち姿へ、つなぎ目で姿勢が飛ばないこと（0.3〜0.5 秒でなめらかに移す）

## 3. 確かめ（違えば 4 は撮らずに止める）
- 依頼165 の依頼文の (a)〜(l) と同じ（(f) は 162 の歩きに戻すので、足のすべり 3 cm 以内・沈む浮く 2 cm 以内として測る）
- (m) つなぎ目：歩き → やり取り、やり取り → 立ち姿 で、1 コマの間に骨（Hips・手首・足首）が 3 cm より大きく飛ばない
- (n) ハコブくん：輪と画面の表示が面から浮いていない・沈んでいない（静止画を目で見る）

## 4. 撮る
- 依頼163 と同じカメラ・大きさで ①・② を1本ずつ。静止画は 165 の依頼文の種類（認証の寄り・上から入れる・取る所の横から）と 163 の種類に、ハコブくんの正面・横・斜めの静止画（看護師なし）を足す
- 出力：/Volumes/KIOXIA/r2r2r-data/service_sim/unity/closeup/20261113_closeup166/。動画と静止画は iCloud の …/サービスロボット導入シミュレーション/hospital/closeup166/ にも写し、md5 を確かめる
- check-in（Mixamo のフォルダ以外は保留0）。実行ファイルは作らない

## 報告
claude_code_reports/20261113_closeup_166.txt に：始める前の確かめ、1〜2 で変えたもの（162 に戻した所・新しく作ったやり取りの時刻の区切り、画面の表示とカードの作り方、腰と股関節の曲げの値、腰の高さの一番低い値）、3 の確かめ（表）、4 のカメラの値と動画の長さ、出力の場所、check-in の番号、決めてほしいこと（直し方の候補は作らない）。
**報告の最後の1行は必ず次のどれか**：「判定：合格」／「判定：止めた（決めてほしいことあり）」／「判定：失敗」
````

### 依頼166 の報告（20261113_166_ハコブくんを新しい形に差し替え看護師の歩きと立ち方を依頼162に戻して撮り直す_20261113_1007.md）

````markdown
# 結果：20261113_166_ハコブくんを新しい形に差し替え看護師の歩きと立ち方を依頼162に戻して撮り直す

- 状態：完了
- 始めた時刻：2026-11-13 10:07
- 終わった時刻：2026-11-13 10:56（48分）
- 作業フォルダ：/Users/shimazueiichi/Robotics_UX/ServiceSim
- この続きを Claude Code で話すとき：作業フォルダで `claude --resume d2afe028-6e7b-451a-8991-fc4ecf90ce21`
- 止められた操作：1件（いちばん下に一覧）

## Claude Code の報告

**やったこと**
- 始める前の確かめはすべて合っていました。保留は Mixamo のフォルダの 78 件だけ、Unity と回し係は動いておらず、KIOXIA はマウント済み、4 つの md5 も依頼の値と同じでした。
- ハコブくんを新しい形（cab3977・横の板なし）に差し替えました。看護師の動きは依頼162 の作りに戻し、Mixamo の動きは外しました。歩く・向きを変える・振り向く・見送る・うなずき・話しかけは 162 の値のままです。
- やり取りは新しく作りました（認証 → 開ける → 上から入れる／取る → 押して閉める）。① は数字のボタンを右手の人さし指で入力し、② は胸ポケットのカードを左手で画面にかざします。
- おじぎは 162 の作り方（背中・胸・首を足す）で足しました。
- 画面の表示（帯・数字のボタン・チェックの印）は、画面の板の 1.5 mm 前に重ねた板です。

**結果**
- 依頼162 そのままの歩き方では足が最大 43 cm すべり、(f) の 3 cm に入りませんでした。そこで脚と腕の振りは 162 のままにして、足だけ床に留める形を足しました。
- 確かめ (a)〜(n) はすべて基準の中で、確かめの結果で止めた項目はありません。主な値は次のとおりです。
  - (a) 開ける時の手と取っ手：0.80 cm
  - (b) 箱の底とトレーの差：−0.19 cm
  - (d) 胴を突き抜けたコマ：0
  - (f) 足のすべり・沈み・浮き：いずれも 0.00 cm
  - (i) 番号は 4 回押し、指先とボタンの中心はどれも 0.45 cm。カードは画面の前 4.50 cm で 1.47 秒止まり、指のめり込みは 0
  - (j) 腰の高さの一番低い値は立った時の 96.6 %（やり取りの間だけでは 97.2 %）、太ももの曲げの最大は 18.4°
  - (k) 手首は箱の上の面より 0.55 cm 以上上
  - (l) 頭がハコブくんに当たるコマは 0
  - (m) つなぎ目での骨の動きは 1 コマ最大 2.85 cm
  - (n) 輪の光る面は横の面より 2〜12 mm 外にあり、沈みはありません。輪の裏の面で横の面より外に出ている点は 12 個（0.5 mm 超・最大 2.0 mm）ありますが、拡大した静止画ではすき間は見えません。
- 動画は ① 34.0 秒（1020 コマ）・② 39.5 秒（1185 コマ）です。静止画は本番 17 枚と確かめ用 19 枚です。
- 出力は KIOXIA の closeup/20261113_closeup166/ にあります。iCloud の hospital/closeup166/ にも 19 ファイルを写し、md5 はすべて同じでした。
- check-in は cs:130 です。保留は Mixamo のフォルダの 78 件だけです。

**変えたファイル**
- `Assets/ServiceSim/Closeups/`：
  - CloseupNurse.cs・CloseupDirector.cs・Editor/CloseupShoot.cs・Editor/CloseupImport.cs
  - 場面・コントローラー・台本 closeup166_script.json
  - 材質 4 つ・画面の絵 11 枚
  - Editor/CloseupProbe.cs は削除
- `Assets/ServiceSim/Models/Hakobu/`：hakobu.fbx・hakobu_albedo.png。emission の画像は md5 が前と同じでした。
- 報告：claude_code_reports/20261113_closeup_166.txt（KIOXIA にも写しを置きました）

**決めてほしいこと**
1. 太ももの曲げ 20° 以内のまま高さ 0.435 m のトレーに手を届かせると、上体がほぼ水平（88°）まで倒れます。そのとき頭はハコブくんの画面の 7.3 cm 前に来ます。この形でよいですか。
2. 歩き方は、162 の脚と腕の振りに足を床に留める形を足したものです。これを「162 の歩き」としてよいですか。
3. ① は右手で番号を押すため、箱は左手で取って手前の左のトレーに入れる形にしました。袋は奥の右のトレーへ移しました。これでよいですか。
4. 看護師の形にポケットがないので、Unity の中で服の色の薄い板を胸ポケットとして足しました。作る側で看護師の形にポケットを入れますか。
5. カードは画面の前で 30° 傾けて持つ形にしました（真上に向けると手首が返りきらないため）。この持ち方でよいですか。
6. Mixamo の FBX の check-in は、依頼163 の答えのとおり保留のままです。

判定：合格

## 止められた操作

- {"tool_name": "Bash", "tool_use_id": "toolu_01X9oRMCTLBsoRM7v8jFxppQ", "tool_input": {"command": "cd /Users/shimazueiichi/Robotics_UX/ServiceSim/Assets/ServiceSim/Closeups; python3 - <<'EOF'\np='Editor/CloseupShoot.cs'\ns=open(p,encoding='utf-8').read()\na='''            public int screenPenFrames;                     // 体のどこかが画面の板の中に入ったコマ'''\nassert a in s\ns=s.replace(a,a+'''\n            public int drawerPenFrames; public float drawerPenT = -1f; public string drawerPenPart = \"\";   // 頭・服・髪・
````

### 依頼167 の依頼文（20261113_167_受け取るの場面で引き出しを自動で開け閉めし看護師は正面の上から取る形に直す.md）

````markdown
---
作業フォルダ: /Users/shimazueiichi/Robotics_UX/ServiceSim
上限時間: 240
---
# 依頼167：②受け取るの1本だけを直す。引き出しは認証で自動で開き、受け取って何もしなければ自動で閉まる。看護師は引き出しの正面に立ち、開いた口の真上から取る（引き出しをすり抜けない）。おじぎを外す

依頼166 の報告（claude_code_reports/20261113_closeup_166.txt）を読んでから始める。166 の場面（cs:130）を土台にする。

エイイチの指摘と決定（2026-11-13、依頼166 の②の動画を見て）：
1. **引き出しは、認証が通ったら自動で開く。** 看護師が取っ手を引かない
2. **看護師が引き出しの横から物を取っていて、引き出しをすり抜けている。** 正面に立ち、開いた口の上から取る
3. **閉まり方：看護師が物を受け取ったあと、何もしなかったら自動で閉まる。** 看護師は閉めない
4. **おじぎを外す**（依頼166 で足した物。依頼162 と同じく、話しかけるだけに戻す）
5. **動画は②受け取るの1本だけ。** ①は撮らない
6. **指示していない動作は変えない。** 歩く・立つ・向きを変える・振り向く・見送る・うなずき・話しかけ・まばたき・カードの出し方とかざし方・画面の表示・足を床に留める形は、166 のまま。ハコブくん（cab3977 の形）・カメラ・場面も 166 のまま

**触るのは Assets/ServiceSim/Closeups/ の中だけ。** Models/・病院の1日の場面・配膳の場面・共通の部品・Packages・実行ファイルには触らない。①の作り（台本・コード）は消さずに残す（撮らないだけ）。Mixamo の FBX は写さない・check-in しない。
**エイイチは見ていない。人に聞かないと進めない所に当たったら、その項目は止めて報告に書き、ほかは続ける。**

## 始める前に確かめること（違えば作らずに報告して終わる）
保留は Mixamo のフォルダ（Models/Animations/）だけ・Unity と回し係が動いていない・KIOXIA がマウント済み・最新が cs:130

## 1. 引き出しが自動で開く・閉まる
- 認証が通ってチェックの印が出たら（166 と同じ時刻）、引き出しが自分で 0.26 m まで出る（1.0〜1.5 秒・はじめと終わりをゆっくり）。看護師は取っ手に触らない
- 看護師が箱を持って引き出しの上から手を抜き、手が引き出しの上の面より 10 cm 以上離れてから 3 秒、何もしなければ引き出しが自分で閉まる（1.0〜1.5 秒）。この3秒の間、看護師は箱を持って立っている（166 の立ち姿）
- 開く・閉まる間、画面と輪の表示は 166 のまま（新しい表示は足さない）

## 2. 看護師は正面に立ち、上から取る
- 看護師は、開いたあとの引き出しの **正面**（引き出しが出てくる向きの延長の上、引き出しの左右の真ん中）に立つ。引き出しが出てきても体に当たらない距離に立ち、出てくる間は立ったまま待つ
- 取る：開いた口の **真上** から手を下ろし、箱を上からつかみ、まっすぐ上へ持ち上げる。箱の底が引き出しの壁の上の面より 3 cm 以上上に出てから、手前（体のほう）へ引く
- かがみ方：166 と同じく、ひざの曲げ 20° まで・股関節から上体を倒す（ここは変えない）。届かない時は止めて報告する
- 箱はトレーの上で 166 と同じ所にある

## 3. 確かめ（違えば 4 は撮らずに止める）
- (o) すり抜け：取る間（手を入れ始めてから手が引き出しの上の面より 10 cm 離れるまで）、看護師の手・指・腕・袖・頭・髪と箱が、引き出しの前の板・横の壁・奥の壁・底の中に入ったコマが 0。どの部品が何コマ入ったかを数える方法を報告に書く
- (p) 立つ位置：看護師の両足の真ん中が、引き出しの左右の真ん中から 10 cm 以内
- (q) 引き出しが出てくる間、看護師の体と引き出しが当たるコマが 0
- (r) 自動で閉まる：手が離れてから閉まり始めるまで 3.0 秒・閉まる間に看護師の手と箱が引き出しに当たるコマ 0
- (s) おじぎがない（Spine・Chest の前へ倒す量が、話しかけの間 166 の話しかけと同じ）
- 166 の (d)(i)(k)(l)(m) と同じ。(a)（手と取っ手）は、取っ手を持たないので数えない
- 歩く・立つ・話しかけの所の骨の動きが、166 と同じ時刻で同じ（ずれが 1 mm 以内）こと。指示した所の前後で時刻がずれる時は、ずれの量を報告に書く

## 4. 撮る
- ②受け取るの1本だけ。カメラ・大きさは 166 の②と同じ。静止画は 166 の②の種類（カードをかざす所・チェックの印・取る所・取る所の横から・話しかけ）に、引き出しが自分で開いた所と、自分で閉まった所を足す
- 出力：/Volumes/KIOXIA/r2r2r-data/service_sim/unity/closeup/20261113_closeup167/。動画と静止画は iCloud の …/サービスロボット導入シミュレーション/hospital/closeup167/ にも写し、md5 を確かめる
- check-in（Mixamo のフォルダ以外は保留0）。実行ファイルは作らない

## 報告
claude_code_reports/20261113_closeup_167.txt に：始める前の確かめ、1〜2 で変えたもの（変えた時刻の区切り・立つ位置・かがみの値）、166 から変えたファイルと変えた理由（指示の 1〜4 のどれに当たるか）、3 の確かめ（表）、動画の長さ、出力の場所、check-in の番号、決めてほしいこと（直し方の候補は作らない）。
**報告の最後の1行は必ず次のどれか**：「判定：合格」／「判定：止めた（決めてほしいことあり）」／「判定：失敗」
````

### 依頼167 の報告（20261113_167_受け取るの場面で引き出しを自動で開け閉めし看護師は正面の上から取る形に直す_20261113_1107.md）

````markdown
# 結果：20261113_167_受け取るの場面で引き出しを自動で開け閉めし看護師は正面の上から取る形に直す

- 状態：完了
- 始めた時刻：2026-11-13 11:07
- 終わった時刻：2026-11-13 11:31（24分）
- 作業フォルダ：/Users/shimazueiichi/Robotics_UX/ServiceSim
- この続きを Claude Code で話すとき：作業フォルダで `claude --resume 3a207e6e-aa95-4611-b4d7-010ca2b8df95`

## Claude Code の報告

**やったこと**
- 始める前の確かめはすべて合っていました。保留は Mixamo のフォルダの 78 件だけ、Unity と回し係は停止、KIOXIA はマウント済み、最新は cs:130 です。
- 引き出しは、チェックの印が出る 14.7 秒（166 と同じ）に自分で開くようにしました。14.7〜15.95 秒で 0.26 m まで出て、看護師は取っ手に触りません。
- 閉まるほうは、箱を持った手が引き出しから 10 cm 離れた時刻を毎コマ測り、その 3 秒後から 1.25 秒で自分で閉まるようにしました。
- 看護師は 166 と同じ所（正面・前へ 1.05 m・左右 0）に立ちます。開いた口の真上へ手を上げ、まっすぐ下ろして箱を上からつかみ、まっすぐ持ち上げてから手前へ引きます。
  - かがみの値：股関節 50°、Spine 24°、Chest 6°、ひざ 10°。166 は 60°・30°・8°・10° でした。
  - 取る間、左手は左の太ももに置きます。下へ垂らすと腕が引き出しに入るためです。
- おじぎ（32.2〜33.8 秒）を外しました。
- すり抜けは、引き出しを前の板・取っ手・横の壁 2 枚・奥の壁・底の 6 つの箱にして数えました。看護師の肌・服・髪の三角形と箱が重なるかを毎コマ調べ、部位ごとに数えています。
- 3 の確かめで (q) が合わなかったので、4 の撮影はしていません。動画・静止画・iCloud への写しはありません。

**結果**
- (o) 取る間（150 コマ）のすり抜け：0 コマ
- (p) 両足の真ん中と引き出しの真ん中の差：0.00 cm
- **(q) 引き出しが出てくる間に体と当たったコマ：38 コマ中 13 コマ（合わない）**
  - 右手は 15.567 秒から、ひざは 15.867 秒から前の板に入り、看護師が下がり始めるまで続きます（場面全体で 26 コマ）。
  - わけ：開く時、看護師はカードをかざす 0.74 m に立っています。開いた前の板は 0.6155 m まで出てくるので、前かがみで下がった右手とひざに届きます。
- (r) 手が離れて（22.833 秒）から閉まり始めるまで 3.000 秒。閉まる間に手と箱が当たったコマは 0 です。
- (s) 話しかけの間の Spine・Chest の差は 0°（おじぎなし）です。
- (d) 0、(i) 166 と同じ、(k) 手首は箱の上の面より 7.59 cm 上、(l) 0・0、(m) 2.85 cm／0.66 cm で、どれも合っています。
- 骨の動きは、0〜17.3 秒・話しかけ・見送りで 166 とのずれが 0.010 mm 以内です。時刻のずれはありません。
- ① は、新しいコードで回しても 166 と全部同じ値でした。
- check-in は cs:131 です。撮影は止めましたが、次の依頼の始める前の確かめのために check-in しました。保留は Mixamo のフォルダの 78 件だけです。

**変えたファイル**
- CloseupDirector.cs
- CloseupNurse.cs
- Editor/CloseupShoot.cs
- closeup167_script.json（新）
- Closeup_Handover.unity（組み直し）

いずれも Assets/ServiceSim/Closeups/ の中です。報告は claude_code_reports/20261113_closeup_167.txt に書き、KIOXIA の …/closeup/20261113_closeup167/report167.txt にも写しました。確かめの数字と静止画は同じ KIOXIA のフォルダの check_final/ にあります。

**決めてほしいこと**
1. 次の 3 つは同時に満たせません。どれを変えてよいか決めてください。
   - 引き出しはチェックの印（14.7 秒）で開く
   - カードのかざし方・立つ所・歩きは 166 のまま
   - 引き出しが出てきても当たらない所に立って待つ
2. 手が離れてからの 3 秒のうち、はじめの 0.27 秒は体を起こし終わる途中になります。これでよいか。
3. 取る間、左手を太ももに置く形でよいか。
4. 話しかけの後の頭の戻しを、162 と同じ 1.0 秒にしました。これでよいか。
5. Mixamo の FBX の check-in（保留のまま）。

判定：止めた（決めてほしいことあり）
````

### 依頼168 の依頼文（20261113_168_受け取るの場面で看護師を引き出しの斜め横に立たせて撮る.md）

````markdown
---
作業フォルダ: /Users/shimazueiichi/Robotics_UX/ServiceSim
上限時間: 240
---
# 依頼168：②受け取るの1本だけ。看護師を引き出しの斜め横に立たせ、出てくる引き出しに当たらないようにして撮る

依頼167 の報告（claude_code_reports/20261113_closeup_167.txt）を読んでから始める。167 の場面（cs:131）を土台にする。

エイイチの決定（2026-11-13、依頼167 の決めてほしいことへの答え）：
1. **看護師は引き出しの斜め横に立つ。** 引き出しが出てくる道の外（引き出しの前の角の、斜め前）に立ち、そこでカードをかざし、そこで引き出しが出てくるのを待ち、そこから開いた口の真上へ手を入れて取る。引き出しは 167 と同じくチェックの印（14.7 秒）で自分で開く
2. **取る間、左手は太ももに置く**（167 のまま）
3. 167 の決めてほしいことの 2（手が離れてからの 3 秒）・4（話しかけの後の頭の戻し 1.0 秒）は答えがないので、167 のまま変えない

**指示していない動作は変えない。** 変えてよいのは、立つ所（斜め横）と、そこへ歩いて止まる所・そこから手を入れて取る所の手と体の向きだけ。歩き方・立ち方・カードの出し方とかざし方・画面の表示・引き出しの開け閉め（時刻・速さ・3 秒）・取る形（上から下ろしてつかみ、まっすぐ上げてから手前へ）・かがみの値（ひざ 20° まで、股関節から倒す）・うなずき・話しかけ・まばたき・ハコブくん・カメラ・場面は 167 のまま。
**触るのは Assets/ServiceSim/Closeups/ の中だけ。** ①の作りは消さずに残す（撮らない）。Mixamo の FBX は写さない・check-in しない。
**エイイチは見ていない。人に聞かないと進めない所に当たったら、その項目は止めて報告に書き、ほかは続ける。**

## 始める前に確かめること（違えば作らずに報告して終わる）
保留は Mixamo のフォルダ（Models/Animations/）だけ・Unity と回し係が動いていない・KIOXIA がマウント済み・最新が cs:131

## 1. 立つ所
- 左右どちらの斜め横にするかは、看護師が歩いて来る向きから近いほうにする（歩く道を変えない、または変える量が少ないほう）。選んだ側と理由を報告に書く
- 立つ所は、開いた引き出しの前の板・横の壁から体（服・手・ひざ・靴）まで 5 cm 以上離れ、かつカードが画面の前 3〜5 cm に届く所。体はハコブくんの画面と開いた引き出しの両方が見える向き
- 届かない・当たる時は止めて報告する（立つ所の候補と、その時の距離を書く）

## 2. 確かめ（違えば 3 は撮らずに止める）
- 167 の (o)(q)(r)(s)(d)(i)(k)(l)(m) と同じ。(q) は「引き出しが出てくる間に体と当たったコマ 0」
- (p) は「立つ所が 1 の条件に入っている」に置きかえる（前の板・横の壁から体までの一番近い距離を書く）
- (t) 取る時、手は開いた口の真上から入る（手首が引き出しの壁の上の面より上を通って口の上へ来てから下りる）
- 167 から変わった所が、立つ所・そこへ止まる所・取る所の手と体の向きだけであること。ほかの所の骨の動きは 167 とのずれが 1 mm 以内。歩く道の終わりを変えた時は、変えた区間と量を報告に書く

## 3. 撮る
- ②受け取るの1本だけ。カメラ・大きさは 166 の②と同じ。ただし看護師が画面の外に出る時は、カメラの向きだけ変えてよい（変えた値を報告に書く）
- 静止画は 167 の依頼の種類（カードをかざす所・チェックの印・引き出しが自分で開いた所・取る所・取る所の横から・自分で閉まった所・話しかけ）
- 出力：/Volumes/KIOXIA/r2r2r-data/service_sim/unity/closeup/20261113_closeup168/。動画と静止画は iCloud の …/サービスロボット導入シミュレーション/hospital/closeup168/ にも写し、md5 を確かめる
- check-in（Mixamo のフォルダ以外は保留0）。実行ファイルは作らない

## 報告
claude_code_reports/20261113_closeup_168.txt に：始める前の確かめ、選んだ側と立つ所の値、167 から変えたファイルと変えた理由、2 の確かめ（表）、動画の長さ、出力の場所、check-in の番号、決めてほしいこと（直し方の候補は作らない）。
**報告の最後の1行は必ず次のどれか**：「判定：合格」／「判定：止めた（決めてほしいことあり）」／「判定：失敗」
````

### 依頼168 の報告（20261113_168_受け取るの場面で看護師を引き出しの斜め横に立たせて撮る_20261113_1149.md）

````markdown
# 結果：20261113_168_受け取るの場面で看護師を引き出しの斜め横に立たせて撮る

- 状態：完了
- 始めた時刻：2026-11-13 11:49
- 終わった時刻：2026-11-13 12:02（12分）
- 作業フォルダ：/Users/shimazueiichi/Robotics_UX/ServiceSim
- この続きを Claude Code で話すとき：作業フォルダで `claude --resume 5aa15cc0-2a14-4062-877b-5bb2a1da6f05`

## Claude Code の報告

**やったこと**
- 始める前の確かめは、すべて合っていました。
  - 保留は Mixamo のフォルダの 78 件だけです。
  - Unity と回し係は動いていません。
  - KIOXIA はマウント済みです。
  - 最新は cs:131 です。
- 斜め横は −x の側（看護師から見て右）を選びました。理由は三つです。
  - 看護師は x −0.05 m から歩いて来るので、歩く道の終わりを変える量が少ない。
  - カードをかざす左手が画面の側に来る。
  - 箱も引き出しの −x の側にある。
- 立つ所の候補を 6 か所、Unity で回して測りました。167 の台本から、立つ所（根の位置と向き）だけを替えています。測ったのは、カードと画面の距離、体（肌・服・靴・手）と開いた前の板・横の壁の一番近い距離です。

**結果**
- 1 の条件を満たす立つ所はありませんでした。依頼のとおり 1 で止め、2 の確かめと 3 の撮影（動画・静止画・iCloud への写し）はしていません。

| 候補 | 根 x・z・向き | カードと画面の一番近い距離 | 体と前の板・横の壁の一番近い距離 |
|---|---|---|---|
| A | −0.28・0.78・180° | 5.15 cm（届かない） | ひざ 3.63 cm／下ろした左手 0.79 cm |
| B | −0.40・0.75・165° | 10.96 cm（届かない） | ひざ 3.35 cm |
| C | −0.30・0.68・185° | 3.85 cm（4.5 cm で 1.40 秒、届く） | ひざが当たる（0 cm、当たったコマ 15） |
| D | −0.29・0.79・192° | 4.72 cm | ひざ 3.04 cm／左手 0 cm |
| E | −0.31・0.80・200° | 5.08 cm（届かない） | ひざ 3.07 cm |
| F | −0.30・0.77・195° | 4.61 cm | ひざ 0.68 cm |

- わけ：167 のかざし方では、左腕がほぼ伸びきっています（肩から手首 0.466 m、腕の長さは約 0.47 m）。カードが届く所に立つと、ひざと、カードをしまった後に体の横へ下ろす左手が、開いた前の板の角から 5 cm 以内に入ります。
- 測る所を足したコードで 167 の台本を回し直し、コマごとの値と骨の位置が 167 の本番と全部同じになることを確かめました。
- 動画の長さ：なし。
- check-in は cs:132 です。保留は Mixamo のフォルダの 78 件だけです。

**変えたファイル**
- `Assets/ServiceSim/Closeups/Editor/CloseupShoot.cs`：立つ所の距離を測る所を足しました。台本に `check_stand` がある時だけ動きます。
- 台本・場面・ほかのコードは変えていません。①の作りもそのままです。候補の台本はプロジェクトから消し、KIOXIA に写しました。
- 報告：`claude_code_reports/20261113_closeup_168.txt`（同じものを KIOXIA の `closeup/20261113_closeup168/report168.txt` にも置きました）。
- 測った値と候補の台本：`/Volumes/KIOXIA/r2r2r-data/service_sim/unity/closeup/20261113_closeup168/`

**決めてほしいこと**
1. 斜め横では「体が前の板・横の壁から 5 cm 以上」と「カードが画面の前 3〜5 cm に届く（167 のかざし方のまま）」が両立しません。どちらを、または何を変えてよいか。
2. カードをしまった後に体の横へ下ろす左手も、5 cm の条件の「手」に入れて測りました。これでよいか。
3. 167 の決めてほしいことの 2（手が離れてからの 3 秒）と 4（話しかけの後の頭の戻し 1.0 秒）は、167 のままにしてあります（答え待ち）。
4. Mixamo の FBX を Plastic に check-in するか（今は保留のまま）。

判定：止めた（決めてほしいことあり）
````

### 依頼169 の依頼文（20261113_169_受け取るの場面を今のまま撮り当たる所を赤で見せる.md）

````markdown
---
作業フォルダ: /Users/shimazueiichi/Robotics_UX/ServiceSim
上限時間: 90
---
# 依頼169：②受け取るを、いまの形（依頼167）のまま撮る。何も直さない。当たる所を赤で見せる

エイイチが「いまの状況を動画で見たい。何が問題か分からない」と言っている。依頼167・168 は確かめで止まり、動画を撮っていない。**動きは何も直さずに**、いまの形を動画にする。

**直さない。** 台本・動き・立つ所・ハコブくん・カメラ・場面は依頼167（cs:131 の closeup167_script.json）のまま。依頼168 の候補の台本も使わない。
**触るのは Assets/ServiceSim/Closeups/ の中だけ**（撮るための表示を足すだけ）。Mixamo の FBX は写さない・check-in しない。

## 始める前に確かめること（違えば作らずに報告して終わる）
保留は Mixamo のフォルダ（Models/Animations/）だけ・Unity と回し係が動いていない・KIOXIA がマウント済み・最新が cs:132

## 撮る（2本）
1. **本番のカメラ（166 の②と同じ）で、②受け取るを通しで1本。** 確かめで止めずに最後まで撮る
2. **横から（引き出しが出てくる向きを真横から見るカメラ）で、カードをかざす所から箱を取って体を起こすまで（約 13〜24 秒）を1本。** 0.5 倍の速さ（ゆっくり）
- どちらも、**看護師の体（肌・服・靴・手）が引き出し（前の板・壁）に入ったコマは、入った部品を赤く光らせる**（依頼167 の (q) で数えた当たり＝引き出しが出てくる時に右手とひざが前の板に入る所）。画面の左上に小さく「当たり：右手」「当たり：ひざ」のように出す。ほかの文字は足さない
- 大きさ・コマ数は 166 と同じ（1920×1080・30 コマ/秒）

## 出力
- /Volumes/KIOXIA/r2r2r-data/service_sim/unity/closeup/20261113_closeup169/ と、iCloud の …/サービスロボット導入シミュレーション/hospital/closeup169/ に動画2本。md5 を確かめる
- 赤く光らせる表示は、撮る時だけ付く作りにする（場面の動きは変えない）。check-in（Mixamo のフォルダ以外は保留0）。実行ファイルは作らない

## 報告
claude_code_reports/20261113_closeup_169.txt に：始める前の確かめ、動画2本の長さとカメラの値、赤くなった部品と時刻（秒）、出力の場所、check-in の番号。
**報告の最後の1行は必ず次のどれか**：「判定：合格」／「判定：止めた（決めてほしいことあり）」／「判定：失敗」
````

### 依頼169 の報告（20261113_169_受け取るの場面を今のまま撮り当たる所を赤で見せる_20261113_1233.md）

````markdown
# 結果：20261113_169_受け取るの場面を今のまま撮り当たる所を赤で見せる

- 状態：完了
- 始めた時刻：2026-11-13 12:33
- 終わった時刻：2026-11-13 12:48（15分）
- 作業フォルダ：/Users/shimazueiichi/Robotics_UX/ServiceSim
- この続きを Claude Code で話すとき：作業フォルダで `claude --resume df0371e3-a482-4e55-a6fa-62e8acc50826`
- 止められた操作：1件（いちばん下に一覧）

## Claude Code の報告

## やったこと
- 始める前の4つの確かめは全部合っていました。保留は Mixamo のフォルダ（Models/Animations/）だけ、Unity と回し係は動いていない、KIOXIA はマウント済み、最新は cs:132 です。
- ②受け取るを、依頼167 の台本（cs:131 の closeup167_script.json）のまま撮りました。動き・立つ所・ハコブくん・カメラ・場面は直していません。場面の組み直しもしていません。
- 撮る時だけ付く表示を `Editor/CloseupShoot.cs` に足しました。指定しなければ、いままでと何も変わりません。
  - 当たりの表示：看護師の体が引き出しの部品に入ったコマだけ、入った所の近く（部品から 6 cm 以内）を赤く描きます。
  - 横からのカメラ：決めた時間だけ、同じコマを横からも撮ります。
- 左上の「当たり：…」の文字は、撮った後に書き足しました。ほかの文字は足していません。
- 1回目はズボンの足首に近い所を「足」と呼んでいたので、「すね」に直して撮り直しました。

## 結果
- 動きが変わっていないことを確かめました。骨の位置は依頼167 の確かめと全部同じです。当たりのコマ数も同じで、引き出しが出てくる間（14.733〜15.967 秒）が 13 コマ、場面全体で 26 コマ（15.567〜16.367 秒）です。
- 動画は2本とも 1920×1080・30 コマ/秒です。
  1. **本番のカメラ**（166 の②と同じ）：通しで 39.500 秒（1185 コマ）。
  2. **横から**：場面の 13〜24 秒を 0.5 倍の速さにして 22.067 秒（662 コマ）。
     - カメラは、ハコブくんの位置から見て位置 (-2.6, 0.8, 0.7)、見る所 (0, 0.8, 0.7)、画角 40 度です。看護師の右側の真横から、引き出しが出てくる向きを見ています。
     - 0.5 倍は同じコマを2回ずつ並べて作りました。動きの計算は変えていないので、絵が変わるのは1秒に15回です。
- 赤くなった部品と時刻（場面の秒）：

| 部位 | 入った部品 | 時刻 | コマ |
|---|---|---|---|
| 右手 | 前の板 | 15.567〜16.233 秒 | 21 |
| ひざ | 前の板 | 15.867〜16.367 秒 | 16 |
| もも・すね | 前の板 | 15.900〜16.367 秒 | 各 15 |
| ひざ・すね | 底 | 16.233〜16.300 秒 | 3 |
| ひざ・もも | 取っ手 | 16.267〜16.300 秒 | 2 |

- 本番のカメラの 15.567〜15.8 秒は画面に寄ったカメラなので、手と脚は写らず、左上の文字だけが出ます。赤は 15.8 秒からの引いたカメラで見えます。横からの動画では 5.13〜6.80 秒の所です。
- 出力の場所：
  - KIOXIA の closeup/20261113_closeup169/video/ と、iCloud の hospital/closeup169/ に2本置きました。md5 は両方で同じでした。
  - 報告は claude_code_reports/20261113_closeup_169.txt に書き、KIOXIA にも写しを置きました。
- check-in は cs:133 です。保留は Mixamo のフォルダだけです。実行ファイルは作っていません。Mixamo の FBX は写しておらず、check-in もしていません。
- 1回目の途中のファイル（try1_tmp169_leftover）が KIOXIA に残っています。消すことが許されなかったので、名前を変えて置いてあります。
- 判定：合格

## 変えたファイル
- `Assets/ServiceSim/Closeups/Editor/CloseupShoot.cs`（cs:133）
- 新しく作ったもの（KIOXIA の closeup/20261113_closeup169/ の中）：
  - tools/shoot169.sh
  - tools/make169.py
  - report169.txt

## 決めてほしいこと
なし（動画を見たうえで、依頼167 の「決めてほしいこと」1〜5 に答えてもらえれば次に進めます）

## 止められた操作

- {"tool_name": "Bash", "tool_use_id": "toolu_01EbPUKr6vXvc2TPtDWv7Z5E", "tool_input": {"command": "D=/Volumes/KIOXIA/r2r2r-data/service_sim/unity/closeup/20261113_closeup169; ls $D/_tmp169; rm -rf \"$D/_tmp169\"; sed -i '' \"s|^T = os.path.join(OUT, '_tmp169')|T = '/tmp/_tmp169'   # KIOXIA（exFAT）の上だと消す時にこける|\" $D/tools/make169.py; grep -n \"^T =\" $D/tools/make169.py; cd /tmp && python3 -I $D/tools/make169.py $D; FP=/opt/homebrew/Caskroom/miniforge/base/envs/ros_env/bin/ffprobe; for v in $D/video
````

### 依頼170 の依頼文（20261113_170_受け取るの場面で斜め横に立ち腰を起こし手首をまっすぐにして撮る.md）

````markdown
---
作業フォルダ: /Users/shimazueiichi/Robotics_UX/ServiceSim
上限時間: 240
---
# 依頼170：②受け取るの1本だけ。斜め横に立つ・腰をもう少し起こす・手首をまっすぐにする

依頼167・168・169 の報告（claude_code_reports/20261113_closeup_167.txt・_168.txt・_169.txt）を読んでから始める。依頼167 の台本（closeup167_script.json）と、依頼168 で足した立つ所を測る所を土台にする。

エイイチの決定（2026-11-13、依頼167 の静止画を見て）：
1. **看護師は引き出しの斜め横に立つ**（依頼168 と同じ。−x の側＝看護師から見て右）。依頼168 の「体と引き出しを 5 cm 以上あける」は Claude が決めた数字で厳しすぎたので、**「当たらない（1 cm 以上）」にゆるめる**。依頼168 の候補 D（根 −0.29・0.79・192°）を出発点にする
2. **腰をもう少し起こす。** 股関節の倒しを 167 の 50° から 40° 前後へ減らし、そのぶんひざを曲げる（25° 前後・30° まで）。深くしゃがむ形にはしない。頭はハコブくんの画面から 15 cm 以上離れる
3. **手首をまっすぐにする。** 取る時に手首が大きく折れていて気持ち悪い。手首の曲がりは前腕に対して 25° 以内。手のひらを下に向けて、箱を上から包むようにつかむ。高さと届きは肘と肩で合わせる
4. **カードを下ろした後の左手**は、引き出しに当たらない所へ下ろす。取る間と同じく太ももに置く形でよい

**指示していない動作は変えない。** 変えてよいのは、上の 1〜4 だけ（立つ所とそこへ止まる所、かがみの値、取る時の右手首と腕、左手の下ろし先）。歩き方・カードの出し方とかざし方（167 の腕の形）・画面の表示・引き出しの自動の開け閉め（時刻・速さ・3 秒）・取る順（上から下ろしてつかみ、まっすぐ上げてから手前へ）・うなずき・話しかけ・まばたき・ハコブくん・場面は 167 のまま。
**触るのは Assets/ServiceSim/Closeups/ の中だけ。** ①の作りは消さずに残す（撮らない）。Mixamo の FBX は写さない・check-in しない。
**エイイチは見ていない。人に聞かないと進めない所に当たったら、その項目は止めて報告に書き、ほかは続ける。**

## 始める前に確かめること（違えば作らずに報告して終わる）
保留は Mixamo のフォルダ（Models/Animations/）だけ・Unity と回し係が動いていない・KIOXIA がマウント済み・依頼169 が終わっている（判定は問わない）

## 1. 立つ所
- 候補 D から始め、次の全部を満たす所を探す：カードが画面の前 3〜5 cm に届く／引き出しが出てくる間と取る間、体（肌・服・靴・手）と引き出しの前の板・壁が 1 cm 以上離れる／新しいかがみ（2）で、開いた口の真上から右手が箱に届く
- 見つからない時は撮らずに止め、試した所と、その時の距離・届きの値を表にして報告する

## 2. 確かめ（違えば 3 は撮らずに止める）
- 167 の (o)(r)(s)(d)(i)(k)(l)(m) と同じ。(q) は「引き出しが出てくる間に体と当たったコマ 0」
- (t) 取る時、手は開いた口の真上から入る
- (u) かがみ：股関節の倒しの最大 35〜45°・ひざの曲げの最大 30° 以内・頭と画面の一番近い距離 15 cm 以上（値を書く）
- (v) 手首：取る間（手を口の上へ上げてから、箱を持って口から 10 cm 離れるまで）、右手首の曲がり（前腕と手の甲の角度）の最大が 25° 以内。左手首も、太ももに置いている間 25° 以内
- 167 から変わった所が 1〜4 だけであること。ほかの所の骨の動きは 167 とのずれが 1 mm 以内。歩く道の終わりを変えた時は、変えた区間と量を書く

## 3. 撮る
- ②受け取るの1本だけ。カメラは依頼169 の本番のカメラ。看護師が画面の外に出る時は、カメラの向きだけ変えてよい（変えた値を書く）
- もう1本、依頼169 と同じ横からのカメラで、カードをかざす所から箱を取って体を起こすまでを 0.5 倍の速さで（当たりの赤い表示は、当たったコマがあれば出す）
- 静止画：カードをかざす所・引き出しが自分で開いた所・取る所・取る所の横から・取る所の手元の寄り（手首が見える向き）・自分で閉まった所
- 出力：/Volumes/KIOXIA/r2r2r-data/service_sim/unity/closeup/20261113_closeup170/。動画と静止画は iCloud の …/サービスロボット導入シミュレーション/hospital/closeup170/ にも写し、md5 を確かめる
- check-in（Mixamo のフォルダ以外は保留0）。実行ファイルは作らない

## 報告
claude_code_reports/20261113_closeup_170.txt に：始める前の確かめ、選んだ立つ所の値、かがみと手首の値、167 から変えたファイルと変えた理由（1〜4 のどれに当たるか）、2 の確かめ（表）、動画の長さ、出力の場所、check-in の番号、決めてほしいこと（直し方の候補は作らない）。
**報告の最後の1行は必ず次のどれか**：「判定：合格」／「判定：止めた（決めてほしいことあり）」／「判定：失敗」
````

### 依頼170 の報告（20261113_170_受け取るの場面で斜め横に立ち腰を起こし手首をまっすぐにして撮る_20261113_1249.md）

````markdown
# 結果：20261113_170_受け取るの場面で斜め横に立ち腰を起こし手首をまっすぐにして撮る

- 状態：完了
- 始めた時刻：2026-11-13 12:49
- 終わった時刻：2026-11-13 13:20（31分）
- 作業フォルダ：/Users/shimazueiichi/Robotics_UX/ServiceSim
- この続きを Claude Code で話すとき：作業フォルダで `claude --resume 69cb5529-7107-45d0-a71c-51ea3d06f98d`

## Claude Code の報告

**やったこと**
- 始める前の確かめは、すべて条件どおりでした。
  - 保留は Mixamo のフォルダの 78 件だけでした。
  - Unity と回し係は動いていませんでした。
  - KIOXIA はマウント済みで、依頼169 は終わっていました（cs:133）。
- 手首と左手の作りを足しました。台本に書かなければ 167 と同じになります。
  - 手首（決定 3）：台本の `<手>_wrist` で、手首の曲がりを前腕からその角度までに抑えます。
  - 左手（決定 4）：カードをしまった後、左手を太ももへ置きます。
- 依頼168 の候補 D から始めて、立つ所 23 か所とかがみ 5 通りを Unity で測りました。

**結果**
1 の「立つ所」で止めました。撮影はしていません。動画・静止画・iCloud への写し・本番の台本はありません。
- **手首はできています。** 右手首の最大は 21.8〜22.1°、左手首は 12.0〜12.2° で、試したすべての所で 25° 以内でした。
- **かがみの値の範囲は守れました。** 股関節の倒しは 40〜45°、ひざの曲げは最大 21.7〜22.7° です。
- **カードと「引き出しが出てくる間」の両方を満たしたのは m1〜m3 だけでした。**
  - m1〜m3 は根 x −0.18〜−0.21 m・z 0.78 m です。カードは 4.50〜4.58 cm で 1.8〜1.9 秒止まり、出てくる間に当たったコマは 0 でした。
  - x −0.25〜−0.30 m では、ひざが前の板から 1 cm 以上離れるのは z 0.77 m 以上です。そこではカードが 5.0 cm を超えます。
- **m1 で取らせると、届きと頭の距離が両立しませんでした。**
  - 右手が箱に届くと、頭は画面から 6.1〜10.1 cm、または画面の中に入ります。
  - 頭を 10.6 cm まで離すと、届きが 6.8 cm 足りません。
  - どのかがみでも、手を上げる途中（19.2 秒前後）に右手の指が前の板の −x の角に当たります。
- 167 の台本を今回のコードで回すと、骨・コマの値・引き出しの当たりの 3 つの出力が 167・169 と md5 で一致しました。足したものは、台本に書かなければ動きを変えません。

**変えたファイル**
- `CloseupNurse.cs`（手首の上限・決定 3）
- `CloseupDirector.cs`（台本の `<手>_wrist` を読む・決定 3）
- `Editor/CloseupShoot.cs`（測る部品を台本の `check_stand_parts` で選べるようにした。奥の壁も測るため・1 の確かめ）
- check-in は cs:134 です。保留は Mixamo のフォルダだけです。
- 報告は `claude_code_reports/20261113_closeup_170.txt` と、KIOXIA の `closeup/20261113_closeup170/report170.txt` に置きました。候補ごとの表もここにあります。試しの結果と道具も同じ KIOXIA のフォルダにあります。

**決めてほしいこと**
1. 次の 4 つを、ひとつの立つ所で同時に満たす所がありませんでした。どれを変えてよいか決めてください。
   - カードが画面の前 3〜5 cm（167 のかざし方のまま）
   - 引き出しが出てくる間と取る間に、体が 1 cm 以上離れる
   - 新しいかがみで、右手が開いた口の真上から箱に届く
   - 頭が画面から 15 cm 以上離れる
2. m1〜m3 は、右足だけが前の板の外に出る「少し −x へずれた正面」です。これを「斜め横」としてよいか決めてください。
3. 左手を太ももに置くと、指先が前の板から 1 cm 以内に近づく所があります（m1 で 1.05 cm、m2 で 0.23 cm）。置く所をこちらで動かしてよいか決めてください。
4. 依頼167 の決めてほしいことの 2（手が離れてからの 3 秒）と 4（話しかけの後の頭の戻し 1.0 秒）は、答え待ちのため 167 のままです。
5. Mixamo の FBX の check-in は、依頼163 の答えのとおり保留のままです。

判定：止めた（決めてほしいことあり）
````

### 依頼171 の依頼文（20261113_171_受け取るの場面を数字をゆるめて必ず撮る.md）

````markdown
---
作業フォルダ: /Users/shimazueiichi/Robotics_UX/ServiceSim
上限時間: 240
---
# 依頼171：②受け取るの1本だけ。依頼170 の続き。Claude が決めた数字をゆるめ、確かめが合わなくても必ず撮る

依頼167〜170 の報告（claude_code_reports/20261113_closeup_167.txt〜_170.txt）を読んでから始める。依頼170 で足した手首の上限・左手を太ももに置く作り・測る所（cs:134）と、依頼170 の候補の表を土台にする。

**依頼170 で止まったわけ**：Claude が依頼に書いた数字（カードと画面 3〜5 cm・頭と画面 15 cm 以上）が厳しすぎて、引き出しに当たらない立つ所と両立しなかった。これはエイイチの指示ではない。次のようにゆるめる：
- カードと画面の距離：**1〜8 cm**（画面に触れない・画面の中に入らない）
- 頭と画面の距離：**5 cm 以上**（当たらない）
- 左手を置く所は、太ももの上で引き出しに当たらない所へ動かしてよい
- 「当たらない（体と引き出しが 1 cm 以上）」と「開いた口の真上から取る」は、エイイチの指示なのでゆるめない
- 依頼170 の決めてほしいことの 2（m1〜m3 のような少しずれた正面）は、斜め横に寄せられる所があればそちらを選ぶ。選んだ所と理由を書く

エイイチの決定（2026-11-13、依頼167 の静止画を見て）：
1. **看護師は引き出しの斜め横に立つ**（依頼168 と同じ。−x の側＝看護師から見て右）。依頼168 の「体と引き出しを 5 cm 以上あける」は Claude が決めた数字で厳しすぎたので、**「当たらない（1 cm 以上）」にゆるめる**。依頼168 の候補 D（根 −0.29・0.79・192°）を出発点にする
2. **腰をもう少し起こす。** 股関節の倒しを 167 の 50° から 40° 前後へ減らし、そのぶんひざを曲げる（25° 前後・30° まで）。深くしゃがむ形にはしない。頭はハコブくんの画面から 5 cm 以上離れる
3. **手首をまっすぐにする。** 取る時に手首が大きく折れていて気持ち悪い。手首の曲がりは前腕に対して 25° 以内。手のひらを下に向けて、箱を上から包むようにつかむ。高さと届きは肘と肩で合わせる
4. **カードを下ろした後の左手**は、引き出しに当たらない所へ下ろす。取る間と同じく太ももに置く形でよい

**指示していない動作は変えない。** 変えてよいのは、上の 1〜4 だけ（立つ所とそこへ止まる所、かがみの値、取る時の右手首と腕、左手の下ろし先）。歩き方・カードの出し方とかざし方（167 の腕の形）・画面の表示・引き出しの自動の開け閉め（時刻・速さ・3 秒）・取る順（上から下ろしてつかみ、まっすぐ上げてから手前へ）・うなずき・話しかけ・まばたき・ハコブくん・場面は 167 のまま。
**触るのは Assets/ServiceSim/Closeups/ の中だけ。** ①の作りは消さずに残す（撮らない）。Mixamo の FBX は写さない・check-in しない。
**エイイチは見ていない。人に聞かないと進めない所に当たったら、その項目は止めて報告に書き、ほかは続ける。**

## 始める前に確かめること（違えば作らずに報告して終わる）
保留は Mixamo のフォルダ（Models/Animations/）だけ・Unity と回し係が動いていない・KIOXIA がマウント済み・最新が cs:134

## 1. 立つ所
- 依頼170 の表から始め、次の全部を満たす所を探す：カードが画面の前 1〜8 cm に届く／引き出しが出てくる間と取る間、体（肌・服・靴・手）と引き出しの前の板・壁が 1 cm 以上離れる／新しいかがみ（2）で、開いた口の真上から右手が箱に届く
- 全部を満たす所がない時も、**一番近い所で撮る**（下の 3）。合わなかった項目と値を報告に書く

## 2. 確かめ（合わなくても 3 は必ず撮る。エイイチは動画で見て判断する）
- 167 の (o)(r)(s)(d)(i)(k)(l)(m) と同じ（(i) のカードの距離は 1〜8 cm）。(q) は「引き出しが出てくる間に体と当たったコマ 0」
- (t) 取る時、手は開いた口の真上から入る
- (u) かがみ：股関節の倒しの最大 35〜45°・ひざの曲げの最大 30° 以内・頭と画面の一番近い距離 5 cm 以上（値を書く）
- (v) 手首：取る間（手を口の上へ上げてから、箱を持って口から 10 cm 離れるまで）、右手首の曲がり（前腕と手の甲の角度）の最大が 25° 以内。左手首も、太ももに置いている間 25° 以内
- 167 から変わった所が 1〜4 だけであること。ほかの所の骨の動きは 167 とのずれが 1 mm 以内。歩く道の終わりを変えた時は、変えた区間と量を書く

## 3. 撮る
- ②受け取るの1本だけ。カメラは依頼169 の本番のカメラ。看護師が画面の外に出る時は、カメラの向きだけ変えてよい（変えた値を書く）
- もう1本、依頼169 と同じ横からのカメラで、カードをかざす所から箱を取って体を起こすまでを 0.5 倍の速さで。依頼169 と同じく、当たったコマは赤く光らせ、左上に「当たり：…」を出す
- 静止画：カードをかざす所・引き出しが自分で開いた所・取る所・取る所の横から・取る所の手元の寄り（手首が見える向き）・自分で閉まった所
- 出力：/Volumes/KIOXIA/r2r2r-data/service_sim/unity/closeup/20261113_closeup171/。動画と静止画は iCloud の …/サービスロボット導入シミュレーション/hospital/closeup171/ にも写し、md5 を確かめる
- check-in（Mixamo のフォルダ以外は保留0）。実行ファイルは作らない

## 報告
claude_code_reports/20261113_closeup_171.txt に：始める前の確かめ、選んだ立つ所の値、かがみと手首の値、167 から変えたファイルと変えた理由（1〜4 のどれに当たるか）、2 の確かめ（表）、動画の長さ、出力の場所、check-in の番号、決めてほしいこと（直し方の候補は作らない）。
**報告の最後の1行は必ず次のどれか**：「判定：合格」／「判定：撮ったが合わない所あり」／「判定：失敗」（撮れなかった時だけ）
````

### 依頼171 の報告（20261113_171_受け取るの場面を数字をゆるめて必ず撮る_20261113_1332.md）

````markdown
# 結果：20261113_171_受け取るの場面を数字をゆるめて必ず撮る

- 状態：完了
- 始めた時刻：2026-11-13 13:32
- 終わった時刻：2026-11-13 14:38（67分）
- 作業フォルダ：/Users/shimazueiichi/Robotics_UX/ServiceSim
- この続きを Claude Code で話すとき：作業フォルダで `claude --resume 6f8f7bf5-10bb-442d-8c3d-3d4686e36d35`

## Claude Code の報告

**やったこと**
- 始める前の確かめはすべて合っていました。保留は Mixamo のフォルダ（Models/Animations/）の 78 件だけ、Unity と回し係は止まっていて、KIOXIA はつながっており、最新は cs:134 でした。
- ゆるめた数字で、立つ所・かがみ・手首・左手を Unity で 65 通り試しました。
- 斜め横の所で全部の確かめを満たせたので、②受け取るを撮りました。
  - カードをかざすのは、依頼168 の候補 D と同じ所（根 −0.29・0.79・192°）です。
  - そのまま取ろうとすると、右手と右腕が前の板・横の壁に当たりました。そこでカードの後に、167 で 3 歩下がっていたのと同じ時刻（16.2〜17.3 秒）・同じ 3 歩で、斜め横のまま箱のほうへ向きを変える形にしました（根 −0.32・0.85・150°）。
  - m1〜m3 のような少しずれた正面は、斜め横で満たせたので選んでいません。
- コードは変えていません。変えたのは新しい台本 closeup171_script.json だけです。

**結果**
- 引き出しとの当たりは場面全体で 0 コマです。
- 体と引き出しの一番近い距離（基準 1 cm 以上）：
  - 出てくる間：3.04 cm
  - 向きを変える間：2.02 cm
  - 取る間：1.52 cm
- 右手は開いた口の真上から入りました（(t) 合う）。
- かがみ：股関節の倒し 40.0°、ひざの曲げ 21.7°、頭と画面 10.85 cm。
- 手首：右 22.5°、左 12.1°（どちらも 25° 以内）。
- カード：1〜8 cm の間に 2.17 秒あり、6.99 cm で止まりました。一番近い所は 3.93 cm で、画面には触れていません。
- (o)(q)(r)(s)(d)(k)(l)(m) はすべて合いました。(k) は＋2.47 cm、(m) は 2.85 cm です。
- 167 との骨のずれ：
  - 7.6 秒より前と、23.567 秒から後は 1 mm 以内です。7.6 秒からは看護師の根の位置と向きをそろえて比べています。
  - カードの左腕は、違う所から同じ画面の所へ手を伸ばすため、最大 365 mm 形が変わります。立つ所を変えたこと（1）に伴うずれです。
- 動画は 2 本です。本番のカメラが 39.5 秒、横からの 0.5 倍が 22.07 秒（場面の 12.5〜23.5 秒）です。
- 本番のカメラでは、カードをかざす所（11.6〜15.8 秒）が看護師の背中でふさがれ、取る所も背中越しになります。看護師は画面の外には出ていないので、カメラは変えていません。カードをかざす所の静止画は、横からのコマも足しました。
- 出力は KIOXIA の closeup/20261113_closeup171/ です。動画と静止画 7 枚は iCloud の hospital/closeup171/ にも写し、md5 はすべて一致しました。
- check-in は cs:135 です。保留は Mixamo のフォルダだけで、実行ファイルは作っていません。

**変えたファイル**
- Assets/ServiceSim/Closeups/closeup171_script.json（新）と、その .meta
- 報告：claude_code_reports/20261113_closeup_171.txt（KIOXIA の report171.txt にも写しました）

**決めてほしいこと**
1. カードの後に、斜め横のまま向きを 192° から 150° へ変える 3 歩を入れました（「立つ所とそこへ止まる所」の内として作りました）。この形でよいか。このため、話しかけと見送りもハコブくんの画面を向いた 150° のままです。
2. 本番のカメラ（167 の値のまま）では、カードをかざす所と取る所が看護師の背中で見えません。本番のカメラをどうするか。
3. 依頼167 の決めてほしいことの 2（手が離れてからの 3 秒）と 4（頭の戻し 1.0 秒）は、答え待ちのまま 167 と同じにしています。
4. Mixamo の FBX の check-in は、依頼163 の答えのとおり保留のままです。

判定：合格
````

### 依頼172 の依頼文（20261113_172_積むの場面を受け取ると同じやり方で直して撮る.md）

````markdown
---
作業フォルダ: /Users/shimazueiichi/Robotics_UX/ServiceSim
上限時間: 240
---
# 依頼172：①積むの1本だけ。②受け取る（依頼171）と同じやり方で直して撮る

依頼166・167・171 の報告（claude_code_reports/20261113_closeup_166.txt・_167.txt・_171.txt）を読んでから始める。①は依頼166 の作り（closeup166_script.json の①）、直し方は依頼171（closeup171_script.json と cs:135 のコード）を手本にする。

エイイチの決定（2026-11-13）：②受け取るは依頼171 の形で完成。**①積むを同じやり方で直す。**

## 変えること（②で決めたことを①に当てはめる）
1. **認証は画面に番号を入力する**（依頼166 の①のまま：右手の人さし指で4回押す・押すごとにボタンが明るくなる・入力欄の●が増える・チェックの印）
2. **引き出しは、チェックの印で自分で開き、看護師が箱を入れて手が引き出しの上の面から 10 cm 離れてから 3 秒、何もしなければ自分で閉まる**（②と同じ時間と速さ）。看護師は取っ手に触らない・閉めない
3. **看護師は引き出しの斜め横に立つ**（②と同じ −x の側・同じ当たらない条件）。番号を押した後、②と同じように斜め横のまま箱のほうへ向き直ってよい（②の 3 歩と同じ作り。向きと歩数は①に合わせて決め、値を書く）
4. **箱は開いた口の真上から入れる。** 箱をトレーの真上へ運び、まっすぐ下ろして置き、手をまっすぐ上げてから離れる。置くトレーは依頼166 の①と同じ
5. **かがみと手首は②と同じ**：股関節の倒し 35〜45°・ひざの曲げ 30° まで・頭と画面 5 cm 以上・手首の曲がり 25° 以内（手のひらを下にして箱を上から持つ）・使わない手は太ももに置く

**指示していない動作は変えない。** カウンターで箱を取る所・歩き方・うなずき・見送り・まばたき・ハコブくん・場面は依頼166 の①のまま。箱を持つ手（166 の①は左手）も変えない。左手のままでは当たって入れられない時だけ持ち替えてよく、その時は持ち替えた所と理由を報告に書く。
**触るのは Assets/ServiceSim/Closeups/ の中だけ。** ②の作り（closeup171_script.json）は変えない。Mixamo の FBX は写さない・check-in しない。
**エイイチは見ていない。人に聞かないと進めない所に当たったら、報告に書き、ほかは続ける。**

## 始める前に確かめること（違えば作らずに報告して終わる）
保留は Mixamo のフォルダ（Models/Animations/）だけ・Unity と回し係が動いていない・KIOXIA がマウント済み・最新が cs:135

## 確かめ（合わなくても必ず撮る。エイイチは動画で見て判断する）
- 依頼171 と同じ項目：当たり（引き出しが出てくる間・向き直る間・入れる間・閉まる間、体と引き出しが 1 cm 以上）、すり抜け 0、真上から入れる、かがみ、手首、頭と画面、自分で閉まるまで 3.0 秒
- 番号：押した回数 4・指先とボタンの中心 1 cm 以内・指が画面に入らない
- 箱の底とトレーの上の面の差 1 cm 以内
- 166 の①から変わった所が上の 1〜5 だけであること。ほかの所の骨の動きは 166 の①とのずれが 1 mm 以内（立つ所を変えたことで動く所は、区間と量を書く）
- ②（closeup171_script.json）を回し直して、171 の出力と同じであること

## 撮る
- ①積むの1本だけ（本番のカメラは依頼166 の①と同じ）と、依頼171 と同じ横からのカメラで、番号を押す所から箱を入れて体を起こすまでを 0.5 倍の速さで1本。当たったコマは赤く光らせ、左上に「当たり：…」を出す
- 静止画：番号を押す所（横から）・チェックの印・引き出しが自分で開いた所・入れる所・入れる所の横から・手首の寄り・自分で閉まった所
- 出力：/Volumes/KIOXIA/r2r2r-data/service_sim/unity/closeup/20261113_closeup172/。動画と静止画は iCloud の …/サービスロボット導入シミュレーション/hospital/closeup172/ にも写し、md5 を確かめる
- check-in（Mixamo のフォルダ以外は保留0）。実行ファイルは作らない

## 報告
claude_code_reports/20261113_closeup_172.txt に：始める前の確かめ、立つ所・向き直り・かがみ・手首の値、166 の①から変えたファイルと変えた理由（1〜5 のどれに当たるか）、確かめ（表）、動画の長さ、出力の場所、check-in の番号、決めてほしいこと（直し方の候補は作らない）。
**報告の最後の1行は必ず次のどれか**：「判定：合格」／「判定：撮ったが合わない所あり」／「判定：失敗」（撮れなかった時だけ）
````

### 依頼172 の報告（20261113_172_積むの場面を受け取ると同じやり方で直して撮る_20261113_1536.md）

````markdown
# 結果：20261113_172_積むの場面を受け取ると同じやり方で直して撮る

- 状態：完了
- 始めた時刻：2026-11-13 15:36
- 終わった時刻：2026-11-13 17:12（96分）
- 作業フォルダ：/Users/shimazueiichi/Robotics_UX/ServiceSim
- この続きを Claude Code で話すとき：作業フォルダで `claude --resume 278af45d-b5ae-4e32-b461-a3e9b03f7538`

## Claude Code の報告

**やったこと**
- 作業前の確かめは 4 つとも合っていました。保留は Mixamo のフォルダの 78 件だけ、Unity と回し係は動いていない、KIOXIA はマウント済み、最新は cs:135。
- 依頼171 の台本をもとに、①積むだけを ② と同じやり方で直した台本 closeup172_script.json を作りました。② の部分は 171 と全く同じです。立つ所などの値は Unity で約 60 通り試して決めました。
- 立つ所と向き直り（値は根の x・z・向き。向き 180 度が真正面）
  - 番号を押す所は −0.42・0.60・130 度で、引き出しの −x の斜め横です。ここからは画面の真ん中の列に指が届かないので、押す間だけ股関節から上体を 18 度倒しました。
  - 押した後、166 の 3 歩と同じ時刻（14.0〜15.1 秒）・同じ足の順で、斜め横のまま −0.18・0.91・167 度へ向き直ります。
- かがみは股関節 44 度・Spine 32・Chest 6・ひざ 19・腰を後ろへ 0.08 m です。
- 箱は左手のまま持ち、持ち替えはしていません。トレー 1 の真上から下ろして置き、手をまっすぐ上げてから離れます。
- 手首と右手
  - 左手首は前腕から 22 度までにし、手のひらを下にして箱を上から持ちます。
  - 使わない右手は太ももに置きます。取っ手には触りません。
- 引き出しはチェックの印（13.5 秒）で自分で開きます。置いた手が 10 cm 離れてから 3 秒で、自分で閉まります。
- このためにコードを 1 か所足しました（CloseupShoot.cs）。台本に「置く場面」と書いてあれば、箱を置いた後の手が離れた時刻から閉まり始めます。

**結果**
- 確かめ（場面の値）

| 項目 | 結果 | 基準 |
|---|---|---|
| 当たったコマ（開く・向き直る・入れる・閉まる間） | 0 | 0 |
| 体と引き出しの一番近い距離 | 1.10 cm | 1 cm 以上 |
| すり抜け・胴を突き抜けたコマ | 0 | 0 |
| 真上から入れる | 左手と指の骨 11 本とも口の内側から入った | 口の真上から |
| 股関節の倒し・ひざの曲げ | 44.0 度・29.3 度 | 35〜45 度・30 度以内 |
| 頭と画面 | 5.59 cm | 5 cm 以上 |
| 左手首の曲がり | 23.1 度 | 25 度以内 |
| 手首と箱の上の面の差 | ＋0.43 cm | 手首が上 |
| 番号 | 4 回とも、指先とボタンの中心 0.45 cm。指は画面に入らない | 4 回・1 cm 以内 |
| 手が離れてから閉まり始めるまで | 3.000 秒 | 3.0 秒 |
| 箱の底とトレーの上の面の差 | ＋0.18 cm | 1 cm 以内 |
| つなぎ目の 1 コマの骨の動き | 2.43 cm 以内 | 3 cm 以内 |

- 166 の①との骨のずれ
  - 0〜5.4 秒と 25.0〜34 秒（うなずき・見送り）は 1 mm 以内でした（後半は 0.009 mm）。
  - 5.4〜25.0 秒は、立つ所と 1〜5 を変えた所です。区間ごとのずれの量は報告ファイルに書きました。
- ② を回し直した出力は、171 と同じでした（9 ファイルと静止画 19 枚の md5 が一致）。
- 動画
  - 本番のカメラ（166 の①と同じ）：34.000 秒。
  - 横からのカメラ（171 と同じ）：番号を押す所から体を起こすまでを 0.5 倍で 25.267 秒。
  - 当たったコマは 0 なので、赤い表示も「当たり：…」の文字も出ていません。
- 静止画は 9 枚です。
- 出力は KIOXIA の closeup/20261113_closeup172/ です。iCloud の hospital/closeup172/ に写した 11 ファイルは、md5 が KIOXIA と全部同じでした。
- check-in は cs:136 と cs:137 です。撮ったのは cs:137 の台本で、手のひらの向きだけ直した版です。保留は Mixamo のフォルダの 78 件だけです。

**変えたファイル**
- Assets/ServiceSim/Closeups/Editor/CloseupShoot.cs
- Assets/ServiceSim/Closeups/closeup172_script.json（新）
- 同 .meta（新）
- 報告：claude_code_reports/20261113_closeup_172.txt（KIOXIA にも写しあり）

**決めてほしいこと**
1. 番号を押す間だけ、上体を股関節から 18 度倒しました（166 は Spine 6 度だけ）。この形でよいか。
2. 向き直った後の根は x −0.18 で、② の −0.32 より引き出しの真ん中寄りです。
   - トレー 1 が看護師から遠い +x 側にあり、これより −x へ離すと左手が届かず、+x へ寄せると頭が画面に 5 cm より近づくためです。
   - これを「斜め横」としてよいか。
3. かがむ間の頭を −34 度にして、顔を少し起こしました。② と同じ −24 度では、頭と画面が 4.5 cm でした。この見え方でよいか。
4. 本番のカメラでは、入れる所で看護師の頭がハコブくんの画面の前に重なります。本番のカメラをどうするか（依頼171 の決めてほしいことの 2 と同じ問いです）。
5. Mixamo の FBX の check-in は、依頼163 の答えのとおり保留のままです。

判定：合格
````



## 作業ログ ― 変えたコード（看護師とハコブくん）

看護師（p2m-site の tools/sim_people/nurse_oshiro/）とハコブくん（tools/sim_robots/hakobu/）の、この回で変えた所の差分と、変えた後のファイルの全文です。どちらも `pip install bpy==5.3.0 scikit-image` のあと、`python3 nurse_build.py <出力フォルダ> --vox 0.0025`・`python3 hakobu_build.py --out <出力フォルダ>` で作り直せます。

### 看護師の差分（nurse_build.py・README.md、d32ed48 と 83d0ef7）

````diff
diff --git a/tools/sim_people/nurse_oshiro/README.md b/tools/sim_people/nurse_oshiro/README.md
index cd6298f..98c894e 100644
--- a/tools/sim_people/nurse_oshiro/README.md
+++ b/tools/sim_people/nurse_oshiro/README.md
@@ -46,7 +46,9 @@
 
 ## 動かした時に気をつけたこと
 
-- 袖は胴とつなげず、別の部品にしてある。つなげると、腕を前に出した時に袖と胴の継ぎ目が裂けて、すき間から肌が見えた。袖は付け根の4cmだけ肩の骨と分け合い、そこから先は上腕の骨だけで動く
+- 袖は胴とつなげず、別の部品にしてある。つなげると、腕を前に出した時に袖と胴の継ぎ目が裂けて、すき間から肌が見えた。袖はまるごと上腕の骨だけで動く（付け根を肩の骨と分け合うと、腕を上げた時に袖の山がゆがんだ・2026-11-13）
+- 面の向きは marching_cubes のまま（外向き）。前の版は逆にしていて、Unity で全部の部品の面が裏返っていた（依頼162 の報告・2026-11-13 に直した）
+- パンツは腰と太ももの重みの境目を広めにしてある（しゃがむと付け根の面が細かく折れたため）。深くしゃがむと、ひざと付け根に面の少なさから来るしわが少し残る
 - 袖の中に隠れる腕の付け根の肌は作っていない（服がのびた時に肌が服から突き出ないように）
 - 指の骨の z 軸は手のひらに向けてある。骨の x 軸まわりに回すと、指が手のひらのほうへ曲がる（握る・つかむ）
 - 胴の服の重みは高さで Hips・Spine・Chest に分け、境目はなめらかにつないである（おじぎで胴を曲げても段にならない）
diff --git a/tools/sim_people/nurse_oshiro/nurse_build.py b/tools/sim_people/nurse_oshiro/nurse_build.py
index b1ada99..0565dfe 100644
--- a/tools/sim_people/nurse_oshiro/nurse_build.py
+++ b/tools/sim_people/nurse_oshiro/nurse_build.py
@@ -364,7 +364,7 @@ def mesh_of(fn, lo, hi, vox):
 
 def to_blender(name, v, f, col, rough=0.6, sss=0.0, ratio=None):
     me = bpy.data.meshes.new(name)
-    me.from_pydata(v.tolist(), [], f[:, ::-1].tolist())
+    me.from_pydata(v.tolist(), [], f.tolist())   # marching_cubes の向きのままで外向き（逆にすると Unity で全部の面が裏返った・依頼162）
     me.update()
     ob = bpy.data.objects.new(name, me)
     bpy.context.scene.collection.objects.link(ob)
@@ -513,7 +513,9 @@ def weight_mesh(ob, kind):
     names = list(R)
     D = np.stack([R[k] for k in names])
     dmin = D.min(axis=0)
-    W = np.stack([np.exp(-np.maximum(D[i] - dmin, 0) / tau_of(k)) for i, k in enumerate(names)])
+    # パンツは腰と太ももの境目を広くなだらかにする（しゃがむと付け根の面が細かく折れた・依頼162）
+    tau = (lambda k: 0.045 if k == "Hips" or "UpperLeg" in k else tau_of(k)) if kind == "pants" else tau_of
+    W = np.stack([np.exp(-np.maximum(D[i] - dmin, 0) / tau(k)) for i, k in enumerate(names)])
     W = W / W.sum(axis=0)
     top4 = np.argsort(-W, axis=0)[:4]
     groups = {k: ob.vertex_groups.new(name=k) for k in names}
@@ -527,22 +529,11 @@ def weight_mesh(ob, kind):
 
 
 def weight_sleeves(ob):
-    """袖：付け根（腕の骨の根元から 4cm）だけ肩の骨と分け合い、そこから先は腕の骨だけ"""
-    gs = {}
+    """袖はまるごと上腕の骨に付ける（付け根を肩の骨と分け合うと、腕を上げた時に袖の山がゆがんだ）。
+    袖の山は肩の関節のすぐ外にあるので、腕と一緒に回しても肩から離れない"""
     for side, L in ((1, "Left"), (-1, "Right")):
-        sh, _, _, d1, _ = arm_points(side)
-        ga = ob.vertex_groups.new(name=L + "UpperArm")
-        gsh = ob.vertex_groups.new(name=L + "Shoulder")
-        for v in ob.data.vertices:
-            if v.co.x * side <= 0:
-                continue
-            proj = float(np.dot(np.array(v.co) - sh, d1))
-            w = min(max((proj + 0.01) / 0.04, 0.0), 1.0)
-            if w > 0:
-                ga.add([v.index], w, "REPLACE")
-            if w < 1:
-                gsh.add([v.index], 1 - w, "REPLACE")
-    return gs
+        g = ob.vertex_groups.new(name=L + "UpperArm")
+        g.add([v.index for v in ob.data.vertices if v.co.x * side > 0], 1.0, "REPLACE")
 
 
 def weight_whole(ob, bone):
````

### ハコブくんの差分（hakobu_build.py・README.md、cab3977）

````diff
diff --git a/tools/sim_robots/hakobu/README.md b/tools/sim_robots/hakobu/README.md
index 21f441d..fd0c2f2 100644
--- a/tools/sim_robots/hakobu/README.md
+++ b/tools/sim_robots/hakobu/README.md
@@ -5,21 +5,21 @@
 同じ場面に出る人（粘土のような簡略化した人）に合わせて、体の角を丸めた形をもとにする（2026-11-13、エイイチの決定）。頭（画面のまわり）は三面図どおりで、画面の面は斜めにスパッと切れたまま。
 
 - 元にした絵：iCloud の ハコブくん/4コママンガ作成データ/キャラ/hakobu_front.png・hakobu_side.png・hakobu_back.png
-- 大きさ：三面図のハコブくんの実寸（幅0.653・奥行0.760・高さ1.150m。幅は台の一番広い所。横の厚い板の所だけ幅0.674m）。エイイチの指示（2026-11-13）。病院の試算のロボット（hospital-unity-params.json の robot.size_m＝KEENON W3 の幅0.459・奥行0.549・高さ1.081m）より大きい
+- 大きさ：三面図のハコブくんの実寸（幅0.653・奥行0.760・高さ1.150m。幅は台の一番広い所。横の面は真ん中だけ外へ1cmふくらむので、そこの幅は0.673m）。エイイチの指示（2026-11-13）。病院の試算のロボット（hospital-unity-params.json の robot.size_m＝KEENON W3 の幅0.459・奥行0.549・高さ1.081m）より大きい
 - 向き：根（Hakobu）は床の中心。顔が Unity の +Z（進む向き）を向く
 
 ## 作り方
 
 1. `python3 hakobu_profiles.py <キャラのフォルダ> hakobu_profiles.json`：三面図から外形を測る（済んでいる。三面図が変わった時だけ）
-2. `pip install bpy==5.3.0`（Blender を Python から使う）→ `python3 hakobu_build.py --out <出力フォルダ>`：hakobu.fbx・hakobu_albedo.png・hakobu_emission.png・hakobu_report.json を作る。大きさの既定は実寸。`--W --D --H`（m）で変えられる。丸めの既定は `--soft 3.5`（体の角を三面図の3.5倍の半径に。`--soft 1` で三面図どおりの角）。横の厚い板は既定で付く（`--no-pods` で外す）
+2. `pip install bpy==5.3.0`（Blender を Python から使う）→ `python3 hakobu_build.py --out <出力フォルダ>`：hakobu.fbx・hakobu_albedo.png・hakobu_emission.png・hakobu_report.json を作る。大きさの既定は実寸。`--W --D --H`（m）で変えられる。丸めの既定は `--soft 3.5`（体の角を三面図の3.5倍の半径に。`--soft 1` で三面図どおりの角）。横の面のふくらみは `--crown 0.010`（m・0 で平ら）。横に厚い板を貼る `--pods` は使わない（2026-11-13 にやめた）
 3. `python3 hakobu_render.py <出力>/hakobu.blend <絵のフォルダ> front side back persp sim sim_open`：確認用の絵（GitHub に入れない。iCloud の サービスロボット導入シミュレーション/hospital/hakobu_3d/）
 
-## 中身（三角形 5,804・材質1つ）
+## 中身（三角形 6,792・材質1つ）
 
 | 名前 | 中身 | 三角形 |
 |---|---|---|
 | Hakobu | 根（空の物）。床の中心 | – |
-| Hakobu_Body | 胴・頭・台・車輪・バンパー・横の厚い板（両側） | 4,760 |
+| Hakobu_Body | 胴・頭・台・車輪・バンパー・画面のまわりの丸いふち | 5,748 |
 | Hakobu_Face | 画面（顔） | 60 |
 | Hakobu_Ring | 横の輪（両側） | 432 |
 | Hakobu_Drawer | 引き出し（前の板・取っ手・中の箱）。前へ 0.26m まで引き出せる | 312 |
@@ -27,7 +27,10 @@
 
 - 色は1枚の画像（hakobu_albedo.png・1024px）にまとめた。光る所（目・口・輪・背中の帯）は hakobu_emission.png。材質は Hakobu_Mat の1つだけ
 - 丸めたのは体だけ：胴の縦の角（高さの6.4割から下。7割までで三面図の角に戻す）、台と黒い帯の角、引き出しの前の板とトレー、タイヤ（太くして角を丸め、上半分を台の中に隠した）
-- 横の厚い板：三面図の横の板（輪の付いた所）の輪郭どおりに、厚さ約2cm・ふちの丸み2cmの板を両側に足した（マンガ ep147 のハコブくんの、お腹の横のふくらみ）。輪はこの板の表面に乗る
+- 横：胴の横の面そのものを、真ん中ほど外へゆるくふくらませた（最大1cm・角は動かさない・高さ0.15〜0.66 で付け、頭と台の近くで0に戻す）。前は厚さ2cmの板を横に貼っていたが、横がパネルに見えたのでやめた（2026-11-13 エイイチの指摘）。輪はふくらんだ面の、輪の中心の所に乗る。輪の外形（三面図の横の板の輪郭）は継ぎ目の線として画像で描く
+- 横の下：車輪の間のへこんだ灰色の板を画像で描いた（三面図の横）
+- 画面のまわり：薄い灰色の丸いふち（画面より2cm大きい・角の丸み4.2cm）を画面の後ろに置いた（三面図の、画面を囲む枠）
+- 車輪：半径0.046m・幅0.052m の太いタイヤを、上半分を台の中に隠す。台の底の角の丸みは1.2cm（大きく丸めると台の底が細り、車輪が脚のように見えた。小さい車輪にしても同じに見えた。2026-11-13）
 - 横の継ぎ目・頭の帯・背中の扉と操作箱・前の引き出しのすき間は、形ではなく画像で描いている（三角形を増やさないため）
 - Tray_1〜4 は FBX では根の直下にある。引き出しの子にすると、FBX の向きの変換が二重にかかってトレーが90°回る（Blender で読み直して確かめた）。Unity のプレハブで Drawer の子にする
 - 輪と顔を別の部品にしてあるのは、あとで状態（移動中・止まった・完了）で色や表情を変えられるようにするため
diff --git a/tools/sim_robots/hakobu/hakobu_build.py b/tools/sim_robots/hakobu/hakobu_build.py
index 50478be..2bdafa8 100644
--- a/tools/sim_robots/hakobu/hakobu_build.py
+++ b/tools/sim_robots/hakobu/hakobu_build.py
@@ -32,6 +32,7 @@ COL = {
     "screen": (14, 14, 16),
     "green": (96, 236, 140),
     "drawer_in": (196, 195, 190),
+    "rim": (184, 186, 186),       # 画面のまわりの丸いふち
     "tray": (206, 228, 240),
     "eye": (250, 250, 250),
     "mouth": (84, 170, 236),
@@ -45,7 +46,7 @@ R_FRONT = (768, 0, 256, 512)
 R_BACK = (0, 512, 256, 512)
 SW_ORIGIN = (256, 512)
 SW_CELL = 64
-SWATCH_NAMES = ["white", "band", "base", "black", "screen", "green", "drawer_in", "tray", "door"]
+SWATCH_NAMES = ["white", "band", "base", "black", "screen", "green", "drawer_in", "tray", "door", "rim"]
 
 
 def swatch_px(name):
@@ -223,6 +224,26 @@ def shell_levels(prof, W, D, H, t0=0.105, t1=0.996, tol=0.0022):
 BELLY = 1.0  # 胴の角だけに掛ける追加の倍率（--belly）
 PODS = False  # 横の丸いふくらみ（--pods）
 SOFT = 1.0   # 角の丸さの倍率（人の粘土の見た目に合わせるときは --soft で大きくする）
+CROWN = 0.010  # 横の面のふくらみ（m）。角は動かさず、横の面の真ん中ほど外へ出す（--crown。0 で平ら）
+
+
+def crown_w(z, H):
+    """横のふくらみを付ける高さの重み：胴（0.15〜0.66）で1、頭（0.70 から上）と台の近くで0"""
+    t = z / H
+    if t < 0.11 or t > 0.70:
+        return 0.0
+    if t < 0.15:
+        return (t - 0.11) / 0.04
+    if t > 0.66:
+        return (0.70 - t) / 0.04
+    return 1.0
+
+
+def crown_shift(y, z, df, db, H, rf, rb):
+    """横の面が外へ出る量。角は動かさず、横の直線の真ん中ほど外へ出す（4f(1-f)）"""
+    y0, y1 = df + rf, db - rb
+    f = min(max((y - y0) / max(y1 - y0, 1e-6), 0.0), 1.0)
+    return CROWN * crown_w(z, H) * 4 * f * (1 - f)
 
 
 def corner_r(z, H, a, depth, front):
@@ -277,6 +298,9 @@ def build_shell(bm, levels, W, D, H, seg=3):
         rf = corner_r(z, H, a, db - df, True)
         rb = corner_r(z, H, a, db - df, False)
         pts = rrect(-a, a, df, db, {"br": (rf, rf), "tr": (rb, rb), "tl": (rb, rb), "bl": (rf, rf)}, seg)
+        if CROWN > 0:   # 横の面をふくらませる：横の面の点を、前後へ行くほど内へ寄せる（前と後ろの面はそのまま）
+            w = crown_w(z, H)
+            pts = [(x + (1 if x > 0 else -1) * CROWN * w * 4 * f * (1 - f), y) for x, y, f in _densify_sides(pts, seg)]
         rings.append([bm.verts.new((x, y, z)) for x, y in pts])
     faces = []
     n = len(rings[0])
@@ -290,6 +314,21 @@ def build_shell(bm, levels, W, D, H, seg=3):
     return faces
 
 
+def _densify_sides(pts, seg, n=10):
+    """横の面の直線（右：br の終わり〜tr の始め、左：tl の終わり〜bl の始め）に n 点ずつ足す。
+    ふくらみを曲線で見せるため。どの段でも点の数は同じ"""
+    k = seg + 1
+    out = []   # (x, y, f)：f は横の直線の上の位置（0〜1）。角の点は 0（動かさない）
+    for i, p in enumerate(pts):
+        out.append((p[0], p[1], 0.0))
+        if i in (k - 1, 3 * k - 1):
+            q = pts[i + 1]
+            for j in range(1, n + 1):
+                f = j / (n + 1)
+                out.append((p[0] + (q[0] - p[0]) * f, p[1] + (q[1] - p[1]) * f, f))
+    return out
+
+
 def uv_shell(bm, faces, W, D, H):
     lay = bm.loops.layers.uv.verify()
     bm.normal_update()
@@ -322,7 +361,8 @@ def build_base(bm, W, D, H):
     bm.normal_update()
     top = cap_edges(faces, "z", z1)
     bot = cap_edges(faces, "z", z0)
-    bmesh.ops.bevel(bm, geom=bot, offset=0.016 * SOFT, segments=2 + (1 if SOFT > 1 else 0), profile=0.5, affect="EDGES", clamp_overlap=True)
+    # 底の角の丸みは小さく（大きく丸めると台の底が細って、車輪が台から離れた脚のように見えた・2026-11-13）
+    bmesh.ops.bevel(bm, geom=bot, offset=0.012, segments=2 + (1 if SOFT > 1 else 0), profile=0.5, affect="EDGES", clamp_overlap=True)
     bm.normal_update()
     top = [e for e in bm.edges if e.is_valid and all(abs(v.co.z - z1) < 1e-6 for v in e.verts) and len(e.link_faces) == 2
            and any(abs(f.normal.z) < 0.9 for f in e.link_faces)]
@@ -332,6 +372,7 @@ def build_base(bm, W, D, H):
 
 def build_wheels(bm, W, D, H, r=0.036, width=0.036, seg=12):
     if SOFT > 1:   # 丸い見た目のとき：少し大きく太いタイヤを、上半分を台の中に隠して置き、角を丸める
+        # （小さくすると台の下から脚のように出て見えた。2026-11-13 エイイチの指摘でこの形に戻した）
         r, width, seg = 0.046, 0.052, 20
     faces = []
     for sx in (-1, 1):
@@ -386,7 +427,7 @@ def build_face_plate(bm, prof, W, D, H):
     L = (b - a).length
     pts = rrect(-hw, hw, 0, L, {k: (0.016, 0.016) for k in ("br", "tr", "tl", "bl")}, 3)   # 画面は丸めの対象にしない
     thick = 0.004
-    base = a + nrm * 0.0055
+    base = a + nrm * 0.0075   # まわりの丸いふち（0.0045 まで）より前に出す
 
     def P(u, v, s):
         return base + Vector((u, 0, 0)) + up * v + nrm * s
@@ -419,6 +460,29 @@ def build_face_plate(bm, prof, W, D, H):
     return faces, (a, b, nrm, hw, L)
 
 
+def build_bezel(bm, face_info, margin=0.020, r=0.042, s0=0.0015, s1=0.0045):
+    """画面のまわりの薄い灰色の丸いふち（三面図の、画面を囲む枠）。画面より少し大きく、画面の後ろに置く"""
+    a, b, nrm, hw, L = face_info
+    up = (b - a).normalized()
+    pts = rrect(-hw - margin, hw + margin, -margin, L + margin, {k: (r, r) for k in ("br", "tr", "tl", "bl")}, 6)
+    if signed_area(pts) < 0:
+        pts = pts[::-1]
+
+    def P(u, v, sft):
+        return a + Vector((u, 0, 0)) + up * v + nrm * sft
+    back = [bm.verts.new(P(u, v, s0 - 0.004)) for u, v in pts]
+    front = [bm.verts.new(P(u, v, s1)) for u, v in pts]
+    faces = [bm.faces.new(front[::-1]), bm.faces.new(back)]
+    n = len(pts)
+    for i in range(n):
+        j = (i + 1) % n
+        faces.append(bm.faces.new((back[i], back[j], front[j], front[i])))
+    bm.normal_update()
+    if faces[0].normal.dot(nrm) < 0:
+        faces[0].normal_flip()
+    return faces
+
+
 def build_ring(bm, W, D, H, x_side, r_out=0.122, r_in=0.1015, seg=36, proud=0.0006, depth=0.006):
     cy, cz = F["ring_c"][0] * D, F["ring_c"][1] * H
     faces, walls = [], []
@@ -607,7 +671,13 @@ def draw_atlas(W, D, H, face_info, ss=2):
     pts = rrect(pod["d"][0], pod["d"][1], pod["t"][0], pod["t"][1],
                 {"tl": pod["r"]["ft"], "tr": pod["r"]["bt"], "br": pod["r"]["bb"], "bl": pod["r"]["fb"]}, 12)
     poly = [tuple(c * ss for c in spx(d, t)) for d, t in pts]
-    da.line(poly + [poly[0]], fill=(176, 174, 169), width=3 * ss, joint="curve")
+    da.line(poly + [poly[0]], fill=(158, 156, 152), width=4 * ss, joint="curve")
+    # 横の下：車輪の間のへこんだ灰色の板（三面図の横。上の角が丸く、下へ少し広がる）
+    sk = rrect(-0.27, 0.30, 0.0, 0.178, {"tl": (0.05, 0.05), "tr": (0.05, 0.05), "br": (0.0, 0.0), "bl": (0.0, 0.0)}, 10)
+    sk = [(d + (-0.02 if d < 0.015 else 0.02) * max(0.0, (0.14 - t) / 0.14), t) for d, t in sk]   # 下へ少し広げる
+    poly = [tuple(c * ss for c in spx(d, t)) for d, t in sk]
+    da.polygon(poly, fill=(172, 171, 168))
+    da.line(poly + [poly[0]], fill=(150, 149, 146), width=2 * ss, joint="curve")
 
     # 前の面
     fx0, fy0, fw_, fh_ = R_FRONT
@@ -753,8 +823,14 @@ def build(W, D, H, out_dir):
     set_uv_all(bm, bm.faces, swatch_uv("black"))
     ob_bump = new_object("Hakobu_Bumper_tmp", bm)
 
+    bm = bmesh.new()
+    build_bezel(bm, face_info)
+    finish(bm)
+    set_uv_all(bm, bm.faces, swatch_uv("rim"))
+    ob_bezel = new_object("Hakobu_Bezel_tmp", bm)
+
     # まとめて1つの本体にする
-    body = join_objects("Hakobu_Body", [body_main, ob_base, ob_wheels, ob_bump])
+    body = join_objects("Hakobu_Body", [body_main, ob_base, ob_wheels, ob_bump, ob_bezel])
     body.parent = root
     body.data.materials.clear()
     body.data.materials.append(mat)
@@ -764,6 +840,11 @@ def build(W, D, H, out_dir):
     # 輪: 横の面の外側（殻の横幅の一番広い所に合わせる）
     ring_lv = [l for l in levels if 0.38 * H <= l[0] <= 0.62 * H]
     x_side = max(l[1] for l in ring_lv)
+    cy_r, cz_r = F["ring_c"][0] * D, F["ring_c"][1] * H
+    lr = min(levels, key=lambda l: abs(l[0] - cz_r))
+    rf_r = corner_r(lr[0], H, lr[1], lr[3] - lr[2], True)
+    rb_r = corner_r(lr[0], H, lr[1], lr[3] - lr[2], False)
+    x_side += crown_shift(cy_r, cz_r, lr[2], lr[3], H, rf_r, rb_r)   # 輪の中心で、ふくらんだ殻の面に合わせる
     if PODS:   # 横のふくらみを付けたときは、輪をふくらみの外の面に乗せる
         bm = bmesh.new()
         x_side = build_pods(bm, levels, W, D, H)
@@ -776,7 +857,7 @@ def build(W, D, H, out_dir):
         body = body2
     r_out = 0.5 * (313 / 2312 * H + 313 / 1529 * D)   # 三面図の輪の外の半径（高さと奥行きの間をとる）
     bm = bmesh.new()
-    glow, walls = build_ring(bm, W, D, H, x_side, r_out=r_out, r_in=r_out * 261 / 313)
+    glow, walls = build_ring(bm, W, D, H, x_side, r_out=r_out, r_in=r_out * 261 / 313, proud=0.0018, depth=0.008)
     set_uv_all(bm, glow, swatch_uv("green"))
     set_uv_all(bm, walls, swatch_uv("white"))
     finish(bm)
@@ -862,12 +943,14 @@ def main():
     ap.add_argument("--D", type=float, default=0.760)
     ap.add_argument("--H", type=float, default=1.150)
     ap.add_argument("--render", action="store_true")
-    ap.add_argument("--no-pods", dest="pods", action="store_false", help="横の丸いふくらみ（輪の付いた横板）を付けない")
+    ap.add_argument("--pods", dest="pods", action="store_true", help="横に厚い板を貼る（2026-11-13 にやめた。横がパネルに見えたため）")
+    ap.add_argument("--crown", type=float, default=0.010, help="横の面のふくらみ（m）。0 で平ら")
     ap.add_argument("--belly", type=float, default=1.0, help="胴の角だけ、さらに丸める倍率")
     ap.add_argument("--soft", type=float, default=3.5, help="体の角の丸さの倍率（1 で三面図どおり。頭は常に三面図どおり）")
     args = ap.parse_args(argv)
-    global SOFT, BELLY, PODS
+    global SOFT, BELLY, PODS, CROWN
     PODS = args.pods
+    CROWN = args.crown
     SOFT = args.soft
     BELLY = args.belly
     os.makedirs(args.out, exist_ok=True)
````

### nurse_build.py（変えた後の全文・83d0ef7）

````python
"""粘土のような簡略化した看護師（大城さん）を、形の式（距離の場）から作る。
python3 nurse_build.py <out_dir> [--vox 0.0025]
前が -y、上が z、単位は m。身長 1.65 m。
出力：nurse_clay.blend・nurse_oshiro.fbx・nurse_report.json（部品ごとの三角形の数・骨の名前）
FBX は Unity 向け（顔が Unity の +Z、上が +Y）。骨は Unity の Humanoid の名前（Hips・LeftUpperArm など）。
口は5つの形（smile・flat・worried・talk・surprised）を別の部品で入れてあり、Unity で表示を切り替える。
"""
import math
import os
import sys

import bpy  # noqa: I001  bpy を先に読む
import numpy as np
from skimage.measure import marching_cubes

OUT = sys.argv[1]
os.makedirs(OUT, exist_ok=True)
VOX = float(sys.argv[sys.argv.index("--vox") + 1]) if "--vox" in sys.argv else 0.0025


# ---------- 形の式 ----------
def length(v):
    return np.sqrt(np.sum(v * v, axis=0))


def ellipsoid(p, c, r):
    q = (p - np.array(c).reshape(3, 1)) / np.array(r).reshape(3, 1)
    k0 = length(q)
    k1 = length(q / np.array(r).reshape(3, 1))
    return k0 * (k0 - 1.0) / np.maximum(k1, 1e-9)


def capsule(p, a, b, ra, rb):
    a = np.array(a, float).reshape(3, 1)
    b = np.array(b, float).reshape(3, 1)
    ba = b - a
    h = np.clip(np.sum((p - a) * ba, axis=0) / np.sum(ba * ba), 0, 1)
    return length(p - a - ba * h) - (ra + (rb - ra) * h)


def smin(a, b, k):
    h = np.clip(0.5 + 0.5 * (b - a) / k, 0, 1)
    return b + (a - b) * h - k * h * (1 - h)


def smax(a, b, k):
    return -smin(-a, -b, k)


def union(ds, k):
    d = ds[0]
    for e in ds[1:]:
        d = smin(d, e, k)
    return d


def shell(d, t):
    return np.maximum(d, -(d + t))


# ---------- 寸法 ----------
HC = np.array([0.0, 0.0, 1.516])          # 頭の中心


def H(x, y, z):
    return (HC + np.array([x, y, z])).tolist()


SH = 0.150                                  # 肩の関節の横の位置
ARM_OUT = math.radians(9)                   # 腕の開き
ARM_FWD = math.radians(4)


def arm_points(side):
    s = 1 if side > 0 else -1
    sh = np.array([s * SH, 0.01, 1.300])
    d1 = np.array([s * math.sin(ARM_OUT), -math.sin(ARM_FWD), -math.cos(ARM_OUT)])
    d1 /= np.linalg.norm(d1)
    el = sh + d1 * 0.250
    d2 = np.array([s * math.sin(ARM_OUT * 0.6), -math.sin(math.radians(14)), -1.0])
    d2 /= np.linalg.norm(d2)
    wr = el + d2 * 0.220
    return sh, el, wr, d1, d2


def hand_geo(side):
    """手の形の骨組み（手首・手のひら・指の関節の位置）。形と骨の両方で使う。
    u＝手首から指先の向き、n＝手のひらの向き（体のほう）、w＝親指のほう（前）"""
    s = 1 if side > 0 else -1
    _, _, wr, _, u = arm_points(side)
    n = np.array([-s, 0.0, 0.0])            # 手のひらは体のほうを向く
    n = n - u * np.dot(n, u)
    n /= np.linalg.norm(n)
    w = np.cross(u, n) * s                  # 前（-y）向きにそろえる
    if w[1] > 0:
        w = -w
    base = wr + u * 0.085
    fingers = {}
    for name, off, ln, r in (("Index", 0.026, 0.070, 0.0105), ("Middle", 0.009, 0.078, 0.0108),
                             ("Ring", -0.009, 0.074, 0.0105), ("Little", -0.025, 0.060, 0.0095)):
        a = base + w * off
        d_1 = u * math.cos(math.radians(14)) + n * math.sin(math.radians(14))
        b = a + d_1 * ln * 0.52
        d_2 = u * math.cos(math.radians(38)) + n * math.sin(math.radians(38))
        c = b + d_2 * ln * 0.48
        fingers[name] = (a, b, c, r)
    ta = wr + u * 0.03 + w * 0.024 + n * 0.006
    td = u * 0.55 + w * 0.65 + n * 0.45
    td /= np.linalg.norm(td)
    tb = ta + td * 0.04
    td2 = u * 0.75 + w * 0.3 + n * 0.6
    td2 /= np.linalg.norm(td2)
    tc = tb + td2 * 0.032
    return {"wr": wr, "u": u, "n": n, "w": w, "palm_end": base, "fingers": fingers, "thumb": (ta, tb, tc)}


def palm(p, side):
    g = hand_geo(side)
    wr, u, w = g["wr"], g["u"], g["w"]
    ds = [ellipsoid(p, (wr + u * 0.048).tolist(), (0.024, 0.024, 0.024))]
    # 手のひらは平たい楕円。軸に沿って並べた小さい楕円で作る
    for t in (0.02, 0.045, 0.07):
        c = wr + u * t
        ds.append(capsule(p, (c - w * 0.022).tolist(), (c + w * 0.022).tolist(), 0.019, 0.019))
    return union(ds, 0.010)


def finger_segs(p, side):
    """指の節ごとの形：{骨の名前の後ろ半分: 形}"""
    g = hand_geo(side)
    out = {}
    for name, (a, b, c, r) in g["fingers"].items():
        out[name + "Proximal"] = capsule(p, a.tolist(), b.tolist(), r, r * 0.95)
        out[name + "Intermediate"] = capsule(p, b.tolist(), c.tolist(), r * 0.95, r * 0.88)
    ta, tb, tc = g["thumb"]
    out["ThumbProximal"] = capsule(p, ta.tolist(), tb.tolist(), 0.0135, 0.012)
    out["ThumbIntermediate"] = capsule(p, tb.tolist(), tc.tolist(), 0.012, 0.0105)
    return out


def hand(p, side):
    return union([palm(p, side)] + list(finger_segs(p, side).values()), 0.010)


def head_solid(p):
    cr = ellipsoid(p, H(0, 0, 0), (0.098, 0.106, 0.108))
    jaw = ellipsoid(p, H(0, -0.010, -0.055), (0.080, 0.082, 0.072))
    d = smin(cr, jaw, 0.03)
    cheeks = [ellipsoid(p, H(s * 0.044, -0.055, -0.045), (0.040, 0.036, 0.035)) for s in (-1, 1)]
    d = union([d] + cheeks, 0.03)
    nose = ellipsoid(p, H(0, -0.099, -0.026), (0.011, 0.011, 0.014))
    d = smin(d, nose, 0.010)
    ears = [ellipsoid(p, H(s * 0.096, 0.008, -0.020), (0.014, 0.021, 0.029)) for s in (-1, 1)]
    return union([d] + ears, 0.008)


def skin(p):
    ds = [head_solid(p)]
    ds.append(capsule(p, (0, 0.012, 1.30), (0, 0.006, 1.45), 0.044, 0.041))
    ds.append(ellipsoid(p, (0, 0.012, 1.27), (0.10, 0.066, 0.08)))
    for sd in (-1, 1):   # 首の付け根から肩へ（服の内側を肌で埋める）
        ds.append(capsule(p, (sd * 0.045, 0.015, 1.335), (sd * 0.12, 0.015, 1.31), 0.024, 0.022))
    d = union(ds, 0.03)
    arms = []
    for side in (-1, 1):
        sh, el, wr, _, _ = arm_points(side)
        a = smin(capsule(p, sh.tolist(), el.tolist(), 0.040, 0.037),
                 capsule(p, el.tolist(), wr.tolist(), 0.036, 0.029), 0.02)
        arms.append(smin(a, hand(p, side), 0.012))
    d = union([d] + arms, 0.01)
    # 袖の中に隠れる腕の付け根の肌は作らない（腕を上げて服がのびたとき、肌が服から突き出ないように）
    for side in (-1, 1):
        sh, _, _, d1, _ = arm_points(side)
        q = p - sh.reshape(3, 1)
        proj = np.sum(q * d1.reshape(3, 1), axis=0)
        radial = length(q - d1.reshape(3, 1) * proj)
        inside = np.maximum(np.maximum(-0.12 - proj, proj - 0.134), radial - 0.065)
        d = smax(d, -inside, 0.005)
    return d


def hairline(p):
    """頭の中心から見た向きごとの、髪の下の端の高さ（頭の中心からの差）。"""
    x = p[0] - HC[0]
    y = p[1] - (HC[1] + 0.005)
    th = np.degrees(np.arctan2(x, -y))
    a = np.abs(th)
    z = np.interp(a, [0, 35, 58, 68, 78, 88, 104, 120, 180],
                  [0.056, 0.046, 0.030, 0.000, -0.012, 0.024, 0.024, -0.080, -0.100])
    # 横に流した前髪：右（+x）が上がり、左（-x）へ下がる
    front = np.clip(np.cos(np.radians(th)), 0, 1) ** 2
    z = z + 0.025 * np.sin(np.radians(th)) * front
    return z + HC[2]


def hair(p):
    s = ellipsoid(p, H(0, 0.010, 0.006), (0.110, 0.118, 0.120))
    nape = ellipsoid(p, H(0, 0.040, -0.062), (0.090, 0.078, 0.070))
    s = smin(s, nape, 0.03)
    d = smax(s, hairline(p) - p[2], 0.010)
    # 耳の前に下りる短い毛束
    bun = ellipsoid(p, H(0, 0.116, -0.088), (0.047, 0.041, 0.044))
    return smin(d, bun, 0.012)


def top(p):
    rr = 0.068
    c = np.array([0.0, 0.010, 1.085]).reshape(3, 1)
    b = np.array([0.158 - rr, 0.098 - rr, 0.30 - rr]).reshape(3, 1)
    qq = np.abs(p - c) - b
    d = length(np.maximum(qq, 0)) + np.minimum(qq.max(axis=0), 0) - rr
    # 首から肩へのなで肩の線
    ax = np.abs(p[0])
    slope = (p[2] - (1.372 - 0.25 * np.clip(ax - 0.05, 0, 0.09) - 0.16 * np.maximum(ax - 0.14, 0))) / 1.03
    d = smax(d, slope, 0.06)
    bust = [ellipsoid(p, (s * 0.060, -0.056, 1.175), (0.058, 0.055, 0.052)) for s in (-1, 1)]
    d = union([d] + bust, 0.045)
    outer = d
    d = shell(d, 0.007)
    d = smax(d, 0.80 - p[2], 0.004)                         # すそ
    neck = np.sqrt(p[0] ** 2 + (p[1] - 0.010) ** 2) - 0.051
    vee = np.maximum(p[1] + 0.02, (1.288 + 1.15 * np.abs(p[0])) - p[2]) / 1.5
    cut = smin(np.maximum(neck, 1.30 - p[2]), vee, 0.02)    # 丸い首まわりと V を角なしでつなぐ
    d = smax(d, -cut, 0.009)                                 # えりぐり（後ろは丸く、前は V）
    # ポケット：表面に貼った薄い板（胸の左に1つ、すその左右に1つずつ）
    for cx, cz, hw, hh in ((0.068, 1.19, 0.034, 0.038), (0.088, 0.875, 0.046, 0.046), (-0.088, 0.875, 0.046, 0.046)):
        rect = np.maximum(np.abs(p[0] - cx) - hw, np.abs(p[2] - cz) - hh)
        layer = np.maximum(outer - 0.0035, -outer - 0.004)
        d = np.minimum(d, np.maximum(np.maximum(rect, layer), p[1]))
    return d


def sleeves(p):
    """袖は胴とつなげず、別の部品にする（胴とつなげると、腕を動かしたとき継ぎ目が裂ける）。
    袖の上の丸みは胴の肩の中に入っていて、腕を上げても袖の筒がそのまま腕といっしょに回る"""
    sl = []
    for side in (-1, 1):
        sh, el, _, d1, _ = arm_points(side)
        # 袖の上の丸みは胴の肩より低くして、肩の線から飛び出さないようにする
        c = capsule(p, (sh + d1 * 0.035).tolist(), (sh + d1 * 0.2).tolist(), 0.051, 0.051)
        c = shell(c, 0.006)
        proj = np.sum((p - sh.reshape(3, 1)) * d1.reshape(3, 1), axis=0)
        sl.append(smax(c, proj - 0.14, 0.004))
    return np.minimum(sl[0], sl[1])


def pants(p):
    ds = [ellipsoid(p, (0, 0.012, 0.86), (0.148, 0.086, 0.10))]
    for s in (-1, 1):
        ds.append(capsule(p, (s * 0.080, 0.01, 0.82), (s * 0.095, 0.0, 0.08), 0.074, 0.062))
    d = union(ds, 0.03)
    return smax(d, 0.075 - p[2], 0.004)


def shoes(p):
    ds = []
    for s in (-1, 1):
        ds.append(ellipsoid(p, (s * 0.096, -0.035, 0.028), (0.048, 0.115, 0.052)))
    d = union(ds, 0.01)
    return smax(d, -p[2], 0.006)


def surface_y(x, z):
    """顔の正面の表面の y を、前から光線を進めて求める。"""
    y = -0.2
    for _ in range(200):
        d = float(head_solid(np.array([[x], [y], [z]]))[0])
        if d < 1e-5:
            break
        y += max(d * 0.8, 1e-5)
    return y


EYE_Z = HC[2] - 0.012


def eye_center(side):
    x = side * 0.038
    return np.array([x, surface_y(x, EYE_Z) + 0.0035, EYE_Z])


def eyes(p):
    ds = []
    for s in (-1, 1):
        x, yc, z = eye_center(s)
        # ハコブくんの縦長の丸い目に寄せる：縦のカプセル（側面がまっすぐ）と楕円の中間の形
        k = 0.0118 / 0.0062
        ps = p.copy()
        ps[1] = (p[1] - yc) * k + yc
        pill = capsule(ps, (x, yc, z - 0.0072), (x, yc, z + 0.0072), 0.0118, 0.0118) / k
        ell = ellipsoid(p, (x, yc, z), (0.0128, 0.0062, 0.0188))
        ds.append(0.65 * pill + 0.35 * ell)
    return union(ds, 0.002)


def brow_points(side):
    xs = np.linspace(0.022, 0.056, 8)
    pts = [(side * x0, 0.0275 + 0.003 * (1 - ((x0 - 0.037) / 0.019) ** 2)) for x0 in xs]
    q = [(x0, surface_y(x0, HC[2] + z0) + 0.0012, HC[2] + z0) for x0, z0 in pts]
    rs = [0.0021 + 0.0005 * (1 - abs(x0 - 0.034) / 0.022) for x0 in xs]
    return q, rs


def brows(p):
    ds = []
    for s in (-1, 1):
        q, rs = brow_points(s)
        for i in range(len(q) - 1):
            ds.append(capsule(p, q[i], q[i + 1], rs[i], rs[i + 1]))
    return union(ds, 0.002)


MOUTH_Z = HC[2] - 0.068


def mouth_line(p, half_w, curve, z0=MOUTH_Z, r=0.0017):
    """横の線の口。curve＞0 で端が上がる（笑顔）、＜0 で端が下がる（困り顔）"""
    xs = np.linspace(-half_w, half_w, 13)
    q = []
    for x0 in xs:
        z = z0 + curve * (x0 / half_w) ** 2
        q.append((x0, surface_y(x0, z) + 0.0012, z))
    ds = [capsule(p, q[i], q[i + 1], r, r) for i in range(len(q) - 1)]
    return union(ds, 0.002)


def mouth_open(p, rx, rz, flat_top=True, z0=MOUTH_Z):
    """開いた口（話す・驚く）。flat_top で上がまっすぐ・下が丸い形"""
    y = surface_y(0.0, z0)
    zc = z0 + (rz * 0.35 if flat_top else 0.0)
    d = ellipsoid(p, (0.0, y + 0.0028, zc), (rx, 0.0045, rz))
    if flat_top:
        d = smax(d, p[2] - (zc - rz * 0.05), 0.0015)
    return d


MOUTHS = {   # 口の形（Unity で表示を切り替える。既定は smile）
    "smile": lambda p: mouth_line(p, 0.015, 0.006),
    "flat": lambda p: mouth_line(p, 0.012, 0.0),
    "worried": lambda p: mouth_line(p, 0.011, -0.004, z0=MOUTH_Z - 0.002),
    "talk": lambda p: mouth_open(p, 0.011, 0.0085),
    "surprised": lambda p: mouth_open(p, 0.0065, 0.0085, flat_top=False),
}


# ---------- 格子で面にする ----------
def mesh_of(fn, lo, hi, vox):
    lo, hi = np.array(lo, float) + vox * 0.37, np.array(hi, float)   # 格子の点が平らな面にちょうど乗らないようにずらす
    n = np.ceil((hi - lo) / vox).astype(int) + 1
    xs = lo[0] + np.arange(n[0]) * vox
    ys = lo[1] + np.arange(n[1]) * vox
    zs = lo[2] + np.arange(n[2]) * vox
    vol = np.empty((n[0], n[1], n[2]), np.float32)
    gx, gy = np.meshgrid(xs, ys, indexing="ij")
    step = 24
    for k0 in range(0, n[2], step):
        zz = zs[k0:k0 + step]
        P = np.stack([np.repeat(gx[..., None], len(zz), 2), np.repeat(gy[..., None], len(zz), 2),
                      np.broadcast_to(zz, (n[0], n[1], len(zz)))]).reshape(3, -1)
        vol[:, :, k0:k0 + step] = fn(P).reshape(n[0], n[1], len(zz))
    v, f, _, _ = marching_cubes(vol, 0.0, spacing=(vox, vox, vox))
    return v + lo, f


def to_blender(name, v, f, col, rough=0.6, sss=0.0, ratio=None):
    me = bpy.data.meshes.new(name)
    me.from_pydata(v.tolist(), [], f.tolist())   # marching_cubes の向きのままで外向き（逆にすると Unity で全部の面が裏返った・依頼162）
    me.update()
    ob = bpy.data.objects.new(name, me)
    bpy.context.scene.collection.objects.link(ob)
    if ratio:
        m = ob.modifiers.new("dec", "DECIMATE")
        m.ratio = ratio
        bpy.context.view_layer.objects.active = ob
        bpy.ops.object.modifier_apply(modifier="dec")
    for poly in ob.data.polygons:
        poly.use_smooth = True
    mat = bpy.data.materials.new(name + "_Mat")
    mat.use_nodes = True
    b = mat.node_tree.nodes["Principled BSDF"]
    b.inputs["Base Color"].default_value = (*[(c / 255) ** 2.2 for c in col], 1)
    b.inputs["Roughness"].default_value = rough
    if sss:
        b.inputs["Subsurface Weight"].default_value = sss
        b.inputs["Subsurface Radius"].default_value = (0.02, 0.008, 0.005)
    ob.data.materials.append(mat)
    print(name, len(ob.data.polygons), "faces")
    return ob


SKIN = (232, 180, 142)
HAIR = (38, 30, 27)
ROYAL = (44, 84, 170)
WHITE = (238, 238, 234)
DARK = (44, 34, 30)
LIP = (150, 72, 66)

# ---------- 骨 ----------
# Unity の Humanoid がそのまま読み取れる名前にする（Left＝本人の左＝+x。前は -y）。
# 目・眉の骨は Humanoid に入れない追加の骨（まばたき・眉の上げ下げをスクリプトで動かす）。
def leg_points(side):
    s = 1 if side > 0 else -1
    hip = np.array([s * 0.085, 0.010, 0.860])
    knee = np.array([s * 0.090, 0.000, 0.470])
    ankle = np.array([s * 0.095, 0.004, 0.085])
    ball = np.array([s * 0.096, -0.075, 0.025])
    tip = np.array([s * 0.096, -0.140, 0.022])
    return hip, knee, ankle, ball, tip


FWD = (0, -1, 0)


def bone_layout():
    """{骨の名前: (根元, 先, 親, 骨の z 軸を向ける向き)}"""
    B = {
        "Hips": ((0, 0.012, 0.90), (0, 0.012, 0.98), None, FWD),
        "Spine": ((0, 0.012, 0.98), (0, 0.012, 1.11), "Hips", FWD),
        "Chest": ((0, 0.012, 1.11), (0, 0.012, 1.30), "Spine", FWD),
        "Neck": ((0, 0.012, 1.31), (0, 0.006, 1.42), "Chest", FWD),
        "Head": ((0, 0.006, 1.42), (0, 0.0, 1.62), "Neck", FWD),
    }
    for side, L, tag in ((1, "Left", "L"), (-1, "Right", "R")):
        s = side
        sh, el, wr, _, _ = arm_points(side)
        g = hand_geo(side)
        n = g["n"]
        B[L + "Shoulder"] = ((s * 0.030, 0.012, 1.325), sh, "Chest", FWD)
        B[L + "UpperArm"] = (sh, el, L + "Shoulder", FWD)
        B[L + "LowerArm"] = (el, wr, L + "UpperArm", FWD)
        B[L + "Hand"] = (wr, g["palm_end"], L + "LowerArm", n)
        for fname, (a, b, c, _) in g["fingers"].items():
            B[L + fname + "Proximal"] = (a, b, L + "Hand", n)
            B[L + fname + "Intermediate"] = (b, c, L + fname + "Proximal", n)
        ta, tb, tc = g["thumb"]
        B[L + "ThumbProximal"] = (ta, tb, L + "Hand", n)
        B[L + "ThumbIntermediate"] = (tb, tc, L + "ThumbProximal", n)
        hip, knee, ankle, ball, tip = leg_points(side)
        B[L + "UpperLeg"] = (hip, knee, "Hips", FWD)
        B[L + "LowerLeg"] = (knee, ankle, L + "UpperLeg", FWD)
        B[L + "Foot"] = (ankle, ball, L + "LowerLeg", (0, 0, 1))
        B[L + "Toes"] = (ball, tip, L + "Foot", (0, 0, 1))
        ec = eye_center(side)
        B["Eye_" + tag] = (ec, ec + np.array([0, -0.015, 0]), "Head", (0, 0, 1))
        q, _ = brow_points(side)
        bc = np.mean(np.array(q), axis=0)
        B["Brow_" + tag] = (bc, bc + np.array([0, -0.012, 0]), "Head", (0, 0, 1))
    return B


def band(p, z0, z1):
    return np.maximum(z0 - p[2], p[2] - z1)


def regions(kind, p):
    """部品の種類ごとに、骨ごとの「その骨の受け持つ形までの距離」を返す。重みはここから決める"""
    R = {}
    if kind == "skin":
        R["Head"] = head_solid(p)
        R["Neck"] = capsule(p, (0, 0.012, 1.30), (0, 0.006, 1.45), 0.044, 0.041)
        R["Chest"] = ellipsoid(p, (0, 0.012, 1.27), (0.10, 0.066, 0.08))
        for side, L in ((1, "Left"), (-1, "Right")):
            sh, el, wr, _, _ = arm_points(side)
            R[L + "Shoulder"] = capsule(p, (side * 0.045, 0.015, 1.335), (side * 0.12, 0.015, 1.31), 0.024, 0.022)
            R[L + "UpperArm"] = capsule(p, sh.tolist(), el.tolist(), 0.040, 0.037)
            R[L + "LowerArm"] = capsule(p, el.tolist(), wr.tolist(), 0.036, 0.029)
            R[L + "Hand"] = palm(p, side)
            for k, v in finger_segs(p, side).items():
                R[L + k] = v
    elif kind == "top":
        rr = 0.068
        c = np.array([0.0, 0.010, 1.085]).reshape(3, 1)
        b = np.array([0.158 - rr, 0.098 - rr, 0.30 - rr]).reshape(3, 1)
        qq = np.abs(p - c) - b
        torso = length(np.maximum(qq, 0)) + np.minimum(qq.max(axis=0), 0) - rr
        R["Hips"] = np.maximum(torso, band(p, 0.60, 0.94))
        R["Spine"] = np.maximum(torso, band(p, 0.94, 1.10))
        R["Chest"] = np.maximum(torso, band(p, 1.10, 1.45))
        for side, L in ((1, "Left"), (-1, "Right")):
            sh, _, _, _, _ = arm_points(side)
            R[L + "Shoulder"] = capsule(p, (side * 0.045, 0.012, 1.33), sh.tolist(), 0.045, 0.045)
    elif kind == "pants":
        R["Hips"] = ellipsoid(p, (0, 0.012, 0.86), (0.148, 0.086, 0.10))
        for side, L in ((1, "Left"), (-1, "Right")):
            hip, knee, ankle, _, _ = leg_points(side)
            R[L + "UpperLeg"] = capsule(p, hip.tolist(), knee.tolist(), 0.074, 0.066)
            R[L + "LowerLeg"] = capsule(p, knee.tolist(), ankle.tolist(), 0.066, 0.062)
    elif kind == "shoes":
        for side, L in ((1, "Left"), (-1, "Right")):
            _, _, ankle, ball, tip = leg_points(side)
            R[L + "Foot"] = capsule(p, ankle.tolist(), ball.tolist(), 0.05, 0.05)
            R[L + "Toes"] = capsule(p, ball.tolist(), tip.tolist(), 0.045, 0.04)
    return R


def tau_of(bone):
    if any(k in bone for k in ("Index", "Middle", "Ring", "Little")):
        return 0.0022
    if "Thumb" in bone:
        return 0.0025
    if bone.endswith("Hand"):
        return 0.005
    if bone in ("Hips", "Spine", "Chest"):
        return 0.018
    if "Leg" in bone or "Foot" in bone or "Toes" in bone:
        return 0.018
    return 0.011


def weight_mesh(ob, kind):
    P = np.array([v.co for v in ob.data.vertices]).T          # 根は原点・回転なしなので形の座標そのまま
    R = regions(kind, P)
    names = list(R)
    D = np.stack([R[k] for k in names])
    dmin = D.min(axis=0)
    # パンツは腰と太ももの境目を広くなだらかにする（しゃがむと付け根の面が細かく折れた・依頼162）
    tau = (lambda k: 0.045 if k == "Hips" or "UpperLeg" in k else tau_of(k)) if kind == "pants" else tau_of
    W = np.stack([np.exp(-np.maximum(D[i] - dmin, 0) / tau(k)) for i, k in enumerate(names)])
    W = W / W.sum(axis=0)
    top4 = np.argsort(-W, axis=0)[:4]
    groups = {k: ob.vertex_groups.new(name=k) for k in names}
    for vi in range(P.shape[1]):
        idx = top4[:, vi]
        w = W[idx, vi]
        w = w / w.sum()
        for j, ww in zip(idx, w):
            if ww > 0.01:
                groups[names[j]].add([vi], float(ww), "REPLACE")


def weight_sleeves(ob):
    """袖はまるごと上腕の骨に付ける（付け根を肩の骨と分け合うと、腕を上げた時に袖の山がゆがんだ）。
    袖の山は肩の関節のすぐ外にあるので、腕と一緒に回しても肩から離れない"""
    for side, L in ((1, "Left"), (-1, "Right")):
        g = ob.vertex_groups.new(name=L + "UpperArm")
        g.add([v.index for v in ob.data.vertices if v.co.x * side > 0], 1.0, "REPLACE")


def weight_whole(ob, bone):
    g = ob.vertex_groups.new(name=bone)
    g.add(list(range(len(ob.data.vertices))), 1.0, "REPLACE")


def weight_by_side(ob, prefix):
    for tag, sgn in (("L", 1), ("R", -1)):
        g = ob.vertex_groups.new(name=prefix + tag)
        g.add([v.index for v in ob.data.vertices if v.co.x * sgn > 0], 1.0, "REPLACE")


def build_armature():
    from mathutils import Vector
    arm = bpy.data.armatures.new("Nurse_Oshiro_Rig")
    rig = bpy.data.objects.new("Nurse_Oshiro", arm)          # 根：床の中心
    bpy.context.scene.collection.objects.link(rig)
    bpy.context.view_layer.objects.active = rig
    bpy.ops.object.mode_set(mode="EDIT")
    B = bone_layout()
    eb = {}
    for name, (h, t, _, _) in B.items():
        b = arm.edit_bones.new(name)
        b.head = Vector([float(x) for x in h])
        b.tail = Vector([float(x) for x in t])
        eb[name] = b
    for name, (_, _, parent, zdir) in B.items():
        if parent:
            eb[name].parent = eb[parent]
        eb[name].align_roll(Vector([float(x) for x in zdir]))
    bpy.ops.object.mode_set(mode="OBJECT")
    return rig


SKIN = (232, 180, 142)
HAIR = (38, 30, 27)
ROYAL = (44, 84, 170)
WHITE = (238, 238, 234)
DARK = (44, 34, 30)
LIP = (150, 72, 66)
MOUTH_IN = (120, 48, 46)

bpy.ops.wm.read_factory_settings(use_empty=True)
FACE_LO, FACE_HI = (-0.08, -0.14, HC[2] - 0.04), (0.08, -0.06, HC[2] + 0.05)
MOUTH_LO, MOUTH_HI = (-0.03, -0.13, HC[2] - 0.095), (0.03, -0.07, HC[2] - 0.05)
# (名前, 形, 範囲, 格子, 色, 粗さ, 肌の透け, 三角形を残す割合, 重みの付け方)
parts = [
    ("Skin", skin, (-0.34, -0.18, 0.58), (0.34, 0.15, 1.67), VOX, SKIN, 0.55, 0.12, 0.07, ("mesh", "skin")),
    ("Hair", hair, (-0.15, -0.16, 1.32), (0.15, 0.21, 1.69), VOX, HAIR, 0.5, 0, 0.06, ("whole", "Head")),
    ("Top", top, (-0.30, -0.15, 0.78), (0.30, 0.15, 1.40), VOX, ROYAL, 0.75, 0, 0.025, ("mesh", "top")),
    ("Sleeves", sleeves, (-0.30, -0.12, 1.08), (0.30, 0.12, 1.40), VOX, ROYAL, 0.75, 0, 0.05, ("sleeves", None)),
    ("Pants", pants, (-0.20, -0.13, 0.05), (0.20, 0.14, 1.0), VOX * 1.4, ROYAL, 0.75, 0, 0.04, ("mesh", "pants")),
    ("Shoes", shoes, (-0.17, -0.17, -0.01), (0.17, 0.10, 0.11), VOX, WHITE, 0.5, 0, 0.05, ("mesh", "shoes")),
    ("Eyes", eyes, FACE_LO, FACE_HI, 0.0008, DARK, 0.35, 0, 0.15, ("side", "Eye_")),
    ("Brows", brows, FACE_LO, FACE_HI, 0.0008, DARK, 0.35, 0, 0.15, ("side", "Brow_")),
]
for k, fn in MOUTHS.items():
    col = MOUTH_IN if k in ("talk", "surprised") else LIP
    parts.append(("Mouth_" + k, fn, MOUTH_LO, MOUTH_HI, 0.0006, col, 0.5, 0, 0.3, ("whole", "Head")))

rig = build_armature()
tris = {}
for name, fn, lo, hi, vox, col, rough, sss, ratio, how in parts:
    v, f = mesh_of(fn, lo, hi, vox)
    ob = to_blender("Nurse_" + name, v, f, col, rough, sss, ratio)
    if how[0] == "mesh":
        weight_mesh(ob, how[1])
    elif how[0] == "sleeves":
        weight_sleeves(ob)
    elif how[0] == "whole":
        weight_whole(ob, how[1])
    else:
        weight_by_side(ob, how[1])
    ob.parent = rig
    mod = ob.modifiers.new("Armature", "ARMATURE")
    mod.object = rig
    if name.startswith("Mouth_") and name != "Mouth_smile":
        ob.hide_render = True                                  # 既定の顔は笑顔。ほかの口は隠しておく
        ob.hide_viewport = True
    tris[ob.name] = sum(len(p.vertices) - 2 for p in ob.data.polygons)
tris["_total_triangles"] = sum(tris.values())
bpy.ops.wm.save_as_mainfile(filepath=os.path.join(OUT, "nurse_clay.blend"))
for ob in bpy.data.objects:      # FBX には隠した口も入れる（Unity で切り替える）
    ob.hide_viewport = False
bpy.ops.export_scene.fbx(
    filepath=os.path.join(OUT, "nurse_oshiro.fbx"), use_selection=False, object_types={"ARMATURE", "MESH"},
    apply_unit_scale=True, apply_scale_options="FBX_SCALE_ALL",
    axis_forward="-Z", axis_up="Y", bake_space_transform=False,
    mesh_smooth_type="FACE", use_mesh_modifiers=False, use_triangles=True,
    add_leaf_bones=False, primary_bone_axis="Y", secondary_bone_axis="X", armature_nodetype="NULL",
    bake_anim=False, path_mode="STRIP", embed_textures=False,
)
import json  # noqa: E402
json.dump({"height_m": 1.65, "triangles": tris, "bones": list(bone_layout())},
          open(os.path.join(OUT, "nurse_report.json"), "w"), ensure_ascii=False, indent=1)
print(json.dumps(tris, ensure_ascii=False))
````

### hakobu_build.py（変えた後の全文・cab3977）

````python
"""ハコブくんの軽い3Dモデルを Blender（bpy）で作り、Unity 用に書き出す。

使い方:
  python3 hakobu_build.py --out <出力フォルダ> [--W 0.459 --D 0.549 --H 1.081] [--render]

大きさの既定は、病院の搬送ロボットのシミュレーションのロボット
（p2m-site tools/hospital_sim/hospital-unity-params.json の robot.size_m）。
外形は hakobu_profiles.json（三面図から測った外形）から作る。
向き: Blender では前が -Y・上が +Z。FBX は Unity で前が +Z・上が +Y になるように書き出す。
"""
import argparse
import json
import math
import os
import sys

import bpy  # noqa: I001  bpy を先に読む（bmesh・mathutils は bpy が用意する）
import bmesh
from mathutils import Vector
from PIL import Image, ImageDraw, ImageFilter

HERE = os.path.dirname(os.path.abspath(__file__))

# ---------------------------------------------------------------- 色（画像の中の色の区画）
COL = {
    "white": (236, 234, 229),
    "band": (214, 212, 207),      # 頭の横の帯
    "seam": (186, 184, 179),      # 継ぎ目の線
    "door": (222, 220, 215),
    "base": (78, 79, 82),         # 台
    "black": (30, 31, 33),        # バンパー・取っ手・口
    "screen": (14, 14, 16),
    "green": (96, 236, 140),
    "drawer_in": (196, 195, 190),
    "rim": (184, 186, 186),       # 画面のまわりの丸いふち
    "tray": (206, 228, 240),
    "eye": (250, 250, 250),
    "mouth": (84, 170, 236),
}
EMIT = {"green": (54, 227, 122)}
ATLAS = 1024
# 区画（左上の x, y, 幅, 高さ）
R_FACE = (0, 0, 512, 488)
R_SIDE = (512, 0, 256, 512)
R_FRONT = (768, 0, 256, 512)
R_BACK = (0, 512, 256, 512)
SW_ORIGIN = (256, 512)
SW_CELL = 64
SWATCH_NAMES = ["white", "band", "base", "black", "screen", "green", "drawer_in", "tray", "door", "rim"]


def swatch_px(name):
    i = SWATCH_NAMES.index(name)
    cols = (ATLAS - SW_ORIGIN[0]) // SW_CELL
    x = SW_ORIGIN[0] + (i % cols) * SW_CELL
    y = SW_ORIGIN[1] + (i // cols) * SW_CELL
    return x, y


def swatch_uv(name):
    x, y = swatch_px(name)
    return ((x + SW_CELL / 2) / ATLAS, 1 - (y + SW_CELL / 2) / ATLAS)


# ---------------------------------------------------------------- 三面図から測った位置（正規化）
# 正面: 幅は全幅=1（中心0）、高さは床0〜てっぺん1。横: 奥行きは全奥行き=1（前が負）。
F = {
    "screen_hw": 0.3094, "screen_t": (0.7216, 0.9639),
    "eye_dx": 0.1596, "eye_t": 0.8473,
    "drawer_hw": 0.4247, "drawer_t": (0.3286, 0.6203),
    "handle_hw": 0.1161, "handle_t": (0.5292, 0.5773),
    "bumper_t": (0.174, 0.235), "bumper_back": -0.303,
    "slot_hw": 0.0798, "slot_t": (0.1426, 0.1649),
    "base_t": (0.058, 0.113),
    "wheel_x": 0.3525, "wheel_y": (-0.3412, 0.3135),
    "ring_c": (0.0217, 0.4661),
    "pod": {"d": (-0.3428, 0.4224), "t": (0.173, 0.762),
            "r": {"ft": (0.203, 0.112), "bt": (0.03, 0.02), "bb": (0.139, 0.129), "fb": (0.116, 0.118)}},
    "band_t": (0.7915, 0.895),
    "head_seam_t": 0.703,
    # 後ろ（見る人から見た左右。u は左端0〜右端1）
    "back": {
        "notch": (0.0534, (0.8724, 0.8892)),
        "ctrl": (0.1988, (0.6279, 0.7213)),
        "door": (0.3352, (0.3323, 0.6248)),
        "panel": (0.3412, (0.1718, 0.3375)),
        "strip": (0.2829, (0.2882, 0.3107)),
        "grill_l": ((-0.299, -0.0463), (0.1804, 0.2648)),
        "grill_r": ((0.0411, 0.3049), (0.1804, 0.2648)),
    },
}


# ---------------------------------------------------------------- 2D の形
def rrect(x0, x1, y0, y1, radii, seg=3):
    """角ごとに半径 (rx, ry) を持つ角丸の四角。反時計回り。
    radii: dict br/tr/tl/bl → (rx, ry)。y0 側を下とする。"""
    w, h = x1 - x0, y1 - y0
    out = []
    spec = [("br", x1, y0, -90), ("tr", x1, y1, 0), ("tl", x0, y1, 90), ("bl", x0, y0, 180)]
    for key, cx, cy, a0 in spec:
        rx, ry = radii[key]
        rx = max(1e-5, min(rx, w * 0.49))
        ry = max(1e-5, min(ry, h * 0.49))
        ccx = cx - rx if key in ("br", "tr") else cx + rx
        ccy = cy + ry if key in ("br", "bl") else cy - ry
        for k in range(seg + 1):
            a = math.radians(a0 + 90 * k / seg)
            out.append((ccx + rx * math.cos(a), ccy + ry * math.sin(a)))
    return out


def ellipse(cx, cy, rx, ry, n):
    return [(cx + rx * math.cos(2 * math.pi * k / n), cy + ry * math.sin(2 * math.pi * k / n)) for k in range(n)]


def signed_area(pts):
    return 0.5 * sum(pts[i][0] * pts[(i + 1) % len(pts)][1] - pts[(i + 1) % len(pts)][0] * pts[i][1] for i in range(len(pts)))


# ---------------------------------------------------------------- bmesh の部品
def prism(bm, pts, axis, lo, hi):
    """2D の形を axis の向きに lo〜hi まで押し出した柱。面の向きは最後にそろえる。"""
    if signed_area(pts) < 0:
        pts = pts[::-1]

    def v3(p, s):
        if axis == "z":
            return (p[0], p[1], s)
        if axis == "x":
            return (s, p[0], p[1])
        return (p[0], s, p[1])  # axis == "y": p=(x, z)

    a = [bm.verts.new(v3(p, lo)) for p in pts]
    b = [bm.verts.new(v3(p, hi)) for p in pts]
    faces = [bm.faces.new(a[::-1]), bm.faces.new(b)]
    n = len(pts)
    for i in range(n):
        j = (i + 1) % n
        faces.append(bm.faces.new((a[i], a[j], b[j], b[i])))
    return faces


def cap_edges(faces, coord_axis, value, eps=1e-6):
    """押し出した柱の、片方のふたの輪郭の辺"""
    idx = "xyz".index(coord_axis)
    edges = set()
    for f in faces:
        for e in f.edges:
            if all(abs(v.co[idx] - value) < eps for v in e.verts):
                if len(e.link_faces) == 2 and any(abs(lf.normal[idx]) < 0.9 for lf in e.link_faces):
                    edges.add(e)
    return list(edges)


def finish(bm):
    bmesh.ops.remove_doubles(bm, verts=bm.verts, dist=1e-6)
    bmesh.ops.recalc_face_normals(bm, faces=bm.faces)


def new_object(name, bm, parent=None, mat=None):
    me = bpy.data.meshes.new(name)
    bm.to_mesh(me)
    bm.free()
    for p in me.polygons:
        p.use_smooth = True
    me.set_sharp_from_angle(angle=math.radians(35))
    if mat:
        me.materials.append(mat)
    ob = bpy.data.objects.new(name, me)
    bpy.context.scene.collection.objects.link(ob)
    if parent:
        ob.parent = parent
    return ob


def set_uv_all(bm, faces, uv):
    lay = bm.loops.layers.uv.verify()
    for f in faces:
        for l in f.loops:
            l[lay].uv = uv


# ---------------------------------------------------------------- 外形の段を選ぶ
def douglas_peucker(pts, tol):
    if len(pts) < 3:
        return pts
    a, b = Vector(pts[0]), Vector(pts[-1])
    ab = b - a
    best, idx = -1, 0
    for i in range(1, len(pts) - 1):
        p = Vector(pts[i])
        t = max(0, min(1, (p - a).dot(ab) / max(ab.dot(ab), 1e-12)))
        d = (p - (a + ab * t)).length
        if d > best:
            best, idx = d, i
    if best > tol:
        return douglas_peucker(pts[: idx + 1], tol)[:-1] + douglas_peucker(pts[idx:], tol)
    return [pts[0], pts[-1]]


def shell_levels(prof, W, D, H, t0=0.105, t1=0.996, tol=0.0022):
    rows = [r for r in prof["levels"] if t0 <= r[0] <= t1]
    bz = F["bumper_t"]
    # バンパーの帯の所は、殻をバンパーの上の溝の線までへこませる（帯は別の部品で前に出す）
    groove = min(rows, key=lambda r: abs(r[0] - 0.232))[2]
    pts = []
    for t, w, df, db in rows:
        if bz[0] - 0.006 <= t <= bz[1] + 0.012:
            df = max(df, groove)
        pts.append((t * H, w * W, df * D, db * D))
    sel = douglas_peucker(pts, tol)
    # 色や部品の境目は必ず段にする
    must = [bz[0] - 0.006, bz[1] + 0.012, F["drawer_t"][0], F["drawer_t"][1]]
    have = {round(p[0], 5) for p in sel}
    for mt in must:
        p = min(pts, key=lambda q: abs(q[0] - mt * H))
        if round(p[0], 5) not in have:
            sel.append(p)
    sel.sort(key=lambda p: p[0])
    return sel


BELLY = 1.0  # 胴の角だけに掛ける追加の倍率（--belly）
PODS = False  # 横の丸いふくらみ（--pods）
SOFT = 1.0   # 角の丸さの倍率（人の粘土の見た目に合わせるときは --soft で大きくする）
CROWN = 0.010  # 横の面のふくらみ（m）。角は動かさず、横の面の真ん中ほど外へ出す（--crown。0 で平ら）


def crown_w(z, H):
    """横のふくらみを付ける高さの重み：胴（0.15〜0.66）で1、頭（0.70 から上）と台の近くで0"""
    t = z / H
    if t < 0.11 or t > 0.70:
        return 0.0
    if t < 0.15:
        return (t - 0.11) / 0.04
    if t > 0.66:
        return (0.70 - t) / 0.04
    return 1.0


def crown_shift(y, z, df, db, H, rf, rb):
    """横の面が外へ出る量。角は動かさず、横の直線の真ん中ほど外へ出す（4f(1-f)）"""
    y0, y1 = df + rf, db - rb
    f = min(max((y - y0) / max(y1 - y0, 1e-6), 0.0), 1.0)
    return CROWN * crown_w(z, H) * 4 * f * (1 - f)


def corner_r(z, H, a, depth, front):
    t = z / H
    if t < 0.70:
        r = 0.022 if front else 0.05
    elif t < 0.80:
        k = (t - 0.70) / 0.10
        r = (0.022 * (1 - k) + 0.026 * k) if front else (0.05 * (1 - k) + 0.045 * k)
    else:
        r = 0.026 if front else 0.045
    # 丸めるのは体だけ。顔の画面のある頭（0.70 から上）は元のまま（画面が浮いて見えないように）
    w = 1.0 if t < 0.64 else (0.0 if t > 0.70 else (0.70 - t) / 0.06)
    soft = 1 + (SOFT - 1) * w
    r *= soft if front else (1 + (soft - 1) * 0.6)
    r *= 1 + (BELLY - 1) * w   # お腹（胴）の角をさらに丸める
    return min(r, a * 0.45, depth * 0.45)


def dome_levels(levels):
    """てっぺんの縁を丸める：上の端から半径 r の範囲を、円に沿って内側へ寄せた段に置きかえる"""
    if True:   # 頭のてっぺんは丸めない（顔のまわりが変わるため）
        return levels
    z_top = levels[-1][0]
    r0 = 0.022 * SOFT
    base = min(levels, key=lambda l: abs(l[0] - (z_top - r0)))   # 丸め始める高さの段の幅と奥行きを使う
    _, a, df, db = base
    r = min(r0, a * 0.35, (db - df) * 0.3)
    out = [l for l in levels if l[0] < z_top - r - 1e-6]
    zs = [l[0] for l in levels]

    import numpy as np

    def at(z):   # 元の段を高さで補間する（頭の前の面は傾いているので、段ごとの前の位置を使う）
        return [float(np.interp(z, zs, [l[i] for l in levels])) for i in (1, 2, 3)]
    n = 6
    for k in range(1, n + 1):
        th = math.radians(90 * k / n)
        inset = r * (1 - math.cos(th))
        z = z_top - r + r * math.sin(th)
        a_, df_, db_ = at(min(z, z_top - 1e-4))
        out.append((z, a_ - inset, df_ + inset, db_ - inset))
    return out


# ---------------------------------------------------------------- 部品を作る
def build_shell(bm, levels, W, D, H, seg=3):
    seg = seg + (2 if SOFT > 1 else 0)
    levels = dome_levels(levels)
    rings = []
    for z, a, df, db in levels:
        rf = corner_r(z, H, a, db - df, True)
        rb = corner_r(z, H, a, db - df, False)
        pts = rrect(-a, a, df, db, {"br": (rf, rf), "tr": (rb, rb), "tl": (rb, rb), "bl": (rf, rf)}, seg)
        if CROWN > 0:   # 横の面をふくらませる：横の面の点を、前後へ行くほど内へ寄せる（前と後ろの面はそのまま）
            w = crown_w(z, H)
            pts = [(x + (1 if x > 0 else -1) * CROWN * w * 4 * f * (1 - f), y) for x, y, f in _densify_sides(pts, seg)]
        rings.append([bm.verts.new((x, y, z)) for x, y in pts])
    faces = []
    n = len(rings[0])
    for i in range(len(rings) - 1):
        A, B = rings[i], rings[i + 1]
        for k in range(n):
            j = (k + 1) % n
            faces.append(bm.faces.new((A[k], A[j], B[j], B[k])))
    faces.append(bm.faces.new(rings[-1]))
    faces.append(bm.faces.new(rings[0][::-1]))
    return faces


def _densify_sides(pts, seg, n=10):
    """横の面の直線（右：br の終わり〜tr の始め、左：tl の終わり〜bl の始め）に n 点ずつ足す。
    ふくらみを曲線で見せるため。どの段でも点の数は同じ"""
    k = seg + 1
    out = []   # (x, y, f)：f は横の直線の上の位置（0〜1）。角の点は 0（動かさない）
    for i, p in enumerate(pts):
        out.append((p[0], p[1], 0.0))
        if i in (k - 1, 3 * k - 1):
            q = pts[i + 1]
            for j in range(1, n + 1):
                f = j / (n + 1)
                out.append((p[0] + (q[0] - p[0]) * f, p[1] + (q[1] - p[1]) * f, f))
    return out


def uv_shell(bm, faces, W, D, H):
    lay = bm.loops.layers.uv.verify()
    bm.normal_update()
    for f in faces:
        n = f.normal
        ax = max(range(3), key=lambda i: abs(n[i]))
        for l in f.loops:
            x, y, z = l.vert.co
            if ax == 2:
                l[lay].uv = swatch_uv("white")
                continue
            if ax == 0:      # 横
                rx, ry, rw, rh = R_SIDE
                u = rx + (y / D + 0.5) * rw
            elif n[1] < 0:   # 前
                rx, ry, rw, rh = R_FRONT
                u = rx + (x / W + 0.5) * rw
            else:            # 後ろ（見る人の右が -X）
                rx, ry, rw, rh = R_BACK
                u = rx + (0.5 - x / W) * rw
            v = ry + (1 - z / H) * rh
            l[lay].uv = (u / ATLAS, 1 - v / ATLAS)


def build_base(bm, W, D, H):
    z0, z1 = F["base_t"][0] * H, F["base_t"][1] * H
    rb = 0.075 * (1 + (SOFT - 1) * 0.5)
    pts = rrect(-W / 2, W / 2, -D / 2, D / 2, {k: (rb, rb) for k in ("br", "tr", "tl", "bl")}, 4 + (2 if SOFT > 1 else 0))
    faces = prism(bm, pts, "z", z0, z1)
    bm.normal_update()
    top = cap_edges(faces, "z", z1)
    bot = cap_edges(faces, "z", z0)
    # 底の角の丸みは小さく（大きく丸めると台の底が細って、車輪が台から離れた脚のように見えた・2026-11-13）
    bmesh.ops.bevel(bm, geom=bot, offset=0.012, segments=2 + (1 if SOFT > 1 else 0), profile=0.5, affect="EDGES", clamp_overlap=True)
    bm.normal_update()
    top = [e for e in bm.edges if e.is_valid and all(abs(v.co.z - z1) < 1e-6 for v in e.verts) and len(e.link_faces) == 2
           and any(abs(f.normal.z) < 0.9 for f in e.link_faces)]
    bmesh.ops.bevel(bm, geom=top, offset=0.006 * SOFT, segments=1 + (2 if SOFT > 1 else 0), profile=0.5, affect="EDGES", clamp_overlap=True)
    return list(bm.faces)


def build_wheels(bm, W, D, H, r=0.036, width=0.036, seg=12):
    if SOFT > 1:   # 丸い見た目のとき：少し大きく太いタイヤを、上半分を台の中に隠して置き、角を丸める
        # （小さくすると台の下から脚のように出て見えた。2026-11-13 エイイチの指摘でこの形に戻した）
        r, width, seg = 0.046, 0.052, 20
    faces = []
    for sx in (-1, 1):
        for wy in F["wheel_y"]:
            cx, cy = sx * F["wheel_x"] * W, wy * D
            pts = ellipse(cy, r, r, r, seg)  # (y, z) 平面の円（床に接する）
            fs = prism(bm, pts, "x", cx - width / 2, cx + width / 2)
            if SOFT > 1:
                bm.normal_update()
                es = cap_edges(fs, "x", cx - width / 2) + cap_edges(fs, "x", cx + width / 2)
                bmesh.ops.bevel(bm, geom=es, offset=0.013, segments=3, profile=0.5, affect="EDGES", clamp_overlap=True)
            faces += fs
    return faces


def build_bumper(bm, shell_lv, W, D, H):
    z0, z1 = F["bumper_t"][0] * H, F["bumper_t"][1] * H
    near = min(shell_lv, key=lambda l: abs(l[0] - (z0 + z1) / 2))
    a = near[1] + 0.006
    front = near[2] - 0.012
    # 前の角は殻の角より少しだけ大きく（殻の角が帯から突き出ないように）
    rf = max(0.045, corner_r((z0 + z1) / 2, H, near[1], near[3] - near[2], True) + 0.010)
    pts = rrect(-a, a, front, D * 0.3, {"br": (rf, rf), "tr": (0.02, 0.02), "tl": (0.02, 0.02), "bl": (rf, rf)}, 4 + (2 if SOFT > 1 else 0))
    faces = prism(bm, pts, "z", z0, z1)
    bm.normal_update()
    for zz in (z0, z1):
        es = [e for e in bm.edges if all(abs(v.co.z - zz) < 1e-6 for v in e.verts) and len(e.link_faces) == 2
              and any(abs(f.normal.z) < 0.9 for f in e.link_faces) and e.verts[0].co.y < 0]
        bmesh.ops.bevel(bm, geom=es, offset=min(0.009 * SOFT, 0.016), segments=2 + (1 if SOFT > 1 else 0), profile=0.5, affect="EDGES", clamp_overlap=True)
        bm.normal_update()
    cut_y = F["bumper_back"] * D
    geom = list(bm.verts) + list(bm.edges) + list(bm.faces)
    res = bmesh.ops.bisect_plane(bm, geom=geom, plane_co=(0, cut_y, 0), plane_no=(0, 1, 0), clear_outer=True)
    edges = [e for e in res["geom_cut"] if isinstance(e, bmesh.types.BMEdge)]
    bmesh.ops.holes_fill(bm, edges=edges, sides=0)
    return list(bm.faces)


def build_face_plate(bm, prof, W, D, H):
    """頭の傾いた前の面に乗る画面の板。返り値は前の面の向きの情報"""
    t0, t1 = F["screen_t"]
    lv = prof["levels"]
    p0 = min(lv, key=lambda r: abs(r[0] - t0))
    p1 = min(lv, key=lambda r: abs(r[0] - t1 - 0.004))
    a = Vector((0, p0[2] * D, t0 * H))
    b = Vector((0, p1[2] * D, t1 * H))
    up = (b - a).normalized()
    nrm = Vector((0, -up.z, up.y))  # 前（-Y 寄り）を向く
    if nrm.y > 0:
        nrm = -nrm
    hw = F["screen_hw"] * W
    L = (b - a).length
    pts = rrect(-hw, hw, 0, L, {k: (0.016, 0.016) for k in ("br", "tr", "tl", "bl")}, 3)   # 画面は丸めの対象にしない
    thick = 0.004
    base = a + nrm * 0.0075   # まわりの丸いふち（0.0045 まで）より前に出す

    def P(u, v, s):
        return base + Vector((u, 0, 0)) + up * v + nrm * s

    if signed_area(pts) < 0:
        pts = pts[::-1]
    back = [bm.verts.new(P(u, v, -thick)) for u, v in pts]
    front = [bm.verts.new(P(u, v, 0.0)) for u, v in pts]
    ff = bm.faces.new(front[::-1])
    faces = [ff, bm.faces.new(back)]
    n = len(pts)
    for i in range(n):
        j = (i + 1) % n
        faces.append(bm.faces.new((back[i], back[j], front[j], front[i])))
    bm.normal_update()
    if ff.normal.dot(nrm) < 0:
        ff.normal_flip()
    lay = bm.loops.layers.uv.verify()
    fx, fy, fw, fh = R_FACE
    for f in faces:
        is_front = f is ff
        for l in f.loops:
            if is_front:
                rel = l.vert.co - base
                u = rel.x / (2 * hw) + 0.5
                v = rel.dot(up) / L
                l[lay].uv = ((fx + u * fw) / ATLAS, 1 - (fy + (1 - v) * fh) / ATLAS)
            else:
                l[lay].uv = swatch_uv("screen")
    return faces, (a, b, nrm, hw, L)


def build_bezel(bm, face_info, margin=0.020, r=0.042, s0=0.0015, s1=0.0045):
    """画面のまわりの薄い灰色の丸いふち（三面図の、画面を囲む枠）。画面より少し大きく、画面の後ろに置く"""
    a, b, nrm, hw, L = face_info
    up = (b - a).normalized()
    pts = rrect(-hw - margin, hw + margin, -margin, L + margin, {k: (r, r) for k in ("br", "tr", "tl", "bl")}, 6)
    if signed_area(pts) < 0:
        pts = pts[::-1]

    def P(u, v, sft):
        return a + Vector((u, 0, 0)) + up * v + nrm * sft
    back = [bm.verts.new(P(u, v, s0 - 0.004)) for u, v in pts]
    front = [bm.verts.new(P(u, v, s1)) for u, v in pts]
    faces = [bm.faces.new(front[::-1]), bm.faces.new(back)]
    n = len(pts)
    for i in range(n):
        j = (i + 1) % n
        faces.append(bm.faces.new((back[i], back[j], front[j], front[i])))
    bm.normal_update()
    if faces[0].normal.dot(nrm) < 0:
        faces[0].normal_flip()
    return faces


def build_ring(bm, W, D, H, x_side, r_out=0.122, r_in=0.1015, seg=36, proud=0.0006, depth=0.006):
    cy, cz = F["ring_c"][0] * D, F["ring_c"][1] * H
    faces, walls = [], []
    for sx in (-1, 1):
        xo = sx * (x_side + proud)
        xi = sx * (x_side - depth)
        outer = [(cy + r_out * math.cos(2 * math.pi * k / seg), cz + r_out * math.sin(2 * math.pi * k / seg)) for k in range(seg)]
        inner = [(cy + r_in * math.cos(2 * math.pi * k / seg), cz + r_in * math.sin(2 * math.pi * k / seg)) for k in range(seg)]
        vo = [bm.verts.new((xo, y, z)) for y, z in outer]
        vi = [bm.verts.new((xo, y, z)) for y, z in inner]
        wo = [bm.verts.new((xi, y, z)) for y, z in outer]
        wi = [bm.verts.new((xi, y, z)) for y, z in inner]
        for k in range(seg):
            j = (k + 1) % seg
            faces.append(bm.faces.new((vo[k], vo[j], vi[j], vi[k])))   # 光る面
            walls.append(bm.faces.new((wo[k], wo[j], vo[j], vo[k])))   # 外の縁
            walls.append(bm.faces.new((vi[k], vi[j], wi[j], wi[k])))   # 内の縁
    return faces, walls


def build_pods(bm, shell_lv, W, D, H, thick=0.022, bevel=0.020):
    """横の丸いふくらみ（輪の付いた横板）。マンガのハコブくんの、お腹の横の厚い丸い板。
    返り値は板の外の面の x（輪をここに乗せる）"""
    pod = F["pod"]
    z0, z1 = pod["t"][0] * H, pod["t"][1] * H
    near = [l for l in shell_lv if z0 <= l[0] <= z1]
    x_in = min(l[1] for l in near) - 0.005
    x_out = max(l[1] for l in near) + thick
    rr = {k: (pod["r"][src][0] * D, pod["r"][src][1] * H) for k, src in (("tl", "ft"), ("tr", "bt"), ("br", "bb"), ("bl", "fb"))}
    pts = rrect(pod["d"][0] * D, pod["d"][1] * D, z0, z1, rr, 8)   # (y, z) 平面
    faces = []
    for sx in (-1, 1):
        lo, hi = (x_in, x_out) if sx > 0 else (-x_out, -x_in)
        fs = prism(bm, pts, "x", lo, hi)
        bm.normal_update()
        outer = sx * x_out
        es = cap_edges(fs, "x", outer)
        bmesh.ops.bevel(bm, geom=es, offset=bevel, segments=4, profile=0.5, affect="EDGES", clamp_overlap=True)
        bm.normal_update()
        faces += fs
    return x_out


def build_drawer(bm, shell_lv, W, D, H):
    t0, t1 = F["drawer_t"]
    z0, z1 = t0 * H, t1 * H
    near = [l for l in shell_lv if z0 <= l[0] <= z1]
    front = min(l[2] for l in near)
    hw = F["drawer_hw"] * W
    # 前の板（殻の前の面より 4mm 前に出す）
    y_face = front - 0.0045
    r_lo, r_hi = 0.03 * (1 + (SOFT - 1) * 0.5), 0.012 * SOFT
    pts = rrect(-hw, hw, z0, z1, {"br": (r_lo, r_lo), "bl": (r_lo, r_lo), "tr": (r_hi, r_hi), "tl": (r_hi, r_hi)}, 3 + (2 if SOFT > 1 else 0))
    panel = prism(bm, pts, "y", y_face, front + 0.012)
    set_uv_all(bm, panel, swatch_uv("white"))
    bm.normal_update()
    edges = cap_edges(panel, "y", y_face)
    bmesh.ops.bevel(bm, geom=edges, offset=0.004 * (1 + (SOFT - 1) * 0.5), segments=1 + (1 if SOFT > 1 else 0), profile=0.5, affect="EDGES", clamp_overlap=True)
    # 取っ手（黒い横長の丸い板）
    h0, h1 = F["handle_t"][0] * H, F["handle_t"][1] * H
    hh = F["handle_hw"] * W
    rr = (h1 - h0) / 2
    hp = rrect(-hh, hh, h0, h1, {k: (rr, rr) for k in ("br", "tr", "tl", "bl")}, 3)
    handle = prism(bm, hp, "y", y_face - 0.0025, y_face + 0.002)
    set_uv_all(bm, handle, swatch_uv("black"))
    # 中の箱（ふたなし）
    bx0, bx1 = -hw + 0.012, hw - 0.012
    by0, by1 = front + 0.010, front + 0.30
    bz0, bz1 = z0 + 0.018, z1 - 0.035
    th = 0.006
    box = []
    box += prism(bm, rrect(bx0, bx1, by0, by1, {k: (0.004, 0.004) for k in ("br", "tr", "tl", "bl")}, 1), "z", bz0, bz0 + th)
    for (xa, xb, ya, yb) in [(bx0, bx1, by1 - th, by1), (bx0, bx0 + th, by0, by1), (bx1 - th, bx1, by0, by1)]:
        box += prism(bm, [(xa, ya), (xb, ya), (xb, yb), (xa, yb)], "z", bz0, bz1)
    set_uv_all(bm, box, swatch_uv("drawer_in"))
    return {"box": (bx0, bx1, by0, by1, bz0 + th), "y_face": y_face}


def build_tray(bm, cx, cy, z0, sx=0.150, sy=0.122, sz=0.032):
    rt = 0.008 * SOFT
    pts = rrect(cx - sx / 2, cx + sx / 2, cy - sy / 2, cy + sy / 2, {k: (rt, rt) for k in ("br", "tr", "tl", "bl")}, 2 + (1 if SOFT > 1 else 0))
    faces = prism(bm, pts, "z", z0, z0 + sz)
    set_uv_all(bm, faces, swatch_uv("tray"))
    return faces



# ---------------------------------------------------------------- 顔（表情集の「基本」）
# 動画の制作ベース hakobu.py の表情集と同じ形：目＝縦長の角丸（open）、口＝青い笑顔（mid）。
# 寸法は hakobu.py の値（表情集の画面の高さ 149 を単位にした長さ）。目の間隔だけは画面の幅に対する比で置く。
FACE_SCREEN_H = 149.0
FACE_EYE_WH = (22.5, 47.0)
FACE_EYE_GAP = 0.486      # 目の中心どうしの間隔（画面の幅に対する比）
FACE_EYE_Y = 0.483        # 目の中心の高さ（画面の上から、高さに対する比）
FACE_MOUTH = {"hw": 27, "depth": 25, "sag": 5, "rr": 5, "top": 0.671}
FACE_WHITE = (246, 248, 252)
FACE_BLUE = (87, 181, 248)
FACE_SCREEN = (19, 19, 17)


def draw_face_base(sw, sh, out_w, out_h, SS=8):
    import numpy as np
    import cv2
    Hs = FACE_SCREEN_H
    Ws = sw / sh * Hs
    W, Hh = int(Ws * SS), int(Hs * SS)
    white = np.zeros((Hh, W), np.uint8)
    blue = np.zeros((Hh, W), np.uint8)
    cx0 = Ws / 2
    ew, eh = FACE_EYE_WH
    ey = FACE_EYE_Y * Hs
    img = Image.fromarray(white)
    d = ImageDraw.Draw(img)
    for sgn in (-1, 1):
        ex = cx0 + sgn * FACE_EYE_GAP * Ws / 2
        d.rounded_rectangle([(ex - ew / 2) * SS, (ey - eh / 2) * SS, (ex + ew / 2) * SS, (ey + eh / 2) * SS], radius=ew / 2 * SS, fill=255)
    white = np.array(img)
    m = FACE_MOUTH
    t = np.linspace(0, 1, 80)
    top = m["top"] * Hs
    xs = cx0 - m["hw"] + 2 * m["hw"] * t
    upper = np.stack([xs, top + m["sag"] * (1 - (2 * t - 1) ** 2)], 1)
    lower = np.stack([xs[::-1], (top + m["depth"] * np.sqrt(np.clip(1 - (2 * t - 1) ** 2, 0, 1)))[::-1]], 1)
    poly = (np.concatenate([upper, lower]) * SS).astype(np.int32)
    cv2.fillPoly(blue, [poly], 255)
    k = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (2 * m["rr"] * SS + 1, 2 * m["rr"] * SS + 1))
    blue = cv2.morphologyEx(blue, cv2.MORPH_OPEN, k)
    # 目のまわりのにじむ光（表情集と同じく、白い目だけ）
    glow = cv2.GaussianBlur(white.astype(np.float32) / 255, (0, 0), 2.6 * SS) * 0.75
    alb = np.zeros((Hh, W, 3), np.float32) + np.array(FACE_SCREEN, np.float32)
    gcol = np.array((205, 225, 255), np.float32)
    alb = alb * (1 - glow[..., None]) + gcol * glow[..., None]
    a = white.astype(np.float32)[..., None] / 255
    alb = alb * (1 - a) + np.array(FACE_WHITE, np.float32) * a
    b = blue.astype(np.float32)[..., None] / 255
    alb = alb * (1 - b) + np.array(FACE_BLUE, np.float32) * b
    emi = gcol * glow[..., None] * 0.6
    emi = emi * (1 - a) + np.array(FACE_WHITE, np.float32) * 0.92 * a
    emi = emi * (1 - b) + np.array(FACE_BLUE, np.float32) * 0.85 * b
    A = Image.fromarray(np.clip(alb, 0, 255).astype(np.uint8)).resize((out_w, out_h), Image.LANCZOS)
    E = Image.fromarray(np.clip(emi, 0, 255).astype(np.uint8)).resize((out_w, out_h), Image.LANCZOS)
    return A, E


# ---------------------------------------------------------------- 色の画像
def draw_atlas(W, D, H, face_info, ss=2):
    S = ATLAS * ss
    alb = Image.new("RGB", (S, S), COL["white"])
    emi = Image.new("RGB", (S, S), (0, 0, 0))
    da, de = ImageDraw.Draw(alb), ImageDraw.Draw(emi)

    def box(r):
        return [c * ss for c in r]

    # 色の区画
    for name in SWATCH_NAMES:
        x, y = swatch_px(name)
        da.rectangle(box((x, y, x + SW_CELL, y + SW_CELL)), fill=COL[name] if name in COL else COL["white"])
        if name in EMIT:
            de.rectangle(box((x, y, x + SW_CELL, y + SW_CELL)), fill=EMIT[name])

    # 顔（画面）
    fx, fy, fw, fh = R_FACE
    da.rectangle(box((fx, fy, fx + fw, fy + fh)), fill=COL["screen"])
    a, b, nrm, hw, L = face_info
    sw, sh = 2 * hw, L  # 画面の実寸（m）

    def fpx(u_m, v_m):  # 画面の中心からの m（右・上が正）→ 画像の px
        return (fx + (u_m / sw + 0.5) * fw, fy + (0.5 - v_m / sh) * fh)

    face_alb, face_emi = draw_face_base(sw, sh, fw, fh)
    alb.paste(face_alb.resize((fw * ss, fh * ss), Image.LANCZOS), (fx * ss, fy * ss))
    emi.paste(face_emi.resize((fw * ss, fh * ss), Image.LANCZOS), (fx * ss, fy * ss))

    # 横の面（u: 前0〜後ろ1、v: 上0〜下1）
    sx0, sy0, sw_, sh_ = R_SIDE

    def spx(dn, t):
        return (sx0 + (dn + 0.5) * sw_, sy0 + (1 - t) * sh_)

    bt = F["band_t"]
    p0, p1 = spx(-0.5, bt[1]), spx(0.5, bt[0])
    da.rectangle(box((p0[0], p0[1], p1[0], p1[1])), fill=COL["band"])
    da.line(box((p0[0], p0[1], p1[0], p0[1])), fill=COL["seam"], width=2 * ss)
    pod = F["pod"]
    pts = rrect(pod["d"][0], pod["d"][1], pod["t"][0], pod["t"][1],
                {"tl": pod["r"]["ft"], "tr": pod["r"]["bt"], "br": pod["r"]["bb"], "bl": pod["r"]["fb"]}, 12)
    poly = [tuple(c * ss for c in spx(d, t)) for d, t in pts]
    da.line(poly + [poly[0]], fill=(158, 156, 152), width=4 * ss, joint="curve")
    # 横の下：車輪の間のへこんだ灰色の板（三面図の横。上の角が丸く、下へ少し広がる）
    sk = rrect(-0.27, 0.30, 0.0, 0.178, {"tl": (0.05, 0.05), "tr": (0.05, 0.05), "br": (0.0, 0.0), "bl": (0.0, 0.0)}, 10)
    sk = [(d + (-0.02 if d < 0.015 else 0.02) * max(0.0, (0.14 - t) / 0.14), t) for d, t in sk]   # 下へ少し広げる
    poly = [tuple(c * ss for c in spx(d, t)) for d, t in sk]
    da.polygon(poly, fill=(172, 171, 168))
    da.line(poly + [poly[0]], fill=(150, 149, 146), width=2 * ss, joint="curve")

    # 前の面
    fx0, fy0, fw_, fh_ = R_FRONT

    def frpx(xn, t):
        return (fx0 + (xn + 0.5) * fw_, fy0 + (1 - t) * fh_)

    g = 0.0035 / W
    d0, d1 = F["drawer_t"]
    p0 = frpx(-F["drawer_hw"] - g, d1 + 0.0035 / H)
    p1 = frpx(F["drawer_hw"] + g, d0 - 0.0035 / H)
    da.rounded_rectangle(box((p0[0], p0[1], p1[0], p1[1])), radius=6 * ss, fill=(52, 53, 56))
    s0, s1 = F["slot_t"]
    p0, p1 = frpx(-F["slot_hw"], s1), frpx(F["slot_hw"], s0)
    da.rounded_rectangle(box((p0[0], p0[1], p1[0], p1[1])), radius=5 * ss, fill=COL["black"])
    hs = F["head_seam_t"]
    p0, p1 = frpx(-0.5, hs), frpx(0.5, hs)
    da.line(box((p0[0], p0[1], p1[0], p1[1])), fill=COL["seam"], width=2 * ss)

    # 後ろの面（見る人から見た向きで描く）
    bx0, by0, bw_, bh_ = R_BACK

    def bpx(un, t):
        return (bx0 + (un + 0.5) * bw_, by0 + (1 - t) * bh_)

    B = F["back"]
    hw_, (t0, t1) = B["notch"]
    p0, p1 = bpx(-hw_, t1), bpx(hw_, t0)
    da.rectangle(box((p0[0], p0[1], p1[0], p1[1])), fill=(70, 71, 74))
    p0, p1 = bpx(-0.5, F["band_t"][1]), bpx(0.5, F["band_t"][1])
    da.line(box((p0[0], p0[1], p1[0], p1[1])), fill=COL["seam"], width=2 * ss)
    hw_, (t0, t1) = B["door"]
    p0, p1 = bpx(-hw_, t1), bpx(hw_, t0)
    da.rounded_rectangle(box((p0[0], p0[1], p1[0], p1[1])), radius=4 * ss, fill=COL["door"], outline=COL["seam"], width=2 * ss)
    k0, k1 = bpx(-0.03, t1 - 0.003), bpx(0.03, t1 - 0.012)
    da.rectangle(box((k0[0], k0[1], k1[0], k1[1])), fill=(90, 91, 94))
    h0, h1 = bpx(hw_ - 0.022, 0.50), bpx(hw_ - 0.006, 0.44)
    da.rectangle(box((h0[0], h0[1], h1[0], h1[1])), fill=(90, 91, 94))
    hw_, (t0, t1) = B["ctrl"]
    p0, p1 = bpx(-hw_, t1), bpx(hw_, t0)
    da.rounded_rectangle(box((p0[0], p0[1], p1[0], p1[1])), radius=6 * ss, fill=COL["black"])
    cy = (p0[1] + p1[1]) / 2
    cx = bpx(-0.1114, 0)[0]
    r = 0.040 * bw_
    da.ellipse(box((cx - r, cy - r, cx + r, cy + r)), fill=(70, 71, 74), outline=(120, 121, 124), width=ss)
    cx = bpx(0.002, 0)[0]
    r = 0.026 * bw_
    da.rectangle(box((cx - r, cy - r, cx + r, cy + r)), fill=(70, 71, 74))
    cx = bpx(0.135, 0)[0]
    da.rectangle(box((cx - 0.036 * bw_, cy - 0.028 * bh_, cx + 0.036 * bw_, cy + 0.028 * bh_)), fill=(70, 71, 74))
    hw_, (t0, t1) = B["panel"]
    p0, p1 = bpx(-hw_, t1), bpx(hw_, t0)
    da.rounded_rectangle(box((p0[0], p0[1], p1[0], p1[1])), radius=5 * ss, fill=(40, 41, 44))
    hw_, (t0, t1) = B["strip"]
    p0, p1 = bpx(-hw_, t1), bpx(hw_, t0)
    rad = (p1[1] - p0[1]) / 2
    da.rounded_rectangle(box((p0[0], p0[1], p1[0], p1[1])), radius=rad * ss, fill=COL["green"])
    de.rounded_rectangle(box((p0[0], p0[1], p1[0], p1[1])), radius=rad * ss, fill=EMIT["green"])
    for key in ("grill_l", "grill_r"):
        (u0, u1), (t0, t1) = B[key]
        p0, p1 = bpx(u0, t1), bpx(u1, t0)
        rows_n = 6
        for i in range(rows_n):
            yy = p0[1] + (i + 0.5) * (p1[1] - p0[1]) / rows_n
            off = 0.5 if i % 2 else 0
            n = 5
            for j in range(n):
                xa = p0[0] + (j + off * 0.5 + 0.1) * (p1[0] - p0[0]) / (n + 0.5)
                xb = xa + 0.7 * (p1[0] - p0[0]) / (n + 0.5)
                da.rounded_rectangle(box((xa, yy - 1.0, xb, yy + 1.0)), radius=ss, fill=(14, 14, 15))

    alb = alb.resize((ATLAS, ATLAS), Image.LANCZOS)
    emi = emi.resize((ATLAS, ATLAS), Image.LANCZOS)
    return alb, emi


# ---------------------------------------------------------------- 材質
def make_material(alb_path, emi_path, strength=1.0):
    mat = bpy.data.materials.new("Hakobu_Mat")
    mat.use_nodes = True
    nt = mat.node_tree
    bsdf = nt.nodes.get("Principled BSDF")
    ta = nt.nodes.new("ShaderNodeTexImage")
    ta.image = bpy.data.images.load(alb_path)
    te = nt.nodes.new("ShaderNodeTexImage")
    te.image = bpy.data.images.load(emi_path)
    te.image.colorspace_settings.name = "sRGB"
    nt.links.new(ta.outputs["Color"], bsdf.inputs["Base Color"])
    nt.links.new(te.outputs["Color"], bsdf.inputs["Emission Color"])
    bsdf.inputs["Emission Strength"].default_value = strength
    bsdf.inputs["Roughness"].default_value = 0.55
    return mat


# ---------------------------------------------------------------- 全体
def build(W, D, H, out_dir):
    prof = json.load(open(os.path.join(HERE, "hakobu_profiles.json")))
    bpy.ops.wm.read_factory_settings(use_empty=True)
    sc = bpy.context.scene
    sc.unit_settings.system = "METRIC"
    sc.unit_settings.scale_length = 1.0

    root = bpy.data.objects.new("Hakobu", None)
    sc.collection.objects.link(root)

    levels = shell_levels(prof, W, D, H)

    # 画面の位置を先に決める（色の画像に使う）
    bm_face = bmesh.new()
    face_faces, face_info = build_face_plate(bm_face, prof, W, D, H)
    finish(bm_face)

    tex_dir = out_dir
    alb, emi = draw_atlas(W, D, H, face_info)
    alb_path = os.path.join(tex_dir, "hakobu_albedo.png")
    emi_path = os.path.join(tex_dir, "hakobu_emission.png")
    alb.save(alb_path)
    emi.save(emi_path)
    mat = make_material(alb_path, emi_path)

    # 本体: 殻・台・車輪・バンパー
    bm = bmesh.new()
    shell = build_shell(bm, levels, W, D, H)
    finish(bm)
    uv_shell(bm, [f for f in bm.faces], W, D, H)
    body_main = new_object("Hakobu_Shell_tmp", bm, None, None)

    bm = bmesh.new()
    build_base(bm, W, D, H)
    finish(bm)
    set_uv_all(bm, bm.faces, swatch_uv("base"))
    ob_base = new_object("Hakobu_Base_tmp", bm)

    bm = bmesh.new()
    build_wheels(bm, W, D, H)
    finish(bm)
    set_uv_all(bm, bm.faces, swatch_uv("black"))
    ob_wheels = new_object("Hakobu_Wheels_tmp", bm)

    bm = bmesh.new()
    build_bumper(bm, levels, W, D, H)
    finish(bm)
    set_uv_all(bm, bm.faces, swatch_uv("black"))
    ob_bump = new_object("Hakobu_Bumper_tmp", bm)

    bm = bmesh.new()
    build_bezel(bm, face_info)
    finish(bm)
    set_uv_all(bm, bm.faces, swatch_uv("rim"))
    ob_bezel = new_object("Hakobu_Bezel_tmp", bm)

    # まとめて1つの本体にする
    body = join_objects("Hakobu_Body", [body_main, ob_base, ob_wheels, ob_bump, ob_bezel])
    body.parent = root
    body.data.materials.clear()
    body.data.materials.append(mat)

    face = new_object("Hakobu_Face", bm_face, root, mat)

    # 輪: 横の面の外側（殻の横幅の一番広い所に合わせる）
    ring_lv = [l for l in levels if 0.38 * H <= l[0] <= 0.62 * H]
    x_side = max(l[1] for l in ring_lv)
    cy_r, cz_r = F["ring_c"][0] * D, F["ring_c"][1] * H
    lr = min(levels, key=lambda l: abs(l[0] - cz_r))
    rf_r = corner_r(lr[0], H, lr[1], lr[3] - lr[2], True)
    rb_r = corner_r(lr[0], H, lr[1], lr[3] - lr[2], False)
    x_side += crown_shift(cy_r, cz_r, lr[2], lr[3], H, rf_r, rb_r)   # 輪の中心で、ふくらんだ殻の面に合わせる
    if PODS:   # 横のふくらみを付けたときは、輪をふくらみの外の面に乗せる
        bm = bmesh.new()
        x_side = build_pods(bm, levels, W, D, H)
        finish(bm)
        set_uv_all(bm, bm.faces, swatch_uv("door"))
        pods = new_object("Hakobu_Pods_tmp", bm)
        body2 = join_objects("Hakobu_Body", [body, pods])
        body2.parent = root
        body2.data.materials.append(mat)
        body = body2
    r_out = 0.5 * (313 / 2312 * H + 313 / 1529 * D)   # 三面図の輪の外の半径（高さと奥行きの間をとる）
    bm = bmesh.new()
    glow, walls = build_ring(bm, W, D, H, x_side, r_out=r_out, r_in=r_out * 261 / 313, proud=0.0018, depth=0.008)
    set_uv_all(bm, glow, swatch_uv("green"))
    set_uv_all(bm, walls, swatch_uv("white"))
    finish(bm)
    ring = new_object("Hakobu_Ring", bm, root, mat)

    # 引き出しと中のトレー4枚
    bm = bmesh.new()
    dinfo = build_drawer(bm, levels, W, D, H)
    finish(bm)
    drawer = new_object("Hakobu_Drawer", bm, root, mat)
    bx0, bx1, by0, by1, bz = dinfo["box"]
    cxs = [(bx0 + (bx0 + bx1) / 2) / 2 + 0.004, ((bx0 + bx1) / 2 + bx1) / 2 - 0.004]
    cys = [by0 + (by1 - by0) * 0.27, by0 + (by1 - by0) * 0.71]
    k = 1
    for cy in cys:
        for cx in cxs:
            bm = bmesh.new()
            build_tray(bm, cx, cy, bz + 0.001)
            finish(bm)
            new_object(f"Hakobu_Tray_{k}", bm, root, mat)  # 入れ子にすると FBX の向きの変換が二重になるので根の直下に置く
            k += 1

    return {"root": root, "levels": levels, "face_info": face_info, "mat": mat, "drawer": drawer}


def join_objects(name, objs):
    bm = bmesh.new()
    for ob in objs:
        tmp = bmesh.new()
        tmp.from_mesh(ob.data)
        me = bpy.data.meshes.new("tmp")
        tmp.to_mesh(me)
        tmp.free()
        bm.from_mesh(me)
        bpy.data.meshes.remove(me)
    for ob in objs:
        me = ob.data
        bpy.data.objects.remove(ob)
        bpy.data.meshes.remove(me)
    me = bpy.data.meshes.new(name)
    bm.to_mesh(me)
    bm.free()
    for p in me.polygons:
        p.use_smooth = True
    me.set_sharp_from_angle(angle=math.radians(35))
    ob = bpy.data.objects.new(name, me)
    bpy.context.scene.collection.objects.link(ob)
    return ob


def stats():
    out = {}
    tot = 0
    for ob in bpy.data.objects:
        if ob.type != "MESH":
            continue
        n = sum(len(p.vertices) - 2 for p in ob.data.polygons)
        out[ob.name] = n
        tot += n
    out["_total_triangles"] = tot
    return out


def export(out_dir):
    fbx = os.path.join(out_dir, "hakobu.fbx")
    bpy.ops.export_scene.fbx(
        filepath=fbx, use_selection=False, object_types={"EMPTY", "MESH"},
        apply_unit_scale=True, apply_scale_options="FBX_SCALE_ALL",
        axis_forward="-Z", axis_up="Y", bake_space_transform=True,
        mesh_smooth_type="OFF", use_mesh_modifiers=True, use_triangles=True,
        add_leaf_bones=False, bake_anim=False, path_mode="STRIP", embed_textures=False,
    )
    glb = os.path.join(out_dir, "hakobu.glb")
    bpy.ops.export_scene.gltf(filepath=glb, export_format="GLB", export_yup=True, export_apply=True)
    return fbx, glb


def main():
    argv = sys.argv[sys.argv.index("--") + 1:] if "--" in sys.argv else sys.argv[1:]
    ap = argparse.ArgumentParser()
    ap.add_argument("--out", required=True)
    ap.add_argument("--W", type=float, default=0.653)
    ap.add_argument("--D", type=float, default=0.760)
    ap.add_argument("--H", type=float, default=1.150)
    ap.add_argument("--render", action="store_true")
    ap.add_argument("--pods", dest="pods", action="store_true", help="横に厚い板を貼る（2026-11-13 にやめた。横がパネルに見えたため）")
    ap.add_argument("--crown", type=float, default=0.010, help="横の面のふくらみ（m）。0 で平ら")
    ap.add_argument("--belly", type=float, default=1.0, help="胴の角だけ、さらに丸める倍率")
    ap.add_argument("--soft", type=float, default=3.5, help="体の角の丸さの倍率（1 で三面図どおり。頭は常に三面図どおり）")
    args = ap.parse_args(argv)
    global SOFT, BELLY, PODS, CROWN
    PODS = args.pods
    CROWN = args.crown
    SOFT = args.soft
    BELLY = args.belly
    os.makedirs(args.out, exist_ok=True)
    info = build(args.W, args.D, args.H, args.out)
    st = stats()
    fbx, glb = export(args.out)
    bpy.ops.wm.save_as_mainfile(filepath=os.path.join(args.out, "hakobu.blend"))
    report = {"size_m": {"W": args.W, "D": args.D, "H": args.H}, "triangles": st,
              "shell_levels": len(info["levels"]), "files": [os.path.basename(fbx), os.path.basename(glb)]}
    json.dump(report, open(os.path.join(args.out, "hakobu_report.json"), "w"), ensure_ascii=False, indent=1)
    print(json.dumps(report, ensure_ascii=False, indent=1))


if __name__ == "__main__":
    main()
````

### make.py に足したサムネイルの並べ方（tools/sim_thumbs/make.py の stills_split と LOG499 の行）

````python
def stills_split(n, a, b, r=0.5, bg=LIGHT):
    """場面の寄り（LOG499〜）：Unity で撮った寄りの静止画2枚（部屋の壁が写るので地は置き換えない）を、それぞれ中心 fx, fy・幅の割合 z で切って左右に並べる。a・b は (素材名, fx, fy, z)"""
    c = canvas(bg); wl = int(W * r) - G // 2; wr = W - wl - G
    for (name, fx, fy, z), w, x in [(a, wl, 0), (b, wr, wl + G)]:
        img = np.array(Image.open(D + name + '.jpg').convert('RGB'))
        h0, w0 = img.shape[:2]; cw = int(w0 * z); ch = int(cw * H / w)
        if ch > h0: ch = h0; cw = int(ch * w / H)
        sx = max(0, min(w0 - cw, int(w0 * fx - cw / 2))); sy = max(0, min(h0 - ch, int(h0 * fy - ch / 2)))
        put(c, cv2.resize(img[sy:sy + ch, sx:sx + cw], (w, H), interpolation=cv2.INTER_AREA if cw > w else cv2.INTER_CUBIC), x, 0)
    save(c, n)

# JOBS の行
    499: lambda n: stills_split(n, ('closeup499_card_side', .52, .40, .50), ('closeup499_take_side', .60, .45, .45), r=.5),  # 受け渡しの場面を直した回（依頼171）。左＝ハコブくんの画面にカードをかざす看護師（横から）、右＝開いた引き出しに上から手を入れて取る所（横から）
````

