路地裏アジャイル

路地裏アジャイルとは何か?

何でそんなことを考えたのか?

私が「アジャイル向いてない人」だから。

2人でなら話せるけど、3人になると途端に話せなくなる。雑談が超苦手。天気の話から先がつながらない。人への興味が少ないんだと思う。個人と対話大事なのに。
自分中心なので、お客さんのためにってのが正直ピンとこないところがある。顧客満足は最優先なんだけども。
飽きっぽくてモクモクやるのが苦手。ふりかえりとかだんだん億劫になってくる。面倒なタスクは後に回しガチで、泣きながら追い込むことは多い。持続可能なペースはどこいった。

動くソフトウェアは好き。働くソフトウェアかと言われると微妙...。品質もまぁ動けばいいってくらいかな。品質は制御変数じゃないのに!!!

なので、まぁデフォルトとしてはアジャイル向いてない人なのだ。

何でそんな人がアジャイルをやろうと思うのか?

ロマンだから。憧れだから。そこに尽きる。自分にないものに憧れるってありませんかね。
漫画だと炎尾燃。映画だとLotRのフロドやサムとか。熱血、誠実。なんかそういうの。

じゃ、黙ってやってればいいではないのか?

いや、ホントその通り。それができたらいいんだけども...。

アジャイル向いてないから「しつけ」が必要になる。とはいえ、50年弱に渡って叩き込んだ習慣を直すのはとっても大変。
というか、直らない。調子の良い時にちらっと良い習慣が顔を出すことはあっても、疲れるとすぐ素の自分に戻る。
ずっとそれの繰り返し。このままずっとそうなんじゃないかと思う。というか、多分ずっとそうなんだ。

そういう状況だから、

「一生逃げるのもなんだけど、一生進むってのも……こわい」(逆境ナインより)

って感じで行ったり来たりしているワケ。
もちろん黙ってるやれるほどの根性はないから、こうやってブツブツ言いながらウロウロしているのである。

岡崎体育氏のSnackの歌詞がぴったりだ。

ズルしてるライフか無理してるライフ
どっちか選べばって殺生じゃない
ちょっとズルして少し無理したい

そう殺生なのだ。ちょっと弱音吐いて、少し修行したい。いや、だいぶ弱音吐いて、少し修行するくらいにしたい。

面倒なやつだ。結局、どうしたいの?

多分、私は私で勝手にダラダラやっていくんだろう。でも、せっかくだからもうちょっと面白くできないかとは思わないではない。
ツラみを面白くする、笑うって話だと、べてるの家と、水曜どうでしょうを思い出す。(理由はちょっと置いておく)
彼らのツラみに比べると、自分のツラみは小さく、取るに足らないものだろうけど、それでも自分のツラみを解消するために参考になることは多そうだ。

以下はべてるの家で活動している方のエントリ。

「笑う力~ユーモアの大切さ」べてるの家の笑いと当事者研究

病気についてだけではなく、誰もが持っている苦労や生活の工夫を仲間と一緒に話し合うことで、
「弱さ」を自分の中から取り出して、みんなでそれを眺めることができ、
弱さと上手に付き合う新しい工夫が生まれます。
精神障害の幻覚妄想の体験は、時に深刻な話題になりがちです。
しかし当事者研究という場には、いつもユーモアと笑いが絶えません。
ユーモアの定義の一つに、「にもかかわらず笑うこと」があります。
「ユーモア」は、究極の生きる勇気だとも言われています。

弱さと上手に付き合う、にもかかわらず笑う。すごい。
ただ、すごいってことは私にとっては憧れで、アジャイルと一緒ってことだ。だからやっぱり到達できないところにあるんだろう。

でも、まぁいいんだ。弱音吐きながらちょっとずつやるから。

路地裏アジャイルとは何か?

路地裏は、メインストリートに繋がってはいるんだけど、ちょっと薄暗い感じ。
それ、いいよね、わかる、わかるんだけど、ツラいよねぇ。それ。ってやつ。
だから、路地裏アジャイルは、アジャイル、いいよね、わかる、わかるんだけど、
修行ツラいよねぇってことになる。

