Look at the Bright side of things

Anderson's Blog - since 2005

PythonからPDF帳票の出力を試みる

業務用システムには欠かせない、明細一覧表。これを作りたいために社長にパソコンをおねだりした。従来は注文伝票の束をどかっと渡されて、それが自体が作業指示票となっていたのだが、当然ながら伝票を一枚一枚めくって、分類、集計の上、作業段取りを決めることになり、極めて非効率だった。
上長から作業を引き継いだ際、なんでこんな無駄なことに疑問を持たずに何十年も仕事していられるのか、不思議でしょうがなかった。
パソコンを受領したあと、LibreOfficeのBaseでデータベースを構築して、クエリーを作り、それをもとに明細一覧表のレポートを作成して使ってみると、当然のことながら注文の全体が見渡せるようになり、格段に作業しやすくなった。しかし、Baseのレポート作成機能は貧弱で、デザインもしづらく、ストレスが溜まる。それもPythonでシステムを組み直す動機の一つになっている。
Pythonで帳票の出力について調べてみたところ、PDFを作成できるReportLabという無料のPythonライブラリが存在している。ReportLabと連携してGUIで帳票をデザインできる機能は有料で、ン十万もするから、英語マニュアルを解読しながらゴリゴリとコードを書かなければならない。
ReportLabは機能が充実しているようで、マニュアルは微に入り細に入り、130ページ以上もあって、読み込むのが極めて大変。DeepL翻訳を使ってコツコツと和訳マニュアルを作成していたものの、遅々として進まなくて、一旦、断念して他の方法を考えてみる。
そもそもPDFを出力するのは、職場にあるプリンターがネットワークに接続されておらず、USBポートにUSBメモリ直挿ししてやると、PDFを読み込んで印刷できるから。Pythonから直でPDFを生成できれば楽だな、とReportLabを選定したのだけれど、PDF自体出力はPythonでなくても、LibreOfficeでもできる。
というわけで、LibreOffice Calcが読めるファイル上に明細票を作成して、LibreOfficeをPDFジェネレータとして使うのもありだ。
ということでXlsxファイルを操作できる、OpenPyXLライブラリを使ってみたところ、基本部分が2日で完成。超簡単。
wxPython上で条件選択をして、実行をすると、MySQLからデータを引っ張ってきて、OpenPyXLで用意したXlsxファイルに書き込み。あとはPython上でsubprocess.runでコマンドラインからLibreOfficeにPDFを作成するように指示し、最後にPDFビューワーを起動して帳票表示。気楽でいい感じ。
無論、何かと鈍重なLibreOfficeを介さないほうが高速かつシンプルだから、暇ができたらReportLabに再度挑戦してみてもいいかも。

wxFormBuilder備忘録

メモ的なもの。

インストールについて

インストールと運用はこのページを参考にした。
naonaorange.hatenablog.com
Mac版をGitHubのページからZipファイルをダウンロードして使っているのだが、試しにソースコードからインストールしてみたところ、フォントがぼやけたり、プロパティ記述エリアが真っ黒になってしまったりと、あんまり良くなかった。

Panelについて

wxPythonの作成チュートリアルなどではFrameの下にPanelを置くようになっている。例えば下記のような感じ。

import wx

application = wx.App()
frame = wx.Frame(None, wx.ID_ANY, 'テストフレーム', size=(600, 300))
panel = wx.Panel(frame, wx.ID_ANY)
panel.SetBackgroundColour('#AFAFAF')

ところがwxFormBuilderではFrameの直下にはPanelを置くことができない。下記のようにBoxSizerなどを配置して、その下に置くほかない。
f:id:anderson:20200322230341p:plain
間違えやすいのだが、このときに追加するPanelは上の画像にあるContainersタブの中にあるPanelを選択しなければならない。Formsタブの中にもPanelがあるが、こちらはFrameの中にもSizerの中にも入らない。

IDについて

通常はwx.ID_ANYで問題ないのだが、下記のようにコントロールにユニークなIDをもたせたい時がある。

button_samp = wx.Button(panel, wx.ID_ANY, 'sample')
button_ok = wx.Button(panel, 1001, 'OKボタン')
button_no= wx.Button(panel, 1002, 'Noボタン')

