さて、「段取り八分、仕事二分」という言葉がありますが、とくに時間的に限られたリソースをうまく回さなければならない週末DIYerとしては、つねに「段取り十分、仕事外注」くらいの勢いでやっていきたいところです。 (外注したらDIYじゃないとか、細かいことは気にせず)
そんなわけで今日は、DIYで木材(SPF材とか)を使うときに、材料をムダなく買ってムダなく使うための上級呪文を紹介したいと思います。
「大きい棚を作りたいけど、部材が多くて材料のカットの割り付けがめんどくさい! 材料買うのは最小限にしたいし、概算金額もバババッと出したい!」 そんな時、この記事がその助けになるでしょう。
定尺の木材、SPF材について
本題に入る前に、主要登場品目の紹介をしておきましょう。
ここで言うSPFってのは、日焼け止めの強さのことじゃなくて、「ツーバイフォー工法」に使われる建材用の木材のことです。
どこのホームセンターでもほとんど必ず置いてあって、流通が多くて安いので、この規格サイズの組み合わせで設計すれば、材料費をけっこう安く抑えることができます。
ちなみに僕は、ワンバイフォーを使うことが多いです。
"仕事外注"(ホームセンター最高)
さてさて、ワンバイフォーを使うていで本棚の設計ができたとします。
続く工程で面倒なのがカットです。 とくに本棚を作るときは部材の直角・直線が命ですが、手鋸で直線をビシッと決めるのはなかなか大変だし、なによりあんまり楽しくないです。
そこで活用したいのがホームセンターの加工サービス。 木材を買って簡単な図を描いて渡すだけで、1カット数十円でサクッと精度よく切ってくれるので、うまく使えば自前の工具がドライバー1本でも、大抵のものは作れてしまうでしょう。
"段取り十分"(材料カットの最適化)
こっちが本題です。 加工はホームセンターに任せるとして、材料が何本いるかとか、カットの割り振りは自分で決めないといけません。
そして、部材が多いほど難しくめんどくさくなっていきます。
その悩みを解決してくれそうなのが「ビンパッキング問題」です。
ビンパッキング問題
概要としては「重さがバラバラなビンを、積載量が決まってるトラックに積んで運ぶとき、トラックの台数が最小で済む組み合わせを求める」っていう問題なんですけど
次のように読み換えると、やりたいことそのまんまって感じですよね!
- トラック→定尺材
- 重さ→長さ
- ビン→部材
ナップサック問題とか、巡回セールスマン問題とかの仲間で、"離散数学の組合せ論の中のNP困難問題" というジャンルだそうです、世の中にはいろんな学問があるんだなぁ。
名前がかっこいいので少し調べてみましたが、正直全然わかりませんでした。まあ、今回は数学世界の探求が目的ってわけじゃないので、使えればよしとします。
ビンパッキング問題を解く強力な呪文「Ortoolpy」
というわけで、難しいところの理解はすっ飛ばして、ラクして簡単にビンパッキング問題を解く方法が知りたい。
そんな堕落したモチベーションをもってGoogle先生に教えを請うたところ、そんな僕にぴったりの、組み合わせ最適化をカンタンに解くためのPythonライブラリを授けてくれました!(厳密にはライブラリではないのかな?)
前置きがだいぶ長くなりましたが、こちらが今日の主役「Ortoolpy」です。
ここからはもう、サクッとツールを使って、バババッと答えを出していきましょう。
(ここから先は、PythonでHello Worldできる程度の知識があれば読み解けると思います。)
ortoolpyのインストール
Ortoolpyのインストールはこのコマンドで一発です。
pip install ortoolpy
使い方
見ての通り、binpacking(入れ物の大きさ,荷物のリスト)
とやるだけです。
from ortoolpy import binpacking resault = binpacking(1820, [780, 560, 780, 780, 600, 580, 580, 780, 800, 800, 1280]) print(resault) # 実行結果:[[1280], [780, 780], [780, 780], [800, 800], [560, 600, 580], [580]]
超簡単ですね、やりたい事があっけなくできてしまいました。
おしまい
ではあまりにあっけないので、ついでに結果をソートしたりとか、金額の見積もりなんかを追加してみました。
あと、リストの並び順によって結果の精度が少し変わるようなので、リストをシャッフルしながら数回繰り返して、一番材料が少なくすむ組み合わせを採用するようにしてみます。
そのコードがこちら。
from ortoolpy import binpacking import random # 材料の定尺、6フィート material_length = 1820 # 材料の単価、ホームセンターで調べる unit_price = 600 # カット単価、これもホームセンターで調べる cut_price = 50 # 鋸の刃幅、ホームセンターの電ノコはだいたい3mmみておけばOK blade_width = 3 # 切り出したい部材の長さリスト、3セットにしてみた parts_list = [78, 56, 78, 78, 60, 78, 560, 780, 780, 600, 580, 580, 780, 800, 800, 1280] * 3 # 計算のために部材それぞれに刃幅を足す parts_list = list(map(lambda l: l + blade_width, parts_list)) # 試行回数 try_count = 5 # 試行結果を入れていくためのリスト resault = [] # 試行回数ぶん回して一番良い結果を採用 for i in range(try_count): # ループごとに部材のリストをシャッフルする random.shuffle(parts_list) # ビンパッキング問題を解いてもらう packed = binpacking(material_length, parts_list) print('{0}回目\t{1}本'.format(i + 1, len(packed))) resault.append(packed) # 結果を表示する print('-----------------------------------------------------') # 材料の本数が少ない順にソート resault.sort(key=lambda x: len(x)) # カットが少ない(リストが小さい)順にソート resault[0].sort(key=lambda x: len(x)) # 必要な材料の本数 material_quantity = len(resault[0]) # 材料金額 material_price_total = len(resault[0])*unit_price # カット回数 cut_count = sum([len(v) - 1 for v in resault[0]]) # カット金額 cut_price_total = cut_count * cut_price print('必要本数:{0}本\t材料小計:{1}円'.format(material_quantity, material_price_total)) print('カット回数:{0}\t加工費小計:{1}円'.format(cut_count, cut_price_total)) print('合計:{0}円'.format(material_price_total + cut_price_total)) print('-----------------------------------------------------') # カットの内訳を表示 for i, stuff in enumerate(resault[0]): # 部材を長い順にソート stuff.sort(reverse=True) stuff_length = list(map(lambda l: l - blade_width, stuff)) print( 'No.:{0}\t部材計:{1}mm\t内訳:{2}\t端材:{3}mm' .format(i + 1, sum(stuff_length), stuff_length, material_length - sum(stuff)) )
実行結果
試行回数は5回にしてみました、部材の数が多いので少しだけ結果にブレがありますね。
その中から狙い通り、材料をより少なくできる組み合わせを選べているようです。
1回目 16本 2回目 15本 3回目 15本 4回目 16本 5回目 15本 ----------------------------------------------------- 必要本数:15本 材料小計:9000円 カット回数:33 加工費小計:1650円 合計:10650円 ----------------------------------------------------- No.:1 部材計:1280mm 内訳:[1280] 端材:537mm No.:2 部材計:1280mm 内訳:[1280] 端材:537mm No.:3 部材計:1280mm 内訳:[1280] 端材:537mm No.:4 部材計:780mm 内訳:[780] 端材:1037mm No.:5 部材計:1638mm 内訳:[780, 780, 78] 端材:173mm No.:6 部材計:1638mm 内訳:[780, 780, 78] 端材:173mm No.:7 部材計:1780mm 内訳:[600, 600, 580] 端材:31mm No.:8 部材計:1780mm 内訳:[580, 580, 560, 60] 端材:28mm No.:9 部材計:1798mm 内訳:[580, 580, 560, 78] 端材:10mm No.:10 部材計:1796mm 内訳:[600, 580, 560, 56] 端材:12mm No.:11 部材計:1756mm 内訳:[800, 800, 78, 78] 端材:52mm No.:12 部材計:1756mm 内訳:[800, 800, 78, 78] 端材:52mm No.:13 部材計:1776mm 内訳:[780, 780, 78, 78, 60] 端材:29mm No.:14 部材計:1772mm 内訳:[780, 780, 78, 78, 56] 端材:33mm No.:15 部材計:1794mm 内訳:[800, 800, 78, 60, 56] 端材:11mm
まとめ
カットのスケジュールと見積もりって実際かなり面倒なので、これはめちゃめちゃ便利です、捗ります。
最近はワンバイフォーでこんな棚を作りました、材料費はビスとかダボとか含めて7千円くらい。
ちなみに、SPFは無垢材なので、塗装して仕上げたほうが耐久性が上がります。
この棚はその後「ビネガーステイン」っていう、ビンテージ風に仕上がる塗装で仕上げたので、その話も今度書こうと思います。