メインストリート=修行を続ける道に繋がっているんだけど、流れがゆったりしているところ、または止まっているところ。 そこでは修行にちょっと疲れた人が一休みしながら、お互いのツラみやたわいもない話をしている...のかも。
というのが、今のところの認識。今のところだから、これから変わるかもしれないし、変わらないかもしれない。

場所にアジャイルってつけるのは変な気もするけれど、そこはえーと、怒られるだけ怒られておきます。 いい名前が思いついたらそっちに乗り換える...ことは多分しないだろうけども。

今後は、私の行ったり来たりをしているところを見せていくって話になるのかな。正直面倒だけど、まぁいいや。ぼちぼちやろう。

目指せ!? 副業プラモデラー!!!

副業でプラモデラー

副業ということは、お金がもらえるお仕事。軽く調べてみたところ、プラモデル作ってお金をもらう方法はいくつかあるっぽい。 模型雑誌のようなお仕事は私にできるわけがないので、それを除くと、完成品をヤフオクに出す、制作代行をする、くらいになる。

以下は、リスペクトしているモデラーさん達。

gumpla-auction.com

値段はほんと様々。もしかすると稼ぐというよりは、次のプラモを買う資金稼ぎくらいでやってるのかもしれない。 食い扶持にならないとは言え、人様に買ってもらえるくらいのプラモデルを作るというのは、なんかこうちょっと憧れるものがある。

最大の敵は自分

では、私の現在のポジションはと言うと、作ったのが2体、作り中が1体。初心者も良いところである。はるか遠くに見える副業の門を見つつ、一歩を踏み出したという感じ。
更に私は飽きっぽい。継続、コツコツが苦手である。なので、最大の敵は自分の飽きっぽさ。こいつを上手く騙しながら、少しずつ門ににじり寄ってく必要があるのだ。超面倒くさい。でもプラモデラーは小学生の頃からの憧れなのでちょっとだけ頑張ってみる。

自分を騙す3つの方法

そんなわけで、憧れのプラモデラーを目指すべく、現時点での自分の飽きっぽさを上手く騙すために心がけていることをここに書く。誰得?とか思わないわけではないが、書かないとアドベントカレンダーのネタが1個足りなくなるので書く。

心がけているのは主に以下の3つである。

  • 失敗してもガックリが少ないものから始める
  • へたっぴを受け入れる
  • やってる様子をつぷやく

それぞれ説明してみる。

失敗してもガックリが少ないものから始める

これはそのまんま。高いガンプラで失敗するとガックリするので、安いものから始める。
積みプラもあったんだけど、HGUCとMGなので、最初のプラモデルとしてはちとお高い。 そこで選んだのはFG。お値段300円。お安い。パーツも少なく組み上げるのが簡単なので、ちょうど良かった。

FG-01 ガンダムと FG-02 シャアザク

ただ、3体目に選んだ旧キットは失敗だったかもしれない。パーツの精度が最近のキットと比べて低くいらしく、段差が大きく、合わせ目消すのが大変なのである。加工の練習にはいいんだけども…。

旧キット ジムコマンド(宇宙用)

へたっぴを受け入れる

こればっかりは仕方がないというか。上手くなるには、今下手なことは受け入れるしかない。 いつも、下手なのを見るのが嫌で辞めちゃうけど、辞めると上手くならないし。

とは言え、下手ばかり見るのもツラい。が、そこは神様も多少配慮してくれてるようで、素人でも、クリティカルヒットが出る=意外に上手くいくことが稀にあるのだ。

なので、下手なところは仕方ないと割り切りつつ、クリティカルヒットが出た(と思われる)ところを見て、悦に入るようにした…と思う。

合わせ目が消えているのを悦に入る写真。あちこちアラがあるけどキニシナイ

やってる様子をつぶやく

狙ってやってたわけではない。Twitterのつぶやきネタに使えるのでポツリポツリと制作の様子をつぶやいてた。

そしたら、アドバイスをくれたり、いいねをくれたりがあった。もちろん数は少ない、少ないけど、反応があったのが嬉しかった。

あと、写真とか撮ってつぶやいてると、何となく憧れのモデラーさんたちと同じことをやってるんだと嬉しくなったりもする。惚れ惚れとする写真をみて、どうやれば撮れるんだろうと、あれこれブログを漁ってみたり。
良い感じに興味がいろいろ出てき始めて、飽きずに済んでいるような気がする。が、興味なくなるとどうなるんだろ…。