wxFormBuilderのコントロールプロパティにそのまま、上記の例のIDである、1001や1002を入力するとコード生成はできるのだが実行時にエラーになる。生成されたコードを見るとimport文の直下が下記のようになっている。

import wx
import wx.xrc

1001 = 1000
1002 = 1001

どうやらIDは自動生成されて、コントロールに指定したIDを名称とする変数に割り当てられるようで、上記の例では変数名1001に1000が割り当てられてしまっている。もちろん、Pythonの変数命名規則に反しているのでエラーになる。なので、コントロールのID欄には下記のようになるよう、id_okなどといった変数名を入力してやらねばならない。

import wx
import wx.xrc

id_ok= 1000
id_no = 1001
Pythonコード生成について

Genarate Codeを選択してPythonのコードを生成すると、インデントがタブになってしまう。実行の際には問題にならないが、見栄え的に良くはない。これについてはProject のプロパティの下の方にある、Python Propertiesの中の、indent_with_spacesをチェックすることでスペース4個でインデントしてくれる。ただし、クラス生成(Generate Inherited Class)には効果なし。

wxFormBuilderでカスタムコントロールを使う


Pythonを使って業務用システムを開発しているのだが、まさかCUIですべてを済ますわけにも行かず、GUIを利用せねばならない。
Pythonで使えるGUIというと、tkinterとかwxPython、kivyなどになるようで、調べてみた結果、Pythonの開祖、ヴァンロッサム氏が標準にしたかったよ、みたいなことを言ってるので、wxPythonで開発することにした。

さまざまなチュートリアルサイトを参考にしつつ、プログラムを学んでいくのだが、GUIの画面をコードバチバチ叩きながらデザインするのは骨が折れる。RADツールはないかいなと検索してみたところ、wxFormBuilderを発見し、インストール。
このツール、結構癖があるけれど、手書きでコード叩くよりは楽にデザインできる。
ところが、ツールパレットから貼り付け可能なコントロールは限られていて、wxPythonで用意されている数値のみを入力可能なintCtrlやらNumCtrl、小数入力が可能なFloatSpinをそのままでは利用できない。業務用アプリでは数値の入力は必須で、できれば数値以外の入力を制限したいのだが、textCtrlは何でも受け付けてしまう。
キープレスイベントを検出して数値以外を撥ねてよいのだがめんどい。いい方法はないかと見てみると、wxFormBuilderのツールパレットのAdditionalタブにCustom Controlというのを見つけた。

f:id:anderson:20200320180805p:plain

wxFormBuilder Custom Control

どうやら自分でコントロールを指定できるらしい。
設定方法は簡単で、コントロールを追加したあと、コントロールのプロパティに、importするパッケージと、Constructionを指定するだけ。こんな感じ。

f:id:anderson:20200320180801p:plain

wxFormBuilder Custom Control

ソース画面を見ながら書き込むと変更が反映されてわかりやすいかもしれない。wxPythonは日本語で説明してくれる情報ソースが割とあるので助かる。ほんとは書籍などがもっとたくさんあるといいのだけれど。

業務用システムを作ろうと頑張り中

