文化祭で開発をしたけど使わなかったので供養

:::note alert
本記事は個人的な開発の記録や発見の共有を目的として執筆したものです。
学校及び学校関係者への問い合わせはご遠慮ください。
:::

# コード

https://github.com/sasasou/label-system

# これまでの高校生活
去年の文化祭でもぐもぐ先輩が何やらすごそうなものを作っていて感動しました。(詳しくは記事を見てください)

https://qiita.com/YumNumm/items/32ffcced7193e11930d9#2023612

今年度クラスの文化祭担当になって食品販売をすると決まった時、自分も何か作りたいと思い立って始めたのがこのシステムです。
~~思い立ったはいいものの筆者にはプログラムを書いた経験がなかったのは内緒~~


# やりたかったこと
* 某ス◯ーバッ◯スのカップに貼ってあるラベルを再現
* 会計の間違いを極力減らすため、注文受付と会計を分離(食品の種類や個数を確認してもらってから会計をする)
* 食品の種類が多いため、注文を正確に食品担当に伝達する


# 使用したもの
### Brother ピータッチQL-650TD[(参考)](https://www.brother.co.jp/product/labelprinter/ql650td/index.aspx)
Brotherのラベルプリンターです。ヤフオクで5000円くらいで売ってます。
###  b-PAC
Brotherが配布している、プリンターを制御することができるSDKです。使用する機種によって、対応するバージョンやbitが異なるので気を付けてください。[(動作環境)](https://support.brother.co.jp/j/s/es/dev/ja/bpac/environment/index.html?c=jp&lang=ja&navi=offall&comple=on&redirect=on)
### Flet
Pythonのみでフロントエンドを実装できるすごいフレームワークです。

# 環境構築
使うラベルプリンターのドライバとb-PACをダウンロードしてください。先述しましたが、b-PACのバージョンには気を付けてください~~私はここで三日間沼にはまった~~。
あとはPythonとpipでflet、pywin32をインストールしたら多分動くと思います。Pythonの実行ファイルと印刷するlbxファイルは同じディレクトリ階層においてください。

# 実際の運用
1\. 受付担当は客の注文内容と客に渡す整理券番号を入力し、注文ボタンを押す
2\. 注文内容と整理券番号が記載されたラベルが印刷される
3\. 食品担当はラベルを見て食品を集め、ラベルを貼って会計担当に渡す
4\. 会計担当はラベルに記載された整理券番号を呼び出し、会計をして食品を渡す

# 仕組み
## ラベルの内容変更
```python:change 
         # チェックボックスのvalueを取得
        is_kanetu = kanetu.value  # 加熱希望のチェックボックスのvalueを取得

        # 各チェックボックスのvalueを取得
        cb1_value = slider1.value
        cb2_value = slider2.value
        cb3_value = slider3.value
        cb4_value = slider4.value
        cb5_value = slider5.value
        cb6_value = slider6.value
        cb7_value = slider7.value
        cb8_value = slider8.value
        cb9_value = slider9.value
        cb10_value = slider10.value
        
        #整理券番号の取得
        dd_value = dd.value

        # 必要なライブラリをインポート
        import xml.etree.ElementTree as ET
        import datetime
        import os
        
        #lbxを複製する
        import shutil
        shutil.copyfile(r"複製元のファイルのパス", r"複製したファイルのパス")
        
        def change_extension(filename, new_extension):
            base_name = os.path.splitext(filename)[0]
            new_filename = base_name + new_extension
            return new_filename

         # 変更したいファイルのパスと新しい拡張子を指定
        old_file_path = r"複製したファイルのパス"
        new_extension = ".zip"
        # 新しい拡張子を持つファイル名を生成
        new_file_path = change_extension(old_file_path, new_extension)
        # あたらしいやつが存在したら　消す
        if os.path.exists(new_file_path):
            os.remove(new_file_path)

        # ファイルをリネームして保存
        os.rename(old_file_path, new_file_path)

        #zipフォルダを解凍
        import zipfile
        with zipfile.ZipFile(new_file_path) as existing_zip:
            existing_zip.extractall(r"解凍後のフォルダのパスを指定")
        # XMLファイルを読み込む
        file_path = r"解凍後のフォルダのlabel.xmlファイルのパス"
        tree = ET.parse(file_path)
        root = tree.getroot()

        #商品リスト
        coffee_list=[[1,"カフェラテ"],
        [2,"ノンスイート"],
        [3,"カカオミント"],
        [4,"クリーミーラテ"]]
        pan_list=[[5,"焼きチーズもち"],
        [6,"オールドファッション"],
        [7,"ミニラスク"],
        [8,"チョコパイ"],
        [9,"よこすかメイプルメロンパン"],
        [10,"よこすか海軍カレーパン"]]
        
        #変数リスト
        coffee_hennsuu=[cb1_value,cb2_value,cb3_value,cb4_value]
        pan_hennsuu=[cb5_value,cb6_value,cb7_value,cb8_value,cb9_value,cb10_value]
        
        # 置換する文字列を指定
        old_string1 = "TimeTimeTime"
        from datetime import datetime

        date = datetime.now()
        # 2023/08/01 09:00:00
        time_str = date.strftime('%H:%M')
        new_string1= str(time_str)

        old_string2="Number"
        new_string2=str(dd_value)

        old_string3="coffee1coffee1coffee1"
        old_string4="coffee2coffee2coffee2"
        
        if coffee_hennsuu.count(0)<=2:
            for i in range(4):
                if coffee_hennsuu[i]>=1:
                    new_string3=coffee_list[i][1] + " " +str(coffee_hennsuu[i])+"個"
                    coffee_hennsuu[i]=0
                    break
            for i in range(4):
                if coffee_hennsuu[i]>=1:
                    new_string4=coffee_list[i][1] + " " +str(coffee_hennsuu[i])+"個"
                    break 
        elif coffee_hennsuu.count(0)==4:     
            new_string3=""
            new_string4=""
        else:    
            for i in range(4):
                if coffee_hennsuu[i]>=1:
                    new_string3=coffee_list[i][1] + " " +str(coffee_hennsuu[i])+"個"
                    new_string4=""
                    break

        
        old_string5="pan1pan1pan1pan1"
        old_string6="pan2pan2pan2pan2"
        new_string5=""
        new_string6=""
        if pan_hennsuu.count(0)<=4:
            for i in range(6):
                if pan_hennsuu[i]>=1:
                    new_string5=pan_list[i][1] + " " +str(pan_hennsuu[i])+"個"
                    pan_hennsuu[i]=0
                    break
            for i in range(6):
                if pan_hennsuu[i]>=1:
                    new_string6=pan_list[i][1] + " " +str(pan_hennsuu[i])+"個"
                    break 
        elif pan_hennsuu.count(0)==6:     
            new_string5=""
            new_string6=""
        else:    
            for i in range(4):
                if pan_hennsuu[i]>=1:
                    new_string5=pan_list[i][1] + " " +str(pan_hennsuu[i])+"個"
                    new_string6=""
                    break
        old_string7="rennzi"
        if is_kanetu:
            new_string7="加熱希望"
        else:
            new_string7="加熱なし"

        # XMLのテキスト要素を検索して置換
        for elem in root.iter():
            if elem.text is not None:
                elem.text = elem.text.replace(old_string1, new_string1)
                elem.text = elem.text.replace(old_string2, new_string2)
                elem.text = elem.text.replace(old_string3, new_string3)
                elem.text = elem.text.replace(old_string4, new_string4)
                elem.text = elem.text.replace(old_string5, new_string5)
                elem.text = elem.text.replace(old_string6, new_string6)
                elem.text = elem.text.replace(old_string7, new_string7)
                
                elem.text = elem.text.replace(old_string1+"Len", str(len(new_string1)))
                elem.text = elem.text.replace(old_string2+"Len", str(len(new_string2)))
                elem.text = elem.text.replace(old_string3+"Len", str(len(new_string3)))
                elem.text = elem.text.replace(old_string4+"Len", str(len(new_string4)))
                elem.text = elem.text.replace(old_string5+"Len", str(len(new_string5))) 
                elem.text = elem.text.replace(old_string6+"Len", str(len(new_string6)))
                elem.text = elem.text.replace(old_string7+"Len", str(len(new_string7)))

        tree.write(file_path,encoding="utf-8")

        print("指定した文字列を置換しました。")
        # 既存のZIPファイルを消去する　存在する場合
        if os.path.exists(r"C:\Users\Agepa\Desktop\starprint.zip"):
            os.remove(r"C:\Users\Agepa\Desktop\starprint.zip")
        #label.xmlを圧縮する
        
        os.system('powershell -Command Compress-Archive -Path C:\\Users\\Agepa\\Desktop\\star1\\label.xml -DestinationPath C:\\Users\\Agepa\\Desktop\\starprint.zip')

        def change_extension(filename, new_extension):
            #時間をUNIX時間で取得
            base_name = os.path.splitext(filename)[0]
            new_filename = base_name + new_extension
            return new_filename
        #zp.close()

        
        # 変更したいファイルのパスと新しい拡張子を指定
        
        old_file_path = r"C:\Users\Agepa\Desktop\starprint.zip"
        new_extension = ".lbx"

        # 新しい拡張子を持つファイル名を生成
        new_file_path = change_extension(old_file_path, new_extension)
        # あたらしいやつが存在したら　消す
        if os.path.exists(new_file_path):
            os.remove(new_file_path)

        # ファイルをリネームして保存
        os.rename(old_file_path, new_file_path)
```
このコードは以下のような流れになっています。
1\. チェックボックスと整理券番号のvalueを取得
2\. テンプレートの.lbxファイルを複製
1\. brotherのラベルファイルの拡張子.lbxを.zipに変更し解凍
2\. 解凍後のフォルダからlabel.xmlというファイルを開く
3\. 商品名とvalueをリストに格納
4\. valueが1以上になったときのみ、そのvalueの商品名にold_string(テンプレートのテキスト)を変更
4\. label.xmlを圧縮して.zipを.lbxに変更


:::note warn
置換するテキストは、old_stringの文字数までという制限があります。
超えた場合は、超えた文字数分だけ変更が反映されません。
そのためold_stringはできるだけ長くしておくことを推奨します。
:::

## ラベルの印刷
```python:print
import win32com.client
        import os
        import pythoncom
        pythoncom.CoInitialize()
        doc = win32com.client.DispatchEx("bpac.Document")
        # プリンタ名のリストを取得
        printers = doc.Printer.GetInstalledPrinters

        if not printers:
            print("利用可能なプリンタが見つかりません")
        else:
            # リストの最初のプリンタ名を選択
            selected_printer = printers[0]
            doc.SetPrinter(selected_printer, True)

        dir = os.path.abspath(os.path.dirname(__file__))
        lbx_path = os.path.join(dir, "starprint.lbx")
        hasOpened = doc.Open(lbx_path)

        doc.StartPrint("", 0x04000000)

        doc.PrintOut(1, 0x04000000)

        doc.EndPrint
        doc.Close
        #印刷終了
```
主に参考記事のものをそっくりそのまま使わせていただいています。Brother_SDKはCOMコンポーネントというものらしく、PythonにはCOMを直接扱う機能がないため、pywin32というライブラリを使います~~自分もよくわかっていない~~。印刷のコマンドをたたいてからプリンターで印刷が始まるまで数秒のラグがあるのでちょっと焦ります。
## フロント
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3530518/4061640c-0992-1eca-5c37-e323547898db.png)
商品名の横のスライダーを動かして個数を調整します。一番下の注文ボタンをクリックすると印刷が開始されます。


