こちらの続きです。
pythonからjsTreeへjson投げる1 - C#ATIA
こちら、思ったような動作をしてくれない事が分かり断念する事に・・・。
実はこちらに対応出来るアドインを作ろうとしていました。
Longer Script List, or Script Panel with Buttons - Autodesk Community
長ったらしい表示なので、Tree上に表示させて折りたたむことで
こじんまりさせようと思っていました。
python <-> jsTree(html) のやり取りは出来るようになりました。
スクリプトを実行する事は可能だったのですが、実行するとアドインも
終了してしまうため、ほぼメリットが無い状態となり断念する事にしました。
恐らくFusion360自体がそのような仕様なのでしょう。
汚いのですが、とりあえずテストしたものを残しておきます。
(無くしてしまいそうなので)
エントリーポイントとなるPalette_test.pyです。
# Fusion360API Python Addin import adsk.core import adsk.fusion import traceback import json import math # from .ktkLanguageMessage import LangMsg from .ScriptsManager import ScriptsManager # # Multilingual Dictionary # msgDict = { # } # lm = LangMsg(msgDict, adsk.core.UserLanguages.JapaneseLanguage) handlers = [] _app: adsk.core.Application = None _ui: adsk.core.UserInterface = None _cmdInfo = { 'id': 'test', 'name': 'test', 'tooltip': 'test', 'resources': '' } _paletteInfo = { 'id': 'testPalette', 'name': _cmdInfo["name"], 'htmlFileURL': 'index.html', 'isVisible': True, 'showCloseButton': True, 'isResizable': True, 'width': 400, 'height': 300, 'useNewWebBrowser': False, # True,#, 'dockingState': None } class MyHTMLEventHandler(adsk.core.HTMLEventHandler): def __init__(self): super().__init__() self.treeJson = None self.scriptsMgr = None def notify(self, args): try: htmlArgs = adsk.core.HTMLEventArgs.cast(args) global _app if htmlArgs.action == 'htmlLoaded': # インポートするフォルダーパス path = r'C:\temp' self.scriptsMgr = ScriptsManager() self.scriptsMgr.addInsideFolder(path) jstreeJson = self.scriptsMgr.getJstreeJson() args.returnData = json.dumps({ 'action': 'send', 'data' : jstreeJson, }) elif htmlArgs.action == 'execScript': data = json.loads(htmlArgs.data) script = self.scriptsMgr.getItemById(int(data['id'])) if script: script.exec() except: _ui.messageBox('Failed:\n{}'.format(traceback.format_exc())) def initPallet(): global _ui, _paletteInfo palette = _ui.palettes.itemById(_paletteInfo['id']) if palette: palette.deleteMe() palette = _ui.palettes.add( _paletteInfo['id'], _paletteInfo['name'], _paletteInfo['htmlFileURL'], _paletteInfo['isVisible'], _paletteInfo['showCloseButton'], _paletteInfo['isResizable'], _paletteInfo['width'], _paletteInfo['height'], _paletteInfo['useNewWebBrowser'], ) if _paletteInfo['dockingState']: palette.dockingState = _paletteInfo['dockingState'] else: palette.setPosition(800, 400) onHTMLEvent = MyHTMLEventHandler() palette.incomingFromHTML.add(onHTMLEvent) handlers.append(onHTMLEvent) onClosed = MyCloseEventHandler() palette.closed.add(onClosed) handlers.append(onClosed) class ShowPaletteCommandExecuteHandler(adsk.core.CommandEventHandler): def __init__(self): super().__init__() def notify(self, args): try: initPallet() except: _ui.messageBox('Command executed failed: {}'.format( traceback.format_exc())) class MyCloseEventHandler(adsk.core.UserInterfaceGeneralEventHandler): def __init__(self): super().__init__() def notify(self, args): try: pass except: _ui.messageBox('Failed:\n{}'.format(traceback.format_exc())) class ShowPaletteCommandCreatedHandler(adsk.core.CommandCreatedEventHandler): def __init__(self): super().__init__() def notify(self, args): try: command = args.command onExecute = ShowPaletteCommandExecuteHandler() command.execute.add(onExecute) handlers.append(onExecute) except: _ui.messageBox('Failed:\n{}'.format(traceback.format_exc())) def run(context): try: global _ui, _app _app = adsk.core.Application.get() _ui = _app.userInterface global _cmdInfo showPaletteCmdDef = _ui.commandDefinitions.itemById(_cmdInfo['id']) if showPaletteCmdDef: showPaletteCmdDef.deleteMe() showPaletteCmdDef = _ui.commandDefinitions.addButtonDefinition( _cmdInfo['id'], _cmdInfo['name'], _cmdInfo['tooltip'], _cmdInfo['resources'] ) global handlers onCommandCreated = ShowPaletteCommandCreatedHandler() showPaletteCmdDef.commandCreated.add(onCommandCreated) handlers.append(onCommandCreated) panel = _ui.allToolbarPanels.itemById('SolidScriptsAddinsPanel') cntrl = panel.controls.itemById('showPalette') if not cntrl: panel.controls.addCommand(showPaletteCmdDef) except: if _ui: _ui.messageBox('Failed:\n{}'.format(traceback.format_exc())) def stop(context): try: global _paletteInfo palette = _ui.palettes.itemById(_paletteInfo['id']) if palette: palette.deleteMe() global _cmdInfo panel = _ui.allToolbarPanels.itemById('SolidScriptsAddinsPanel') cmd = panel.controls.itemById(_cmdInfo['id']) if cmd: cmd.deleteMe() cmdDef = _ui.commandDefinitions.itemById(_cmdInfo['id']) if cmdDef: cmdDef.deleteMe() _app.log('Stop addin') except: if _ui: _ui.messageBox('Failed:\n{}'.format(traceback.format_exc()))
続いて、Tree表示させたるスクリプトのパスや情報、起動を担当する
コンテナーScriptsManager.pyです。
# Fusion360API Python Addin import traceback import adsk.fusion import adsk.core import sys import pathlib import json import re from typing import List from dataclasses import dataclass from dataclasses import field from importlib import import_module _CONTEXT = {'IsApplicationStartup': True} @dataclass class ScriptContainer: id: int path: pathlib.WindowsPath def __post_init__(self): pass def toJson(self): stem = self.path.stem # ここでアイコン読み込みたい return { 'id' : self.id, 'text' : stem, # 'icon' : None, } def exec(self): app = None def _getModule(path): try: stem = path.stem sys.path.append(str(path)) module = import_module(stem) except: module = None finally: del sys.path[-1] return module # ***** try: app: adsk.core.Application = adsk.core.Application.get() # モジュール取得 script = _getModule(self.path) # 外部スプリクト実行 global _CONTEXT script.run(_CONTEXT) except: app.log('Failed:\n{}'.format(traceback.format_exc())) @dataclass class ScriptsManager: count: int = field(default=1, compare=False) # patterns: List[str] = field(default_factory=list) items: List[pathlib.WindowsPath] = field(default_factory=list) def add( self, path: str) -> bool: pathObj: pathlib.WindowsPath = pathlib.Path(path) if not self.isFusionScript(pathObj): return False item: dataclass = ScriptContainer( self.count, pathObj ) self.count += 1 self.items.append(item) return True def addInsideFolder( self, path: str) -> bool: pathlib_obj: pathlib.WindowsPath = pathlib.Path(path) files = list(pathlib_obj.glob("*")) for file in files: if file.is_dir() and self.isFusionScript(file): self.add(file) return True def isFusionScript( self, pathObj: pathlib.Path) -> bool: # check filesS stem = pathObj.stem requiredList = [ f'{stem}.manifest', f'{stem}.py' ] files = [f.name for f in list(pathObj.glob("*"))] if len(set(requiredList) & set(files)) != len(requiredList): return False # check manifest manifest = pathObj / requiredList[0] with open(str(manifest),'r', encoding='utf-8') as f: data = f.read() data_json = json.loads(data) if not 'type' in data_json: return False if data_json['type'] != 'script': return False return True def getItemById( self, id: int) -> dataclass: for item in self.items: if item.id == id: return item return None def groupBy( self) -> list: # sort items = sorted(self.items, key = lambda x:x.path.stem.lower()) # key # コンストラクタにキーを入れるべき patterns = [ # 'a1', ['A-E', '^[a-e]'], ['F-J', '^[f-j]'], ['K-O', '^[k-o]'], ['P-T', '^[p-t]'], ['U-Z', '^[u-z]'], ] otherKey = ['other', 'other'] group = {key[0]: [] for key in patterns} group[otherKey[0]] = [] for item in items: stem = item.path.stem hitFg = False for pattern in patterns: match = re.search(pattern[1], stem.lower()) if match: group[pattern[0]].append(item) hitFg = True break if not hitFg: group[otherKey[0]].append(item) return group def getJstreeJson( self): # https://www.jstree.com/ groups = self.groupBy() treeContent = [] groupCount = self.count for key in groups: if len(groups[key]) < 1: continue children = [item.toJson() for item in groups[key]] treeContent.append( { 'id' : groupCount, 'text' : key, 'children' : children, } ) groupCount += 1 return treeContent
最後にTreeを表示させるパレットのindex.htmlです。
!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <title>jstree basic demos</title> <style> html { margin: 0; padding: 0; font-size: 62.5%; } body { max-width: 800px; min-width: 300px; margin: 0 auto; padding: 20px 10px; font-size: 14px; font-size: 1.4em; } .tree { overflow: auto; border: 1px solid silver; min-height: 100px; } </style> <link rel="stylesheet" href="./dist/themes/default/style.min.css" /> </head> <body> <button id="evts_button">test</button> <em>test</em> <div id="tree" class="tree"></div> <p id="event_result">-</p> <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script> <script src="./dist/jstree.min.js"></script> <script> // document load window.onload = function () { var adskWaiter = setInterval(function () { if (window.adsk) { clearInterval(adskWaiter); let ret = adsk.fusionSendData("htmlLoaded", ""); let obj = JSON.parse(ret); $("#tree").jstree(true).settings.core.data = obj.data; $("#tree").jstree(true).refresh(); } }, 100); }; // init tree $("#tree").jstree({ core: { multiple: false, data: [ { text: "Loading...", }, ], }, }); // double click $('#tree').on('dblclick', '.jstree-anchor', function(e){ var args = { id: $(this).parent().attr('id') } adsk.fusionSendData("execScript", JSON.stringify(args)) }); </script> </body> </html>
折角表示させる事が出来たから、Treeを何かに使いたいな。