今の勤め先は気前が良いのか、たまたま、俺の入社時期と機械の寿命が重なっただけなのか、俺が入社してから毎年のように最新工作機械を導入して、それまでの古いもの(25年間稼働しているものもあった)を更新している。
新しいものになればなるほど使い勝手がよいのでありがたいのだけれど、仕事のやり方自体はほとんどと言っていいほどアップデートされていない。経営者が爺さんなため、やりかたが平成どころか昭和のままなのだ。
俺はある部門の製造とその管理を任されており、製造指示は伝票の束をどかっと渡される。それを仕分けして分類して、整理して、注文数量をまとめる。そして一つ一つ在庫をあたって、足りないようなら材料を発注。在庫があるようならそこから必要数を取り出してきて、注文に応じて追加工、伝票を貼り付けて出荷用の箱にまとめ、出荷内容を別途控えておく、というやり方。ペーパーレスって何?美味しいの?以前の話で、現在受注している品物の一覧表すら作成されていない。全部、実伝票ベース。
預かってる伝票について「あれ、どうなってる」と尋ねられたら、伝票の山をかき分けて取り出し、内容を確認しないといけないし、出荷したものについて問い合わせがあると、事務ではなく俺が控えをめくって出荷したか否かを確認しないといけない。その間、工作機械を止めないといけないのでロスになる。
どう考えてもバカバカしい。できればシステム化したかったので俺と同い年ぐらいの次期社長に「パソコンくれ」とお願いしたところ、快諾してくれた。
当初、システム化はフリーのオフィスソフト、LibreOfficeのデータベースソフトであるBaseで行おうとしていた。大して調べももせずに、多分、MS-OfficeのAccessと似たようなものだろうと高を括っていた。AccessやらExcelは前職でVBAを使ってシステムを構築できていたし、自分しか使わないシステムならその程度で十分だからだ。自宅のメイン機がMacなので、そこで開発を行うこともあり得るために、マルチプラットフォームなのも前提条件だった。
ところがいざ開発を進めていくと、LibreOffice-Baseのレベルは俺がシステム屋だったころ使っていた、使い古しのOffice2000、つまり20年前のソフトのレベルにも達しておらず、こんなものデータベースソフトとしてリリースすんな、という代物だった。
確かにデータベースエンジンとしてFirebaseを使っていて、信頼性はあるけれど、GUIでこさえられるクエリーがSelectのみで、更新と削除はいちいちSQL叩かないといけないとか、一度作ったViewの再定義ができないとか、頑張ってProcedure作ってもその内容を表示させられないとか、プログラムに疎いユーザーが使うであろうOfficeソフトとしてリリースしてはイカンだろ。
更に、マクロ作成についてもVBAと同等のことができるという触れ込みのOfficeBasicも極めて使いにくく、付属のIDEはゴミ。例えばVBAIDEならsubやらFunctionとタイプしたら、end sub、end functionを自動保管してくれるのに、それすらない。テキストエディタで自前でシンタックス定義を行ったほうがずっとマシ。
レポートエディタもゴミ。細かい位置制御ができないし、改ページの際にタイトルの再印刷もしてくれない。
それでも頑張ってなんとか管理を行えるレベルの代物をこさえたけれど、頻繁に固まって落ちるし、一度異常終了を起こしたあと、何をやってもデータベースを開けない事態に陥り、OfficeどころかWindowsそのものを再インストールさせられてしまった。
これではストレスが溜まる一方。たくさん勉強して知見をためていったとしても、他に使いみちもないし。
いっそのことこんなものは投げ捨てて、別の仕組み使って構築しちゃおうと決意して、データベースエンジンはMySQL、フロントはPythonwxPythonを使ってみようと思い、仕事と家事と育児の合間にチョコチョコと環境構築やら言語の勉強やらをなかなか捗らないながらも行っている。

せめて、来年ぐらいには業務のあり方を平成10年代ぐらいにまで持っていきたいと考えている。伝票をシステムに手入力して、一覧表出して、制作完了したらまたシステムに入力して、在庫データが自動で増減して、在庫切れ警告も出して。

平成20年代なら、データは発注元からオンラインで流れてきて、制作指示も画面で確認。出荷はバーコードで管理。補充のための材料発注もほぼ自動、入力は殆どしなくて済む感じかな。そこまで行ければ御の字だろうね。

NC旋盤のマクロ

今の勤務先、小さな金属加工工場に採用されて7年になる。前職まではいわゆるIT関連職に従事しており、とあるシステム開発会社からキャリアをスタートさせて、転職を二回ほどおこなって小さな企業のシステム管理職に収まっていた。ところがリーマンショックのアオリを受けて事業規模が縮小され、あえなくリストラになったのが2012年の春。 そのころ、ライトセーバーを自作する趣味にハマっていたこともあり、ものづくりの面白さに惹かれ、システム開発に飽きていたこともあって、就職活動の結果、金属加工関連の職に就くことに。

anderson-s.hatenablog.com
今はオークマ製CNC旋盤のオペレーターをそつなくこなしている。コンピューターのプログラム開発なんか懲り懲りだと思っていたのだが、結局はコンピューターを使う仕事からは離れられていない。
NCのプログラムは上長である工場長が作成したものがほとんどで、刃物座標を直打ちしている。なので切削でよくある繰り返し工程なんかだと以下のように冗長なものになりがち。