# このプログラムの問題点
## ファイルの扱い
ファイルの保存先をすべてデスクトップにしていたため、自分の環境では何も起きなかったが環境によってはバグる可能性がある。またファイルを複製して印刷後消すという手段をとっていたため、何かのはずみでファイルが消去されずに次の操作がされた場合大変なことになりそう。
## 複数端末からの操作
印刷にBrother_SDKとプリンタドライバが必要なのとfletをローカルホストして動かしていた関係で、端末を複数台使用することを想定してなかった。もしかしたら分岐ケーブルを使って環境構築済みのPCを複数台
プリンタに接続すれば動くかもしれないけど未確認です。

# 経緯
### 6月
なんとなーーーーーーくラベル発行システムを作りたいなと思い立つ。

### 7月
7月2日にヤフオクでプリンターを落札して喜んでた。
7月中旬にもぐもぐ先輩と話して、大まかなシステムの構成を固める。
7月末にはb-PACを使った印刷に成功。

### 8月
この辺は本業の文化祭準備と副業の受験生が忙しくて全然進まなかった。

### 9月
完全に焦る。
2日くらいに初めてPOSに存在を知る。
文化祭当日の0時時に完成~~この時重大な欠陥があることに気づいていない~~。
結局このシステムは使われず文化祭終了。