そんなこんなで、今のところは3台、5ヶ月くらいは飽きずにボチボチ続けられている。

まとめ?

最大の敵である自分の飽きっぽさへの対処をまとめてみた。

今は新しいとこばかりで、興味によって飽きを遠ざけてるところも多いので、この先、5体、10体続けていると、似たようなことも増えて飽きがぐっと近寄ってきそうな気もする。ウェザリングとか、改造とか、ちょいちょい新しいものを取り込んでいくのかな?

ちなみに、このエントリを書くのも、飽き対策になってる気がする。ガンプラ作りを、ガンプラが上手に作れるようになるためだけの修行にしてしまうとツラみが増えるので、こんな感じでエントリを書くネタにも使えるよ、と自分を洗脳しているんだと思う。

そうそう、これは自分洗脳の一環なんだな。自分洗脳*1が完了したら、副業にできるかどうかは分からないけど、コツコツ作り続けられるようになってるんだろう。

下地用に買った塗料が到着したから、明日ちょっと吹いてみようっと。

*1:文字通り自分で自分を洗脳する。みうらじゅん氏の『「ない仕事」の作り方』に出てくる言葉

ハトマスクステッカーアプリを作ったよ

ハトマスクステッカーアプリって?

顔や全身が写っている写真をURL、または、ファイルで指定すると、適当な感じでステッカーと合成するアプリ。 Agile Japan 2020 ハトマスクステッカー(未認可・非公式)おみまいするぞ で遊ぶことができる。 Agile Japan 2020というイベントのちょっとしたお遊びとして作ろうと思ったのがきっかけ。

f:id:couger:20201123213035p:plain
ハトマスクステッカー「に」合成

f:id:couger:20201123213023p:plain
ハトマスクステッカー「を」合成

どう作った?

コマンドラインバージョン

最初はコマンドラインで、「ハトマスクステッカー合成」する機能を作ろうとしていた。

f:id:couger:20201123213035p:plain

最初の一歩として、写真の背景を切り抜く処理を探す。なるべく自動化したいので、APIが提供されているものを探して、見つけたのは以下の2つ。

残念だけど両方ともお財布に合わなかったので、別のものを探すことに。でもなかなか見つからない...。自分の財力ではムリがある。 GitHubにないかなぁと "github remove background" でググったら、トピックのリストが見つかった!!!

github.com

なんとなく使ってる人の多そうな danielgatis/rembg: Rembg is a tool to remove images background. を使うことにした。 GitHubスゴい。OSSスゴい。

説明を見ながらCLIで実行。キレイに切り抜きができた!!! スゴい。 更に、ステッカーに合成する簡単なプログラムを見よう見まねでPythonで作り実行。上手くいった!
※ RembgがPythonだったのでそれに合わせた。

その時、作ったコードが以下である。

curl -s $1 | rembg > work/target.png
python hatomask-gosei.py work/target.png
import sys
from PIL import Image

def resize_image(original, width):
  return original.resize((width, (int)(width * original.size[1] / original.size[0])))

hatomask = Image.open('hatomask-aj2020.png')

pathToTarget = sys.argv[1]

target = resize_image(Image.open(pathToTarget), 300)

hatomask.paste(target, (1024-300, 600), target)
hatomask.save('omimaisuruzo.png')

これで、合成したい写真のURLを指定すれば、ハトマスクステッカーに合成ができるようになった。ここで辞めておけばよかったのに、Webアプリにしたい欲求が湧いてきてしまう。
そういえば、最近プログラム全くしてなかったし。Azureも久しぶりに使ってみようかと軽い気持ちで始めたら、結局土日2日を潰すことになった。思いつきはだいたい高くつく...。

Webアプリにする

まずは軽いWebアプリフレームワークと、CSSフレームワークを探す。選んだのは以下の2つ。

初めて触るフレームワークだったので、以下のような感じで1ステップずつ進めていった。都度、App Serviceにデプロイして動作確認をしていたと思う。

  • 画面を表示する
  • 画像のURLが指定できるようにする
  • 指定したURLの画像を表示する

