こちらの続きです。
動的にモジュールをインポートする2 - C#ATIA
もうちょっと狙っている状態に近づけます。
test.pyを実行して、fuga,hoge,piyoのrun関数を呼び出す事には
変わらないのですが、後々の事を考え "commands" フォルダ内に
各フォルダを設置し、それぞれにpyファイルを入れた状態です。
│ test.manifest │ test.py │ └─commands ├─fuga │ fuga.py │ ├─hoge │ hoge.py │ └─piyo piyo.py
これを動的にモジュールとして読み込み、関数実行です。
こんな感じで出来ました。
# fusion360 API python import traceback import adsk.core import adsk.fusion import sys import pathlib import importlib import inspect import collections # このフォルダーをパスに追加 THIS_DIR = pathlib.Path(__file__).resolve().parent if str(THIS_DIR) not in sys.path: sys.path.append(str(THIS_DIR)) COMMANDS_DIR = THIS_DIR / 'commands' def run(context): ui = None try: app = adsk.core.Application.get() ui = app.userInterface # run関数を持つモジュールの取得 modules = getHasRunModules() # run関数実行 [module.run(context) for module in modules] except: if ui: ui.messageBox('Failed:\n{}'.format(traceback.format_exc())) # https://note.nkmk.me/python-list-flatten/ def flatten(l): for el in l: if isinstance(el, collections.abc.Iterable) and not isinstance(el, (str, bytes)): yield from flatten(el) else: yield el def getHasRunModules() -> list: # commandsフォルダ内の子フォルダ内のpyファイルの取得 files = flatten([d.iterdir() for d in COMMANDS_DIR.iterdir() if d.is_dir()]) pyFiles = [f for f in files if f.is_file() and f.suffix == '.py'] # run関数を持つモジュールの取得 modules = [] for file in pyFiles: # 各モジュールの親フォルダのパス追加 path = str(file.parent) if path not in sys.path: sys.path.append(path) # モジュールとしてインポート module = importlib.import_module(file.stem) importlib.reload(module) # パス削除 sys.path.remove(path) # モジュール内から関数名取得 funcs = [func for func, _ in inspect.getmembers(module, inspect.isfunction)] if 'run' in funcs: # run関数を持っている modules.append(module) else: # モジュールの破棄 del module return modules
import している量が多いな・・・。
commandsフォルダ内の子フォルダ全てパスを通さないと、
importlib.import_moduleでエラーになりました。多分。
これで、コマンドをフォルダ毎に分けられるし、メニュー用の
アドイン自体と各コマンドが分かれている上、動的に
呼びさせるので、登録・削除が楽になりつつ各コマンド毎に
スッキリした状態になりそうです。