AKAGI Rails

鉄道模型シミュレーターNXで遊んでいたはずが、気づいたらPythonなども。

ATENXA式イベントの仕組み

前回はatenxa.richeventを使った時限式信号機のサンプルを紹介しました。

akagi.hateblo.jp

今回はその別語として,ATENXA式イベントシステムの仕組みをもう少し詳しく書いていきます。

サンプルコード

昨日の記事からの再掲です。まずはレイアウト。

#LAYOUT
import vrmapi

# レイアウトと同じ場所のatenxaパッケージをimportするためにこの2行を書く
import os, sys
sys.path.insert(0, vrmapi.SYSTEM().GetLayoutDir())

from atenxa.richevent import richevent # 有効化コマンドをインポートした

def vrmevent(obj,ev,param):
    richevent(obj,ev,param) # ATENXA式イベントシステムを有効化
    if ev == 'init':
        pass
        # 以下省略

そして,次がセンサーのスクリプトです。

#OBJID=515
import vrmapi
from atenxa.richevent import AfterEvent #AfterEventを読み込み

def vrmevent_515(obj,ev,param):
    if ev == 'catch':
        # センサー通過時の動作
        pairsignal = vrmapi.LAYOUT().GetSignal("RAILSIGNAL_516") # ペアの信号のデータ名を設定
        d = obj.GetDict()
        if param['tire'] == 1:
            # 先頭台車の検出時
            # 既存のタイマーイベントを削除
            try:
                d["ev_up3"].kill()
            except KeyError:
                pass
            try:
                d["ev_up6"].kill()
            except KeyError:
                pass
            # 即時信号を停止現示に
            pairsignal.SetStat(0, 1)
        else:
            # 後尾台車の検出時
            # 5秒後に注意現示
            d["ev_up3"] = AfterEvent(5, pairsignal.SetStat, args=(0, 3))
            # 10秒後に進行現示
            d["ev_up6"] = AfterEvent(10, pairsignal.SetStat, args=(0, 6))
        
    elif ev == 'init':
        # 起動時の初期設定
        # 前後台車の両方を検知
        obj.SetSNSMode(2)

昨日も強調しましたが信号機には何も書く必要がありません。

続きを読む

時限式信号機をATENXAで

怎麼生

ATENXA踏切が使えるようになり次は信号機を実装してほしいです。かといって閉塞の設定は面倒くさいので,TOMIXの信号機のように,列車通過で青→赤にしたあとは時限式で順に黄→青になってくれればいいです。なんとかなりませんか。

f:id:AKAGI-vrmstation:20210730233710p:plain

  • 怎麼生
  • 説破
    • ATENXA式イベントシステムとは
    • atenxaのセットアップ(レイアウト)
    • 信号機とセンサーの配置
    • センサーのスクリプトを設定
  • 著語

説破

時限式信号機はATENXA式イベントシステムで簡単に実装することができます。Python言語の便利なところを活用して,スクリプトを書くのはセンサーだけで,信号機はノータッチで済む方法をご紹介します。

ここでは,自動センサー1つではダイアログ設定のみでの実現が難しい,

  • 編成の先頭が通過すると赤にする
  • 先頭の最後尾が通過してからx秒で黄,y秒で青にする

というギミックを実装し,多少自分でスクリプトを書く努力が報われるようにしてみます。

続きを読む

VRMNXで使う汎用モジュールをどこに保存するか

(20216/22 修正)

普通のPythonであれば,pipというパッケージ管理ツールで,サードパーティのモジュールやパッケージを使うことができるようになりますが,VRMNXに搭載されているPythonエンジンではそういう便利なことはできません。撮る夫くん などのVRMNX用汎用モジュールは、レイアウトファイルと同じ場所にpyファイルを保存することで,importできるようになります。しかし、撮る夫くんを多数のレイアウトで使うたびにいろんなフォルダにtoruo.pyをコピーするのは面倒です。

これを回避して楽をする方法も存在します。Pythonのインポートシステムの解説も交えながらその方法を説明するとともに、Python拡張を利用するVRMNXレイアウトの配布や利用の指針を提案したいと思います。

