├── .gitattributes ├── .github └── FUNDING.yml ├── .gitignore ├── FNS_TDDefault_2023.toe ├── FunctionStore_tools_2023.code-workspace ├── LICENSE ├── README.md ├── icons ├── CustomParTools.png ├── Fx.png ├── GlobalVol.png ├── HogCHOP.png ├── ListIOPs.jpg ├── Mapper.png ├── Olib.png ├── OpTemplates.png ├── ParOpDrop.png ├── ParentHierarchy.png ├── PerformTools.png ├── QuickMouse.png ├── QuickTime.png ├── Random.png ├── ResetPLS.png ├── Set Smoothness.png ├── SwapOPs.png ├── Toggle Grid.png ├── ToggleBackdrop.png ├── VSCodeTools.png ├── Wiki.png └── main.png ├── modules ├── release │ ├── AltSelect.tox │ ├── AutoCombine.tox │ ├── AutoRes.tox │ ├── BorderlessTD.tox │ ├── BorderlessWindow.tox │ ├── ColorUI.tox │ ├── ExprHotStrings.tox │ ├── FNS_Navbar.tox │ ├── FunctionStore_tools_2023.tox │ ├── GlobalVolControl.tox │ ├── HideTimeline.tox │ ├── MIDIResetPLS.tox │ ├── NoUI.tox │ ├── OpToClipboard.tox │ ├── OpenExt.tox │ ├── ParRandomizer.tox │ ├── QuickCollapse.tox │ ├── QuickExt.tox │ ├── QuickMarks.tox │ ├── QuickOp.tox │ ├── QuickPane.tox │ ├── QuickParCustom.tox │ ├── ResetPLS1.tox │ ├── SwapOps.tox │ ├── SwitchOPs.tox │ ├── VSCodeTools.tox │ ├── midiMapperMulti.tox │ └── paste_from_clipboard.tox └── suspects │ ├── FunctionStore_tools_2023.tox │ ├── FunctionStore_tools_2023 │ ├── AltSelect.tox │ ├── AutoCombine.tox │ ├── AutoRes.tox │ ├── BorderlessTD.tox │ ├── BorderlessTD │ │ ├── BorderlessWindow.tox │ │ └── HideTimeline.tox │ ├── ClearPars.tox │ ├── ColorUI.tox │ ├── CustomParPromoter.tox │ ├── ExprHotStrings.tox │ ├── FNS_Config.tox │ ├── FNS_HotkeyManager.tox │ ├── FNS_Navbar.tox │ ├── FNS_OpMenu.tox │ ├── FNS_OpMenu │ │ └── IOFilter.tox │ ├── FNS_Toolbar.tox │ ├── GlobalOutSelect.tox │ ├── GlobalVolControl.tox │ ├── HydroHomie.tox │ ├── MISC.tox │ ├── MY_HOTKEYS.tox │ ├── OUTPUT.tox │ ├── Olib_Browser1.tox │ ├── OpTemplates.tox │ ├── OpToClipboard.tox │ ├── OpenExt.tox │ ├── ParOPDrop.tox │ ├── ParRandomizer.tox │ ├── QuickCollapse.tox │ ├── QuickExt.tox │ ├── QuickExtTest.tox │ ├── QuickMarks.tox │ ├── QuickOp.tox │ ├── QuickPane.tox │ ├── QuickParCustom.tox │ ├── QuickParent.tox │ ├── QuickTime.tox │ ├── ResetPLS1.tox │ ├── SetSmoothness.tox │ ├── SwapOps.tox │ ├── SwitchOPs.tox │ ├── UPDATER.tox │ ├── VSCodeTools.tox │ ├── VSCodeTools │ │ ├── ClearScriptSyncFile.tox │ │ ├── OpenVSCode.tox │ │ ├── ScriptSyncFile.tox │ │ ├── TDTypings.tox │ │ ├── stubser.tox │ │ ├── stubser_wrapper.tox │ │ └── stubser_wrapper │ │ │ └── stubser.tox │ ├── iopPromoter.tox │ ├── mapTables │ │ └── ExternalTables.tox │ ├── midiMapper.tox │ ├── midiMapper │ │ └── MIDIResetPLS.tox │ ├── midiMapperMulti.tox │ ├── op_store_mod.tox │ ├── oscMapper.tox │ ├── paste_from_clipboard.tox │ └── tools_ui.tox │ └── private_investigator1_withmyhacks.tox ├── scripts ├── AltSelect │ └── AltSelectExt.py ├── AutoCombine │ └── AutoCombineExt.py ├── AutoRes │ └── AutoResExt.py ├── BorderlessWindow │ ├── ExtBorderlessWindow.py │ └── install.py ├── ClearPars │ └── ClearParsExt.py ├── ColorUI │ └── ExtColorUI.py ├── CustomParPromoter │ ├── PopDialogExt1.py │ └── customParPromoterExt.py ├── ExprHotStrings │ └── extExprHotString.py ├── FNS_Config │ └── ExtFNSConfig.py ├── FNS_HotkeyManager │ └── HotkeyManagerExt.py ├── FNS_OpMenu │ ├── ExtOpMenuMod.py │ └── install.py ├── FNS_Toolbar │ └── ExtToolbar.py ├── FNS_navbar │ └── install.py ├── HydroHomie │ └── HydroHomieExt.py ├── MidiMapper │ └── bypasser │ │ └── ExtMidiBypasser.py ├── NavBar │ └── ParentHierarchy │ │ └── ParentHierarchyExt.py ├── NoUI │ └── ExtNoUI.py ├── OpTemplates │ └── OpTemplateExt.py ├── OpToClipboard │ └── ExtOpToClipboard.py ├── OpenExt │ └── ExtOpenExt.py ├── ParOPDrop │ └── ExtParOPPlace.py ├── ParRandomizer │ └── ExtParRandomizer.py ├── QuickCollapse │ ├── ExtQuickCollapse.py │ └── PopDialogExt.py ├── QuickExt │ ├── ExtQuickExt.py │ ├── stubser │ │ └── extStubser_.py │ └── templates │ │ └── ExtUtils │ │ ├── CustomParHelper │ │ ├── CustomParHelper.py │ │ ├── extParExec.py │ │ ├── extParGroupExec.py │ │ ├── extParPropDatExec.py │ │ └── extSeqParExec.py │ │ ├── ExtTest.py │ │ ├── NoNode │ │ ├── NoNode.py │ │ ├── extChopOffToOnExec.py │ │ ├── extChopOnToOffExec.py │ │ ├── extChopValueChangeExec.py │ │ ├── extChopWhileOffExec.py │ │ ├── extChopWhileOnExec.py │ │ ├── extDatCellChangeExec.py │ │ ├── extDatColChangeExec.py │ │ ├── extDatRowChangeExec.py │ │ ├── extDatSizeChangeExec.py │ │ ├── extDatTableChangeExec.py │ │ ├── extKeyboardin_callbacks.py │ │ ├── extParExecNoNodeOnPulse.py │ │ └── extParExecNoNodeValueChange.py │ │ ├── description.txt │ │ └── extTemplate.py ├── QuickMarks │ └── QuickmarkStorageExt.py ├── QuickOp │ └── QuickOpExt.py ├── QuickPane │ └── QuickPaneExt.py ├── QuickParCustom │ └── QuickParCustomExt.py ├── QuickParent │ └── QuickParentExt.py ├── ResetPLS │ └── script_reset.py ├── SetSmoothness │ └── SmoothnessExt.py ├── SwapOps │ └── SwapOpsExt.py ├── SwitchOPs │ └── ExtSwitchOp.py ├── UI_OpColor │ └── ExtOpColorChange.py ├── UPDATER │ └── ExtUpdater.py ├── VSCodeTools │ ├── ClearScriptSyncFile │ │ └── ExtClearScriptFile.py │ ├── OpenVSCode │ │ └── ExtOpenVSCode.py │ ├── ScriptSyncFile │ │ └── ExtScriptExternalize_.py │ ├── TDTypings │ │ └── ExtTDTypings.py │ └── stubser_wrapper │ │ └── ExtStubserWrapper.py ├── iopPromoter │ └── ExtIopPromoter.py ├── midiMapperMulti │ └── MidiMapperMultiExt.py └── paste_from_clipboard │ └── ClipboardImageEXT.py └── typings ├── __builtins__.pyi ├── custom_typings └── QuickExt │ ├── CustomParHelper.pyi │ └── NoNode.pyi ├── parameter.py └── parameterTypes.py /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: function-store 2 | patreon: function_store 3 | custom: ["https://www.paypal.com/donate/?hosted_button_id=9MJD37VEZL4PS"] 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | share/python-wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | MANIFEST 28 | *.log 29 | 30 | # Environments 31 | .env 32 | .venv 33 | env/ 34 | venv/ 35 | ENV/ 36 | env.bak/ 37 | venv.bak/ 38 | 39 | # TD 40 | *.toe 41 | Backup/ 42 | *.dmp 43 | !FNS_TDDefault.toe 44 | !FNS_TDDefault_2023.toe 45 | olib/ 46 | TDImportCache/ 47 | mapping/ 48 | .DS_Store 49 | /.VSCodeCounter 50 | Log/OrbbecSDK.log.txt 51 | /Assets 52 | /clipboard_images 53 | -------------------------------------------------------------------------------- /FNS_TDDefault_2023.toe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/function-store/FunctionStore_tools/11669c6abb3796a74762ce6d3c2181b7b455ad65/FNS_TDDefault_2023.toe -------------------------------------------------------------------------------- /FunctionStore_tools_2023.code-workspace: -------------------------------------------------------------------------------- 1 | { 2 | "folders": [ 3 | { 4 | "path": "." 5 | } 6 | ], 7 | "settings": { 8 | "python.defaultInterpreterPath": "C:\\Program Files\\Derivative\\TouchDesigner.2023.32139\\bin\\python.exe", 9 | "python.terminal.activateEnvironment": true 10 | } 11 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Daniel Molnar / Function Store 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /icons/CustomParTools.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/function-store/FunctionStore_tools/11669c6abb3796a74762ce6d3c2181b7b455ad65/icons/CustomParTools.png -------------------------------------------------------------------------------- /icons/Fx.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/function-store/FunctionStore_tools/11669c6abb3796a74762ce6d3c2181b7b455ad65/icons/Fx.png -------------------------------------------------------------------------------- /icons/GlobalVol.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/function-store/FunctionStore_tools/11669c6abb3796a74762ce6d3c2181b7b455ad65/icons/GlobalVol.png -------------------------------------------------------------------------------- /icons/HogCHOP.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/function-store/FunctionStore_tools/11669c6abb3796a74762ce6d3c2181b7b455ad65/icons/HogCHOP.png -------------------------------------------------------------------------------- /icons/ListIOPs.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/function-store/FunctionStore_tools/11669c6abb3796a74762ce6d3c2181b7b455ad65/icons/ListIOPs.jpg -------------------------------------------------------------------------------- /icons/Mapper.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/function-store/FunctionStore_tools/11669c6abb3796a74762ce6d3c2181b7b455ad65/icons/Mapper.png -------------------------------------------------------------------------------- /icons/Olib.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/function-store/FunctionStore_tools/11669c6abb3796a74762ce6d3c2181b7b455ad65/icons/Olib.png -------------------------------------------------------------------------------- /icons/OpTemplates.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/function-store/FunctionStore_tools/11669c6abb3796a74762ce6d3c2181b7b455ad65/icons/OpTemplates.png -------------------------------------------------------------------------------- /icons/ParOpDrop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/function-store/FunctionStore_tools/11669c6abb3796a74762ce6d3c2181b7b455ad65/icons/ParOpDrop.png -------------------------------------------------------------------------------- /icons/ParentHierarchy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/function-store/FunctionStore_tools/11669c6abb3796a74762ce6d3c2181b7b455ad65/icons/ParentHierarchy.png -------------------------------------------------------------------------------- /icons/PerformTools.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/function-store/FunctionStore_tools/11669c6abb3796a74762ce6d3c2181b7b455ad65/icons/PerformTools.png -------------------------------------------------------------------------------- /icons/QuickMouse.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/function-store/FunctionStore_tools/11669c6abb3796a74762ce6d3c2181b7b455ad65/icons/QuickMouse.png -------------------------------------------------------------------------------- /icons/QuickTime.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/function-store/FunctionStore_tools/11669c6abb3796a74762ce6d3c2181b7b455ad65/icons/QuickTime.png -------------------------------------------------------------------------------- /icons/Random.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/function-store/FunctionStore_tools/11669c6abb3796a74762ce6d3c2181b7b455ad65/icons/Random.png -------------------------------------------------------------------------------- /icons/ResetPLS.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/function-store/FunctionStore_tools/11669c6abb3796a74762ce6d3c2181b7b455ad65/icons/ResetPLS.png -------------------------------------------------------------------------------- /icons/Set Smoothness.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/function-store/FunctionStore_tools/11669c6abb3796a74762ce6d3c2181b7b455ad65/icons/Set Smoothness.png -------------------------------------------------------------------------------- /icons/SwapOPs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/function-store/FunctionStore_tools/11669c6abb3796a74762ce6d3c2181b7b455ad65/icons/SwapOPs.png -------------------------------------------------------------------------------- /icons/Toggle Grid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/function-store/FunctionStore_tools/11669c6abb3796a74762ce6d3c2181b7b455ad65/icons/Toggle Grid.png -------------------------------------------------------------------------------- /icons/ToggleBackdrop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/function-store/FunctionStore_tools/11669c6abb3796a74762ce6d3c2181b7b455ad65/icons/ToggleBackdrop.png -------------------------------------------------------------------------------- /icons/VSCodeTools.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/function-store/FunctionStore_tools/11669c6abb3796a74762ce6d3c2181b7b455ad65/icons/VSCodeTools.png -------------------------------------------------------------------------------- /icons/Wiki.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/function-store/FunctionStore_tools/11669c6abb3796a74762ce6d3c2181b7b455ad65/icons/Wiki.png -------------------------------------------------------------------------------- /icons/main.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/function-store/FunctionStore_tools/11669c6abb3796a74762ce6d3c2181b7b455ad65/icons/main.png -------------------------------------------------------------------------------- /modules/release/AltSelect.tox: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/function-store/FunctionStore_tools/11669c6abb3796a74762ce6d3c2181b7b455ad65/modules/release/AltSelect.tox -------------------------------------------------------------------------------- /modules/release/AutoCombine.tox: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/function-store/FunctionStore_tools/11669c6abb3796a74762ce6d3c2181b7b455ad65/modules/release/AutoCombine.tox -------------------------------------------------------------------------------- /modules/release/AutoRes.tox: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/function-store/FunctionStore_tools/11669c6abb3796a74762ce6d3c2181b7b455ad65/modules/release/AutoRes.tox -------------------------------------------------------------------------------- /modules/release/BorderlessTD.tox: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/function-store/FunctionStore_tools/11669c6abb3796a74762ce6d3c2181b7b455ad65/modules/release/BorderlessTD.tox -------------------------------------------------------------------------------- /modules/release/BorderlessWindow.tox: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/function-store/FunctionStore_tools/11669c6abb3796a74762ce6d3c2181b7b455ad65/modules/release/BorderlessWindow.tox -------------------------------------------------------------------------------- /modules/release/ColorUI.tox: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/function-store/FunctionStore_tools/11669c6abb3796a74762ce6d3c2181b7b455ad65/modules/release/ColorUI.tox -------------------------------------------------------------------------------- /modules/release/ExprHotStrings.tox: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/function-store/FunctionStore_tools/11669c6abb3796a74762ce6d3c2181b7b455ad65/modules/release/ExprHotStrings.tox -------------------------------------------------------------------------------- /modules/release/FNS_Navbar.tox: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/function-store/FunctionStore_tools/11669c6abb3796a74762ce6d3c2181b7b455ad65/modules/release/FNS_Navbar.tox -------------------------------------------------------------------------------- /modules/release/FunctionStore_tools_2023.tox: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/function-store/FunctionStore_tools/11669c6abb3796a74762ce6d3c2181b7b455ad65/modules/release/FunctionStore_tools_2023.tox -------------------------------------------------------------------------------- /modules/release/GlobalVolControl.tox: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/function-store/FunctionStore_tools/11669c6abb3796a74762ce6d3c2181b7b455ad65/modules/release/GlobalVolControl.tox -------------------------------------------------------------------------------- /modules/release/HideTimeline.tox: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/function-store/FunctionStore_tools/11669c6abb3796a74762ce6d3c2181b7b455ad65/modules/release/HideTimeline.tox -------------------------------------------------------------------------------- /modules/release/MIDIResetPLS.tox: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/function-store/FunctionStore_tools/11669c6abb3796a74762ce6d3c2181b7b455ad65/modules/release/MIDIResetPLS.tox -------------------------------------------------------------------------------- /modules/release/NoUI.tox: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/function-store/FunctionStore_tools/11669c6abb3796a74762ce6d3c2181b7b455ad65/modules/release/NoUI.tox -------------------------------------------------------------------------------- /modules/release/OpToClipboard.tox: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/function-store/FunctionStore_tools/11669c6abb3796a74762ce6d3c2181b7b455ad65/modules/release/OpToClipboard.tox -------------------------------------------------------------------------------- /modules/release/OpenExt.tox: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/function-store/FunctionStore_tools/11669c6abb3796a74762ce6d3c2181b7b455ad65/modules/release/OpenExt.tox -------------------------------------------------------------------------------- /modules/release/ParRandomizer.tox: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/function-store/FunctionStore_tools/11669c6abb3796a74762ce6d3c2181b7b455ad65/modules/release/ParRandomizer.tox -------------------------------------------------------------------------------- /modules/release/QuickCollapse.tox: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/function-store/FunctionStore_tools/11669c6abb3796a74762ce6d3c2181b7b455ad65/modules/release/QuickCollapse.tox -------------------------------------------------------------------------------- /modules/release/QuickExt.tox: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/function-store/FunctionStore_tools/11669c6abb3796a74762ce6d3c2181b7b455ad65/modules/release/QuickExt.tox -------------------------------------------------------------------------------- /modules/release/QuickMarks.tox: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/function-store/FunctionStore_tools/11669c6abb3796a74762ce6d3c2181b7b455ad65/modules/release/QuickMarks.tox -------------------------------------------------------------------------------- /modules/release/QuickOp.tox: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/function-store/FunctionStore_tools/11669c6abb3796a74762ce6d3c2181b7b455ad65/modules/release/QuickOp.tox -------------------------------------------------------------------------------- /modules/release/QuickPane.tox: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/function-store/FunctionStore_tools/11669c6abb3796a74762ce6d3c2181b7b455ad65/modules/release/QuickPane.tox -------------------------------------------------------------------------------- /modules/release/QuickParCustom.tox: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/function-store/FunctionStore_tools/11669c6abb3796a74762ce6d3c2181b7b455ad65/modules/release/QuickParCustom.tox -------------------------------------------------------------------------------- /modules/release/ResetPLS1.tox: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/function-store/FunctionStore_tools/11669c6abb3796a74762ce6d3c2181b7b455ad65/modules/release/ResetPLS1.tox -------------------------------------------------------------------------------- /modules/release/SwapOps.tox: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/function-store/FunctionStore_tools/11669c6abb3796a74762ce6d3c2181b7b455ad65/modules/release/SwapOps.tox -------------------------------------------------------------------------------- /modules/release/SwitchOPs.tox: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/function-store/FunctionStore_tools/11669c6abb3796a74762ce6d3c2181b7b455ad65/modules/release/SwitchOPs.tox -------------------------------------------------------------------------------- /modules/release/VSCodeTools.tox: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/function-store/FunctionStore_tools/11669c6abb3796a74762ce6d3c2181b7b455ad65/modules/release/VSCodeTools.tox -------------------------------------------------------------------------------- /modules/release/midiMapperMulti.tox: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/function-store/FunctionStore_tools/11669c6abb3796a74762ce6d3c2181b7b455ad65/modules/release/midiMapperMulti.tox -------------------------------------------------------------------------------- /modules/release/paste_from_clipboard.tox: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/function-store/FunctionStore_tools/11669c6abb3796a74762ce6d3c2181b7b455ad65/modules/release/paste_from_clipboard.tox -------------------------------------------------------------------------------- /modules/suspects/FunctionStore_tools_2023.tox: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/function-store/FunctionStore_tools/11669c6abb3796a74762ce6d3c2181b7b455ad65/modules/suspects/FunctionStore_tools_2023.tox -------------------------------------------------------------------------------- /modules/suspects/FunctionStore_tools_2023/AltSelect.tox: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/function-store/FunctionStore_tools/11669c6abb3796a74762ce6d3c2181b7b455ad65/modules/suspects/FunctionStore_tools_2023/AltSelect.tox -------------------------------------------------------------------------------- /modules/suspects/FunctionStore_tools_2023/AutoCombine.tox: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/function-store/FunctionStore_tools/11669c6abb3796a74762ce6d3c2181b7b455ad65/modules/suspects/FunctionStore_tools_2023/AutoCombine.tox -------------------------------------------------------------------------------- /modules/suspects/FunctionStore_tools_2023/AutoRes.tox: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/function-store/FunctionStore_tools/11669c6abb3796a74762ce6d3c2181b7b455ad65/modules/suspects/FunctionStore_tools_2023/AutoRes.tox -------------------------------------------------------------------------------- /modules/suspects/FunctionStore_tools_2023/BorderlessTD.tox: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/function-store/FunctionStore_tools/11669c6abb3796a74762ce6d3c2181b7b455ad65/modules/suspects/FunctionStore_tools_2023/BorderlessTD.tox -------------------------------------------------------------------------------- /modules/suspects/FunctionStore_tools_2023/BorderlessTD/BorderlessWindow.tox: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/function-store/FunctionStore_tools/11669c6abb3796a74762ce6d3c2181b7b455ad65/modules/suspects/FunctionStore_tools_2023/BorderlessTD/BorderlessWindow.tox -------------------------------------------------------------------------------- /modules/suspects/FunctionStore_tools_2023/BorderlessTD/HideTimeline.tox: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/function-store/FunctionStore_tools/11669c6abb3796a74762ce6d3c2181b7b455ad65/modules/suspects/FunctionStore_tools_2023/BorderlessTD/HideTimeline.tox -------------------------------------------------------------------------------- /modules/suspects/FunctionStore_tools_2023/ClearPars.tox: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/function-store/FunctionStore_tools/11669c6abb3796a74762ce6d3c2181b7b455ad65/modules/suspects/FunctionStore_tools_2023/ClearPars.tox -------------------------------------------------------------------------------- /modules/suspects/FunctionStore_tools_2023/ColorUI.tox: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/function-store/FunctionStore_tools/11669c6abb3796a74762ce6d3c2181b7b455ad65/modules/suspects/FunctionStore_tools_2023/ColorUI.tox -------------------------------------------------------------------------------- /modules/suspects/FunctionStore_tools_2023/CustomParPromoter.tox: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/function-store/FunctionStore_tools/11669c6abb3796a74762ce6d3c2181b7b455ad65/modules/suspects/FunctionStore_tools_2023/CustomParPromoter.tox -------------------------------------------------------------------------------- /modules/suspects/FunctionStore_tools_2023/ExprHotStrings.tox: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/function-store/FunctionStore_tools/11669c6abb3796a74762ce6d3c2181b7b455ad65/modules/suspects/FunctionStore_tools_2023/ExprHotStrings.tox -------------------------------------------------------------------------------- /modules/suspects/FunctionStore_tools_2023/FNS_Config.tox: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/function-store/FunctionStore_tools/11669c6abb3796a74762ce6d3c2181b7b455ad65/modules/suspects/FunctionStore_tools_2023/FNS_Config.tox -------------------------------------------------------------------------------- /modules/suspects/FunctionStore_tools_2023/FNS_HotkeyManager.tox: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/function-store/FunctionStore_tools/11669c6abb3796a74762ce6d3c2181b7b455ad65/modules/suspects/FunctionStore_tools_2023/FNS_HotkeyManager.tox -------------------------------------------------------------------------------- /modules/suspects/FunctionStore_tools_2023/FNS_Navbar.tox: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/function-store/FunctionStore_tools/11669c6abb3796a74762ce6d3c2181b7b455ad65/modules/suspects/FunctionStore_tools_2023/FNS_Navbar.tox -------------------------------------------------------------------------------- /modules/suspects/FunctionStore_tools_2023/FNS_OpMenu.tox: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/function-store/FunctionStore_tools/11669c6abb3796a74762ce6d3c2181b7b455ad65/modules/suspects/FunctionStore_tools_2023/FNS_OpMenu.tox -------------------------------------------------------------------------------- /modules/suspects/FunctionStore_tools_2023/FNS_OpMenu/IOFilter.tox: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/function-store/FunctionStore_tools/11669c6abb3796a74762ce6d3c2181b7b455ad65/modules/suspects/FunctionStore_tools_2023/FNS_OpMenu/IOFilter.tox -------------------------------------------------------------------------------- /modules/suspects/FunctionStore_tools_2023/FNS_Toolbar.tox: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/function-store/FunctionStore_tools/11669c6abb3796a74762ce6d3c2181b7b455ad65/modules/suspects/FunctionStore_tools_2023/FNS_Toolbar.tox -------------------------------------------------------------------------------- /modules/suspects/FunctionStore_tools_2023/GlobalOutSelect.tox: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/function-store/FunctionStore_tools/11669c6abb3796a74762ce6d3c2181b7b455ad65/modules/suspects/FunctionStore_tools_2023/GlobalOutSelect.tox -------------------------------------------------------------------------------- /modules/suspects/FunctionStore_tools_2023/GlobalVolControl.tox: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/function-store/FunctionStore_tools/11669c6abb3796a74762ce6d3c2181b7b455ad65/modules/suspects/FunctionStore_tools_2023/GlobalVolControl.tox -------------------------------------------------------------------------------- /modules/suspects/FunctionStore_tools_2023/HydroHomie.tox: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/function-store/FunctionStore_tools/11669c6abb3796a74762ce6d3c2181b7b455ad65/modules/suspects/FunctionStore_tools_2023/HydroHomie.tox -------------------------------------------------------------------------------- /modules/suspects/FunctionStore_tools_2023/MISC.tox: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/function-store/FunctionStore_tools/11669c6abb3796a74762ce6d3c2181b7b455ad65/modules/suspects/FunctionStore_tools_2023/MISC.tox -------------------------------------------------------------------------------- /modules/suspects/FunctionStore_tools_2023/MY_HOTKEYS.tox: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/function-store/FunctionStore_tools/11669c6abb3796a74762ce6d3c2181b7b455ad65/modules/suspects/FunctionStore_tools_2023/MY_HOTKEYS.tox -------------------------------------------------------------------------------- /modules/suspects/FunctionStore_tools_2023/OUTPUT.tox: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/function-store/FunctionStore_tools/11669c6abb3796a74762ce6d3c2181b7b455ad65/modules/suspects/FunctionStore_tools_2023/OUTPUT.tox -------------------------------------------------------------------------------- /modules/suspects/FunctionStore_tools_2023/Olib_Browser1.tox: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/function-store/FunctionStore_tools/11669c6abb3796a74762ce6d3c2181b7b455ad65/modules/suspects/FunctionStore_tools_2023/Olib_Browser1.tox -------------------------------------------------------------------------------- /modules/suspects/FunctionStore_tools_2023/OpTemplates.tox: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/function-store/FunctionStore_tools/11669c6abb3796a74762ce6d3c2181b7b455ad65/modules/suspects/FunctionStore_tools_2023/OpTemplates.tox -------------------------------------------------------------------------------- /modules/suspects/FunctionStore_tools_2023/OpToClipboard.tox: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/function-store/FunctionStore_tools/11669c6abb3796a74762ce6d3c2181b7b455ad65/modules/suspects/FunctionStore_tools_2023/OpToClipboard.tox -------------------------------------------------------------------------------- /modules/suspects/FunctionStore_tools_2023/OpenExt.tox: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/function-store/FunctionStore_tools/11669c6abb3796a74762ce6d3c2181b7b455ad65/modules/suspects/FunctionStore_tools_2023/OpenExt.tox -------------------------------------------------------------------------------- /modules/suspects/FunctionStore_tools_2023/ParOPDrop.tox: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/function-store/FunctionStore_tools/11669c6abb3796a74762ce6d3c2181b7b455ad65/modules/suspects/FunctionStore_tools_2023/ParOPDrop.tox -------------------------------------------------------------------------------- /modules/suspects/FunctionStore_tools_2023/ParRandomizer.tox: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/function-store/FunctionStore_tools/11669c6abb3796a74762ce6d3c2181b7b455ad65/modules/suspects/FunctionStore_tools_2023/ParRandomizer.tox -------------------------------------------------------------------------------- /modules/suspects/FunctionStore_tools_2023/QuickCollapse.tox: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/function-store/FunctionStore_tools/11669c6abb3796a74762ce6d3c2181b7b455ad65/modules/suspects/FunctionStore_tools_2023/QuickCollapse.tox -------------------------------------------------------------------------------- /modules/suspects/FunctionStore_tools_2023/QuickExt.tox: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/function-store/FunctionStore_tools/11669c6abb3796a74762ce6d3c2181b7b455ad65/modules/suspects/FunctionStore_tools_2023/QuickExt.tox -------------------------------------------------------------------------------- /modules/suspects/FunctionStore_tools_2023/QuickExtTest.tox: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/function-store/FunctionStore_tools/11669c6abb3796a74762ce6d3c2181b7b455ad65/modules/suspects/FunctionStore_tools_2023/QuickExtTest.tox -------------------------------------------------------------------------------- /modules/suspects/FunctionStore_tools_2023/QuickMarks.tox: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/function-store/FunctionStore_tools/11669c6abb3796a74762ce6d3c2181b7b455ad65/modules/suspects/FunctionStore_tools_2023/QuickMarks.tox -------------------------------------------------------------------------------- /modules/suspects/FunctionStore_tools_2023/QuickOp.tox: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/function-store/FunctionStore_tools/11669c6abb3796a74762ce6d3c2181b7b455ad65/modules/suspects/FunctionStore_tools_2023/QuickOp.tox -------------------------------------------------------------------------------- /modules/suspects/FunctionStore_tools_2023/QuickPane.tox: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/function-store/FunctionStore_tools/11669c6abb3796a74762ce6d3c2181b7b455ad65/modules/suspects/FunctionStore_tools_2023/QuickPane.tox -------------------------------------------------------------------------------- /modules/suspects/FunctionStore_tools_2023/QuickParCustom.tox: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/function-store/FunctionStore_tools/11669c6abb3796a74762ce6d3c2181b7b455ad65/modules/suspects/FunctionStore_tools_2023/QuickParCustom.tox -------------------------------------------------------------------------------- /modules/suspects/FunctionStore_tools_2023/QuickParent.tox: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/function-store/FunctionStore_tools/11669c6abb3796a74762ce6d3c2181b7b455ad65/modules/suspects/FunctionStore_tools_2023/QuickParent.tox -------------------------------------------------------------------------------- /modules/suspects/FunctionStore_tools_2023/QuickTime.tox: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/function-store/FunctionStore_tools/11669c6abb3796a74762ce6d3c2181b7b455ad65/modules/suspects/FunctionStore_tools_2023/QuickTime.tox -------------------------------------------------------------------------------- /modules/suspects/FunctionStore_tools_2023/ResetPLS1.tox: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/function-store/FunctionStore_tools/11669c6abb3796a74762ce6d3c2181b7b455ad65/modules/suspects/FunctionStore_tools_2023/ResetPLS1.tox -------------------------------------------------------------------------------- /modules/suspects/FunctionStore_tools_2023/SetSmoothness.tox: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/function-store/FunctionStore_tools/11669c6abb3796a74762ce6d3c2181b7b455ad65/modules/suspects/FunctionStore_tools_2023/SetSmoothness.tox -------------------------------------------------------------------------------- /modules/suspects/FunctionStore_tools_2023/SwapOps.tox: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/function-store/FunctionStore_tools/11669c6abb3796a74762ce6d3c2181b7b455ad65/modules/suspects/FunctionStore_tools_2023/SwapOps.tox -------------------------------------------------------------------------------- /modules/suspects/FunctionStore_tools_2023/SwitchOPs.tox: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/function-store/FunctionStore_tools/11669c6abb3796a74762ce6d3c2181b7b455ad65/modules/suspects/FunctionStore_tools_2023/SwitchOPs.tox -------------------------------------------------------------------------------- /modules/suspects/FunctionStore_tools_2023/UPDATER.tox: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/function-store/FunctionStore_tools/11669c6abb3796a74762ce6d3c2181b7b455ad65/modules/suspects/FunctionStore_tools_2023/UPDATER.tox -------------------------------------------------------------------------------- /modules/suspects/FunctionStore_tools_2023/VSCodeTools.tox: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/function-store/FunctionStore_tools/11669c6abb3796a74762ce6d3c2181b7b455ad65/modules/suspects/FunctionStore_tools_2023/VSCodeTools.tox -------------------------------------------------------------------------------- /modules/suspects/FunctionStore_tools_2023/VSCodeTools/ClearScriptSyncFile.tox: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/function-store/FunctionStore_tools/11669c6abb3796a74762ce6d3c2181b7b455ad65/modules/suspects/FunctionStore_tools_2023/VSCodeTools/ClearScriptSyncFile.tox -------------------------------------------------------------------------------- /modules/suspects/FunctionStore_tools_2023/VSCodeTools/OpenVSCode.tox: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/function-store/FunctionStore_tools/11669c6abb3796a74762ce6d3c2181b7b455ad65/modules/suspects/FunctionStore_tools_2023/VSCodeTools/OpenVSCode.tox -------------------------------------------------------------------------------- /modules/suspects/FunctionStore_tools_2023/VSCodeTools/ScriptSyncFile.tox: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/function-store/FunctionStore_tools/11669c6abb3796a74762ce6d3c2181b7b455ad65/modules/suspects/FunctionStore_tools_2023/VSCodeTools/ScriptSyncFile.tox -------------------------------------------------------------------------------- /modules/suspects/FunctionStore_tools_2023/VSCodeTools/TDTypings.tox: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/function-store/FunctionStore_tools/11669c6abb3796a74762ce6d3c2181b7b455ad65/modules/suspects/FunctionStore_tools_2023/VSCodeTools/TDTypings.tox -------------------------------------------------------------------------------- /modules/suspects/FunctionStore_tools_2023/VSCodeTools/stubser.tox: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/function-store/FunctionStore_tools/11669c6abb3796a74762ce6d3c2181b7b455ad65/modules/suspects/FunctionStore_tools_2023/VSCodeTools/stubser.tox -------------------------------------------------------------------------------- /modules/suspects/FunctionStore_tools_2023/VSCodeTools/stubser_wrapper.tox: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/function-store/FunctionStore_tools/11669c6abb3796a74762ce6d3c2181b7b455ad65/modules/suspects/FunctionStore_tools_2023/VSCodeTools/stubser_wrapper.tox -------------------------------------------------------------------------------- /modules/suspects/FunctionStore_tools_2023/VSCodeTools/stubser_wrapper/stubser.tox: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/function-store/FunctionStore_tools/11669c6abb3796a74762ce6d3c2181b7b455ad65/modules/suspects/FunctionStore_tools_2023/VSCodeTools/stubser_wrapper/stubser.tox -------------------------------------------------------------------------------- /modules/suspects/FunctionStore_tools_2023/iopPromoter.tox: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/function-store/FunctionStore_tools/11669c6abb3796a74762ce6d3c2181b7b455ad65/modules/suspects/FunctionStore_tools_2023/iopPromoter.tox -------------------------------------------------------------------------------- /modules/suspects/FunctionStore_tools_2023/mapTables/ExternalTables.tox: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/function-store/FunctionStore_tools/11669c6abb3796a74762ce6d3c2181b7b455ad65/modules/suspects/FunctionStore_tools_2023/mapTables/ExternalTables.tox -------------------------------------------------------------------------------- /modules/suspects/FunctionStore_tools_2023/midiMapper.tox: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/function-store/FunctionStore_tools/11669c6abb3796a74762ce6d3c2181b7b455ad65/modules/suspects/FunctionStore_tools_2023/midiMapper.tox -------------------------------------------------------------------------------- /modules/suspects/FunctionStore_tools_2023/midiMapper/MIDIResetPLS.tox: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/function-store/FunctionStore_tools/11669c6abb3796a74762ce6d3c2181b7b455ad65/modules/suspects/FunctionStore_tools_2023/midiMapper/MIDIResetPLS.tox -------------------------------------------------------------------------------- /modules/suspects/FunctionStore_tools_2023/midiMapperMulti.tox: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/function-store/FunctionStore_tools/11669c6abb3796a74762ce6d3c2181b7b455ad65/modules/suspects/FunctionStore_tools_2023/midiMapperMulti.tox -------------------------------------------------------------------------------- /modules/suspects/FunctionStore_tools_2023/op_store_mod.tox: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/function-store/FunctionStore_tools/11669c6abb3796a74762ce6d3c2181b7b455ad65/modules/suspects/FunctionStore_tools_2023/op_store_mod.tox -------------------------------------------------------------------------------- /modules/suspects/FunctionStore_tools_2023/oscMapper.tox: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/function-store/FunctionStore_tools/11669c6abb3796a74762ce6d3c2181b7b455ad65/modules/suspects/FunctionStore_tools_2023/oscMapper.tox -------------------------------------------------------------------------------- /modules/suspects/FunctionStore_tools_2023/paste_from_clipboard.tox: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/function-store/FunctionStore_tools/11669c6abb3796a74762ce6d3c2181b7b455ad65/modules/suspects/FunctionStore_tools_2023/paste_from_clipboard.tox -------------------------------------------------------------------------------- /modules/suspects/FunctionStore_tools_2023/tools_ui.tox: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/function-store/FunctionStore_tools/11669c6abb3796a74762ce6d3c2181b7b455ad65/modules/suspects/FunctionStore_tools_2023/tools_ui.tox -------------------------------------------------------------------------------- /modules/suspects/private_investigator1_withmyhacks.tox: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/function-store/FunctionStore_tools/11669c6abb3796a74762ce6d3c2181b7b455ad65/modules/suspects/private_investigator1_withmyhacks.tox -------------------------------------------------------------------------------- /scripts/AltSelect/AltSelectExt.py: -------------------------------------------------------------------------------- 1 | class AltSelectExt: 2 | """ 3 | AltSelectExt description 4 | """ 5 | def __init__(self, ownerComp): 6 | # The component to which this extension is attached 7 | self.ownerComp = ownerComp 8 | self.lastSelectedPos = (0, 0) 9 | self.family_to_selectpar = { 10 | 'SOP':'sop', 11 | 'TOP':'top', 12 | 'CHOP':'chops', 13 | 'DAT':'dat', 14 | 'COMP':'selectpanel' 15 | } 16 | self.selectColor = (0.71, 0.53, 0.16) 17 | self.lastSelectedPos = None 18 | 19 | 20 | def OnSelectOP(self, _op): 21 | if _op: 22 | self.lastSelectedPos = {'op_pos': (_op.nodeX, _op.nodeY), 'docked_pos': {_dock: (_dock.nodeX, _dock.nodeY) for _dock in _op.docked}} 23 | 24 | 25 | @property 26 | def hotkey(self) -> bool: 27 | return bool(self.ownerComp.op('null_hk')[0].eval()) 28 | 29 | 30 | def OnUpdate(self, _op: OP): 31 | if not self.hotkey: 32 | return 33 | 34 | new_pos = (_op.nodeX, _op.nodeY) 35 | if self.lastSelectedPos and (new_pos != self.lastSelectedPos): 36 | self.onPosChange(_op, new_pos, self.lastSelectedPos) 37 | 38 | 39 | def onPosChange(self, _op, new_pos, last_pos): 40 | if _op.family not in self.family_to_selectpar.keys(): 41 | self.lastSelectedPos = None 42 | return 43 | # selectCOMP only works with Panel types 44 | if isinstance(_op, COMP) and not _op.isPanel: 45 | self.lastSelectedPos = None 46 | return 47 | 48 | ui.undo.startBlock('Selecting OP') #--------- 49 | 50 | sel_par = self.family_to_selectpar[_op.family] 51 | sel_fam = _op.family 52 | 53 | new_select = _op.parent().create(f'select{sel_fam}', 54 | f"select_{_op.name.split('_')[-1]}") 55 | new_select.nodeX = new_pos[0] 56 | new_select.nodeY = new_pos[1] 57 | new_select.par[sel_par] = _op 58 | new_select.viewer = True 59 | new_select.color = self.selectColor 60 | _op.selected = False 61 | new_select.selected = True 62 | new_select.current = True 63 | 64 | # I usually have this enabled anyway 65 | if _op.isPanel: 66 | new_select.par.matchsize = True 67 | 68 | ui.undo.endBlock() # ------------------------- 69 | 70 | # restore pos 71 | _op.nodeX = last_pos['op_pos'][0] 72 | _op.nodeY = last_pos['op_pos'][1] 73 | 74 | for _dock in _op.docked: 75 | if _dock in last_pos['docked_pos'].keys(): 76 | _dock.nodeX = last_pos['docked_pos'][_dock][0] 77 | _dock.nodeY = last_pos['docked_pos'][_dock][1] 78 | -------------------------------------------------------------------------------- /scripts/AutoCombine/AutoCombineExt.py: -------------------------------------------------------------------------------- 1 | """ 2 | Extension classes enhance TouchDesigner components with python. An 3 | extension is accessed via ext.ExtensionClassName from any operator 4 | within the extended component. If the extension is promoted via its 5 | Promote Extension parameter, all its attributes with capitalized names 6 | can be accessed externally, e.g. op('yourComp').PromotedFunction(). 7 | 8 | Help: search "Extensions" in wiki 9 | """ 10 | 11 | from TDStoreTools import StorageManager 12 | import TDFunctions as TDF 13 | 14 | class AutoCombineExt: 15 | """ 16 | AutoCombineExt description 17 | """ 18 | def __init__(self, ownerComp): 19 | # The component to which this extension is attached 20 | self.ownerComp = ownerComp 21 | 22 | def SetCombine(self, _op): 23 | if not _op.inputs: 24 | return 25 | if self.ownerComp.op('null_hk')['activate'].eval(): 26 | if _op.pars('combineinput'): 27 | try: 28 | _op.par.combineinput.val = parent.AutoCombine.par.Combineinput.eval() 29 | _op.par.operand.val = parent.AutoCombine.par.Operand.eval() 30 | except: 31 | pass 32 | if _op.pars('rgb'): 33 | try: 34 | _op.par.rgb.val = parent.AutoCombine.par.Rgb.eval() 35 | except: 36 | pass 37 | if _op.pars('format'): 38 | _op.par.format.val = parent.AutoCombine.par.Format.eval() 39 | pass -------------------------------------------------------------------------------- /scripts/AutoRes/AutoResExt.py: -------------------------------------------------------------------------------- 1 | """ 2 | Extension classes enhance TouchDesigner components with python. An 3 | extension is accessed via ext.ExtensionClassName from any operator 4 | within the extended component. If the extension is promoted via its 5 | Promote Extension parameter, all its attributes with capitalized names 6 | can be accessed externally, e.g. op('yourComp').PromotedFunction(). 7 | 8 | Help: search "Extensions" in wiki 9 | """ 10 | 11 | from TDStoreTools import StorageManager 12 | import TDFunctions as TDF 13 | 14 | class AutoResExt: 15 | """ 16 | AutoResExt description 17 | """ 18 | def __init__(self, ownerComp): 19 | # The component to which this extension is attached 20 | self.ownerComp = ownerComp 21 | 22 | def SetRes(self, _op): 23 | if self.ownerComp.op('null_hk')['activate'].eval() and _op.pars('outputresolution'): 24 | if not _op.inputs: 25 | if _op.par.outputresolution.enable and not _op.isFilter: 26 | 27 | parentPanel = False 28 | i = 1 29 | try: 30 | while not parentPanel: 31 | parentPanel = _op.parent(i).isPanel 32 | i += 1 33 | except: 34 | pass 35 | if parentPanel: 36 | _op.par.outputresolution.val = 10 37 | else: 38 | _op.par.outputresolution.val = 9 39 | _op.par.resolutionw.expr = "tdu.tryExcept(lambda: parent.Project.width, op.AUTO_RES.par.Resolutionw)" 40 | _op.par.resolutionh.expr = "tdu.tryExcept(lambda: parent.Project.height, op.AUTO_RES.par.Resolutionh)" 41 | 42 | if _op.pars('rgb'): 43 | try: 44 | _op.par.rgb.val = parent.AutoRes.par.Rgb.eval() 45 | except: 46 | pass 47 | if _op.pars('format'): 48 | _op.par.format.val = parent.AutoRes.par.Format.eval() 49 | 50 | pass -------------------------------------------------------------------------------- /scripts/BorderlessWindow/install.py: -------------------------------------------------------------------------------- 1 | targets = [op('/ui/dialogs/mainmenu')] 2 | # 3 | containers = [op('projname')] 4 | containers = sorted(containers, key=lambda x: x.nodeX) 5 | for target in targets: 6 | for i, cont in enumerate(containers): 7 | if _op := target.op(cont.name): 8 | _op.destroy() 9 | newOP = target.copy(cont) 10 | newOP.nodeX = 500 + i*200 11 | newOP.nodeY = -400 12 | try: 13 | newOP.inputCOMPConnectors[0].connect(target.op('emptypanel').outputCOMPConnectors[0]) 14 | except: 15 | pass 16 | newOP.allowCooking = True 17 | 18 | ui.status = 'Function Store - Navbar installed' -------------------------------------------------------------------------------- /scripts/ClearPars/ClearParsExt.py: -------------------------------------------------------------------------------- 1 | 2 | class ClearParsExt: 3 | """ 4 | ClearParsExt description 5 | """ 6 | def __init__(self, ownerComp): 7 | # The component to which this extension is attached 8 | self.ownerComp = ownerComp 9 | self.Op = None 10 | 11 | def ClearPars(self): 12 | 13 | _ops = [self.Op] 14 | 15 | if self.ownerComp.par.Recursive.eval(): 16 | if _op := _ops[0]: 17 | if _op.isCOMP: 18 | _ops.extend(_op.findChildren(depth=1, includeUtility=False, key=lambda _o: _o.opType != 'annotateCOMP' and 'annotate' not in _o.name)) 19 | 20 | ui.undo.startBlock('Clear Par Errors') 21 | for _op in _ops: 22 | if _op is None: 23 | continue 24 | for _par in _op.pars(): 25 | if _par.name == 'autoexportroot' or 'expr' in _par.name: 26 | continue 27 | if _par.valid: 28 | if _par.mode == ParMode.BIND: 29 | if _par.bindMaster == None: 30 | _par.bindExpr = None 31 | _par.expr = None 32 | _par.mode = ParMode.CONSTANT 33 | pass 34 | if _par.mode == ParMode.EXPRESSION: 35 | try: 36 | _par.eval() 37 | except: 38 | _par.expr = None 39 | _par.bindExpr = None 40 | _par.mode = ParMode.CONSTANT 41 | 42 | if _op.isCOMP and _op.opType != annotateCOMP: 43 | _op.clearScriptErrors(recurse=True) 44 | ui.undo.endBlock() -------------------------------------------------------------------------------- /scripts/CustomParPromoter/PopDialogExt1.py: -------------------------------------------------------------------------------- 1 | # This file and all related intellectual property rights are 2 | # owned by Derivative Inc. ("Derivative"). The use and modification 3 | # of this file is governed by, and only permitted under, the terms 4 | # of the Derivative [End-User License Agreement] 5 | # [https://www.derivative.ca/Agreements/UsageAgreementTouchDesigner.asp] 6 | # (the "License Agreement"). Among other terms, this file can only 7 | # be used, and/or modified for use, with Derivative's TouchDesigner 8 | # software, and only by employees of the organization that has licensed 9 | # Derivative's TouchDesigner software by [accepting] the License Agreement. 10 | # Any redistribution or sharing of this file, with or without modification, 11 | # to or with any other person is strictly prohibited [(except as expressly 12 | # permitted by the License Agreement)]. 13 | # 14 | # Version: 099.2017.30440.28Sep 15 | # 16 | # _END_HEADER_# 17 | 18 | from TDStoreTools import StorageManager 19 | import TDFunctions as TDF 20 | class PopDialogExt: 21 | 22 | def __init__(self, ownerComp): 23 | """ 24 | Popup dialog extension. Just call the DoPopup method to create a popup. 25 | Provide info in that method. This component can be used over and over, 26 | no need for a different component for each dialog, unless you want to 27 | change the insides. 28 | """ 29 | self.ownerComp = ownerComp 30 | self.windowComp = ownerComp.op('popDialogWindow') 31 | self.details = None 32 | self.entries = self.ownerComp.ops('entry*') 33 | self.checkBoxes = self.ownerComp.ops('entry*/buttonCheckbox') 34 | for entry in self.entries: 35 | entry_index = tdu.digits(entry.name) 36 | TDF.createProperty(self, f'EnteredText{entry_index}', value='') 37 | for idx, checkBox in enumerate(self.checkBoxes): 38 | TDF.createProperty(self, f'CheckBox{idx+1}', value=False) 39 | 40 | 41 | # upgrade version 42 | self.ownerComp.par.Version = self.ownerComp.par.clone.eval().par.Version 43 | h = self.ownerComp.par.h 44 | if h.mode == ParMode.EXPRESSION and h.expr == "op('./dialog').par.h": 45 | h.expr = "me.DialogHeight" 46 | h.readOnly = True 47 | 48 | TDF.createProperty(self, 'TextHeight', value=0) 49 | run("args[0].UpdateTextHeight()", self, delayFrames=1, 50 | delayRef=op.TDResources) 51 | 52 | def OpenDefault(self, text='', title='', buttons=('OK',), callback=None, 53 | details=None, textEntry1=False, textEntry2=False, textEntries=None, escButton=1, 54 | escOnClickAway=True, enterButton=1): 55 | self.Open(text, title, buttons, callback, details, textEntry1, textEntry2, textEntries, escButton, 56 | escOnClickAway, enterButton) 57 | 58 | def Open(self, text=None, title=None, buttons=None, callback=None, 59 | details=None, textEntry1=None, textEntry2=None, textEntries=None, escButton=None, 60 | escOnClickAway=None, enterButton=None): 61 | """ 62 | Open a popup dialog. 63 | text goes in the center of the dialog. Default None, use pars. 64 | title goes on top of the dialog. Blank means no title bar. Default None, 65 | use pars 66 | buttons is a list of strings. The number of buttons is equal to the 67 | number of buttons, up to 4. Default is ['OK'] 68 | callback: a method that will be called when a selection is made, see the 69 | SetCallback method. This is in addition to all internal callbacks. 70 | If not provided, Callback DAT will be searched. 71 | details: will be passed to callback in addition to item chosen. 72 | Default is None. 73 | If textEntry is a string, display textEntry field and use the string 74 | as a default. If textEntry is False, no entry field. Default is 75 | None, use pars 76 | escButton is a number from 1-4 indicating which button is simulated when 77 | esc is pressed or False for no button simulation. Default is None, 78 | use pars. First button is 1 not 0!!! 79 | enterButton is a number from 1-4 indicating which button is simulated 80 | when enter is pressed or False for no button simulation. Default is 81 | None, use pars. First button is 1 not 0!!! 82 | escOnClickAway is a boolean indicating whether esc is simulated when user 83 | clicks somewhere besides the dialog. Default is None, use pars 84 | """ 85 | self.Close() 86 | # text and title 87 | if text is not None: 88 | self.ownerComp.par.Text = text 89 | if title is not None: 90 | self.ownerComp.par.Title = title 91 | # buttons 92 | if buttons is not None: 93 | if not isinstance(buttons, list): 94 | buttons = ['OK'] 95 | self.ownerComp.par.Buttons = len(buttons) 96 | for i, label in enumerate(buttons[:4]): 97 | getattr(self.ownerComp.par, 98 | 'Buttonlabel' + str(i + 1)).val = label 99 | # callbacks 100 | if callback: 101 | ext.CallbacksExt.SetAssignedCallback('onSelect', callback) 102 | else: 103 | ext.CallbacksExt.SetAssignedCallback('onSelect', None) 104 | # textEntry 105 | if textEntry1 is not None: 106 | if isinstance(textEntry1, str): 107 | self.ownerComp.par.Textentryarea = True 108 | self.ownerComp.par.Textentrydefault = str(textEntry1) 109 | elif textEntry1: 110 | self.ownerComp.par.Textentryarea = True 111 | self.ownerComp.par.Textentrydefault = '' 112 | else: 113 | self.ownerComp.par.Textentryarea = False 114 | if textEntry2 is not None: 115 | if isinstance(textEntry2, str): 116 | self.ownerComp.par.Textentryarea2 = True 117 | self.ownerComp.par.Textentrydefault2 = str(textEntry2) 118 | elif textEntry2: 119 | self.ownerComp.par.Textentryarea2 = True 120 | self.ownerComp.par.Textentrydefault2 = '' 121 | 122 | # self.EnteredText1 = self.ownerComp.par.Textentrydefault.eval() 123 | # self.EnteredText2 = self.ownerComp.par.Textentrydefault2.eval() 124 | for idx, (entry, text) in enumerate(zip(self.entries, textEntries or [])): 125 | if text is not None: 126 | setattr(self, f'EnteredText{idx + 1}', text) 127 | 128 | # set focus to first entry0 129 | 130 | run('op("' + self.entries[0].path + '").op("inputText").setKeyboardFocus(selectAll=True)', 131 | delayFrames=3, delayRef=op.TDResources) 132 | # if app.osName == 'Windows': 133 | self.entries[0].op('inputText').setKeyboardFocus(selectAll=True) 134 | 135 | for checkBox_index, checkbox in enumerate(self.checkBoxes): 136 | if checkbox.par.Value0.eval(): 137 | setattr(self, f'CheckBox{checkBox_index}', False) 138 | 139 | self.details = details 140 | for idx, entry in enumerate(self.entries): 141 | entry.op('inputText').par.text = getattr(self, f'EnteredText{idx + 1}') 142 | entry.op('inputText').cook(force=True) 143 | for idx, checkBox in enumerate(self.checkBoxes): 144 | checkBox.par.Value0.val = getattr(self, f'CheckBox{idx}') 145 | 146 | if escButton is not None: 147 | if escButton is False or not (1 <= escButton <= 4): 148 | self.ownerComp.par.Escbutton = 'None' 149 | else: 150 | self.ownerComp.par.Escbutton = str(escButton) 151 | if escOnClickAway is not None: 152 | self.ownerComp.par.Esconclickaway = escOnClickAway 153 | if enterButton is not None: 154 | if enterButton is False or not (1 <= enterButton <= 4): 155 | self.ownerComp.par.Enterbutton = 'None' 156 | else: 157 | self.ownerComp.par.Enterbutton = str(enterButton) 158 | self.UpdateTextHeight() 159 | # HACK shouldn't be necessary - problem with clones/replicating 160 | self.ownerComp.op('replicator1').par.recreateall.pulse() 161 | self.actualOpen() 162 | # run("op('" + self.ownerComp.path + "').ext.PopDialogExt.actualOpen()", 163 | # delayFrames=1, delayRef=op.TDResources) 164 | 165 | def actualOpen(self): 166 | # needs to be deferred so that sizes can update properly 167 | self.windowComp.par.winopen.pulse() 168 | ext.CallbacksExt.DoCallback('onOpen') 169 | if self.ownerComp.op('entry1').par.display.eval(): 170 | self.ownerComp.setFocus() 171 | # self.ownerComp.op('entry1/inputText').setKeyboardFocus(selectAll=True) 172 | # hack shouldn't have to wait a frame 173 | run('op("' + self.ownerComp.path + '").op("entry1/inputText").' 174 | 'setKeyboardFocus(selectAll=True)', 175 | delayFrames=1, delayRef=op.TDResources) 176 | if app.osName == 'Windows': 177 | self.ownerComp.op('entry1/inputText').setKeyboardFocus(selectAll=True) 178 | else: 179 | self.ownerComp.setFocus() 180 | 181 | def Close(self): 182 | """ 183 | Close the dialog 184 | """ 185 | #ext.CallbacksExt.SetAssignedCallback('onSelect', None) 186 | ext.CallbacksExt.DoCallback('onClose') 187 | self.windowComp.par.winclose.pulse() 188 | for idx, entry in enumerate(self.entries): 189 | setattr(self, f'EnteredText{idx + 1}', entry.op('inputText').par.text) 190 | for idx, checkBox in enumerate(self.checkBoxes): 191 | setattr(self, f'CheckBox{idx}', checkBox.par.Value0.eval()) 192 | 193 | def OnButtonClicked(self, buttonNum): 194 | """ 195 | Callback from buttons 196 | """ 197 | infoDict = {'buttonNum': buttonNum, 198 | 'button': getattr(self.ownerComp.par, 199 | 'Buttonlabel' + str(buttonNum)).eval(), 200 | 'details': self.details} 201 | if self.ownerComp.par.Textentryarea.eval(): 202 | infoDict['enteredText'] = [] 203 | infoDict['checkBoxes'] = [] 204 | for entry in self.entries: 205 | infoDict['enteredText'].append(entry.op('inputText').par.text.eval()) 206 | for checkBox in self.checkBoxes: 207 | infoDict['checkBoxes'].append(checkBox.par.Value0.eval()) 208 | 209 | try: 210 | ext.CallbacksExt.DoCallback('onSelect', infoDict) 211 | finally: 212 | self.Close() 213 | 214 | def OnKeyPressed(self, key): 215 | """ 216 | Callback for esc or enterpressed. 217 | """ 218 | if key == 'esc' and self.ownerComp.par.Escbutton.eval() != 'None': 219 | button = int(self.ownerComp.par.Escbutton.eval()) 220 | if button <= self.ownerComp.par.Buttons: 221 | self.OnButtonClicked(button) 222 | elif key == 'enter' and self.ownerComp.par.Enterbutton.eval() != 'None': 223 | button = int(self.ownerComp.par.Enterbutton.eval()) 224 | if button <= self.ownerComp.par.Buttons: 225 | self.OnButtonClicked(button) 226 | elif key == 'tab' and not self.ownerComp.op('KeyModifiers1/out1')['shift'].eval(): 227 | # get currently focused entry and move to next entry 228 | currIdx = None 229 | for idx, entry in enumerate(self.entries): 230 | if entry.op('inputText').panel.focusselect: 231 | currIdx = idx 232 | break 233 | if currIdx is not None: 234 | newEntry = self.entries[(currIdx + 1) % len(self.entries)] 235 | # debug(f'new entry: {newEntry.path}') 236 | run('op("' + newEntry.path + '").op("inputText").setKeyboardFocus(selectAll=True)', 237 | delayFrames=1, delayRef=op.TDResources) 238 | if app.osName == 'Windows': 239 | newEntry.op('inputText').setKeyboardFocus(selectAll=True) 240 | elif key == 'shift.tab': 241 | # get currently focused entry and move to previous entry 242 | currIdx = None 243 | for idx, entry in enumerate(self.entries): 244 | if entry.op('inputText').panel.focusselect: 245 | currIdx = idx 246 | break 247 | if currIdx is not None: 248 | newEntry = self.entries[(currIdx - 1) % len(self.entries)] 249 | run('op("' + newEntry.path + '").op("inputText").setKeyboardFocus(selectAll=True)', 250 | delayFrames=1, delayRef=op.TDResources) 251 | if app.osName == 'Windows': 252 | newEntry.op('inputText').setKeyboardFocus(selectAll=True) 253 | 254 | 255 | def OnClickAway(self): 256 | """ 257 | Callback for esc pressed. Only happens when Escbutton is not None 258 | """ 259 | if self.ownerComp.par.Esconclickaway.eval(): 260 | self.OnKeyPressed('esc') 261 | 262 | def OnParValueChange(self, par, val, prev): 263 | """ 264 | Callback for when parameters change 265 | """ 266 | if par.name == "Textentryarea": 267 | self.ownerComp.par.Textentrydefault.enable = par.eval() 268 | if par.name == "Escbutton": 269 | self.ownerComp.par.Esconclickaway.enable = par.eval() != "None" 270 | 271 | def OnParPulse(self, par): 272 | if par.name == "Open": 273 | self.Open() 274 | elif par.name == "Close": 275 | self.Close() 276 | elif par.name == 'Editcallbacks': 277 | dat = self.ownerComp.par.Callbackdat.eval() 278 | if dat: 279 | dat.par.edit.pulse() 280 | else: 281 | print("No callback dat for", self.ownerComp.path) 282 | elif par.name == 'Helppage': 283 | ui.viewFile('https://docs.derivative.ca/' 284 | 'index.php?title=Palette:popDialog') 285 | 286 | def UpdateTextHeight(self): 287 | self.TextHeight = self.ownerComp.op('text/text').evalTextSize( 288 | self.ownerComp.par.Text)[1] 289 | 290 | @property 291 | def DialogHeight(self): 292 | return 65 + self.TextHeight*0 + \ 293 | (20 if self.ownerComp.par.Title else 0) + \ 294 | (37 if self.ownerComp.par.Textentryarea else 0) * len([entry for entry in self.entries if entry.par.display.eval()]) - 20 -------------------------------------------------------------------------------- /scripts/ExprHotStrings/extExprHotString.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | class extExprHotString: 4 | """ 5 | extExprHotString description 6 | """ 7 | def __init__(self, ownerComp): 8 | # The component to which this extension is attached 9 | self.ownerComp = ownerComp 10 | self.opShortStr = '#@' 11 | self.parentShortStr = '#!' 12 | self.hotstrings = op('ExprHotStrings_tab') 13 | 14 | def opWrap(self, text): 15 | # Handling the #@something@ case 16 | # Examples: 17 | # #@something@ -> op('something') 18 | # #@.something@ -> op('something') 19 | text = re.sub(r'#@\.?([a-zA-Z0-9_/]+)@', r"op('\1')", text) 20 | 21 | # # Handling the case where dot appears immediately after #@ 22 | # text = re.sub(r'#@(\./[a-zA-Z0-9_]+)', r"op('\1')", text) 23 | 24 | # Handling the .something case 25 | # Examples: 26 | # #@something.else -> op('something').else 27 | # #@.something.else -> op('something').else 28 | dot_match = re.search(r'#@\.?([a-zA-Z0-9_/]+)\.([a-zA-Z0-9_]+)', text) 29 | if dot_match: 30 | main_text, dot_text = dot_match.groups() 31 | text = text.replace(dot_match.group(0), f"op('{main_text}').{dot_text}") 32 | 33 | # Default handling 34 | # Examples: 35 | # #@something -> op('something') 36 | # #@.something -> op('something') 37 | text = re.sub(r'#@\.?([a-zA-Z0-9_/]+)', r"op('\1')", text) 38 | 39 | return text 40 | 41 | def parentWrap(self, _op, text): 42 | _parent = self.closestParent(_op) 43 | # Handle parameter reference case first (#!!) 44 | # Examples: 45 | # #!!something -> parent.par.something 46 | # #!!.something -> parent.par.something 47 | # #!! -> parent.par 48 | text = re.sub(r'#!!(\.?)(.*)', lambda m: f"{_parent}.par{m.group(1) or ('.' if m.group(2).strip() else '')}{m.group(2)}", text) 49 | 50 | # Then handle normal case 51 | # Examples: 52 | # #!something -> parent.something 53 | # #!.something -> parent.something 54 | # #! -> parent 55 | text = re.sub(r'#!(\.?)(.*)', lambda m: f"{_parent}{m.group(1) or ('.' if m.group(2).strip() else '')}{m.group(2)}", text) 56 | return text 57 | 58 | 59 | def can_transform_pattern(self, text, check = '#@'): 60 | # Check if text is inside single or double quotes 61 | single_quote_match = re.search(r"'[^']*" + check + r"[^']*'", text) 62 | double_quote_match = re.search(r'"[^"]*' + check + r'[^"]*"', text) 63 | 64 | # If inside f-string, allow transformation inside curly braces 65 | if text.startswith("f'") or text.startswith('f"'): 66 | f_string_match = re.search(r'f[\'"][^\'"]*{[^}]*' + check + r'[^}]*}[^\'"]*[\'"]', text) 67 | return f_string_match is not None 68 | elif single_quote_match is not None or double_quote_match is not None: 69 | return True 70 | else: 71 | # Return True if #@ pattern is present and not inside any string 72 | return check in text 73 | 74 | 75 | def HotstringCheck(self, OP, PAR, EXPR, BINDEXPR): 76 | if OP == 'None': 77 | return 78 | op_par = op(OP).par[PAR] 79 | if op_par.mode == ParMode.BIND: 80 | EXPR = BINDEXPR 81 | if op_par is None: 82 | return 83 | if op_par.mode in [ParMode.EXPRESSION, ParMode.CONSTANT, ParMode.BIND] and EXPR != 'None': 84 | expands = None 85 | 86 | if self.ownerComp.par.Openable.eval() and self.opShortStr in EXPR and self.can_transform_pattern(EXPR, self.opShortStr): 87 | expands = EXPR = self.opWrap(EXPR) 88 | if self.ownerComp.par.Parentshortcutenable.eval() and self.parentShortStr in EXPR and self.can_transform_pattern(EXPR, self.parentShortStr): 89 | expands = EXPR = self.parentWrap(OP, EXPR) 90 | 91 | if self.ownerComp.par.Replaceinline: 92 | # match longest abbreviation first 93 | abvs = [(cell.row, cell.val) for cell in self.hotstrings.col('Abbreviation')[1:]] 94 | if not abvs: 95 | return 96 | abvs.sort(key=lambda x: len(x[1]),reverse=True) 97 | abbreviation = [s for s in abvs if s[1] in EXPR] 98 | 99 | if abbreviation: 100 | abbreviation = abbreviation[0] 101 | expands = self.hotstrings.cell(abbreviation[0], 'Expands', val=True) 102 | if expands is None or expands == '': 103 | return 104 | expands = re.sub(abbreviation[1], expands, EXPR) 105 | 106 | else: 107 | hotstring = self.hotstrings.findCell(EXPR, cols = ['Abbreviation'], valuePattern=False, caseSensitive=True) 108 | if hotstring: 109 | expands = self.hotstrings.cell(hotstring.row, 'Expands', val=True) 110 | 111 | if expands: 112 | # replace 113 | if op_par.mode == ParMode.BIND: 114 | op_par.bindExpr = expands 115 | else: 116 | op_par.expr = expands 117 | # recurse in case multiple abbreviations were used 118 | self.HotstringCheck(OP,PAR,expands, expands) 119 | 120 | 121 | def closestParent(self, _op): 122 | # Start with the given operator 123 | current_op = op(_op) 124 | visited = set() # Keep track of visited operators to prevent loops 125 | 126 | while current_op: 127 | # Add current operator to visited set 128 | visited.add(current_op.path) 129 | 130 | # Get parent 131 | parent = current_op.parent() 132 | if not parent: 133 | return 'parent()' 134 | 135 | # Check if we've already visited this parent 136 | if parent.path in visited: 137 | return 'parent()' 138 | 139 | # Check for parent shortcut 140 | if _shortcut := parent.par.parentshortcut.eval(): 141 | return f'parent.{_shortcut}' 142 | 143 | # Move up to next parent 144 | current_op = parent 145 | 146 | return 'parent()' 147 | -------------------------------------------------------------------------------- /scripts/FNS_Config/ExtFNSConfig.py: -------------------------------------------------------------------------------- 1 | import TDJSON 2 | import json 3 | 4 | class ExtFNSConfig: 5 | def __init__(self, ownerComp): 6 | self.ownerComp = ownerComp 7 | self.exceptions = ['Version Ctrl','About','Info','Callbacks'] 8 | self.extraAttrs = ['val','eval','expr','bindExpr','mode'] 9 | 10 | @property 11 | def file_path(self): 12 | return self.ownerComp.par.Path.eval() 13 | 14 | def filter_keys_from_second_level(self, nested_dict, keys_to_remove): 15 | # Create a new dictionary to hold the filtered result 16 | filtered_dict = {} 17 | 18 | # Iterate over the first-level keys and values 19 | for key, sub_dict in nested_dict.items(): 20 | if isinstance(sub_dict, dict): 21 | # Create a new sub-dictionary excluding the specified keys 22 | filtered_sub_dict = {sub_key: sub_value for sub_key, sub_value in sub_dict.items() if sub_key not in keys_to_remove} 23 | filtered_dict[key] = filtered_sub_dict 24 | else: 25 | # If the value is not a dictionary, just copy it over 26 | filtered_dict[key] = sub_dict 27 | 28 | return filtered_dict 29 | 30 | def SaveAllToJSON(self): 31 | debug('Saving all configs to JSON') 32 | full_data = {} 33 | full_data['op.FNS'] = TDJSON.opToJSONOp(op.FNS, forceAttrLists=False, extraAttrs=self.extraAttrs) 34 | for _op in parent.FNS.findChildren(depth=1, key=lambda x: x.family == 'COMP' and not x.type in ['annotate','comment','network']): 35 | if _op is not None: 36 | full_data[f"op.FNS.op('{_op.name}')"] = TDJSON.opToJSONOp(_op, forceAttrLists=False, extraAttrs=self.extraAttrs) 37 | 38 | filtered_data = self.filter_keys_from_second_level(full_data, self.exceptions) 39 | self.toFile(filtered_data) 40 | 41 | def toFile(self, data): 42 | with open(self.file_path, 'w') as fp: 43 | json.dump(data, fp) 44 | 45 | def LoadAllFromJSON(self): 46 | debug('Loading all configs from JSON') 47 | with open(self.file_path) as json_file: 48 | full_data = json.load(json_file) 49 | for _comp_shortcut, _data in full_data.items(): 50 | try: 51 | _op = eval(_comp_shortcut) 52 | TDJSON.addParametersFromJSONOp(_op, _data) 53 | self.dealWithExtraAttrs(_op, _data) 54 | except Exception as e: 55 | print(f"Error loading {_comp_shortcut}: {e}") 56 | #debug(_data) 57 | 58 | def dealWithExtraAttrs(self, _op, _pages_data): 59 | for _page_data in _pages_data.values(): 60 | for _par_name, _par_data in _page_data.items(): 61 | for _extra_attr in self.extraAttrs: 62 | _target_par = getattr(_op.parGroup, _par_data['name']) 63 | _extra_attr_val = _par_data[_extra_attr] 64 | if _extra_attr in ['eval']: 65 | continue 66 | if _extra_attr == 'mode': 67 | _mode = getattr(ParMode,_extra_attr_val) 68 | setattr(_target_par, _extra_attr, _mode) 69 | if _mode in [ParMode.BIND]: 70 | _target_par.val = _par_data['eval'] 71 | else: 72 | setattr(_target_par, _extra_attr, _extra_attr_val) 73 | 74 | def Saveall(self, _): 75 | self.SaveAllToJSON() 76 | 77 | def Loadall(self, _): 78 | self.LoadAllFromJSON() 79 | 80 | def OnStart(self): 81 | post_update = parent.FNS.fetch('post_update', False) 82 | if post_update or self.ownerComp.par.Autoload.eval(): 83 | self.LoadAllFromJSON() 84 | parent.FNS.unstore('post_update') 85 | # okay I really fucked this up, so mega-hack below 86 | op.FNS_RPLS.par.Limitdepth = False 87 | op.FNS_OUTPUT.par.Spoutactive = False 88 | op.FNS_OUTPUT.par.Ndiactive = False 89 | self.SaveAllToJSON() 90 | try: 91 | run( 92 | "args[0].open_changelog() if args[0] " 93 | "and hasattr(args[0], 'open_changelog') else None", 94 | self, 95 | endFrame=True, 96 | delayRef=op.TDResources 97 | ) 98 | except: 99 | pass 100 | 101 | def open_changelog(self): 102 | try: 103 | ret = ui.messageBox('FNS_tools updated', 'Would you like to see the changelog?', buttons=['No', 'Yes']) 104 | if ret == 1: 105 | ui.viewFile('https://github.com/function-store/FunctionStore_tools/releases/latest') 106 | except: 107 | pass -------------------------------------------------------------------------------- /scripts/FNS_OpMenu/ExtOpMenuMod.py: -------------------------------------------------------------------------------- 1 | import TDFunctions as TDF 2 | 3 | class ExtOpMenuMod: 4 | def __init__(self, ownerComp): 5 | self.ownerComp = ownerComp 6 | self.searchWordsTable = self.ownerComp.op('OpSearchWords') 7 | TDF.createProperty(self, 'SearchWordDict', value={}, dependable=True) 8 | self.UpdateSearchWords() 9 | 10 | def UpdateSearchWords(self): 11 | words = {} 12 | for row in self.searchWordsTable.rows()[1:]: 13 | words[row[0].val] = [_word.strip() for _word in row[1].val.split(',')] 14 | self.SearchWordDict = words -------------------------------------------------------------------------------- /scripts/FNS_OpMenu/install.py: -------------------------------------------------------------------------------- 1 | '''Info Header Start 2 | Name : install 3 | Author : Dan@DAN-4090 4 | Saveorigin : FunctionStore_tools_2023.427.toe 5 | Saveversion : 2023.11600 6 | Info Header End''' 7 | 8 | def inject(target_comp, target_op=None, inject_op=None, panelparent=None): 9 | if _op := target_comp.op(inject_op.name): 10 | if _op.outputConnectors: 11 | out_conns = _op.outputConnectors[0].connections 12 | else: 13 | out_conns = [] 14 | _op.destroy() 15 | else: 16 | if target_op and target_op.outputConnectors: 17 | out_conns = target_op.outputConnectors[0].connections 18 | else: 19 | out_conns = [] 20 | 21 | _op = target_comp.copy(inject_op) 22 | if target_op: 23 | _op.nodeX = target_op.nodeX + 150 24 | _op.nodeY = target_op.nodeY 25 | if _op.docked: 26 | _op.docked[0].nodeX = _op.nodeX 27 | _op.docked[0].nodeY = _op.nodeY - 100 28 | if _op.isPanel and panelparent and _op.inputCOMPConnectors: 29 | _op.inputCOMPConnectors[0].connect(panelparent.outputCOMPConnectors[0]) 30 | 31 | if _op.outputConnectors and out_conns: 32 | for out_conn in out_conns: 33 | _op.outputConnectors[0].connect(out_conn.owner) 34 | if _op.inputConnectors and target_op: 35 | _op.inputConnectors[0].connect(target_op) 36 | _op.bypass = False 37 | return _op 38 | 39 | # mark hack 40 | 41 | clipboard_save = ui.clipboard 42 | 43 | target_comp = op('/ui/dialogs/menu_op/nodetable') 44 | target_op = target_comp.op('families') 45 | inject_op = op('script_inject') 46 | injected_op = inject(target_comp, target_op, inject_op) 47 | 48 | # iofilter hack 49 | if parent.FNS.par.Activeopmenuiofilter.eval(): 50 | target_op = injected_op 51 | inject_op = op('IOFilter/script_IOFilter') 52 | inject(target_comp, target_op, inject_op) 53 | 54 | target_comp = op('/ui/dialogs/menu_op') 55 | inject_op = op('IOFilter/radioExpose') 56 | panelparent = target_comp.op('families') 57 | radioExpose = inject(target_comp, target_op=None, inject_op=inject_op, panelparent=panelparent) 58 | radioExpose.par.display = True 59 | 60 | 61 | # right click menu hack 62 | comp = op('/ui/dialogs/menu_op/nodetable/popMenu') 63 | 64 | comp.par.h.expr = '18 * op("./itemsLayout").numRows' 65 | 66 | items = comp.par.Items 67 | items_list = eval(items.eval()) 68 | if len(items_list) <= 3: 69 | items_list.append('Edit Templates...') 70 | items.val = str(items_list) 71 | 72 | cb = 'popMenuCallbacks' 73 | 74 | comp.parent().op(cb).text = op(cb).text 75 | 76 | op('menu_op_compatible_mod').run() 77 | ui.clipboard = clipboard_save -------------------------------------------------------------------------------- /scripts/FNS_Toolbar/ExtToolbar.py: -------------------------------------------------------------------------------- 1 | class ExtToolbar: 2 | def __init__(self, ownerComp): 3 | self.ownerComp = ownerComp 4 | 5 | # def On(self, _par, _val, _prev): 6 | # def On(self, _par): 7 | 8 | def OnInstall(self): 9 | self.postInit() 10 | 11 | def postInit(self): 12 | ui.status = 'Installed Custom Toolbar' 13 | try: 14 | target = op('/ui/dialogs/bookmark_bar') 15 | 16 | containers = op('containers').ops('*') 17 | containers = sorted(containers, key=lambda x: x.nodeX) 18 | 19 | for i, cont in enumerate(containers): 20 | if _op := target.op(cont.name): 21 | _op.destroy() 22 | newOP = target.copy(cont) 23 | newOP.nodeX = 500 + i*200 24 | newOP.nodeY = -400 25 | try: 26 | if newOP.isPanel: 27 | newOP.inputCOMPConnectors[0].connect(target.op('emptypanel').outputCOMPConnectors[0]) 28 | except: 29 | pass 30 | 31 | ui.status = 'Function Store - Toolbar installed' 32 | except: 33 | pass 34 | 35 | def CreatePars(self): 36 | owner = self.ownerComp 37 | seq = owner.seq.Def 38 | containers: COMP = op('containers') 39 | children = containers.findChildren(tags=['FNS']) 40 | 41 | # order children by _child.par.alignorder.eval() 42 | children = sorted(children, key=lambda _child: _child.par.alignorder.eval()) 43 | numChildren = len(children) 44 | debug(numChildren) 45 | seq.numBlocks = numChildren-1 46 | 47 | for _block, _child in zip(seq.blocks, children): 48 | _block.par.Comp = _child.name 49 | _block.par.Order = _child.par.alignorder.eval()-self.ownerComp.par.Layoutstart.eval() 50 | 51 | def OnResetdefs(self): 52 | mainTable = self.ownerComp.op('ToolbarDef') 53 | defaultTable = self.ownerComp.op('ToolbarDef_default') 54 | 55 | mainTable.clear() 56 | mainTable.text = defaultTable.text 57 | 58 | -------------------------------------------------------------------------------- /scripts/FNS_navbar/install.py: -------------------------------------------------------------------------------- 1 | targets = [op('/ui/dialogs/panebar/panebar_default')] 2 | targets.extend(op('/ui/panes/panebar').ops('*')) 3 | 4 | containers = op('containers').ops('*') 5 | containers = sorted(containers, key=lambda x: x.nodeX) 6 | for target in targets: 7 | for i, cont in enumerate(containers): 8 | if _op := target.op(cont.name): 9 | _op.destroy() 10 | newOP = target.copy(cont) 11 | newOP.nodeX = 500 + i*200 12 | newOP.nodeY = -400 13 | #try: 14 | #newOP.inputCOMPConnectors[0].connect(target.op('emptypanel').outputCOMPConnectors[0]) 15 | #except: 16 | #pass 17 | newOP.allowCooking = True 18 | 19 | ui.status = 'Function Store - Navbar installed' -------------------------------------------------------------------------------- /scripts/HydroHomie/HydroHomieExt.py: -------------------------------------------------------------------------------- 1 | 2 | CustomParHelper: CustomParHelper = next(d for d in me.docked if 'ExtUtils' in d.tags).mod('CustomParHelper').CustomParHelper # import 3 | ######### 4 | 5 | class HydroHomieExt: 6 | ### TODO: REFACTOR: WHY AM I SO HACKY TODAY? 7 | def __init__(self, ownerComp): 8 | CustomParHelper.Init(self, ownerComp, enable_properties=True, enable_callbacks=True) 9 | self.ownerComp = ownerComp 10 | self.Show = tdu.Dependency(False) 11 | self.timer: timerCHOP = self.ownerComp.op('timer1') 12 | self.button = self.ownerComp.op('buttonMomentary') 13 | self.onStart() 14 | 15 | @property 16 | def display(self): 17 | return self.ownerComp.par.display.eval() 18 | 19 | @display.setter 20 | def display(self, _val): 21 | self.ownerComp.par.display.val = _val 22 | 23 | def onStart(self): 24 | #self.onParEnable(self.evalEnable) 25 | if self.evalEnable: 26 | self.button.allowCooking = True 27 | self.ownerComp.op('timer3').par.start.pulse() 28 | self.timer.par.start.pulse() 29 | else: 30 | self.timer.par.initialize.pulse() 31 | self.button.allowCooking = False 32 | run('args[0].display = args[1]', self, self.evalEnable and self.evalOnstartactive, delayFrames=1) 33 | #self.display = self.evalEnable and self.evalOnstartactive 34 | #if self.evalEnable: 35 | #self.timer.par.start.pulse() 36 | 37 | 38 | def onTimerDone(self): 39 | if self.evalEnable: 40 | #self.ownerComp.op('timer3').par.initialize.pulse() 41 | self.ownerComp.op('timer3').par.start.pulse() 42 | run('args[0].display = args[1]', self, True, delayFrames=1) 43 | 44 | def Dismiss(self): 45 | #self.Show.val = False 46 | if self.evalEnable: 47 | self.timer.par.start.pulse() 48 | 49 | def onParEnable(self, _val): 50 | if _val: 51 | self.button.allowCooking = True 52 | self.ownerComp.op('timer3').par.start.pulse() 53 | run('args[0].display = args[1]', self, True, delayFrames=1) 54 | else: 55 | self.timer.par.initialize.pulse() 56 | self.button.allowCooking = False 57 | 58 | 59 | 60 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /scripts/MidiMapper/bypasser/ExtMidiBypasser.py: -------------------------------------------------------------------------------- 1 | 2 | CustomParHelper: CustomParHelper = next(d for d in me.docked if 'ExtUtils' in d.tags).mod('CustomParHelper').CustomParHelper # import 3 | ### 4 | 5 | class ExtMidiBypasser: 6 | def __init__(self, ownerComp): 7 | self.ownerComp = ownerComp 8 | CustomParHelper.Init(self, ownerComp, enable_properties=True, enable_callbacks=True) 9 | 10 | 11 | @property 12 | def AllMidiOps(self): 13 | all_midis = [] 14 | # look in operators stemming from root, but not '/local' or '/sys', or '/ui' 15 | device_id = self.evalDeviceid 16 | for _op in op('/').findChildren(key=lambda x: x.opType in ['midiinCHOP', 'midioutCHOP','midiinDAT']): 17 | if _op.path.startswith('/local') or _op.path.startswith('/sys') or _op.path.startswith('/ui'): 18 | continue 19 | all_midis.append(_op) 20 | return all_midis 21 | 22 | def DoBypass(self, _val: bool, channel: int = None): 23 | for _op in self.AllMidiOps: 24 | if channel is not None and int(_op.par.id.eval()) != channel: 25 | continue 26 | _op.bypass = _val 27 | 28 | 29 | if _val == False: 30 | to_reset = [] 31 | if self.evalResetchannels: 32 | to_reset.append('resetchannelspulse') 33 | if self.evalResetvalues: 34 | to_reset.append('resetpulse') 35 | 36 | for _p in to_reset: 37 | if _op.par[_p] is not None: 38 | _op.par[_p].pulse() 39 | 40 | def onParResetmidi(self): 41 | self.BypassOnOff(self.evalDeviceid) 42 | 43 | def OnParResetall(self): 44 | self.onParResetall() 45 | 46 | def onParResetall(self): 47 | self.BypassOnOff() 48 | pass 49 | 50 | def BypassOnOff(self, channel: int = None): 51 | self.DoBypass(True, channel) 52 | run('parent().DoBypass(False, args[0])', channel, delayMilliSeconds=self.ownerComp.par.Resetdelayms.eval(), fromOP=me) 53 | 54 | -------------------------------------------------------------------------------- /scripts/NavBar/ParentHierarchy/ParentHierarchyExt.py: -------------------------------------------------------------------------------- 1 | 2 | '''Info Header Start 3 | Name : ParentHierarchyExt 4 | Author : Dan@DAN-4090 5 | Saveorigin : FunctionStore_tools_2023.519.toe 6 | Saveversion : 2023.11880 7 | Info Header End''' 8 | 9 | import TDFunctions as TDF 10 | import TDStoreTools as TDS 11 | import re 12 | from copy import copy 13 | 14 | class ParentHierarchyExt: 15 | """ 16 | ParentHierarchyExt description 17 | """ 18 | def __init__(self, ownerComp): 19 | # The component to which this extension is attached# 20 | self.ownerComp = ownerComp 21 | # properties 22 | self.ParentShortcutList = [] 23 | self.ParentCompList = [] 24 | self.SelectedParentStr = '' 25 | self.ParentNuggetContainer = self.ownerComp.op('popMenu_nugget') 26 | self.ParentBarcontainer = self.ownerComp.op('popMenu_bar') 27 | self.current_comp_save = None 28 | self.NuggetList = TDS.DependList(['']) 29 | self.divider = ['(hold ctrl for pars)', '(hit ctrl to poll pars, click to copy ref)'] 30 | self.hasPars = False 31 | self.keepOpen = tdu.Dependency(False) 32 | self.showPars = False 33 | self.showVals = False 34 | self.idx = None 35 | self.compEditor = op('/sys/TDDialogs/CompEditor') 36 | run( 37 | "args[0].postInit() if args[0] " 38 | "and hasattr(args[0], 'postInit') else None", 39 | self, 40 | endFrame=True, 41 | delayRef=op.TDResources 42 | ) 43 | 44 | def postInit(self): 45 | TDF.createProperty(self, 'ParentShortcuts', value=self.ParentHierarchyContent(), dependable=True, 46 | readOnly=False) 47 | 48 | @property 49 | def NavbarContent(self): 50 | if self.ownerComp.op('../panenav/out1'): 51 | return self.ownerComp.op('../panenav/out1').text.strip() 52 | return None 53 | 54 | # def updateNuggetList(self, state): 55 | # # check if we have parameters in the nugget list by checking if there is an item after (!!!) the divider 56 | # if self.parsFromIndex == -1 and self.showVals: 57 | # return 58 | # nugget_list = self.NuggetList.getRaw() 59 | # pars_list = nugget_list[self.parsFromIndex:] 60 | # curr_comp = self.curr_comp_save 61 | # # evaluate the parameters and append to the element 62 | # for idx, _par in enumerate(pars_list): 63 | # par_name = _par.split(':')[0].strip() 64 | # # check if item already has a value, but keep in mind label can have = sign in it, so not a good check 65 | # if '=' in _par: 66 | # # update the value 67 | # # parse the value from the string 68 | # par_vals = _par.split('=') 69 | # if len(par_vals) == 2: 70 | # par_label = par_vals[0].strip() 71 | # if state: 72 | # eval_val = curr_comp.par[par_name].eval() 73 | # # truncate length to 75 characters if it's a string and add dots if it's longer 74 | # if isinstance(eval_val, str): 75 | # if len(eval_val) > 75: 76 | # eval_val = eval_val[:75] + '...' 77 | # self.NuggetList[self.parsFromIndex + idx] = f'{par_label} = {eval_val}' 78 | # else: 79 | # self.NuggetList[self.parsFromIndex + idx] = f'{par_label}' 80 | # else: 81 | # if state: 82 | # eval_val = curr_comp.par[par_name].eval() 83 | # # truncate length to 75 characters if it's a string and add dots if it's longer 84 | # if isinstance(eval_val, str): 85 | # if len(eval_val) > 75: 86 | # eval_val = eval_val[:75] + '...' 87 | # self.NuggetList[self.parsFromIndex + idx] = f'{_par} = {eval_val}' 88 | # else: 89 | # self.NuggetList[self.parsFromIndex + idx] = f'{_par}' 90 | 91 | 92 | 93 | def SelectParentContent(self, idx = None, extraInfo = False, extraTrigger = False, showPars = False, showVals = False): 94 | def get_replaced_list(input_string, index): 95 | pattern = r'(\/|[^/]+)' 96 | elements = re.findall(pattern, input_string) 97 | replaced_list = [] 98 | for i, element in enumerate(elements): 99 | if i == index: 100 | replaced_list.append(element) 101 | else: 102 | replaced_list.append(' ' * len(element)) 103 | 104 | return replaced_list 105 | if idx is not None: 106 | self.idx = idx 107 | else: 108 | idx = self.idx 109 | if idx is None: 110 | return 111 | 112 | if self.ownerComp.op('null_hk1')[0][0]: 113 | showVals = True 114 | showPars = True 115 | extraInfo = True 116 | 117 | self.showPars = showPars 118 | self.showVals = showVals 119 | # if self.ownerComp.op('null_hk2')[0][0]: 120 | # showPars = True 121 | 122 | parent_shortcut = get_replaced_list(self.ParentHierarchyContent(), idx) 123 | parent_shortcut = ''.join(parent_shortcut).strip() 124 | #self.NuggetList = self.NuggetList.strip() 125 | ### Initially I wanted this to overlay the other window, but positioning is messed up, so let's strip the whitespaces and make just a nugget 126 | self.NuggetList = TDS.DependList([]) 127 | 128 | comp_list = copy(self.ParentCompList) 129 | comp_list.reverse() 130 | if len(comp_list) <= int(idx/2): 131 | return 132 | 133 | curr_comp = comp_list[int(idx/2)] 134 | self.curr_comp_save = curr_comp 135 | 136 | globalshort = tdu.tryExcept(lambda: curr_comp.par.opshortcut.eval(), None) 137 | ting = globalshort or '___' 138 | if ting and ting != '___': 139 | self.NuggetList += [f"G: {ting}"] 140 | 141 | if parent_shortcut and parent_shortcut != '___': 142 | self.NuggetList += [f'P: {parent_shortcut}'] 143 | 144 | for idx, (intopname, intop) in enumerate(list(curr_comp.internalOPs.items())): 145 | self.NuggetList += [f"iop{idx+1}: {intopname or '___'}"] 146 | if extraInfo: 147 | self.NuggetList[-1] += f" = {curr_comp.relativePath(intop)}" 148 | 149 | self.NuggetList += [self.divider[0]] 150 | if showPars: 151 | self.NuggetList[-1] = self.divider[1] 152 | self.parsFromIndex = -1 153 | for par in self.curr_comp_save.customPars: 154 | if self.parsFromIndex == -1: 155 | self.parsFromIndex = len(self.NuggetList) 156 | par_label= f"{par.name}:{par.label}" 157 | 158 | if showVals: 159 | _par = self.curr_comp_save.par[par.name] 160 | if _par.isPulse: 161 | eval_val = "{pulse}" 162 | elif _par.isSequence and not _par.sequenceBlock: 163 | eval_val = "{seq}" 164 | else: 165 | eval_val = _par.eval() 166 | par_label += f' = {eval_val}' 167 | 168 | if len(par_label) > 75: 169 | par_label = par_label[:75] + '...' 170 | 171 | self.NuggetList += [par_label] 172 | 173 | if self.NuggetList: 174 | #self.ParentNuggetContainer.par.Items.val = self.NuggetList 175 | if not self.ownerComp.op('popMenu_bar/window').isOpen and not extraTrigger: 176 | self.ShowHideNugget(True) 177 | if (showPars or showVals) and extraTrigger: 178 | self.ParentNuggetContainer.ext.PopMenuExt.refresh() 179 | self.ParentNuggetContainer.ext.PopMenuExt.CalculateOptimalDimensions() 180 | self.ParentNuggetContainer.ext.PopMenuExt.RecalculateOffsets() 181 | self.keepOpen.val = True 182 | else: 183 | self.keepOpen.val = False 184 | 185 | 186 | def onPopMenuSetLook(self, info): 187 | lister = info['ownerComp'] 188 | row = info['row'] 189 | col = 0 190 | # get element from nuggetlist based on row 191 | _element = info['cellText'] 192 | # get lister reference 193 | if _element.startswith('P: '): 194 | lister.SetCellLook(row, col, 'buttonParent') 195 | pass 196 | if _element.startswith('G: '): 197 | lister.SetCellLook(row, col, 'buttonGlobal') 198 | pass 199 | if _element.startswith('iop'): 200 | lister.SetCellLook(row, col, 'buttonIop') 201 | pass 202 | pass 203 | 204 | def ParentHierarchyContent(self): 205 | self.ParentShortcutList = [] 206 | self.ParentCompList = [] 207 | self.GetInfo() 208 | 209 | # Define the list 210 | my_list = self.ParentShortcutList 211 | my_list.reverse() 212 | 213 | # Replace empty strings with '___' 214 | my_list = ['___' if i == '' else i for i in my_list if i != None] 215 | 216 | # Join the items in the list with '/' as the separator 217 | result = '' 218 | if my_list: 219 | result = " / ".join(my_list) 220 | result = "/ " + result + ' /' 221 | self.ParentBarcontainer.par.Items.val = [result] 222 | return result 223 | 224 | def GetInfo(self, comp = None): 225 | '''Get parent shortcuts recursively in a list''' 226 | if comp is None: 227 | comp = op(self.NavbarContent) 228 | self.ParentShortcutList = [] 229 | self.ParentCompList = [] 230 | if comp == op('/'): 231 | return 232 | if comp is None: 233 | return 234 | 235 | self.ParentShortcutList.append(tdu.tryExcept(lambda: comp.par.parentshortcut.eval(), None)) 236 | self.ParentCompList.append(comp) 237 | if _parent := comp.parent(): 238 | self.GetInfo(_parent) 239 | 240 | 241 | #### 242 | 243 | def ShowHideNugget(self, show): 244 | if show: 245 | self.ParentNuggetContainer.par.Open.pulse() 246 | self.ParentNuggetContainer.op('lister').scroll(0, 0) 247 | else: 248 | if not self.keepOpen: 249 | self.ParentNuggetContainer.par.Close.pulse() 250 | 251 | def ShowHideBar(self, show): 252 | if show: 253 | self.ParentBarcontainer.par.Open.pulse() 254 | else: 255 | self.ParentBarcontainer.par.Close.pulse() 256 | 257 | 258 | def onNuggetItemClicked(self, info, openEditor = False): 259 | nugget_comp = self.curr_comp_save 260 | target_comp = op(op('../panenav/out1').text.strip()).ops('*') 261 | if not target_comp: 262 | target_comp = op(op('../panenav/out1').text.strip()) 263 | else: 264 | target_comp = target_comp[0] 265 | 266 | item = info['item'] 267 | clipboard_text = None 268 | if item.startswith('P: '): 269 | # get parent shortcut 270 | parent_shortcut = item.split(':')[1].strip() 271 | clipboard_text = f'parent.{parent_shortcut}' 272 | 273 | if openEditor: 274 | nugget_comp.openParameters() 275 | elif item.startswith('G: '): 276 | # get global shortcut 277 | global_shortcut = item.split(':')[1].strip() 278 | clipboard_text = f'op.{global_shortcut}' 279 | if openEditor: 280 | nugget_comp.openParameters() 281 | pass 282 | elif re.match(r'iop\d+:\s', item): 283 | # get internal op shortcut 284 | internal_shortcut = item.split(' ')[1].strip() 285 | clipboard_text = f'iop.{internal_shortcut}' 286 | if openEditor: 287 | nugget_comp.internalOPs[internal_shortcut].openParameters() 288 | pass 289 | elif re.match(r'(\w+):(.+)', item): 290 | 291 | # get parameter shortcut 292 | par_name = item.split(':')[0].strip() 293 | clipboard_text = TDF.getShortcutPath(target_comp, nugget_comp) 294 | # get par 295 | _par = nugget_comp.par[par_name] 296 | if _seqBlock := _par.sequenceBlock: 297 | # TODO: get sequencepar name from parname which is {sequencename}{number}{sequenceparname} 298 | sequenceparname = par_name.split(f'{_seqBlock.sequence.name}{_seqBlock.index}')[1] 299 | clipboard_text += f'.seq.{_seqBlock.sequence.name}[{_seqBlock.index}].par.{sequenceparname.capitalize()}' 300 | elif _par.isSequence: 301 | clipboard_text += f'.seq.{par_name}' 302 | else: 303 | clipboard_text += f'.par.{par_name}' 304 | 305 | if openEditor: 306 | if not self.compEditor.op('window').isOpen: 307 | self.compEditor.Open(nugget_comp) 308 | else: 309 | self.compEditor.Connect(nugget_comp) 310 | self.compEditor.CurrentPage = _par.page.name 311 | self.compEditor.CurrentPar = _par 312 | self.compEditor.RefreshListers() 313 | _comp_editor_pages = self.compEditor.op('pagesAndParameters/listerPages') 314 | _comp_editor_pars = self.compEditor.op('pagesAndParameters/listerPars') 315 | _page = _par.page.name 316 | 317 | # get page index from comp editor pages 318 | _page_list = list(filter(lambda x: x['pageName'] == _page, _comp_editor_pages.Data)) 319 | _page_idx = _page_list[0]['sourceIndex'] 320 | if _page_idx != 'Auto-Header': 321 | _comp_editor_pages.SelectRow(_page_idx+1) 322 | _comp_editor_pages.scroll(_page_idx, 0) 323 | 324 | if len(_par.parGroup) > 1: 325 | par_name = _par.parGroup.name 326 | _par_list = list(filter(lambda x: x['ParName'] == par_name, _comp_editor_pars.Data)) 327 | _par_idx = _par_list[0]['sourceIndex'] 328 | if _par_idx != 'Auto-Header': 329 | _comp_editor_pars.SelectRow(_par_idx+1) 330 | _comp_editor_pars.scroll(_par_idx, 0) 331 | pass 332 | 333 | if clipboard_text: 334 | ui.clipboard = clipboard_text 335 | ui.status = f'Copied to clipboard: {clipboard_text}' 336 | self.ownerComp.op('../panenav/path').panel.celloverid = -1 337 | 338 | -------------------------------------------------------------------------------- /scripts/NoUI/ExtNoUI.py: -------------------------------------------------------------------------------- 1 | 2 | '''Info Header Start 3 | Name : ExtNoUI 4 | Author : Dan@DAN-4090 5 | Saveorigin : FunctionStore_tools_2023.514.toe 6 | Saveversion : 2023.11880 7 | Info Header End''' 8 | CustomParHelper: CustomParHelper = next(d for d in me.docked if 'ExtUtils' in d.tags).mod('CustomParHelper').CustomParHelper # import 9 | ### 10 | 11 | class ExtNoUI: 12 | # TODO: is there a safer way to make sure we always restore to the "actual" height? currently using magic number 75 at one place 13 | # ------ but I guess you need something at least, the actual height gets stored anyway on first hide 14 | def __init__(self, ownerComp): 15 | self.ownerComp = ownerComp 16 | CustomParHelper.Init(self, ownerComp, enable_properties=True, enable_callbacks=True) 17 | self.timeline = op('/ui/dialogs/timeline') 18 | self._timeline_height_saved = self.ownerComp.fetch('timeline_height', 75) 19 | self.Shortcuts = tdu.Dependency('') 20 | self._setShortcuts() 21 | self._save_bg_color = self.bg_color if self.play_state else [0.25, 0.25, 0.25] 22 | self.UpdatePlayState(self.play_state) 23 | 24 | @property 25 | def pause_indicator_ui_element(self): 26 | val = self.evalPauseindicator 27 | return val if val and val in ui.colors else 'default.bg' 28 | 29 | @property 30 | def play_state(self): 31 | return self.ownerComp.op('null_state')['play'].eval() 32 | 33 | @property 34 | def bg_color(self): 35 | return ui.colors[self.pause_indicator_ui_element] 36 | 37 | @bg_color.setter 38 | def bg_color(self, value): 39 | ui.colors[self.pause_indicator_ui_element] = value 40 | 41 | @property 42 | def module_enabled(self): 43 | return self.evalEnabletimeline 44 | 45 | @property 46 | def timeline_height(self): 47 | return self.timeline.par.h.eval() 48 | 49 | @timeline_height.setter 50 | def timeline_height(self, value): 51 | self.timeline.par.h.val = value 52 | if value > 0: # Only store non-zero values 53 | self.ownerComp.store('timeline_height', value) 54 | 55 | def SetStateTimeline(self, on_create = None, on_start = None): 56 | if on_create: 57 | state = not self.evalHidetimeline 58 | elif on_start: 59 | state = not self.evalStateonstartuptimeline 60 | 61 | self._setStateTimeline(state) 62 | 63 | 64 | def _setStateTimeline(self, state=None): 65 | if not self.module_enabled: 66 | return 67 | 68 | if state is None: 69 | state = not bool(self.timeline_height) # Toggle based on current height 70 | 71 | if state: 72 | height = max(self._timeline_height_saved, 75) # Ensure we never restore to 0 73 | if self.timeline_height == 0: 74 | self.timeline_height = height 75 | else: 76 | if self.timeline_height != 0: # Only save if current height is non-zero 77 | self._timeline_height_saved = self.timeline_height 78 | self.timeline_height = 0 79 | 80 | self.parHidetimeline.val = not state 81 | 82 | self._updateUIPlayState(self.play_state)##################### 83 | 84 | 85 | def _setShortcuts(self): 86 | shortcuts = [] 87 | for _par in self.ownerComp.pars('Shortcut*'): 88 | shortcuts.append(_par.eval()) 89 | self.Shortcuts.val = shortcuts 90 | 91 | 92 | def onParHidetimeline(self, value): 93 | self._setStateTimeline(not value) 94 | 95 | 96 | def OnShortcut(self, shortcutName): 97 | self._setStateTimeline() 98 | 99 | def UpdatePlayState(self, state): 100 | if self.evalHidetimeline or (not self.evalHidetimeline and state): 101 | self._updateUIPlayState(state) 102 | 103 | def _updateUIPlayState(self, state): 104 | _save_bg_color = self.bg_color 105 | self.bg_color = self.evalGroupPausestatecolor if state == False else self._save_bg_color 106 | if not state and any(abs(a - b) > 0.001 for a, b in zip(_save_bg_color, self.evalGroupPausestatecolor)): 107 | self._save_bg_color = _save_bg_color 108 | pass 109 | 110 | def onParGroupPausestatecolor(self, vals): 111 | self.bg_color = vals 112 | 113 | 114 | def onParPauseindicator(self, vals): 115 | self.UpdatePlayState(self.play_state) 116 | 117 | -------------------------------------------------------------------------------- /scripts/OpToClipboard/ExtOpToClipboard.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | class ExtOpToClipboard: 4 | def __init__(self, ownerComp): 5 | self.ownerComp = ownerComp 6 | self._op_ref = None 7 | # Define the regular expression pattern with a capturing group, we choose @ as the delimiter 8 | self.patternize = lambda _opname: f"op('{_opname}')@" 9 | self.regex = lambda _op_ref: rf".*(op\('({re.escape(_op_ref)})'\)\@).*" 10 | self.mod = self.ownerComp.op('null_mod') 11 | 12 | def OnCopy(self): 13 | if _op := ui.panes.current.owner.currentChild: 14 | ui.clipboard = self.patternize(_op.name) 15 | self._op_ref = _op 16 | 17 | def OnRolloverPar(self, _op_str, _par_str, _expr): 18 | _op = op(_op_str) 19 | if not _op: 20 | return 21 | _par = op(_op_str).par[_par_str] 22 | if _par.mode in [ParMode.EXPRESSION, ParMode.CONSTANT] and _expr != 'None': 23 | if self._op_ref and (_to_replace := self.__checkExprReplace(_expr)): 24 | if not self.mod['shift'].eval(): 25 | shortcut = f"op('{_op.relativePath(self._op_ref)}')" 26 | else: 27 | # RFE: this is a workaround to get the shortcut path of the op 28 | # so this kinda sucks since keyboardin DAT/CHOP is not working during text input, 29 | # so if you want to use this feature, you need to move your cursor out of the text input field before pasting 30 | # then paste the expression, 31 | # and then press enter or click away to stop text ipnut, 32 | # and then press shift and hover over the parameter 33 | shortcut = _op.shortcutPath(self._op_ref) 34 | if shortcut: 35 | _par.expr = _par.expr.replace(_to_replace, shortcut) 36 | pass 37 | 38 | 39 | def __checkExprReplace(self, _expr): 40 | 41 | pattern = self.regex(self._op_ref.name) 42 | 43 | # Use re.match to check if _expr matches the pattern 44 | match = re.match(pattern, _expr) 45 | # If there's a match 46 | if match: 47 | return match.group(1) 48 | 49 | # If no match, return None 50 | return False 51 | 52 | -------------------------------------------------------------------------------- /scripts/OpenExt/ExtOpenExt.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | class ExtOpenExt: 4 | def __init__(self, ownerComp): 5 | self.ownerComp = ownerComp 6 | 7 | def OnOpen(self): 8 | _op = ui.panes.current.owner.currentChild 9 | if not _op.isCOMP: 10 | return 11 | 12 | _object = None 13 | for _block in _op.seq.ext: 14 | if _block.par.promote: 15 | _object = _block.par.object 16 | break 17 | if _object: 18 | _op_relative_path = self._getExtOpPath(_object.eval()) 19 | if not _op_relative_path: 20 | return 21 | 22 | _dat = _op.op(_op_relative_path) 23 | if _dat: 24 | _dat.par.edit.pulse() 25 | 26 | def _getExtOpPath(self, _object): 27 | pattern = r"op\('(.*?)'\)\.module\..*\(me\)" 28 | match = re.search(pattern, _object) 29 | if not match: 30 | return None 31 | return match.group(1) 32 | 33 | -------------------------------------------------------------------------------- /scripts/ParOPDrop/ExtParOPPlace.py: -------------------------------------------------------------------------------- 1 | import TDFunctions as TDF 2 | 3 | class ExtParOPPlace: 4 | def __init__(self, ownerComp): 5 | # Initialization of class variables 6 | self.ownerComp = ownerComp 7 | self.chopPreEnabled = False 8 | self.datPreEnabled = False 9 | self.datExecPreEnabled = False 10 | self.parameterChop = None 11 | self.parameterDat = None 12 | self.parameterExecDat = None 13 | 14 | def OnChopPre(self, value: bool): 15 | # Set CHOP pre-enabled state and reset parameter CHOP if disabled 16 | self.chopPreEnabled = value 17 | if not value: 18 | self.parameterChop = None 19 | 20 | def OnDatPre(self, value: bool): 21 | # Set DAT pre-enabled state and reset parameter DAT if disabled 22 | self.datPreEnabled = value 23 | if not value: 24 | self.parameterDat = None 25 | 26 | def OnDatExecPre(self, value: bool): 27 | # Set DAT pre-enabled state and reset parameter DAT if disabled 28 | self.datExecPreEnabled = value 29 | if not value: 30 | self.parameterExecDat = None 31 | 32 | def OnPlaceParOp(self, _par = None): 33 | 34 | current_parameter = _par if _par is not None else ui.rolloverPar 35 | if current_parameter is None or current_parameter.owner.family != "COMP": 36 | return 37 | 38 | current_selected = ui.panes.current.owner.currentChild 39 | self._update_generic_parameter(current_selected, current_parameter) 40 | 41 | def _update_generic_parameter(self, selected_op, curr_parameter): 42 | # Determine the appropriate parameters based on enabled flags 43 | param_name = 'parameters' # Default parameter attribute name 44 | operators_param_name = 'ops' 45 | if self.datExecPreEnabled: 46 | parameter_instance = self.parameterExecDat 47 | op_type = 'parameterexecuteDAT' 48 | op_name = 'parexec1' 49 | param_name = 'pars' # Parameter attribute name for parameterexecuteDAT 50 | operators_param_name = 'op' 51 | elif self.datPreEnabled: 52 | parameter_instance = self.parameterDat 53 | op_type = 'parameterDAT' 54 | op_name = 'parameter1' 55 | else: # self.chopPreEnabled 56 | parameter_instance = self.parameterChop 57 | op_type = 'parameterCHOP' 58 | op_name = 'parameter1' 59 | 60 | newly_created = False 61 | 62 | # Helper function to create a new parameter instance 63 | def create_new_instance(): 64 | new_instance = ui.panes.current.owner.create(op_type, op_name) 65 | new_instance.viewer = True 66 | new_instance.current = True 67 | new_instance.nodeCenterX = ui.panes.current.x 68 | new_instance.nodeCenterY = ui.panes.current.y 69 | return new_instance 70 | 71 | if selected_op and selected_op.opType == op_type: 72 | # Check if the parameter_instance's operator reference matches curr_parameter.owner 73 | current_op_path = getattr(selected_op.par, operators_param_name).eval() 74 | if current_op_path == curr_parameter.owner: 75 | parameter_instance = selected_op 76 | else: 77 | # Create new instance if the referenced operator doesn't match 78 | parameter_instance = create_new_instance() 79 | newly_created = True 80 | elif not parameter_instance: 81 | parameter_instance = create_new_instance() 82 | newly_created = True 83 | 84 | if newly_created: 85 | getattr(parameter_instance.par, param_name).val = curr_parameter.name 86 | getattr(parameter_instance.par, operators_param_name).expr = TDF.getShortcutPath(parameter_instance, curr_parameter.owner) 87 | elif curr_parameter.name not in getattr(parameter_instance.par, param_name).val.split(' '): 88 | getattr(parameter_instance.par, param_name).val += ' ' + curr_parameter.name 89 | 90 | self.parameterExecDat = False 91 | self.parameterChop = False 92 | self.parameterDat = False -------------------------------------------------------------------------------- /scripts/ParRandomizer/ExtParRandomizer.py: -------------------------------------------------------------------------------- 1 | 2 | class ExtParRandomizer: 3 | def __init__(self, ownerComp): 4 | self.ownerComp = ownerComp 5 | self.random = iop.Random # this is an extension 6 | self.checkShortcutRayTK()# 7 | 8 | @property 9 | def shortcutopEval(self): 10 | return self.ownerComp.par.Shortcutop.eval() 11 | 12 | @property# 13 | def shortcutparEval(self): 14 | return self.ownerComp.par.Shortcutpar.eval() 15 | 16 | # par callbacks 17 | def Shortcutop(self, _par, _val, _prev): 18 | self.checkShortcutRayTK() 19 | 20 | # par callbacks# 21 | def Shortcutpar(self, _par, _val, _prev): 22 | self.checkShortcutRayTK() 23 | 24 | def checkShortcutRayTK(self): 25 | conflict = False 26 | if raytk := getattr(op, 'raytk', None): 27 | if keyboardshortcut := getattr(raytk.par, 'Keyboardshortcut', None): 28 | if keyboardshortcut.eval() in [self.shortcutopEval, self.shortcutparEval]: 29 | conflict = True 30 | if tools_keyboardshortcut := getattr(raytk.par, 'Toolskeyboardshortcut', None): 31 | if tools_keyboardshortcut.eval() in [self.shortcutopEval, self.shortcutparEval]: 32 | conflict = True 33 | if conflict: 34 | result = ui.messageBox('Conflict with RayTK shortcut','The shortcut for FNS_tools:ParRandomzier is already in use by RayTK.', buttons = ['Ignore', 'Change it']) 35 | if result == 1: 36 | raytk.openParameters() 37 | self.ownerComp.openParameters() 38 | return conflict 39 | 40 | def OnRayTKChange(self, info): 41 | if info != '': 42 | self.checkShortcutRayTK() 43 | 44 | def OnRandomizeOp(self, _op = None): 45 | ui.undo.startBlock('Randomize OP parameters') 46 | _op = _op or ui.panes.current.owner.currentChild 47 | if not _op: 48 | return 49 | _par_list = [] 50 | 51 | _page = _op.currentPage 52 | for _par in _page.pars: 53 | if not (_par.readOnly and _par.enable): 54 | _par_list.append(_par) 55 | 56 | for _par in _par_list: 57 | self.RandomizePar(_par) 58 | ui.undo.endBlock() 59 | return 60 | 61 | def RandomizePar(self, _par): 62 | 63 | if isinstance(_par, ParGroup): 64 | for _subpar in _par: 65 | self.RandomizePar(_subpar) 66 | return 67 | 68 | 69 | if _par.mode not in [ParMode.CONSTANT, ParMode.BIND]: 70 | return 71 | 72 | if _par.isNumber: 73 | _min = _par.min if _par.clampMin else _par.normMin 74 | _max = _par.max if _par.clampMax else _par.normMax 75 | if _par.isFloat: 76 | new_val = self.random.Uniform(_min, _max) 77 | elif _par.isInt: 78 | new_val = self.random.RandInt(_min, _max) 79 | elif _par.isToggle: 80 | new_val = self.random.RandInt(0, 1) 81 | elif _par.isMenu: 82 | new_val = self.random.RandInt(0, len(_par.menuNames) - 1) 83 | else: 84 | return 85 | 86 | _par.val = new_val 87 | 88 | 89 | def OnRandomizeRolloverPar(self): 90 | ui.undo.startBlock('Randomize parameter') 91 | _par = ui.rolloverPar 92 | if _par is None: 93 | return 94 | 95 | self.RandomizePar(_par) 96 | ui.undo.endBlock() 97 | 98 | def onResetPar(self): 99 | ui.undo.startBlock('Reset parameter') 100 | _par = ui.rolloverPar 101 | if _par is None: 102 | return 103 | _par.val = _par.reset() 104 | ui.undo.endBlock() 105 | 106 | def onResetAllCustom(self): 107 | ui.undo.startBlock('Reset all custom parameters') 108 | _par = ui.rolloverPar 109 | if _par is None: 110 | return 111 | _owner = _par.owner 112 | par_names = [_par.name for _par in _owner.currentPage.pars if not _par.readOnly and _par.enable] 113 | _owner.resetPars(parNames=' '.join(par_names)) 114 | ui.undo.endBlock() 115 | 116 | def OnShortcut(self, shortcutName): 117 | if shortcutName == self.ownerComp.par.Shortcutop.eval(): 118 | self.OnRandomizeOp() 119 | if shortcutName == self.ownerComp.par.Shortcutpar.eval(): 120 | self.OnRandomizeRolloverPar() 121 | if shortcutName == self.ownerComp.par.Shortcutreset.eval(): 122 | self.onResetPar() 123 | if shortcutName == self.ownerComp.par.Shortcutresetallcustom.eval(): 124 | self.onResetAllCustom() 125 | 126 | -------------------------------------------------------------------------------- /scripts/QuickCollapse/ExtQuickCollapse.py: -------------------------------------------------------------------------------- 1 | CustomParHelper: CustomParHelper = next(d for d in me.docked if 'ExtUtils' in d.tags).mod('CustomParHelper').CustomParHelper # import 2 | ### 3 | 4 | class ExtQuickCollapse: 5 | def __init__(self, ownerComp): 6 | self.ownerComp = ownerComp 7 | CustomParHelper.Init(self, ownerComp, enable_properties=True, enable_callbacks=True) 8 | self.popDialog = ownerComp.op('popDialog') 9 | self.newCOMP = None 10 | self.selected = None 11 | # 12 | 13 | def undoCollapse(self, isUndo, _return): 14 | ui.panes.current.owner = _return 15 | pass 16 | 17 | def OnCollapse(self, customize=False): 18 | self.selected = ui.panes.current.owner.selectedChildren 19 | if not self.selected: 20 | return 21 | 22 | if customize: 23 | self.popDialog.Open(callback=self.OnCustomizeCallback) 24 | return 25 | else: 26 | self.collapse() 27 | pass 28 | 29 | def collapse(self, _name=None, _shortcut=None, ok_by_enter=False): 30 | ui.undo.startBlock('Collapsing') 31 | ui.undo.addCallback(self.undoCollapse, info = ui.panes.current.owner) 32 | 33 | if not self.selected or ok_by_enter: 34 | return# 35 | self.selected[0].parent().collapseSelected() 36 | self.newCOMP = ui.panes.current.owner.currentChild 37 | 38 | if not self.newCOMP: 39 | ui.undo.endBlock() 40 | return 41 | 42 | if _name: 43 | self.newCOMP.name = _name 44 | if _shortcut: 45 | self.newCOMP.par.parentshortcut = _shortcut 46 | 47 | ui.panes.current.owner = self.newCOMP 48 | ui.undo.endBlock() 49 | 50 | 51 | 52 | def OnCustomizeCallback(self, info): 53 | if info['buttonNum'] == 1: 54 | # we need to check if `OK` was pressed by enter key ### might be only for Mac??? 55 | # TODO: check if this is the case for Windows 56 | key = self.ownerComp.op('keyboardin1')[1,'key'].val 57 | ok_by_enter = key == 'enter' 58 | 59 | self.collapse(_name=info['enteredText'][0], _shortcut=info['enteredText'][1], ok_by_enter=ok_by_enter) 60 | 61 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /scripts/QuickCollapse/PopDialogExt.py: -------------------------------------------------------------------------------- 1 | # This file and all related intellectual property rights are 2 | # owned by Derivative Inc. ("Derivative"). The use and modification 3 | # of this file is governed by, and only permitted under, the terms 4 | # of the Derivative [End-User License Agreement] 5 | # [https://www.derivative.ca/Agreements/UsageAgreementTouchDesigner.asp] 6 | # (the "License Agreement"). Among other terms, this file can only 7 | # be used, and/or modified for use, with Derivative's TouchDesigner 8 | # software, and only by employees of the organization that has licensed 9 | # Derivative's TouchDesigner software by [accepting] the License Agreement. 10 | # Any redistribution or sharing of this file, with or without modification, 11 | # to or with any other person is strictly prohibited [(except as expressly 12 | # permitted by the License Agreement)]. 13 | # 14 | # Version: 099.2017.30440.28Sep 15 | # 16 | # _END_HEADER_# 17 | 18 | from TDStoreTools import StorageManager 19 | import TDFunctions as TDF 20 | class PopDialogExt: 21 | 22 | def __init__(self, ownerComp): 23 | """ 24 | Popup dialog extension. Just call the DoPopup method to create a popup. 25 | Provide info in that method. This component can be used over and over, 26 | no need for a different component for each dialog, unless you want to 27 | change the insides. 28 | """ 29 | self.ownerComp = ownerComp 30 | self.windowComp = ownerComp.op('popDialogWindow') 31 | self.details = None 32 | 33 | # upgrade version 34 | self.ownerComp.par.Version = self.ownerComp.par.clone.eval().par.Version 35 | h = self.ownerComp.par.h 36 | if h.mode == ParMode.EXPRESSION and h.expr == "op('./dialog').par.h": 37 | h.expr = "me.DialogHeight" 38 | h.readOnly = True 39 | 40 | TDF.createProperty(self, 'EnteredText1', value='') 41 | TDF.createProperty(self, 'EnteredText2', value='') 42 | TDF.createProperty(self, 'TextHeight', value=0) 43 | run("args[0].UpdateTextHeight()", self, delayFrames=1, 44 | delayRef=op.TDResources) 45 | 46 | def OpenDefault(self, text='', title='', buttons=('OK',), callback=None, 47 | details=None, textEntry1=False, textEntry2=False, escButton=1, 48 | escOnClickAway=True, enterButton=1): 49 | self.Open(text, title, buttons, callback, details, textEntry1, textEntry2, escButton, 50 | escOnClickAway, enterButton) 51 | 52 | def Open(self, text=None, title=None, buttons=None, callback=None, 53 | details=None, textEntry1=None, textEntry2=None, escButton=None, 54 | escOnClickAway=None, enterButton=None): 55 | """ 56 | Open a popup dialog. 57 | text goes in the center of the dialog. Default None, use pars. 58 | title goes on top of the dialog. Blank means no title bar. Default None, 59 | use pars 60 | buttons is a list of strings. The number of buttons is equal to the 61 | number of buttons, up to 4. Default is ['OK'] 62 | callback: a method that will be called when a selection is made, see the 63 | SetCallback method. This is in addition to all internal callbacks. 64 | If not provided, Callback DAT will be searched. 65 | details: will be passed to callback in addition to item chosen. 66 | Default is None. 67 | If textEntry is a string, display textEntry field and use the string 68 | as a default. If textEntry is False, no entry field. Default is 69 | None, use pars 70 | escButton is a number from 1-4 indicating which button is simulated when 71 | esc is pressed or False for no button simulation. Default is None, 72 | use pars. First button is 1 not 0!!! 73 | enterButton is a number from 1-4 indicating which button is simulated 74 | when enter is pressed or False for no button simulation. Default is 75 | None, use pars. First button is 1 not 0!!! 76 | escOnClickAway is a boolean indicating whether esc is simulated when user 77 | clicks somewhere besides the dialog. Default is None, use pars 78 | """ 79 | self.Close() 80 | # text and title 81 | if text is not None: 82 | self.ownerComp.par.Text = text 83 | if title is not None: 84 | self.ownerComp.par.Title = title 85 | # buttons 86 | if buttons is not None: 87 | if not isinstance(buttons, list): 88 | buttons = ['OK'] 89 | self.ownerComp.par.Buttons = len(buttons) 90 | for i, label in enumerate(buttons[:4]): 91 | getattr(self.ownerComp.par, 92 | 'Buttonlabel' + str(i + 1)).val = label 93 | # callbacks 94 | if callback: 95 | ext.CallbacksExt.SetAssignedCallback('onSelect', callback) 96 | else: 97 | ext.CallbacksExt.SetAssignedCallback('onSelect', None) 98 | # textEntry 99 | if textEntry1 is not None: 100 | if isinstance(textEntry1, str): 101 | self.ownerComp.par.Textentryarea = True 102 | self.ownerComp.par.Textentrydefault = str(textEntry1) 103 | elif textEntry1: 104 | self.ownerComp.par.Textentryarea = True 105 | self.ownerComp.par.Textentrydefault = '' 106 | else: 107 | self.ownerComp.par.Textentryarea = False 108 | if textEntry2 is not None: 109 | if isinstance(textEntry2, str): 110 | self.ownerComp.par.Textentryarea2 = True 111 | self.ownerComp.par.Textentrydefault2 = str(textEntry2) 112 | elif textEntry2: 113 | self.ownerComp.par.Textentryarea2 = True 114 | self.ownerComp.par.Textentrydefault2 = '' 115 | 116 | self.EnteredText1 = self.ownerComp.par.Textentrydefault.eval() 117 | self.EnteredText2 = self.ownerComp.par.Textentrydefault2.eval() 118 | self.details = details 119 | self.ownerComp.op('entry1/inputText').par.text = self.EnteredText1 120 | self.ownerComp.op('entry2/inputText').par.text = self.EnteredText2 121 | self.ownerComp.op('entry1').cook(force=True) 122 | self.ownerComp.op('entry2').cook(force=True) 123 | if escButton is not None: 124 | if escButton is False or not (1 <= escButton <= 4): 125 | self.ownerComp.par.Escbutton = 'None' 126 | else: 127 | self.ownerComp.par.Escbutton = str(escButton) 128 | if escOnClickAway is not None: 129 | self.ownerComp.par.Esconclickaway = escOnClickAway 130 | if enterButton is not None: 131 | if enterButton is False or not (1 <= enterButton <= 4): 132 | self.ownerComp.par.Enterbutton = 'None' 133 | else: 134 | self.ownerComp.par.Enterbutton = str(enterButton) 135 | self.UpdateTextHeight() 136 | # HACK shouldn't be necessary - problem with clones/replicating 137 | self.ownerComp.op('replicator1').par.recreateall.pulse() 138 | run("op('" + self.ownerComp.path + "').ext.PopDialogExt.actualOpen()", 139 | delayFrames=1, delayRef=op.TDResources) 140 | 141 | def actualOpen(self): 142 | # needs to be deferred so that sizes can update properly 143 | self.windowComp.par.winopen.pulse() 144 | ext.CallbacksExt.DoCallback('onOpen') 145 | if self.ownerComp.op('entry1').par.display.eval(): 146 | # self.ownerComp.setFocus() 147 | # hack shouldn't have to wait a frame 148 | run('op("' + self.ownerComp.path + '").op("entry1/inputText").' 149 | 'setKeyboardFocus(selectAll=True)', 150 | delayFrames=1, delayRef=op.TDResources) 151 | else: 152 | self.ownerComp.setFocus() 153 | 154 | def Close(self): 155 | """ 156 | Close the dialog 157 | """ 158 | #ext.CallbacksExt.SetAssignedCallback('onSelect', None) 159 | ext.CallbacksExt.DoCallback('onClose') 160 | self.windowComp.par.winclose.pulse() 161 | self.ownerComp.op('entry1/inputText').par.text = self.EnteredText1 162 | self.ownerComp.op('entry2/inputText').par.text = self.EnteredText2 163 | 164 | def OnButtonClicked(self, buttonNum): 165 | """ 166 | Callback from buttons 167 | """ 168 | infoDict = {'buttonNum': buttonNum, 169 | 'button': getattr(self.ownerComp.par, 170 | 'Buttonlabel' + str(buttonNum)).eval(), 171 | 'details': self.details} 172 | if self.ownerComp.par.Textentryarea.eval(): 173 | infoDict['enteredText'] = [self.EnteredText1, self.EnteredText2] 174 | try: 175 | ext.CallbacksExt.DoCallback('onSelect', infoDict) 176 | finally: 177 | self.Close() 178 | 179 | def OnKeyPressed(self, key): 180 | """ 181 | Callback for esc or enterpressed. 182 | """ 183 | if key == 'esc' and self.ownerComp.par.Escbutton.eval() != 'None': 184 | button = int(self.ownerComp.par.Escbutton.eval()) 185 | if button <= self.ownerComp.par.Buttons: 186 | self.OnButtonClicked(button) 187 | if key == 'enter' and self.ownerComp.par.Enterbutton.eval() != 'None': 188 | button = int(self.ownerComp.par.Enterbutton.eval()) 189 | if button <= self.ownerComp.par.Buttons: 190 | self.OnButtonClicked(button) 191 | if key == 'tab': 192 | # focus next entry 193 | self.ownerComp.op('entry2/inputText').setKeyboardFocus(selectAll=True) 194 | 195 | def OnClickAway(self): 196 | """ 197 | Callback for esc pressed. Only happens when Escbutton is not None 198 | """ 199 | if self.ownerComp.par.Esconclickaway.eval(): 200 | self.OnKeyPressed('esc') 201 | 202 | def OnParValueChange(self, par, val, prev): 203 | """ 204 | Callback for when parameters change 205 | """ 206 | if par.name == "Textentryarea": 207 | self.ownerComp.par.Textentrydefault.enable = par.eval() 208 | if par.name == "Escbutton": 209 | self.ownerComp.par.Esconclickaway.enable = par.eval() != "None" 210 | 211 | def OnParPulse(self, par): 212 | if par.name == "Open": 213 | self.Open() 214 | elif par.name == "Close": 215 | self.Close() 216 | elif par.name == 'Editcallbacks': 217 | dat = self.ownerComp.par.Callbackdat.eval() 218 | if dat: 219 | dat.par.edit.pulse() 220 | else: 221 | print("No callback dat for", self.ownerComp.path) 222 | elif par.name == 'Helppage': 223 | ui.viewFile('https://docs.derivative.ca/' 224 | 'index.php?title=Palette:popDialog') 225 | 226 | def UpdateTextHeight(self): 227 | self.TextHeight = self.ownerComp.op('text/text').evalTextSize( 228 | self.ownerComp.par.Text)[1] 229 | 230 | @property 231 | def DialogHeight(self): 232 | return 65 + self.TextHeight*0 + \ 233 | (20 if self.ownerComp.par.Title else 0) + \ 234 | (37 if self.ownerComp.par.Textentryarea else 0) * len(self.ownerComp.ops('entry*')) - 20 -------------------------------------------------------------------------------- /scripts/QuickExt/ExtQuickExt.py: -------------------------------------------------------------------------------- 1 | 2 | from TDStoreTools import StorageManager 3 | import TDFunctions as TDF 4 | import copy 5 | 6 | class ExtQuickExt: 7 | def __init__(self, ownerComp): 8 | self.ownerComp = ownerComp 9 | self.dialog = self.ownerComp.op('popDialog') 10 | self.ConfigComp = None 11 | self.extTemplate = self.ownerComp.op('extTemplate') 12 | self.stubser = self.ownerComp.op('stubser') 13 | self.my_ext_type = 'QuickExt' 14 | # < - DO NOT REMOVE THIS VERY IMPORTANT LINE!!! used by QuickExt to inject extension - > 15 | self.MY_FORCED_TAG = f'# < - DO NOT REMOVE THIS VERY IMPORTANT LINE!!! used by {self.my_ext_type} to inject extension - >' 16 | self.__modifyCompEditor() 17 | 18 | def __modifyCompEditor(self): 19 | compEditor = op.TDDialogs.op('CompEditor') 20 | 21 | # copy self.extTemplate to compEditor as lower case self.my_ext_type+ExtensionText 22 | if not compEditor.op(self.my_ext_type.lower()+"ExtensionText"): 23 | new_ext = compEditor.copy(self.extTemplate, name=self.my_ext_type.lower()+"ExtensionText") 24 | new_ext.nodeY = compEditor.op('emptyExtensionText').nodeY - new_ext.nodeHeight - 20 25 | new_ext.nodeX = compEditor.op('emptyExtensionText').nodeX 26 | # erase file par value 27 | if hasattr(new_ext.par, 'file'): 28 | new_ext.par.file.mode = ParMode.CONSTANT 29 | new_ext.par.file.expr = '' 30 | new_ext.par.file = '' 31 | 32 | compEditor_ext = compEditor.op('CompEditorExt') 33 | # Look for the line that contains "if extType in ['Standard', 'Empty']:" and modify it 34 | lines = compEditor_ext.text.split('\n') 35 | modified = False 36 | for i, line in enumerate(lines): 37 | if "if extType in ['Standard', 'Empty']:" in line: 38 | lines[i] = line.replace("['Standard', 'Empty']", f"['Standard', 'Empty', '{self.my_ext_type}']") 39 | modified = True 40 | break 41 | if modified: 42 | compEditor_ext.text = '\n'.join(lines) 43 | 44 | addMenuExec = compEditor.op('extensions/ext1/addMenuExec') 45 | # Look for line that contains "menu.Open(['Standard', 'Empty'" and modify it 46 | lines = addMenuExec.text.split('\n') 47 | modified = False 48 | for i, line in enumerate(lines): 49 | if "menu.Open(['Standard', 'Empty']" in line: 50 | lines[i] = line.replace("['Standard', 'Empty']", f"['Standard', 'Empty', '{self.my_ext_type}']") 51 | modified = True 52 | break 53 | if modified: 54 | addMenuExec.text = '\n'.join(lines) 55 | 56 | def CreateExtension(self, _target): 57 | if not _target: 58 | return 59 | self.ConfigComp = _target 60 | self.dialog.Open(textEntry='Ext') 61 | pass 62 | 63 | def getExtIndex(self): 64 | idx = self.ConfigComp.par.ext.sequence.numBlocks+1 65 | for _seqBlock in self.ConfigComp.par.ext.sequence: 66 | if not _seqBlock.par.object.eval(): 67 | idx = _seqBlock.index+1 68 | break 69 | return idx 70 | 71 | def OnSelect(self, info, inject_ext = None): 72 | if inject_ext is not None and self.MY_FORCED_TAG not in inject_ext.text: 73 | # so: in kindergaertner we can only react to the tag `TDExtension` 74 | # and for some reason when injecting an extTemplate through the comp editor, the tags are removed 75 | # so when not injecting we also have the tag `TDExtension` and we've already done the work 76 | return 77 | sel = True if inject_ext is not None or info['buttonNum'] == 1 else False 78 | if sel and self.ConfigComp: 79 | masterUtils = self.ownerComp.op('ExtUtils') 80 | masterExt = self.extTemplate 81 | 82 | extIndex = self.getExtIndex() 83 | extIndex = extIndex if inject_ext is None else extIndex-1 84 | extModuleName = info['enteredText'] if info is not None else op.TDDialogs.op('CompEditor').ExtClassNames.get(extIndex) 85 | edges = TDF.findNetworkEdges(self.ConfigComp) 86 | 87 | if edges: 88 | edgeX = edges['positions']['left'] 89 | edgeY = edges['positions']['top'] - 220 90 | else: 91 | edgeX = 0 92 | edgeY = 0 93 | xPos = edgeX - 400 94 | yPos = edgeY 95 | 96 | 97 | extPar = getattr(self.ConfigComp.par, 'extension' + str(extIndex)) 98 | extPromotePar = getattr(self.ConfigComp.par, 99 | 'promoteextension' + str(extIndex)) 100 | extNamePar = getattr(self.ConfigComp.par, 101 | 'extname' + str(extIndex)) 102 | 103 | if inject_ext is None: 104 | extDat = self.ConfigComp.copy(masterExt, name=extModuleName) 105 | else: 106 | extDat = inject_ext 107 | extDat.color = masterExt.color 108 | extDat.tags = masterExt.tags 109 | extDat.nodeWidth = masterExt.nodeWidth 110 | extDat.nodeHeight = masterExt.nodeHeight 111 | 112 | extDat.par.file.mode = ParMode.CONSTANT 113 | extDat.par.file.expr = '' 114 | extDat.par.file = '' 115 | extDat.par.language = 'python' 116 | 117 | extUtils = self.ConfigComp.copy(masterUtils, includeDocked=True) 118 | extUtils.dock = extDat 119 | #extUtils.par.file = '' 120 | 121 | extensionText = copy.deepcopy(masterExt.text) 122 | 123 | if self.MY_FORCED_TAG in extensionText: 124 | # remove the manual tag from the text 125 | new_text = extensionText.replace(self.MY_FORCED_TAG, '') 126 | extensionText = new_text 127 | 128 | extensionText = extensionText.replace('QuickExtTemplate', 129 | extModuleName) 130 | extDat.nodeX = xPos 131 | extDat.nodeY = yPos 132 | extDat.viewer = True 133 | extDat.tags.add('TDExtension') 134 | extPromotePar.val = True 135 | extNamePar.val = '' 136 | extDat.docked[0].nodeX = extDat.nodeX 137 | extDat.docked[0].nodeY = extDat.nodeY - 200 138 | extDat.docked[0].showDocked = True 139 | extDat.current = True 140 | 141 | self.__purgeTags(extDat) 142 | self.__purgeTags(extUtils) 143 | 144 | for _dock in extUtils.ops('*'): 145 | #_dock.showDocked = False 146 | self.__purgeTags(_dock) 147 | if hasattr(_dock.par, 'file'): 148 | _dock.par.file.mode = ParMode.CONSTANT 149 | _dock.par.file.expr = '' 150 | _dock.par.file = '' 151 | 152 | extPar.val = "op('./" + extModuleName + "').module." \ 153 | + extModuleName + '(me)' 154 | extDat.text = extensionText 155 | 156 | self.ConfigComp.initializeExtensions(extIndex-1) 157 | self.__updateCompEditor(extIndex) 158 | 159 | # TODO: stubify packages 160 | #if self.ownerComp.par.Deploystubs.eval(): 161 | for _op in extUtils.findChildren(type=DAT): 162 | if 'extPackage' in _op.tags: 163 | self.stubser.StubifyDat(_op) 164 | 165 | def __updateCompEditor(self, index): 166 | compEditor = op.TDDialogs.op('CompEditor') 167 | entry = compEditor.op('extensions/ext'+str(int(index))) 168 | if entry is None: 169 | return 170 | bg = entry.op('extClassStatus1/bg') 171 | bg.cook(force=True) 172 | 173 | def __purgeTags(self, _op): 174 | TAGS_TO_REMOVE = ['FNS_externalized', 'pi_suspect'] 175 | for _tag in TAGS_TO_REMOVE: 176 | if _tag in _op.tags: 177 | _op.tags.remove(_tag) 178 | 179 | 180 | def OnCompEditor(self, new_ops): 181 | # get the ops (probably only one) from new ops with the tags 'CompEditor' and 'extTemplate' 182 | self.ConfigComp = op.TDDialogs.op('CompEditor').ConfigComp 183 | for _op in new_ops: 184 | self.OnSelect(None, inject_ext=_op) 185 | pass 186 | 187 | def OnClose(self, info): 188 | pass 189 | 190 | def OnOpen(self, info): 191 | pass 192 | -------------------------------------------------------------------------------- /scripts/QuickExt/stubser/extStubser_.py: -------------------------------------------------------------------------------- 1 | '''Info Header Start 2 | Name : extStubser 3 | Author : Dan@DAN-4090 4 | Saveorigin : FunctionStore_tools_2023.427.toe 5 | Saveversion : 2023.11600 6 | Info Header End''' 7 | import ast 8 | from pathlib import Path 9 | import re 10 | from stubsTransformer import StubsTransformer 11 | 12 | debug = op("logger").Log 13 | 14 | class extStubser: 15 | """ 16 | A Utility to automaticaly generate stubs for touchdesigner Extensions and modules. 17 | """ 18 | def __init__(self, ownerComp:COMP): 19 | # The component to which this extension is attached 20 | self.ownerComp = ownerComp 21 | 22 | def Stubify(self, input:str, includePrivate:bool = False, includeUnpromoted:bool = True) -> str: 23 | """Generate a stubified String of a module, removing all unnecesarry elements of functions.""" 24 | data = ast.parse(input) 25 | transformedData = StubsTransformer( includePrivate, includeUnpromoted).visit( data ) 26 | 27 | return ast.unparse(transformedData) 28 | 29 | def _parse_td_version(self, path: Path) -> tuple[int, int] | None: 30 | """Extract TouchDesigner version from path.""" 31 | td_pattern = re.compile(r'TouchDesigner\.(\d+)\.(\d+)') 32 | # Check folder name for version 33 | match = td_pattern.match(path.name) 34 | if match: 35 | return (int(match.group(1)), int(match.group(2))) 36 | return None 37 | 38 | def _is_valid_td_version(self, version: tuple[int, int] | None) -> bool: 39 | """Check if version meets minimum requirements (>= 2023.3000).""" 40 | if version is None: 41 | return False 42 | major, minor = version 43 | if major > 2023: 44 | return True 45 | elif major == 2023: 46 | return minor >= 30000 47 | return False 48 | 49 | def _find_td_builtins(self) -> Path | None: 50 | """Search for valid __builtins__.pyi in the highest version TD installation.""" 51 | # First check current app version 52 | current_td = Path(app.installFolder) 53 | version = self._parse_td_version(current_td) 54 | debug(f"Current TD version: {version}") 55 | 56 | # Always check if current builtins exists 57 | td_builtins = current_td / 'bin' / '__builtins__.pyi' 58 | if td_builtins.exists(): 59 | if self._is_valid_td_version(version): 60 | debug(f"Using current TD builtins in {version[0]}.{version[1]}") 61 | return td_builtins 62 | else: 63 | debug("Current TD version is not valid, but builtins exists") 64 | # Store current builtins as fallback 65 | current_builtins = td_builtins 66 | else: 67 | current_builtins = None 68 | debug("Current TD builtins not found") 69 | 70 | # If current version is not valid, search for highest version 71 | td_installations = current_td.parent 72 | highest_match = None 73 | highest_version = (0, 0) 74 | 75 | for td_folder in td_installations.iterdir(): 76 | if not td_folder.is_dir(): 77 | continue 78 | 79 | version = self._parse_td_version(td_folder) 80 | if self._is_valid_td_version(version) and version > highest_version: 81 | td_builtins = td_folder / 'bin' / '__builtins__.pyi' 82 | if td_builtins.exists(): 83 | highest_version = version 84 | highest_match = td_builtins 85 | debug(f"Found builtins in TD {version[0]}.{version[1]}") 86 | 87 | # Return highest valid version if found, otherwise return current builtins 88 | return highest_match if highest_match else current_builtins 89 | 90 | def _get_typing_paths(self, name: str) -> tuple[Path, Path]: 91 | """Determine paths for builtins and stubs files.""" 92 | if self.ownerComp.par.Tointerpreter.eval(): 93 | # Try to find builtins in highest version TD installation 94 | td_builtins = self._find_td_builtins() 95 | if td_builtins: 96 | builtins_file = td_builtins 97 | else: 98 | debug("No valid TD installation (>= 2023.30000) found with __builtins__.pyi") 99 | # Fallback to local typings 100 | builtins_file = Path("typings", "__builtins__.pyi") 101 | else: 102 | debug("Using local typings directory") 103 | builtins_file = Path("typings", "__builtins__.pyi") 104 | 105 | # Ensure parent directory exists 106 | builtins_file.parent.mkdir(exist_ok=True) 107 | if not builtins_file.exists(): 108 | debug("Creating new __builtins__.pyi file") 109 | builtins_file.touch() 110 | 111 | # Create custom_typings/QuickExt directory next to __builtins__.pyi 112 | stubs_dir = builtins_file.parent / "custom_typings" / "QuickExt" 113 | stubs_dir.mkdir(parents=True, exist_ok=True) 114 | 115 | return builtins_file, stubs_dir / f"{name}.pyi" 116 | 117 | def _placeTyping(self, stubsString: str, name: str): 118 | # TODO: factor out custom_typings.QuickExt. as optional subfolders to stubify to 119 | """Export the given stubs string into a file and add it as a builtin.""" 120 | debug("Placing Typings", name) 121 | 122 | builtinsFile, stubsFile = self._get_typing_paths(name) 123 | 124 | debug("Placing Typings", name) 125 | debug("Typings File", stubsFile) 126 | debug("Builtins File", builtinsFile) 127 | 128 | currentBuiltinsText = builtinsFile.read_text() 129 | if not re.search(f"from custom_typings.QuickExt.{name} import *", currentBuiltinsText): 130 | with builtinsFile.open("t+a") as builtinsFileHandler: 131 | builtinsFileHandler.write(f"\nfrom custom_typings.QuickExt.{name} import *") 132 | 133 | 134 | stubsFile.touch( exist_ok=True) 135 | stubsFile.write_text( stubsString ) 136 | 137 | 138 | def StubifyDat(self, target:textDAT, includePrivate:bool = False, includeUnpromoted:bool = True): 139 | debug( "Stubifying Dat", target.name) 140 | self._placeTyping( 141 | self.Stubify( 142 | target.text, 143 | includePrivate=includePrivate, 144 | includeUnpromoted=includeUnpromoted), 145 | target.name ) 146 | 147 | def StubifyComp(self, target:COMP, depth = 1, tag = "stubser", includePrivate:bool = False, includeUnpromoted:bool = True): 148 | debug( "Stubifying COMP", target.name ) 149 | for child in target.findChildren( 150 | tags=[ tag ], 151 | type=textDAT, 152 | maxDepth = depth ): 153 | 154 | self.StubifyDat( 155 | child, 156 | includePrivate=includePrivate, 157 | includeUnpromoted=includeUnpromoted ) 158 | 159 | def _findParPage(self, name): 160 | pagename = name 161 | owner = self.ownerComp.par.Owner.eval() 162 | for page in owner.customPages: 163 | if page.name == pagename: 164 | return page 165 | return owner.appendCustomPage( pagename ) 166 | 167 | def InitOwner(self): 168 | page = self._findParPage("Stubser") 169 | page.appendPulse( 170 | "Deploystubs", 171 | label = "Deploy Stubs", 172 | replace = True ) 173 | return 174 | -------------------------------------------------------------------------------- /scripts/QuickExt/templates/ExtUtils/CustomParHelper/extParExec.py: -------------------------------------------------------------------------------- 1 | # This is a callback system for parameters. 2 | # to use: 3 | # - create a function in ParCallbacksExt/ParCallbacks 4 | # using the same name as the parameter. 5 | 6 | def onValueChange(par, prev): 7 | ## In Extension code implement as follows: 8 | # def On(self, _par, _val, _prev): 9 | # ... 10 | package = mod(me.dock.name) 11 | package.CustomParHelper.OnValueChange(me.par.op.eval(), par, prev) 12 | 13 | def onPulse(par): 14 | ## In Extension code implement as follows: 15 | # def On(self, _par): 16 | # ... 17 | package = mod(me.dock.name) 18 | package.CustomParHelper.OnPulse(me.par.op.eval(), par) -------------------------------------------------------------------------------- /scripts/QuickExt/templates/ExtUtils/CustomParHelper/extParGroupExec.py: -------------------------------------------------------------------------------- 1 | # me - this DAT 2 | # par - the Par object that has changed 3 | # val - the current value 4 | # prev - the previous value 5 | # 6 | # Make sure the corresponding toggle is enabled in the Parameter Execute DAT. 7 | 8 | def onValueChange(par, prev): 9 | # use par.eval() to get current value 10 | return 11 | 12 | # Called at end of frame with complete list of individual parameter changes. 13 | # The changes are a list of named tuples, where each tuple is (Par, previous value) 14 | def onValuesChanged(changes): 15 | package = mod(me.dock.name) 16 | package.CustomParHelper.OnValuesChanged(changes) 17 | return 18 | 19 | def onPulse(par): 20 | return 21 | 22 | def onExpressionChange(par, val, prev): 23 | return 24 | 25 | def onExportChange(par, val, prev): 26 | return 27 | 28 | def onEnableChange(par, val, prev): 29 | return 30 | 31 | def onModeChange(par, val, prev): 32 | return 33 | -------------------------------------------------------------------------------- /scripts/QuickExt/templates/ExtUtils/CustomParHelper/extParPropDatExec.py: -------------------------------------------------------------------------------- 1 | # me - this DAT. 2 | # 3 | # dat - the changed DAT 4 | # rows - a list of row indices 5 | # cols - a list of column indices 6 | # cells - the list of cells that have changed content 7 | # prev - the list of previous string contents of the changed cells 8 | # 9 | # Make sure the corresponding toggle is enabled in the DAT Execute DAT. 10 | # 11 | # If rows or columns are deleted, sizeChange will be called instead of row/col/cellChange. 12 | 13 | 14 | def onTableChange(dat): 15 | package = mod(me.dock.dock.name) 16 | package.CustomParHelper.UpdateCustomParsAsProperties() 17 | return 18 | 19 | def onRowChange(dat, rows): 20 | return 21 | 22 | def onColChange(dat, cols): 23 | return 24 | 25 | def onCellChange(dat, cells, prev): 26 | return 27 | 28 | def onSizeChange(dat): 29 | return 30 | -------------------------------------------------------------------------------- /scripts/QuickExt/templates/ExtUtils/CustomParHelper/extSeqParExec.py: -------------------------------------------------------------------------------- 1 | # me - this DAT 2 | # par - the Par object that has changed 3 | # val - the current value 4 | # prev - the previous value 5 | # 6 | # Make sure the corresponding toggle is enabled in the Parameter Execute DAT. 7 | 8 | def onValueChange(par, prev): 9 | # use par.eval() to get current value 10 | return 11 | 12 | # Called at end of frame with complete list of individual parameter changes. 13 | # The changes are a list of named tuples, where each tuple is (Par, previous value) 14 | def onValuesChanged(changes): 15 | package = mod(me.dock.name) 16 | package.CustomParHelper.OnSeqValuesChanged(changes) 17 | return 18 | 19 | def onPulse(par): 20 | return 21 | 22 | def onExpressionChange(par, val, prev): 23 | return 24 | 25 | def onExportChange(par, val, prev): 26 | return 27 | 28 | def onEnableChange(par, val, prev): 29 | return 30 | 31 | def onModeChange(par, val, prev): 32 | return 33 | -------------------------------------------------------------------------------- /scripts/QuickExt/templates/ExtUtils/ExtTest.py: -------------------------------------------------------------------------------- 1 | CustomParHelper: CustomParHelper = next(d for d in me.docked if 'ExtUtils' in d.tags).mod('CustomParHelper').CustomParHelper # import 2 | NoNode: NoNode = next(d for d in me.docked if 'ExtUtils' in d.tags).mod('NoNode').NoNode # import 3 | #### 4 | 5 | class ExtTest: 6 | def __init__(self, ownerComp): 7 | self.ownerComp = ownerComp 8 | CustomParHelper.Init(self, ownerComp, enable_properties=True, enable_callbacks=True) 9 | #CustomParHelper.DisableCallbacks() 10 | NoNode.Init(ownerComp, enable_chopexec=True, enable_datexec=True, enable_parexec=True, enable_keyboard_shortcuts=True) 11 | NoNode.SetMarkColor((0.5, 0.05, 0.5)) 12 | 13 | # CHOP Exec tests 14 | NoNode.RegisterChopExec(NoNode.ChopExecType.ValueChange, self.ownerComp.op('null_test_chopexec'), 'v1', self.onTestChopValueChange) 15 | NoNode.RegisterChopExec(NoNode.ChopExecType.OffToOn, self.ownerComp.op('null_test_chopexec'), 'v*, v2', self.onTestChopOffToOn) 16 | NoNode.RegisterChopExec(NoNode.ChopExecType.OnToOff, self.ownerComp.op('null_test_chopexec'), ['v1', 'v2'], self.onTestChopOnToOff) 17 | #NoNode.RegisterChopExec(NoNode.ChopExecType.WhileOn, self.ownerComp.op('null_test_chopexec'), '*', self.onTestChopWhileOn) 18 | #NoNode.RegisterChopExec(NoNode.ChopExecType.WhileOff, self.ownerComp.op('null_test_chopexec'), '*', self.onTestChopWhileOff) 19 | #NoNode.DisableChopExec() 20 | 21 | # DAT Exec tests 22 | NoNode.RegisterDatExec(NoNode.DatExecType.TableChange, self.ownerComp.op('null_test_datexec'), self.onTestDatExecTableChange) 23 | NoNode.RegisterDatExec(NoNode.DatExecType.RowChange, self.ownerComp.op('null_test_datexec'), self.onTestDatExecRowChange) 24 | NoNode.RegisterDatExec(NoNode.DatExecType.ColChange, self.ownerComp.op('null_test_datexec'), self.onTestDatExecColChange) 25 | NoNode.RegisterDatExec(NoNode.DatExecType.CellChange, self.ownerComp.op('null_test_datexec'), self.onTestDatExecCellChange) 26 | NoNode.RegisterDatExec(NoNode.DatExecType.SizeChange, self.ownerComp.op('null_test_datexec'), self.onTestDatExecSizeChange) 27 | #NoNode.DisableDatExec() 28 | 29 | # Parexec tests 30 | NoNode.RegisterParExec(NoNode.ParExecType.ValueChange, self.ownerComp, 'Float', self.onTestParValueChange) 31 | NoNode.RegisterParExec(NoNode.ParExecType.OnPulse, self.ownerComp, 'Pulse', self.onTestParOnPulse) 32 | NoNode.RegisterParExec(NoNode.ParExecType.ValueChange, self.ownerComp, self.ownerComp.par.Testseq0float, self.onTestParSeqValueChange) 33 | NoNode.RegisterParExec(NoNode.ParExecType.ValueChange, self.ownerComp.op('button1'), 'value0', self.onTestParButton1ValueChange) 34 | NoNode.RegisterParExec(NoNode.ParExecType.OnPulse, self.ownerComp.op('speed1'), 'resetpulse', self.onTestParSpeed1ResetPulse) 35 | #NoNode.DisableParExec() 36 | 37 | # Keyboard shortcut test 38 | NoNode.RegisterKeyboardShortcut('ctrl.t', self.onTestKeyboardShortcut) 39 | NoNode.DisableKeyboardShortcuts() 40 | 41 | 42 | # CHOP Exec callbacks 43 | def onTestChopValueChange(self, _channel, _sampleIndex, _val, _prev): 44 | debug(f'onTestChopValueChange: {_channel.name} {_sampleIndex} {_val} {_prev}') 45 | 46 | def onTestChopValueChange2(self, _channel, _sampleIndex, _val, _prev): 47 | debug(f'onTestChopValueChange2: {_channel.name} {_sampleIndex} {_val} {_prev}') 48 | 49 | def onTestChopOffToOn(self, _channel, _sampleIndex, _val, _prev): 50 | debug(f'onTestChopOffToOn: {_channel.name} {_sampleIndex} {_val} {_prev}') 51 | 52 | def onTestChopOnToOff(self, _channel, _sampleIndex, _val, _prev): 53 | debug(f'onTestChopOnToOff: {_channel.name} {_sampleIndex} {_val} {_prev}') 54 | 55 | def onTestChopWhileOn(self, _channel, _sampleIndex, _val, _prev): 56 | debug(f'onTestChopWhileOn: {_channel.name} {_sampleIndex} {_val} {_prev}') 57 | 58 | def onTestChopWhileOff(self, _channel, _sampleIndex, _val, _prev): 59 | debug(f'onTestChopWhileOff: {_channel.name} {_sampleIndex} {_val} {_prev}') 60 | 61 | 62 | # DAT Exec callbacks 63 | def onTestDatExecTableChange(self, _dat): 64 | debug(f'onTestDatExecTableChange: {_dat}') 65 | 66 | def onTestDatExecRowChange(self, _dat, _row): 67 | debug(f'onTestDatExecRowChange: {_dat} {_row}') 68 | 69 | def onTestDatExecColChange(self, _dat, _col): 70 | debug(f'onTestDatExecColChange: {_dat} {_col}') 71 | 72 | def onTestDatExecCellChange(self, _dat, _cells, _prev): 73 | debug(f'onTestDatExecCellChange: {_dat} {_cells} {_prev}') 74 | 75 | def onTestDatExecSizeChange(self, _dat): 76 | debug(f'onTestDatExecSizeChange: {_dat}') 77 | 78 | 79 | # Existing parameter callbacks 80 | def onParFloat(self, _par, _val, _prev): 81 | debug(f'onParFloat: {_par} {_val} {_prev}') 82 | 83 | def onParPulse(self, _par): 84 | debug(f'onParPulse: {_par}') 85 | print('printing parameters as self.par properties:', self.parFloat, self.parPulse, self.parGroupInt) 86 | print('printing parameters as self.eval properties:', self.evalFloat, self.evalPulse, self.evalGroupInt) 87 | 88 | def onParGroupInt(self, _parGroup, _val): 89 | debug(f'onParGroupInt: {_parGroup} {_val}') 90 | 91 | def onSeqTestseqN(self, idx): 92 | debug(f'onSeqTestN: {idx}') 93 | 94 | def onSeqTestseqNfloat(self, _par, idx, _val, _prev): 95 | debug(f'onSeqTestNFloat: {_par} {idx} {_val} {_prev}') 96 | 97 | def onSeqTestseqNstr(self, _par, idx, _val, _prev): 98 | debug(f'onSeqTestNStr: {_par} {idx} {_val} {_prev}') 99 | 100 | 101 | # Parexec callbacks 102 | def onTestParValueChange(self, _par, _val): 103 | debug(f'onTestParValueChange: {_par.name} {_val}') 104 | 105 | def onTestParOnPulse(self): 106 | debug(f'onTestParOnPulse') 107 | 108 | def onTestParSeqValueChange(self, _par, _val): 109 | debug(f'onTestParSeqValueChange: {_par.name} {_val}') 110 | 111 | def onTestParButton1ValueChange(self, _par, _val): 112 | debug(f'onTestParButton1ValueChange: {_par.name} {_val}') 113 | 114 | def onTestParSpeed1ResetPulse(self): 115 | debug(f'onTestParSpeed1ResetPulse') 116 | 117 | 118 | # Keyboard shortcut callback 119 | def onTestKeyboardShortcut(self): 120 | debug('onTestKeyboardShortcut: ctrl+t pressed') -------------------------------------------------------------------------------- /scripts/QuickExt/templates/ExtUtils/NoNode/extChopOffToOnExec.py: -------------------------------------------------------------------------------- 1 | def onOffToOn(channel, sampleIndex, val, prev): 2 | package = mod(me.dock.name).NoNode 3 | package.OnChopExec(package.ChopExecType.OffToOn, channel, sampleIndex, val, prev) 4 | return 5 | -------------------------------------------------------------------------------- /scripts/QuickExt/templates/ExtUtils/NoNode/extChopOnToOffExec.py: -------------------------------------------------------------------------------- 1 | def onOnToOff(channel, sampleIndex, val, prev): 2 | package = mod(me.dock.name).NoNode 3 | package.OnChopExec(package.ChopExecType.OnToOff, channel, sampleIndex, val, prev) 4 | return 5 | -------------------------------------------------------------------------------- /scripts/QuickExt/templates/ExtUtils/NoNode/extChopValueChangeExec.py: -------------------------------------------------------------------------------- 1 | def onValueChange(channel, sampleIndex, val, prev): 2 | package = mod(me.dock.name).NoNode 3 | package.OnChopExec(package.ChopExecType.ValueChange, channel, sampleIndex, val, prev) 4 | return 5 | -------------------------------------------------------------------------------- /scripts/QuickExt/templates/ExtUtils/NoNode/extChopWhileOffExec.py: -------------------------------------------------------------------------------- 1 | def whileOff(channel, sampleIndex, val, prev): 2 | package = mod(me.dock.name).NoNode 3 | package.OnChopExec(package.ChopExecType.WhileOff, channel, sampleIndex, val, prev) 4 | return 5 | -------------------------------------------------------------------------------- /scripts/QuickExt/templates/ExtUtils/NoNode/extChopWhileOnExec.py: -------------------------------------------------------------------------------- 1 | def whileOn(channel, sampleIndex, val, prev): 2 | package = mod(me.dock.name).NoNode 3 | package.OnChopExec(package.ChopExecType.WhileOn, channel, sampleIndex, val, prev) 4 | return 5 | -------------------------------------------------------------------------------- /scripts/QuickExt/templates/ExtUtils/NoNode/extDatCellChangeExec.py: -------------------------------------------------------------------------------- 1 | def onCellChange(dat, cells, prev): 2 | package = mod(me.dock.name).NoNode 3 | package.OnDatExec(package.DatExecType.CellChange, dat, cells=cells, prev=prev) 4 | return 5 | -------------------------------------------------------------------------------- /scripts/QuickExt/templates/ExtUtils/NoNode/extDatColChangeExec.py: -------------------------------------------------------------------------------- 1 | def onColChange(dat, cols): 2 | package = mod(me.dock.name).NoNode 3 | package.OnDatExec(package.DatExecType.ColChange, dat, cols=cols) 4 | return 5 | -------------------------------------------------------------------------------- /scripts/QuickExt/templates/ExtUtils/NoNode/extDatRowChangeExec.py: -------------------------------------------------------------------------------- 1 | def onRowChange(dat, rows): 2 | package = mod(me.dock.name).NoNode 3 | package.OnDatExec(package.DatExecType.RowChange, dat, rows=rows) 4 | return 5 | -------------------------------------------------------------------------------- /scripts/QuickExt/templates/ExtUtils/NoNode/extDatSizeChangeExec.py: -------------------------------------------------------------------------------- 1 | def onSizeChange(dat): 2 | package = mod(me.dock.name).NoNode 3 | package.OnDatExec(package.DatExecType.SizeChange, dat) 4 | return 5 | -------------------------------------------------------------------------------- /scripts/QuickExt/templates/ExtUtils/NoNode/extDatTableChangeExec.py: -------------------------------------------------------------------------------- 1 | def onTableChange(dat): 2 | package = mod(me.dock.name).NoNode 3 | package.OnDatExec(package.DatExecType.TableChange, dat) 4 | return 5 | 6 | -------------------------------------------------------------------------------- /scripts/QuickExt/templates/ExtUtils/NoNode/extKeyboardin_callbacks.py: -------------------------------------------------------------------------------- 1 | # me - This DAT 2 | # 3 | # dat - The DAT that received the key event 4 | # keyInfo - A namedtuple containing the following members: 5 | # key - The name of the key attached to the event. 6 | # This tries to be consistent regardless of which language 7 | # the keyboard is set to. The values will be the english/ASCII 8 | # values that most closely match the key pressed. 9 | # This is what should be used for shortcuts instead of 'character'. 10 | # webCode - The name of the key following web-programming standards. 11 | # character - The unicode character generated. 12 | # alt - True if the alt modifier is pressed 13 | # lAlt - True if the left-alt modifier is pressed 14 | # rAlt - True if the right-alt modifier is pressed 15 | # ctrl - True if the ctrl modifier is pressed 16 | # lCtrl - True if the left-ctrl modifier is pressed 17 | # rCtrl - True if the right-ctrl modifier is pressed 18 | # shift - True if the shift modifier is pressed 19 | # lShift - True if the left-shift modifier is pressed 20 | # rShift - True if the right-shift modifier is pressed 21 | # state - True if the event is a key press event 22 | # time - The time when the event came in milliseconds 23 | # cmd - True if the cmd modifier is pressed 24 | # lCmd - True if the left-cmd modifier is pressed 25 | # rCmd - True if the right-cmd modifier is pressed 26 | 27 | def onKey(dat, keyInfo): 28 | return 29 | 30 | # shortcutName is the name of the shortcut 31 | 32 | def onShortcut(dat, shortcutName, time): 33 | package = mod(me.dock.dock.name).NoNode 34 | package.OnKeyboardShortcut(shortcutName) 35 | return; 36 | -------------------------------------------------------------------------------- /scripts/QuickExt/templates/ExtUtils/NoNode/extParExecNoNodeOnPulse.py: -------------------------------------------------------------------------------- 1 | 2 | def onPulse(par): 3 | ## In Extension code implement as follows: 4 | # def On(self, _par): 5 | # ... 6 | package = mod(me.dock.name).NoNode 7 | package.OnParExec(package.ParExecType.OnPulse, par) -------------------------------------------------------------------------------- /scripts/QuickExt/templates/ExtUtils/NoNode/extParExecNoNodeValueChange.py: -------------------------------------------------------------------------------- 1 | 2 | def onValueChange(par, prev): 3 | ## In Extension code implement as follows: 4 | # def On(self, _par, _val, _prev): 5 | # ... 6 | package = mod(me.dock.name).NoNode 7 | package.OnParExec(package.ParExecType.ValueChange, par, par.eval(), prev) 8 | 9 | -------------------------------------------------------------------------------- /scripts/QuickExt/templates/ExtUtils/extTemplate.py: -------------------------------------------------------------------------------- 1 | # < - DO NOT REMOVE THIS VERY IMPORTANT LINE!!! used by QuickExt to inject extension - > 2 | 3 | CustomParHelper: CustomParHelper = next(d for d in me.docked if 'ExtUtils' in d.tags).mod('CustomParHelper').CustomParHelper # import 4 | ### 5 | 6 | class QuickExtTemplate: 7 | def __init__(self, ownerComp): 8 | CustomParHelper.Init(self, ownerComp, enable_properties=True, enable_callbacks=True) 9 | self.ownerComp = ownerComp 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /scripts/QuickMarks/QuickmarkStorageExt.py: -------------------------------------------------------------------------------- 1 | ### Code and idea from Alex Guevara 2 | ### Modified by Function Store 3 | 4 | from datetime import datetime 5 | from TDStoreTools import StorageManager 6 | 7 | TDF = op.TDModules.mod.TDFunctions 8 | 9 | class QuickmarkStorageExt: 10 | def __init__(self, ownerComp): 11 | # The component to which this extension is attached 12 | self.ownerComp = ownerComp 13 | 14 | # Stored items for quickmarks 15 | storedItems = [{'name': f'Quickmark{i}', 'default': None} for i in range(10)] 16 | self.stored = StorageManager(self, ownerComp, storedItems) 17 | 18 | def custom_print(self, message): 19 | # Prints a message to the status bar and the console 20 | current_time = datetime.now().strftime('%H:%M:%S') 21 | formatted_message = f"{current_time} {message}" 22 | 23 | ui.status = formatted_message 24 | 25 | def StoreQuickmark(self, key): 26 | # Get current pane details and store it 27 | current_pane = ui.panes.current 28 | # Workaround to get precise position and zoom of the network 29 | quickmark_value = { 30 | 'current_network': current_pane.owner.path, 31 | 'current_child': current_pane.owner.currentChild.path if current_pane.owner.currentChild else None, 32 | 'x': current_pane.x, 33 | 'y': current_pane.y, 34 | 'zoom': current_pane.zoom 35 | } 36 | self.stored[key] = quickmark_value 37 | self.custom_print(f"QuickMarks {key} is set to: {quickmark_value}") 38 | 39 | def UnstoreQuickmark(self, key): 40 | # Unstores the quickmark specified by the key 41 | self.stored[key] = None 42 | self.custom_print(f"QuickMarks {key} unstored") 43 | 44 | def RetrieveQuickmark(self, key): 45 | # Get the quickmark from the stored dictionary 46 | quickmark = self.stored.get(key, None) 47 | if quickmark and 'current_network' in quickmark: 48 | ui.panes.current.owner = op(quickmark['current_network']) 49 | if 'current_child' in quickmark: 50 | child_op = op(quickmark['current_child']) 51 | if child_op: 52 | child_op.current = True 53 | child_op.selected = True 54 | 55 | ui.panes.current.x = quickmark.get('x', 0) 56 | ui.panes.current.y = quickmark.get('y', 0) 57 | ui.panes.current.zoom = quickmark.get('zoom', 1) 58 | 59 | self.custom_print(f"QuickMarks Jump to {key}") 60 | return quickmark 61 | 62 | def HandleShortcut(self, shortcutName): 63 | # Extract the key number from the shortcutName 64 | key_number = shortcutName.split('.')[-1] 65 | if shortcutName == 'ctrl.alt.shift.0': 66 | parent().par.Active = not parent().par.Active 67 | self.custom_print(f"QuickMarks Active is now set to: {parent().par.Active}") 68 | 69 | elif shortcutName.startswith('ctrl.') and 'alt.' not in shortcutName and 'shift.' not in shortcutName: 70 | if parent().par.Active: 71 | quickmark_key = f"Quickmark{key_number}" 72 | self.RetrieveQuickmark(quickmark_key) 73 | self.RetrieveQuickmark(quickmark_key) # need to call twice to get the correct position 74 | 75 | # For storing the quickmark with 'ctrl.' but not 'ctrl.alt.' or 'ctrl.0' 76 | elif shortcutName.startswith('ctrl.alt.') and 'shift.' not in shortcutName and key_number in [str(i) for i in range(1, 10)]: 77 | quickmark_key = f"Quickmark{key_number}" 78 | self.StoreQuickmark(quickmark_key) 79 | 80 | # For unstoring the quickmark with 'ctrl.alt.' 81 | elif 'ctrl.' in shortcutName and 'alt.shift.' in shortcutName and key_number in [str(i) for i in range(1, 10)]: 82 | quickmark_key = f"Quickmark{key_number}" 83 | self.UnstoreQuickmark(quickmark_key) 84 | -------------------------------------------------------------------------------- /scripts/QuickOp/QuickOpExt.py: -------------------------------------------------------------------------------- 1 | from enum import Enum, auto 2 | 3 | class FamilyOrder(Enum): 4 | COMP = auto() 5 | TOP = auto() 6 | CHOP = auto() 7 | SOP = auto() 8 | MAT = auto() 9 | DAT = auto() 10 | CUSTOM = auto() 11 | 12 | class QuickOpExt: 13 | def __init__(self, ownerComp): 14 | self.ownerComp = ownerComp 15 | 16 | self.lastSelectedPos = {} 17 | self.newPos = (0,0) 18 | 19 | self.MenuOp = op('/ui/dialogs/menu_op') 20 | self.familyPanel = op('/ui/dialogs/menu_op/families/family').panel 21 | self.nodeScript = op('/ui/dialogs/menu_op/node_script') 22 | self.NodeTable = op('/ui/dialogs/menu_op/nodetable') 23 | self.opTable = op('/ui/dialogs/menu_op/op_table') 24 | self.connecttoTable = op('/ui/dialogs/menu_op/connectto') 25 | self.menuLocal = op('/ui/dialogs/menu_op/local/set_variables') 26 | 27 | self.newOpPending = False 28 | self.menuClicked = False 29 | self.origOp = None 30 | self.isInsert = False 31 | 32 | @property 33 | def hk_add(self) -> bool: 34 | return bool(self.ownerComp.op('null_hk')['add'].eval()) 35 | 36 | @property 37 | def hk_insert(self) -> bool: 38 | return bool(self.ownerComp.op('null_hk')['insert'].eval()) 39 | 40 | def OnUIChange(self, _op: OP) -> None: 41 | if not (self.hk_add or self.hk_insert): 42 | return 43 | self.isInsert = self.hk_insert 44 | 45 | new_pos = (_op.nodeX, _op.nodeY) 46 | if new_pos != self.lastSelectedPos: 47 | self.onPosChange(_op, new_pos, self.lastSelectedPos) 48 | 49 | 50 | def onPosChange(self, _op, new_pos, last_pos) -> None: 51 | self.origOp = _op 52 | self.newPos = new_pos 53 | self.newOpPending = True 54 | 55 | self.OverrideNodeScriptActive(False) 56 | # VERY DANGEROUS, THIS CAN BREAK OP CREATE IF NOT RE-ENABLED! 57 | # So we add a broad try-except block to ensure it gets re-enabled 58 | try: 59 | self.MenuOp.par.winopen.pulse() 60 | self.setFamily(self.origOp.family) 61 | 62 | self.MenuOp.op('search/textfield').panel.field = '' 63 | self.MenuOp.op('search/textfield').setKeyboardFocus() 64 | 65 | # restore pos 66 | self.origOp.nodeX = last_pos['op_pos'][0] 67 | self.origOp.nodeY = last_pos['op_pos'][1] 68 | 69 | for _dock in self.origOp.docked: 70 | if _dock in last_pos['docked_pos'].keys(): 71 | _dock.nodeX = last_pos['docked_pos'][_dock][0] 72 | _dock.nodeY = last_pos['docked_pos'][_dock][1] 73 | except: 74 | self.OverrideNodeScriptActive(True) 75 | self.OnCancel() 76 | 77 | 78 | def OnOpCreate(self, sel_id): 79 | if not (self.menuClicked and self.newOpPending): 80 | return 81 | 82 | self.OverrideNodeScriptActive(False) 83 | # VERY DANGEROUS, THIS CAN BREAK OP CREATE IF NOT RE-ENABLED! 84 | # So we add a broad try-except block to ensure it gets re-enabled 85 | try: 86 | self.MenuOp.par.winclose.pulse() 87 | 88 | owner = self.origOp.parent() 89 | if not owner: 90 | return 91 | opType = self.id_to_opType(sel_id) 92 | name = self.id_to_name(sel_id) 93 | 94 | ui.undo.startBlock('Creating OP (QuickOp)') 95 | 96 | new_op = owner.create(opType, name) 97 | 98 | ui.undo.endBlock() 99 | 100 | new_op.nodeX = self.newPos[0] 101 | new_op.nodeY = self.newPos[1] 102 | 103 | if self.isInsert and len(self.origOp.outputConnectors): 104 | if len(new_op.outputConnectors) and len(self.origOp.outputConnectors): 105 | if output_conn := new_op.outputConnectors[0]: 106 | if conns := self.origOp.outputConnectors[0].connections: 107 | for conn in conns: 108 | output_conn.connect(conn) 109 | if len(new_op.inputConnectors) and len(self.origOp.outputConnectors): 110 | if input_conn := new_op.inputConnectors[0]: 111 | input_conn.connect(self.origOp) 112 | 113 | 114 | new_op.cook(force=True) 115 | self.origOp.selected = False 116 | new_op.current = True 117 | new_op.selected = True 118 | new_op.viewer = True 119 | 120 | self.OverrideNodeScriptActive(True) 121 | self.OnCancel() 122 | except: 123 | self.OverrideNodeScriptActive(True) 124 | self.OnCancel() 125 | 126 | 127 | def id_to_opType(self, id): 128 | return self.opTable[id+1,'opType'] 129 | 130 | 131 | def id_to_name(self, id): 132 | return self.opTable[id+1,'name'] 133 | 134 | 135 | def OnSelectOP(self, _op): 136 | self.lastSelectedPos = {'op_pos': (_op.nodeX, _op.nodeY), 'docked_pos': {_dock: (_dock.nodeX, _dock.nodeY) for _dock in _op.docked}} 137 | 138 | def OnFocusLoss(self) -> None: 139 | if not self.menuClicked: 140 | self.newOpPending = False 141 | self.menuClicked = False 142 | self.isInsert = False 143 | self.OverrideNodeScriptActive(True) 144 | return 145 | 146 | def OnMenuClick(self) -> None: 147 | self.menuClicked = True 148 | self.OverrideNodeScriptActive(True) 149 | return 150 | 151 | def OnCancel(self) -> None: 152 | self.newOpPending = False 153 | self.menuClicked = False 154 | self.isInsert = False 155 | self.OverrideNodeScriptActive(True) 156 | 157 | 158 | def OverrideNodeScriptActive(self, _bool): 159 | # VERY DANGEROUS, THIS CAN BREAK OP CREATE IF NOT RE-ENABLED! 160 | self.nodeScript.par.active = _bool 161 | 162 | 163 | def setFamily(self, sel_fam): 164 | self.connecttoTable[0,0] = sel_fam 165 | self.connecttoTable[0,1] = 'output' 166 | self.menuLocal['menu_type', 1] = sel_fam 167 | self.menuLocal['menu_pane', 1] = ui.panes.current 168 | 169 | self.familyPanel.cellradioid = FamilyOrder[sel_fam].value-1 170 | -------------------------------------------------------------------------------- /scripts/QuickPane/QuickPaneExt.py: -------------------------------------------------------------------------------- 1 | class QuickPaneExt: 2 | def __init__(self, ownerComp): 3 | # The component to which this extension is attached 4 | self.ownerComp = ownerComp 5 | 6 | self.leftPane = None 7 | self.rightPane = None 8 | self.topPane = None 9 | self.bottomPane = None 10 | self.pane_map = { 11 | 'left': 'leftPane', 12 | 'right': 'rightPane', 13 | 'top': 'topPane', 14 | 'bottom': 'bottomPane' 15 | } 16 | 17 | @property 18 | def allPanes(self): 19 | return [pane.owner for pane in ui.panes] 20 | 21 | def OnOpenDir(self, dir='left'): 22 | curr_comp = ui.panes.current.owner.currentChild 23 | if not curr_comp: 24 | return 25 | 26 | pane_actions = { 27 | 'left': ('leftPane', lambda: ui.panes.current.splitLeft()), 28 | 'right': ('rightPane', lambda: ui.panes.current.splitRight()), 29 | 'top': ('topPane', lambda: ui.panes.current.splitTop()), 30 | 'bottom': ('bottomPane', lambda: ui.panes.current.splitBottom()) 31 | } 32 | 33 | pane_ratios = { 34 | 'left': self.ownerComp.par.Ratiow.eval(), 35 | 'right': self.ownerComp.par.Ratiow.eval(), 36 | 'top': 1-self.ownerComp.par.Ratioh.eval(), 37 | 'bottom': 1-self.ownerComp.par.Ratioh.eval() 38 | } 39 | 40 | pane_attr, split_method = pane_actions.get(dir, (None, None)) 41 | if not pane_attr: 42 | return # Direction not recognized 43 | 44 | pane_exists = lambda _my_pane: _my_pane.id in [pane.id for pane in ui.panes] if _my_pane else None 45 | my_pane = getattr(self, pane_attr) 46 | if not pane_exists(my_pane) and curr_comp.isCOMP: 47 | new_pane = split_method() 48 | setattr(self, pane_attr, new_pane) 49 | else: 50 | self.onCloseDir(dir) 51 | return 52 | 53 | if new_pane: 54 | new_pane.owner = curr_comp 55 | new_pane.home() 56 | new_pane.ratio = pane_ratios.get(dir) 57 | panenav = op(f'/ui/panes/panebar/{new_pane.name}/panenav') 58 | if panenav: 59 | self.setBorder(panenav, 1) 60 | 61 | def onCloseDir(self, dir='left'): 62 | pane_attr = self.pane_map.get(dir) 63 | if pane_attr: 64 | pane = getattr(self, pane_attr) 65 | 66 | if pane: 67 | panenav = op(f'/ui/panes/panebar/{pane.name}/panenav') 68 | if panenav: 69 | self.setBorder(panenav, 0) 70 | setattr(self, pane_attr, None) 71 | pane.close() 72 | 73 | def clearBorders(self): 74 | for _pane in ui.panes: 75 | if _pane.type == PaneType.NETWORKEDITOR: 76 | panenav = op(f'/ui/panes/panebar/{_pane.name}/panenav') 77 | self.setBorder(panenav, 0) 78 | 79 | 80 | def setBorder(self, panenav, state): 81 | panenav.parGroup.bordera = (0.1, 0.4, 0.1) 82 | panenav.par.borderover = state 83 | panenav.par.leftborder = state 84 | panenav.par.rightborder = state 85 | panenav.par.bottomborder = state 86 | panenav.par.topborder = state -------------------------------------------------------------------------------- /scripts/QuickParCustom/QuickParCustomExt.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | CustomParHelper: CustomParHelper = next(d for d in me.docked if 'ExtUtils' in d.tags).mod('CustomParHelper').CustomParHelper # import 4 | ### 5 | 6 | class QuickParCustomExt: 7 | def __init__(self, ownerComp): 8 | CustomParHelper.Init(self, ownerComp, enable_properties=True, enable_callbacks=True) 9 | self.ownerComp = ownerComp 10 | self.compEditor = op('/sys/TDDialogs/CompEditor') 11 | self.customParPromoter : customParPromoterExt = getattr(op, 'FNS_CPP', None) 12 | 13 | @property 14 | def rolloverPar(self): 15 | return ui.rolloverPar 16 | 17 | @property 18 | def mod(self): 19 | return self.ownerComp.op('null_hk')['shift'].eval() 20 | 21 | def onShortcut(self, shortcutName): 22 | _par = self.rolloverPar 23 | if _par is None: 24 | return 25 | _owner = _par.owner 26 | _target = None 27 | do_promote = True 28 | do_open = False 29 | 30 | if _par.mode == ParMode.EXPORT: 31 | return 32 | 33 | if shortcutName in [self.evalShortcutrolloverpromote, self.evalShortcutrolloverpromotemod]: 34 | if _owner is not None and _par is not None: 35 | if _par.mode in [ParMode.BIND, ParMode.EXPRESSION]: 36 | _target, _par = self._getExpressionTarget(_par) 37 | do_open = True 38 | do_promote = False 39 | if do_promote: 40 | self.customParPromoter.Target = _owner.parent() if _target is None else _target 41 | self.customParPromoter.Reference = _owner 42 | ui.undo.startBlock('Promote param') 43 | _new_par = self.customParPromoter.PromotePar(_par, None) 44 | ui.undo.endBlock() 45 | if _new_par is not None: 46 | _par = _new_par[0] 47 | _owner = _par.owner 48 | if do_open: 49 | self.compEditorOpenPar(_owner if _target is None else _target, _par) 50 | elif shortcutName in [self.evalShortcutrollovercustomize]: 51 | if _owner.isCOMP: 52 | self.compEditorOpenPar(_owner, _par) 53 | else: 54 | if _par.mode in [ParMode.BIND, ParMode.EXPRESSION]: 55 | _owner, _par = self._getExpressionTarget(_par) 56 | self.compEditorOpenPar(_owner, _par) 57 | elif shortcutName in [self.evalShortcutrolloverswitchparmode]: 58 | if _par.mode not in [ParMode.BIND, ParMode.EXPRESSION]: 59 | return 60 | 61 | _expr_target = _par.expr if _par.mode == ParMode.EXPRESSION else _par.bindExpr 62 | _par.mode = ParMode.EXPRESSION if _par.mode == ParMode.BIND else ParMode.BIND 63 | if _par.mode == ParMode.EXPRESSION: 64 | _par.expr = _expr_target 65 | else: 66 | _par.bindExpr = _expr_target 67 | 68 | 69 | def _getExpressionTarget(self, _par): 70 | if _par.mode == ParMode.EXPRESSION: 71 | _exprEval = _par.evalExpression() 72 | if isinstance(_exprEval, Par): 73 | return (_exprEval.owner, _exprEval) 74 | elif _par.mode == ParMode.BIND: 75 | _master = _par.bindMaster 76 | if isinstance((_master_par:=_master), Par) and isinstance( (_master_comp:=_master.owner) , COMP): 77 | return (_master_comp, _master_par) 78 | 79 | def compEditorOpenPar(self, comp, _par): 80 | par_name = _par.name 81 | if not self.compEditor.op('window').isOpen: 82 | self.compEditor.Open(comp) 83 | else: 84 | self.compEditor.Connect(comp) 85 | self.compEditor.CurrentPage = _par.page.name 86 | self.compEditor.CurrentPar = _par 87 | self.compEditor.RefreshListers() 88 | _comp_editor_pages = self.compEditor.op('pagesAndParameters/listerPages') 89 | _comp_editor_pars = self.compEditor.op('pagesAndParameters/listerPars') 90 | _page = _par.page.name 91 | 92 | # get page index from comp editor pages 93 | _page_list = list(filter(lambda x: x['pageName'] == _page, _comp_editor_pages.Data)) 94 | _page_idx = _page_list[0]['sourceIndex'] 95 | if _page_idx != 'Auto-Header': 96 | _comp_editor_pages.SelectRow(_page_idx+1) 97 | _comp_editor_pages.scroll(_page_idx, 0) 98 | 99 | if not isinstance(_par, Par): 100 | return 101 | if len(_par.parGroup) > 1: 102 | par_name = _par.parGroup.name 103 | _par_list = list(filter(lambda x: x['ParName'] == par_name, _comp_editor_pars.Data)) 104 | _par_idx = _par_list[0]['sourceIndex'] 105 | if _par_idx != 'Auto-Header': 106 | _comp_editor_pars.SelectRow(_par_idx+1) 107 | _comp_editor_pars.scroll(_par_idx, 0) -------------------------------------------------------------------------------- /scripts/QuickParent/QuickParentExt.py: -------------------------------------------------------------------------------- 1 | class QuickParentExt: 2 | def __init__(self, ownerComp): 3 | self.ownerComp = ownerComp 4 | self.popDialog = self.ownerComp.op('popDialog') 5 | self.paneParent = None 6 | 7 | 8 | 9 | def AddParentshortcut(self, _target): 10 | self.paneParent = _target 11 | defaultText = self.paneParent.name if self.paneParent.par.parentshortcut.eval() == '' else self.paneParent.par.parentshortcut.eval() 12 | self.popDialog.Open(text='Add Parent Shortcut', title='Add Parent Shortcut', buttons=['OK', 'Cancel'], 13 | escButton=2, enterButton=1, escOnClickAway=True, textEntry=defaultText, callback=self.OnAddParentshortcutCallback) 14 | 15 | 16 | def OnAddParentshortcutCallback(self, result): 17 | if result['button'] == 'OK': 18 | if self.paneParent is not None: 19 | parentshortcutpar = self.paneParent.par.parentshortcut 20 | if parentshortcutpar is not None: 21 | parentshortcutpar.val = result['enteredText'] 22 | 23 | 24 | -------------------------------------------------------------------------------- /scripts/ResetPLS/script_reset.py: -------------------------------------------------------------------------------- 1 | customables = ['baseCOMP', 2 | 'containerCOMP', 3 | 'geometryCOMP', 4 | 'scriptCHOP', 5 | 'scriptDAT', 6 | 'scriptSOP', 7 | 'scriptTOP'] 8 | 9 | toreset = op('null_toreset') 10 | custompars = op('null_custom_resetpars') 11 | 12 | for r in toreset.rows()[1:]: 13 | op_type = r[0].val 14 | o = op(r[1].val) 15 | 16 | # there are some exceptions 17 | if 'DAT' in op_type and op_type != 'scriptDAT': 18 | o.par.clear.pulse() 19 | elif op_type in ['replicatorCOMP']: 20 | o.par.recreateall.pulse() 21 | elif op_type in ['audiofileinCHOP','moviefileinTOP']: 22 | o.par.cuepulse.pulse() 23 | elif op_type == 'actorCOMP': 24 | o.par.updatecspulse.pulse() 25 | elif op_type in ['timerCHOP','flexsolverCOMP','bulletsolverCOMP','flowTOP']: 26 | o.par.start.pulse() 27 | elif op_type not in customables: 28 | # default case 29 | if op_type in ['speedCHOP']: 30 | if o.parent().OPType == 'timeCOMP' and not parent().par.Timeline.eval(): 31 | continue 32 | try: 33 | o.par.resetpulse.pulse() 34 | except: 35 | continue 36 | 37 | if op_type in customables: 38 | for resetpar in op('table_custom_resetpars').rows(): 39 | try: 40 | o.par[resetpar[0].val].pulse() 41 | except: 42 | continue 43 | 44 | # misc 45 | if parent().par.Timeline.eval(): 46 | op('/').time.frame = parent().par.Frame.eval() 47 | try: 48 | ui.panes.current.owner.time.frame = parent().par.Frame.eval() 49 | except: 50 | pass 51 | 52 | if parent().par.Customscript.eval(): 53 | op('callbackManager').Execute('onReset')() -------------------------------------------------------------------------------- /scripts/SetSmoothness/SmoothnessExt.py: -------------------------------------------------------------------------------- 1 | """ 2 | Extension classes enhance TouchDesigner components with python. An 3 | extension is accessed via ext.ExtensionClassName from any operator 4 | within the extended component. If the extension is promoted via its 5 | Promote Extension parameter, all its attributes with capitalized names 6 | can be accessed externally, e.g. op('yourComp').PromotedFunction(). 7 | 8 | Help: search "Extensions" in wiki 9 | """ 10 | 11 | from TDStoreTools import StorageManager 12 | import TDFunctions as TDF 13 | 14 | class SmoothnessExt: 15 | """ 16 | SmoothnessExt description 17 | """ 18 | def __init__(self, ownerComp): 19 | # The component to which this extension is attached 20 | self.ownerComp = ownerComp 21 | 22 | def OnSelected(self): 23 | selected = ui.panes.current.owner.selectedChildren 24 | self.setSmoothness(selected) 25 | 26 | def OnAll(self): 27 | selected = selected = ui.panes.current.owner.ops('*') 28 | self.setSmoothness(selected) 29 | 30 | def setSmoothness(self, _ops): 31 | insmooth = parent().par.Inputfiltertype.menuIndex 32 | viewsmooth = parent().par.Filtertype.menuIndex 33 | 34 | tops = list(filter(lambda _op: _op.family == 'TOP' and _op.pars('inputfiltertype', 'filtertype') ,_ops)) 35 | 36 | for top in tops: 37 | top.par.inputfiltertype = insmooth 38 | top.par.filtertype = viewsmooth+1 #skipping same as input 39 | 40 | comps = list(filter(lambda _op: _op.family == 'COMP' and _op.pars('Inputfiltertype', 'Filtertype'), _ops)) 41 | for comp in comps: 42 | comp.par.Inputfiltertype = insmooth 43 | comp.par.Filtertype = viewsmooth+1 #skipping same as input 44 | -------------------------------------------------------------------------------- /scripts/SwapOps/SwapOpsExt.py: -------------------------------------------------------------------------------- 1 | class SwapOpsExt: 2 | """ 3 | SwapOpsExt description 4 | """ 5 | def __init__(self, ownerComp): 6 | # The component to which this extension is attached 7 | self.ownerComp = ownerComp 8 | 9 | def OnSwap(self): 10 | selected = ui.panes.current.owner.selectedChildren 11 | selected = sorted(selected, key=lambda x: x.nodeCenterX) 12 | num_selected = len(selected) 13 | 14 | ui.undo.startBlock("Swap selected OPs") 15 | # reversed effect: middle-out order 16 | for i in reversed(range(int(num_selected/2))): 17 | op_a = selected[i] 18 | op_b = selected[num_selected-i-1] 19 | self.swapPosition(op_a, op_b) 20 | self.swapConnectorsMult(op_a, op_b) 21 | ui.undo.endBlock() 22 | 23 | def swapPosition(self, op1: OP, op2: OP): 24 | # Save the current positions of the operators 25 | x1, y1 = op1.nodeCenterX , op1.nodeCenterY 26 | x2, y2 = op2.nodeCenterX , op2.nodeCenterY 27 | op1.nodeCenterX, op1.nodeCenterY = x2, y2 28 | op2.nodeCenterX, op2.nodeCenterY = x1, y1 29 | 30 | def swapConnectorsMult(self, op1: OP, op2: OP): 31 | # "leftmost" is always the male 32 | orig_male_op = op1 33 | orig_male_inputs = orig_male_op.inputs.copy() 34 | orig_male_outputs = orig_male_op.outputs.copy() 35 | orig_female_op = op2 36 | orig_female_inputs = orig_female_op.inputs.copy() 37 | orig_female_outputs = orig_female_op.outputs.copy() 38 | 39 | # prepare for annoying edge case 40 | middle_op_inputs = None 41 | if middle_op := list(set(orig_male_outputs).intersection(set(orig_female_inputs))): 42 | # if there was an OP in between the two swapping ones 43 | # and that OP had multi-ins 44 | # then we do this to keep the order after connecting new male outs 45 | # see annoying edge case comments 46 | middle_op = middle_op[0] 47 | middle_op_inputs = middle_op.inputs.copy() 48 | 49 | for _, _ in enumerate(orig_male_op.inputConnectors): 50 | orig_male_op.inputConnectors[0].disconnect() 51 | for _, _ in enumerate(orig_male_op.outputConnectors): 52 | orig_male_op.outputConnectors[0].disconnect() 53 | for _, _ in enumerate(orig_female_op.inputConnectors): 54 | orig_female_op.inputConnectors[0].disconnect() 55 | for _, _ in enumerate(orig_female_op.outputConnectors): 56 | orig_female_op.outputConnectors[0].disconnect() 57 | 58 | new_male_op, new_female_op = orig_female_op, orig_male_op # "switch" 59 | 60 | 61 | ## easy ones ## 62 | 63 | # new female outs to original female outs 64 | for idx, ofo in enumerate(orig_female_outputs): 65 | new_female_op.outputConnectors[0].connect(ofo) 66 | 67 | # if they were connected (next to eachother) connect them again (swapped) 68 | if orig_female_op in orig_male_outputs: 69 | new_male_op.outputConnectors[0].connect(new_female_op.inputConnectors[0]) 70 | 71 | ## edge casing ## 72 | 73 | if (not new_female_op.isMultiInputs) and (not new_male_op.isMultiInputs): 74 | # if both have multi-inputs (not sure why this attribute is the opposite...) 75 | # then we swap everything 76 | for idx, omi in enumerate(orig_male_inputs): 77 | new_male_op.inputConnectors[idx].connect(omi) 78 | 79 | for idx, ofi in enumerate(orig_female_inputs): 80 | if ofi != new_female_op: 81 | new_female_op.inputConnectors[idx].connect(ofi) 82 | elif new_male_op.isMultiInputs and (not new_female_op.isMultiInputs): 83 | # if orig male had multi ins but new male does not (again this bool is inverted...) 84 | # then we keep the multi-ins in the new female 85 | new_male_op.inputConnectors[0].connect(orig_male_inputs[0]) 86 | 87 | # skip first connector as it's now connected to new male 88 | for idx, omi in enumerate(orig_male_inputs[1:]): 89 | if omi != orig_male_op: 90 | new_female_op.inputConnectors[1+idx].connect(omi) 91 | elif new_female_op.isMultiInputs and (not new_male_op.isMultiInputs): 92 | # if orig female had multi ins but new female does not (again this bool is inverted...) 93 | # then we keep the multi-ins in the new male 94 | new_male_op.inputConnectors[0].connect(orig_male_inputs[0]) 95 | 96 | # skip first connector as it's now connected to new male 97 | for idx, ofi in enumerate(orig_female_inputs[1:]): 98 | if ofi != orig_female_op: 99 | new_male_op.inputConnectors[1+idx].connect(ofi) 100 | pass 101 | else: 102 | # both are "single input" 103 | limit_len = min(len(orig_male_inputs),len(new_male_op.inputConnectors)) 104 | for omi in orig_male_inputs[:limit_len]: 105 | if omi != new_male_op: 106 | new_male_op.inputConnectors[0].connect(omi) 107 | 108 | 109 | #dirty... 110 | if middle_op: 111 | # this was the original case 112 | for ofi in orig_female_inputs: 113 | if ofi != orig_male_op: 114 | new_female_op.inputConnectors[0].connect(ofi) 115 | else: 116 | new_female_op.inputConnectors[0].connect(orig_female_op.outputConnectors[0]) 117 | 118 | # annoying edge case 119 | if middle_op_inputs: 120 | for _, _ in enumerate(middle_op.inputConnectors): 121 | middle_op.inputConnectors[0].disconnect() 122 | 123 | # normal case 124 | for omo in orig_male_outputs: 125 | if omo not in new_male_op.outputs and omo != new_male_op: 126 | new_male_op.outputConnectors[0].connect(omo) 127 | pass 128 | 129 | # annoying edge case 130 | if middle_op_inputs: 131 | for idx, moi in enumerate(middle_op_inputs[1:]): 132 | if orig_male_op != moi: 133 | middle_op.inputConnectors[1+idx].connect(moi) 134 | 135 | -------------------------------------------------------------------------------- /scripts/SwitchOPs/ExtSwitchOp.py: -------------------------------------------------------------------------------- 1 | 2 | class ExtSwitchOp: 3 | def __init__(self, ownerComp): 4 | self.ownerComp = ownerComp 5 | self.fifo = self.ownerComp.op('fifo1') 6 | 7 | def OnSelectOP(self, _op): 8 | if _op not in self.fifo.rows(val=True): 9 | self.fifo.appendRow(_op) 10 | pass 11 | 12 | def OnSwitch(self): 13 | _current = ui.panes.current.owner.currentChild 14 | _swop = next((_op for _op in self.fifo.rows(val=True) if _op != _current.path), None) 15 | opex(_swop[0]).current = True 16 | pass -------------------------------------------------------------------------------- /scripts/UI_OpColor/ExtOpColorChange.py: -------------------------------------------------------------------------------- 1 | CustomParHelper: CustomParHelper = next(d for d in me.docked if 'ExtUtils' in d.tags).mod('CustomParHelper').CustomParHelper # import 2 | #NoNode: NoNode = next(d for d in me.docked if 'ExtUtils' in d.tags).mod('NoNode').NoNode # import 3 | ### 4 | 5 | class ExtOpColorChange: 6 | def __init__(self, ownerComp): 7 | self.ownerComp : COMP = ownerComp 8 | CustomParHelper.Init(self, ownerComp, enable_properties=True, enable_callbacks=True) 9 | #NoNode.Init(ownerComp, enable_chopexec=True, enable_datexec=True, enable_parexec=True, enable_keyboard_shortcuts=True) 10 | 11 | self.activeColorTable : tableDAT= self.ownerComp.op('null_active') 12 | self.resetToDefaults() 13 | self.populateSequence() # with defaults 14 | 15 | if self.evalAutoload: 16 | self.loadSavedColors() 17 | 18 | @property 19 | def __available_families(self): 20 | return self.activeColorTable.col(0, val=True) 21 | 22 | @property 23 | def sequence(self) -> Sequence: 24 | return self.ownerComp.seq.Family 25 | 26 | def populateSequence(self): 27 | families = self.__available_families 28 | 29 | self.sequence.numBlocks = 1 30 | self.sequence.numBlocks = len(families) 31 | for block in self.sequence.blocks: 32 | fam = families[block.index] 33 | # for some reason setting the MenuSource with TableMenu kept evaluating correctly but throwing an error 34 | block.par.Type.menuNames = families 35 | block.par.Type.menuLabels = families 36 | 37 | block.par.Type = fam 38 | block.parGroup.Rgb.val = self.activeColorTable.row(fam)[1:] 39 | 40 | 41 | 42 | def setColor(self, _type: str, _color: list[float]): 43 | if _type in ui.colors: 44 | ui.colors[_type] = _color 45 | self.saveColors() 46 | 47 | def saveColors(self): 48 | _colors : dict[str, list[float]] = self.ownerComp.fetch('colors', {}) 49 | 50 | for _row in self.activeColorTable.rows(val=True): 51 | _colors[_row[0]] = _row[1:] 52 | self.ownerComp.store('colors', _colors) 53 | 54 | def loadSavedColors(self): 55 | colors : dict[str, list[float]] = self.ownerComp.fetch('colors', None) 56 | if colors: 57 | for _type, _color in colors.items(): 58 | if _type in ui.colors: 59 | ui.colors[_type] = _color 60 | else: 61 | # TODO: handle custom OP families 62 | pass 63 | self.populateSequence() 64 | # 65 | def resetToDefaults(self): 66 | ui.colors.resetToDefaults() 67 | 68 | def onParSet(self): 69 | for block in self.sequence.blocks: 70 | block : SequenceBlock = block 71 | self.setColor(block.par.Type.eval(), block.parGroup.Rgb.eval()) 72 | 73 | def onParLoad(self): 74 | self.loadSavedColors() 75 | 76 | def onParReset(self): 77 | self.resetToDefaults() 78 | self.populateSequence() # with defaults 79 | -------------------------------------------------------------------------------- /scripts/UPDATER/ExtUpdater.py: -------------------------------------------------------------------------------- 1 | import TDFunctions as TDF 2 | 3 | class ExtUpdater: 4 | def __init__(self, ownerComp): 5 | self.ownerComp = ownerComp 6 | self.update_button = op('/ui/dialogs/bookmark_bar/wiki/text') 7 | self.IsUpdatable = tdu.Dependency(False) 8 | self.newTag = None 9 | 10 | def Check(self, _): 11 | if self.ownerComp.par.Enabled.eval(): 12 | iop.TDAsyncIO.Run([self._doDaCheck()]) 13 | 14 | 15 | async def _doDaCheck(self): 16 | iop.GitHub.PollLatestTag() 17 | 18 | def OnPolledLatestTag(self, new_tag): 19 | self.newTag = new_tag 20 | _base = self.ownerComp.par.Target.eval() 21 | fetchedTag = _base.par.Gittag.eval() 22 | fetchedTag = fetchedTag.strip('v') 23 | new_tag = new_tag.strip('v') 24 | 25 | new_major = int(new_tag.split('.')[0]) 26 | base_major = int(fetchedTag.split('.')[0]) 27 | tag_flag = new_tag[-1] 28 | 29 | if new_major > base_major and not tag_flag != 'f': 30 | self.IsUpdatable.val = False 31 | else: 32 | self.IsUpdatable.val = (fetchedTag != new_tag) 33 | 34 | def PromptUpdate(self): 35 | ret = ui.messageBox('FNS_tools update available', 'Would you like to update FNS_tools to a newer version?',buttons=['No','Yes']) 36 | if ret: 37 | self.Update('dummy') 38 | else: 39 | self.update_button.parent().op('docsHelper').OpenDocs() 40 | 41 | def Update(self, _): 42 | op.FNS_CONFIG.SaveAllToJSON() 43 | iop.Downloader.par.Download.pulse() 44 | 45 | 46 | 47 | def OnFileDownloaded(self, callbackInfo): 48 | debug(callbackInfo) 49 | comp_path = callbackInfo['compPath'] 50 | newComp = op(comp_path) 51 | fp = tdu.FileInfo(str(callbackInfo['path'])) 52 | if newComp: 53 | # Store docked operators information before replacement 54 | oldComp = parent.FNS 55 | docked_ops = [] 56 | for docked_op in oldComp.docked: 57 | docked_info = { 58 | 'op': docked_op, 59 | 'pos': (docked_op.nodeX, docked_op.nodeY), 60 | } 61 | docked_ops.append(docked_info) 62 | # Undock the operator before replacement 63 | docked_op.dock = None 64 | 65 | newComp.par.externaltox.mode = ParMode.EXPRESSION 66 | newComp.par.externaltox.expr = f"f'{{app.userPaletteFolder}}/FNStools_ext/{fp.baseName}'" 67 | newComp.par.Gittag = self.newTag 68 | newComp.par.savebackup = True 69 | newComp.store('post_update', True) 70 | 71 | TDF.replaceOp(parent.FNS, newComp) 72 | newComp.destroy() 73 | 74 | # Restore docked operators 75 | newComp = parent.FNS 76 | for dock_info in docked_ops: 77 | docked_op = dock_info['op'] 78 | if docked_op: 79 | # Restore position first 80 | docked_op.nodeX, docked_op.nodeY = dock_info['pos'] 81 | # Then re-dock 82 | docked_op.dock = newComp 83 | #ret = ui.messageBox('FNS_tools updated', 'Would you like to see the changelog?', buttons=['No', 'Yes']) 84 | #if ret: 85 | #ui.viewFile('https://github.com/function-store/FunctionStore_tools/releases/latest') 86 | pass 87 | -------------------------------------------------------------------------------- /scripts/VSCodeTools/ClearScriptSyncFile/ExtClearScriptFile.py: -------------------------------------------------------------------------------- 1 | class ExtClearScriptFile: 2 | def __init__(self, ownerComp): 3 | self.ownerComp = ownerComp 4 | 5 | @property 6 | def tags(self): 7 | return self.ownerComp.par.Tags.eval().split(" ") 8 | 9 | 10 | def OnClearselected(self): 11 | _ops = ui.panes.current.owner.selectedChildren 12 | self.__clear(_ops) 13 | 14 | 15 | def OnClearcomp(self): 16 | _ops = ui.panes.current.owner.findChildren(type=DAT, tags=self.tags, allTags=True, maxDepth=1) 17 | self.__clear(_ops) 18 | pass 19 | 20 | 21 | def OnClearcompchildren(self): 22 | _ops = ui.panes.current.owner.findChildren(type=DAT, tags=self.tags, allTags=True) 23 | self.__clear(_ops) 24 | pass 25 | 26 | 27 | def OnClearall(self): 28 | _ops = root.findChildren(type=DAT, tags=self.tags, allTags=True) 29 | self.__clear(_ops) 30 | pass 31 | 32 | 33 | def __restoreTags(self, isUndo, _ops): 34 | if not isUndo: 35 | return 36 | for _op in _ops: 37 | if _op.isDAT and (_filepar := getattr(_op.par, 'file', None)): 38 | for _tag in self.tags: 39 | if _tag not in _op.tags: 40 | _op.tags.add(_tag) 41 | _op.color = (1, 0.5, 0.5) 42 | 43 | 44 | def __clear(self, _ops): 45 | ui.undo.startBlock('Clear Externalized Script Files') 46 | ui.undo.addCallback(self.__restoreTags, info = _ops) 47 | for _op in _ops: 48 | if _op and all(_tag in _op.tags for _tag in self.tags): 49 | if _op.isDAT and (_filepar := getattr(_op.par, 'file', None)): 50 | _filepar.val = "" 51 | _op.color = (0.55,0.55,0.55) 52 | for _tag in self.tags: 53 | if _tag in _op.tags: 54 | _op.tags.remove(_tag) 55 | 56 | ui.undo.endBlock() -------------------------------------------------------------------------------- /scripts/VSCodeTools/OpenVSCode/ExtOpenVSCode.py: -------------------------------------------------------------------------------- 1 | import subprocess 2 | import os 3 | import json 4 | from pathlib import Path 5 | 6 | class ExtOpenVSCode: 7 | def __init__(self, ownerComp): 8 | self.ownerComp = ownerComp 9 | self.logger = self.ownerComp.op('logger1') 10 | run( 11 | "args[0].postInit() if args[0] " 12 | "and hasattr(args[0], 'postInit') else None", 13 | self, 14 | endFrame=True, 15 | delayRef=op.TDResources 16 | ) 17 | 18 | 19 | @property 20 | def is_autodetect(self): 21 | return self.ownerComp.par.Autodetect.eval() 22 | 23 | @property 24 | def is_startup(self): 25 | return self.ownerComp.par.Startup.eval() 26 | 27 | @property 28 | def codeexe(self): 29 | return self.ownerComp.par.Codeexe.eval() 30 | 31 | @codeexe.setter 32 | def codeexe(self, value): 33 | self.ownerComp.par.Codeexe = value 34 | 35 | @property 36 | def workspace(self): 37 | return self.ownerComp.par.Workspace.eval() 38 | 39 | @property 40 | def projectname(self): 41 | return project.name.split('.')[0] 42 | 43 | @workspace.setter 44 | def workspace(self, value): 45 | self.ownerComp.par.Workspace = value 46 | 47 | 48 | def postInit(self): 49 | if self.is_autodetect: 50 | self.set_codeexe() 51 | self.set_workspace() 52 | 53 | if self.is_startup: 54 | self.OpenVSCode() 55 | 56 | 57 | def OnAutodetect(self, val): 58 | if val: 59 | self.postInit() 60 | 61 | def OnOpen(self): 62 | if self.is_autodetect: 63 | self.set_codeexe() 64 | self.set_workspace() 65 | self.OpenVSCode() 66 | 67 | def set_codeexe(self): 68 | code_exe_path = self.codeexe # Get the current value of Codeexe 69 | 70 | # Step 1 & 2: Check if Codeexe is Set and Verify Existence 71 | if code_exe_path and Path(code_exe_path).exists(): 72 | self.logger.Log(f"VS Code executable found at: {code_exe_path}") 73 | else: 74 | # Step 3: Fallback to Preferences 75 | fallback_path = ui.preferences['dats.texteditor'] 76 | if fallback_path and Path(fallback_path).exists(): 77 | self.logger.Log(f"Falling back to preference's text editor at: {fallback_path}") 78 | # Detect MacOS 79 | if fallback_path.endswith(".app"): 80 | if 'Cursor.app' in fallback_path: 81 | fallback_path = f"{fallback_path}/Contents/MacOS/Cursor" 82 | else: 83 | fallback_path = f"{fallback_path}/Contents/MacOS/Electron" 84 | self.codeexe = fallback_path # Update Codeexe to the fallback path 85 | else: 86 | # Step 4: Error Handling 87 | self.logger.Log("Error: No valid VS Code executable found. Please set the path manually.") 88 | 89 | 90 | def set_workspace(self): 91 | project_folder = Path(project.folder) # Convert project_folder to a Path object for easier manipulation 92 | 93 | # Step 2: Find or Create Workspace File 94 | workspace_files = list(project_folder.glob('*.code-workspace')) 95 | if workspace_files: 96 | workspace_file = workspace_files[0] 97 | else: 98 | workspace_file = project_folder / f"{self.projectname}.code-workspace" 99 | 100 | # Step 3: Convert to Relative Path 101 | # Ensure the workspace_file path is relative to project_folder 102 | relative_workspace_file = workspace_file.relative_to(project_folder) 103 | 104 | # Step 4: Handle Path Conversion 105 | # Update the workspace property to use the relative path 106 | self.workspace = str(relative_workspace_file) 107 | self.logger.Log(f"Using workspace file: {relative_workspace_file}") 108 | 109 | 110 | def OpenVSCode(self): 111 | # check if both exist 112 | missing_exe = not self.codeexe or not Path(self.codeexe).exists() 113 | 114 | # Ensure workspace file exists, create if necessary 115 | project_folder = Path(project.folder) 116 | workspace_path = project_folder / self.workspace 117 | 118 | # Get the interpreter path 119 | interpreter_path = self._get_interpreter_path() 120 | # Check if we're using a valid TD interpreter or falling back to local typings 121 | interpreter_parent = interpreter_path.parent 122 | version = self._parse_td_version(interpreter_parent.parent) 123 | is_valid_td = self._is_valid_td_version(version) 124 | 125 | # Load existing workspace config or create new one 126 | if workspace_path.exists(): 127 | with workspace_path.open('r') as file: 128 | workspace_config = json.load(file) 129 | else: 130 | workspace_config = {"folders": [{"path": "."}]} 131 | 132 | if is_valid_td: 133 | # Initialize settings if it doesn't exist 134 | if "settings" not in workspace_config: 135 | workspace_config["settings"] = {} 136 | 137 | # Update Python settings to force workspace interpreter 138 | python_settings = { 139 | "python.defaultInterpreterPath": str(interpreter_path), 140 | "python.terminal.activateEnvironment": True 141 | } 142 | 143 | # Update workspace settings 144 | workspace_config["settings"].update(python_settings) 145 | 146 | # Save the workspace config 147 | with workspace_path.open('w') as file: 148 | json.dump(workspace_config, file, indent=4) 149 | 150 | if not workspace_path.exists(): 151 | self.logger.Log(f"Created workspace file: {workspace_path}") 152 | else: 153 | self.logger.Log(f"Updated interpreter path in workspace file: {workspace_path}") 154 | 155 | 156 | # Only deploy stubs if we're not using a valid TD interpreter 157 | if not is_valid_td: 158 | if TDTypings := getattr(op, 'FNS_TDTYPINGS', None): 159 | self.logger.Log("Deploying stubs because no valid TD interpreter found") 160 | TDTypings.DeployStubs() 161 | 162 | missing_workspace = not self.workspace or not Path(self.workspace).exists() 163 | 164 | if missing_exe or missing_workspace: 165 | self._popErrorMessage(missing_exe, missing_workspace) 166 | return 167 | 168 | subprocess.Popen([self.codeexe, self.workspace]) 169 | pass 170 | 171 | def _popErrorMessage(self, missing_exe=False, missing_workspace=False): 172 | missing_components = [] 173 | if missing_exe: 174 | missing_components.append("VS Code executable") 175 | if missing_workspace: 176 | missing_components.append("workspace file") 177 | 178 | if missing_components: 179 | base_error_msg = "Error: No valid {} found. Please set the path manually and make sure it exists." 180 | formatted_components = " and ".join(missing_components) 181 | message = base_error_msg.format(formatted_components) 182 | 183 | if not self.is_autodetect: 184 | message += '\nOr try turning on Auto-detect!' 185 | 186 | ui.messageBox("Error", message, buttons=["OK"]) 187 | self.ownerComp.openParameters() 188 | pass 189 | 190 | def _parse_td_version(self, path: Path) -> tuple[int, int] | None: 191 | """Extract TouchDesigner version from path.""" 192 | td_pattern = re.compile(r'TouchDesigner\.(\d+)\.(\d+)') 193 | # Check folder name for version 194 | match = td_pattern.match(path.name) 195 | if match: 196 | return (int(match.group(1)), int(match.group(2))) 197 | return None 198 | 199 | def _is_valid_td_version(self, version: tuple[int, int] | None) -> bool: 200 | """Check if version meets minimum requirements (>= 2023.30000).""" 201 | if version is None: 202 | return False 203 | major, minor = version 204 | if major > 2023: 205 | return True 206 | elif major == 2023: 207 | return minor >= 30000 208 | return False 209 | 210 | def _find_td_interpreter(self) -> Path | None: 211 | """Search for valid python.exe in the highest version TD installation.""" 212 | # First check current app version 213 | current_td = Path(app.installFolder) 214 | version = self._parse_td_version(current_td) 215 | self.logger.Log(f"Current TD version: {version}") 216 | 217 | if app.osName != 'Windows': # this will be the norm for windows too in the future 218 | return Path(app.pythonExecutable) 219 | 220 | # Always check if current interpreter exists 221 | td_interpreter = current_td / 'bin' / 'python.exe' 222 | if td_interpreter.exists(): 223 | if self._is_valid_td_version(version): 224 | self.logger.Log(f"Using current TD interpreter in {version[0]}.{version[1]}") 225 | return td_interpreter 226 | else: 227 | self.logger.Log("Current TD version is not valid, but interpreter exists") 228 | # Store current interpreter as fallback 229 | current_interpreter = td_interpreter 230 | else: 231 | current_interpreter = None 232 | self.logger.Log("Current TD interpreter not found") 233 | 234 | # If current version is not valid, search for highest version 235 | td_installations = current_td.parent 236 | highest_match = None 237 | highest_version = (0, 0) 238 | 239 | for td_folder in td_installations.iterdir(): 240 | if not td_folder.is_dir(): 241 | continue 242 | 243 | version = self._parse_td_version(td_folder) 244 | if self._is_valid_td_version(version) and version > highest_version: 245 | td_interpreter = td_folder / 'bin' / 'python.exe' 246 | if td_interpreter.exists(): 247 | highest_version = version 248 | highest_match = td_interpreter 249 | self.logger.Log(f"Found interpreter in TD {version[0]}.{version[1]}") 250 | 251 | # Return highest valid version if found, otherwise return current interpreter 252 | return highest_match if highest_match else current_interpreter 253 | 254 | def _get_interpreter_path(self) -> Path: 255 | """Determine paths for builtins and stubs files.""" 256 | # Try to find builtins in highest version TD installation 257 | td_interpreter = self._find_td_interpreter() 258 | if td_interpreter: 259 | interpreter_file = td_interpreter 260 | else: 261 | self.logger.Log("No valid TD installation (>= 2023.30000) found with python.exe") 262 | # Fallback to local typings 263 | interpreter_file = Path("typings", "python.exe") 264 | 265 | return interpreter_file 266 | -------------------------------------------------------------------------------- /scripts/VSCodeTools/ScriptSyncFile/ExtScriptExternalize_.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | 3 | class ExtScriptExternalize: 4 | def __init__(self, ownerComp): 5 | self.ownerComp = ownerComp 6 | self.__script = None 7 | self.popDialog = self.ownerComp.op('popDialog') 8 | self.tags = ['FNS_externalized', 'pi_suspect'] 9 | 10 | ############################################# 11 | # Properties 12 | @property 13 | def ScriptOp(self) -> OP: 14 | return self.__script 15 | 16 | @ScriptOp.setter 17 | def ScriptOp(self, _op): 18 | if self.__check_eligibility(_op): 19 | self.__script = _op 20 | else: 21 | self.__script = None 22 | 23 | @property 24 | def Folder(self): 25 | return self.ownerComp.par.Folder.eval() 26 | 27 | ############################################# 28 | # Callbacks 29 | 30 | def OnExternalizeselected(self): 31 | self.ScriptOp = ui.panes.current.owner.currentChild 32 | self.Externalize() 33 | 34 | 35 | def Externalize(self, file_path=None, forceDialog=False, forceOverwrite=False): 36 | if self.ScriptOp is None: 37 | return 38 | 39 | if not file_path: 40 | file_path = self.__createFilePathFull() 41 | 42 | if not forceOverwrite: 43 | file_path = self._checkFilePath(file_path, forceDialog) 44 | if file_path is None: 45 | return 46 | 47 | self._setFilePath(file_path) 48 | 49 | 50 | def _checkFilePath(self, file_path, forceDialog=False): 51 | # Check if the file exists and open a dialog to choose a new name 52 | if file_path.exists() or forceDialog: 53 | self.popDialog.par.Text = f"{self.__ensureForwardSlashes(file_path)} already exists. Please choose a new name." if not forceDialog else f"Please choose a new name." 54 | self.popDialog.Open(textEntry=f"{self.__ensureForwardSlashes(file_path.with_suffix(''))}") 55 | return None 56 | return file_path 57 | 58 | 59 | def OnDialogFinish(self, info): 60 | if info['buttonNum'] == 1: 61 | file_path = Path(f"{info['enteredText']}.{self.__getFileExtensionForOp(self.ScriptOp)}") 62 | self.Externalize(file_path=file_path) 63 | elif info['buttonNum'] == 2: 64 | self.Externalize(file_path=self.__createFilePathFull(), forceOverwrite=True) 65 | 66 | 67 | def _setFilePath(self, file_path): 68 | self.ScriptOp.par['file'] = self.__ensureForwardSlashes(file_path) 69 | self.ScriptOp.par.syncfile = True 70 | self.ScriptOp.color = (1, 0.5, 0.5) 71 | for tag in self.tags: 72 | if tag not in self.ScriptOp.tags: 73 | self.ScriptOp.tags.add(tag) 74 | self.ScriptOp.par.edit.pulse() 75 | 76 | 77 | ############################################# 78 | # Helper Functions 79 | 80 | def __check_eligibility(self, _op): 81 | if isinstance(_op, OP) and _op.isDAT and hasattr(_op.par, 'file'): 82 | return True 83 | return False 84 | 85 | def __ensureForwardSlashes(self, path): 86 | return str(path).replace('\\', '/') 87 | 88 | def __getFileExtensionForOp(self, _op) -> str: 89 | # Check if the operation is a tableDAT, return 'tsv' 90 | if isinstance(_op, tableDAT): 91 | return 'tsv' 92 | 93 | # Attempt to get the docked operation 94 | if _op_dockedto := _op.dock: 95 | # Check if the docked operation type includes 'glsl' 96 | if 'glsl' in _op_dockedto.OPType: 97 | # Determine the file extension based on the operation name 98 | if '_vertex' in _op.name: 99 | return 'vert' 100 | elif '_pixel' in _op.name: 101 | return 'frag' 102 | 103 | # Default return 'py' if none of the above conditions are met 104 | return 'py' 105 | 106 | def __createFilePathFull(self, _op=None): 107 | if _op is None: 108 | _op = self.ScriptOp 109 | name = _op.name 110 | # Construct the initial file path using pathlib 111 | extension = self.__getFileExtensionForOp(self.ScriptOp) 112 | 113 | if not name.endswith(f'.{extension}'): 114 | name = f"{name}.{extension}" 115 | 116 | if 'TDExtension' not in _op.tags: 117 | file_path = Path(self.Folder) / name 118 | else: 119 | file_path = Path(self.Folder) / _op.parent().name / name 120 | return file_path -------------------------------------------------------------------------------- /scripts/VSCodeTools/TDTypings/ExtTDTypings.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | 3 | class ExtTDTypings: 4 | def __init__(self, ownerComp): 5 | self.ownerComp = ownerComp 6 | 7 | @property 8 | def repo(self): 9 | return self.ownerComp.op('repo') 10 | 11 | @property 12 | def path(self): 13 | return Path(self.ownerComp.par.Path.eval()) 14 | 15 | 16 | def __fixFileName(self, name): 17 | # extract filename.ext from name_ext, keeping in mind that name can contain underscores 18 | 19 | # find the last underscore 20 | underscoreIndex = name.rfind('_') 21 | if underscoreIndex == -1: 22 | return name 23 | else: 24 | return name[:underscoreIndex] + '.' + name[underscoreIndex+1:] 25 | 26 | 27 | def OnInstall(self): 28 | self.DeployStubs(force=False) 29 | 30 | def OnForce(self): 31 | self.DeployStubs(force=True) 32 | 33 | 34 | def DeployStubs(self, force=False): 35 | #check if the path exists if not create it 36 | if not self.path.exists(): 37 | self.path.mkdir(parents=True) 38 | for child in self.repo.findChildren(type=DAT): 39 | fileName = self.__fixFileName(child.name) 40 | fullPath = self.path / fileName 41 | # if the file already exists in self.path, skip unless force is True 42 | if (fullPath).exists() and not force: 43 | continue 44 | # write the file to disk, overwrite if exists 45 | with open(fullPath, 'w') as f: 46 | f.write(child.text) 47 | ui.status = 'Deployed stubs to ' + str(fullPath) + ' successfully.' 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /scripts/VSCodeTools/stubser_wrapper/ExtStubserWrapper.py: -------------------------------------------------------------------------------- 1 | class ExtStubserWrapper: 2 | def __init__(self, ownerComp): 3 | self.ownerComp = ownerComp 4 | self.stubser : extStubser = self.ownerComp.op('stubser') 5 | 6 | def OnDeploystubs(self): 7 | includePrivate = self.ownerComp.par.Private.eval() 8 | includeUnpromoted = self.ownerComp.par.Unpromoted.eval() 9 | tags = self.ownerComp.par.Tags.eval() 10 | tags = tags.split(' ') if tags else [] 11 | for _op in ui.panes.current.owner.selectedChildren: 12 | if _op.family == 'COMP': 13 | ui.status = f'Stubifying COMP {_op.name}' 14 | # we need to iterate cause we can have multiple tags, but tag parameter only accepts one 15 | for tag in tags: 16 | self.stubser.StubifyComp(_op, tag=tag, includePrivate=includePrivate, includeUnpromoted=includeUnpromoted) 17 | elif _op.family == 'DAT': 18 | ui.status = f'Stubifying DAT {_op.name}' 19 | self.stubser.StubifyDat(_op) -------------------------------------------------------------------------------- /scripts/iopPromoter/ExtIopPromoter.py: -------------------------------------------------------------------------------- 1 | """ 2 | Extension classes enhance TouchDesigner components with python. An 3 | extension is accessed via ext.ExtensionClassName from any operator 4 | within the extended component. If the extension is promoted via its 5 | Promote Extension parameter, all its attributes with capitalized names 6 | can be accessed externally, e.g. op('yourComp').PromotedFunction(). 7 | 8 | Help: search "Extensions" in wiki 9 | """ 10 | 11 | from TDStoreTools import StorageManager 12 | import TDFunctions as TDF 13 | 14 | class ExtIopPromoter: 15 | """ 16 | ExtIopPromoter description 17 | """ 18 | def __init__(self, ownerComp): 19 | # The component to which this extension is attached 20 | self.ownerComp = ownerComp 21 | self.dialog = self.ownerComp.op('popDialog') 22 | self.target = None 23 | self._op = None 24 | 25 | def PromoteIop(self, _op, target): 26 | self.target = target 27 | self._op = _op 28 | self.dialog.Open(textEntry=_op.name) 29 | 30 | def OnSelect(self, info): 31 | sel = True if info['buttonNum'] == 1 else False 32 | 33 | if sel and self.target and self._op: 34 | seq = self.target.par.iop.sequence 35 | seqBlock = None 36 | 37 | for _seqBlock in seq: 38 | if (not _seqBlock.par.op.eval()) and (not _seqBlock.par.shortcut.eval()): 39 | seqBlock = _seqBlock 40 | break 41 | if not seqBlock: 42 | seq.numBlocks += 1 43 | seqBlock = seq[-1] 44 | 45 | seqBlock.par.shortcut = info['enteredText'] 46 | seqBlock.par.op = self.target.relativePath(self._op) 47 | 48 | def OnOpen(self, info): 49 | #debug(info) 50 | pass 51 | 52 | def OnClose(self, info): 53 | #debug(info) 54 | pass 55 | -------------------------------------------------------------------------------- /scripts/midiMapperMulti/MidiMapperMultiExt.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | CustomParHelper: CustomParHelper = next(d for d in me.docked if 'ExtUtils' in d.tags).mod('CustomParHelper').CustomParHelper # import 4 | ### 5 | 6 | class MidiMapperMultiExt: 7 | def __init__(self, ownerComp): 8 | CustomParHelper.Init(self, ownerComp, enable_properties=True, enable_callbacks=True) 9 | self.ownerComp = ownerComp 10 | self.folderTabs = self.ownerComp.op('masterFolderTabs1') 11 | self.masterMidiMapper = self.ownerComp.op('midiMapper1') 12 | self._prev_mapper_learn_states = {} 13 | self._addingParameter = False 14 | self.parComp = self.ownerComp.op('parameter1') 15 | self.parWindow = self.ownerComp.op('window1') 16 | 17 | @property 18 | def midiMappers(self): 19 | return self.ownerComp.ops('midiMapper*') 20 | 21 | def getMappingTable(self, _mapper): 22 | return _mapper.op("repo_maker").Repo 23 | 24 | def OnDelete(self, idx): 25 | namesList = self.folderTabs.par.Menunames.eval().split(' ') 26 | if len(namesList) == 1: 27 | return 28 | self.folderTabs.par.Menunames = self.folderTabs.par.Menunames.eval().replace(f' {namesList[idx]}','') 29 | if len(self.midiMappers) == 1: 30 | return 31 | self.midiMappers[idx].destroy() 32 | pass 33 | 34 | def OnAdd(self): 35 | new_mapper = self.ownerComp.copy(self.midiMappers[-1]) 36 | new_mapper.nodeY = self.midiMappers[-1].nodeY - 200 37 | new_mapper.par.Clear.pulse() 38 | new_mapper.par.Id.val += 1 39 | 40 | self.folderTabs.par.Menunames += f' {new_mapper.name}' 41 | self.folderTabs.par.Value0 = new_mapper.name 42 | 43 | def LearnDone(self, idx): 44 | if not self._addingParameter: 45 | return 46 | idx -= 1 47 | for _idx, _mapper in enumerate(self.midiMappers): 48 | _mapper.par.Learn.val = self._prev_mapper_learn_states[_mapper.name] 49 | 50 | if _idx == idx: 51 | self.folderTabs.par.Value0 = _mapper.name 52 | continue 53 | 54 | 55 | mappingTable = self.getMappingTable(_mapper) 56 | idx_to_delete = [] 57 | for _row in mappingTable.rows()[1:]: 58 | if _row[0] and _row[0].val == "Learn": 59 | idx_to_delete.append(_row[0].row) 60 | mappingTable.deleteRows(idx_to_delete) 61 | self._addingParameter = False 62 | 63 | 64 | def AddParameter(self, _par): 65 | self._addingParameter = True 66 | for _mapper in self.midiMappers: 67 | _mapper.AddParameter(_par) 68 | self._prev_mapper_learn_states[_mapper.name] = _mapper.par.Learn.eval() 69 | _mapper.par.Learn.val = True 70 | 71 | def Resetall(self): 72 | for _mapper in self.midiMappers: 73 | _mapper.par.Resetall.pulse() 74 | 75 | def OpenParWindow(self, _comp): 76 | debug(f'opening {_comp.path}') 77 | self.parComp.par.op = _comp.path 78 | debug(self.parWindow) 79 | self.parWindow.par.winopen.pulse() -------------------------------------------------------------------------------- /typings/custom_typings/QuickExt/CustomParHelper.pyi: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | class CustomParHelper: 4 | """ 5 | Author: Dan Molnar aka Function Store (@function.str dan@functionstore.xyz) 2024 6 | 7 | CustomParHelper is a helper class that provides easy access to custom parameters 8 | of a COMP and simplifies the implementation of custom parameter callbacks in TouchDesigner extensions. 9 | 10 | ## Features: 11 | - Access custom parameters as properties 12 | - Set parameter values through properties 13 | - Simplified custom parameter callbacks 14 | - Support for sequence parameters 15 | - Support for parameter groups (parGroups) 16 | - Support for general callbacks that catch all parameter changes 17 | - Configurable inclusion for properties and callbacks (by default all parameters are included) 18 | - Configurable exceptions for pages, properties, callbacks, and sequences 19 | 20 | ## Usage in your extension class: 21 | 1. Import the CustomParHelper class: 22 | ```python 23 | CustomParHelper: CustomParHelper = next(d for d in me.docked if 'ExtUtils' in d.tags).mod('CustomParHelper').CustomParHelper # import 24 | ``` 25 | 26 | 2. Initialize in your extension's __init__ method as follows: 27 | ```python 28 | CustomParHelper.Init(self, ownerComp) 29 | ``` 30 | 31 | Full signature and optional parameters: 32 | ```python 33 | CustomParHelper.Init(self, ownerComp, enable_properties: bool = True, enable_callbacks: bool = True, enable_parGroups: bool = True, enable_seq: bool = True, expose_public: bool = False, 34 | par_properties: list[str] = ['*'], par_callbacks: list[str] = ['*'], 35 | except_properties: list[str] = [], except_sequences: list[str] = [], except_callbacks: list[str] = [], except_pages: list[str] = [], 36 | enable_stubs: bool = False, general_callback_enable: bool = True) 37 | ``` 38 | 39 | Additional options: 40 | - `enable_properties`: If True, creates properties for custom parameters (default: True) 41 | - `enable_callbacks`: If True, creates callbacks for custom parameters (default: True) 42 | - `enable_parGroups`: If True, creates properties and methods for parGroups (default: True) 43 | - `enable_seq`: If True, creates properties and methods for sequence parameters (default: True) 44 | - `expose_public`: If True, uses capitalized property and method names (e.g., Par, Eval instead of par, eval) 45 | - `par_properties`: List of parameter names to include in property creation, by default all parameters are included 46 | - `par_callbacks`: List of parameter names to include in callback handling, by default all parameters are included 47 | - `except_properties`: List of parameter names to exclude from property creation 48 | - `except_callbacks`: List of parameter names to exclude from callback handling 49 | - `except_pages`: List of parameter pages to exclude from property and callback handling 50 | - `except_sequences`: List of sequence names to exclude from property and callback handling 51 | - `enable_stubs`: If True, automatically creates and updates stubs for the extension (default: False) (thanks to AlphaMoonbase.berlin for Stubser) 52 | - `general_callback_enable`: If True, enables general callbacks that catch all parameter changes (default: True) 53 | 54 | 3. Access and set custom parameters as properties (if enable_properties=True (default)): 55 | 56 | There are two ways to access and set parameter values: 57 | 58 | a) Using Eval properties (recommended for simple value setting): 59 | - `self.eval`: Get/set the evaluated value of the parameter 60 | ```python 61 | # Get value 62 | value = self.evalMyParam 63 | # Set value (always sets .val regardless of parameter mode) 64 | self.evalMyParam = 5 65 | ``` 66 | - `self.evalGroup`: Get/set the evaluated value of the parameter group 67 | ```python 68 | # Get values 69 | values = self.evalGroupXyz 70 | # Set values (always sets .val for each parameter) 71 | self.evalGroupXyz = [1, 2, 3] 72 | ``` 73 | 74 | b) Using Par properties (for advanced parameter control): 75 | - `self.par`: Access/set the parameter object 76 | ```python 77 | # Get parameter object for advanced operations 78 | self.parMyParam.expr = "op('something').par.value" 79 | self.parMyParam.bindExpr = "op('other').par.value" 80 | # Set value (only works in CONSTANT or BIND modes) 81 | self.parMyParam = 5 # Ignored if parameter is in EXPRESSION mode 82 | ``` 83 | - `self.parGroup`: Access/set the parameter group object 84 | ```python 85 | # Get parameter group for advanced operations 86 | myGroup = self.parGroupXyz 87 | # Set values (only works for parameters in CONSTANT or BIND modes) 88 | self.parGroupXyz = [1, 2, 3] # Only affects non-expression parameters 89 | ``` 90 | 91 | > NOTE: to expose public properties, eg. self.Par instead of self.par, set expose_public=True in the Init function 92 | 93 | 4. Implement callbacks (if enable_callbacks=True (default)): 94 | a) Parameter-specific callbacks: 95 | - For regular parameters: 96 | ```python 97 | def onPar(self, _par, _val, _prev): 98 | # _par and _prev can be omitted if not needed 99 | ``` 100 | 101 | - For pulse parameters: 102 | ```python 103 | def onPar(self, _par): 104 | # _par can be omitted if not needed 105 | ``` 106 | 107 | - For sequence blocks: 108 | ```python 109 | def onSeqN(self, idx): 110 | ``` 111 | 112 | - For sequence parameters: 113 | ```python 114 | def onSeqN(self, _par, idx, _val, _prev): 115 | # _par and _prev can be omitted if not needed 116 | ``` 117 | 118 | - For parameter groups if enable_parGroups=True (default): 119 | ```python 120 | def onParGroup(self, _parGroup, _val): 121 | # _parGroup can be omitted if not needed 122 | ``` 123 | 124 | b) General callbacks (if general_callback_enable=True (default)): 125 | These catch all parameter changes that aren't handled by specific callbacks: 126 | 127 | - For value changes: 128 | ```python 129 | def onValueChange(self, _par, _val, _prev): 130 | # Called when any parameter value changes that doesn't have a specific callback 131 | # _val and _prev can be omitted if not needed 132 | ``` 133 | 134 | - For pulse parameters: 135 | ```python 136 | def onPulse(self, _par): 137 | # Called when any pulse parameter is triggered that doesn't have a specific callback 138 | # _par can be omitted if not needed 139 | ``` 140 | 141 | > NOTE: This class is part of the extUtils package, and is designed to work with the QuickExt framework. 142 | > NOTE: The reason this is implemented with static methods, is to omit the need to instantiate the class, providing a simpler interface (arguably). 143 | """ 144 | EXT_SELF = None 145 | EXT_OWNERCOMP = None 146 | PAR_EXEC = op('extParExec') 147 | DAT_EXEC = op('extParPropDatExec') 148 | PAR_GROUP_EXEC = op('extParGroupExec') 149 | SEQ_EXEC = op('extSeqParExec') 150 | STUBSER = op('extStubser') 151 | EXCEPT_PAGES_STATIC: list[str] = ['Version Ctrl', 'About', 'Info'] 152 | EXCEPT_PAGES: list[str] = EXCEPT_PAGES_STATIC 153 | EXCEPT_PROPS: list[str] = [] 154 | EXCEPT_CALLBACKS: list[str] = [] 155 | EXCEPT_SEQUENCES: list[str] = [] 156 | PAR_PROPS: list[str] = ['*'] 157 | PAR_CALLBACKS: list[str] = ['*'] 158 | SEQUENCE_PATTERN: str = '(\\w+?)(\\d+)(.+)' 159 | IS_EXPOSE_PUBLIC: bool = False 160 | STUBS_ENABLED: bool = False 161 | GENERAL_CALLBACK_ENABLE: bool = True 162 | 163 | @classmethod 164 | def Init(cls, extension_self, ownerComp: COMP, enable_properties: bool=True, enable_callbacks: bool=True, enable_parGroups: bool=True, enable_seq: bool=True, expose_public: bool=False, par_properties: list[str]=['*'], par_callbacks: list[str]=['*'], except_properties: list[str]=[], except_sequences: list[str]=[], except_callbacks: list[str]=[], except_pages: list[str]=[], enable_stubs: bool=False, general_callback_enable: bool=True) -> None: 165 | """Initialize the CustomParHelper.""" 166 | pass 167 | 168 | @classmethod 169 | def CustomParsAsProperties(cls, extension_self, ownerComp: COMP, enable_parGroups: bool=True) -> None: 170 | """Create properties for custom parameters.""" 171 | pass 172 | 173 | @classmethod 174 | def UpdateCustomParsAsProperties(cls) -> None: 175 | """Update the properties for custom parameters.""" 176 | pass 177 | 178 | @classmethod 179 | def EnableCallbacks(cls, enable_parGroups: bool=True, enable_seq: bool=True) -> None: 180 | """Enable callbacks for custom parameters.""" 181 | pass 182 | 183 | @classmethod 184 | def DisableCallbacks(cls, disable_parGroups: bool=True, disable_seq: bool=True) -> None: 185 | """Disable callbacks for custom parameters.""" 186 | pass 187 | 188 | @classmethod 189 | def OnValueChange(cls, comp: COMP, _par: Par, prev: Par) -> None: 190 | """Handle value change events for custom parameters.""" 191 | pass 192 | 193 | @classmethod 194 | def OnPulse(cls, comp: COMP, _par: Par) -> None: 195 | """Handle pulse events for custom parameters.""" 196 | pass 197 | 198 | @classmethod 199 | def OnValuesChanged(cls, changes: list[tuple[Par, Par]]) -> None: 200 | """Handle value change events for ParGroups.""" 201 | pass 202 | 203 | @classmethod 204 | def OnSeqValuesChanged(cls, changes: list[tuple[Par, Par]]) -> None: 205 | """Handle value change events for Sequence blocks.""" 206 | pass 207 | 208 | @classmethod 209 | def EnableStubs(cls) -> None: 210 | """Enable stubs for the extension.""" 211 | pass 212 | 213 | @classmethod 214 | def DisableStubs(cls) -> None: 215 | """Disable stubs for the extension.""" 216 | pass 217 | 218 | @classmethod 219 | def UpdateStubs(cls) -> None: 220 | """Update the stubs for the extension.""" 221 | pass -------------------------------------------------------------------------------- /typings/parameterTypes.py: -------------------------------------------------------------------------------- 1 | class DefaultPar(Par): 2 | """DefaultPar""" 3 | val : any 4 | """Read or set the value.""" 5 | pass 6 | 7 | 8 | class FloatPar(Par): 9 | """FloatPar""" 10 | val : float 11 | """Read or set the value.""" 12 | pass 13 | 14 | 15 | class OPPar(Par): 16 | """OPPar""" 17 | val : OP 18 | """Read or set the value.""" 19 | pass 20 | 21 | 22 | class CHOPPar(Par): 23 | """CHOPPar""" 24 | val : CHOP 25 | """Read or set the value.""" 26 | pass 27 | 28 | 29 | class COMPPar(Par): 30 | """COMPPar""" 31 | val : COMP 32 | """Read or set the value.""" 33 | pass 34 | 35 | 36 | class TOPPar(Par): 37 | """TOPPar""" 38 | val : TOP 39 | """Read or set the value.""" 40 | pass 41 | 42 | 43 | class MATPar(Par): 44 | """MATPar""" 45 | val : MAT 46 | """Read or set the value.""" 47 | pass 48 | 49 | 50 | class IntPar(Par): 51 | """IntPar""" 52 | val : int 53 | """Read or set the value.""" 54 | pass 55 | 56 | 57 | class StrPar(Par): 58 | """StrPar""" 59 | val : str 60 | """Read or set the value.""" 61 | pass 62 | 63 | 64 | class XYZPar(Par): 65 | """XYZPar""" 66 | val : float 67 | """Read or set the value.""" 68 | pass 69 | 70 | 71 | class RGBPar(Par): 72 | """RGBPar""" 73 | val : float 74 | """Read or set the value.""" 75 | pass 76 | 77 | 78 | class RGBAPar(Par): 79 | """RGBAPar""" 80 | val : float 81 | """Read or set the value.""" 82 | pass 83 | 84 | 85 | class MenuPar(Par): 86 | """MenuPar""" 87 | val : str 88 | """Read or set the value.""" 89 | pass 90 | 91 | 92 | class StrMenuPar(Par): 93 | """StrMenuPar""" 94 | val : str 95 | """Read or set the value.""" 96 | pass 97 | 98 | 99 | class XYPar(Par): 100 | """XYPar""" 101 | val : float 102 | """Read or set the value.""" 103 | pass 104 | 105 | 106 | class WHPar(Par): 107 | """WHPar""" 108 | val : float 109 | """Read or set the value.""" 110 | pass 111 | 112 | 113 | class TogglePar(Par): 114 | """TogglePar""" 115 | val : bool 116 | """Read or set the value.""" 117 | pass 118 | 119 | 120 | class FilePar(Par): 121 | """FilePar""" 122 | val : str 123 | """Read or set the value.""" 124 | pass 125 | 126 | 127 | class FolderPar(Par): 128 | """FolderPar""" 129 | val : str 130 | """Read or set the value.""" 131 | pass 132 | 133 | 134 | --------------------------------------------------------------------------------