# なぜこのプログラムは使われなかったのか
一つ目の理由として、開発の遅延があります。完成したのは文化祭当日の0時頃かつ、朝学校に行って動かすとバグが発生しました。ラベルに商品名がすべて印刷されず、途中で途切れるというものです。文化祭一日目終了後にマックでダラダラしていて解決策を思いつきました。もっと早くに完成をさせ試運転をしておけばこんなことにはならなかったと思います。
二つ目にこのプログラムの開発目的として、注文内容を食品担当と会計担当に伝達するということがありました。しかし私の高校の優秀な**社会科教員**の手によって、スプレッドシートを用いたPOSシステムが生み出されてしまいました。このシステムは、注文受付と注文内容の伝達、会計画面のついたとても優秀なものでした~~会計画面が重いけど~~。そのためラベルが不要になってしまいました。一つ目の理由に関連しますが、このシステムの存在を初めて知った一週間前に完成していればラベル印刷を組み込めたかもしれません。

# 改良したかった点
## UI
現状では注文ボタンを押しても商品の個数がリセットされません。また注文ボタンを複数回クリックすると、クリックした回数だけ印刷されてしまいます。
## 複数端末からの操作
b-PACのヘルプかどっかにプログラムをサーバーにおいて、クライアント端末から操作するときの方法が紹介されていたのですが、難解すぎて諦めました。