Pythonのインポートシステム

例えば,よく使われる標準モジュールに,乱数を扱うrandomがあります。Pythonimport randomという文を実行する際は、モジュール検索パスの場所を順番に探して、見つかったrandom.pyをインポートしています。

このあたりの細かい仕様は,Python公式マニュアルに記述があるので興味のある方はご覧ください。以下では,利用者目線で必要な事柄をかいつまんで紹介します。

モジュール検索パス

さて、Pythonがモジュールを探しに行く場所を、 モジュール検索パス といいます。 これを確認してみましょう。

import sys
for p in sys.path:
    vrmapi.LOG(p) #VRMNXのPythonエンジンの場合
    print(p) # 普通のPythonの場合

sys.pathにはリスト形式でいくつかのパスが収められており,このリストの先頭からモジュールを検索します。同名のモジュールがあった場合には,最初に見つかったモジュールがimportされ,それ以降のパスの検索は中止されます。

VRMNXのPythonエンジンの場合,最初の3つは

 C:\\Program Files\\I.MAGIC\\鉄道模型シミュレーターオンラインNX\\python\\Lib
 C:\\Program Files\\I.MAGIC\\鉄道模型シミュレーターオンラインNX\\python\\Lib\\site-packages
 C:\\Users\\yourname\\AppData\\Roaming\\imagic\\vrmonline\\python\\Lib

のようになっているはずです(VRMNX Version 6.0.0.210)。このうち1つ目は,先程のrandom.pyなどの標準モジュールを収めておくディレクトリであり,先程のrandomモジュールはここから読み出されます。

そして,どこかに保存済みのレイアウトでこれを実行していれば,4つ目に,レイアウトを保存したディレクトリが来ていると思います。したがって,レイアウトと同じ場所にモジュール(またはパッケージ)を保存しておけば,Pythonエンジンがそのモジュールを見つけ出すことができ,無事importが行われるわけです。

しかし当然,モジュール検索の仕様上,リストにある他のフォルダにモジュールやパッケージを置いておくことでも,Pythonエンジンからimportできるようになります。ただし,python/Libとpython/Lib/site-packagesの違いについて留意しておくことが必要です。python/LibにはPythonの標準モジュールの置き場なので,サードパーティのモジュールやライブラリ(当然,我々が作る自作モジュールも含む)はsite-packagesに収めるべきです。

site-packagesにモジュールをインストールしたら,配布時のバージョン管理に注意

pythonパッケージには作者によってアップデートが入る可能性があります。例えば,私が新バージョンの撮る夫くんを同梱してレイアウトを配布したとして,それをダウンロードしたユーザの C:\Program Files(中略)\Lib\site-packages に旧バージョンの撮る夫くんがインストールされていた場合,VRMNXシステムは私のレイアウトからでも旧バージョンの撮る夫くんを優先的にインポートします。Pythonのimport文は,モジュール検索パスの最初から順番に探していき,見つけたところで検索を打ち切る仕様だからです。これでは不都合が生じる可能性があります。

このことを回避するためには,システムのモジュール検索パスの先頭にレイアウトのディレクトリを強制的に指定してしまえばよく,次のような記述をレイアウトスクリプトの先頭に書き加えることで対応できます。

#LAYOUT
import os, sys
import vrmapi
sys.path.insert(0, vrmapi.SYSTEM().GetLayoutDir())

外部モジュールを同梱してレイアウトを配布する際には,この一手間を加えてやるのが安心です。

なお, sys.path.insert(0, <somewhere>)というイディオムは,Pythonモジュールのリファレンス文書を自動生成してくれるSphinx-apidocのconf.pyや,pipの内部仕様にも見られ,割とポピュラーなものです。

まとめ

  • 撮る夫くん(toruo.py)などの汎用モジュール(汎用パッケージ)は, C:\Program Files\I.MAGIC\鉄道模型シミュレーターオンラインNX\python\Lib\site-packages に保存すべし。
  • 外部モジュールに依存したレイアウトを作って配布するときには,レイアウトと同じフォルダにモジュールを保存すべし。