この後、Rembgを使った「指定したURLの画像の背景を切り抜く」処理を追加、App Serviceにデプロイしようとするとエラーが出た。 数回試したけどダメ。エラーメッセージにはメモリがないとかディスクのスペースがないとか出てくる。仕方がないので、プランをFreeから、B1にグレードアップ。やっぱり失敗する。

泣く泣くP1v2にして、デプロイが成功。ただし、デプロイに10分くらいかかる。ツラい。1ヶ月使うと1万円かかる。ツラい。
※ S1にしなかった理由は、P1v2と比べて1000円くらいしか違わないから。なんとなく性能の問題のような気もしたし。

とはいえ、のんびり調査している暇もないので、そのまま開発を続けて最初のバージョンをリリースした。 リリースした後「ハトマスクステッカーを合成」する方がニーズあるんじゃないかな? と思い、急遽機能を追加、次の日に再度リリース。

最終的なコードは以下(アプリ部分のみ)。

import sys
import io
import base64
import urllib.request
import hashlib
from PIL import Image
from flask import Flask, render_template, request
from rembg.bg import remove
app = Flask(__name__)

app.config['MAX_CONTENT_LENGTH'] = 10 * 1024 * 1024

def resize_image(original, width):
  return original.resize((width, (int)(width * original.size[1] / original.size[0])))

def crop_image(original):
  # https://stackoverflow.com/questions/14211340/automatically-cropping-an-image-with-python-pil/51703287
  imageSize = original.size
  imageBox = original.getbbox()

  imageComponents = original.split()

  rgbImage = Image.new("RGB", imageSize, (0,0,0))
  rgbImage.paste(original, mask=imageComponents[3])
  croppedBox = rgbImage.getbbox()

  return original.crop(croppedBox)

def convert_image_to_base64(image):
  image_buffered = io.BytesIO()
  image.save(image_buffered, format="PNG")
  return base64.b64encode(image_buffered.getvalue()).decode("utf-8")

def remove_background(imageStream):
  r = lambda i: i.buffer.read() if hasattr(i, "buffer") else i.read()
  w = lambda o, data: o.buffer.write(data) if hasattr(o, "buffer") else o.write(data)

  result = io.BytesIO()

  w(
    result,
    remove(
      r(imageStream),
      model_name="u2net",
      alpha_matting=False,
      alpha_matting_foreground_threshold=240,
      alpha_matting_background_threshold=10,
      alpha_matting_erode_structure_size=10,
    ),
  )

  return Image.open(result)

def hatomask_omimaisuruzo(target):
  result = Image.open('static/images/hatomask-aj2020.png')
  target_resized = resize_image(target, 300)
  result.paste(target_resized, (1024-300, 600), target_resized)
  return result

def sticker_ga_ikuzo(target):
  sticker = Image.open('static/images/hatomask-aj2020-rembg.png')
  sticker_resized = resize_image(sticker, (int)(target.size[0] / 3))

  paste_lefttop = (target.size[0] - sticker_resized.size[0], target.size[1] - sticker_resized.size[1])
  target.paste(sticker_resized, paste_lefttop, sticker_resized)
  return target


@app.after_request
def add_header(r):
    r.headers["Cache-Control"] = "no-cache, no-store, must-revalidate"
    r.headers["Pragma"] = "no-cache"
    r.headers["Expires"] = "0"
    r.headers['Cache-Control'] = 'public, max-age=0'
    return r

@app.route("/")
def hello():
  return render_template('aj2020.html')

@app.route('/sareruzo_url', methods=['POST'])
def sareruzo_url():
  imageUrl = request.form.get('imageUrl')
  f = io.BytesIO(urllib.request.urlopen(imageUrl).read())
  target_removebg = remove_background(f)
  target_cropped = crop_image(target_removebg)

  hatomasked = hatomask_omimaisuruzo(target_cropped)
  return render_template('aj2020-omimaishitazo.html', imageBase64=convert_image_to_base64(hatomasked))


@app.route('/sareruzo_upload', methods=['POST'])
def sareruzo_upload():
  if 'imageFile' not in request.files:
    return render_template('aj2020-omimaishitazo.html', imageFile="images/hatomask-aj2020.png")

  imageFile = request.files['imageFile']
  imageFileName = imageFile.filename

  if '' == imageFileName:
    return render_template('aj2020-omimaishitazo.html', imageFile="images/hatomask-aj2020.png")

  target_removebg = remove_background(imageFile.stream)
  target_cropped = crop_image(target_removebg)

  hatomasked = hatomask_omimaisuruzo(target_cropped)
  return render_template('aj2020-omimaishitazo.html', imageBase64=convert_image_to_base64(hatomasked))