(サンプル)
長さ60mm、直径80mmの品物の外形を深さ30mm、直径50mmまで、5mmずつ削る場合(z原点座標を0としている)

G00 X75 Z62 (G00 は早送り。切削開始位置まで刃物を送っている)
G96 G01 Z30 F0.2 S120 (G01は切削送り。Fは一回転あたり0.2mm刃物を動かす。G96指定があるのでS120は周速。)
G00 X77 Z62 (30mmまで切削したので刃物を一旦退避。)
    X70 (次の切削位置まで刃物を移動)
G01 Z30 (以下、繰り返しになる)
G00 X72 Z62
    X65
G01 Z30
G00 X67	Z62
    X60
G01 Z30
G00 X62	Z62
    X55
G01 Z30
G00 X57	Z62
    X50
G01 Z30
G00 X52	Z62 (繰り返し完了)

…てな感じ。いかにも面倒くさい。マニュアルを読むと他社製のNCと同じように、オークマの独自拡張でマクロが組めるので書き換えてみるとこんな感じに。*1

RID=75 (変数定義)
NLOOP (ループ先アドレス)
	G00 X=RID Z62 
	G96 G01 Z30 F0.2 S120
	G00 X=RID+2 Z62
	RID=RID-5
IF[RID GE 50]NLOOP (RID が 50になるまでNLOOPに飛ぶ)

超簡単。さらにはこのルーチンをサブプログラムとして切り出せて、必要に応じてパラメーターを与えてCallすることもできる。こんな感じ。

(呼び出し元)
CALL OS01 SRD=80 SID=50 SLNG=60 SDP=30 SRP=120 SFS=0.2 SCT=5
サブプログラム
OS01(プログラム名)
RID=SRD-SCT
NLOOP
    G00 X=RID Z=SLNG+2 
    G96 G01 Z=SDP F=SFS S=SRP
    G00 X=RID+2 Z=SLNG+2
    RID=RID-SCT
IF[RID GE SID]NLOOP
RTS(終了)

これを書けるようになってからは、工場長謹製のプログラムを全部この方式に書き換えて、プログラムの本数を圧倒的に減らしてやった。それまでは同一品で一部の径が異なるものについて、径ごとにプログラムを用意していたのだけれど、変数で定義してやると一本のプログラムで済む。
さらには関数電卓叩いて三角関数使っていちいち座標を求めて、プログラム内に埋め込んでいたような数値も、パラメーターから自動で計算するように書き換えてやった。

そもそも世間ではNC旋盤と呼ばれるものは本来、CNC旋盤なのであり、Computerized Numerical Controlなわけで、座標なんか自動計算できて当たり前。それを使わないのであれば、ただのNCとしてしか使えていない。宝の持ち腐れである。
マクロの利点は大きく、プログラムを新規に作成する場合でも圧倒的にスピードが早い。上記のような冗長なコマンドを書かなくてよいのだ。類似品ならコピペでパラメータ打ち替えるとほぼ終わり。間違えることが少ないためデバッグの必要も大きく減る。

さらには切削量を状況に合わせて調整するのも楽。上記の例で言えば、5mmを4mmに変えるのも座標直打ちだとかなり面倒だけど、変数使えば2箇所ほど変えてやればいいだけ。時間に追われる中、切削条件を色々変えて試行できるようになり、品質も大きくあがるし、知見を得ることもできた。
また、処理に意味が付与されるから、いちいちプログラムの座標値を追っかけて、これは何しているのか、というのを把握しようとしなくても良くなる。ここは外径粗挽き、ここはC面取り、など、ひと目で分かる。
いいことづく目なのだが、デメリットしては、このプログラムを理解できるのが工場内では俺だけ。工場長は興味も示さないし、仮に新人が入ってきたとして、プログラミングの適性がなければ教えても理解することができないかもしれない。

*1:オークマのコマンドでこういった繰り返し作業などをできるものがあるのだけれど、いまいち使い勝手が悪いのでマクロのほうが楽

スターウォーズ スカイウォーカーの夜明け