Sphinx-automodapiでVRMNX用パッケージのリファレンスを作る

ATENXAという名前で、VRMNXpy用のパッケージを計画中です。

f:id:AKAGI-vrmstation:20201013234850p:plain

汎用ライブラリとしてもお使いいただける機能があるので、リファレンスマニュアルを準備したいのですが、その方法をいつも忘れては調べ直している(そして、そこそこ時間がかかる)ので、備忘録として書き残しておきます。おきましたが、直後に結局方策を改めたので、以下、全面的に書き換えました。(9/22)

経過のあらまし

  • 当初、Sphinx-apidocでのドキュメント作成を計画した。
  • apidocは複雑なimport関係をもつパッケージのドキュメント化に向かないことが分かった。
  • Sphinx-autosummaryをプランBとして検討したが、構文解析中のImportError (atenxaがimportできない) により、断念した。(ディレクトリ構成は合っているはずなのだが…)
  • サードパーティSphinxむけドキュメント生成ツールとして、Sphinx-automodapiを見つけた。ユーザが使用するときのパッケージの階層構造の見た目に適応してドキュメントを作ることができるとのことで、これを採用した。

必要なもの

restructured-text (reST) や Markdown をビルドしてhtmlにしてくれる。公式ドキュメントは日本語訳が中途半端で、かつ、オリジナルの英語版も難解であり、そして、巷間のブログの情報も(少なくとも日本語では)上質なものが少ないが、Python関係のドキュメントといえばデファクト・スタンダードとなっている。Pythonパッケージとして動作するので

pip install sphinx

する。

sphinx-automodapi.readthedocs.io

これも同様に、

pip install sphinx-automodapi

する。

  • HTMLテーマ

書き出されたHTMLドキュメントの見た目のプリセットがいろいろ配布されている。有名どこだと、Read the Docsとか。

今回は、I.MAGIC御用達のMaterial for MkDocsをオマージュしたMaterial for Sphinxを使う。

pip install git+https://github.com/bashtage/sphinx-material.git
  • ダミーの vrmapi.py

VRMNX用の拡張モジュールなので、どうしてもvrmapiモジュールに依存する。しかしこれが、通常使われるPythonシステムのsite-packagesにはないので、Sphinxのパーサーが一度ATENXAのコードを実行するときに ImportError: No module 'vrmapi' was found. が出てしまう。このエラーを回避するため、ダミーのvrmapi.pyを作っておく。

このエラーは、autodocに対しては、autodoc_mock_importsオプションによってどうやら回避できるらしい。automodapiに対する回避策として適当かは未検証である。しかし、Visual Studio Code(のPython拡張)の字句解析上のエラーメッセージを封じるためにもある程度有用なので、作っておくとよい。

# -*- coding: utf-8 -*-
def LAYOUT():
    return None

def SYSTEM():
    return None

def LOG(obj):
    return

def CLEARLOG():
    return

def ImGui():
    return None

フォルダ構成

ATENXA-0.1.0alpha
├── docs
├── atenxa
│ ├── __init__.py
│ └── (他いろいろ)
├── vrmapi.py (ダミーのやつ)
└── (テスト用の.vrmnxとかいろいろ)

Sphinx のセットアップ(最初だけ)

./docs 以下にSphinx関係のもろもろをセットアップする。コマンドライン

cd docs
sphinx-quickstart

を実行すると、対話形式でいろいろ聞かれる。

Welcome to the Sphinx 1.7.1 quickstart utility.

Please enter values for the following settings (just press Enter to
accept a default value, if one is given in brackets).

Selected root path: .

You have two options for placing the build directory for Sphinx output.
Either, you use a directory "_build" within the root path, or you separate
"source" and "build" directories within the root path.
> Separate source and build directories (y/n) [n]: y

... 途中省略

For a list of supported codes, see
http://sphinx-doc.org/config.html#confval-language.
> Project language [ja]: (ENTER)