@app.route('/suruzo_url', methods=['POST'])
def suruzo_url():
  imageUrl = request.form.get('imageUrl')
  imageFile = Image.open(io.BytesIO(urllib.request.urlopen(imageUrl).read()))

  hatomasked = sticker_ga_ikuzo(imageFile)
  return render_template('aj2020-omimaishitazo.html', imageBase64=convert_image_to_base64(hatomasked))


@app.route('/suruzo_upload', methods=['POST'])
def suruzo_upload():
  if 'imageFile' not in request.files:
    return render_template('aj2020-omimaishitazo.html', imageFile="images/hatomask-aj2020.png")

  imageFile = request.files['imageFile']
  imageFileName = imageFile.filename

  if '' == imageFileName:
    return render_template('aj2020-omimaishitazo.html', imageFile="images/hatomask-aj2020.png")

  hatomasked = sticker_ga_ikuzo(Image.open(io.BytesIO(imageFile.read())))
  return render_template('aj2020-omimaishitazo.html', imageBase64=convert_image_to_base64(hatomasked))

ハマったところは?

Azure App ServiceのFreeプランでは動かない

B2プランなら大丈夫。Freeプランでは、Rembgを動かすためのリソースが足りないのだろうと推測。

やりたい処理によって画像ファイルを扱うクラスを変える必要がある

コピペ優先でホイホイ作ってたから、この辺を理解しておらず、追加機能を作る際に苦労した。
ちなみに以下の3つを覚えておけば多分いいはず。(それくらい最初に調べとけってツッコミは甘んじで受けます...)

  • アップロードされたファイル: werkzeug.datastructures.FileStorage
  • 画像の透過、貼り付け: PIL.Image
  • Rembgを使った背景除去: io.BytesIO

今後は?

ステッカーの種類を増やしたり、位置や大きさを変えられるようにしようかなーと思ってたりするんだけども。さて。

ガンプラ作りとイテレーティブ、インクリメンタル

アジャイルでよく聞く言葉にイテレーティブと、インクリメンタルというものがある。

インクリメンタルでもイテレーティヴでも同じものを目指しているように見えるが、 インクリメンタルな開発は顧客が満足しないものを作ってしまうリスクが軽減されていない。 大きな絵はプロジェクトのいちばん最後にしか見ることができない。 インクリメンタル開発で細部まで作りこんでしまうと、修正が発生した場合に、多くの努力が無駄になってしまう。 イテレーティヴな開発は開始時点からの絵の変化を見ることができる機会を提供している。 そして一歩ずつ全体の絵の完成に向けて進んでいくのをガイドしてくれる。 イテレーティヴとインクリメンタルの違い | Ryuzee.com

https://www.ryuzee.com/contents/blog/images/2985/mona-lisa.png

最近ガンプラ作りを始めたのだけど、その中で、インクリメンタルとイテレーティブを実感したので、説明してみるテスト。何を今更お前は...というツッコミは気にしないことにする。

1体目 - 1/144 FG-01 RX-78-2 ガンダム

サフを吹いてパチ組*1しただけのモデル。これにはインクリメンタルもイテレーティブもない気がする。

f:id:couger:20201123113636j:plain
FG-01 ガンダム サフ+パチ組。サフ吹くだけで悦に入れるのはなぜだろう?

2体目 - 1/144 FG-02 MS-06S シャア・アズナブル専用 ザクII

合わせ目消し+全塗装にチャレンジした。いきなりハードルを上げすぎた気はするが、やりたかったんだから仕方がない。 最初は、パーツごとに合わせ目消し+塗装まで行い、完成させていた。

f:id:couger:20201123113815j:plain
FG-02 シャアザクの太もも。太もも。

合わせ目消しと塗装は超久しぶりで、どんな道具を使い、どういう風に、どの程度までやるのが自分に合っているのかさっぱりわからない。 私はカジュアルにくじけるタイプなので、途中でくじけないように、モチベーションを維持することがとっても大切。 この時は「一部でも完成しているもの」を見て「悦に入る」ことで、モチベーションを維持していた。