1977年に始まったスターウォーズシリーズがこの作品を以て一応の完結となった。シリーズと銘打たれてはいるが、実際のところ、シリーズが始まった1977年にはかっちりとしたストーリーラインが構想されていたわけでもなく、ただただぼんやりとした筋書きがある程度だったことは、さまざまな資料からも明らか。

シリーズ最初の作品となるエピソード4(以下、エピソードはEpと略す)だって、登場人物の設定は構想段階から二転三転していて、実際に撮影が始まってからも生き残るはずだったオビワンを「そのほうが面白いから」という理由で殺してしまうことになって、脚本と違う展開に怒ったオビワン役のアレック・ギネスがルーカスと揉めたりしている。

おそらくだけれどもEp9まであるよ、と発言したときのルーカスはEp4の成功に高揚してしまって中身のない大風呂敷を広げただけなんじゃないか。当然ながら舞い上がったものは冷静になれば現実的な線に着地せざるを得ず、Ep6まで、と発言を訂正せざるを得なくなったのだろう。

つまり、Ep7からEp9は当初から想定外、構想外の完全な蛇足。設計図もなにもないし、話はEp6できれいに終わっているのだから、続きを作るだけ野暮、やぶ蛇になるしかないのだ。

しかしながら、どうしてもファンはEp9まであるよとの発言を忘れてくれず、ファン層の老化に伴うグッズマーケットの縮小を食い止めたい権利者たちはそこに一縷の望みをかけて蛇足、やぶ蛇覚悟でシリーズのリブートを試みる。リブートに実績のあるJ.J.エイブラムスを起用し、Ep7以降のシリーズを走らせた。

残念ながら、Ep9までやってみた結果から言っても、スターウォーズはリブートではだめなのだ。ルーカスは常に新しい光景を見せようとしていた。俺的にはクソとしか評価できないEp1ですら、Ep4,5,6とは異なる世界観を提示し、なおかつ当時の映像技術水準の最先端を走っていた。それこそがスターウォーズを制作、公開する意義だった。

スターウォーズ以前のSF映画の金字塔「2001年宇宙の旅」を制作する際、監督のキューブリックは共同原作者のSF作家クラークに「語りぐさになるSF映画を作ろう」との気概を示し、実際にそれを成し遂げた。1977年にEp4を作り上げたルーカスにもそういった気概があった。あの映画はまさしく当時誰も観たことのない異世界だった。

Ep7は本当にただのリブートで、J.Jはリブートをよく心得ており、かつてどこかで観た光景しか提示していない。悪しざまに言えば、ファンに媚びることしかしていない。J.Jにはキューブリックやルーカスのような気概は感じられない。

Ep8はその軛から逃れようとしていた点は大いに評価できるけれど、設定やらストーリーが破綻してしまっていた。あまりに投げっぱなしすぎるし、あまりに意表を突こうとしすぎてご都合主義になってしまっていたのが残念だった。

そして公開されたEp9。ファンに媚びまくりつつ、シリーズすべてを総括するような出来にはなっていて、ファン的な視点からは拍手してやれるし、よくやったなと監督のJ.Jの頭を撫でてやりたくもなる。

だが、どんなに出来が良かろうと蛇足は蛇足。いくらきれいに終わらせようと、スターウォーズに求められる水準には達していない。

ルーカスはEp7以降の構想として微小共生体であるミディクロリアンの世界を描こうとしていたという話がある。もちろん、監督能力に疑問のあるルーカスが撮ったらクソみたいな話にはなっただろうが、そこには誰にも観たことのない世界を描いてみたいという、スターウォーズを貫く気概がある。

 

蛇足で終わってしまったEp7からEp9だけど、シリーズ全体を通してライトセーバーがキーアイテムになっていて、ライトセーバー自体のクローズアップが多かったのは、ライトセーバーが好きな俺には嬉しかった。あと、ルーカスが導入したクソ設定のミディクロリアンを完全に無視したのも良かったかな。

宇宙空間での戦闘が全く熱くなかったのには本当に失望させられた。「スターウォーズ」なのによ。TVシリーズに過ぎないリマジネーション版の「ギャラクティカ」のほうがずっと熱かったのはどういうこと?