├── .gitignore ├── LICENSE ├── MagicPlugins ├── init.py ├── install_plugin_dialog.py ├── magic_plugins.py ├── menu.py ├── plugins │ ├── Internet.png │ └── Internet │ │ ├── 3D.png │ │ ├── 3D │ │ └── placeholder │ │ ├── Blink.png │ │ ├── Blink │ │ └── placeholder │ │ ├── Channel.png │ │ ├── Channel │ │ └── placeholder │ │ ├── Color.png │ │ ├── Color │ │ └── placeholder │ │ ├── Deep.png │ │ ├── Deep │ │ └── placeholder │ │ ├── Draw.png │ │ ├── Draw │ │ └── placeholder │ │ ├── Filter.png │ │ ├── Filter │ │ └── placeholder │ │ ├── Keyer.png │ │ ├── Keyer │ │ └── placeholder │ │ ├── Merge.png │ │ ├── Merge │ │ └── placeholder │ │ ├── Other.png │ │ ├── Other │ │ └── placeholder │ │ ├── Transform.png │ │ └── Transform │ │ └── placeholder └── resources │ ├── MagicPlugins.png │ ├── header.png │ ├── icon.png │ ├── install_plugin.png │ ├── installing_plugin_library.png │ ├── installing_plugin_menu.png │ ├── installing_plugin_ui.png │ └── open_folder.png ├── README.md └── init.py /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .pyc 3 | __pycache__ 4 | .vscode 5 | tab_stats.dat 6 | .MagicPlugins/plugin 7 | ./test 8 | MagicPlugins/plugins/ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Gilles Vink 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 | -------------------------------------------------------------------------------- /MagicPlugins/init.py: -------------------------------------------------------------------------------- 1 | """ 2 | MagicPlugins by Gilles Vink 3 | 4 | Init.py script to automatically load Nuke plugins 5 | 6 | """ 7 | 8 | import magic_plugins 9 | 10 | magic_plugins = magic_plugins.MagicPlugins() 11 | magic_plugins.load_plugins() 12 | -------------------------------------------------------------------------------- /MagicPlugins/install_plugin_dialog.py: -------------------------------------------------------------------------------- 1 | import nuke 2 | import nukescripts 3 | import os 4 | 5 | 6 | class InstallPluginDialog(nukescripts.PythonPanel): 7 | def __init__(self): 8 | """Allows user to select plugin to install, we will ask the user 9 | a few things. 10 | 11 | We need the path to the plugin the user wants to install. 12 | We may want the icon path to the plugin. 13 | Then we want to specify a category for the plugin. 14 | 15 | And last but not least, if it is a library plugin, we need to 16 | correct Nuke version for the plugin. This option is only available 17 | if the plugin is a .dll, .so or a .dylib file.""" 18 | nukescripts.PythonPanel.__init__(self, "MagicPlugins - Install plugin") 19 | 20 | plugin_categories = [ 21 | "3D", 22 | "Blink", 23 | "Channel", 24 | "Color", 25 | "Deep", 26 | "Draw", 27 | "Filter", 28 | "Keyer", 29 | "Merge", 30 | "Other", 31 | "Transform", 32 | ] 33 | 34 | # Getting the current Nuke version 35 | current_nuke_version = str( 36 | ("%i.%i" % (nuke.NUKE_VERSION_MAJOR, nuke.NUKE_VERSION_MINOR)) 37 | ) 38 | 39 | # List containing all Nuke versions to date 40 | nuke_versions = [ 41 | "13.2", 42 | "13.1", 43 | "13.0", 44 | "12.2", 45 | "12.1", 46 | "12.0", 47 | "11.3", 48 | "11.2", 49 | "11.1", 50 | "11.0", 51 | ] 52 | 53 | # Add Nuke version if it is not present in the list already 54 | # (new releases while code is not updated) 55 | if current_nuke_version not in nuke_versions: 56 | nuke_versions.append(current_nuke_version) 57 | 58 | # Defining extensions we would call basic 59 | self.basic_extensions = ( 60 | ".nk", 61 | ".gizmo", 62 | ) 63 | 64 | # These are the extensions that are Nuke specific plugins 65 | self.library_extensions = ( 66 | ".dll", 67 | ".so", 68 | ".dylib", 69 | ) 70 | 71 | # Getting the script location to locate the header image 72 | script_location = os.path.dirname(os.path.realpath(__file__)) 73 | header_image = os.path.join(script_location, "resources", "header.png") 74 | header_image = header_image.replace(os.sep, "/") 75 | 76 | # Here we will create all the knobs 77 | 78 | self.header = nuke.Text_Knob( 79 | "header", 80 | "", 81 | "" % header_image, 82 | ) 83 | self.help_text = nuke.Text_Knob( 84 | "helpText", 85 | "", 86 | "Select the file you want to install \n (.gizmo, .nk, .dll, .so " 87 | "and .dylib files are supported). \n You can optionally select an " 88 | "icon for the file to use.", 89 | ) 90 | 91 | self.divider_1 = nuke.Text_Knob("divider1", "", "") 92 | 93 | self.plugin_file_path = nuke.File_Knob( 94 | "pluginPath", "Plugin file path" 95 | ) 96 | 97 | self.divider_2 = nuke.Text_Knob("divider2", "", "") 98 | 99 | self.icon_file_path = nuke.File_Knob( 100 | "iconPath", "Icon file path (optional)" 101 | ) 102 | 103 | self.divider_3 = nuke.Text_Knob("divider3", "", "") 104 | 105 | self.plugin_category = nuke.Enumeration_Knob( 106 | "pluginCategory", "Plugin category", plugin_categories 107 | ) 108 | 109 | self.divider_4 = nuke.Text_Knob("divider4", "", "") 110 | 111 | self.library_nuke_version = nuke.Enumeration_Knob( 112 | "nukeVersion", "Nuke version", nuke_versions 113 | ) 114 | self.library_nuke_version.setVisible(False) 115 | 116 | self.divider_5 = nuke.Text_Knob("divider5", "", "") 117 | self.divider_5.setVisible(False) 118 | 119 | # Now we will add the knobs to the dialog window 120 | for knob in ( 121 | self.header, 122 | self.help_text, 123 | self.divider_1, 124 | self.plugin_file_path, 125 | self.divider_2, 126 | self.icon_file_path, 127 | self.divider_3, 128 | self.plugin_category, 129 | self.divider_4, 130 | self.library_nuke_version, 131 | self.divider_5, 132 | ): 133 | self.addKnob(knob) 134 | 135 | def knobChanged(self, knob): 136 | # If the knob is the plugin path, check and validate it 137 | if knob == self.plugin_file_path: 138 | file_path = knob.value() 139 | 140 | # Skip validation if the file path is empty 141 | if file_path is not "": 142 | 143 | # We will call the validation function to check the path 144 | validated = self.__validate_plugin_path(file_path) 145 | if validated is not True: 146 | 147 | # Let the user know if it is not validated 148 | nuke.message(validated) 149 | 150 | # Set the knob to empty 151 | knob.setValue("") 152 | 153 | # If the knob is the icon path, check and validate it 154 | elif knob == self.icon_file_path: 155 | file_path = knob.value() 156 | 157 | # Skip validation if the file path is empty 158 | if file_path is not "": 159 | 160 | # We will call the validation function to check the path 161 | validated = self.__validate_icon_path(file_path) 162 | if validated is not True: 163 | 164 | # Let the user know if it is not validated 165 | nuke.message(validated) 166 | 167 | # Set the knob to empty 168 | knob.setValue("") 169 | 170 | def __validate_plugin_path(self, file_path): 171 | """This function will validate the plugin, if the plugin is validated 172 | it will return True, else it will return a reason why 173 | it is not validated. 174 | 175 | Besides that, it will set the visibility for the version knobs. Because 176 | for the library plugins we will need to specify the Nuke version.""" 177 | 178 | # First of all let's check if the file really exists on disk 179 | if os.path.isfile(file_path): 180 | 181 | # Let's assume we won't validate the file, and that we 182 | # don't want to see the Nuke version knob 183 | validated = False 184 | visibility = False 185 | 186 | # If the file ends with a basic extension (.nk and .gizmo) we 187 | # will accept it and set the nuke version visibility to false 188 | if file_path.endswith(self.basic_extensions): 189 | validated = True 190 | visibility = False 191 | 192 | # If the file ends with a library extension (.dll, .so and .dylib) 193 | # we will accept it and set the nuke version visibility to true 194 | elif file_path.endswith(self.library_extensions): 195 | validated = True 196 | visibility = True 197 | 198 | # If we can't validate it, the extension is not accepted 199 | else: 200 | validated = "Unknown extension" 201 | 202 | # Set the visibility for the Nuke version knob 203 | for knob in ( 204 | self.library_nuke_version, 205 | self.divider_5, 206 | ): 207 | knob.setVisible(visibility) 208 | 209 | return validated 210 | 211 | # If the file is not found, let the user know 212 | else: 213 | return "File %s not found" % file_path 214 | 215 | def __validate_icon_path(self, file_path): 216 | """Function to validate the icon provided, if 217 | the file is validated we will return a True value. Otherwise 218 | a message containing the error will be provided.""" 219 | 220 | # First of all let's check if the file exists 221 | if os.path.isfile(file_path): 222 | 223 | # Now we will check if the file is a png file 224 | if file_path.endswith(".png"): 225 | return True 226 | 227 | # If the file is something different than a png, we will 228 | # let the user know another file is needed 229 | else: 230 | return "File %s is not a png" % file_path 231 | 232 | # If the file is not found, we will let the user know the 233 | # file does not exist on disk 234 | else: 235 | return "File %s is not found" % file_path 236 | -------------------------------------------------------------------------------- /MagicPlugins/magic_plugins.py: -------------------------------------------------------------------------------- 1 | """ 2 | MagicPlugins by Gilles Vink (2022) 3 | 4 | Script to automatically load gizmo, nk and library files 5 | and create menus 6 | 7 | """ 8 | 9 | import os 10 | import sys 11 | import nuke 12 | 13 | 14 | # Only load if we have a GUI 15 | if nuke.GUI: 16 | import install_plugin_dialog 17 | from shutil import copy2 18 | 19 | 20 | class MagicPlugins(object): 21 | """Main class containing all functions to 22 | populate our menu and load plugins""" 23 | 24 | def __init__(self): 25 | """In the init function we will create the variables 26 | we need in the entire script to load the plugins. 27 | 28 | Like the directory to scan for plugins, the current Nuke version, 29 | and the list of plugins we want to load. 30 | 31 | In the menu_name variable we can assign the name for the menu.""" 32 | 33 | # Startup message 34 | magic_plugins_version = 1.2 35 | self.__print("Version %s" % str(magic_plugins_version)) 36 | 37 | # Getting install location to load plugins 38 | self.script_directory = os.path.dirname(os.path.realpath(__file__)) 39 | plugins_directory = os.path.join(self.script_directory, "plugins") 40 | 41 | # Fix for Windows based systems 42 | self.plugins_directory = plugins_directory.replace(os.sep, "/") 43 | 44 | # Getting the current Nuke version required 45 | # to define which library file to use 46 | self.nuke_version = str( 47 | ("%i.%i" % (nuke.NUKE_VERSION_MAJOR, nuke.NUKE_VERSION_MINOR)) 48 | ) 49 | 50 | # Getting operating system to determine library extension 51 | # and to call the folder open function 52 | self.operating_system = sys.platform 53 | # Always collect all the plugins when this script is initialized 54 | self.plugins = self.__locate_plugins(self.plugins_directory) 55 | 56 | # This is the name we use for our menu in Nuke 57 | self.menu_name = "MagicPlugins" 58 | 59 | # These are the plugins we would call library 60 | self.library_extensions = (".dll", ".so", ".dylib") 61 | 62 | def load_plugins(self): 63 | """We will always use this function to load plugins 64 | in the init.py file""" 65 | 66 | # Print out that we are starting to load the plugins 67 | self.__print("Loading all plugins") 68 | added_directories = [] 69 | 70 | for plugin in self.plugins: 71 | file_path = plugin.get("file_path") 72 | plugin_directory = os.path.dirname(file_path) 73 | 74 | if plugin_directory not in added_directories: 75 | nuke.pluginAddPath(plugin_directory) 76 | added_directories.append(plugin_directory) 77 | 78 | self.__print("Loaded plugins") 79 | 80 | def build_menu(self): 81 | """We will use this function in the menu.py file 82 | to build the menus in the UI""" 83 | 84 | self.__print("Adding plugins to menu") 85 | 86 | # Defining the toolbar to add menus to 87 | magic_toolbar = nuke.toolbar("Nodes") 88 | 89 | # Getting the collected plugins 90 | plugins = self.plugins 91 | 92 | # Via the create menu function we will build the folders in the menu 93 | self.__create_menus(magic_toolbar, plugins) 94 | 95 | # Via the populate menu function we will add the plugins in the menu 96 | self.__populate_menu(magic_toolbar, plugins) 97 | 98 | self.__print("Done, menu builded and populated with plugins :)") 99 | 100 | def install_plugin(self): 101 | """Using this function the user can install plugins 102 | easily via Nuke itself using a popup where the user 103 | can select the path to the plugin, select an icon 104 | and click install.""" 105 | 106 | plugin_dialog = install_plugin_dialog.InstallPluginDialog() 107 | plugin_dialog.setMinimumSize(360, 380) 108 | if plugin_dialog.showModalDialog(): 109 | file_path = plugin_dialog.plugin_file_path.value() 110 | 111 | # Making sure file is selected 112 | if not file_path == "": 113 | extension = os.path.splitext(file_path)[1] 114 | icon_path = plugin_dialog.icon_file_path.value() 115 | category = plugin_dialog.plugin_category.value() 116 | 117 | nuke_version = None 118 | 119 | if icon_path is "": 120 | icon_path = None 121 | 122 | if file_path.endswith((".dll", ".so", ".dylib")): 123 | nuke_version = plugin_dialog.library_nuke_version.value() 124 | 125 | # Installing plugin 126 | installation = self.__install_to_plugins( 127 | plugin_path=file_path, 128 | category=category, 129 | icon_path=icon_path, 130 | extension=extension, 131 | nuke_version=nuke_version, 132 | ) 133 | 134 | nuke.message(installation) 135 | self.__print(installation) 136 | 137 | # If no file is selected, let user know 138 | else: 139 | nuke.message("Please select a plugin file to install") 140 | 141 | return 142 | 143 | @staticmethod 144 | def __print(text): 145 | message = "[MagicPlugins] %s" % text 146 | print(message) 147 | 148 | def __install_to_plugins( 149 | self, 150 | plugin_path, 151 | category, 152 | extension, 153 | icon_path=None, 154 | nuke_version=None, 155 | ): 156 | """This function contains all the logic to copy the 157 | provided tool to the correct folder and ingest it into the 158 | Nuke menu so the artist can use it.""" 159 | 160 | # Extract the plugin name, so we can use it for the name in Nuke 161 | plugin_name = os.path.basename(plugin_path) 162 | 163 | # If a Nuke version is provided, we will need to build an extra folder 164 | # Other than that we will build the install directory given 165 | # the provided variables 166 | if nuke_version: 167 | install_directory = os.path.join( 168 | self.plugins_directory, 169 | "Internet", 170 | category, 171 | nuke_version, 172 | ) 173 | 174 | else: 175 | install_directory = os.path.join( 176 | self.plugins_directory, 177 | "Internet", 178 | category, 179 | ) 180 | 181 | # Now we will append the plugin name to complete the install path 182 | # for the plugin 183 | plugin_install_path = os.path.join(install_directory, plugin_name) 184 | plugin_install_path = plugin_install_path.replace(os.sep, "/") 185 | 186 | # If an icon path is provided, we will build the installation 187 | # path for that one too 188 | if icon_path is not None: 189 | # Basically we will just get the name from the plugin, and replace 190 | # the extension with png, so it matches the plugin name 191 | icon_name = plugin_name.replace(extension, ".png") 192 | icon_install_path = os.path.join(install_directory, icon_name) 193 | icon_install_path = icon_install_path.replace(os.sep, "/") 194 | 195 | # If a plugin already exists, ask the user if overwrite is desired 196 | if os.path.isfile(plugin_install_path): 197 | if not nuke.ask( 198 | "There is already a plugin installed in that directory, " 199 | "do you want to overwrite the file?" 200 | ): 201 | self.__print("Installation aborted") 202 | return "Installation aborted" 203 | 204 | try: 205 | # Let's copy the plugin 206 | copy2(plugin_path, plugin_install_path) 207 | if icon_path is not None: 208 | copy2(icon_path, icon_install_path) 209 | 210 | # Prevent loading if it is not the correct Nuke version 211 | if nuke_version: 212 | if not nuke_version == self.nuke_version: 213 | return "Installation successful for %s" % plugin_name 214 | 215 | # Now we will add the plugin to the menu, here 216 | # we will add the icon too 217 | self.__add_plugin_to_menu( 218 | file_path=plugin_install_path, 219 | plugin_name=plugin_name, 220 | icon_path=icon_install_path, 221 | ) 222 | 223 | else: 224 | 225 | # Prevent loading if it is not the correct Nuke version 226 | if nuke_version: 227 | if not nuke_version == self.nuke_version: 228 | return "Installation successful for %s" % plugin_name 229 | 230 | # Now we will add the plugin to the menu, without icon 231 | self.__add_plugin_to_menu( 232 | file_path=plugin_install_path, 233 | plugin_name=plugin_name, 234 | ) 235 | 236 | # Append the plugin path to Nuke 237 | nuke.pluginAddPath(install_directory) 238 | 239 | return "Installation successful for %s" % plugin_name 240 | 241 | # If something went wrong, we will catch the error here, 242 | # and the artist will see it 243 | except Exception as error: 244 | return "Something went wrong... %s" % str(error) 245 | 246 | def __add_plugin_to_menu(self, file_path, plugin_name, icon_path=None): 247 | """Basically the same as the populate menu function, except we 248 | won't walk through a directory to add every plugin""" 249 | 250 | # Create the toolbar to add the node command to 251 | magic_toolbar = nuke.toolbar("Nodes") 252 | 253 | # Node types where we use the createNode() function 254 | node_types = (".gizmo", ".dll", ".dylib", ".so") 255 | 256 | # Get the plugin category for the plugin given the file path 257 | # so we can build the correct name in the menu 258 | menu_name = self.__get_plugin_category(file_path) 259 | 260 | # We need to scan every directory again to see new created directories 261 | plugins = self.__locate_plugins(self.plugins_directory) 262 | 263 | # Build new menu's 264 | self.__create_menus(magic_toolbar, plugins) 265 | 266 | # If the current plugin is a node, 267 | # like we specified in the node_types variable, 268 | # build the createNode() function 269 | if file_path.endswith(node_types): 270 | magic_toolbar.addCommand( 271 | menu_name, 272 | "nuke.createNode('%s')" % plugin_name, 273 | icon=icon_path, 274 | ) 275 | 276 | # If the plugin is a Nuke file, we use the nodePaste() function 277 | elif file_path.endswith(".nk"): 278 | magic_toolbar.addCommand( 279 | menu_name, 280 | "nuke.nodePaste('%s')" % file_path, 281 | icon=icon_path, 282 | ) 283 | 284 | def __create_menus(self, toolbar, plugins): 285 | """Via this function we will build the folders in the menu. 286 | We could skip this function, but if we want icons, 287 | (of course we want icons!), we need to build the menu first.""" 288 | 289 | # Getting the directory to scan for plugins 290 | plugins_directory = self.plugins_directory 291 | 292 | menu_name = self.menu_name 293 | menu_icon = os.path.join( 294 | self.script_directory, "resources", "icon.png" 295 | ) 296 | menu_icon = menu_icon.replace(os.sep, "/") 297 | 298 | # Creating the main menu item 299 | toolbar.addMenu(menu_name, icon=menu_icon) 300 | 301 | # We are collecting the length for the directory, to only keep 302 | # the category names. 303 | # Like we have //network_drive/nuke_plugins/plugins/myplugins/folder/ 304 | # we keep /plugins/myplugins/folder/ 305 | plugins_directory_length = len(plugins_directory) + 1 306 | 307 | plugin_file_paths = [] 308 | 309 | # We build a little dictionary for the folders we need to scan 310 | for plugin in plugins: 311 | file_path = plugin.get("file_path") 312 | plugin_file_paths.append(file_path) 313 | 314 | # Walk through the dictionary to scan every folder, and 315 | # if possible, add an icon. 316 | for root, dirs, files in os.walk(plugins_directory): 317 | for directory in sorted(dirs): 318 | directory_path = os.path.join(root, directory) 319 | directory_path = directory_path.replace(os.sep, "/") 320 | 321 | # If the folder is added in the plugin_file_paths list, add 322 | # the folder as a menu item. This makes sure no empty folders 323 | # are added. 324 | if any(directory_path in s for s in plugin_file_paths): 325 | directory_dirname = os.path.dirname(directory_path) 326 | directory_name = directory 327 | 328 | # We will build the possible icon path, and check if the 329 | # icon exists. 330 | directory_icon = directory_name + ".png" 331 | icon_path = os.path.join(directory_dirname, directory_icon) 332 | icon_path = icon_path.replace(os.sep, "/") 333 | 334 | # Here we will build the directory path to only contain the 335 | # path after the plugins folder, so we can save it as a 336 | # category path to add in the menu 337 | category = directory_path[plugins_directory_length:] 338 | 339 | category = os.path.join(menu_name, category) 340 | category = category.replace(os.sep, "/") 341 | 342 | # If the icon exists, add it, otherwise just 343 | # create a simple menu item 344 | if os.path.isfile(icon_path): 345 | toolbar.addMenu(category, icon=icon_path) 346 | 347 | else: 348 | toolbar.addMenu(category) 349 | 350 | # Adding a divider line to distinguish commands and plugins 351 | divider_name = os.path.join(menu_name, "-") 352 | divider_name = divider_name.replace(os.sep, "/") 353 | toolbar.addCommand(divider_name, "") 354 | 355 | # Add install plugin button 356 | install_plugin_name = os.path.join(menu_name, "Install plugin") 357 | install_plugin_name = install_plugin_name.replace(os.sep, "/") 358 | 359 | # Getting the icon path 360 | plugin_icon = os.path.join( 361 | self.script_directory, "resources", "install_plugin.png" 362 | ) 363 | plugin_icon = plugin_icon.replace(os.sep, "/") 364 | 365 | toolbar.addCommand( 366 | install_plugin_name, 367 | "magic_plugins.install_plugin()", 368 | icon=plugin_icon, 369 | ) 370 | 371 | # Add install plugin button 372 | open_folder_name = os.path.join(menu_name, "Open plugin folder") 373 | open_folder_name = open_folder_name.replace(os.sep, "/") 374 | 375 | # Getting the icon path 376 | folder_icon = os.path.join( 377 | self.script_directory, "resources", "open_folder.png" 378 | ) 379 | folder_icon = folder_icon.replace(os.sep, "/") 380 | 381 | toolbar.addCommand( 382 | open_folder_name, 383 | "magic_plugins.open_folder()", 384 | icon=folder_icon, 385 | ) 386 | 387 | def open_folder(self): 388 | """Via this function the user can 389 | easily open the folder where the plugins are located. 390 | 391 | It will open de file browser depending on the 392 | system the user is using. 393 | """ 394 | plugin_folder = self.plugins_directory 395 | operating_system = self.operating_system 396 | 397 | if operating_system == "darwin": 398 | os.system("open %s" % plugin_folder) 399 | 400 | elif operating_system == "win32": 401 | plugin_folder = os.path.normpath(plugin_folder) 402 | os.system("explorer %s" % plugin_folder) 403 | 404 | elif operating_system == "linux": 405 | os.system('xdg-open "%s"' % plugin_folder) 406 | 407 | else: 408 | nuke.critical("Couldn't find operating system") 409 | 410 | def __populate_menu(self, magic_toolbar, plugins): 411 | """Via this function we will add all 412 | the available plugins in the menu 413 | 414 | To use this function we need a specified 415 | toolbar and a plugin dictionary. 416 | """ 417 | 418 | # Iterate trough the provided dictionary to add plugins 419 | for plugin in plugins: 420 | # Get the data for the plugin necessary to build the menu item 421 | plugin_name = plugin.get("plugin_name") 422 | plugin_type = plugin.get("plugin_type") 423 | file_path = plugin.get("file_path") 424 | icon_path = plugin.get("icon_path") 425 | 426 | # Node types where we use the createNode() function 427 | node_types = ("gizmo", "dll", "dylib", "so") 428 | 429 | # Get the plugin category for the plugin given the file path 430 | # so we can build the correct name in the menu 431 | menu_name = self.__get_plugin_category(file_path) 432 | 433 | # If the current plugin is a node, 434 | # like we specified in the node_types variable, 435 | # build the createNode() function 436 | if any(s in plugin_type for s in node_types): 437 | magic_toolbar.addCommand( 438 | menu_name, 439 | "nuke.createNode('%s')" % plugin_name, 440 | icon=icon_path, 441 | ) 442 | 443 | # If the plugin is a Nuke file, we use the nodePaste() function 444 | elif plugin_type == "nk": 445 | magic_toolbar.addCommand( 446 | menu_name, 447 | "nuke.nodePaste('%s')" % file_path, 448 | icon=icon_path, 449 | ) 450 | 451 | def __locate_plugins(self, plugins_directory): 452 | """This function will scan the specified folder for 453 | plugins, and build a list containing all necessary information 454 | to load the plugins""" 455 | 456 | # First we create an empty list where we will add al the plugins 457 | # we want to process to load 458 | plugins = [] 459 | 460 | # Here we will determine the corresponding library extension 461 | # to the current operating system 462 | operating_system = self.operating_system 463 | 464 | if operating_system == "darwin": 465 | library_extension = ".dylib" 466 | 467 | elif operating_system == "win32": 468 | library_extension = ".dll" 469 | 470 | else: 471 | library_extension = ".so" 472 | 473 | # Now we will walk through the entire 474 | # specified directory to scan for plugins 475 | for root, dirs, files in os.walk(plugins_directory): 476 | for filename in files: 477 | # First we will build the path for the plugin we might 478 | # want to append to the list 479 | file_path = os.path.join(root, filename) 480 | # Small fix which is necessary for Windows systems 481 | file_path = file_path.replace(os.sep, "/") 482 | 483 | # If the file is a gizmo or a nk file, we just go ahead and 484 | # add it to our list 485 | if file_path.endswith((".gizmo", ".nk")): 486 | # Add the plugin to the list to load 487 | plugin_information = self.__collect_plugin(file_path) 488 | plugins.append(plugin_information) 489 | 490 | # If the file is a library file (.dll, .so or .dylib), 491 | # we need to be a little bit more careful because 492 | # every library file is build for a specific version of Nuke. 493 | # So we don't want to add a library file thats build for 494 | # Nuke 12.2 when we are in Nuke 13.2 495 | elif file_path.endswith(library_extension): 496 | # Here we will validate if we want to load this plugin 497 | if self.__validate_plugin(file_path): 498 | # We want to load this plugin! Let's add it 499 | # to the list. 500 | plugin_information = self.__collect_plugin(file_path) 501 | plugins.append(plugin_information) 502 | 503 | return plugins 504 | 505 | def __validate_plugin(self, file_path): 506 | """This function will check if the plugin 507 | is ready to be loaded, or if we don't want to load it. 508 | 509 | It will check if the .dll, .so or .dylib file upper folder 510 | matches the current Nuke version. 511 | 512 | If you want to add plugins to load, always make sure to create 513 | a folder for the Nuke version where the plugins are compiled for. 514 | Like if you want to use a plugin for version 13.1, 515 | create a folder called '13.1' where you add the plugins inside.""" 516 | 517 | # We will firstly assume we won't validate the plugin, unless 518 | # the plugin is indeed correct. 519 | validated = False 520 | 521 | # If the upper folder of the plugin matches the 522 | # current Nuke version (e.g 13.2) we will let the validation pass. 523 | dirname = os.path.dirname(file_path) 524 | basename = os.path.basename(dirname) 525 | 526 | if basename == str(self.nuke_version): 527 | validated = True 528 | 529 | return validated 530 | 531 | def __collect_plugin(self, file_path): 532 | """In this function we create a dictionary item for the plugin 533 | provided the file path. At the end we will return 534 | a dictionary item like this: 535 | 536 | plugin_information = { 537 | plugin_type: "gizmo", 538 | file_path: "path/to/my/MagicTool.gizmo", 539 | plugin_name: "MagicTool", 540 | icon_path: "path/to/my/MagicTool.png" 541 | } 542 | 543 | 544 | """ 545 | 546 | # Get the file extension for the file, so we can 547 | # check the plugin type 548 | plugin_extension = os.path.splitext(file_path)[1] 549 | plugin_type = plugin_extension.replace(".", "") 550 | 551 | # Get the plugin name without the extension 552 | plugin_name = os.path.basename(file_path) 553 | plugin_name = os.path.splitext(plugin_name)[0] 554 | 555 | # Build the dictionary with the required data 556 | plugin_information = { 557 | "plugin_type": plugin_type, 558 | "file_path": file_path, 559 | "plugin_name": plugin_name, 560 | "icon_path": None, 561 | } 562 | 563 | # If there is an icon available, lets add it to the dictionary. 564 | icon_path = file_path.replace(plugin_extension, ".png") 565 | if os.path.isfile(icon_path): 566 | plugin_information["icon_path"] = icon_path 567 | 568 | return plugin_information 569 | 570 | def __get_plugin_category(self, file_path): 571 | """This function will detect the plugin dictionary 572 | given the file path of a plugin. This is necessary to build the 573 | correct menu name. 574 | 575 | File path: //path/to/plugins/folder/plugin.gizmo 576 | plugins directory: //path/to/plugins 577 | Category: /plugins/folder/""" 578 | 579 | # We need the plugins directory, to calculate the length 580 | # we need to strip to build the category path 581 | plugins_directory = self.plugins_directory 582 | 583 | # And of course we need the menu name to add in front of the category 584 | # because we want to add the item to our created menu 585 | menu_name = self.menu_name 586 | 587 | # Calculate the length of the path, to strip from the file path 588 | plugins_directory_length = len(plugins_directory) + 1 589 | 590 | # Here we remove the extension from the file path 591 | # because we only want the category 592 | file_path_category = os.path.splitext(file_path)[0] 593 | 594 | # Now we will strip the first part of the file path 595 | # to only keep the category 596 | category = file_path_category[plugins_directory_length:] 597 | 598 | # Finally we have to add the menu name in front of the category 599 | # because we want to add items to menu we created 600 | category = os.path.join(menu_name, category) 601 | # Small fix for Windows based systems 602 | category = category.replace(os.sep, "/") 603 | 604 | return category 605 | -------------------------------------------------------------------------------- /MagicPlugins/menu.py: -------------------------------------------------------------------------------- 1 | """ 2 | MagicPlugins by Gilles Vink 3 | 4 | Menu.py script to automatically build Nuke menu 5 | 6 | """ 7 | 8 | magic_plugins.build_menu() 9 | -------------------------------------------------------------------------------- /MagicPlugins/plugins/Internet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gillesvink/MagicPlugins/bc68b0c9d0d8f341d262136e98cc3705d0d2f1d1/MagicPlugins/plugins/Internet.png -------------------------------------------------------------------------------- /MagicPlugins/plugins/Internet/3D.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gillesvink/MagicPlugins/bc68b0c9d0d8f341d262136e98cc3705d0d2f1d1/MagicPlugins/plugins/Internet/3D.png -------------------------------------------------------------------------------- /MagicPlugins/plugins/Internet/3D/placeholder: -------------------------------------------------------------------------------- 1 | Place .gizmo, .nk, .dll, .so or .dylib files here -------------------------------------------------------------------------------- /MagicPlugins/plugins/Internet/Blink.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gillesvink/MagicPlugins/bc68b0c9d0d8f341d262136e98cc3705d0d2f1d1/MagicPlugins/plugins/Internet/Blink.png -------------------------------------------------------------------------------- /MagicPlugins/plugins/Internet/Blink/placeholder: -------------------------------------------------------------------------------- 1 | Place .gizmo, .nk, .dll, .so or .dylib files here -------------------------------------------------------------------------------- /MagicPlugins/plugins/Internet/Channel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gillesvink/MagicPlugins/bc68b0c9d0d8f341d262136e98cc3705d0d2f1d1/MagicPlugins/plugins/Internet/Channel.png -------------------------------------------------------------------------------- /MagicPlugins/plugins/Internet/Channel/placeholder: -------------------------------------------------------------------------------- 1 | Place .gizmo, .nk, .dll, .so or .dylib files here -------------------------------------------------------------------------------- /MagicPlugins/plugins/Internet/Color.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gillesvink/MagicPlugins/bc68b0c9d0d8f341d262136e98cc3705d0d2f1d1/MagicPlugins/plugins/Internet/Color.png -------------------------------------------------------------------------------- /MagicPlugins/plugins/Internet/Color/placeholder: -------------------------------------------------------------------------------- 1 | Place .gizmo, .nk, .dll, .so or .dylib files here -------------------------------------------------------------------------------- /MagicPlugins/plugins/Internet/Deep.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gillesvink/MagicPlugins/bc68b0c9d0d8f341d262136e98cc3705d0d2f1d1/MagicPlugins/plugins/Internet/Deep.png -------------------------------------------------------------------------------- /MagicPlugins/plugins/Internet/Deep/placeholder: -------------------------------------------------------------------------------- 1 | Place .gizmo, .nk, .dll, .so or .dylib files here -------------------------------------------------------------------------------- /MagicPlugins/plugins/Internet/Draw.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gillesvink/MagicPlugins/bc68b0c9d0d8f341d262136e98cc3705d0d2f1d1/MagicPlugins/plugins/Internet/Draw.png -------------------------------------------------------------------------------- /MagicPlugins/plugins/Internet/Draw/placeholder: -------------------------------------------------------------------------------- 1 | Place .gizmo, .nk, .dll, .so or .dylib files here -------------------------------------------------------------------------------- /MagicPlugins/plugins/Internet/Filter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gillesvink/MagicPlugins/bc68b0c9d0d8f341d262136e98cc3705d0d2f1d1/MagicPlugins/plugins/Internet/Filter.png -------------------------------------------------------------------------------- /MagicPlugins/plugins/Internet/Filter/placeholder: -------------------------------------------------------------------------------- 1 | Place .gizmo, .nk, .dll, .so or .dylib files here -------------------------------------------------------------------------------- /MagicPlugins/plugins/Internet/Keyer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gillesvink/MagicPlugins/bc68b0c9d0d8f341d262136e98cc3705d0d2f1d1/MagicPlugins/plugins/Internet/Keyer.png -------------------------------------------------------------------------------- /MagicPlugins/plugins/Internet/Keyer/placeholder: -------------------------------------------------------------------------------- 1 | Place .gizmo, .nk, .dll, .so or .dylib files here -------------------------------------------------------------------------------- /MagicPlugins/plugins/Internet/Merge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gillesvink/MagicPlugins/bc68b0c9d0d8f341d262136e98cc3705d0d2f1d1/MagicPlugins/plugins/Internet/Merge.png -------------------------------------------------------------------------------- /MagicPlugins/plugins/Internet/Merge/placeholder: -------------------------------------------------------------------------------- 1 | Place .gizmo, .nk, .dll, .so or .dylib files here -------------------------------------------------------------------------------- /MagicPlugins/plugins/Internet/Other.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gillesvink/MagicPlugins/bc68b0c9d0d8f341d262136e98cc3705d0d2f1d1/MagicPlugins/plugins/Internet/Other.png -------------------------------------------------------------------------------- /MagicPlugins/plugins/Internet/Other/placeholder: -------------------------------------------------------------------------------- 1 | Place .gizmo, .nk, .dll, .so or .dylib files here -------------------------------------------------------------------------------- /MagicPlugins/plugins/Internet/Transform.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gillesvink/MagicPlugins/bc68b0c9d0d8f341d262136e98cc3705d0d2f1d1/MagicPlugins/plugins/Internet/Transform.png -------------------------------------------------------------------------------- /MagicPlugins/plugins/Internet/Transform/placeholder: -------------------------------------------------------------------------------- 1 | Place .gizmo, .nk, .dll, .so or .dylib files here -------------------------------------------------------------------------------- /MagicPlugins/resources/MagicPlugins.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gillesvink/MagicPlugins/bc68b0c9d0d8f341d262136e98cc3705d0d2f1d1/MagicPlugins/resources/MagicPlugins.png -------------------------------------------------------------------------------- /MagicPlugins/resources/header.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gillesvink/MagicPlugins/bc68b0c9d0d8f341d262136e98cc3705d0d2f1d1/MagicPlugins/resources/header.png -------------------------------------------------------------------------------- /MagicPlugins/resources/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gillesvink/MagicPlugins/bc68b0c9d0d8f341d262136e98cc3705d0d2f1d1/MagicPlugins/resources/icon.png -------------------------------------------------------------------------------- /MagicPlugins/resources/install_plugin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gillesvink/MagicPlugins/bc68b0c9d0d8f341d262136e98cc3705d0d2f1d1/MagicPlugins/resources/install_plugin.png -------------------------------------------------------------------------------- /MagicPlugins/resources/installing_plugin_library.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gillesvink/MagicPlugins/bc68b0c9d0d8f341d262136e98cc3705d0d2f1d1/MagicPlugins/resources/installing_plugin_library.png -------------------------------------------------------------------------------- /MagicPlugins/resources/installing_plugin_menu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gillesvink/MagicPlugins/bc68b0c9d0d8f341d262136e98cc3705d0d2f1d1/MagicPlugins/resources/installing_plugin_menu.png -------------------------------------------------------------------------------- /MagicPlugins/resources/installing_plugin_ui.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gillesvink/MagicPlugins/bc68b0c9d0d8f341d262136e98cc3705d0d2f1d1/MagicPlugins/resources/installing_plugin_ui.png -------------------------------------------------------------------------------- /MagicPlugins/resources/open_folder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gillesvink/MagicPlugins/bc68b0c9d0d8f341d262136e98cc3705d0d2f1d1/MagicPlugins/resources/open_folder.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Python 2.7 3.7](https://img.shields.io/badge/python-2.7%20%7C%203.7-blue.svg)](https://www.python.org/) 2 | [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black) 3 | 4 | ![MagicPlugins, automatic plugin loader for Nuke](/MagicPlugins/resources/MagicPlugins.png) 5 | 6 | ## What does it do? 7 | It scans the MagicPlugins directory for plugins, and automatically adds them according to the folder/category created. 8 | 9 | ## Which versions of Nuke are supported? 10 | * Currently Nuke 11+ is supported, tested on Linux, Mac and Windows. But earlier versions should also just work fine. 11 | 12 | ## Which files are supported? 13 | * `.gizmo` 14 | * `.nk` 15 | * `.dll` (Windows), `.so` (Linux), `.dylib` (Mac OS) 16 | 17 | ## How to install 18 | * Copy the entire contents of this repository in your `.nuke` folder. 19 | * Add the line 20 | `nuke.pluginAddPath("./MagicPlugins")` 21 | to your `init.py` file in your `.nuke` folder. 22 | * Another option is to add the environment `NUKE_PATH` to your system with the value `your/MagicPlugins/installation/folder/`. 23 | 24 | ## How to use 25 | All folders in the MagicPlugins directory are at startup scanned. If you add a gizmo in the home directory, it will be added to the menu. If you add the gizmo in a folder somewhere, all the folders will be created accordingly. This allows you to create categories. 26 | 27 | ### Installing via the GUI 28 | When using the GUI installer, the plugin will be added inside the Internet folder/category. 29 | 30 | 1. Select the Install plugin option in the MagicPlugins menu. 31 | 32 | ![The option in the menu](/MagicPlugins/resources/installing_plugin_menu.png) 33 | 34 | 2. Select the plugin you want to install, and optionally you can select an icon as well. 35 | 36 | ![MagicPlugin installer](/MagicPlugins/resources/installing_plugin_ui.png) 37 | 38 | 3. If the file is one of the library files (`.dll`, `.so`, `.dylib`) you also need to specify the Nuke version for the selected file. 39 | 40 | ![MagicPlugin install library file](/MagicPlugins/resources/installing_plugin_library.png) 41 | 42 | 43 | ### Manually installing (multiple files) 44 | All the files that are loaded in startup are located in the plugins folder inside the MagicPlugin folder. 45 | 46 | Every item that is added inside this folder will be added automatically to Nuke. So if you want to add a gizmo or a Nuke script file, just copy and paste it into there. 47 | 48 | ### Installing library files (`.dll`, `.so`, `.dylib`) 49 | Because library files are compiled for every Nuke version specifically, you don't want to load a plugin compiled for Nuke 12.2 if you are in 13.0. Using MagicPlugins it's possible to load library files for the correct Nuke version, and skip the others. 50 | * When adding library files, create a folder named with the target Nuke version. So for example, if I want to add a plugin called myPlugin.dll for `Nuke 13.0`, it needs to be added like `myLibraryPluginsCategory/13.0/myPlugin.dll`. 51 | * If you want to add the plugin for `Nuke 12.2`, it needs to be added like `myLibraryPluginsCategory/12.2/myPlugin.dll`, and so on 52 | -------------------------------------------------------------------------------- /init.py: -------------------------------------------------------------------------------- 1 | import nuke 2 | 3 | nuke.pluginAddPath("./MagicPlugins") 4 | --------------------------------------------------------------------------------