当然、最初と最後でやり方も練度も異なるので、できあがりの精度が(へたっぴレベルで)マチマチ。 最初の方に作ったパーツは、塗装を削って、合わせ目消しをやり直すようなこともあった。

全体的に見ると、非効率極まりない気はするが、それよりも、自分のモチベーションの維持が大事だったので、これはこれで良かったんだと思う。 最後は、せっかくだからと、撮影ブースを自作して撮影してみたり。ガンプラ沼も広くて深い。ヤバい。

f:id:couger:20201123113844j:plain
FG-02 シャアザク。撮影ブースで。(塗り分けはギブアップしております)

3体目 - 1/144 RGM-79GS ジム・コマンド [宇宙用] (旧キット)

HGUCに行く前に練習と思って買ったわけだけど、あれこれ見てると旧キット作る方が難しいっぽい...。よくこの手の間違いを犯すんだけど、仕方がない。

今回は、あとハメ*2+合わせ目消し+全塗装にチャレンジ。シャアザクで合わせ目消しや、塗装の経験があったせいか、ちょっと作り方が変わった。作り方というか「悦の入り方」が変わったというのが正しいだろうか?

具体的には、パチ組で悦に入り、あとハメ加工したあと組み上げて悦に入り、合わせ目消しした後組み上げて悦に入り...。という流れ。各工程は(工夫の余地はたくさんあれど)やり方が分かってきたので、区切りの良いところで、組み上げて全体的なものを見て悦に入るという感じ。 まだ合わせ目消しの途中だけど、多分、この後は試し吹きして組み上げ、合わせ目修正+スジ彫りして組み上げ、塗装して組み上げ、という流れになると予想される。

f:id:couger:20201123113858j:plain
ジム・コマンド [宇宙用] あとハメ+合わせ目消しして組み上げたやつ。ポーズが気に入っている

で、何が言いたいの?

ようやく、タイトルの話になるんだけども、2体目の組み方がインクリメンタル、3体目の組み方がイテレーティブなんじゃないかなと。 で、インクリメンタルとイテレーティブでは「悦の入り方」が違うのかもなーと思ったというお話でおしまい。 仕事に使えるかどうかはさっぱり分からない。が、まぁ、そこはいいのである。

とはいえ、最初から3体目のような悦の入り方で進めるのは、私には無理そう。手馴れたものだとイテレーティブにできるよねってだけかな?

*1:スナップフィットモデルを接着、塗装など行わずパチパチと組み立てたもの https://twitter.com/ravensnest8525/status/894866675954089984

*2:組み立て後に分解できなくなるパーツを、組み立て後もパーツ同士をハメたり外したりできるように加工する工作 https://yzphouse.com/gunpla-kaizo-atohame

ハトマスクおみまいするぞアプリを作ってみたよ

機能

  • 顔を検出すると見境無くハトマスクをかぶせるよ
  • 顔の向きに合わせてハトマスクをかぶせるよ
  • たくさん顔があってもかぶせるよ
  • かぶせるマスクを変えることもできるよ
    • f:id:couger:20191223193207p:plain
  • リアルタイムでかぶせるよ
  • 保存もできるよ
  • 今の所は iPhone でのみ動くよ
    • UnityだからAndroidの対応も楽なんじゃないかなぁと期待しているよ

きっかけ

使ったツール

使った時間

  • 原型作るのは1週間だったよ
    • 顔の向きにある程度合わせてマスクが出るところまで作ったよ
    • dlibのサンプルプログラムを改変したらできるかなと安易に考えてたけど、Unityさっぱりわからないので結局チュートリアルから始めたよ
    • マスクのディティールアップに1日使ってたりするよ
  • その後、コードの整理や顔の向きの調整、スマフォの縦向き、横向きに対応してたら2ヶ月経ってたよ
    • 自分の時間にやってる(ので平日は1時間くらい、休日は5,6時間)とはいえ、そんなにかかるとは思ってなかったよ
    • 原型はコピペで作ったんだけど、細かい調整するためには理屈をある程度理解する必要があったので苦労したよ
    • コードは整理というかほぼ書き直したよ
  • 理屈が先か、動かすのが先か悩むけど、私は最初に理屈を求めるとメゲるので、動かすのが先派だよ