... 以下省略
  • sourceとbuildのフォルダを分けるかと聞かれるが、分けておいたほうが整理できてよい。
  • 言語を聞かれたら、jaで日本語になる。

初期設定

sphinx-quickstartをすると ./docs に色々フォルダやファイルが生成される。 そのなかで、 ./docs/souce/conf.py./docs/source/index.rst を編集する。

conf.py の設定

冒頭のコメントアウトされている3行を戻す。3行目はちょこっと編集して、atenxaパッケージとvrmapi.pyが見えるトップディレクトリを参照している。(conf.pyからの相対参照である。)

import os
import sys
sys.path.insert(0, os.path.abspath('../../'))

必要なSphinx拡張を有効化する。

extensions = [
    #'sphinx.ext.autodoc',
    #'sphinx.ext.autosummary',
    'sphinx_automodapi.automodapi',
    'sphinx.ext.napoleon',
    'sphinx.ext.viewcode',
    'sphinx.ext.todo',
    'recommonmark',
]

Sphinx拡張のオプションを設定する。

source_suffix = {
    '.rst': 'restructuredtext',
    '.md': 'markdown',
}

#autodoc_mock_imports = ['vrmapi']
#autodoc_member_order = 'bysource'
napoleon_use_rtype = False
todo_include_todos = True

テーマにsphinx-materialを指定し、細かい設定をいくつか。

html_theme = 'sphinx_material'

# Material theme options (see theme.conf for more information)
html_theme_options = {

    # Set the name of the project to appear in the navigation.
    'nav_title': 'ATENXA',

    # Set you GA account ID to enable tracking
    #'google_analytics_account': 'UA-XXXXX',

    # Specify a base_url used to generate sitemap.xml. If not
    # specified, then no sitemap will be built.
    #'base_url': 'https://project.github.io/project',

    # Set the color and the accent color
    'color_primary': 'red',
    'color_accent': 'yellow',

    # Set the repo location to get a badge with stats
    #'repo_url': 'https://github.com/project/project/',
    #'repo_name': 'Project',

    # Visible levels of the global TOC; -1 means unlimited
    'globaltoc_depth': 3,
    # If False, expand all TOC entries
    'globaltoc_collapse': False,
    # If True, show hidden TOC entries
    'globaltoc_includehidden': False,
}

index.rst の設定

indexでなくてもよいのだが、関数とその概要の一覧表を入れたい箇所に

.. automodapi:: atenxa
    :no-inheritance-diagram:

こんなのを書いておく。(TOCtreeと同じ要領である。)

ドキュメントのビルド

./docsで

make clean make html

1行目で./docs/buildを一度きれいに空にして、改めてhtmlをビルドする。

./buildにHTMLなど必要なものが書き出されるので、まとめてFTPでアップすればよい。

f:id:AKAGI-vrmstation:20201013235108p:plain

かなり良くできているSphinx-automodapiであるが、唯一心配なのは、あまりに知名度が低いことである。

参考

sphinx-rtd-tutorial.readthedocs.io

bashtage.github.io

www.sphinx-doc.org

www.sphinx-doc.org

Pydoc-Markdown をお試し

Pythonのdocstringを自動でドキュメント化するときはSphinxが定番ですが,それに代わるPydoc-Markdownというのを見つけたので,備忘録がてら紹介します。

pypi.org

インストール

pipからインストールできます。pipxでのインストールが推奨されています。私は無視して普通にpipでインストールしましたが,言われたとおりにするとしたら

pip install pipx
pipx install pydoc-markdown

どうせ必要になるので,MkDocsも入れておきます。MkDocs-Materialも。

お試し

Pydoc-MarkdownのGithubからまるごとダウンロードしてきて,Pydoc-MarkdownでPydoc-Markdownのドキュメントを作ってみます。

pydoc-markdown-develop (pydoc-markdown.yamlとかreadme.mdがあるディレクトリ) でターミナルを開き,次のコマンドを実行すると,ブラウザが立ち上がりlocalhostでドキュメントが読めます。

