├── tests ├── __init__.py ├── files │ ├── parser_result.pkl │ └── test_file.nk ├── test_parser.py └── test_nukery.py ├── pyproject.toml ├── nukery ├── __init__.py ├── _base.py ├── parser.py ├── constants.py ├── _nuke.py └── store.py ├── LICENSE ├── README.md └── example.py /tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/files/parser_result.pkl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rasheedgm/nukery/HEAD/tests/files/parser_result.pkl -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["hatchling >= 1.26"] 3 | build-backend = "hatchling.build" 4 | 5 | [project] 6 | name = "nukery" 7 | dynamic = "version" 8 | requires-python = ">=3.7" 9 | authors = [ 10 | {name="Abdul Rasheed G M"} 11 | ] 12 | readme = "README.md" 13 | license = "MIT" 14 | license-files = "LICEN[CS]E" 15 | keywords = ["nuke", "vfx", "foundry", "parser", "parse"] -------------------------------------------------------------------------------- /tests/test_parser.py: -------------------------------------------------------------------------------- 1 | import os 2 | import unittest 3 | from collections import OrderedDict 4 | from nukery.parser import NukeScriptParser 5 | 6 | import pickle 7 | 8 | 9 | class TestPaser(unittest.TestCase): 10 | 11 | def __init__(self, *args, **kwargs): 12 | super(TestPaser, self).__init__(*args, **kwargs) 13 | self.file_path = os.path.join(os.path.dirname(__file__), "files/test_file.nk") 14 | self.result_file = os.path.join(os.path.dirname(__file__), "files/parser_result.pkl") 15 | 16 | def test_from_file(self): 17 | file_ = "" # TODO set this 18 | with open(self.result_file, 'rb') as f: 19 | expected_result = pickle.load(f) 20 | 21 | result = list(NukeScriptParser.from_file(self.file_path)) 22 | 23 | self.assertEqual(expected_result, result) -------------------------------------------------------------------------------- /nukery/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | from nukery._nuke import ( 3 | all_nodes, 4 | selected_node, 5 | selected_nodes, 6 | script_open, 7 | save_script_as, 8 | script_clear, 9 | delete, 10 | to_node, 11 | node_copy, 12 | node_paste, 13 | create_node, 14 | clear_selection, 15 | get_script_text, 16 | root, 17 | select_all, 18 | SessionStore, 19 | ) 20 | 21 | __author__ = "@rasheedgm" 22 | __version__ = "v0.1" 23 | 24 | 25 | __all__ = [ 26 | 'SessionStore', 27 | 'all_nodes', 28 | 'selected_node', 29 | 'selected_nodes', 30 | 'script_open', 31 | 'save_script_as', 32 | 'delete', 33 | 'script_clear', 34 | 'to_node', 35 | 'node_copy', 36 | 'node_paste', 37 | 'create_node', 38 | 'clear_selection', 39 | 'get_script_text', 40 | 'root', 41 | 'select_all', 42 | ] 43 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Abdul Rasheed G M 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 | -------------------------------------------------------------------------------- /tests/test_nukery.py: -------------------------------------------------------------------------------- 1 | import os.path 2 | import unittest 3 | import nukery 4 | 5 | 6 | 7 | class TestNukery(unittest.TestCase): 8 | 9 | def __init__(self, *args, **kwargs): 10 | super(TestNukery, self).__init__(*args, **kwargs) 11 | self.file_path = os.path.join(os.path.dirname(__file__), "files/test_file.nk") 12 | 13 | def test_script_open(self): 14 | nukery.script_open(self.file_path) 15 | self.all_nodes = nukery.all_nodes() 16 | nukery.script_clear() 17 | self.assertTrue(self.all_nodes) 18 | 19 | def test_all_nodes(self): 20 | expected_root_names = set(['ColorWheel1', 'Keylight1', 'Grade9', 'Grade10', 'Grade11', 'ColorBars1', 'Primatte1', 'Group1', 'Roto1', 'RotoPaint1', 'CheckerBoard1', 'Grade3', 'Grade8', 'Grade1', 'Grade2', 'Grade4', 'Grade5', 'Grade6', 'Grade7', 'Copy1', 'Premult1', 'Viewer1']) 21 | expected_group1 = set(['Input1', 'Grade1', 'Transform1', 'Merge1', 'Output1']) 22 | expected_recursive = expected_root_names.union(expected_group1) 23 | 24 | nukery.script_open(self.file_path) 25 | result_root = set([n.name for n in nukery.all_nodes()]) 26 | result_recursive = set([n.name for n in nukery.all_nodes(recursive=True)]) 27 | result_group1 = set([n.name for n in nukery.all_nodes(group="root.Group1")]) 28 | nukery.script_clear() 29 | self.assertEqual(expected_root_names, result_root) 30 | self.assertEqual(expected_group1, result_group1) 31 | self.assertEqual(expected_recursive, result_recursive) 32 | 33 | def test_selected_nodes(self): 34 | expected_result = set(['Grade4', 'Grade5', 'Grade7', 'Grade6']) 35 | nukery.script_open(self.file_path) 36 | result = set([n.name for n in nukery.selected_nodes()]) 37 | nukery.script_clear() 38 | 39 | self.assertEqual(expected_result, result) -------------------------------------------------------------------------------- /nukery/_base.py: -------------------------------------------------------------------------------- 1 | from nukery.constants import CLONE_KNOBS 2 | from nukery.store import NodeStore, SessionStore 3 | 4 | 5 | class Node(object): 6 | 7 | def __init__(self, class_=None, knobs=None, user_knobs=None, node_store=None): 8 | if node_store: 9 | self.node_store = node_store 10 | else: 11 | node_data = { 12 | "type": "node", 13 | "class": class_, 14 | "knobs": knobs, 15 | "user_knobs": user_knobs, 16 | "inputs": "", 17 | } 18 | self.node_store = NodeStore(**node_data) 19 | 20 | @property 21 | def name(self): 22 | return self["name"] 23 | 24 | @property 25 | def full_name(self): 26 | return "{0}.{1}".format(self.parent, self.name) 27 | 28 | @property 29 | def selected(self): 30 | return self.node_store.knobs.get("selected", "false") == "true" 31 | 32 | @property 33 | def parent(self): 34 | return self.node_store.parent 35 | 36 | def knobs(self): 37 | return self.node_store.knobs 38 | 39 | def knob(self, name): 40 | return self[name] 41 | 42 | def get_class(self): 43 | return self.node_store.node_class 44 | 45 | def get_inputs(self): 46 | return self.node_store.inputs 47 | 48 | def input(self, index): 49 | if len(self.node_store.inputs) <= index: 50 | return None 51 | return self.node_store.inputs[index] 52 | 53 | def get_outputs(self): 54 | return self.node_store.outputs 55 | 56 | def set_input(self, index, node): 57 | self.node_store.set_input(index, node.node_store) 58 | 59 | def set_name(self, name): 60 | self["name"] = name 61 | 62 | def set_xpos(self, value): 63 | self["xpos"] = str(value) 64 | 65 | def set_ypos(self, value): 66 | self["ypos"] = str(value) 67 | 68 | def set_xypos(self, x, y): 69 | self.set_xpos(x) 70 | self.set_ypos(y) 71 | 72 | def set_selected(self, value=True): 73 | if not isinstance(value, bool): 74 | raise ValueError("value has to be bool") 75 | if value: 76 | self.node_store.knobs["selected"] = "true" 77 | else: 78 | self.node_store.knobs.pop("selected", None) 79 | 80 | def __setitem__(self, key, value): 81 | if key == "name": 82 | ns = self.node_store.get_by_name(value) 83 | if ns == self.node_store: 84 | return 85 | if ns: 86 | raise Exception("Node name already exists") 87 | 88 | if self.node_store.type == "clone": 89 | if key not in CLONE_KNOBS: 90 | original = SessionStore.get_variable(self.node_store.variable) 91 | original.knobs[key] = value 92 | return True 93 | 94 | self.node_store.knobs[key] = value 95 | 96 | def __getitem__(self, item): 97 | if self.node_store.type == "clone": 98 | if item not in self.node_store.knobs: 99 | original = SessionStore.get_variable(self.node_store.variable) 100 | return original.knobs.get(item) 101 | return self.node_store.knobs.get(item) 102 | 103 | def __enter__(self): # TODO test this 104 | if self.node_store.is_group: 105 | NodeStore.set_current_parent("{0}.{1}".format(self.parent, self.name)) 106 | return self 107 | else: 108 | raise TypeError("Context is only available with group nodes.") 109 | 110 | def __exit__(self, exc_type, exc_val, exc_tb): 111 | if self.node_store.is_group: 112 | NodeStore.un_join_last_child() 113 | 114 | def __repr__(self): 115 | mem = hex(id(self)) 116 | if self.node_store.type == "clone": 117 | return "".format(self.name, mem) 118 | elif self.node_store.node_class == "Root": 119 | return "".format(mem) 120 | return "".format(self.name, mem) 121 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # Nukery: A Lightweight Nuke Python Mock 3 | 4 | ## Introduction 5 | **Nukery** is a Python library designed to emulate the core features of the Nuke python. It allows users to read and manipulate Nuke script files (.nk) using common operations from the Nuke Python API, **all without the need for a Nuke license.** 6 | 7 | ## Why Nukery? 8 | - **Accessibility**: Nukery makes it possible to handle Nuke scripts even without the full Nuke application. 9 | - **Prototyping**: Test and prototype Nuke scripts swiftly, bypassing the need to launch the entire software suite. 10 | - **Education**: An excellent resource for those looking to understand Nuke script structures and syntax. 11 | 12 | ## Capabilities of Nukery 13 | - Reads and interprets Nuke script files. 14 | - Grants access to script components such as nodes, properties, and connections. 15 | - Permits elementary script element manipulation within the scope of implementation. 16 | 17 | ## Limitations of Nukery 18 | - Nukery is not equipped to render or composite images as Nuke does. 19 | 20 | ## Getting Started 21 | ```python 22 | import nukery 23 | 24 | # Load a Nuke script 25 | nukery.script_open("/path/to/script.nk") 26 | 27 | # Retrieve all nodes in the current session 28 | nodes = nukery.all_nodes() 29 | 30 | # Fetch selected nodes 31 | selected = nukery.selected_nodes() 32 | ``` 33 | 34 | ## Available Methods 35 | Nukery includes the following commonly used methods, with more on the horizon: 36 | - `SessionStore` 37 | - `all_nodes` 38 | - `selected_node` 39 | - `selected_nodes` 40 | - `script_open` 41 | - `save_script_as` 42 | - `delete` 43 | - `script_clear` 44 | - `to_node` 45 | - `node_copy` 46 | - `node_paste` 47 | - `create_node` 48 | - `clear_selection` 49 | - `get_script_text` 50 | - `root` 51 | - `select_all` 52 | 53 | ## Examples 54 | ### Creating Nodes 55 | ```python 56 | import nukery 57 | 58 | nukery.create_node("Constant") 59 | nukery.create_node("Grade") 60 | nukery.create_node("Transform") 61 | 62 | print(nukery.get_script_text()) 63 | ``` 64 | #### result 65 | ```tcl 66 | Constant { 67 | inputs 0 68 | ypos 0 69 | xpos 0 70 | name Constant1 71 | } 72 | Grade { 73 | ypos 50 74 | xpos 0 75 | name Grade1 76 | } 77 | Transform { 78 | ypos 100 79 | xpos 0 80 | name Transform1 81 | selected true 82 | } 83 | ``` 84 | ### Connecting Input 85 | ```python 86 | import nukery 87 | 88 | constant = nukery.create_node("Constant") 89 | grade = nukery.create_node("Grade") 90 | transform = nukery.create_node("Transform") 91 | 92 | transform.set_input(0, constant) 93 | print(nukery.get_script_text()) 94 | ``` 95 | #### result 96 | ```tcl 97 | Constant { 98 | inputs 0 99 | ypos 0 100 | xpos 0 101 | name Constant1 102 | } 103 | set Nc019c415 [stack 0] 104 | Grade { 105 | ypos 50 106 | xpos 0 107 | name Grade1 108 | } 109 | push $Nc019c415 110 | Transform { 111 | ypos 100 112 | xpos 0 113 | name Transform1 114 | selected true 115 | } 116 | ``` 117 | 118 | ### Working Across Multiple Sessions 119 | 120 | ```python 121 | import nukery 122 | 123 | session1 = nukery.SessionStore("Session1") 124 | session2 = nukery.SessionStore("Session2") 125 | 126 | with session1: 127 | nukery.create_node("Grade") 128 | print("Session1", nukery.all_nodes()) 129 | 130 | with session2: 131 | nukery.create_node("Transform") 132 | print("Session2", nukery.all_nodes()) 133 | 134 | # default session 135 | print(nukery.all_nodes()) 136 | ``` 137 | #### result 138 | ``` 139 | Session1 [] 140 | Session2 [] 141 | [] 142 | ``` 143 | 144 | 145 | ## Parsing Nuke Scripts 146 | Nuke scripts are parsed using regex-based string parsing, returning a list of dictionaries as the result. 147 | ```python 148 | from nukery.parser import NukeScriptParser 149 | 150 | # to parse from file 151 | NukeScriptParser.from_file("/file/path") 152 | 153 | # to parse from string 154 | text = """ 155 | Grade { 156 | name Garde1 157 | xpos 0 158 | ypos 0 159 | white 1.2 160 | } 161 | """ 162 | NukeScriptParser.from_text(text) 163 | ``` 164 | 165 | The resulting dictionary structure is as follows: 166 | 167 | 168 | ``` 169 | { 170 | 'type': 'node', 171 | 'class': 'Grade', 172 | 'knobs': { 173 | 'name': 'Grade1', 174 | 'xpos': '0', 175 | 'ypos': '0', 176 | 'white': '1.2' 177 | }, 178 | 'inputs': '', 179 | 'user_knobs': [], 180 | 'var': None, 181 | 'stack_index': None, 182 | 'node_content': ' name Garde1\n xpos 0\n ypos 0\n white 1.2\n' 183 | } 184 | ``` 185 | 186 | 187 | ## Conclusion 188 | Nukery is not foolproof, I have only tested it in basic scripts, I hope you find Nukery to be a useful addition to your toolkit. Happy scripting! -------------------------------------------------------------------------------- /example.py: -------------------------------------------------------------------------------- 1 | import tempfile 2 | from pathlib import Path 3 | 4 | import nukery 5 | 6 | # Copy the test file's content to temporary file so we can modify it without breaking the test file 7 | temp_file = tempfile.NamedTemporaryFile(mode="w", delete=True, encoding="utf-8") 8 | file_path = temp_file.name 9 | test_file_path = Path(__file__).parent / "tests" / "files" / "test_file.nk" 10 | with open(test_file_path, "r") as test_file: 11 | content = test_file.read() 12 | temp_file.write(content) 13 | 14 | # open a nuke script to the session 15 | nukery.script_open(file_path) 16 | 17 | # get all nodes in the script, from current context and current session. 18 | nukery.all_nodes() 19 | # context and session example can be found in the page. 20 | 21 | # get all nodes of type 'Read' 22 | nukery.all_nodes('Read') 23 | 24 | # get all nodes form a group 25 | nukery.all_nodes(group='Group1') 26 | # or 27 | nukery.all_nodes(group='root.Group1') 28 | 29 | # get all nodes recursively 30 | nukery.all_nodes(recursive=True) 31 | 32 | # get one node by name 33 | grade1 = nukery.to_node('Grade1') 34 | 35 | # set a node selected 36 | grade1.set_selected(True) 37 | 38 | # get all selected nodes from the session and context 39 | nukery.selected_nodes() 40 | 41 | # get last selected node, this will return last in the selected list equal to selected_nodes()[-1] 42 | nukery.selected_node() 43 | 44 | # save script to file 45 | nukery.save_script_as(file_path) 46 | # note nukery doesn't have scrip_save intentionally, I don't want to override actual file. 47 | # save_script_as will not check file exits, it will override if exists. 48 | 49 | # copy selected nodes to a file 50 | nukery.node_copy(file_path) 51 | # if file path is not provided selected nodes will be copied to clipboard 52 | 53 | # paste nodes from a file, this will paste nodes form clipboard if file_path is not provided 54 | nukery.node_paste(file_path) 55 | 56 | # set input to node 57 | grade = nukery.to_node('Grade1') 58 | merge = nukery.to_node('Grade2') 59 | merge.set_input(0, grade) 60 | 61 | # delete node 62 | grade = nukery.to_node('Grade1') 63 | nukery.delete(grade) 64 | 65 | # create node 66 | nukery.create_node('Grade') 67 | 68 | # create node with knob values 69 | nukery.create_node('Grade', white=1.2, xpos=101) 70 | 71 | # all of these can be done in multiple sessions 72 | session1 = nukery.SessionStore("Session1") 73 | with session1: 74 | nukery.script_open(file_path) 75 | for node in nukery.all_nodes(): 76 | print(node.name) 77 | 78 | session2 = nukery.SessionStore("Session2") 79 | with session2: 80 | nukery.script_open(file_path) 81 | for node in nukery.all_nodes(): 82 | print(node.name) 83 | 84 | # you can also run all methods in different node context as well. 85 | # to run in Group1 86 | group1 = nukery.create_node('Group') 87 | with group1: 88 | for node in nukery.all_nodes(): 89 | print(node.name) 90 | 91 | # create new node within group 92 | nukery.create_node("Transform") 93 | 94 | 95 | # let's see how can I build a fresh nuke script, from scratch. 96 | root = nukery.create_node( 97 | "Root", 98 | first=1, 99 | last=1001, 100 | format="1920 1080 0 0 1920 1080 1 HD_1080" 101 | ) 102 | read = nukery.create_node( 103 | "Read", 104 | file="/file/path/file.###.exr", 105 | first=1, 106 | last=1001, 107 | ) 108 | 109 | nukery.script_clear() 110 | transform = nukery.create_node("Transform") 111 | grade = nukery.create_node("Grade") 112 | merge = nukery.create_node("Merge2", operation="plus") 113 | # when you are creating nodes in sequence by default input zero will be connected to last node 114 | # but if you want to connect other inputs then you will connect like this. 115 | merge.set_input(1, read) 116 | write = nukery.create_node( 117 | "Write", 118 | file="/file/path/file.####.exr", 119 | file_type="exr", 120 | channels="rgba" 121 | ) 122 | print(nukery.get_script_text()) 123 | 124 | # this will create a script like below 125 | """ 126 | Root { 127 | inputs 0 128 | first 1 129 | last 1001 130 | format 1920 1080 0 0 1920 1080 1 HD_1080 131 | ypos 0 132 | xpos 0 133 | } 134 | Read { 135 | inputs 0 136 | file /file/path/file.###.exr 137 | first 1 138 | last 1001 139 | ypos 50 140 | xpos 0 141 | name Read1 142 | } 143 | set N4dd60ff0 [stack 0] 144 | push $N4dd60ff0 145 | Transform { 146 | ypos 100 147 | xpos 0 148 | name Transform1 149 | } 150 | Grade { 151 | ypos 150 152 | xpos 0 153 | name Grade1 154 | } 155 | Merge2 { 156 | inputs 2 157 | operation plus 158 | ypos 200 159 | xpos 0 160 | name Merge21 161 | } 162 | Write { 163 | file /file/path/file.####.exr 164 | file_type exr 165 | channels rgba 166 | ypos 250 167 | xpos 0 168 | selected true 169 | name Write1 170 | } 171 | """ -------------------------------------------------------------------------------- /nukery/parser.py: -------------------------------------------------------------------------------- 1 | import re 2 | import os.path 3 | from collections import OrderedDict 4 | 5 | 6 | class NukeScriptParser: 7 | knob_pattern = r"(^\s*)(\w+|(?:[\w\.]+)|(?:\"(?:[^\"]*)\"))\s+((?:\{(?:[^{}]*\{[^{}]*\}[^{}]*)*\})|(?:\{[^{}]+\})|(?:[^\n]+))" 8 | stack_command_pattern = r"\s*(\S+)\s?\$?(?:((?:[\S\s]*\}$)|(?:\S+(?:\sv\d+)?))\s*)?(?:\s*\[stack\s*(\d+)\])?" 9 | clone_pattern = r"(clone)\s(?:\$(\w*))?|(?:[\w\|*s*]+\s(\w+))$" 10 | user_knob_pattern = r"\{([\d]+)\s([\w]+)(?:[^\}]*\})" 11 | node_pattern = r"(?:\s*((?:clone[\s\$\w\|]+)|(?:\b\w+)|(?:[\w\.]+)))(?:\s*\{\n((?:\s*(?:[\w\.\"]+\s)(?:\{{1}[^{}]+\}{1})?(?:.*\n?)+?)+)(?:\s*\}\n*))|((?:\b(?:set|push|add_layer|^version))\s*(?:(?:.*)+?)|(?:end_group))" 12 | 13 | def __init__(self, input_string): 14 | if os.path.isfile(input_string): 15 | self.__result = list(self.from_file(input_string)) 16 | elif isinstance(input_string, str): 17 | self.__result = list(self.parse_nuke_script(input_string)) 18 | else: 19 | raise Exception("input_string should be either .nk file or string") 20 | 21 | @classmethod 22 | def parse_nuke_script(cls, script_text): 23 | """ Parse nuke script text, with regext pattern parses script text as nodes matches and 24 | then from node match it will try to match knobs and values. 25 | 26 | Args: 27 | script_text(str): 28 | Returns: 29 | list(dict): list of node details dict. 30 | """ 31 | # when copied from nuke some time it contains with \r will get rid of that, to make the pattern to work 32 | script_text = script_text.replace("\r", "") 33 | last_node_line_end = last_node_content_start = None 34 | node_data = [] 35 | node_matches = [m for m in re.finditer(cls.node_pattern, script_text, re.MULTILINE)] 36 | for i, match in enumerate(node_matches): 37 | node_class, node_content, stack_statement = match.groups() 38 | this_node_start = match.start() 39 | if node_data and last_node_line_end != this_node_start: 40 | last_node_correct_content = script_text[last_node_content_start:this_node_start] 41 | last = node_data.pop(-1) 42 | node_data.append((last[0], last_node_correct_content, last[2])) 43 | node_data.append((node_class, node_content, stack_statement)) 44 | last_node_line_end = match.end() 45 | last_node_content_start = match.start(2) 46 | 47 | for node_class, node_content, stack_statement in node_data: 48 | 49 | knobs, user_knobs, inputs = cls.parse_knob_script(node_content) 50 | 51 | type_ = var = stack_index = None 52 | if not knobs: 53 | match = re.match(cls.stack_command_pattern, stack_statement) 54 | if match: 55 | type_ = match.group(1) 56 | var = match.group(2) 57 | stack_index = match.group(3) 58 | node_class = None 59 | else: 60 | type_ = "node" 61 | clone_match = re.match(cls.clone_pattern, node_class) 62 | if clone_match: 63 | type_ = "clone" 64 | if clone_match.group(2): 65 | node_class = None 66 | var = clone_match.group(2) 67 | elif clone_match.group(3): 68 | node_class = clone_match.group(3) 69 | var = None 70 | 71 | yield { 72 | "type": type_, 73 | "class": node_class, 74 | "knobs": OrderedDict(knobs), 75 | "inputs": inputs, 76 | "user_knobs": user_knobs, 77 | "var": var, 78 | "stack_index": stack_index, 79 | "node_content": node_content 80 | } 81 | 82 | @classmethod 83 | def parse_knob_script(cls, node_content): 84 | knobs = [] 85 | inputs = "" 86 | user_knobs = [] 87 | last_knob = None 88 | last_knob_line_end = last_knob_value_start = None 89 | if node_content is None: 90 | node_content = "" 91 | for knob_match in re.finditer(cls.knob_pattern, node_content, re.MULTILINE): 92 | _spaces, knob_name, knob_value = knob_match.groups() 93 | this_knob_line_start = knob_match.start() 94 | 95 | if knob_name == "addUserKnob": 96 | user_knob_match = re.match(cls.user_knob_pattern, knob_value) 97 | knob_id = user_knob_name = "" 98 | if user_knob_match: 99 | knob_id, user_knob_name = user_knob_match.groups() 100 | user_knobs.append((user_knob_name, knob_id, knob_value)) 101 | else: 102 | if knobs: 103 | if last_knob_line_end + 1 != this_knob_line_start: 104 | last_knob_correct_value = node_content[last_knob_value_start:this_knob_line_start] 105 | knobs.pop(-1) 106 | knobs.append((last_knob, last_knob_correct_value)) 107 | else: 108 | # if first knob script is inputs then its note knob, but inputs number 109 | if knob_name == "inputs": 110 | inputs = knob_value 111 | continue 112 | 113 | knobs.append((knob_name, knob_value)) 114 | 115 | last_knob = knob_name 116 | last_knob_line_end = knob_match.end() 117 | last_knob_value_start = this_knob_line_start + len(_spaces) + len(knob_name) 118 | 119 | return knobs, user_knobs, inputs 120 | 121 | @classmethod 122 | def from_text(cls, text): 123 | return cls.parse_nuke_script(text) 124 | 125 | @classmethod 126 | def from_file(cls, file_path): 127 | if not os.path.exists(file_path): 128 | raise Exception("file {} not found".format(file_path)) 129 | 130 | with open(file_path, "r") as file_open: 131 | text = file_open.read() 132 | return cls.parse_nuke_script(text) 133 | 134 | -------------------------------------------------------------------------------- /nukery/constants.py: -------------------------------------------------------------------------------- 1 | """contents""" 2 | 3 | # if anything missing add here, 4 | # (Class, minimumInputs, maximumInputs, has maskChannelInput knob) 5 | NODE_DEFAULT_INPUTS = { 6 | 'AddSTMap': (1, 1, False), 7 | 'AppendClip': (1, 10000, False), 8 | 'AudioRead': (0, 0, False), 9 | 'Axis': (1, 1, False), 10 | 'Axis2': (2, 2, False), 11 | 'BackdropNode': (0, 0, False), 12 | 'Bezier': (2, 2, True), 13 | 'Black': (0, 0, False), 14 | 'Blur': (2, 2, True), 15 | 'BurnIn': (1, 1, False), 16 | 'CCorrect': (2, 2, True), 17 | 'Camera': (1, 1, False), 18 | 'CameraShake': (1, 1, False), 19 | 'CameraShake2': (1, 1, False), 20 | 'CameraShake3': (1, 1, False), 21 | 'Card': (1, 1, False), 22 | 'Card2': (1, 1, False), 23 | 'CardObj': (2, 2, False), 24 | 'ChannelMerge': (3, 3, True), 25 | 'Clamp': (2, 2, True), 26 | 'ColorCorrect': (2, 2, True), 27 | 'ColorMatrix': (2, 2, True), 28 | 'Colorspace': (2, 2, True), 29 | 'Constant': (0, 0, False), 30 | 'Copy': (3, 3, True), 31 | 'CopyBBox': (2, 2, False), 32 | 'Crop': (1, 1, False), 33 | 'Cube': (1, 1, False), 34 | 'CubeObj': (2, 2, False), 35 | 'Cylinder': (1, 1, False), 36 | 'CylinderObj': (2, 2, False), 37 | 'DeInterlace': (1, 1, False), 38 | 'DeepExpression': (1, 1, False), 39 | 'DegrainBlue': (1, 1, False), 40 | 'DepthToPoints': (3, 3, False), 41 | 'DepthToPosition': (2, 2, False), 42 | 'Dilate': (2, 2, True), 43 | 'Dissolve': (3, 10001, True), 44 | 'Dot': (1, 1, False), 45 | 'DrawCursorShaderOp': (2, 2, True), 46 | 'DropShadow': (2, 2, False), 47 | 'EdgeDetect': (1, 1, False), 48 | 'EdgeDetectWrapper': (1, 1, False), 49 | 'Erode': (2, 2, True), 50 | 'ErrorIop': (1, 1, False), 51 | 'Expression': (2, 2, True), 52 | 'FieldSelect': (1, 1, False), 53 | 'FilterErode': (2, 2, True), 54 | 'FnNukeMultiTypeOpDeepOp': (1, 1, False), 55 | 'FnNukeMultiTypeOpGeoOp': (1, 1, False), 56 | 'FnNukeMultiTypeOpIop': (1, 1, False), 57 | 'FnNukeMultiTypeOpParticleOp': (0, 0, False), 58 | 'FrameHold': (1, 1, False), 59 | 'FrameRange': (1, 1, False), 60 | 'GeoSelect': (1, 1, False), 61 | 'GodRays': (2, 2, True), 62 | 'Grade': (2, 2, True), 63 | 'Grain2': (1, 1, False), 64 | 'Group': (1, 1, False), 65 | 'Histogram': (2, 2, True), 66 | 'HydraViewer': (1, 10, False), 67 | 'IBK': (4, 4, False), 68 | 'IBKColourV3': (1, 1, False), 69 | 'IBKEdge': (1, 1, False), 70 | 'IBKGizmo': (2, 2, False), 71 | 'IBKGizmoV3': (3, 3, False), 72 | 'IBKSFill': (1, 1, False), 73 | 'IBKSplit': (3, 3, False), 74 | 'Input': (0, 0, False), 75 | 'Invert': (2, 2, True), 76 | 'JoinViews': (1, 1, False), 77 | 'Keyer': (1, 1, False), 78 | 'Light': (1, 1, False), 79 | 'LightWrap': (2, 2, False), 80 | 'LiveGroup': (0, 0, False), 81 | 'LiveInput': (0, 0, False), 82 | 'Matrix': (2, 2, True), 83 | 'Merge': (3, 3, True), 84 | 'Merge2': (3, 101, True), 85 | 'MergeExpression': (3, 3, True), 86 | 'MergeGeo': (1, 999, False), 87 | 'Multiply': (2, 2, True), 88 | 'MyGizmo': (1, 1, False), 89 | 'NoOp': (1, 1, False), 90 | 'NoProxy': (1, 1, False), 91 | 'NoTimeBlur': (1, 1, False), 92 | 'Noise': (2, 2, True), 93 | 'OCIOColorSpace': (2, 2, True), 94 | 'Output': (1, 1, False), 95 | 'PSDMerge': (2, 2, True), 96 | 'Paint': (4, 4, False), 97 | 'PanelNode': (0, 0, False), 98 | 'ParticleAttractToSphere': (1, 1, False), 99 | 'ParticleBlinkScript': (1, 1, False), 100 | 'ParticleCache': (1, 1, False), 101 | 'ParticleColorByAge': (1, 1, False), 102 | 'ParticleConstrainToSphere': (1, 1, False), 103 | 'ParticleCylinderFlow': (1, 1, False), 104 | 'ParticleDirection': (1, 1, False), 105 | 'ParticleDistributeSphere': (1, 1, False), 106 | 'ParticleFlock': (1, 1, False), 107 | 'ParticleFuse': (1, 1, False), 108 | 'ParticleGrid': (1, 1, False), 109 | 'ParticleHelixFlow': (1, 1, False), 110 | 'ParticleKill': (1, 1, False), 111 | 'ParticleProjectDisplace': (2, 2, False), 112 | 'ParticleProjectImage': (2, 2, False), 113 | 'ParticleRender': (1, 1, False), 114 | 'ParticleShockWave': (1, 1, False), 115 | 'ParticleToGeo': (1, 1, False), 116 | 'PositionToPoints2': (1, 3, False), 117 | 'PostageStamp': (1, 1, False), 118 | 'Precomp': (0, 0, False), 119 | 'Preferences': (0, 0, False), 120 | 'Premult': (1, 1, False), 121 | 'Profile': (1, 1, False), 122 | 'Radial': (2, 2, True), 123 | 'ReConverge': (1, 1, False), 124 | 'Read': (0, 0, False), 125 | 'ReadGeo': (1, 1, False), 126 | 'Rectangle': (2, 2, True), 127 | 'Reformat': (1, 1, False), 128 | 'Remove': (1, 1, False), 129 | 'Retime': (1, 1, False), 130 | 'Roto': (2, 5, True), 131 | 'RotoPaint': (2, 5, True), 132 | 'Saturation': (2, 2, True), 133 | 'Scene': (1, 999, False), 134 | 'Shuffle': (1, 1, False), 135 | 'Shuffle1': (1, 1, False), 136 | 'Shuffle2': (1, 2, False), 137 | 'ShuffleCopy': (2, 2, False), 138 | 'Sphere': (1, 1, False), 139 | 'SphereObj': (2, 2, False), 140 | 'SplineWarp3': (3, 3, False), 141 | 'StabTrack': (1, 1, False), 142 | 'StickyNote': (0, 0, False), 143 | 'Switch': (2, 10000, False), 144 | 'Text2': (2, 2, True), 145 | 'TextureMap': (1, 1, False), 146 | 'TextureSampler': (1, 1, False), 147 | 'TimeBlur': (1, 1, False), 148 | 'TimeClip': (1, 1, False), 149 | 'TimeOffset': (1, 1, False), 150 | 'TimeWarp': (1, 1, False), 151 | 'Tracker': (1, 1, False), 152 | 'Tracker4': (1, 1, False), 153 | 'Transform': (1, 1, False), 154 | 'TransformGeo': (3, 3, False), 155 | 'TransformMasked': (2, 2, True), 156 | 'Unpremult': (1, 1, False), 157 | 'VectorBlur': (2, 2, True), 158 | 'VectorToMotion': (1, 1, False), 159 | 'ViewMetaData': (1, 1, False), 160 | 'Viewer': (1, 10000, False), 161 | 'ViewerCaptureOp': (1, 1, False), 162 | 'ViewerChannelSelector': (1, 1, False), 163 | 'ViewerClipTest': (2, 2, False), 164 | 'ViewerDitherDisable': (1, 1, False), 165 | 'ViewerDitherHighFrequency': (1, 1, False), 166 | 'ViewerDitherLowFrequency': (1, 1, False), 167 | 'ViewerGain': (1, 1, False), 168 | 'ViewerGamma': (1, 1, False), 169 | 'ViewerInterlacedStereo': (1, 1, False), 170 | 'ViewerLUT': (1, 1, False), 171 | 'ViewerProcess_1DLUT': (1, 1, False), 172 | 'ViewerProcess_None': (1, 1, False), 173 | 'ViewerScopeOp': (1, 1, False), 174 | 'ViewerWipe': (2, 2, False), 175 | 'VolumeRays': (2, 2, False), 176 | 'Write': (1, 1, False), 177 | 'WriteGeo': (1, 1, False), 178 | 'add32p': (1, 1, False), 179 | 'objReaderObj': (2, 2, False), 180 | 'remove32p': (1, 1, False) 181 | } 182 | 183 | NODE_SCRIPT_FORMAT = """{0} {{ 184 | {1} 185 | }}""" 186 | 187 | CLONE_KNOBS = ("xpos", "ypos", "selected") 188 | -------------------------------------------------------------------------------- /nukery/_nuke.py: -------------------------------------------------------------------------------- 1 | import platform 2 | import subprocess 3 | 4 | from nukery.parser import NukeScriptParser 5 | from nukery.store import SessionStore, NodeStore 6 | from nukery._base import Node 7 | 8 | 9 | def script_open(file_path): 10 | """ Open file in to the session. 11 | 12 | Args: 13 | file_path(str): nuke file path. 14 | 15 | """ 16 | if SessionStore.has_value(): 17 | raise Exception("Script already open") 18 | 19 | for node_data in NukeScriptParser.from_file(file_path): 20 | NodeStore(**node_data) 21 | 22 | 23 | def script_clear(): 24 | """ Clear script""" 25 | SessionStore.clear() # TOOD test 26 | 27 | 28 | def all_nodes(filter_=None, group=None, recursive=False): 29 | """ Get all nodes from the session. 30 | 31 | Args: 32 | filter_: filter by class 33 | group: name of the group, root.Group1 34 | recursive: if true return node recursively form child groups 35 | 36 | Returns: 37 | list(Nodes) : Return list of nodes. 38 | """ 39 | nodes = [] 40 | if group and not group.startswith("root"): 41 | group = "root." + group 42 | parent = group if group else NodeStore.get_current_parent() 43 | parent_keys = [parent] 44 | if recursive: 45 | parent_keys = [k for k in SessionStore.get_current().keys() if k.startswith(parent)] 46 | for parent_key in parent_keys: 47 | for node_store in SessionStore.get_current()[parent_key]: 48 | if node_store.node_class == "Root": 49 | continue 50 | if filter_ and node_store.node_class != filter_: 51 | continue 52 | node = Node(node_store=node_store) 53 | nodes.append(node) 54 | return nodes 55 | 56 | 57 | def to_node(name): 58 | """ Get node object by name, node will search in the context and the session 59 | """ 60 | node_store = NodeStore.get_by_name(name) 61 | if node_store: 62 | return Node(node_store=node_store) 63 | 64 | 65 | def select_all(): 66 | """ Set all nodes to selected. 67 | """ 68 | for node_store in SessionStore.get_current()[NodeStore.get_current_parent()]: 69 | if node_store.node_class == "Root": 70 | continue 71 | node = Node(node_store=node_store) 72 | if node and node["selected"] not in (True, "true"): 73 | node.set_selected(True) 74 | 75 | 76 | def selected_nodes(): 77 | """ Get selected nodes from the session and the context. 78 | 79 | Returns: 80 | list: list of Nodes 81 | """ 82 | selected = [] 83 | for node_store in SessionStore.get_current()[NodeStore.get_current_parent()]: 84 | node = Node(node_store=node_store) 85 | if node and node["selected"] in (True, "true"): 86 | selected.append(node) 87 | return selected 88 | 89 | 90 | def selected_node(): 91 | """Select last selected node(this last by store session, 92 | does not promise returning node that last selected 93 | """ 94 | for node_store in reversed(SessionStore.get_current()[NodeStore.get_current_parent()]): 95 | node = Node(node_store=node_store) 96 | if node and node["selected"] in (True, "true"): 97 | return node 98 | 99 | 100 | def clear_selection(): 101 | """ Clear current selection """ 102 | for node in selected_nodes(): 103 | node.set_selected(False) 104 | 105 | 106 | def save_script_as(file_path): 107 | """ Save current session to a file.""" 108 | script = SessionStore.build_script("root") 109 | with open(file_path, "w") as f: 110 | f.write(script) 111 | 112 | return True 113 | 114 | 115 | def delete(node): 116 | """Delete a node """ 117 | node.node_store.delete() 118 | 119 | 120 | def get_script_text(selected=False): 121 | """ Returns script text. 122 | 123 | Args: 124 | selected(bool): if true then script text for selected node will be returned 125 | """ 126 | node_stores = [] 127 | current_nodes = SessionStore.get_current()[NodeStore.get_current_parent()] 128 | if selected: 129 | for node_store in current_nodes: 130 | if node_store.type in ("node", "clone"): 131 | if node_store.knobs.get("selected", "false") == "false": 132 | continue 133 | node_stores.append(node_store) 134 | else: 135 | node_stores = current_nodes.copy() 136 | 137 | return SessionStore.build_script_from_list(node_stores) 138 | 139 | 140 | def node_copy(file_name=None): 141 | """Save selected node to file or clipboard 142 | 143 | Args: 144 | file_name(str): file name to save, if None the script text will be copied to clipboard 145 | """ 146 | 147 | if not selected_node(): 148 | raise Exception("No node selected") 149 | 150 | copy_script = get_script_text(selected=True) 151 | if file_name is None: 152 | system_os = platform.system() 153 | if system_os == 'Windows': 154 | cmd = 'clip' 155 | elif system_os == 'Darwin': 156 | cmd = 'pbcopy' 157 | else: 158 | cmd = ['xclip', '-selection', 'c'] 159 | process = subprocess.Popen( 160 | cmd, 161 | stdin=subprocess.PIPE, 162 | close_fds=True 163 | ) 164 | res, error = process.communicate(input=copy_script.encode('utf-8')) 165 | if error: 166 | raise Exception("Error copying to clipboard") 167 | else: 168 | with open(file_name, "w") as f: # TODO test 169 | f.write(copy_script) 170 | 171 | return True 172 | 173 | 174 | def node_paste(file_name=None): 175 | """Paste node from file or clipboard 176 | 177 | Args: 178 | file_name(str): file path to import node from, 179 | if this is None then will try to import form clipboard 180 | """ 181 | if file_name is None: 182 | system_os = platform.system() 183 | # windows 184 | if system_os == 'Windows': 185 | cmd = ['powershell', 'Get-Clipboard'] 186 | elif system_os == 'Darwin': 187 | cmd = ['pbpaste'] 188 | else: 189 | cmd = ['xclip', '-selection', 'clipboard', '-out', '-nonewline'] 190 | 191 | process = subprocess.Popen(cmd, stdout=subprocess.PIPE) 192 | result, error = process.communicate() 193 | if error: 194 | raise Exception("Error copying to clipboard") 195 | 196 | script = result.decode('utf-8') 197 | 198 | else: 199 | with open(file_name, "r") as f: 200 | script = f.read() 201 | 202 | nodes = [] 203 | for node_data in NukeScriptParser.from_text(script): 204 | node_store = NodeStore(**node_data) 205 | nodes.append(Node(node_store=node_store)) 206 | 207 | return nodes 208 | 209 | 210 | def create_node(node_class, **kwargs): 211 | """Create a node 212 | 213 | Args: 214 | node_class(str): class for the node to create 215 | Keyword Args: 216 | knobs can be initialized by passing as key word args 217 | """ 218 | _selected = selected_node() 219 | _selected = selected_node().node_store if _selected else None 220 | clear_selection() # TODO check if this is time taking 221 | last_node = _selected 222 | inputs = "" 223 | current_stack = SessionStore.get_current_stack()[NodeStore.get_current_parent()] 224 | # if no selected nodes then we need node from stack to set x,y pos. 225 | if not _selected: 226 | last_node = current_stack[0] if current_stack else None 227 | inputs = "0" 228 | if last_node: 229 | if not kwargs.get("ypos"): 230 | kwargs["ypos"] = str(int(last_node.knobs.get("ypos", "0")) + 50) 231 | 232 | if not kwargs.get("xpos"): 233 | kwargs["xpos"] = last_node.knobs.get("xpos", "0") 234 | else: 235 | if not kwargs.get("ypos"): 236 | kwargs["ypos"] = "0" 237 | 238 | if not kwargs.get("xpos"): 239 | kwargs["xpos"] = "0" 240 | 241 | # if selected node is not in the current stack add it to the stack. 242 | if _selected and current_stack and _selected != current_stack[0]: 243 | current_stack.insert(0, _selected) 244 | 245 | if not node_class == "Root": 246 | kwargs["selected"] = kwargs.get("selected", "true") 247 | 248 | stack_data = { 249 | "type": "node", 250 | "class": node_class, 251 | "knobs": kwargs, 252 | "inputs": inputs, 253 | 254 | } 255 | node_store = NodeStore(**stack_data) 256 | node = Node(node_store=node_store) 257 | if node_store.is_group: 258 | inputs_node = { 259 | "type": "node", 260 | "class": "Input", 261 | "knobs": { 262 | "xpos": "0", 263 | }, 264 | "inputs": "0", 265 | } 266 | NodeStore(**inputs_node) 267 | outputs_node = { 268 | "type": "node", 269 | "class": "Output", 270 | "knobs": { 271 | "xpos": "0", 272 | "ypos": "300" 273 | }, 274 | "inputs": "", 275 | } 276 | NodeStore(**outputs_node) 277 | end_group = { 278 | "type": "end_group", 279 | "class": "", 280 | } 281 | NodeStore(**end_group) 282 | 283 | return node 284 | 285 | 286 | def root(): 287 | """Get Root nodes.""" 288 | 289 | node_store = NodeStore.get_by_class("Root") 290 | if node_store: 291 | return Node(node_store=node_store) 292 | else: 293 | current_parent = NodeStore.get_current_parent() 294 | NodeStore.set_current_parent("root") 295 | node = create_node("Root") 296 | NodeStore.set_current_parent(current_parent) 297 | return node 298 | -------------------------------------------------------------------------------- /nukery/store.py: -------------------------------------------------------------------------------- 1 | import re 2 | import random 3 | from collections import defaultdict, OrderedDict 4 | from copy import deepcopy 5 | 6 | from nukery.constants import NODE_DEFAULT_INPUTS, NODE_SCRIPT_FORMAT, CLONE_KNOBS 7 | 8 | 9 | class SessionStore(object): 10 | """""" 11 | __default__ = "__default__" 12 | __sessions = {__default__: defaultdict(list)} 13 | __variable = {__default__: {}} 14 | _current_session = __default__ 15 | __stack = {__default__: defaultdict(list)} 16 | 17 | def __init__(self, session): 18 | self.session = session 19 | if self.session not in self.__sessions.keys(): 20 | self.__class__.__sessions[self.session] = defaultdict(list) 21 | self.__class__.__variable[self.session] = {} 22 | self.__class__.__stack[self.session] = defaultdict(list) 23 | 24 | @classmethod 25 | def append(cls, item): 26 | cls.get_current()[item.parent].append(item) 27 | 28 | @classmethod 29 | def remove(cls, item): 30 | cls.get_current()[item.parent].remove(item) 31 | if item in cls.get_current_stack()[item.parent]: 32 | cls.get_current_stack()[item.parent].remove(item) 33 | 34 | @classmethod 35 | def clear(cls): 36 | cls.get_current().clear() 37 | cls.get_current_stack().clear() 38 | cls.__variable[cls._current_session].clear() 39 | 40 | 41 | @classmethod 42 | def has_value(cls): 43 | return any(s for s in cls.get_current().values()) 44 | 45 | @classmethod 46 | def set_variable(cls, var, item): 47 | cls.__variable[cls._current_session][var] = item 48 | 49 | @classmethod 50 | def get_variable(cls, var): 51 | if var in ("0", 0): 52 | return None 53 | return cls.__variable[cls._current_session][var] 54 | 55 | @classmethod 56 | def get_current(cls): 57 | return cls.__sessions[cls._current_session] 58 | 59 | @classmethod 60 | def get_current_stack(cls): 61 | return cls.__stack[cls._current_session] 62 | 63 | @classmethod 64 | def set_current(cls, session): 65 | cls._current_session = session 66 | 67 | @classmethod 68 | def add_to_stack(cls, item): 69 | parent = item.parent if item else NodeStore.get_current_parent() 70 | cls.get_current_stack()[parent].insert(0, item) 71 | 72 | @classmethod 73 | def pop_from_stack(cls, parent): 74 | stack = cls.get_current_stack()[parent] 75 | return stack.pop(0) if stack else None 76 | 77 | @classmethod 78 | def build_script(cls, parent=None): 79 | if parent is None: 80 | parent = NodeStore.get_current_parent() 81 | return cls.__build_script(cls.get_current()[parent]) 82 | 83 | @classmethod 84 | def build_script_from_list(cls, stack_list): 85 | return cls.__build_script(stack_list) 86 | 87 | @classmethod 88 | def __build_script(cls, node_store_list): 89 | """Build script text from node_store list. 90 | """ 91 | _instance_copy = [] 92 | _all = [] 93 | _duplicates = {} 94 | _clones = {} 95 | _variables = defaultdict(str) 96 | _add_layers = {} 97 | _end_group = None 98 | _root_item = None 99 | _tail_item = [] 100 | for var, item in cls.__variable[cls._current_session].items(): 101 | _variables[item] = var 102 | _stacks = [] 103 | for item in node_store_list: 104 | if item.node_class == "Root": 105 | _root_item = item 106 | continue 107 | out_len = len([o for o in item.outputs if o in node_store_list]) 108 | _duplicates[item] = out_len 109 | if out_len == 0: 110 | _tail_item.append(item) 111 | 112 | if item.type == "clone": 113 | if item.variable not in _clones: 114 | _clones[item.variable] = [SessionStore.get_variable(item.variable)] 115 | _clones[item.variable].append(item) 116 | _sort_order = {"BackdropNode": 3, "StickyNote": 2} 117 | _tail_item = sorted(_tail_item, key=lambda x: _sort_order.get(x.node_class, 1)) 118 | last_item = _tail_item.pop(0) 119 | _new_stacks_list = [] 120 | 121 | while last_item is not False: 122 | push_item = None 123 | if last_item is None: 124 | push_item = "push 0" 125 | _new_stacks_list.insert(0, push_item) 126 | elif _duplicates[last_item] > 1: 127 | var = _variables[last_item] 128 | if not var: 129 | var = _variables[last_item] = cls.get_random_variable_name() 130 | push_item = "push ${0}".format(var) 131 | _new_stacks_list.insert(0, push_item) 132 | if _duplicates[last_item] == 2: 133 | _duplicates[last_item] = -1 134 | 135 | else: 136 | _duplicates[last_item] -= 1 137 | else: 138 | if _duplicates[last_item] == -1: 139 | var = _variables[last_item] 140 | set_item = "set {0} [stack 0]".format(var) 141 | _new_stacks_list.insert(0, set_item) 142 | 143 | if last_item.variable and last_item in _clones[last_item.variable]: 144 | _clones[last_item.variable].remove(last_item) 145 | if not _clones[last_item.variable]: 146 | # there are not more clone then this will be base for other clones 147 | set_item = "set {0} [stack 0]".format(last_item.variable) 148 | _new_stacks_list.insert(0, set_item) 149 | _new_stacks_list.insert(0, last_item.to_script()) 150 | else: 151 | # there are more clones so this has to be clone script 152 | _new_stacks_list.insert(0, last_item.to_script(as_clone=True)) 153 | else: 154 | _new_stacks_list.insert(0, last_item.to_script()) 155 | 156 | if push_item is None and last_item: 157 | _inputs = last_item.inputs 158 | for _input in _inputs: 159 | if _input not in node_store_list: 160 | _index = _inputs.index(_input) 161 | _inputs.remove(_input) 162 | _inputs.insert(_index, None) 163 | _stacks = _inputs + _stacks 164 | 165 | if _stacks: 166 | last_item = _stacks.pop(0) 167 | elif _tail_item: 168 | last_item = _tail_item.pop(0) 169 | else: 170 | last_item = False 171 | 172 | if _root_item: 173 | _new_stacks_list.insert(0, _root_item.to_script()) 174 | 175 | return "\n".join(_new_stacks_list) 176 | 177 | @classmethod 178 | def get_random_variable_name(cls, prefix="N"): 179 | var = prefix + ''.join(random.choice("0123456789abcdef") for _ in range(8)) 180 | 181 | if var in cls.__variable[cls._current_session].keys(): 182 | return cls.get_random_variable_name(prefix) 183 | else: 184 | return var 185 | 186 | def __enter__(self): 187 | self.__class__._current_session = self.session 188 | return self 189 | 190 | def __exit__(self, exc_type, exc_val, exc_tb): 191 | self.__class__._current_session = self.__default__ 192 | 193 | def __del__(self): 194 | self.__class__.__sessions[self.session] = defaultdict(list) 195 | self.__class__.__variable[self.session] = {} 196 | self.__class__.__stack[self.session] = defaultdict(list) 197 | 198 | 199 | class NodeStore(object): 200 | # stack = defaultdict(list) # needs session. 201 | _current_parent = "root" 202 | __add_layer = None 203 | _name_pattern = re.compile("^(.*?)(\d+)?$") 204 | 205 | def __init__(self, **kwargs): 206 | 207 | self.type = kwargs.get("type") 208 | self._node_class = kwargs.get("class") 209 | self.knobs = kwargs.get("knobs", {}) 210 | self.user_knobs = kwargs.get("user_knobs", []) 211 | self.variable = kwargs.get("var", "") 212 | self.stack_index = kwargs.get("stack_index", "0") 213 | self.node_content = kwargs.get("node_content", "") 214 | input_script = kwargs.get("inputs") 215 | self.parent = self.get_current_parent() 216 | 217 | self.inputs = [] 218 | self.outputs = [] 219 | self._add_layer = None 220 | 221 | if self.type == "node" and self.knobs.get("name") is None: 222 | self.knobs["name"] = "{0}1".format(self.node_class) 223 | 224 | # if node name exists in this context increment the name suffix 225 | if self.type == "node" and self.get_by_name(self.name): 226 | name, _ = self._name_pattern.match(self.name).groups() 227 | node_numbers = set() 228 | 229 | for item in SessionStore.get_current()[self.parent]: 230 | item_name = item.name 231 | match = self._name_pattern.match(item_name) 232 | _name, _number = match.groups() if match else (None, None) 233 | if _number is not None and _name == name: 234 | node_numbers.add(int(_number)) 235 | 236 | number_range = set(range(1, max(node_numbers) + 2)) 237 | missing = number_range - node_numbers 238 | number = min(missing) 239 | self.knobs["name"] = "{0}{1}".format(name, number) 240 | 241 | if self.type in ("node", "clone"): 242 | if input_script == "": 243 | # if there is no inputs script then it mean default input is connected. 244 | input_count = 1 245 | elif re.match(r"^[\d]+(?:(?:[\+\s\d]+)$)?", input_script): 246 | input_count = eval(input_script) 247 | else: 248 | raise Exception("input number {0} is unknown, " 249 | "please report it to developer".format(input_script)) 250 | for i in range(input_count): 251 | item = SessionStore.pop_from_stack(self.parent) 252 | self.set_input(i, item) 253 | 254 | SessionStore.append(self) 255 | SessionStore.add_to_stack(self) 256 | if self.is_group: 257 | self.join_to_parent(self.name) 258 | 259 | if self.type == "set": 260 | if self.parent in SessionStore.get_current_stack(): 261 | stack_item = SessionStore.get_current_stack()[self.parent][int(self.stack_index)] 262 | else: 263 | stack_item = None 264 | 265 | if self.variable.startswith("C"): 266 | stack_item.variable = self.variable 267 | SessionStore.set_variable(self.variable, stack_item) 268 | 269 | if self.type == "push": 270 | SessionStore.add_to_stack(SessionStore.get_variable(self.variable)) 271 | if self.type == "end_group": 272 | self.un_join_last_child() 273 | 274 | if self.__class__.__add_layer: 275 | if self.type == "clone": 276 | original = SessionStore.get_variable(self.variable) 277 | original.add_layer = self.__class__.__add_layer 278 | else: 279 | self._add_layer = self.__class__.__add_layer 280 | self.__class__.__add_layer = None 281 | 282 | if self.type == "add_layer": 283 | self.__class__.__add_layer = self.variable 284 | 285 | def to_script(self, as_clone=False): 286 | """ Get script text of the node, if as clone is True then it will only return clone like text 287 | 288 | Args: 289 | as_clone(bool): if True it will only return clone like text 290 | 291 | Returns: 292 | str: script text of the node. 293 | """ 294 | if self.is_group: 295 | if as_clone: 296 | raise Exception("Clone is not supported with group nodes.") 297 | node_scripts = [self._get_node_script()] 298 | nodes_stores = SessionStore.get_current()["{}.{}".format(self.parent, self.name)] 299 | node_scripts.append(SessionStore.build_script_from_list(nodes_stores)) 300 | node_scripts.append("end_group") 301 | return "\n".join(node_scripts) 302 | else: 303 | if as_clone: 304 | node_script = self._get_clone_script() 305 | else: 306 | node_script = self._get_node_script() 307 | if self.add_layer: 308 | node_script = "add_layer {0}\n{1}".format(self.add_layer, node_script) 309 | 310 | return node_script 311 | 312 | def _get_clone_script(self): 313 | knob_line_format = "{0} {1}" 314 | knob_lines = [] 315 | if self.input_script != "": 316 | knob_lines.append("inputs " + self.input_script) 317 | for name, value in self.knobs.items(): 318 | if name not in CLONE_KNOBS: 319 | continue 320 | knob_line = knob_line_format.format(name, value) 321 | 322 | knob_lines.append(knob_line) 323 | 324 | knob_line_script = "\n ".join(knob_lines) 325 | if not self.variable: 326 | raise Exception("Clone variable is not defined for {0}".format(self.name)) 327 | class_text = "clone ${}".format(self.variable) 328 | return NODE_SCRIPT_FORMAT.format(class_text, knob_line_script) 329 | 330 | def _get_node_script(self): 331 | knob_line_format = "{0} {1}" 332 | knob_lines = [] 333 | if self.input_script != "": 334 | knob_lines.append("inputs " + self.input_script) 335 | user_knobs = OrderedDict() 336 | for n, _id, v in self.user_knobs: 337 | user_knobs[n] = v 338 | user_knob_value = {} 339 | if self.type == "clone": 340 | original = SessionStore.get_variable(self.variable) 341 | knobs = deepcopy(original.knobs) 342 | knobs.update(deepcopy(self.knobs)) 343 | else: 344 | knobs = self.knobs 345 | for name, value in knobs.items(): 346 | knob_line = knob_line_format.format(name, value) 347 | if name in user_knobs.keys(): 348 | user_knob_value[name] = value 349 | continue 350 | knob_lines.append(knob_line) 351 | for name, value in user_knobs.items(): 352 | knob_lines.append("addUserKnob " + value) 353 | if name in user_knob_value.keys(): 354 | knob_lines.append(knob_line_format.format(name, user_knob_value[name])) 355 | knob_line_script = "\n ".join(knob_lines) 356 | return NODE_SCRIPT_FORMAT.format(self.node_class, knob_line_script) 357 | 358 | def delete(self): 359 | for out in self.outputs: 360 | input0 = self.inputs[0] if self.inputs else None 361 | index = out.inputs.index(self) 362 | out.set_input(index, input0) 363 | 364 | for input_ in self.inputs: 365 | if input_: 366 | input_.remove_output(self) 367 | 368 | self.inputs = [] 369 | self.outputs = [] 370 | 371 | SessionStore.remove(self) 372 | 373 | def set_input(self, index, item): 374 | # check if input item is dependent on this node 375 | dependants = [self] 376 | while dependants: 377 | dependant = dependants.pop() 378 | if dependant == item: 379 | raise Exception("Node {0} is dependent on {1}".format(item.name, self.name)) 380 | dependants.extend(dependant.outputs) 381 | 382 | input_exists = index < len(self.inputs) 383 | if input_exists: 384 | self.inputs.pop(index) 385 | else: 386 | for i in range(len(self.inputs), index): 387 | self.inputs.insert(i, None) 388 | self.inputs.insert(index, item) 389 | 390 | # if it is None and if last items are none then remove those. 391 | if item is None and input_exists: 392 | for i in range(len(self.inputs)-1, -1, -1): 393 | if self.inputs[i] is None: 394 | self.inputs.pop(i) 395 | else: 396 | break 397 | if item: 398 | item.add_output(self) 399 | 400 | def unset_input(self, index): 401 | item = self.inputs.pop(index) 402 | # if there are inputs after index then this should be set to None 403 | if len(self.inputs) > index: 404 | self.inputs.insert(index, None) 405 | # if all of next items are none then remove those. 406 | if all((n is None for n in self.inputs[index:])): 407 | self.inputs = self.inputs[:index] 408 | if item: 409 | item.remove_output(self) 410 | 411 | def add_output(self, item): 412 | if item not in self.outputs: 413 | self.outputs.append(item) 414 | 415 | def remove_output(self, item): 416 | if item in self.outputs: 417 | self.outputs.remove(item) 418 | 419 | @property 420 | def name(self): 421 | if self.type == "clone": 422 | original = SessionStore.get_variable(self.variable) 423 | return original.name 424 | elif self.type != "node": 425 | return self.type.title() 426 | 427 | return self.knobs.get("name") 428 | 429 | @property 430 | def node_class(self): 431 | if self.type == "clone": 432 | original = SessionStore.get_variable(self.variable) 433 | return original.node_class 434 | 435 | return self._node_class 436 | 437 | @property 438 | def add_layer(self): 439 | if self.type == "clone": 440 | original = SessionStore.get_variable(self.variable) 441 | return original.add_layer 442 | return self._add_layer 443 | 444 | @property 445 | def input_script(self): 446 | min_, max_, has_mask = NODE_DEFAULT_INPUTS.get(self.node_class, (0, 0, False)) 447 | input_len = len(self.inputs) 448 | if has_mask and input_len >= min_: 449 | return "{0}+1".format(input_len - 1) 450 | elif input_len == 1: 451 | return "" 452 | else: 453 | return str(input_len) 454 | 455 | @property 456 | def is_group(self): 457 | return self.node_class == "Group" or \ 458 | (self.node_class == "LiveGroup" and self.knobs.get("published", "false") == "false") 459 | 460 | @classmethod 461 | def get_by_name(cls, name): 462 | 463 | if "." in name: 464 | parent = ".".join(name.split(".")[:-1]) 465 | name = name.split(".")[-1] 466 | else: 467 | if name == "root": # TODO not working 468 | parent = "root" 469 | else: 470 | parent = cls._current_parent 471 | 472 | if parent not in SessionStore.get_current(): 473 | return None 474 | 475 | return next((item for item in SessionStore.get_current()[parent] if item.name == name and item.type == "node"), None) 476 | 477 | @classmethod 478 | def get_by_class(cls, class_): 479 | parent = NodeStore.get_current_parent() 480 | 481 | if parent not in SessionStore.get_current(): 482 | return None 483 | 484 | return next((item for item in SessionStore.get_current()[parent] if item.node_class == class_), None) 485 | 486 | @classmethod 487 | def join_to_parent(cls, child): 488 | cls._current_parent += "." + child 489 | 490 | @classmethod 491 | def un_join_last_child(cls): 492 | cls._current_parent = ".".join(cls._current_parent.split(".")[:-1]) 493 | 494 | @classmethod 495 | def set_current_parent(cls, parent): 496 | cls._current_parent = parent 497 | 498 | @classmethod 499 | def get_current_parent(cls): 500 | return cls._current_parent 501 | 502 | def __eq__(self, other): 503 | if other is None: 504 | return False 505 | return (other.name, other.parent) == (self.name, self.parent) 506 | 507 | def __hash__(self): 508 | if self.type == "node": 509 | return hash((self.parent, self.name)) 510 | else: 511 | return hash(id(self)) 512 | 513 | def __repr__(self): 514 | name = self.name if self.name else self.type 515 | return "".format(name, hex(id(self))) 516 | -------------------------------------------------------------------------------- /tests/files/test_file.nk: -------------------------------------------------------------------------------- 1 | #! C:/Program Files/Nuke13.2v8/nuke-13.2.8.dll -nx 2 | version 13.2 v8 3 | define_window_layout_xml { 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | } 34 | Root { 35 | inputs 0 36 | name C:/Projects/nukery/tests/files/test_file.nk 37 | frame 19 38 | colorManagement Nuke 39 | workingSpaceLUT linear 40 | monitorLut sRGB 41 | monitorOutLUT rec709 42 | int8Lut sRGB 43 | int16Lut sRGB 44 | logLut Cineon 45 | floatLut linear 46 | } 47 | ColorWheel { 48 | inputs 0 49 | gamma 0.45 50 | name ColorWheel1 51 | xpos 271 52 | ypos -304 53 | } 54 | OFXuk.co.thefoundry.keylight.keylight_v201 { 55 | show "Final Result" 56 | unPreMultiply false 57 | screenColour {0.003951878287 0.0001788787049 1} 58 | screenGain 2 59 | screenBalance 0.95 60 | alphaBias {0.5 0.5 0.5} 61 | despillBias {0.5 0.5 0.5} 62 | gangBiases true 63 | preBlur 0 64 | "Screen Matte" 0 65 | screenClipMin 0 66 | screenClipMax 1 67 | screenClipRollback 0 68 | screenGrowShrink 0 69 | screenSoftness 0 70 | screenDespotBlack 0 71 | screenDespotWhite 0 72 | screenReplaceMethod "Soft Colour" 73 | screenReplaceColour {0.5 0.5 0.5} 74 | Tuning 0 75 | midPoint 0.5 76 | lowGain 1 77 | midGain 1 78 | highGain 1 79 | "Inside Mask" 0 80 | sourceAlphaHandling Ignore 81 | insideReplaceMethod "Soft Colour" 82 | insideReplaceColour {0.5 0.5 0.5} 83 | Crops 0 84 | SourceXMethod Colour 85 | SourceYMethod Colour 86 | SourceEdgeColour 0 87 | SourceCropL 0 88 | SourceCropR 1 89 | SourceCropB 0 90 | SourceCropT 1 91 | balanceSet false 92 | insideComponent None 93 | outsideComponent None 94 | cacheBreaker true 95 | name Keylight1 96 | xpos 271 97 | ypos -141 98 | } 99 | set N4f264c00 [stack 0] 100 | push 0 101 | Grade { 102 | inputs 1+1 103 | name Grade9 104 | xpos 490 105 | ypos -141 106 | } 107 | Grade { 108 | name Grade10 109 | xpos 490 110 | ypos -115 111 | } 112 | Grade { 113 | name Grade11 114 | xpos 490 115 | ypos -89 116 | } 117 | ColorBars { 118 | inputs 0 119 | name ColorBars1 120 | xpos 74 121 | ypos -282 122 | } 123 | Primatte3 { 124 | data { 5 125 | 0 48191 0 126 | 65552 127 | 0 5 128 | 30234 30234 30234 0 129 | 1 1 48672.9 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 130 | 130560 130560 130560 130560 47277.7 130560 130560 130560 130560 130560 130560 130560 130560 67470.8 51106.6 130560 130560 60083.8 130560 130560 130560 130560 130560 130560 130560 130560 130560 130560 130560 130560 130560 130560 130560 130560 130560 39549.5 130560 130560 130560 130560 130560 49089.6 130560 130560 130560 130560 130560 130560 130560 130560 130560 130560 130560 130560 130560 130560 130560 130560 130560 130560 130560 130560 130560 53720.3 38357.6 43639.6 131 | 130560 130560 130560 130560 47277.7 130560 130560 130560 130560 130560 130560 130560 130560 67470.8 51106.6 130560 130560 60083.8 130560 130560 130560 130560 130560 130560 130560 130560 130560 130560 130560 130560 130560 130560 130560 130560 130560 39549.5 130560 130560 130560 130560 130560 49089.6 130560 130560 130560 130560 130560 130560 130560 130560 130560 130560 130560 130560 130560 130560 130560 130560 130560 130560 130560 130560 130560 53720.3 38357.6 43639.6 132 | -0.0996094 -8.05892e+13 -8.05892e+13 4819.1 8.2146e+13 8.2146e+13 133 | -0.0996094 -2.44122e+13 -2.44122e+13 4819.1 2.48838e+13 2.48838e+13 134 | -0.100098 -2.22551e+14 -2.22551e+14 -4819.1 -8.2146e+13 -8.2146e+13 135 | -0.100098 -2.22551e+14 -2.22551e+14 -4819.1 -8.2146e+13 -8.2146e+13 136 | -0.0996094 -3.03009e+13 -3.03009e+13 4819.1 3.08863e+13 3.08863e+13 137 | -0.0996094 -9.17882e+12 -9.17882e+12 4819.1 9.35613e+12 9.35613e+12 138 | -0.100098 -2.22551e+14 -2.22551e+14 -4819.1 -8.2146e+13 -8.2146e+13 139 | -0.100098 -2.22551e+14 -2.22551e+14 -4819.1 -8.2146e+13 -8.2146e+13 140 | -0.182617 -4.06321e+14 -4.06321e+14 4752.75 8.1015e+13 8.1015e+13 141 | -0.182617 -4.06321e+14 -4.06321e+14 4752.75 8.1015e+13 8.1015e+13 142 | -0.182617 -4.06321e+14 -4.06321e+14 -4752.75 -8.1015e+13 -8.1015e+13 143 | -0.182617 -4.06321e+14 -4.06321e+14 -4752.75 -8.1015e+13 -8.1015e+13 144 | -0.182617 -1.86989e+14 -1.86989e+14 4752.75 2.0902e+14 2.0902e+14 145 | -0.182617 -5.31627e+13 -5.31627e+13 4752.75 7.53149e+13 7.53149e+13 146 | -0.182617 -4.06321e+14 -4.06321e+14 -4752.75 -8.1015e+13 -8.1015e+13 147 | -0.182617 -4.06321e+14 -4.06321e+14 -4752.75 -8.1015e+13 -8.1015e+13 148 | -0.182617 -4.06321e+14 -4.06321e+14 4752.75 8.1015e+13 8.1015e+13 149 | -0.182617 -1.59051e+14 -1.59051e+14 4752.75 2.25325e+14 2.25325e+14 150 | -0.182617 -4.06321e+14 -4.06321e+14 -4752.75 -8.1015e+13 -8.1015e+13 151 | -0.182617 -4.06321e+14 -4.06321e+14 -4752.75 -8.1015e+13 -8.1015e+13 152 | -0.182617 -4.06321e+14 -4.06321e+14 4752.75 8.1015e+13 8.1015e+13 153 | -0.182617 -7.69388e+13 -7.69388e+13 4752.75 8.60036e+13 8.60036e+13 154 | -0.182617 -4.06321e+14 -4.06321e+14 -4752.75 -8.1015e+13 -8.1015e+13 155 | -0.182617 -4.06321e+14 -4.06321e+14 -4752.75 -8.1015e+13 -8.1015e+13 156 | -0.236816 -5.24559e+14 -5.24559e+14 3727.59 6.35401e+13 6.35401e+13 157 | -0.236816 -5.24559e+14 -5.24559e+14 3727.59 6.35401e+13 6.35401e+13 158 | -0.236572 -5.24559e+14 -5.24559e+14 -3727.59 -6.35401e+13 -6.35401e+13 159 | -0.236572 -5.24559e+14 -5.24559e+14 -3727.59 -6.35401e+13 -6.35401e+13 160 | -0.236816 -5.24559e+14 -5.24559e+14 3727.59 6.35401e+13 6.35401e+13 161 | -0.236816 -3.7282e+13 -3.7282e+13 3727.59 3.48964e+13 3.48964e+13 162 | -0.236572 -5.24559e+14 -5.24559e+14 -3727.59 -6.35401e+13 -6.35401e+13 163 | -0.236572 -2.71081e+14 -2.71081e+14 -3727.59 1.65637e+14 1.65637e+14 164 | -0.182617 -4.06321e+14 -4.06321e+14 7690.11 1.31085e+14 1.31085e+14 165 | -0.182617 -4.06321e+14 -4.06321e+14 7690.11 1.31085e+14 1.31085e+14 166 | -0.182617 -4.06322e+14 -4.06322e+14 -7690.11 -1.31085e+14 -1.31085e+14 167 | -0.182617 -4.06322e+14 -4.06322e+14 -7690.11 -1.31085e+14 -1.31085e+14 168 | -0.182617 -7.03067e+13 -7.03067e+13 7690.11 6.92557e+13 6.92557e+13 169 | -0.182617 -1.4155e+13 -1.4155e+13 7690.11 1.78331e+13 1.78331e+13 170 | -0.182617 -4.06322e+14 -4.06322e+14 -7690.11 -1.31085e+14 -1.31085e+14 171 | -0.182617 -4.06322e+14 -4.06322e+14 -7690.11 -1.31085e+14 -1.31085e+14 172 | -0.100037 -2.22551e+14 -2.22551e+14 782.033 1.33305e+13 1.33305e+13 173 | -0.100037 -2.22551e+14 -2.22551e+14 782.033 1.33305e+13 1.33305e+13 174 | -0.100037 -2.22551e+14 -2.22551e+14 -782.033 -1.33305e+13 -1.33305e+13 175 | -0.100037 -2.22551e+14 -2.22551e+14 -782.033 -1.33305e+13 -1.33305e+13 176 | -4864 -2.22551e+14 -2.22551e+14 7.41728e+08 1.33305e+13 1.33305e+13 177 | -0.100037 -2.22551e+14 -2.22551e+14 782.033 1.33305e+13 1.33305e+13 178 | -0.100037 -2.22551e+14 -2.22551e+14 -782.033 -1.33305e+13 -1.33305e+13 179 | -4864 -2.22551e+14 -2.22551e+14 -7.41728e+08 -1.33305e+13 -1.33305e+13 180 | -0.183472 -4.06321e+14 -4.06321e+14 1418.7 2.4183e+13 2.4183e+13 181 | -0.183472 -4.06321e+14 -4.06321e+14 1418.7 2.4183e+13 2.4183e+13 182 | -0.18335 -4.06321e+14 -4.06321e+14 -1418.7 -2.4183e+13 -2.4183e+13 183 | -0.18335 -4.06321e+14 -4.06321e+14 -1418.7 -2.4183e+13 -2.4183e+13 184 | -0.183472 -4.06321e+14 -4.06321e+14 1418.7 2.4183e+13 2.4183e+13 185 | -0.183472 -8.6398e+13 -8.6398e+13 1418.7 9.57899e+13 9.57899e+13 186 | -0.18335 -4.06321e+14 -4.06321e+14 -1418.7 -2.4183e+13 -2.4183e+13 187 | -0.18335 -2.09979e+14 -2.09979e+14 -1418.7 3.87482e+13 3.87482e+13 188 | -0.234863 -5.24559e+14 -5.24559e+14 8031.83 1.3691e+14 1.3691e+14 189 | -0.234863 -2.05334e+14 -2.05334e+14 8031.83 1.3691e+14 1.3691e+14 190 | -0.234863 -5.24559e+14 -5.24559e+14 -8031.83 -1.3691e+14 -1.3691e+14 191 | -0.234863 -5.24559e+14 -5.24559e+14 -8031.83 -1.3691e+14 -1.3691e+14 192 | -0.234863 -5.24559e+14 -5.24559e+14 8031.83 1.3691e+14 1.3691e+14 193 | -0.234863 -2.91817e+13 -2.91817e+13 8031.83 2.93913e+13 2.93913e+13 194 | -0.234863 -5.24559e+14 -5.24559e+14 -8031.83 -1.3691e+14 -1.3691e+14 195 | -0.234863 -5.24559e+14 -5.24559e+14 -8031.83 -1.3691e+14 -1.3691e+14 196 | -0.183105 -4.06321e+14 -4.06321e+14 7690.11 1.31085e+14 1.31085e+14 197 | -0.183105 -4.81801e+13 -4.81801e+13 7690.11 6.06996e+13 6.06996e+13 198 | -0.182617 -4.06322e+14 -4.06322e+14 -7690.11 -1.31085e+14 -1.31085e+14 199 | -0.182617 -4.06322e+14 -4.06322e+14 -7690.11 -1.31085e+14 -1.31085e+14 200 | -0.183105 -4.06321e+14 -4.06321e+14 7690.11 1.31085e+14 1.31085e+14 201 | -0.183105 -2.06556e+13 -2.06556e+13 7690.11 2.03468e+13 2.03468e+13 202 | -0.182617 -4.06322e+14 -4.06322e+14 -7690.11 -1.31085e+14 -1.31085e+14 203 | -0.182617 -4.06322e+14 -4.06322e+14 -7690.11 -1.31085e+14 -1.31085e+14 204 | -0.183472 -4.06321e+14 -4.06321e+14 1418.7 2.4183e+13 2.4183e+13 205 | -0.183472 -4.06321e+14 -4.06321e+14 1418.7 2.4183e+13 2.4183e+13 206 | -0.18335 -4.06321e+14 -4.06321e+14 -1418.7 -2.4183e+13 -2.4183e+13 207 | -0.18335 -4.06321e+14 -4.06321e+14 -1418.7 -2.4183e+13 -2.4183e+13 208 | -0.183472 -4.06321e+14 -4.06321e+14 1418.7 2.4183e+13 2.4183e+13 209 | -0.183472 -7.01853e+13 -7.01853e+13 1418.7 1.1344e+14 1.1344e+14 210 | -0.18335 -4.06321e+14 -4.06321e+14 -1418.7 -2.4183e+13 -2.4183e+13 211 | -0.18335 -2.09979e+14 -2.09979e+14 -1418.7 3.87482e+13 3.87482e+13 212 | -0.100037 -2.22551e+14 -2.22551e+14 782.033 1.33305e+13 1.33305e+13 213 | -0.100037 -2.22551e+14 -2.22551e+14 782.033 1.33305e+13 1.33305e+13 214 | -0.100037 -2.22551e+14 -2.22551e+14 -782.033 -1.33305e+13 -1.33305e+13 215 | -0.100037 -2.22551e+14 -2.22551e+14 -782.033 -1.33305e+13 -1.33305e+13 216 | -0.100037 -2.22551e+14 -2.22551e+14 782.033 1.33305e+13 1.33305e+13 217 | -4864 -2.22551e+14 -2.22551e+14 7.41728e+08 1.33305e+13 1.33305e+13 218 | -4864 -2.22551e+14 -2.22551e+14 -7.41728e+08 -1.33305e+13 -1.33305e+13 219 | -0.100037 -2.22551e+14 -2.22551e+14 -782.033 -1.33305e+13 -1.33305e+13 220 | -0.234863 -5.24559e+14 -5.24559e+14 8031.83 1.3691e+14 1.3691e+14 221 | -0.234863 -5.24559e+14 -5.24559e+14 8031.83 1.3691e+14 1.3691e+14 222 | -0.234863 -5.24559e+14 -5.24559e+14 -8031.83 -1.3691e+14 -1.3691e+14 223 | -0.234863 -5.24559e+14 -5.24559e+14 -8031.83 -1.3691e+14 -1.3691e+14 224 | -0.234863 -2.41402e+14 -2.41402e+14 8031.83 1.3691e+14 1.3691e+14 225 | -0.234863 -2.01638e+13 -2.01638e+13 8031.83 1.71999e+13 1.71999e+13 226 | -0.234863 -5.24559e+14 -5.24559e+14 -8031.83 -1.3691e+14 -1.3691e+14 227 | -0.234863 -5.24559e+14 -5.24559e+14 -8031.83 -1.3691e+14 -1.3691e+14 228 | -0.163574 -3.63425e+14 -3.63425e+14 7623.76 1.29954e+14 1.29954e+14 229 | -0.163574 -1.10089e+14 -1.10089e+14 7623.76 1.13291e+14 1.13291e+14 230 | -0.163086 -3.63425e+14 -3.63425e+14 -7623.76 -1.29954e+14 -1.29954e+14 231 | -0.163086 -3.63425e+14 -3.63425e+14 -7623.76 -1.29954e+14 -1.29954e+14 232 | -0.163574 -1.36645e+14 -1.36645e+14 7623.76 1.15038e+14 1.15038e+14 233 | -0.163574 -1.21609e+13 -1.21609e+13 7623.76 1.17966e+13 1.17966e+13 234 | -0.163086 -3.63425e+14 -3.63425e+14 -7623.76 -1.29954e+14 -1.29954e+14 235 | -0.163086 -3.63425e+14 -3.63425e+14 -7623.76 -1.29954e+14 -1.29954e+14 236 | -0.163818 -3.63425e+14 -3.63425e+14 2014.44 3.43379e+13 3.43379e+13 237 | -0.163818 -3.63425e+14 -3.63425e+14 2014.44 3.43379e+13 3.43379e+13 238 | -0.16394 -3.63425e+14 -3.63425e+14 -2014.44 -3.43379e+13 -3.43379e+13 239 | -0.16394 -3.63425e+14 -3.63425e+14 -2014.44 -3.43379e+13 -3.43379e+13 240 | -0.163818 -3.63425e+14 -3.63425e+14 2014.44 3.43379e+13 3.43379e+13 241 | -0.163818 -1.21475e+14 -1.21475e+14 2014.44 1.75544e+14 1.75544e+14 242 | -0.16394 -3.63425e+14 -3.63425e+14 -2014.44 -3.43379e+13 -3.43379e+13 243 | -0.16394 -3.63425e+14 -3.63425e+14 -2014.44 -3.43379e+13 -3.43379e+13 244 | -0.163818 -3.63425e+14 -3.63425e+14 2014.44 3.43379e+13 3.43379e+13 245 | -0.163818 -3.63425e+14 -3.63425e+14 2014.44 3.43379e+13 3.43379e+13 246 | -0.163818 -3.63425e+14 -3.63425e+14 -2014.44 -3.43379e+13 -3.43379e+13 247 | -0.163818 -3.63425e+14 -3.63425e+14 -2014.44 -3.43379e+13 -3.43379e+13 248 | -0.163818 -3.63425e+14 -3.63425e+14 2014.44 3.43379e+13 3.43379e+13 249 | -0.163818 -1.49535e+14 -1.49535e+14 2014.44 1.59167e+14 1.59167e+14 250 | -0.163818 -3.63425e+14 -3.63425e+14 -2014.44 -3.43379e+13 -3.43379e+13 251 | -0.163818 -3.63425e+14 -3.63425e+14 -2014.44 -3.43379e+13 -3.43379e+13 252 | -0.271484 -6.05708e+14 -6.05708e+14 8031.83 1.3691e+14 1.3691e+14 253 | -0.271484 -6.05708e+14 -6.05708e+14 8031.83 1.3691e+14 1.3691e+14 254 | -0.271484 -6.05708e+14 -6.05708e+14 -8031.83 -1.3691e+14 -1.3691e+14 255 | -0.271484 -6.05708e+14 -6.05708e+14 -8031.83 -1.3691e+14 -1.3691e+14 256 | -0.271484 -6.05708e+14 -6.05708e+14 8031.83 1.3691e+14 1.3691e+14 257 | -0.271484 -2.44739e+13 -2.44739e+13 8031.83 2.6493e+13 2.6493e+13 258 | -0.271484 -6.05708e+14 -6.05708e+14 -8031.83 -1.3691e+14 -1.3691e+14 259 | -0.271484 -6.05708e+14 -6.05708e+14 -8031.83 -1.3691e+14 -1.3691e+14 260 | -1 -1 -1 -1 261 | -1 -1 999999 999999 262 | -1 -1 -1 -1 263 | 0.05 1.732 1.732 264 | 0.05 1.732 1.732 265 | 0.05 0.866 0.866 266 | 0.05 0.866 0.866 267 | 0.05 0.866 0.866 268 | 0.05 0.866 0.866 269 | 0.05 0.866 0.866 270 | 0.05 0.866 0.866 271 | } 272 | 273 | primEdgedata { 5 274 | 0 48191 0 275 | 65552 276 | 0 5 277 | 30234 30234 30234 0 278 | 1 1 48672.9 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 279 | 130560 130560 130560 130560 47277.7 130560 130560 130560 130560 130560 130560 130560 130560 67470.8 51106.6 130560 130560 60083.8 130560 130560 130560 130560 130560 130560 130560 130560 130560 130560 130560 130560 130560 130560 130560 130560 130560 39549.5 130560 130560 130560 130560 130560 49089.6 130560 130560 130560 130560 130560 130560 130560 130560 130560 130560 130560 130560 130560 130560 130560 130560 130560 130560 130560 130560 130560 53720.3 38357.6 43639.6 280 | 130560 130560 130560 130560 47277.7 130560 130560 130560 130560 130560 130560 130560 130560 67470.8 51106.6 130560 130560 60083.8 130560 130560 130560 130560 130560 130560 130560 130560 130560 130560 130560 130560 130560 130560 130560 130560 130560 39549.5 130560 130560 130560 130560 130560 49089.6 130560 130560 130560 130560 130560 130560 130560 130560 130560 130560 130560 130560 130560 130560 130560 130560 130560 130560 130560 130560 130560 53720.3 38357.6 43639.6 281 | 0 0 0 0 0 0 282 | 0 0 0 0 0 0 283 | 0 0 0 0 0 0 284 | 0 0 0 0 0 0 285 | 0 0 0 0 0 0 286 | 0 0 0 0 0 0 287 | 0 0 0 0 0 0 288 | 0 0 0 0 0 0 289 | 0 0 0 0 0 0 290 | 0 0 0 0 0 0 291 | 0 0 0 0 0 0 292 | 0 0 0 0 0 0 293 | 0 0 0 0 0 0 294 | 0 0 0 0 0 0 295 | 0 0 0 0 0 0 296 | 0 0 0 0 0 0 297 | 0 0 0 0 0 0 298 | 0 0 0 0 0 0 299 | 0 0 0 0 0 0 300 | 0 0 0 0 0 0 301 | 0 0 0 0 0 0 302 | 0 0 0 0 0 0 303 | 0 0 0 0 0 0 304 | 0 0 0 0 0 0 305 | 0 0 0 0 0 0 306 | 0 0 0 0 0 0 307 | 0 0 0 0 0 0 308 | 0 0 0 0 0 0 309 | 0 0 0 0 0 0 310 | 0 0 0 0 0 0 311 | 0 0 0 0 0 0 312 | 0 0 0 0 0 0 313 | 0 0 0 0 0 0 314 | 0 0 0 0 0 0 315 | 0 0 0 0 0 0 316 | 0 0 0 0 0 0 317 | 0 0 0 0 0 0 318 | 0 0 0 0 0 0 319 | 0 0 0 0 0 0 320 | 0 0 0 0 0 0 321 | 0 0 0 0 0 0 322 | 0 0 0 0 0 0 323 | 0 0 0 0 0 0 324 | 0 0 0 0 0 0 325 | 0 0 0 0 0 0 326 | 0 0 0 0 0 0 327 | 0 0 0 0 0 0 328 | 0 0 0 0 0 0 329 | 0 0 0 0 0 0 330 | 0 0 0 0 0 0 331 | 0 0 0 0 0 0 332 | 0 0 0 0 0 0 333 | 0 0 0 0 0 0 334 | 0 0 0 0 0 0 335 | 0 0 0 0 0 0 336 | 0 0 0 0 0 0 337 | 0 0 0 0 0 0 338 | 0 0 0 0 0 0 339 | 0 0 0 0 0 0 340 | 0 0 0 0 0 0 341 | 0 0 0 0 0 0 342 | 0 0 0 0 0 0 343 | 0 0 0 0 0 0 344 | 0 0 0 0 0 0 345 | 0 0 0 0 0 0 346 | 0 0 0 0 0 0 347 | 0 0 0 0 0 0 348 | 0 0 0 0 0 0 349 | 0 0 0 0 0 0 350 | 0 0 0 0 0 0 351 | 0 0 0 0 0 0 352 | 0 0 0 0 0 0 353 | 0 0 0 0 0 0 354 | 0 0 0 0 0 0 355 | 0 0 0 0 0 0 356 | 0 0 0 0 0 0 357 | 0 0 0 0 0 0 358 | 0 0 0 0 0 0 359 | 0 0 0 0 0 0 360 | 0 0 0 0 0 0 361 | 0 0 0 0 0 0 362 | 0 0 0 0 0 0 363 | 0 0 0 0 0 0 364 | 0 0 0 0 0 0 365 | 0 0 0 0 0 0 366 | 0 0 0 0 0 0 367 | 0 0 0 0 0 0 368 | 0 0 0 0 0 0 369 | 0 0 0 0 0 0 370 | 0 0 0 0 0 0 371 | 0 0 0 0 0 0 372 | 0 0 0 0 0 0 373 | 0 0 0 0 0 0 374 | 0 0 0 0 0 0 375 | 0 0 0 0 0 0 376 | 0 0 0 0 0 0 377 | 0 0 0 0 0 0 378 | 0 0 0 0 0 0 379 | 0 0 0 0 0 0 380 | 0 0 0 0 0 0 381 | 0 0 0 0 0 0 382 | 0 0 0 0 0 0 383 | 0 0 0 0 0 0 384 | 0 0 0 0 0 0 385 | 0 0 0 0 0 0 386 | 0 0 0 0 0 0 387 | 0 0 0 0 0 0 388 | 0 0 0 0 0 0 389 | 0 0 0 0 0 0 390 | 0 0 0 0 0 0 391 | 0 0 0 0 0 0 392 | 0 0 0 0 0 0 393 | 0 0 0 0 0 0 394 | 0 0 0 0 0 0 395 | 0 0 0 0 0 0 396 | 0 0 0 0 0 0 397 | 0 0 0 0 0 0 398 | 0 0 0 0 0 0 399 | 0 0 0 0 0 0 400 | 0 0 0 0 0 0 401 | 0 0 0 0 0 0 402 | 0 0 0 0 0 0 403 | 0 0 0 0 0 0 404 | 0 0 0 0 0 0 405 | 0 0 0 0 0 0 406 | 0 0 0 0 0 0 407 | 0 0 0 0 0 0 408 | 0 0 0 0 0 0 409 | -1 -1 -1 -1 410 | -1 -1 999999 999999 411 | -1 -1 -1 -1 412 | 0.05 1.732 1.732 413 | 0.05 1.732 1.732 414 | 0.05 0.866 0.866 415 | 0.05 0.866 0.866 416 | 0.05 0.866 0.866 417 | 0.05 0.866 0.866 418 | 0.05 0.866 0.866 419 | 0.05 0.866 0.866 420 | } 421 | 422 | primBodydata { 5 423 | 0 48191 0 424 | 65552 425 | 0 5 426 | 30234 30234 30234 0 427 | 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 428 | 130560 130560 130560 130560 47277.7 130560 130560 130560 130560 130560 130560 130560 130560 67470.8 51106.6 130560 130560 60083.8 130560 130560 130560 130560 130560 130560 130560 130560 130560 130560 130560 130560 130560 130560 130560 130560 130560 39549.5 130560 130560 130560 130560 130560 49089.6 130560 130560 130560 130560 130560 130560 130560 130560 130560 130560 130560 130560 130560 130560 130560 130560 130560 130560 130560 130560 130560 53720.3 38357.6 43639.6 429 | 130560 130560 130560 130560 47277.7 130560 130560 130560 130560 130560 130560 130560 130560 67470.8 51106.6 130560 130560 60083.8 130560 130560 130560 130560 130560 130560 130560 130560 130560 130560 130560 130560 130560 130560 130560 130560 130560 39549.5 130560 130560 130560 130560 130560 49089.6 130560 130560 130560 130560 130560 130560 130560 130560 130560 130560 130560 130560 130560 130560 130560 130560 130560 130560 130560 130560 130560 53720.3 38357.6 43639.6 430 | 0 0 0 0 0 0 431 | 0 0 0 0 0 0 432 | 0 0 0 0 0 0 433 | 0 0 0 0 0 0 434 | 0 0 0 0 0 0 435 | 0 0 0 0 0 0 436 | 0 0 0 0 0 0 437 | 0 0 0 0 0 0 438 | 0 0 0 0 0 0 439 | 0 0 0 0 0 0 440 | 0 0 0 0 0 0 441 | 0 0 0 0 0 0 442 | 0 0 0 0 0 0 443 | 0 0 0 0 0 0 444 | 0 0 0 0 0 0 445 | 0 0 0 0 0 0 446 | 0 0 0 0 0 0 447 | 0 0 0 0 0 0 448 | 0 0 0 0 0 0 449 | 0 0 0 0 0 0 450 | 0 0 0 0 0 0 451 | 0 0 0 0 0 0 452 | 0 0 0 0 0 0 453 | 0 0 0 0 0 0 454 | 0 0 0 0 0 0 455 | 0 0 0 0 0 0 456 | 0 0 0 0 0 0 457 | 0 0 0 0 0 0 458 | 0 0 0 0 0 0 459 | 0 0 0 0 0 0 460 | 0 0 0 0 0 0 461 | 0 0 0 0 0 0 462 | 0 0 0 0 0 0 463 | 0 0 0 0 0 0 464 | 0 0 0 0 0 0 465 | 0 0 0 0 0 0 466 | 0 0 0 0 0 0 467 | 0 0 0 0 0 0 468 | 0 0 0 0 0 0 469 | 0 0 0 0 0 0 470 | 0 0 0 0 0 0 471 | 0 0 0 0 0 0 472 | 0 0 0 0 0 0 473 | 0 0 0 0 0 0 474 | 0 0 0 0 0 0 475 | 0 0 0 0 0 0 476 | 0 0 0 0 0 0 477 | 0 0 0 0 0 0 478 | 0 0 0 0 0 0 479 | 0 0 0 0 0 0 480 | 0 0 0 0 0 0 481 | 0 0 0 0 0 0 482 | 0 0 0 0 0 0 483 | 0 0 0 0 0 0 484 | 0 0 0 0 0 0 485 | 0 0 0 0 0 0 486 | 0 0 0 0 0 0 487 | 0 0 0 0 0 0 488 | 0 0 0 0 0 0 489 | 0 0 0 0 0 0 490 | 0 0 0 0 0 0 491 | 0 0 0 0 0 0 492 | 0 0 0 0 0 0 493 | 0 0 0 0 0 0 494 | 0 0 0 0 0 0 495 | 0 0 0 0 0 0 496 | 0 0 0 0 0 0 497 | 0 0 0 0 0 0 498 | 0 0 0 0 0 0 499 | 0 0 0 0 0 0 500 | 0 0 0 0 0 0 501 | 0 0 0 0 0 0 502 | 0 0 0 0 0 0 503 | 0 0 0 0 0 0 504 | 0 0 0 0 0 0 505 | 0 0 0 0 0 0 506 | 0 0 0 0 0 0 507 | 0 0 0 0 0 0 508 | 0 0 0 0 0 0 509 | 0 0 0 0 0 0 510 | 0 0 0 0 0 0 511 | 0 0 0 0 0 0 512 | 0 0 0 0 0 0 513 | 0 0 0 0 0 0 514 | 0 0 0 0 0 0 515 | 0 0 0 0 0 0 516 | 0 0 0 0 0 0 517 | 0 0 0 0 0 0 518 | 0 0 0 0 0 0 519 | 0 0 0 0 0 0 520 | 0 0 0 0 0 0 521 | 0 0 0 0 0 0 522 | 0 0 0 0 0 0 523 | 0 0 0 0 0 0 524 | 0 0 0 0 0 0 525 | 0 0 0 0 0 0 526 | 0 0 0 0 0 0 527 | 0 0 0 0 0 0 528 | 0 0 0 0 0 0 529 | 0 0 0 0 0 0 530 | 0 0 0 0 0 0 531 | 0 0 0 0 0 0 532 | 0 0 0 0 0 0 533 | 0 0 0 0 0 0 534 | 0 0 0 0 0 0 535 | 0 0 0 0 0 0 536 | 0 0 0 0 0 0 537 | 0 0 0 0 0 0 538 | 0 0 0 0 0 0 539 | 0 0 0 0 0 0 540 | 0 0 0 0 0 0 541 | 0 0 0 0 0 0 542 | 0 0 0 0 0 0 543 | 0 0 0 0 0 0 544 | 0 0 0 0 0 0 545 | 0 0 0 0 0 0 546 | 0 0 0 0 0 0 547 | 0 0 0 0 0 0 548 | 0 0 0 0 0 0 549 | 0 0 0 0 0 0 550 | 0 0 0 0 0 0 551 | 0 0 0 0 0 0 552 | 0 0 0 0 0 0 553 | 0 0 0 0 0 0 554 | 0 0 0 0 0 0 555 | 0 0 0 0 0 0 556 | 0 0 0 0 0 0 557 | 0 0 0 0 0 0 558 | -1 -1 -1 -1 559 | -1 -1 999999 999999 560 | -1 -1 -1 -1 561 | 0.05 1.732 1.732 562 | 0.05 1.732 1.732 563 | 0.05 0.866 0.866 564 | 0.05 0.866 0.866 565 | 0.05 0.866 0.866 566 | 0.05 0.866 0.866 567 | 0.05 0.866 0.866 568 | 0.05 0.866 0.866 569 | } 570 | 571 | crop {0 0 640 480} 572 | "Adjust Lighting" 0 573 | "Hybrid Matte" 0 574 | "Fine Tuning" 0 575 | name Primatte1 576 | xpos 74 577 | ypos -176 578 | } 579 | set N46ae1000 [stack 0] 580 | Group { 581 | name Group1 582 | xpos 74 583 | ypos -19 584 | } 585 | Input { 586 | inputs 0 587 | name Input1 588 | xpos 0 589 | } 590 | Grade { 591 | name Grade1 592 | xpos 4 593 | ypos 77 594 | } 595 | set N46ae0800 [stack 0] 596 | push $N46ae0800 597 | Transform { 598 | center {320 240} 599 | name Transform1 600 | selected true 601 | xpos 90 602 | ypos 133 603 | } 604 | Merge2 { 605 | inputs 2 606 | name Merge1 607 | xpos 10 608 | ypos 209 609 | } 610 | Output { 611 | name Output1 612 | xpos 0 613 | ypos 300 614 | } 615 | end_group 616 | Roto { 617 | inputs 0 618 | output alpha 619 | curves {{{v x3f99999a} 620 | {f 0} 621 | {n 622 | {layer Root 623 | {f 2097152} 624 | {t x43a00000 x43700000} 625 | {a pt1x 0 pt1y 0 pt2x 0 pt2y 0 pt3x 0 pt3y 0 pt4x 0 pt4y 0 ptex00 0 ptex01 0 ptex02 0 ptex03 0 ptex10 0 ptex11 0 ptex12 0 ptex13 0 ptex20 0 ptex21 0 ptex22 0 ptex23 0 ptex30 0 ptex31 0 ptex32 0 ptex33 0 ptof1x 0 ptof1y 0 ptof2x 0 ptof2y 0 ptof3x 0 ptof3y 0 ptof4x 0 ptof4y 0 pterr 0 ptrefset 0 ptmot x40800000 ptref 0} 626 | {curvegroup Bezier1 512 bezier 627 | {{cc 628 | {f 8192} 629 | {px 630 | {1 x41980000} 631 | {{0} 632 | {xc0000000}} 633 | {{x4257fff4 x4333fffc} 634 | {x4386ffff x43acffff}} 635 | {{0} 636 | {x40000000}} 637 | {{0} 638 | {0}} 639 | {{x4301fffd x437ffffc} 640 | {x438cffff x43b2ffff}} 641 | {{0} 642 | {0}} 643 | {{x42780000} 644 | {xc2f80000}} 645 | {{x42cffffa x4365fffc} 646 | {x430bffff x4357ffff}} 647 | {{xc2780000} 648 | {x42f80000}}}} idem} 649 | {tx 1 x43500000 x43835555} 650 | {a osw x41200000 osf 0 str 1 sb 1 tt x40800000}}}}}} 651 | toolbox {selectAll { 652 | { selectAll str 1 ssx 1 ssy 1 sf 1 } 653 | { createBezier str 1 ssx 1 ssy 1 sf 1 sb 1 tt 4 } 654 | { createBezierCusped str 1 ssx 1 ssy 1 sf 1 sb 1 } 655 | { createBSpline str 1 ssx 1 ssy 1 sf 1 sb 1 } 656 | { createEllipse str 1 ssx 1 ssy 1 sf 1 sb 1 } 657 | { createRectangle str 1 ssx 1 ssy 1 sf 1 sb 1 } 658 | { createRectangleCusped str 1 ssx 1 ssy 1 sf 1 sb 1 } 659 | { brush str 1 ssx 1 ssy 1 sf 1 sb 1 } 660 | { eraser src 2 str 1 ssx 1 ssy 1 sf 1 sb 1 } 661 | { clone src 1 str 1 ssx 1 ssy 1 sf 1 sb 1 } 662 | { reveal src 3 str 1 ssx 1 ssy 1 sf 1 sb 1 } 663 | { dodge src 1 str 1 ssx 1 ssy 1 sf 1 sb 1 } 664 | { burn src 1 str 1 ssx 1 ssy 1 sf 1 sb 1 } 665 | { blur src 1 str 1 ssx 1 ssy 1 sf 1 sb 1 } 666 | { sharpen src 1 str 1 ssx 1 ssy 1 sf 1 sb 1 } 667 | { smear src 1 str 1 ssx 1 ssy 1 sf 1 sb 1 } 668 | } } 669 | toolbar_brush_hardness 0.200000003 670 | toolbar_source_transform_scale {1 1} 671 | toolbar_source_transform_center {320 240} 672 | colorOverlay {0 0 0 0} 673 | lifetime_type "all frames" 674 | motionblur_shutter_offset_type centred 675 | source_black_outside true 676 | name Roto1 677 | xpos -300 678 | ypos -73 679 | } 680 | RotoPaint { 681 | inputs 0 682 | curves {{{v x3f99999a} 683 | {f 0} 684 | {n 685 | {layer Root 686 | {f 2097152} 687 | {t x43a00000 x43700000} 688 | {a pt1x 0 pt1y 0 pt2x 0 pt2y 0 pt3x 0 pt3y 0 pt4x 0 pt4y 0 ptex00 0 ptex01 0 ptex02 0 ptex03 0 ptex10 0 ptex11 0 ptex12 0 ptex13 0 ptex20 0 ptex21 0 ptex22 0 ptex23 0 ptex30 0 ptex31 0 ptex32 0 ptex33 0 ptof1x 0 ptof1y 0 ptof2x 0 ptof2y 0 ptof3x 0 ptof3y 0 ptof4x 0 ptof4y 0 pterr 0 ptrefset 0 ptmot x40800000 ptref 0} 689 | {cubiccurve Brush1 512 catmullrom 690 | {cc 691 | {f 2080} 692 | {px x41980000 693 | {x437a0000 x43b00000 1} 694 | {x43720000 x43ae0000 1} 695 | {x43480000 x43980000 1} 696 | {x43240000 x437c0000 1} 697 | {x42d40000 x42cc0000 1} 698 | {x43080000 x42840000 1} 699 | {x43440000 x42180000 1} 700 | {x43860000 x42700000 1} 701 | {x43ab0000 x42dc0000 1} 702 | {x43c10000 x432c0000 1} 703 | {x43c10000 x433c0000 1} 704 | {x43ba0000 x434a0000 1} 705 | {x43b80000 x43520000 1} 706 | {x43b60000 x43520000 1} 707 | {x43af0000 x43620000 1} 708 | {x43ab0000 x436a0000 1}}} 709 | {tx x41980000 x438bc000 x43402000} 710 | {a ro 0 go 0 bo 0 ao 0 bu 1 str 1 sb 1 ltn x41980000 ltm x41980000 ltt x40000000 tt x41880000}}}}}} 711 | toolbox {brush { 712 | { selectAll str 1 ssx 1 ssy 1 sf 1 } 713 | { createBezier str 1 ssx 1 ssy 1 sf 1 sb 1 } 714 | { createBezierCusped str 1 ssx 1 ssy 1 sf 1 sb 1 } 715 | { createBSpline str 1 ssx 1 ssy 1 sf 1 sb 1 } 716 | { createEllipse str 1 ssx 1 ssy 1 sf 1 sb 1 } 717 | { createRectangle str 1 ssx 1 ssy 1 sf 1 sb 1 } 718 | { createRectangleCusped str 1 ssx 1 ssy 1 sf 1 sb 1 } 719 | { brush str 1 ssx 1 ssy 1 sf 1 sb 1 ltn 19 ltm 19 tt 17 } 720 | { eraser src 2 str 1 ssx 1 ssy 1 sf 1 sb 1 } 721 | { clone src 1 str 1 ssx 1 ssy 1 sf 1 sb 1 } 722 | { reveal src 3 str 1 ssx 1 ssy 1 sf 1 sb 1 } 723 | { dodge src 1 str 1 ssx 1 ssy 1 sf 1 sb 1 } 724 | { burn src 1 str 1 ssx 1 ssy 1 sf 1 sb 1 } 725 | { blur src 1 str 1 ssx 1 ssy 1 sf 1 sb 1 } 726 | { sharpen src 1 str 1 ssx 1 ssy 1 sf 1 sb 1 } 727 | { smear src 1 str 1 ssx 1 ssy 1 sf 1 sb 1 } 728 | } } 729 | toolbar_brush_hardness 0.200000003 730 | toolbar_lifetime_type single 731 | toolbar_source_transform_scale {1 1} 732 | toolbar_source_transform_center {320 240} 733 | colorOverlay {0 0 0 0} 734 | lifetime_start 19 735 | lifetime_end 19 736 | brush_spacing 0.05000000075 737 | brush_hardness 0.200000003 738 | source_black_outside true 739 | name RotoPaint1 740 | xpos -238 741 | ypos -142 742 | } 743 | push $N4f264c00 744 | push $N46ae1000 745 | CheckerBoard2 { 746 | inputs 0 747 | name CheckerBoard1 748 | xpos -139 749 | ypos -399 750 | } 751 | Grade { 752 | name Grade3 753 | xpos -139 754 | ypos -290 755 | } 756 | Grade { 757 | name Grade8 758 | xpos -139 759 | ypos -219 760 | } 761 | Grade { 762 | inputs 1+1 763 | name Grade1 764 | xpos -139 765 | ypos -176 766 | } 767 | Grade { 768 | inputs 1+1 769 | name Grade2 770 | xpos -139 771 | ypos -141 772 | } 773 | Grade { 774 | inputs 1+1 775 | black -0.33 776 | multiply 0.13 777 | name Grade4 778 | selected true 779 | xpos -139 780 | ypos -98 781 | } 782 | Grade { 783 | name Grade5 784 | selected true 785 | xpos -139 786 | ypos -72 787 | } 788 | Grade { 789 | name Grade6 790 | selected true 791 | xpos -139 792 | ypos -46 793 | } 794 | Grade { 795 | inputs 1+1 796 | name Grade7 797 | selected true 798 | xpos -139 799 | ypos -20 800 | } 801 | add_layer {layer1 layer1.red layer1.green layer1.blue layer1.alpha} 802 | Copy { 803 | inputs 2 804 | from0 rgba.alpha 805 | to0 rgba.alpha 806 | channels layer1 807 | name Copy1 808 | xpos -54 809 | ypos 56 810 | } 811 | Premult { 812 | name Premult1 813 | xpos -54 814 | ypos 156 815 | } 816 | Viewer { 817 | frame_range 1-100 818 | colour_sample_bbox {-0.3249999881 0.65625 -0.321875006 0.6593750119} 819 | samplepoints {{-0.3249999881 0.65625} 820 | } 821 | monitorOutOutputTransform rec709 822 | name Viewer1 823 | xpos -54 824 | ypos 261 825 | } 826 | --------------------------------------------------------------------------------