ちなみに右往左往はScrapboxにメモしたよ。
フォーマットあんまり気にせずガシガシ書けるから実況に使うと捗るよ。
ハトマスクをおみまいするぞアプリ - のび犬.メモ

今後

  • AppStoreに出すのが目標だよ
  • まずはβ版を出してみるので、フィードバックもらえると嬉しいよ
    • iPhoneだとTestFlightってアプリを入れる必要があるのでちょっと敷居が高いかもしれないよ
  • ハトマスクはアーチーマクフィーのものを参考にしてるので、許可を取ろうと思っているよ
    • 実はお問い合わせから打診のメールを送ったんだけど、全く相手にされなかったよ...。
      • ぶっきらぼうなメールだったので、怪しいメールで終わっちゃったんだと思ってるよ

Azure FunctionsでAzure Storageに置いたExcelファイルを取ってきて、編集する

やりたいこと

シフト表を作りたい。

  • シフト表のテンプレートはAzure Storageに置いている。
  • スケジュールの調整は、スケジュール調整サービス(調整さんとか、TONTONとか)でやっている。

スケジュール調整サービスから結果を取り出し、シフト表のテンプレートに反映して、ダウンロードしたい。

やったこと

とりあえず、Azure Functionsで、ExcelファイルをAzure Storageから取ってきて、ダウンロードできるところまでを作ってみる。
ExcelJavaScriptで操作するライブラリは exceljs を使ってみた。

github.com

コード

特に変わったことはしていない。
ハマったところは、function.json"dataType": "binary" を設定するところ。設定しないと workbook.xlsx.load でコケてしまう。

index.js

const Excel = require('exceljs');

module.exports = async function (context, req) {
    context.log('JavaScript HTTP trigger function processed a request.');

    var workbook = new Excel.Workbook();
    await workbook.xlsx.load(context.bindings.shiftScheduleTemplate);

    context.res.setHeader("Content-Type", "application/vnd.ms-excel")
    context.res.setHeader("Content-Disposition", "attachment; filename=ShiftSchedule.xlsx");
    var buff = await workbook.xlsx.writeBuffer();
    context.res.body = buff;
};

function.json

{
  "bindings": [
    {
      "type": "blob",
      "name": "shiftScheduleTemplate",
      "direction": "in",
      "dataType": "binary",
      "path": "assets/shiftschedule-template.xlsx",
      "connection": "Assets_STORAGE"
    }
  ]
}

コツコツが苦手な人が何をコツコツやれるのか? って探してんのかも

コツコツが苦手な人が何をコツコツやれるのか? って探してんのかもね。

コツコツではなく、ダラダラでもやるだけマシ、ついついやっちゃうものは自分が気づいてないか、気づいててもその使い方が分かんない。

ダラダラは3日坊主を月3回やったら、1/3くらいできてるのでよし。とか、ハードル下げとくのと、好きなことを絡めること。好きなもので解釈したり、好きなもので練習したり。(好きでもコツコツできないものもあるだろう)

ついついやるのは、なんらかの理由によって自分にとっては必要なもの。と考えると、その理由がわかれば、同じニーズや課題を持つ人を助けられるかもしんない。

なかなか上達しない自分を許す、自分の「ついついやる」をのんびり掘ってみる。

単にせっかちでのんびり続けるにはで終わりそうな気もしてきた。

ただ、せっかちも使いどころなので、なくすともったいない。そもそもそんなに簡単に治らんw

変化が速い(気がする)コミュニティと、ゆっくりな(ような気がする)会社にいるので、両方を体験はできてるのかもな。 子育てとかもゆっくりだ。

この辺は速度の違う複数の環境に身を置いてると言える。どれもできない、中途半端な立ち位置にいるのが、オレだとすれば「情報生産者になる」の「境界人」になりえるかもしんない。

んなこと言ってる間にやればいいって話はあるんだけども、やれるんだったらこうなってないので、こういう風に文字に起こしながら、自分の納得をゆっくり育ててる。

あー、せっかちで頑固ってことか。うわ、面倒。 ま、それも使いどころってことで。