pydoc-markdown  --watch-and-serve -o

もう少し詳しく

pydoc-markdown.yaml の中身を見てみると,ここでドキュメントの構造をはじめいろいろなオプションを設定していることが分かります。

pydoc-markdown.yamlのひな型は

pydoc-markdown --bootstrap-mkdocs

で生成できます。

ただしコイツの書き方の詳しいところが私もよく分かっていません。pydoc-markdown-develop/pydoc-markdown.yamlを雰囲気でマネするのがいいかも。

HTML化

./docs/build をさらにMkDocsでビルドすると,HTMLドキュメントにできます。手順はCaldia氏のMkDocsマニュアルが参考になりますのでご参照ください。

蛇足

VRMNXpy用の拡張パッケージを作れないかと思っていろいろ試していますが,マニュアル作りのことも考えておかないといけません。 しかしSphinxではコードを一通り実行してdocstringを整形するので,実行中にvrmapiのImportErrorが出ると止まってしまい,うまくいきません。

その点Pydoc-Markdownは,コードを実行せずにdocstringをパースして整形してくれるので,この問題を回避できる…と思われたのですが,謎のエラーに遭遇しておりこちらもうまくいっていません。

Factorioのサプライチェーンネットワーク

Factorioの各アイテムについて、材料として何が必要かの関係性をとりあえず図示してみました。

  1. 各アイテムの材料をExcelにデータベースのように打ち込む(手打ちしたw)
  2. NetworkXというライブラリで、アイテムをノード、依存関係をアークとするようなグラフをExcelを元に作る
  3. NetworkX (というかmatplotlib.pyplot) で可視化

f:id:AKAGI-vrmstation:20200421235538p:plain
必要なモノ同士が矢印で結ばれている

ゲームの進み具合的に、まだ宇宙サイエンスパック(白フラスコ)を製造する必要がなさそうなので、それ以外のフラスコの製造に必要なアイテムのみ抽出してグラフにしてあります。

各ノードの位置関係についてはまだしっかりと作り込んでいませんが、このグラフに表された関係性が、各アイテムの必要数の解析や、施設配置最適化を考えていくための土台となります。

リスト型をベクトルに見立てて計算したい

いろいろ試していますがVRMNXpyで一向にnumpyが使えません。いい加減やってられないので,リスト型をベクトルに見立てて線形代数の主要な計算ができるコードを用意しました。

vrmapiの命令で使うことが前提なので,クラスを作って演算子をオーバーライドとかは一切せず,リスト型を放り込んでリスト型を返す関数に仕立てました。

def vecdot(vec1, vec2):
    # 内積スカラー)を返す
    p = 0.0
    for i, j in zip(vec1, vec2):
        p += i*j
    return p

def vectrans(matrixA, vecx):
    # 線形変換 Ax
    vecy = []
    for k in range(len(vecx)):
        vecy.append(vecdot(matrixA[k], vecx))
    return vecy

def turnmatrix(matrixA):
    # 行列の転置
    m = len(matrixA)
    n = len(matrixA[0])
    return [[matrixA[i][j] for i in range(m)] for j in range(n)]

def matrixproduct(matrixA, matrixB):
    # 行列の積
    return [vectrans(turnmatrix(matrixB), ai) for ai in matrixA]

 

本来はベクトル計算は,ベクトルの次元が合っているかとか確認しなきゃいけないのですが,あえてそれもしていません。変な行列やベクトルを放り込んだらfor文のあたりでIndexErrorが出るはず。いろいろ雑ですがVRMNXpyで使いそうなベクトル計算はせいぜい3次元なので…。

>>> A = [[1,2],[3,4]]
>>> B = [[5,6],[7,8]]
>>> x = [1,2]
>>> y = [2,2]
>>> vecdot(x,y)
6.0
>>> vectrans(A,x)
[5.0, 11.0]
>>> turnmatrix(A)
[[1, 3], [2, 4]]
>>> matrixproduct(A,B)
[[19.0, 22.0], [43.0, 50.0]]

サンプルは2次元ですが3次元もいけます。