# 感想
今までの人生ではプログラムを書くということに興味は持つものの、時間がなかったり作りたいものがないなどの理由で手を付けてきませんでした。しかし人生最後の文化祭でこのようなことができたのはとても楽しかったですし、新しく何か役に立つものを作ってみたいと考えるきっかけにもなりました。

# 謝辞
フロントの実装をほぼ丸投げしたにもかかわらず、とても早くやってくれたのぶ
こんがらがったコードの中身を説明したところ、競プロの知識を生かしうまくリスト化してくれたHaru[(Twitter)](https://x.com/haru_astro_?s=20 )
開発を思い立ったころからたくさん相談に乗ってくれたもぐもぐ先輩[(Twitter)](https://x.com/YumNumm?s=20 )
運営全体について振り回してしまったクラスの文化祭担当の3人及びクラスの人達
忙しい文化祭準備と並行して便利なPOSを作ってくださった社会科教員

本当に感謝しています!!!!!!!!!!
私の努力が足りなかったばかりに、このシステムを使うことができなかった点について深く反省と謝罪をし、筆をおきたいと思います。

# 参考にさせていただいた記事
https://qiita.com/YumNumm/items/32ffcced7193e11930d9#2023612

↑文化祭開発をしようと思った原点

https://note.com/sqm/n/nce64247ed4f0

↑この記事を書いている最中に発見しました。テキストの変更についてわかりやすく説明されています。

https://qiita.com/flowphantom/items/8a15c191940ffbce78d1

https://thinkami.hatenablog.com/entry/2016/05/11/061626