Compare commits
	
		
			No commits in common. "9940a9c8ca45620e01343f7efba55f319430b921" and "b3cd661adc393a1c8fbc5659fb4b684da1c3166e" have entirely different histories.
		
	
	
		
			9940a9c8ca
			...
			b3cd661adc
		
	
		
| @ -1,5 +0,0 @@ | |||||||
| ${CommitTitle} |  | ||||||
| 
 |  | ||||||
| ${CommitBody} |  | ||||||
| 
 |  | ||||||
| Pull Request: https://git.autourdeminuit.com/${BaseRepoOwnerName}/${BaseRepoName}/pulls/${PullRequestIndex} |  | ||||||
| @ -1,3 +0,0 @@ | |||||||
| ${PullRequestTitle} |  | ||||||
| 
 |  | ||||||
| Pull Request: https://git.autourdeminuit.com/${BaseRepoOwnerName}/${BaseRepoName}/pulls/${PullRequestIndex} |  | ||||||
| @ -1,22 +0,0 @@ | |||||||
| name: Bug Report |  | ||||||
| about: Use Help > Report a Bug from the top of Blender to automatically fill out part of this form. |  | ||||||
| labels: |  | ||||||
|   - "Type/Bug" |  | ||||||
| body: |  | ||||||
|   - type: textarea |  | ||||||
|     id: body |  | ||||||
|     attributes: |  | ||||||
|       label: "Description" |  | ||||||
|       hide_label: true |  | ||||||
|       value: | |  | ||||||
|         **System Information** |  | ||||||
|         Operating system: |  | ||||||
|         Graphics card: |  | ||||||
| 
 |  | ||||||
|         **Software Version** |  | ||||||
|         Broken: (example: 3.0.0, main, `f1cca3055776`, 2020-12-30) |  | ||||||
|         Worked: (newest version that worked as expected) |  | ||||||
| 
 |  | ||||||
|         **Short description of error** |  | ||||||
| 
 |  | ||||||
|         **Exact steps for others to reproduce the error** |  | ||||||
| @ -1 +0,0 @@ | |||||||
| blank_issues_enabled: false |  | ||||||
| @ -1,10 +0,0 @@ | |||||||
| name: Design |  | ||||||
| about: Create a design task (for developers only) |  | ||||||
| labels: |  | ||||||
|   - "Type/Design" |  | ||||||
| body: |  | ||||||
|   - type: textarea |  | ||||||
|     id: body |  | ||||||
|     attributes: |  | ||||||
|       label: "Description" |  | ||||||
|       hide_label: true |  | ||||||
| @ -1,10 +0,0 @@ | |||||||
| name: To Do |  | ||||||
| about: Create a to do task (for developers only) |  | ||||||
| labels: |  | ||||||
|   - "Type/To Do" |  | ||||||
| body: |  | ||||||
|   - type: textarea |  | ||||||
|     id: body |  | ||||||
|     attributes: |  | ||||||
|       label: "Description" |  | ||||||
|       hide_label: true |  | ||||||
| @ -1,8 +0,0 @@ | |||||||
| name: Pull Request |  | ||||||
| about: Code contribution |  | ||||||
| body: |  | ||||||
|   - type: textarea |  | ||||||
|     id: body |  | ||||||
|     attributes: |  | ||||||
|       label: "Description" |  | ||||||
|       hide_label: true |  | ||||||
							
								
								
									
										137
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										137
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @ -1,17 +1,138 @@ | |||||||
| # Python Artifacts | # Byte-compiled / optimized / DLL files | ||||||
| __pycache__ | __pycache__ | ||||||
| __pycache__/ | __pycache__/ | ||||||
| *.py[cod] | *.py[cod] | ||||||
| *$py.class | *$py.class | ||||||
| 
 | 
 | ||||||
| # Python virtualenvs |  | ||||||
| .venv/ |  | ||||||
| .env/ |  | ||||||
| 
 |  | ||||||
| # Editor config files |  | ||||||
| .idea/ |  | ||||||
| *.code-workspace | *.code-workspace | ||||||
| *.vscode | *.vscode | ||||||
|  | test_* | ||||||
| 
 | 
 | ||||||
| # Poetry build artifacts | # C extensions | ||||||
|  | *.so | ||||||
|  | 
 | ||||||
|  | # Distribution / packaging | ||||||
|  | /dist/ | ||||||
|  | /release/ | ||||||
|  | .Python | ||||||
|  | #build/ | ||||||
|  | develop-eggs/ | ||||||
| dist/ | dist/ | ||||||
|  | downloads/ | ||||||
|  | eggs/ | ||||||
|  | .eggs/ | ||||||
|  | #lib/ | ||||||
|  | lib64/ | ||||||
|  | parts/ | ||||||
|  | sdist/ | ||||||
|  | var/ | ||||||
|  | wheels/ | ||||||
|  | pip-wheel-metadata/ | ||||||
|  | share/python-wheels/ | ||||||
|  | *.egg-info/ | ||||||
|  | .installed.cfg | ||||||
|  | *.egg | ||||||
|  | MANIFEST | ||||||
|  | 
 | ||||||
|  | # PyInstaller | ||||||
|  | #  Usually these files are written by a python script from a template | ||||||
|  | #  before PyInstaller builds the exe, so as to inject date/other infos into it. | ||||||
|  | *.manifest | ||||||
|  | *.spec | ||||||
|  | 
 | ||||||
|  | # Installer logs | ||||||
|  | pip-log.txt | ||||||
|  | pip-delete-this-directory.txt | ||||||
|  | 
 | ||||||
|  | # Unit test / coverage reports | ||||||
|  | htmlcov/ | ||||||
|  | .tox/ | ||||||
|  | .nox/ | ||||||
|  | .coverage | ||||||
|  | .coverage.* | ||||||
|  | .cache | ||||||
|  | nosetests.xml | ||||||
|  | coverage.xml | ||||||
|  | *.cover | ||||||
|  | *.py,cover | ||||||
|  | .hypothesis/ | ||||||
|  | .pytest_cache/ | ||||||
|  | 
 | ||||||
|  | # Translations | ||||||
|  | *.mo | ||||||
|  | *.pot | ||||||
|  | 
 | ||||||
|  | # Django stuff: | ||||||
|  | *.log | ||||||
|  | local_settings.py | ||||||
|  | db.sqlite3 | ||||||
|  | db.sqlite3-journal | ||||||
|  | 
 | ||||||
|  | # Flask stuff: | ||||||
|  | instance/ | ||||||
|  | .webassets-cache | ||||||
|  | 
 | ||||||
|  | # Scrapy stuff: | ||||||
|  | .scrapy | ||||||
|  | 
 | ||||||
|  | # Sphinx documentation | ||||||
|  | _build/ | ||||||
|  | Makefile | ||||||
|  | make.bat | ||||||
|  | 
 | ||||||
|  | # PyBuilder | ||||||
|  | target/ | ||||||
|  | 
 | ||||||
|  | # Jupyter Notebook | ||||||
|  | .ipynb_checkpoints | ||||||
|  | 
 | ||||||
|  | # IPython | ||||||
|  | profile_default/ | ||||||
|  | ipython_config.py | ||||||
|  | 
 | ||||||
|  | # pyenv | ||||||
|  | .python-version | ||||||
|  | 
 | ||||||
|  | # pipenv | ||||||
|  | #   According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. | ||||||
|  | #   However, in case of collaboration, if having platform-specific dependencies or dependencies | ||||||
|  | #   having no cross-platform support, pipenv may install dependencies that don't work, or not | ||||||
|  | #   install all needed dependencies. | ||||||
|  | #Pipfile.lock | ||||||
|  | 
 | ||||||
|  | # PEP 582; used by e.g. github.com/David-OConnor/pyflow | ||||||
|  | __pypackages__/ | ||||||
|  | 
 | ||||||
|  | # Celery stuff | ||||||
|  | celerybeat-schedule | ||||||
|  | celerybeat.pid | ||||||
|  | 
 | ||||||
|  | # SageMath parsed files | ||||||
|  | *.sage.py | ||||||
|  | 
 | ||||||
|  | # Environments | ||||||
|  | .env | ||||||
|  | .venv | ||||||
|  | #env/ | ||||||
|  | venv/ | ||||||
|  | #ENV/ | ||||||
|  | env.bak/ | ||||||
|  | venv.bak/ | ||||||
|  | 
 | ||||||
|  | # Spyder project settings | ||||||
|  | .spyderproject | ||||||
|  | .spyproject | ||||||
|  | 
 | ||||||
|  | # Rope project settings | ||||||
|  | .ropeproject | ||||||
|  | 
 | ||||||
|  | # mkdocs documentation | ||||||
|  | /site | ||||||
|  | 
 | ||||||
|  | # mypy | ||||||
|  | .mypy_cache/ | ||||||
|  | .dmypy.json | ||||||
|  | dmypy.json | ||||||
|  | 
 | ||||||
|  | # Pyre type checker | ||||||
|  | .pyre/ | ||||||
|  | |||||||
							
								
								
									
										51
									
								
								ISSUE_TEMPLATE.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								ISSUE_TEMPLATE.yml
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,51 @@ | |||||||
|  | name: Bug Report | ||||||
|  | 
 | ||||||
|  | about: File a bug report | ||||||
|  | 
 | ||||||
|  | title: "[Bug]: " | ||||||
|  | 
 | ||||||
|  | body: | ||||||
|  |   - id : ticket_type | ||||||
|  |     type: dropdown | ||||||
|  |     attributes: | ||||||
|  |       label: Type | ||||||
|  |       description: Type du ticket | ||||||
|  |       options: | ||||||
|  |         - bug | ||||||
|  |         - feature | ||||||
|  |         - maintenance | ||||||
|  |         - documentation | ||||||
|  |       validation: | ||||||
|  |         required: true | ||||||
|  | 
 | ||||||
|  |   - id : priority | ||||||
|  |     type: dropdown | ||||||
|  |     attributes: | ||||||
|  |       label: Priorité | ||||||
|  |       description: priorité du ticket | ||||||
|  |       options: | ||||||
|  |         - normal | ||||||
|  |         - faible | ||||||
|  |         - élevée | ||||||
|  |         - critique | ||||||
|  |    | ||||||
|  |   - id: description | ||||||
|  |     type: textarea | ||||||
|  |     attributes: | ||||||
|  |       label: Description | ||||||
|  |       description: Description du problème rencontré | ||||||
|  |       value: "ça marche pô... \nN'oubliez pas de préciser la task, l'asset ou le shot, et sa version dans le cas d'un bug" | ||||||
|  |     validations: | ||||||
|  |       required: true | ||||||
|  | 
 | ||||||
|  |   - id: path | ||||||
|  |     type: input | ||||||
|  |     attributes: | ||||||
|  |       label: filepath | ||||||
|  |       description: le chemin de la scene s'il existe | ||||||
|  | 
 | ||||||
|  |   - id: log | ||||||
|  |     type: textarea | ||||||
|  |     attributes: | ||||||
|  |       label: Log console | ||||||
|  |       description: Rensigner le crash log s'il existe | ||||||
							
								
								
									
										16
									
								
								PULL_REQUEST_TEMPLATE.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								PULL_REQUEST_TEMPLATE.md
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,16 @@ | |||||||
|  | ## Related ticket | ||||||
|  | Link to the ticket | ||||||
|  | 
 | ||||||
|  | ## Modifications | ||||||
|  | Description of what your PR is doing | ||||||
|  | 
 | ||||||
|  | ## How to test it | ||||||
|  | Description of how to test it | ||||||
|  | 
 | ||||||
|  | ## Risk involved | ||||||
|  | - LOW | ||||||
|  | - MID | ||||||
|  | - HIGH | ||||||
|  | 
 | ||||||
|  | ## Related PR | ||||||
|  | List of PR involved with this one | ||||||
							
								
								
									
										39
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										39
									
								
								README.md
									
									
									
									
									
								
							| @ -1,23 +1,32 @@ | |||||||
| # Node Kit | # NODE_KIT | ||||||
| Blender Node Toolkit - Collection of node-related tools (copy-pasting, packing, updating, etc..) all accessible from a simple menu. | > Blender addon to handle some operations on nodes | ||||||
| 
 | 
 | ||||||
| ## Development | Node kit is a blender addon creating a menu to execute operations on nodes like copy/pasting or exporting node_tree | ||||||
| ### Cloning | 
 | ||||||
| 1. Create a development directory, such as: | <!-- TABLE OF CONTENTS --> | ||||||
|  | *** | ||||||
|  | <summary>Table of Contents</summary> | ||||||
|  | <ol> | ||||||
|  | <li> | ||||||
|  |     <a href="#installation">Installation</a> | ||||||
|  | </li> | ||||||
|  | <li> | ||||||
|  |     <a href="#contents">Contents</a> | ||||||
|  | </li> | ||||||
|  | </ol> | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | <!-- INSTALLATION --> | ||||||
|  | ## Installation | ||||||
|  | 
 | ||||||
|  | 1. Create your own local directory in  | ||||||
|     ```sh |     ```sh | ||||||
|    /home/<USER>/dev |    /home/<USER>/dev | ||||||
|    ``` |    ``` | ||||||
| 2. Clone the repository with the following command: | 2. Create your own local directory in  | ||||||
|     ```sh |     ```sh | ||||||
|    git clone ssh://git@git.autourdeminuit.com:222/autour_de_minuit/node_kit.git |    git clone ssh://git@git.autourdeminuit.com:222/autour_de_minuit/node_kit.git | ||||||
|    ``` |    ``` | ||||||
| 
 | 
 | ||||||
| ### Branches | <!-- CONTENTS --> | ||||||
| The main branch is **master**. | ## Contents | ||||||
| It is a **protected branch** that only user with the `Owner` role can push to. |  | ||||||
| 
 |  | ||||||
| To contribute to this repository, you will have to create you own development branch, and then create a Pull Request. After at least **one validation**, you will be able to merge your changes. |  | ||||||
| 
 |  | ||||||
| Branches should be named in `kebab-case`, and can be grouped using `/`. |  | ||||||
| 
 |  | ||||||
| Examples: `bug/fix-crash-on-startup`, `bug/fix-font`, `feature/new-colors`, `theme-logic-refactor`, etc... |  | ||||||
|  | |||||||
							
								
								
									
										23
									
								
								__init__.py
									
									
									
									
									
								
							
							
						
						
									
										23
									
								
								__init__.py
									
									
									
									
									
								
							| @ -1,13 +1,14 @@ | |||||||
| bl_info = { | bl_info = { | ||||||
|     "name": "Node Kit", |     "name": "Node Kit", | ||||||
|     "author": "Florentin Luce, Christophe Seux, Jonas Holzman", |     "author": "Florentin Luce", | ||||||
|     "version": (0, 1), |     "version": (0, 1), | ||||||
|     "blender": (4, 3, 2), |     "blender": (4, 0, 2), | ||||||
|     "location": "Node Editor -> Node Kit", |     "category": "Node"} | ||||||
|     "description": "Collection of node-related tools", | 
 | ||||||
|     "doc_url": "https://git.autourdeminuit.com/autour_de_minuit/node_kit", | 
 | ||||||
|     "category": "Node", | import sys | ||||||
| } | import importlib | ||||||
|  | from pathlib import Path | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| from . import ui, operators | from . import ui, operators | ||||||
| @ -18,7 +19,15 @@ modules = ( | |||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | if "bpy" in locals(): | ||||||
|  |     import importlib | ||||||
|  | 
 | ||||||
|  |     for mod in modules: | ||||||
|  |         importlib.reload(mod) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
| def register(): | def register(): | ||||||
|  |     print('Register Node kit') | ||||||
|     for mod in modules: |     for mod in modules: | ||||||
|         mod.register() |         mod.register() | ||||||
| 
 | 
 | ||||||
|  | |||||||
							
								
								
									
										0
									
								
								bl_utils.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								bl_utils.py
									
									
									
									
									
										Normal file
									
								
							| @ -1,3 +1,4 @@ | |||||||
|  | 
 | ||||||
| import bpy | import bpy | ||||||
| from mathutils import Color, Vector | from mathutils import Color, Vector | ||||||
| 
 | 
 | ||||||
| @ -8,6 +9,7 @@ class Node: | |||||||
|     """Blender Node abstraction.""" |     """Blender Node abstraction.""" | ||||||
| 
 | 
 | ||||||
|     def __init__(self, bl_node, parent): |     def __init__(self, bl_node, parent): | ||||||
|  | 
 | ||||||
|         self.bl_node = bl_node |         self.bl_node = bl_node | ||||||
|         self.tree = parent |         self.tree = parent | ||||||
|         self.id = hex(id(self.bl_node)) |         self.id = hex(id(self.bl_node)) | ||||||
| @ -65,12 +67,12 @@ class Node: | |||||||
|             self._parent = value |             self._parent = value | ||||||
| 
 | 
 | ||||||
|         # Node id case |         # Node id case | ||||||
|         elif isinstance(value, str) and value.startswith("0x"): |         elif isinstance(value, str) and value.startswith('0x'): | ||||||
|             for node in self.tree.nodes: |             for node in self.tree.nodes: | ||||||
|                 if node.id == value: |                 if node.id == value: | ||||||
|                     self._parent = node |                     self._parent = node | ||||||
|             else: |             else: | ||||||
|                 print("Cannot find parent") |                 print('Cannot find parent') | ||||||
| 
 | 
 | ||||||
|         # blender node case |         # blender node case | ||||||
|         elif isinstance(value, bpy.types.Node): |         elif isinstance(value, bpy.types.Node): | ||||||
| @ -92,10 +94,10 @@ class Node: | |||||||
|         Returns: |         Returns: | ||||||
|             Node: Node abstract according to the blender node type. |             Node: Node abstract according to the blender node type. | ||||||
|         """ |         """ | ||||||
|         if bl_node.bl_idname == "CompositorNodeRLayers": |         if bl_node.bl_idname == 'CompositorNodeRLayers': | ||||||
|             return RenderLayersNode(bl_node, tree) |             return RenderLayersNode(bl_node, tree) | ||||||
| 
 | 
 | ||||||
|         elif bl_node.bl_idname == "CompositorNodeValToRGB": |         elif bl_node.bl_idname == 'CompositorNodeValToRGB': | ||||||
|             return ColorRampNode(bl_node, tree) |             return ColorRampNode(bl_node, tree) | ||||||
| 
 | 
 | ||||||
|         else: |         else: | ||||||
| @ -113,23 +115,19 @@ class Node: | |||||||
|             Node: Create abstract node. |             Node: Create abstract node. | ||||||
|         """ |         """ | ||||||
| 
 | 
 | ||||||
|         new_bl_node = tree.bl_node_tree.nodes.new(type=data["bl_idname"]) |         new_bl_node = tree.bl_node_tree.nodes.new(type=data['bl_idname']) | ||||||
|         node = cls.from_blender_node(new_bl_node, tree) |         node = cls.from_blender_node(new_bl_node, tree) | ||||||
| 
 | 
 | ||||||
|         node.id = data["id"] |         node.id = data['id'] | ||||||
|         for p in node.parameters: |         for p in node.parameters: | ||||||
|             setattr(node, p, data[p]) |             setattr(node, p, data[p]) | ||||||
| 
 | 
 | ||||||
|             # set attribute on the blender node only if correct type is retrieve |             # set attribute on the blender node only if correct type is retrieve | ||||||
|             if p not in ("parent", "scene"): |             if p not in ('parent', 'scene'): | ||||||
|                 setattr(node.bl_node, p, getattr(node, p)) |                 setattr(node.bl_node, p, getattr(node, p)) | ||||||
| 
 | 
 | ||||||
|         node.inputs = [ |         node.inputs = [Input.from_dict(ipt_data, node) for ipt_data in data['inputs'].values()] | ||||||
|             Input.from_dict(ipt_data, node) for ipt_data in data["inputs"].values() |         node.outputs = [Output.from_dict(opt_data, node) for opt_data in data['outputs'].values()] | ||||||
|         ] |  | ||||||
|         node.outputs = [ |  | ||||||
|             Output.from_dict(opt_data, node) for opt_data in data["outputs"].values() |  | ||||||
|         ] |  | ||||||
|         return node |         return node | ||||||
| 
 | 
 | ||||||
|     def dump(self): |     def dump(self): | ||||||
| @ -140,6 +138,7 @@ class Node: | |||||||
|         """ |         """ | ||||||
| 
 | 
 | ||||||
|         for prop_id in self.parameters: |         for prop_id in self.parameters: | ||||||
|  | 
 | ||||||
|             if not hasattr(self, prop_id): |             if not hasattr(self, prop_id): | ||||||
|                 continue |                 continue | ||||||
| 
 | 
 | ||||||
| @ -155,9 +154,9 @@ class Node: | |||||||
| 
 | 
 | ||||||
|             self.data[prop_id] = attr_value |             self.data[prop_id] = attr_value | ||||||
| 
 | 
 | ||||||
|         self.data["id"] = self.id |         self.data['id'] = self.id | ||||||
|         self.data["inputs"] = {ipt.id: ipt.dump() for ipt in self.inputs} |         self.data['inputs'] = {ipt.id: ipt.dump() for ipt in self.inputs} | ||||||
|         self.data["outputs"] = {opt.id: opt.dump() for opt in self.outputs} |         self.data['outputs'] = {opt.id: opt.dump() for opt in self.outputs} | ||||||
| 
 | 
 | ||||||
|         return self.data |         return self.data | ||||||
| 
 | 
 | ||||||
| @ -196,6 +195,7 @@ class Link: | |||||||
|     """Blender Link abstraction.""" |     """Blender Link abstraction.""" | ||||||
| 
 | 
 | ||||||
|     def __init__(self, bl_link, parent): |     def __init__(self, bl_link, parent): | ||||||
|  | 
 | ||||||
|         self.bl_link = bl_link |         self.bl_link = bl_link | ||||||
|         self.tree = parent |         self.tree = parent | ||||||
|         self.id = hex(id(self.bl_link)) |         self.id = hex(id(self.bl_link)) | ||||||
| @ -206,6 +206,7 @@ class Link: | |||||||
|         self.data = {} |         self.data = {} | ||||||
| 
 | 
 | ||||||
|     def dump(self): |     def dump(self): | ||||||
|         self.data["id"] = self.id | 
 | ||||||
|  |         self.data['id'] = self.id | ||||||
| 
 | 
 | ||||||
|         return self.data |         return self.data | ||||||
|  | |||||||
| @ -8,6 +8,7 @@ class NodeTree: | |||||||
|     """Blender node tree abstraction.""" |     """Blender node tree abstraction.""" | ||||||
| 
 | 
 | ||||||
|     def __init__(self, bl_node_tree): |     def __init__(self, bl_node_tree): | ||||||
|  | 
 | ||||||
|         self.bl_node_tree = bl_node_tree |         self.bl_node_tree = bl_node_tree | ||||||
| 
 | 
 | ||||||
|         self.data = {} |         self.data = {} | ||||||
| @ -27,12 +28,8 @@ class NodeTree: | |||||||
|         Returns: |         Returns: | ||||||
|             dict: Nodes and links as dict.  |             dict: Nodes and links as dict.  | ||||||
|         """ |         """ | ||||||
|         self.data["nodes"] = { |         self.data['nodes'] = {n.id: n.dump() for n in self.nodes if not select_only or (select_only and n.select)} | ||||||
|             n.id: n.dump() |         self.data['links'] = [l.id for l in self.links] | ||||||
|             for n in self.nodes |  | ||||||
|             if not select_only or (select_only and n.select) |  | ||||||
|         } |  | ||||||
|         self.data["links"] = [l.id for l in self.links] |  | ||||||
| 
 | 
 | ||||||
|         return self.data |         return self.data | ||||||
| 
 | 
 | ||||||
| @ -49,7 +46,8 @@ class NodeTree: | |||||||
| 
 | 
 | ||||||
|         self.data = data |         self.data = data | ||||||
| 
 | 
 | ||||||
|         for node_id, node_data in self.data["nodes"].items(): |         for node_id, node_data in self.data['nodes'].items(): | ||||||
|  | 
 | ||||||
|             new_node = Node.from_dict(node_data, self) |             new_node = Node.from_dict(node_data, self) | ||||||
|             self.nodes.append(new_node) |             self.nodes.append(new_node) | ||||||
| 
 | 
 | ||||||
| @ -57,15 +55,15 @@ class NodeTree: | |||||||
| 
 | 
 | ||||||
|             for ipt in new_node.inputs: |             for ipt in new_node.inputs: | ||||||
|                 if ipt.is_linked: |                 if ipt.is_linked: | ||||||
|                     connections.setdefault(ipt.link, {})["input"] = ipt.bl_input |                     connections.setdefault(ipt.link, {})['input'] = ipt.bl_input | ||||||
| 
 | 
 | ||||||
|             for opt in new_node.outputs: |             for opt in new_node.outputs: | ||||||
|                 if opt.is_linked: |                 if opt.is_linked: | ||||||
|                     for link in opt.link: |                     for link in opt.link: | ||||||
|                         connections.setdefault(link, {})["output"] = opt.bl_output |                         connections.setdefault(link, {})['output'] = opt.bl_output | ||||||
| 
 | 
 | ||||||
|         for link_id in self.data["links"]: |         for link_id in self.data['links']: | ||||||
|             ipt = connections[link_id]["input"] |             ipt = connections[link_id]['input'] | ||||||
|             opt = connections[link_id]["output"] |             opt = connections[link_id]['output'] | ||||||
| 
 | 
 | ||||||
|             self.bl_node_tree.links.new(ipt, opt) |             self.bl_node_tree.links.new(ipt, opt) | ||||||
|  | |||||||
| @ -1,5 +1,9 @@ | |||||||
|  | 
 | ||||||
|  | 
 | ||||||
| class Socket: | class Socket: | ||||||
|  | 
 | ||||||
|     def __init__(self, bl_socket, tree): |     def __init__(self, bl_socket, tree): | ||||||
|  | 
 | ||||||
|         self.tree = tree |         self.tree = tree | ||||||
|         self.bl_socket = bl_socket |         self.bl_socket = bl_socket | ||||||
|         self.data = {} |         self.data = {} | ||||||
| @ -10,11 +14,12 @@ class Socket: | |||||||
| 
 | 
 | ||||||
|         self._value = None |         self._value = None | ||||||
| 
 | 
 | ||||||
|         if hasattr(bl_socket, "default_value"): |         if hasattr(bl_socket, 'default_value'): | ||||||
|             self._value = bl_socket.default_value |             self._value = bl_socket.default_value | ||||||
| 
 | 
 | ||||||
|     @property |     @property | ||||||
|     def value(self): |     def value(self): | ||||||
|  | 
 | ||||||
|         if not isinstance(self._value, (str, int, float, bool)): |         if not isinstance(self._value, (str, int, float, bool)): | ||||||
|             self._value = [v for v in self._value] |             self._value = [v for v in self._value] | ||||||
| 
 | 
 | ||||||
| @ -27,15 +32,16 @@ class Socket: | |||||||
|         return self._value |         return self._value | ||||||
| 
 | 
 | ||||||
|     def to_dict(self): |     def to_dict(self): | ||||||
|         self.data["id"] = self.id |         self.data['id'] = self.id | ||||||
|         self.data["value"] = self.value |         self.data['value'] = self.value | ||||||
|         self.data["identifier"] = self.identifier |         self.data['identifier'] = self.identifier | ||||||
|         self.data["is_linked"] = self.is_linked |         self.data['is_linked'] = self.is_linked | ||||||
|         self.data["link"] = self.get_link() |         self.data['link'] = self.get_link() | ||||||
|         return self.data |         return self.data | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class Input(Socket): | class Input(Socket): | ||||||
|  | 
 | ||||||
|     def __init__(self, bl_input, tree): |     def __init__(self, bl_input, tree): | ||||||
|         super().__init__(bl_input, tree) |         super().__init__(bl_input, tree) | ||||||
| 
 | 
 | ||||||
| @ -43,8 +49,10 @@ class Input(Socket): | |||||||
| 
 | 
 | ||||||
|     @classmethod |     @classmethod | ||||||
|     def from_dict(cls, data, node): |     def from_dict(cls, data, node): | ||||||
|  | 
 | ||||||
|         for bl_ipt in node.bl_node.inputs: |         for bl_ipt in node.bl_node.inputs: | ||||||
|             if bl_ipt.identifier != data["identifier"]: | 
 | ||||||
|  |             if bl_ipt.identifier != data['identifier']: | ||||||
|                 continue |                 continue | ||||||
| 
 | 
 | ||||||
|             new_ipt = cls(bl_ipt, node.tree) |             new_ipt = cls(bl_ipt, node.tree) | ||||||
| @ -55,6 +63,7 @@ class Input(Socket): | |||||||
|             return new_ipt |             return new_ipt | ||||||
| 
 | 
 | ||||||
|     def get_link(self): |     def get_link(self): | ||||||
|  | 
 | ||||||
|         if not self.is_linked: |         if not self.is_linked: | ||||||
|             return None |             return None | ||||||
| 
 | 
 | ||||||
| @ -65,6 +74,7 @@ class Input(Socket): | |||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class Output(Socket): | class Output(Socket): | ||||||
|  | 
 | ||||||
|     def __init__(self, bl_output, tree): |     def __init__(self, bl_output, tree): | ||||||
|         super().__init__(bl_output, tree) |         super().__init__(bl_output, tree) | ||||||
| 
 | 
 | ||||||
| @ -72,8 +82,10 @@ class Output(Socket): | |||||||
| 
 | 
 | ||||||
|     @classmethod |     @classmethod | ||||||
|     def from_dict(cls, data, node): |     def from_dict(cls, data, node): | ||||||
|  | 
 | ||||||
|         for bl_opt in node.bl_node.outputs: |         for bl_opt in node.bl_node.outputs: | ||||||
|             if bl_opt.identifier != data["identifier"]: | 
 | ||||||
|  |             if bl_opt.identifier != data['identifier']: | ||||||
|                 continue |                 continue | ||||||
| 
 | 
 | ||||||
|             new_opt = cls(bl_opt, node.tree) |             new_opt = cls(bl_opt, node.tree) | ||||||
| @ -84,6 +96,7 @@ class Output(Socket): | |||||||
|             return new_opt |             return new_opt | ||||||
| 
 | 
 | ||||||
|     def get_link(self): |     def get_link(self): | ||||||
|  | 
 | ||||||
|         links = [] |         links = [] | ||||||
| 
 | 
 | ||||||
|         if not self.is_linked: |         if not self.is_linked: | ||||||
|  | |||||||
							
								
								
									
										320
									
								
								core/dumper.py
									
									
									
									
									
								
							
							
						
						
									
										320
									
								
								core/dumper.py
									
									
									
									
									
								
							| @ -7,12 +7,13 @@ from copy import copy | |||||||
| from os.path import abspath | from os.path import abspath | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
| def get_default(prop): | def get_default(prop): | ||||||
|     """Get the default value of a bl property""" |     """Get the default value of a bl property""" | ||||||
| 
 | 
 | ||||||
|     if getattr(prop, "is_array", False): |     if getattr(prop, 'is_array', False): | ||||||
|         return list(prop.default_array) |         return list(prop.default_array) | ||||||
|     elif hasattr(prop, "default"): |     elif hasattr(prop, 'default'): | ||||||
|         return prop.default |         return prop.default | ||||||
|          |          | ||||||
| 
 | 
 | ||||||
| @ -27,7 +28,7 @@ def get_dumper(bl_object, fallback=None): | |||||||
| 
 | 
 | ||||||
| def get_bl_object(data): | def get_bl_object(data): | ||||||
|     """Find the bl object for loading data into it depending on the type and the context""" |     """Find the bl object for loading data into it depending on the type and the context""" | ||||||
|     if data.get("_new", {}).get("type") == "GeometryNodeTree": |     if data.get('_new', {}).get('type') == 'GeometryNodeTree': | ||||||
|         return bpy.context.object.modifiers.active.node_group |         return bpy.context.object.modifiers.active.node_group | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| @ -49,7 +50,7 @@ def load(data, bl_object=None): | |||||||
|     """Generic Load to create an object from a dict""" |     """Generic Load to create an object from a dict""" | ||||||
| 
 | 
 | ||||||
|     Dumper.pointers.clear() |     Dumper.pointers.clear() | ||||||
|     # print(Dumper.pointers) |     #print(Dumper.pointers) | ||||||
| 
 | 
 | ||||||
|     if bl_object is None: |     if bl_object is None: | ||||||
|         bl_object = get_bl_object(data) |         bl_object = get_bl_object(data) | ||||||
| @ -70,23 +71,19 @@ def set_attribute(bl_object, attr, value): | |||||||
| class Dumper: | class Dumper: | ||||||
|     pointers = {} |     pointers = {} | ||||||
|     includes = [] |     includes = [] | ||||||
|     excludes = ["rna_type", "bl_rna", "id_data", "depsgraph"] |     excludes = ["rna_type", "bl_rna", 'id_data', 'depsgraph'] | ||||||
|      |      | ||||||
|     @classmethod  |     @classmethod  | ||||||
|     def properties(cls, bl_object): |     def properties(cls, bl_object): | ||||||
|         if cls.includes and not cls.excludes: |         if cls.includes and not cls.excludes: | ||||||
|             return [bl_object.bl_rna.properties[p] for p in cls.includes] |             return [bl_object.bl_rna.properties[p] for p in cls.includes] | ||||||
|         else: |         else: | ||||||
|             return [ |             return [ p for p in bl_object.bl_rna.properties if not | ||||||
|                 p |                      p.identifier.startswith('bl_') and p.identifier not in cls.excludes] | ||||||
|                 for p in bl_object.bl_rna.properties |  | ||||||
|                 if not p.identifier.startswith("bl_") |  | ||||||
|                 and p.identifier not in cls.excludes |  | ||||||
|             ] |  | ||||||
|      |      | ||||||
|     @classmethod |     @classmethod | ||||||
|     def new(cls, data): |     def new(cls, data): | ||||||
|         print(f"New not implemented for data {data}") |         print(f'New not implemented for data {data}') | ||||||
| 
 | 
 | ||||||
|     @classmethod   |     @classmethod   | ||||||
|     def load(cls, data, bl_object=None): |     def load(cls, data, bl_object=None): | ||||||
| @ -96,36 +93,34 @@ class Dumper: | |||||||
|         if bl_object is None: |         if bl_object is None: | ||||||
|             return |             return | ||||||
|          |          | ||||||
|         # pprint(data) |         #pprint(data) | ||||||
|         if bl_pointer := data.get("bl_pointer"): |         if bl_pointer := data.get('bl_pointer'): | ||||||
|             cls.pointers[bl_pointer] = bl_object |             cls.pointers[bl_pointer] = bl_object | ||||||
| 
 | 
 | ||||||
|         props = cls.properties(bl_object) |         props = cls.properties(bl_object) | ||||||
|         for key, value in sorted( |         for key, value in sorted(data.items(), key=lambda x: props.index(x[0]) if x[0] in props else 0): | ||||||
|             data.items(), key=lambda x: props.index(x[0]) if x[0] in props else 0 |             if key.startswith('_') or key not in bl_object.bl_rna.properties: | ||||||
|         ): |  | ||||||
|             if key.startswith("_") or key not in bl_object.bl_rna.properties: |  | ||||||
|                 continue |                 continue | ||||||
|              |              | ||||||
|             prop = bl_object.bl_rna.properties[key] |             prop = bl_object.bl_rna.properties[key] | ||||||
|             attr = getattr(bl_object, key) |             attr = getattr(bl_object, key) | ||||||
|                                      |                                      | ||||||
|             if prop.type == "COLLECTION": |             if prop.type == 'COLLECTION': | ||||||
|                 dumper = PropCollection |                 dumper = PropCollection | ||||||
|                 if hasattr(attr, "bl_rna"): |                 if hasattr(attr, 'bl_rna'):  | ||||||
|                     bl_type = attr.bl_rna.type_recast() |                     bl_type = attr.bl_rna.type_recast() | ||||||
|                     dumper = get_dumper(bl_type, fallback=PropCollection) |                     dumper = get_dumper(bl_type, fallback=PropCollection) | ||||||
|                  |                  | ||||||
|                 dumper.load(value, attr) |                 dumper.load(value, attr) | ||||||
|                 continue |                 continue | ||||||
|                  |                  | ||||||
|             elif prop.type == "POINTER": |             elif prop.type == 'POINTER': | ||||||
|                 # if key == 'node_tree': |                 # if key == 'node_tree': | ||||||
|                 #     print('--------------') |                 #     print('--------------') | ||||||
|                 #     print(bl_object, value) |                 #     print(bl_object, value) | ||||||
|                 #     print(cls.pointers) |                 #     print(cls.pointers) | ||||||
| 
 | 
 | ||||||
|                 if isinstance(value, int):  # It's a pointer |                 if isinstance(value, int): # It's a pointer | ||||||
|                     if value not in cls.pointers: |                     if value not in cls.pointers: | ||||||
|                         print(bl_object, "not loaded yet", prop) |                         print(bl_object, "not loaded yet", prop) | ||||||
|                     value = cls.pointers[value] |                     value = cls.pointers[value] | ||||||
| @ -138,17 +133,17 @@ class Dumper: | |||||||
|                     dumper = get_dumper(bl_type) |                     dumper = get_dumper(bl_type) | ||||||
| 
 | 
 | ||||||
|                     # If the pointer exist register the pointer then load data |                     # If the pointer exist register the pointer then load data | ||||||
|                     # print('-----', value) |                     #print('-----', value) | ||||||
|                     # pointer = |                     #pointer =  | ||||||
|                     if attr is None: |                     if attr is None: | ||||||
|                         attr = dumper.new(value) |                         attr = dumper.new(value) | ||||||
| 
 | 
 | ||||||
|                     dumper.load(value, attr) |                     dumper.load(value, attr) | ||||||
|                     # attr = getattr(bl_object, key) |                     #attr = getattr(bl_object, key) | ||||||
|                     # if not attr: |                     #if not attr: | ||||||
|                     cls.pointers[value["bl_pointer"]] = attr |                     cls.pointers[value['bl_pointer']] = attr | ||||||
| 
 | 
 | ||||||
|                     if hasattr(attr, "update"): |                     if hasattr(attr, 'update'): | ||||||
|                         attr.update() |                         attr.update() | ||||||
|                  |                  | ||||||
|                     value = attr |                     value = attr | ||||||
| @ -157,38 +152,39 @@ class Dumper: | |||||||
|                     set_attribute(bl_object, key, value) |                     set_attribute(bl_object, key, value) | ||||||
|                  |                  | ||||||
|                 # Some coll needs a manual update like curve mapping |                 # Some coll needs a manual update like curve mapping | ||||||
|                 if hasattr(attr, "update"): |                 if hasattr(attr, 'update'): | ||||||
|                     attr.update() |                     attr.update() | ||||||
|              |              | ||||||
|             elif not prop.is_readonly: |             elif not prop.is_readonly: | ||||||
|                 # print(key, value) |                 #print(key, value) | ||||||
|                 set_attribute(bl_object, key, value) |                 set_attribute(bl_object, key, value) | ||||||
|                 continue |                 continue | ||||||
|          |          | ||||||
|         # return bl_object |         #return bl_object | ||||||
| 
 | 
 | ||||||
|     @classmethod |     @classmethod | ||||||
|     def dump(cls, bl_object): |     def dump(cls, bl_object): | ||||||
|         if isinstance(bl_object, (str, int, float, dict, list, type(None))): |         if isinstance(bl_object, (str, int, float, dict, list, type(None))): | ||||||
|             return bl_object |             return bl_object | ||||||
|          |          | ||||||
|         # print('Dumping object', bl_object) |         #print('Dumping object', bl_object) | ||||||
| 
 | 
 | ||||||
|         data = {"bl_pointer": bl_object.as_pointer()} |         data = {"bl_pointer": bl_object.as_pointer()} | ||||||
|         cls.pointers[bl_object.as_pointer()] = bl_object |         cls.pointers[bl_object.as_pointer()] = bl_object | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
|         for prop in cls.properties(bl_object):             |         for prop in cls.properties(bl_object):             | ||||||
|             if not hasattr(bl_object, prop.identifier): |             if not hasattr(bl_object, prop.identifier): | ||||||
|                 print(f"{bl_object} has no attribute {prop.identifier}") |                 print(f'{bl_object} has no attribute {prop.identifier}') | ||||||
|                 continue |                 continue | ||||||
|              |              | ||||||
|             # print(prop.identifier) |             #print(prop.identifier) | ||||||
| 
 | 
 | ||||||
|             value = getattr(bl_object, prop.identifier) |             value = getattr(bl_object, prop.identifier) | ||||||
|              |              | ||||||
|             # Not storing default value |             # Not storing default value | ||||||
|             if prop.identifier not in cls.includes: |             if prop.identifier not in cls.includes: | ||||||
|                 if (array := getattr(prop, "default_array", None)) and value == array: |                 if (array := getattr(prop, 'default_array', None)) and value == array: | ||||||
|                     continue |                     continue | ||||||
|                 if isinstance(value, (str, int, float)) and value == prop.default: |                 if isinstance(value, (str, int, float)) and value == prop.default: | ||||||
|                     continue |                     continue | ||||||
| @ -196,11 +192,11 @@ class Dumper: | |||||||
|             if getattr(prop, "is_array", False): |             if getattr(prop, "is_array", False): | ||||||
|                 value = PropArray.dump(value) |                 value = PropArray.dump(value) | ||||||
|              |              | ||||||
|             elif prop.type == "COLLECTION": |             elif prop.type == 'COLLECTION': | ||||||
|                 value = PropCollection.dump(value) |                 value = PropCollection.dump(value) | ||||||
|                  |                  | ||||||
|             elif prop.type == "POINTER" and value: |             elif prop.type == 'POINTER' and value: | ||||||
|                 # if prop.identifier == 'image': |                 #if prop.identifier == 'image': | ||||||
|                 #    print(bl_object, cls.pointers) |                 #    print(bl_object, cls.pointers) | ||||||
|                 if value.as_pointer() in cls.pointers: |                 if value.as_pointer() in cls.pointers: | ||||||
|                     value = value.as_pointer() |                     value = value.as_pointer() | ||||||
| @ -238,6 +234,7 @@ class PropCollection(Dumper): | |||||||
|         # Value cannot be None |         # Value cannot be None | ||||||
|         return [v for v in values if v is not None] |         return [v for v in values if v is not None] | ||||||
|          |          | ||||||
|  |      | ||||||
|     @classmethod |     @classmethod | ||||||
|     def load(cls, values, coll): |     def load(cls, values, coll): | ||||||
|         if not values: |         if not values: | ||||||
| @ -245,54 +242,53 @@ class PropCollection(Dumper): | |||||||
| 
 | 
 | ||||||
|         dumper = None |         dumper = None | ||||||
|          |          | ||||||
|         if not hasattr(coll, "new"):  # Static collection |         if not hasattr(coll, 'new'): # Static collection | ||||||
|             for item, value in zip(coll, values): |             for item, value in zip(coll, values): | ||||||
|                 dumper = dumper or get_dumper(item) |                 dumper = dumper or get_dumper(item) | ||||||
|                 dumper.load(value, item) |                 dumper.load(value, item) | ||||||
| 
 | 
 | ||||||
|             return      |             return      | ||||||
| 
 | 
 | ||||||
|         new_func = coll.bl_rna.functions["new"] |         new_func = coll.bl_rna.functions['new'] | ||||||
|         for i, value in enumerate(values): |         for i, value in enumerate(values): | ||||||
|             if value.get("_new"): |                              | ||||||
|                 params = value["_new"] |             if value.get('_new'): | ||||||
|  |                 params = value['_new'] | ||||||
|             else: |             else: | ||||||
|                 params = { |                 params = {k: value.get(k, get_default(v)) for k, v in new_func.parameters.items()[:-1]} | ||||||
|                     k: value.get(k, get_default(v)) |  | ||||||
|                     for k, v in new_func.parameters.items()[:-1] |  | ||||||
|                 } |  | ||||||
|              |              | ||||||
|             # Replace arg pointer with bl object |             # Replace arg pointer with bl object | ||||||
|             valid_pointers = True |             valid_pointers = True | ||||||
|             for param in coll.bl_rna.functions["new"].parameters: |             for param in coll.bl_rna.functions['new'].parameters: | ||||||
|                 if param.identifier not in params or param.type != "POINTER": |                 if param.identifier not in params or param.type != 'POINTER': | ||||||
|                     continue |                     continue | ||||||
|                  |                  | ||||||
|                 pointer_id = params[param.identifier] |                 pointer_id = params[param.identifier] | ||||||
|                 if bl_object := cls.pointers.get(pointer_id): |                 if bl_object := cls.pointers.get(pointer_id): | ||||||
|                     params[param.identifier] = bl_object |                     params[param.identifier] = bl_object | ||||||
|                 else: |                 else: | ||||||
|                     print(f"No Pointer found for param {param.identifier} of {coll}") |                     print(f'No Pointer found for param {param.identifier} of {coll}') | ||||||
|                     valid_pointers = False |                     valid_pointers = False | ||||||
|              |              | ||||||
|             if not valid_pointers: |             if not valid_pointers: | ||||||
|                 continue |                 continue | ||||||
| 
 | 
 | ||||||
|                 # print(param.identifier, cls.pointers[pointer_id]) |                 #print(param.identifier, cls.pointers[pointer_id]) | ||||||
| 
 | 
 | ||||||
|             try: |             try: | ||||||
|  | 
 | ||||||
|                 item = coll.new(**params) |                 item = coll.new(**params) | ||||||
|             except RuntimeError as e: |             except RuntimeError as e: | ||||||
|                 # print(e, coll.data) |                 #print(e, coll.data) | ||||||
|                 # print() |                 #print() | ||||||
|                 try: |                 try: | ||||||
|                     item = coll[i] |                     item = coll[i] | ||||||
|                 except IndexError as e: |                 except IndexError as e: | ||||||
|                     # print(e, coll.data) |                     #print(e, coll.data) | ||||||
|                     break |                     break | ||||||
|              |              | ||||||
|             dumper = get_dumper(item)     |             dumper = get_dumper(item)     | ||||||
|             dumper.load(value, item)  # (item, value) |             dumper.load(value, item)#(item, value) | ||||||
|      |      | ||||||
|      |      | ||||||
| class PropArray(Dumper): | class PropArray(Dumper): | ||||||
| @ -311,25 +307,19 @@ class PropArray(Dumper): | |||||||
| 
 | 
 | ||||||
| class NodeSocket(Dumper): | class NodeSocket(Dumper): | ||||||
|     bl_type = bpy.types.NodeSocket |     bl_type = bpy.types.NodeSocket | ||||||
|     excludes = Dumper.excludes + [ |     excludes = Dumper.excludes + ["node", "links", "display_shape", "rna_type", "link_limit"] | ||||||
|         "node", |  | ||||||
|         "links", |  | ||||||
|         "display_shape", |  | ||||||
|         "rna_type", |  | ||||||
|         "link_limit", |  | ||||||
|     ] |  | ||||||
| 
 | 
 | ||||||
|     @classmethod   |     @classmethod   | ||||||
|     def dump(cls, socket): |     def dump(cls, socket): | ||||||
|         if socket.is_unavailable: |         if socket.is_unavailable: | ||||||
|             return None |             return None | ||||||
| 
 | 
 | ||||||
|         # cls.pointers[socket.as_pointer()] = socket |         #cls.pointers[socket.as_pointer()] = socket | ||||||
|          |          | ||||||
|         data = super().dump(socket) |         data = super().dump(socket) | ||||||
|                  |                  | ||||||
|         # data["_id"] = socket.as_pointer() |         #data["_id"] = socket.as_pointer() | ||||||
|         # data.pop('name', '') |         #data.pop('name', '') | ||||||
|          |          | ||||||
|         return data |         return data | ||||||
| 
 | 
 | ||||||
| @ -349,10 +339,9 @@ class NodeLink(Dumper): | |||||||
|      |      | ||||||
|     @classmethod   |     @classmethod   | ||||||
|     def dump(cls, link): |     def dump(cls, link): | ||||||
|         return { |         return {"_new": { | ||||||
|             "_new": { |  | ||||||
|                     "input": link.from_socket.as_pointer(),  |                     "input": link.from_socket.as_pointer(),  | ||||||
|                 "output": link.to_socket.as_pointer(), |                     "output": link.to_socket.as_pointer() | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
|          |          | ||||||
| @ -363,31 +352,34 @@ class NodeTreeInterfaceSocket(Dumper): | |||||||
|      |      | ||||||
|     @classmethod   |     @classmethod   | ||||||
|     def dump(cls, socket): |     def dump(cls, socket): | ||||||
|         # cls.pointers[socket.as_pointer()] = socket |         #cls.pointers[socket.as_pointer()] = socket | ||||||
|          |          | ||||||
|         data = super().dump(socket) |         data = super().dump(socket) | ||||||
|         # data["_id"] = socket.as_pointer() |         #data["_id"] = socket.as_pointer() | ||||||
|          |          | ||||||
|         data["_new"] = {"name": data.get("name", "")} |         data['_new'] = {"name": data.get('name', '')} | ||||||
|  | 
 | ||||||
|  |         if socket.item_type == 'SOCKET': | ||||||
|  |             data['_new']["in_out"] = socket.in_out | ||||||
| 
 | 
 | ||||||
|         if socket.item_type == "SOCKET": |  | ||||||
|             data["_new"]["in_out"] = socket.in_out |  | ||||||
|          |          | ||||||
|         # It's a real panel not the interface root |         # It's a real panel not the interface root | ||||||
|         if socket.parent.parent:  |         if socket.parent.parent:  | ||||||
|             data["parent"] = socket.parent.as_pointer() |             data['parent'] = socket.parent.as_pointer() | ||||||
|          |          | ||||||
|         return data |         return data | ||||||
|      |      | ||||||
| 
 | 
 | ||||||
| class NodeSockets(PropCollection): | class NodeSockets(PropCollection): | ||||||
|  | 
 | ||||||
|     @classmethod   |     @classmethod   | ||||||
|     def load(cls, values, coll): |     def load(cls, values, coll): | ||||||
|         # return | 
 | ||||||
|  |         #return | ||||||
|          |          | ||||||
|         node_sockets = [s for s in coll if not s.is_unavailable] |         node_sockets = [s for s in coll if not s.is_unavailable] | ||||||
|         for socket, value in zip(node_sockets, values): |         for socket, value in zip(node_sockets, values): | ||||||
|             cls.pointers[value["bl_pointer"]] = socket |             cls.pointers[value['bl_pointer']] = socket | ||||||
|             Dumper.load(value, socket) |             Dumper.load(value, socket) | ||||||
|             # for k, v in value.items(): |             # for k, v in value.items(): | ||||||
|             #     if k not in socket.bl_rna.properties: |             #     if k not in socket.bl_rna.properties: | ||||||
| @ -427,27 +419,20 @@ class NodeOutputs(NodeSockets): | |||||||
| 
 | 
 | ||||||
| class Node(Dumper): | class Node(Dumper): | ||||||
|     bl_type = bpy.types.Node |     bl_type = bpy.types.Node | ||||||
|     excludes = Dumper.excludes + [ |     excludes = Dumper.excludes + ["dimensions", "height", "internal_links", "paired_output"] | ||||||
|         "dimensions", |  | ||||||
|         "height", |  | ||||||
|         "internal_links", |  | ||||||
|         "paired_output", |  | ||||||
|     ] |  | ||||||
| 
 | 
 | ||||||
|     @classmethod   |     @classmethod   | ||||||
|     def dump(cls, node=None): |     def dump(cls, node=None): | ||||||
|         # cls.pointers[node.as_pointer()] = node |         #cls.pointers[node.as_pointer()] = node | ||||||
|          |          | ||||||
|         data = super().dump(node) |         data = super().dump(node) | ||||||
|         # data["_id"] = node.as_pointer() |         #data["_id"] = node.as_pointer() | ||||||
|         data["_new"] = { |         data["_new"] = {"type": node.bl_rna.identifier} # 'node_tree': node.id_data.as_pointer() | ||||||
|             "type": node.bl_rna.identifier |  | ||||||
|         }  # 'node_tree': node.id_data.as_pointer() |  | ||||||
| 
 | 
 | ||||||
|         if paired_output := getattr(node, "paired_output", None): |         if paired_output := getattr(node, "paired_output", None): | ||||||
|             data["_pair_with_output"] = paired_output.as_pointer() |             data["_pair_with_output"] = paired_output.as_pointer() | ||||||
|          |          | ||||||
|         # if node.parent: |         #if node.parent: | ||||||
|         #    data['location'] -= Vector()node.parent.location |         #    data['location'] -= Vector()node.parent.location | ||||||
| 
 | 
 | ||||||
|         return data |         return data | ||||||
| @ -456,15 +441,15 @@ class Node(Dumper): | |||||||
|     def load(cls, data, node): |     def load(cls, data, node): | ||||||
|         if node is None: |         if node is None: | ||||||
|             return |             return | ||||||
|         # cls.pointers[data['bl_pointer']] = node |         #cls.pointers[data['bl_pointer']] = node | ||||||
|          |          | ||||||
|         inputs = copy(data.pop("inputs", [])) |         inputs = copy(data.pop('inputs', [])) | ||||||
|         outputs = copy(data.pop("outputs", [])) |         outputs = copy(data.pop('outputs', [])) | ||||||
| 
 | 
 | ||||||
|         super().load(data, node) |         super().load(data, node) | ||||||
| 
 | 
 | ||||||
|         data["inputs"] = inputs |         data['inputs'] = inputs | ||||||
|         data["outputs"] = outputs |         data['outputs'] = outputs | ||||||
| 
 | 
 | ||||||
|         # Loading input and outputs after the properties |         # Loading input and outputs after the properties | ||||||
|         super().load({"inputs": inputs, "outputs": outputs}, node) |         super().load({"inputs": inputs, "outputs": outputs}, node) | ||||||
| @ -472,7 +457,7 @@ class Node(Dumper): | |||||||
|         if node.parent: |         if node.parent: | ||||||
|             node.location += node.parent.location |             node.location += node.parent.location | ||||||
|          |          | ||||||
|         # if node.type != 'FRAME': |         #if node.type != 'FRAME': | ||||||
|         #    node.location.y -= 500 |         #    node.location.y -= 500 | ||||||
|          |          | ||||||
| 
 | 
 | ||||||
| @ -487,19 +472,20 @@ class NodeTreeInterface(Dumper): | |||||||
|      |      | ||||||
|     @classmethod   |     @classmethod   | ||||||
|     def load(cls, data, interface):     |     def load(cls, data, interface):     | ||||||
|         print("Load Interface") |  | ||||||
| 
 | 
 | ||||||
|         for value in data.get("items_tree", []): |         print('Load Interface') | ||||||
|             item_type = value.get("item_type", "SOCKET") | 
 | ||||||
|             if item_type == "SOCKET": |         for value in data.get('items_tree', []): | ||||||
|                 item = interface.new_socket(**value["_new"]) |             item_type = value.get('item_type', 'SOCKET') | ||||||
|             elif item_type == "PANEL": |             if item_type == 'SOCKET': | ||||||
|                 # print(value['_new']) |                 item = interface.new_socket(**value['_new']) | ||||||
|                 item = interface.new_panel(**value["_new"]) |             elif item_type == 'PANEL': | ||||||
|  |                 #print(value['_new']) | ||||||
|  |                 item = interface.new_panel(**value['_new']) | ||||||
|              |              | ||||||
|             NodeTreeInterfaceSocket.load(value, item) |             NodeTreeInterfaceSocket.load(value, item) | ||||||
|      |      | ||||||
|         interface.active_index = data.get("active_index", 0) |         interface.active_index = data.get('active_index', 0) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class Nodes(PropCollection): | class Nodes(PropCollection): | ||||||
| @ -511,16 +497,13 @@ class Nodes(PropCollection): | |||||||
| 
 | 
 | ||||||
|         # Pair zone input and output |         # Pair zone input and output | ||||||
|         for node_data in values: |         for node_data in values: | ||||||
|             if paired_output_id := node_data.get("_pair_with_output", None): |             if paired_output_id := node_data.get('_pair_with_output', None): | ||||||
|                 node = cls.pointers[node_data["bl_pointer"]] |                 node = cls.pointers[node_data['bl_pointer']] | ||||||
|                 node.pair_with_output(cls.pointers[paired_output_id]) |                 node.pair_with_output(cls.pointers[paired_output_id]) | ||||||
| 
 | 
 | ||||||
|                 # print(node, node_data['outputs']) |                 #print(node, node_data['outputs']) | ||||||
| 
 | 
 | ||||||
|                 Dumper.load( |                 Dumper.load({"inputs": node_data['inputs'], "outputs": node_data['outputs']}, node) | ||||||
|                     {"inputs": node_data["inputs"], "outputs": node_data["outputs"]}, |  | ||||||
|                     node, |  | ||||||
|                 ) |  | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class NodeTree(Dumper): | class NodeTree(Dumper): | ||||||
| @ -530,42 +513,32 @@ class NodeTree(Dumper): | |||||||
| 
 | 
 | ||||||
|     @classmethod   |     @classmethod   | ||||||
|     def new(cls, data): |     def new(cls, data): | ||||||
|         if link := data.get("_link"): |         if link := data.get('_link'): | ||||||
|             with bpy.data.libraries.load(link["filepath"], link=True) as ( |             with bpy.data.libraries.load(link['filepath'], link=True) as (data_from, data_to): | ||||||
|                 data_from, |                 setattr(data_to, link['data_type'], [link['name']]) | ||||||
|                 data_to, |             return getattr(data_to, link['data_type'])[0] | ||||||
|             ): |  | ||||||
|                 setattr(data_to, link["data_type"], [link["name"]]) |  | ||||||
|             return getattr(data_to, link["data_type"])[0] |  | ||||||
| 
 | 
 | ||||||
|         return bpy.data.node_groups.new(**data["_new"]) |         return bpy.data.node_groups.new(**data["_new"]) | ||||||
| 
 | 
 | ||||||
|     @classmethod   |     @classmethod   | ||||||
|     def dump(cls, node_tree): |     def dump(cls, node_tree): | ||||||
|         if node_tree.library: |         if node_tree.library: | ||||||
|             data = {"bl_pointer": node_tree.as_pointer()} |             data = {'bl_pointer': node_tree.as_pointer()} | ||||||
|             filepath = abspath( |             filepath = abspath(bpy.path.abspath(node_tree.library.filepath, library=node_tree.library.library)) | ||||||
|                 bpy.path.abspath( |             data["_link"] = {"filepath": filepath, "data_type": 'node_groups', 'name': node_tree.name} | ||||||
|                     node_tree.library.filepath, library=node_tree.library.library |  | ||||||
|                 ) |  | ||||||
|             ) |  | ||||||
|             data["_link"] = { |  | ||||||
|                 "filepath": filepath, |  | ||||||
|                 "data_type": "node_groups", |  | ||||||
|                 "name": node_tree.name, |  | ||||||
|             } |  | ||||||
|         else: |         else: | ||||||
|             data = super().dump(node_tree) |             data = super().dump(node_tree) | ||||||
|             data["_new"] = {"type": node_tree.bl_rna.identifier, "name": node_tree.name} |             data["_new"] = {"type": node_tree.bl_rna.identifier, 'name': node_tree.name} | ||||||
|          |          | ||||||
|         return data |         return data | ||||||
|      |      | ||||||
|      |      | ||||||
| class Points(PropCollection): | class Points(PropCollection): | ||||||
|  | 
 | ||||||
|     @classmethod |     @classmethod | ||||||
|     def load(cls, values, coll): |     def load(cls, values, coll): | ||||||
|         new_func = coll.bl_rna.functions["new"] |         new_func = coll.bl_rna.functions['new'] | ||||||
|         params = {k: get_default(v) + 1.1 for k, v in new_func.parameters.items()[:-1]} |         params = {k: get_default(v)+1.1 for k, v in new_func.parameters.items()[:-1]} | ||||||
|                  |                  | ||||||
|         # Match the same number of elements in collection |         # Match the same number of elements in collection | ||||||
|         if len(values) > len(coll): |         if len(values) > len(coll): | ||||||
| @ -574,8 +547,8 @@ class Points(PropCollection): | |||||||
|          |          | ||||||
|         for i, value in enumerate(values): |         for i, value in enumerate(values): | ||||||
|             Dumper.load(value, coll[i]) |             Dumper.load(value, coll[i]) | ||||||
|             # for k, v in value.items(): |             #for k, v in value.items(): | ||||||
|             # setattr(coll[i], k, v) |                 #setattr(coll[i], k, v) | ||||||
|                  |                  | ||||||
| 
 | 
 | ||||||
| class CurveMapPoints(Points): | class CurveMapPoints(Points): | ||||||
| @ -610,7 +583,7 @@ class AOVs(PropCollection): | |||||||
|     @classmethod |     @classmethod | ||||||
|     def load(cls, values, coll): |     def load(cls, values, coll): | ||||||
|         for value in values: |         for value in values: | ||||||
|             aov = coll.get(value["name"]) |             aov = coll.get(value['name']) | ||||||
| 
 | 
 | ||||||
|             if not aov: |             if not aov: | ||||||
|                 aov = coll.add() |                 aov = coll.add() | ||||||
| @ -622,7 +595,7 @@ class Image(Dumper): | |||||||
|     bl_type = bpy.types.Image |     bl_type = bpy.types.Image | ||||||
| 
 | 
 | ||||||
|     excludes = [] |     excludes = [] | ||||||
|     includes = ["name", "filepath"] |     includes = ['name', 'filepath'] | ||||||
| 
 | 
 | ||||||
|     @classmethod |     @classmethod | ||||||
|     def new(cls, data): |     def new(cls, data): | ||||||
| @ -632,20 +605,20 @@ class Image(Dumper): | |||||||
|         # if image is None: |         # if image is None: | ||||||
|         #     image = bpy.data.images.load(data['filepath']) |         #     image = bpy.data.images.load(data['filepath']) | ||||||
| 
 | 
 | ||||||
|         return bpy.data.images.load(data["filepath"], check_existing=True) |         return bpy.data.images.load(data['filepath'], check_existing=True) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class Material(Dumper): | class Material(Dumper): | ||||||
|     bl_type = bpy.types.Material |     bl_type = bpy.types.Material | ||||||
| 
 | 
 | ||||||
|     excludes = Dumper.excludes + ["preview", "original"] |     excludes = Dumper.excludes + ['preview', "original"] | ||||||
| 
 | 
 | ||||||
|     @classmethod |     @classmethod | ||||||
|     def new(cls, data): |     def new(cls, data): | ||||||
|         material = bpy.data.materials.get(data.get("name", "")) |         material = bpy.data.materials.get(data.get('name', '')) | ||||||
| 
 | 
 | ||||||
|         if material is None: |         if material is None: | ||||||
|             material = bpy.data.materials.new(data["name"]) |             material = bpy.data.materials.new(data['name']) | ||||||
|          |          | ||||||
|         return material |         return material | ||||||
| 
 | 
 | ||||||
| @ -653,25 +626,26 @@ class Material(Dumper): | |||||||
| class Object(Dumper): | class Object(Dumper): | ||||||
|     bl_type = bpy.types.Object |     bl_type = bpy.types.Object | ||||||
|     excludes = [] |     excludes = [] | ||||||
|     includes = ["name"] |     includes = ['name'] | ||||||
| 
 | 
 | ||||||
|     @classmethod |     @classmethod | ||||||
|     def new(cls, data): |     def new(cls, data): | ||||||
|         if name := data.get("name"): |         if name := data.get('name'): | ||||||
|             return bpy.data.objects.get(name) |             return bpy.data.objects.get(name) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class Scene(Dumper): | class Scene(Dumper): | ||||||
|     bl_type = bpy.types.Scene |     bl_type = bpy.types.Scene | ||||||
|     excludes = [] |     excludes = [] | ||||||
|     includes = ["name"] |     includes = ['name'] | ||||||
|  |      | ||||||
| 
 | 
 | ||||||
|     @classmethod |     @classmethod | ||||||
|     def new(cls, data): |     def new(cls, data): | ||||||
|         if scene := bpy.data.scenes.get(data.get("name", "")): |         if scene := bpy.data.scenes.get(data.get('name', '')): | ||||||
|             return scene |             return scene | ||||||
|          |          | ||||||
|         return bpy.data.scenes.new(name=data.get("name", "")) |         return bpy.data.scenes.new(name=data.get('name', '')) | ||||||
| 
 | 
 | ||||||
|     """ |     """ | ||||||
|     @classmethod   |     @classmethod   | ||||||
| @ -687,15 +661,14 @@ class Scene(Dumper): | |||||||
|         } |         } | ||||||
|         """ |         """ | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
| class Collection(Dumper): | class Collection(Dumper): | ||||||
|     bl_type = bpy.types.Collection |     bl_type = bpy.types.Collection | ||||||
|     includes = ["name"] |     includes = ['name'] | ||||||
|     excludes = [] |     excludes = [] | ||||||
| 
 | 
 | ||||||
|     @classmethod |     @classmethod | ||||||
|     def new(cls, data): |     def new(cls, data): | ||||||
|         if name := data.get("name"): |         if name := data.get('name'): | ||||||
|             return bpy.data.collections.get(name) |             return bpy.data.collections.get(name) | ||||||
| 
 | 
 | ||||||
|     # @classmethod |     # @classmethod | ||||||
| @ -709,15 +682,16 @@ class Collection(Dumper): | |||||||
| class CompositorNodeRLayers(Node): | class CompositorNodeRLayers(Node): | ||||||
|     bl_type = bpy.types.CompositorNodeRLayers |     bl_type = bpy.types.CompositorNodeRLayers | ||||||
| 
 | 
 | ||||||
|     excludes = Dumper.excludes + ["scene"] |     excludes = Dumper.excludes + ['scene'] | ||||||
| 
 | 
 | ||||||
|     @classmethod   |     @classmethod   | ||||||
|     def load(cls, data, node): |     def load(cls, data, node): | ||||||
|         # print('load CompositorNodeRLayers') |  | ||||||
| 
 | 
 | ||||||
|         scene_data = data.pop("scene") |         #print('load CompositorNodeRLayers') | ||||||
|         # print(scene_data) | 
 | ||||||
|         layer = data.pop("layer") |         scene_data = data.pop('scene') | ||||||
|  |         #print(scene_data) | ||||||
|  |         layer = data.pop('layer') | ||||||
|         scene = Scene.new(scene_data) |         scene = Scene.new(scene_data) | ||||||
|         Scene.load(scene_data, scene) |         Scene.load(scene_data, scene) | ||||||
|          |          | ||||||
| @ -729,37 +703,36 @@ class CompositorNodeRLayers(Node): | |||||||
|         # Resetter the view_layer because it might have been created |         # Resetter the view_layer because it might have been created | ||||||
|         # with the scene attr in the dictionnary and nor available yet |         # with the scene attr in the dictionnary and nor available yet | ||||||
| 
 | 
 | ||||||
|         # print(bpy.) |         #print(bpy.) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
| 
 | 
 | ||||||
|     @classmethod   |     @classmethod   | ||||||
|     def dump(cls, node): |     def dump(cls, node): | ||||||
|         # Add scene and viewlayer passes |         # Add scene and viewlayer passes | ||||||
|         data = super().dump(node) |         data = super().dump(node) | ||||||
| 
 | 
 | ||||||
|         # if |         #if  | ||||||
| 
 | 
 | ||||||
|         view_layer = node.scene.view_layers[node.layer] |         view_layer = node.scene.view_layers[node.layer] | ||||||
|         view_layer_data = ViewLayer.dump(view_layer) |         view_layer_data = ViewLayer.dump(view_layer) | ||||||
| 
 | 
 | ||||||
|         """ |         ''' | ||||||
|         view_layer_data = { |         view_layer_data = { | ||||||
|             "name": view_layer.name} |             "name": view_layer.name} | ||||||
|         properties = {p.name: p for p in view_layer.bl_rna.properties} |         properties = {p.name: p for p in view_layer.bl_rna.properties} | ||||||
|         for prop in view_layer.bl_rna: |         for prop in view_layer.bl_rna: | ||||||
|             if prop.identifier.startswith('use_pass'): |             if prop.identifier.startswith('use_pass'): | ||||||
|                 view_layer_data[prop.identifier] |                 view_layer_data[prop.identifier] | ||||||
|                 """ |                 ''' | ||||||
| 
 | 
 | ||||||
|         # cls.pointers[bl_object.as_pointer()] = bl_object |         #cls.pointers[bl_object.as_pointer()] = bl_object | ||||||
| 
 | 
 | ||||||
|         data["scene"] = { |         data['scene'] = { | ||||||
|             "bl_pointer": node.scene.as_pointer(), |             'bl_pointer': node.scene.as_pointer(), | ||||||
|             "name": node.scene.name, |             'name': node.scene.name, | ||||||
|             "render": { |             'render' : {'bl_pointer': node.scene.render.as_pointer(), "engine": node.scene.render.engine}, | ||||||
|                 "bl_pointer": node.scene.render.as_pointer(), |             'view_layers': [view_layer_data] | ||||||
|                 "engine": node.scene.render.engine, |  | ||||||
|             }, |  | ||||||
|             "view_layers": [view_layer_data], |  | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         return data |         return data | ||||||
| @ -767,16 +740,9 @@ class CompositorNodeRLayers(Node): | |||||||
| 
 | 
 | ||||||
| class ViewLayer(Dumper): | class ViewLayer(Dumper): | ||||||
|     bl_type = bpy.types.ViewLayer |     bl_type = bpy.types.ViewLayer | ||||||
|     excludes = Dumper.excludes + [ |     excludes = Dumper.excludes + ['freestyle_settings', 'eevee', 'cycles', 'active_layer_collection', | ||||||
|         "freestyle_settings", |         'active_aov', 'active_lightgroup_index', 'active_lightgroup'] | ||||||
|         "eevee", |     #includes = ['name'] | ||||||
|         "cycles", |  | ||||||
|         "active_layer_collection", |  | ||||||
|         "active_aov", |  | ||||||
|         "active_lightgroup_index", |  | ||||||
|         "active_lightgroup", |  | ||||||
|     ] |  | ||||||
|     # includes = ['name'] |  | ||||||
|      |      | ||||||
| 
 | 
 | ||||||
| class ViewLayers(PropCollection): | class ViewLayers(PropCollection): | ||||||
| @ -784,12 +750,12 @@ class ViewLayers(PropCollection): | |||||||
| 
 | 
 | ||||||
|     @classmethod |     @classmethod | ||||||
|     def load(cls, values, coll): |     def load(cls, values, coll): | ||||||
|         # print('LOAD VIEWLAYERS', values) |         #print('LOAD VIEWLAYERS', values) | ||||||
|         for value in values: |         for value in values: | ||||||
|             view_layer = coll.get(value["name"]) |             view_layer = coll.get(value['name']) | ||||||
| 
 | 
 | ||||||
|             if view_layer is None: |             if view_layer is None: | ||||||
|                 view_layer = coll.new(value["name"]) |                 view_layer = coll.new(value['name']) | ||||||
|              |              | ||||||
|             Dumper.load(value, view_layer) |             Dumper.load(value, view_layer) | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -1,18 +1,18 @@ | |||||||
|  | 
 | ||||||
| import bpy | import bpy | ||||||
| import re | import re | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def clean_name(name): | def clean_name(name): | ||||||
|     if re.match(r"(.*)\.\d{3}$", name): |     if re.match(r'(.*)\.\d{3}$', name): | ||||||
|         return name[:-4] |         return name[:-4] | ||||||
|     return name |     return name | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def is_node_groups_duplicate(node_groups): | def is_node_groups_duplicate(node_groups): | ||||||
|     node_group_types = sorted([n.type for n in node_groups[0].nodes]) |     node_group_types = sorted([n.type for n in node_groups[0].nodes]) | ||||||
|     return all( |     return all( sorted([n.type for n in ng.nodes]) ==  | ||||||
|         sorted([n.type for n in ng.nodes]) == node_group_types for ng in node_groups[1:] |              node_group_types for ng in node_groups[1:]) | ||||||
|     ) |  | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def remap_node_group_duplicates(nodes=None, force=False): | def remap_node_group_duplicates(nodes=None, force=False): | ||||||
| @ -41,7 +41,7 @@ def remap_node_group_duplicates(nodes=None, force=False): | |||||||
|             continue |             continue | ||||||
| 
 | 
 | ||||||
|         if not force: |         if not force: | ||||||
|             node_groups.sort(key=lambda x: x.name, reverse=True) |             node_groups.sort(key=lambda x : x.name, reverse=True) | ||||||
|          |          | ||||||
|         print(node_groups) |         print(node_groups) | ||||||
| 
 | 
 | ||||||
| @ -50,13 +50,11 @@ def remap_node_group_duplicates(nodes=None, force=False): | |||||||
|              |              | ||||||
|             if not is_duplicate and not force: |             if not is_duplicate and not force: | ||||||
|                 failed.append((node_group.name, node_groups[0].name)) |                 failed.append((node_group.name, node_groups[0].name)) | ||||||
|                 print( |                 print(f'Cannot merge Nodegroup {node_group.name} with {node_groups[0].name} they are different') | ||||||
|                     f"Cannot merge Nodegroup {node_group.name} with {node_groups[0].name} they are different" |  | ||||||
|                 ) |  | ||||||
|                 continue |                 continue | ||||||
| 
 | 
 | ||||||
|             merged.append((node_group.name, node_groups[0].name)) |             merged.append((node_group.name, node_groups[0].name)) | ||||||
|             print(f"Merge Nodegroup {node_group.name} into {node_groups[0].name}") |             print(f'Merge Nodegroup {node_group.name} into {node_groups[0].name}') | ||||||
|              |              | ||||||
|             node_group.user_remap(node_groups[0]) |             node_group.user_remap(node_groups[0]) | ||||||
|             bpy.data.node_groups.remove(node_group) |             bpy.data.node_groups.remove(node_group) | ||||||
|  | |||||||
| @ -1,6 +1,5 @@ | |||||||
| import bpy | import bpy | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
| def set_params(src, tgt, mod_to_node=True, org_modifier=None): | def set_params(src, tgt, mod_to_node=True, org_modifier=None): | ||||||
|     # mod to node: est-ce qu'on copie les valeurs d'un modifier a une node, ou l'inverse |     # mod to node: est-ce qu'on copie les valeurs d'un modifier a une node, ou l'inverse | ||||||
|     if mod_to_node: #  syntax for node and modifier are slightly different |     if mod_to_node: #  syntax for node and modifier are slightly different | ||||||
| @ -9,9 +8,9 @@ def set_params(src, tgt, mod_to_node=True, org_modifier=None): | |||||||
|         tree = src.node_tree.interface.items_tree |         tree = src.node_tree.interface.items_tree | ||||||
| 
 | 
 | ||||||
|     for param in tree: |     for param in tree: | ||||||
|         if param.socket_type == "NodeSocketGeometry": |         if param.socket_type == 'NodeSocketGeometry': | ||||||
|             continue |             continue | ||||||
|         if param.in_out == "OUTPUT": |         if param.in_out == 'OUTPUT': | ||||||
|             continue |             continue | ||||||
| 
 | 
 | ||||||
|         # seulement en extract mode, src est une node donc on check si des parametres sont dans le modifier |         # seulement en extract mode, src est une node donc on check si des parametres sont dans le modifier | ||||||
| @ -27,23 +26,19 @@ def set_params(src, tgt, mod_to_node=True, org_modifier=None): | |||||||
|             else: |             else: | ||||||
|                 tgt[identifier] = src.inputs[identifier].default_value |                 tgt[identifier] = src.inputs[identifier].default_value | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
| def set_group_inputs(target, objects, group): | def set_group_inputs(target, objects, group): | ||||||
|     mod = target.modifiers[0] |     mod = target.modifiers[0] | ||||||
|     node_dct = {} # used for cleanup |     node_dct = {} # used for cleanup | ||||||
|     for key, inp in get_node_inputs(objects).items(): |     for key, inp in get_node_inputs(objects).items(): | ||||||
|         # add the socket to the node group / modifier pannel |         # add the socket to the node group / modifier pannel | ||||||
|         sock = group.interface.new_socket( |         sock = group.interface.new_socket(inp["label"],in_out="INPUT",socket_type=inp["socket"]) | ||||||
|             inp["label"], in_out="INPUT", socket_type=inp["socket"] |  | ||||||
|         ) |  | ||||||
|         mod[sock.identifier] = inp["data"] |         mod[sock.identifier] = inp["data"] | ||||||
| 
 | 
 | ||||||
|         # inspect all nodes and add a group input node when that socket is used |         # inspect all nodes and add a group input node when that socket is used | ||||||
|         for node in parse_nodes(objects): |         for node in parse_nodes(objects): | ||||||
|             for param in node.node_tree.interface.items_tree: |             for param in node.node_tree.interface.items_tree: | ||||||
|                 nkey = get_input_socket_key(node, param) |                 nkey = get_input_socket_key(node, param) | ||||||
|                 if not nkey: |                 if not nkey: continue | ||||||
|                     continue |  | ||||||
|                 if nkey == key: |                 if nkey == key: | ||||||
|                     input_node = add_input_node(group, node, param.identifier, sock) |                     input_node = add_input_node(group, node, param.identifier, sock) | ||||||
| 
 | 
 | ||||||
| @ -58,13 +53,12 @@ def set_group_inputs(target, objects, group): | |||||||
|                         node_dct[node].append(input_node) |                         node_dct[node].append(input_node) | ||||||
| 
 | 
 | ||||||
|         # on refait la meme chose pour les object info nodes car leur syntaxe est un peu differente |         # on refait la meme chose pour les object info nodes car leur syntaxe est un peu differente | ||||||
|         for node in parse_nodes(objects, type="OBJECT_INFO"): |         for node in parse_nodes(objects, type = "OBJECT_INFO"): | ||||||
|             nkey = get_input_socket_key(node, param) |             nkey = get_input_socket_key(node, param) | ||||||
|             if not nkey: |             if not nkey: continue | ||||||
|                 continue |  | ||||||
|             if nkey == key: |             if nkey == key: | ||||||
|                 input_node = add_input_node(group, node, "Object", sock) |                 input_node = add_input_node(group, node, 'Object', sock) | ||||||
|                 node.inputs["Object"].default_value = None |                 node.inputs['Object'].default_value = None | ||||||
| 
 | 
 | ||||||
|                 # add to dict for cleanup |                 # add to dict for cleanup | ||||||
|                 if not node in node_dct.keys(): |                 if not node in node_dct.keys(): | ||||||
| @ -78,7 +72,6 @@ def set_group_inputs(target, objects, group): | |||||||
|             input_node.location[1] += 50 * offset |             input_node.location[1] += 50 * offset | ||||||
|             hide_sockets(input_node) |             hide_sockets(input_node) | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
| def get_node_link_value(node, param_name, org_mod): | def get_node_link_value(node, param_name, org_mod): | ||||||
|     if not org_mod: |     if not org_mod: | ||||||
|         return |         return | ||||||
| @ -89,25 +82,20 @@ def get_node_link_value(node, param_name, org_mod): | |||||||
| 
 | 
 | ||||||
|     return org_mod[socket_id] |     return org_mod[socket_id] | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
| def get_geo_socket(node, input=True): | def get_geo_socket(node, input=True): | ||||||
|     if node.type != "GROUP":  |     if node.type != "GROUP":  | ||||||
|         return "Geometry" |         return('Geometry') | ||||||
|     for param in node.node_tree.interface.items_tree: |     for param in node.node_tree.interface.items_tree: | ||||||
|         if param.socket_type != "NodeSocketGeometry": |         if param.socket_type != 'NodeSocketGeometry': | ||||||
|             continue |             continue | ||||||
|         if input and param.in_out == "INPUT": |         if input and param.in_out == 'INPUT' : return param.identifier | ||||||
|             return param.identifier |         if not input and param.in_out == 'OUTPUT' : return param.identifier | ||||||
|         if not input and param.in_out == "OUTPUT": |  | ||||||
|             return param.identifier |  | ||||||
|     return None |     return None | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| def get_input_socket_key(node, param): | def get_input_socket_key(node, param): | ||||||
|     if node.type == "GROUP": |     if node.type == "GROUP": | ||||||
|         if param.in_out != "INPUT": |         if param.in_out != 'INPUT': | ||||||
|             return False |             return False | ||||||
|         if not param.socket_type in ["NodeSocketObject", "NodeSocketCollection"]: |         if not param.socket_type in ['NodeSocketObject','NodeSocketCollection']: | ||||||
|             return False |             return False | ||||||
|         tgt = node.inputs[param.identifier].default_value |         tgt = node.inputs[param.identifier].default_value | ||||||
| 
 | 
 | ||||||
| @ -116,12 +104,11 @@ def get_input_socket_key(node, param): | |||||||
|         return f"{param.socket_type[10:][:3]} {tgt.name}" |         return f"{param.socket_type[10:][:3]} {tgt.name}" | ||||||
| 
 | 
 | ||||||
|     if node.type == "OBJECT_INFO": |     if node.type == "OBJECT_INFO": | ||||||
|         tgt = node.inputs["Object"].default_value |         tgt = node.inputs['Object'].default_value | ||||||
|         if not tgt: |         if not tgt: | ||||||
|             return False |             return False | ||||||
|         return f"Object {tgt.name}" |         return f"Object {tgt.name}" | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
| def get_node_inputs(combined_nodes): | def get_node_inputs(combined_nodes): | ||||||
|     # inputs["Col COL.name"] = {name = COL.name, data = COL, socket = "COLLECTION"} |     # inputs["Col COL.name"] = {name = COL.name, data = COL, socket = "COLLECTION"} | ||||||
|     # inputs["Obj OBJ.name"] = {name = OBJ.name, data = OBJ, socket = "OBJECT"} |     # inputs["Obj OBJ.name"] = {name = OBJ.name, data = OBJ, socket = "OBJECT"} | ||||||
| @ -132,28 +119,17 @@ def get_node_inputs(combined_nodes): | |||||||
|             if not key:  |             if not key:  | ||||||
|                 continue |                 continue | ||||||
|             tgt = node.inputs[param.identifier].default_value |             tgt = node.inputs[param.identifier].default_value | ||||||
|             inputs[key] = { |             inputs[key] = {'name': tgt.name, 'data': tgt, 'label': param.name , 'socket': param.socket_type} | ||||||
|                 "name": tgt.name, |  | ||||||
|                 "data": tgt, |  | ||||||
|                 "label": param.name, |  | ||||||
|                 "socket": param.socket_type, |  | ||||||
|             } |  | ||||||
| 
 | 
 | ||||||
|     for node in parse_nodes(combined_nodes, type="OBJECT_INFO"): |     for node in parse_nodes(combined_nodes, type = "OBJECT_INFO"): | ||||||
|         key = get_input_socket_key(node, None) |         key = get_input_socket_key(node, None) | ||||||
|         if not key: |         if not key: | ||||||
|             continue |             continue | ||||||
|         tgt = node.inputs["Object"].default_value |         tgt = node.inputs['Object'].default_value | ||||||
|         inputs[key] = { |         inputs[key] = {'name': tgt.name, 'data': tgt, 'label': 'Source OB' , 'socket': "NodeSocketObject"} | ||||||
|             "name": tgt.name, |  | ||||||
|             "data": tgt, |  | ||||||
|             "label": "Source OB", |  | ||||||
|             "socket": "NodeSocketObject", |  | ||||||
|         } |  | ||||||
| 
 | 
 | ||||||
|     return inputs |     return inputs | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
| def get_node_bounds(objects, mode=0, x=0, y=0): | def get_node_bounds(objects, mode=0, x=0, y=0): | ||||||
|     min_x = min_y = 10000000 |     min_x = min_y = 10000000 | ||||||
|     max_x = max_y = 0 |     max_x = max_y = 0 | ||||||
| @ -161,14 +137,13 @@ def get_node_bounds(objects, mode=0, x=0, y=0): | |||||||
|     for ob in objects: |     for ob in objects: | ||||||
|         for node in ob: |         for node in ob: | ||||||
|             co = node.location |             co = node.location | ||||||
|             min_x = min(co[0], min_x) |             min_x = min(co[0],min_x) | ||||||
|             max_x = max(co[0], max_x) |             max_x = max(co[0],max_x) | ||||||
| 
 | 
 | ||||||
|             min_y = min(co[1], min_y) |             min_y = min(co[1],min_y) | ||||||
|             max_y = max(co[1], max_y) |             max_y = max(co[1],max_y) | ||||||
|     if mode == 0: |     if mode == 0: | ||||||
|         return [max_x + x, (min_y + max_y) / 2] |         return([max_x+x, (min_y+max_y)/2 ]) | ||||||
| 
 |  | ||||||
| 
 | 
 | ||||||
| def get_collection(name): | def get_collection(name): | ||||||
|     scn = bpy.context.scene |     scn = bpy.context.scene | ||||||
| @ -177,29 +152,23 @@ def get_collection(name): | |||||||
| 
 | 
 | ||||||
|     # look for existing |     # look for existing | ||||||
|     for c in bpy.data.collections: |     for c in bpy.data.collections: | ||||||
|         if c.name == name: |         if c.name == name: col = c | ||||||
|             col = c |  | ||||||
| 
 | 
 | ||||||
|     # create if needed |     # create if needed | ||||||
|     if not col: |     if not col: col = bpy.data.collections.new(name) | ||||||
|         col = bpy.data.collections.new(name) |  | ||||||
| 
 | 
 | ||||||
|     # link to scene if needed |     # link to scene if needed | ||||||
|     for c in scn.collection.children_recursive: |     for c in scn.collection.children_recursive: | ||||||
|         if c.name == col.name: |         if c.name == col.name: link = True | ||||||
|             link = True |  | ||||||
|     if not link: |     if not link: | ||||||
|         scn.collection.children.link(col) |         scn.collection.children.link(col) | ||||||
|     return col |     return col | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
| def get_mod_frames(grp): | def get_mod_frames(grp): | ||||||
|     frames = [] |     frames = [] | ||||||
|     for node in grp.nodes: |     for node in grp.nodes: | ||||||
|         if node.type == "FRAME": |         if node.type == "FRAME": frames.append(node) | ||||||
|             frames.append(node) |     return(frames) | ||||||
|     return frames |  | ||||||
| 
 |  | ||||||
| 
 | 
 | ||||||
| def get_frame_childrens(frame): | def get_frame_childrens(frame): | ||||||
|     childrens = [] |     childrens = [] | ||||||
| @ -214,16 +183,13 @@ def get_frame_childrens(frame): | |||||||
|     childrens = [locs[x] for x in entries] |     childrens = [locs[x] for x in entries] | ||||||
|     return childrens |     return childrens | ||||||
| 
 | 
 | ||||||
| 
 | def parse_nodes(combined_nodes, type = "GROUP"): | ||||||
| def parse_nodes(combined_nodes, type="GROUP"): |  | ||||||
|     nodes = [] |     nodes = [] | ||||||
|     for frame in combined_nodes: |     for frame in combined_nodes: | ||||||
|         for node in frame: |         for node in frame: | ||||||
|             if node.type == type: |             if node.type == type: nodes.append(node) | ||||||
|                 nodes.append(node) |  | ||||||
|     return nodes |     return nodes | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
| def copy_source_ob(ob, col): | def copy_source_ob(ob, col): | ||||||
|     # est-ce que l'objet a des data ? si oui on cree une copie , |     # est-ce que l'objet a des data ? si oui on cree une copie , | ||||||
|     # si non on renvois None |     # si non on renvois None | ||||||
| @ -244,8 +210,7 @@ def copy_source_ob(ob, col): | |||||||
|         col.objects.link(new_ob) |         col.objects.link(new_ob) | ||||||
|     return new_ob |     return new_ob | ||||||
| 
 | 
 | ||||||
| 
 | def hide_sockets(node,collapse = True): | ||||||
| def hide_sockets(node, collapse=True): |  | ||||||
|     for socket in node.outputs: |     for socket in node.outputs: | ||||||
|         if not socket.links:  |         if not socket.links:  | ||||||
|             socket.hide = True |             socket.hide = True | ||||||
| @ -255,15 +220,14 @@ def hide_sockets(node, collapse=True): | |||||||
|     if collapse:  |     if collapse:  | ||||||
|         node.hide = True |         node.hide = True | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
| def add_input_node(group, node, param_id, socket): | def add_input_node(group, node, param_id, socket): | ||||||
|     group_input_node = group.nodes.new("NodeGroupInput") |     group_input_node = group.nodes.new('NodeGroupInput') | ||||||
|     group_input_node.location = node.location |     group_input_node.location = node.location | ||||||
|     group_input_node.location[1] += 70 |     group_input_node.location[1] += 70 | ||||||
|     group_input_node.label = socket.name |     group_input_node.label = socket.name | ||||||
|     group.links.new(group_input_node.outputs[socket.identifier], node.inputs[param_id]) |     group.links.new(group_input_node.outputs[socket.identifier], | ||||||
|     return group_input_node |                     node.inputs[param_id]) | ||||||
| 
 |     return(group_input_node) | ||||||
| 
 | 
 | ||||||
| def add_material_node(ob, group, nodes): | def add_material_node(ob, group, nodes): | ||||||
|     if not ob.material_slots: |     if not ob.material_slots: | ||||||
| @ -271,59 +235,54 @@ def add_material_node(ob, group, nodes): | |||||||
|     if not ob.material_slots[0].material: |     if not ob.material_slots[0].material: | ||||||
|         return nodes |         return nodes | ||||||
|     last_node = nodes[-1:][0] |     last_node = nodes[-1:][0] | ||||||
|     node = group.nodes.new("GeometryNodeSetMaterial") |     node = group.nodes.new('GeometryNodeSetMaterial') | ||||||
|     node.inputs["Material"].default_value = ob.material_slots[0].material |     node.inputs['Material'].default_value = ob.material_slots[0].material | ||||||
|     node.location = last_node.location |     node.location = last_node.location | ||||||
|     node.location[0] += 300 |     node.location[0] += 300 | ||||||
|     nodes.append(node) |     nodes.append(node) | ||||||
|     return nodes |     return nodes | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
| def join_nodes(group, nodes): | def join_nodes(group, nodes): | ||||||
|     prev = None |     prev = None | ||||||
|     for i, node in enumerate(nodes): |     for i , node in enumerate(nodes): | ||||||
|         if not prev: |         if not prev: | ||||||
|             prev = node |             prev = node | ||||||
|             continue |             continue | ||||||
|         geo_in = get_geo_socket(node) |         geo_in = get_geo_socket(node) | ||||||
|         geo_out = get_geo_socket(prev, input=False) |         geo_out = get_geo_socket(prev, input = False) | ||||||
| 
 | 
 | ||||||
|         if not geo_in or not geo_out: |         if not geo_in or not geo_out: | ||||||
|             continue |             continue | ||||||
|         group.links.new(prev.outputs[geo_out], node.inputs[geo_in]) |         group.links.new(prev.outputs[geo_out], node.inputs[geo_in]) | ||||||
|         prev = node |         prev = node | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
| def frame_nodes(group, nodes, ob): | def frame_nodes(group, nodes, ob): | ||||||
|     nd = group.nodes.new("NodeFrame") |     nd = group.nodes.new('NodeFrame') | ||||||
|     # frame = nodes.new(type='NodeFrame') |     # frame = nodes.new(type='NodeFrame') | ||||||
|     for n in nodes:  |     for n in nodes:  | ||||||
|         n.parent = nd |         n.parent = nd | ||||||
| 
 | 
 | ||||||
|     nd.label = ob.name |     nd.label = ob.name | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
| def combine_ob(ob, group, y=0, col=None): | def combine_ob(ob, group, y=0, col=None): | ||||||
|     nodes = [] |     nodes = [] | ||||||
| 
 | 
 | ||||||
|     # object info node |     # object info node | ||||||
|     nd = group.nodes.new("GeometryNodeObjectInfo") |     nd = group.nodes.new('GeometryNodeObjectInfo') | ||||||
|     nd.location[0] -= 300 |     nd.location[0] -= 300 | ||||||
|     nd.location[1] = y * 800 |     nd.location[1] = y * 800 | ||||||
|     nd.transform_space = "RELATIVE" |     nd.transform_space = "RELATIVE" | ||||||
|     nd.inputs["Object"].default_value = copy_source_ob( |     nd.inputs['Object'].default_value = copy_source_ob(ob, col) # si l'objet contient des data on crée une copie | ||||||
|         ob, col |  | ||||||
|     )  # si l'objet contient des data on crée une copie |  | ||||||
|     nodes.append(nd) |     nodes.append(nd) | ||||||
| 
 | 
 | ||||||
|     # ob modifiers |     # ob modifiers | ||||||
|     for x, md in enumerate(ob.modifiers): |     for x,md in enumerate(ob.modifiers): | ||||||
|         if md.type != "NODES": |         if md.type != "NODES" : | ||||||
|             print(abordage) |             print(abordage) | ||||||
|         if md.node_group == group: |         if md.node_group == group: | ||||||
|             continue |             continue | ||||||
| 
 | 
 | ||||||
|         nd = group.nodes.new("GeometryNodeGroup") |         nd = group.nodes.new('GeometryNodeGroup') | ||||||
|         nd.label = md.name |         nd.label = md.name | ||||||
|         nd.width = 230 |         nd.width = 230 | ||||||
|         nd.location[0] = x * 300 |         nd.location[0] = x * 300 | ||||||
| @ -337,7 +296,6 @@ def combine_ob(ob, group, y=0, col=None): | |||||||
|     frame_nodes(group, nodes, ob) |     frame_nodes(group, nodes, ob) | ||||||
|     return nodes |     return nodes | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
| def gen_target_ob(group, col=None): | def gen_target_ob(group, col=None): | ||||||
|     ob = gen_empty_ob(group.name, col=col) |     ob = gen_empty_ob(group.name, col=col) | ||||||
|     mod = ob.modifiers.new(group.name, "NODES") |     mod = ob.modifiers.new(group.name, "NODES") | ||||||
| @ -345,22 +303,20 @@ def gen_target_ob(group, col=None): | |||||||
| 
 | 
 | ||||||
|     ob.show_name = True |     ob.show_name = True | ||||||
|     bpy.context.view_layer.objects.active = ob |     bpy.context.view_layer.objects.active = ob | ||||||
|     return ob |     return(ob) | ||||||
| 
 |  | ||||||
| 
 | 
 | ||||||
| def gen_empty_ob(name, col=None): | def gen_empty_ob(name, col=None): | ||||||
|     scn = bpy.context.scene |     scn = bpy.context.scene | ||||||
|     ob = bpy.data.objects.new(name, object_data=bpy.data.meshes.new(name)) |     ob = bpy.data.objects.new(name, object_data=bpy.data.meshes.new(name)) | ||||||
| 
 | 
 | ||||||
|     ob.data.materials.append(None) |     ob.data.materials.append(None) | ||||||
|     ob.material_slots[0].link = "OBJECT" |     ob.material_slots[0].link = 'OBJECT' | ||||||
| 
 | 
 | ||||||
|     if not col: |     if not col: | ||||||
|         scn.collection.objects.link(ob) |         scn.collection.objects.link(ob) | ||||||
|     else: |     else: | ||||||
|         col.objects.link(ob) |         col.objects.link(ob) | ||||||
|     return ob |     return(ob) | ||||||
| 
 |  | ||||||
| 
 | 
 | ||||||
| def assign_modifiers(ob, frame, org_modifier): | def assign_modifiers(ob, frame, org_modifier): | ||||||
|     for node in get_frame_childrens(frame): |     for node in get_frame_childrens(frame): | ||||||
| @ -372,14 +328,11 @@ def assign_modifiers(ob, frame, org_modifier): | |||||||
|         set_params(node, mod, mod_to_node=False, org_modifier=org_modifier) |         set_params(node, mod, mod_to_node=False, org_modifier=org_modifier) | ||||||
|         mod.node_group.interface_update(bpy.context) |         mod.node_group.interface_update(bpy.context) | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
| def join_branches(objects, group): | def join_branches(objects, group): | ||||||
|     # join all trees and add an output node |     # join all trees and add an output node | ||||||
|     join = group.nodes.new("GeometryNodeJoinGeometry") |     join = group.nodes.new('GeometryNodeJoinGeometry') | ||||||
|     out = group.nodes.new("NodeGroupOutput") |     out = group.nodes.new('NodeGroupOutput') | ||||||
|     out_sock = group.interface.new_socket( |     out_sock = group.interface.new_socket("Geometry",in_out="OUTPUT",socket_type="NodeSocketGeometry") | ||||||
|         "Geometry", in_out="OUTPUT", socket_type="NodeSocketGeometry" |  | ||||||
|     ) |  | ||||||
| 
 | 
 | ||||||
|     loc = get_node_bounds(objects, x=500) |     loc = get_node_bounds(objects, x=500) | ||||||
|     join.location = loc |     join.location = loc | ||||||
| @ -388,52 +341,45 @@ def join_branches(objects, group): | |||||||
| 
 | 
 | ||||||
|     for ob in objects: |     for ob in objects: | ||||||
|         node = ob[-1:][0] |         node = ob[-1:][0] | ||||||
|         group.links.new( |         group.links.new(node.outputs[get_geo_socket(node, input=False)], | ||||||
|             node.outputs[get_geo_socket(node, input=False)], |                         join.inputs[get_geo_socket(join)]) | ||||||
|             join.inputs[get_geo_socket(join)], |  | ||||||
|         ) |  | ||||||
| 
 |  | ||||||
|     group.links.new( |  | ||||||
|         join.outputs[get_geo_socket(join, input=False)], out.inputs[out_sock.identifier] |  | ||||||
|     ) |  | ||||||
| 
 | 
 | ||||||
|  |     group.links.new(join.outputs[get_geo_socket(join, input=False)], | ||||||
|  |                     out.inputs[out_sock.identifier]) | ||||||
| 
 | 
 | ||||||
| def gen_extracted_ob(name, frame, col, mod): | def gen_extracted_ob(name, frame, col, mod): | ||||||
|     ob = None |     ob = None | ||||||
|     for node in get_frame_childrens(frame): |     for node in get_frame_childrens(frame): | ||||||
|         if node.type != "OBJECT_INFO": |         if node.type != "OBJECT_INFO": | ||||||
|             continue |             continue | ||||||
|         target = get_node_link_value(node, "Object", mod) |         target = get_node_link_value(node, 'Object', mod) | ||||||
| 
 | 
 | ||||||
|         if target: |         if target: | ||||||
|             ob = target.copy() |             ob = target.copy() | ||||||
|             ob.data = ob.data.copy() |             ob.data = ob.data.copy() | ||||||
|             col.objects.link(ob) |             col.objects.link(ob) | ||||||
|     if not ob: |     if not ob: ob = gen_empty_ob(name , col = col) | ||||||
|         ob = gen_empty_ob(name, col=col) |  | ||||||
| 
 | 
 | ||||||
|     # assign material |     # assign material | ||||||
|     for node in get_frame_childrens(frame): |     for node in get_frame_childrens(frame): | ||||||
|         if node.type != "SET_MATERIAL": |         if node.type != "SET_MATERIAL": | ||||||
|             continue |             continue | ||||||
|         ob.material_slots[0].material = node.inputs["Material"].default_value |         ob.material_slots[0].material = node.inputs['Material'].default_value | ||||||
|     return ob |     return ob | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
| def combine_objects(objs): | def combine_objects(objs): | ||||||
|     name = f"NODEGROUP_combined" |     name = f"NODEGROUP_combined" | ||||||
|     col = get_collection(name) |     col = get_collection(name) | ||||||
|     group = bpy.data.node_groups.new(name=name, type="GeometryNodeTree") |     group = bpy.data.node_groups.new(name=name, type='GeometryNodeTree') | ||||||
| 
 | 
 | ||||||
|     objects = [] |     objects = [] | ||||||
|     for y, ob in enumerate(objs): |     for y , ob in enumerate(objs): | ||||||
|         objects.append(combine_ob(ob, group, y=y, col=col)) |         objects.append(combine_ob(ob, group, y=y, col=col)) | ||||||
| 
 | 
 | ||||||
|     target = gen_target_ob(group, col=col) |     target = gen_target_ob(group, col = col) | ||||||
|     set_group_inputs(target, objects, group) |     set_group_inputs(target, objects, group) | ||||||
|     join_branches(objects, group) |     join_branches(objects, group) | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
| def extract_objects(object): | def extract_objects(object): | ||||||
|     mod = object.modifiers[0] |     mod = object.modifiers[0] | ||||||
|     grp = mod.node_group |     grp = mod.node_group | ||||||
| @ -444,9 +390,8 @@ def extract_objects(object): | |||||||
|         ob = gen_extracted_ob(name, frame, col, mod) |         ob = gen_extracted_ob(name, frame, col, mod) | ||||||
|         assign_modifiers(ob, frame, mod) |         assign_modifiers(ob, frame, mod) | ||||||
| 
 | 
 | ||||||
| 
 | #combine_objects(bpy.context.selected_objects) | ||||||
| # combine_objects(bpy.context.selected_objects) | #extract_objects(bpy.context.active_object) | ||||||
| # extract_objects(bpy.context.active_object) |  | ||||||
| """ | """ | ||||||
| TODO: extract copier les transform de l'objet original ... | TODO: extract copier les transform de l'objet original ... | ||||||
| OK ! combine: si un objet a un materiel on cree un node set material en fin de liste | OK ! combine: si un objet a un materiel on cree un node set material en fin de liste | ||||||
|  | |||||||
							
								
								
									
										12
									
								
								file_utils.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								file_utils.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,12 @@ | |||||||
|  | import plateform | ||||||
|  | from pathlib import Path | ||||||
|  | from os.path import expandvars | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def get_cache_dir() | ||||||
|  |     if plateform.system() == 'Linux': | ||||||
|  |         return Path(expandvars('$HOME/.cache/blender')) | ||||||
|  |     elif plateform.system() == 'Darwin': | ||||||
|  |         return Path('/Library/Caches/Blender') | ||||||
|  |     elif plateform.system() == 'Windows': | ||||||
|  |         return Path(expandvars('%USERPROFILE%\AppData\Local\Blender Foundation\Blender')) | ||||||
							
								
								
									
										172
									
								
								operators.py
									
									
									
									
									
								
							
							
						
						
									
										172
									
								
								operators.py
									
									
									
									
									
								
							| @ -14,29 +14,26 @@ import bpy | |||||||
| from bpy.props import BoolProperty, EnumProperty | from bpy.props import BoolProperty, EnumProperty | ||||||
| from bpy.types import Operator | from bpy.types import Operator | ||||||
| 
 | 
 | ||||||
| # from node_kit.core.node_tree import NodeTree | #from node_kit.core.node_tree import NodeTree | ||||||
| from .core.dumper import dump, load | from . core.dumper import dump, load | ||||||
| from .core.node_utils import remap_node_group_duplicates | from .core.node_utils import remap_node_group_duplicates | ||||||
| from .core.pack_nodes import combine_objects, extract_objects | from .core.pack_nodes import combine_objects, extract_objects | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class NODEKIT_OT_copy(Operator): | class NODEKIT_OT_copy(Operator): | ||||||
|     bl_idname = "node_kit.copy_node_tree" |     bl_idname = 'node_kit.copy_node_tree' | ||||||
|     bl_label = "Copy nodes" |     bl_label = 'Copy nodes' | ||||||
|     bl_options = {"REGISTER", "UNDO"} |     bl_options = {'REGISTER', 'UNDO'} | ||||||
| 
 | 
 | ||||||
|     select_only: BoolProperty(default=True) |     select_only: BoolProperty(default=True) | ||||||
| 
 | 
 | ||||||
|     def execute(self, context): |     def execute(self, context): | ||||||
|  | 
 | ||||||
|         ntree = context.space_data.edit_tree |         ntree = context.space_data.edit_tree | ||||||
|         if self.select_only: |         if self.select_only: | ||||||
|             ntree_data = { |             ntree_data = { | ||||||
|                 "nodes": dump( |                 "nodes" : dump([n for n in ntree.nodes if n.select]) ,#[dump(n) for n in ntree.nodes if n.select], | ||||||
|                     [n for n in ntree.nodes if n.select] |                 "links" : dump([l for l in ntree.links if l.from_node.select and l.to_node.select]) | ||||||
|                 ),  # [dump(n) for n in ntree.nodes if n.select], |  | ||||||
|                 "links": dump( |  | ||||||
|                     [l for l in ntree.links if l.from_node.select and l.to_node.select] |  | ||||||
|                 ), |  | ||||||
|             } |             } | ||||||
|         else: |         else: | ||||||
|             ntree_data = dump(ntree) |             ntree_data = dump(ntree) | ||||||
| @ -45,91 +42,76 @@ class NODEKIT_OT_copy(Operator): | |||||||
| 
 | 
 | ||||||
|         context.window_manager.clipboard = json.dumps(ntree_data) |         context.window_manager.clipboard = json.dumps(ntree_data) | ||||||
| 
 | 
 | ||||||
|         return {"FINISHED"} |         return {'FINISHED'} | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class NODEKIT_OT_paste(Operator): | class NODEKIT_OT_paste(Operator): | ||||||
|     bl_idname = "node_kit.paste_node_tree" |     bl_idname = 'node_kit.paste_node_tree' | ||||||
|     bl_label = "Paste nodes" |     bl_label = 'Paste nodes' | ||||||
| 
 | 
 | ||||||
|     def execute(self, context): |     def execute(self, context): | ||||||
|  | 
 | ||||||
|         ntree_data = json.loads(context.window_manager.clipboard) |         ntree_data = json.loads(context.window_manager.clipboard) | ||||||
|         load(ntree_data, context.space_data.edit_tree) |         load(ntree_data, context.space_data.edit_tree) | ||||||
| 
 | 
 | ||||||
|         return {"FINISHED"} |         return {'FINISHED'} | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class NODEKIT_OT_remap_node_group_duplicates(Operator): | class NODEKIT_OT_remap_node_group_duplicates(Operator): | ||||||
|     bl_idname = "node_kit.remap_node_group_duplicates" |     bl_idname = 'node_kit.remap_node_group_duplicates' | ||||||
|     bl_label = "Clean nodes" |     bl_label = 'Clean nodes' | ||||||
|     bl_options = {"REGISTER", "UNDO"} |     bl_options = {"REGISTER", "UNDO"} | ||||||
| 
 | 
 | ||||||
|     selection: EnumProperty( |     selection : EnumProperty(items=[(s, s.title(), '') for s in ('ALL', 'SELECTED', 'CURRENT')], default="CURRENT", name='All Nodes') | ||||||
|         items=[(s, s.title(), "") for s in ("ALL", "SELECTED", "CURRENT")], |     force : BoolProperty(default=False, description='Remap nodes even if there are different', name='Force') | ||||||
|         default="CURRENT", |  | ||||||
|         name="All Nodes", |  | ||||||
|     ) |  | ||||||
|     force: BoolProperty( |  | ||||||
|         default=False, |  | ||||||
|         description="Remap nodes even if there are different", |  | ||||||
|         name="Force", |  | ||||||
|     ) |  | ||||||
| 
 | 
 | ||||||
|     def invoke(self, context, event): |     def invoke(self, context, event): | ||||||
|         return context.window_manager.invoke_props_dialog(self) |         return context.window_manager.invoke_props_dialog(self) | ||||||
| 
 | 
 | ||||||
|     def execute(self, context): |     def execute(self, context): | ||||||
|         nodes = None |         nodes = None | ||||||
|         if self.selection == "SELECTED": |         if self.selection == 'SELECTED': | ||||||
|             nodes = [ |             nodes = [ n.node_tree for n in context.space_data.edit_tree.nodes  | ||||||
|                 n.node_tree |                                 if n.type == "GROUP" and n.select] | ||||||
|                 for n in context.space_data.edit_tree.nodes |         elif self.selection == 'ACTIVE': | ||||||
|                 if n.type == "GROUP" and n.select |  | ||||||
|             ] |  | ||||||
|         elif self.selection == "ACTIVE": |  | ||||||
|             active_node = context.space_data.edit_tree |             active_node = context.space_data.edit_tree | ||||||
|             nodes = [active_node] |             nodes = [active_node] | ||||||
| 
 | 
 | ||||||
|         merged, failed = remap_node_group_duplicates(nodes=nodes, force=self.force) |         merged, failed = remap_node_group_duplicates(nodes=nodes, force=self.force) | ||||||
| 
 | 
 | ||||||
|         if failed and not merged: |         if failed and not merged: | ||||||
|             self.report({"ERROR"}, "No duplicates remapped, Node Group are differents") |             self.report({"ERROR"}, 'No duplicates remapped, Node Group are differents') | ||||||
|             return {"CANCELLED"} |             return {"CANCELLED"} | ||||||
| 
 | 
 | ||||||
|         self.report( |         self.report({"INFO"}, f'{len(merged)} Node Groups Remapped, {len(failed)} Node Groups failed') | ||||||
|             {"INFO"}, |  | ||||||
|             f"{len(merged)} Node Groups Remapped, {len(failed)} Node Groups failed", |  | ||||||
|         ) |  | ||||||
| 
 | 
 | ||||||
|         return {"FINISHED"} |         return {'FINISHED'} | ||||||
|      |      | ||||||
|     def draw(self, context): |     def draw(self, context): | ||||||
|  |          | ||||||
|  | 
 | ||||||
|         layout = self.layout |         layout = self.layout | ||||||
|         layout.prop(self, "selection", expand=True) |         layout.prop(self, "selection", expand=True) | ||||||
|         layout.prop(self, "force") |         layout.prop(self, "force") | ||||||
|         if self.force and self.selection == "CURRENT": |         if self.force and self.selection == 'CURRENT': | ||||||
|             ntree = context.space_data.edit_tree |             ntree = context.space_data.edit_tree | ||||||
|             layout.label(text=f"Remap node {ntree.name} to others") |             layout.label(text=f'Remap node {ntree.name} to others') | ||||||
|         elif self.force and self.selection == "SELECTED": |         elif self.force and self.selection == 'SELECTED': | ||||||
|             layout.label(text="Selected nodes will override others") |             layout.label(text='Selected nodes will override others') | ||||||
|         elif self.selection == "SELECTED": |         elif self.selection == 'SELECTED': | ||||||
|             layout.label(text="Remap last .*** nodes") |             layout.label(text='Remap last .*** nodes') | ||||||
|             layout.label(text="Ex: Node.001 will override Node") |             layout.label(text='Ex: Node.001 will override Node') | ||||||
|         elif self.selection in ("CURRENT", "ALL"): |         elif self.selection in ('CURRENT', 'ALL'): | ||||||
|             layout.label(text="Remap last .*** nodes") |             layout.label(text='Remap last .*** nodes') | ||||||
|             layout.label(text="Ex: Node.001 will override Node") |             layout.label(text='Ex: Node.001 will override Node') | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class NODEKIT_OT_update_nodes(Operator): | class NODEKIT_OT_update_nodes(Operator): | ||||||
|     bl_idname = "node_kit.update_nodes" |     bl_idname = 'node_kit.update_nodes' | ||||||
|     bl_label = "Update node" |     bl_label = 'Update node' | ||||||
|     bl_options = {"REGISTER", "UNDO"} |     bl_options = {"REGISTER", "UNDO"} | ||||||
| 
 | 
 | ||||||
|     selection: EnumProperty( |     selection : EnumProperty(items=[(s, s.title(), '') for s in ('ALL', 'SELECTED', 'ACTIVE')], default="ACTIVE", name='All Nodes') | ||||||
|         items=[(s, s.title(), "") for s in ("ALL", "SELECTED", "ACTIVE")], |  | ||||||
|         default="ACTIVE", |  | ||||||
|         name="All Nodes", |  | ||||||
|     ) |  | ||||||
| 
 | 
 | ||||||
|     def invoke(self, context, event): |     def invoke(self, context, event): | ||||||
|         return context.window_manager.invoke_props_dialog(self) |         return context.window_manager.invoke_props_dialog(self) | ||||||
| @ -141,75 +123,61 @@ class NODEKIT_OT_update_nodes(Operator): | |||||||
|         ntree_name = ntree.name |         ntree_name = ntree.name | ||||||
|         new_ntree = None |         new_ntree = None | ||||||
| 
 | 
 | ||||||
|         if self.selection == "SELECTED": |         if self.selection == 'SELECTED': | ||||||
|             nodes = [ |             nodes = [ n.node_tree for n in context.space_data.edit_tree.nodes  | ||||||
|                 n.node_tree |                                 if n.type == "GROUP" and n.select] | ||||||
|                 for n in context.space_data.edit_tree.nodes |         elif self.selection == 'ACTIVE': | ||||||
|                 if n.type == "GROUP" and n.select |  | ||||||
|             ] |  | ||||||
|         elif self.selection == "ACTIVE": |  | ||||||
|             active_node = context.space_data.edit_tree |             active_node = context.space_data.edit_tree | ||||||
|             nodes = [active_node] |             nodes = [active_node] | ||||||
|         else: |         else: | ||||||
|             nodes = list(bpy.data.node_groups) |             nodes = list(bpy.data.node_groups) | ||||||
|          |          | ||||||
|         node_names = set(n.name for n in nodes) |  | ||||||
|         # new_node_groups = [] |  | ||||||
|          |          | ||||||
|         # print("node_names", node_names) |         node_names = set(n.name for n in nodes) | ||||||
|  |         #new_node_groups = [] | ||||||
|  | 
 | ||||||
|  |         #print("node_names", node_names) | ||||||
| 
 | 
 | ||||||
|         for asset_library in asset_libraries: |         for asset_library in asset_libraries: | ||||||
|             library_path = Path(asset_library.path) |             library_path = Path(asset_library.path) | ||||||
|             blend_files = [fp for fp in library_path.glob("**/*.blend") if fp.is_file()] |             blend_files = [fp for fp in library_path.glob("**/*.blend") if fp.is_file()] | ||||||
| 
 | 
 | ||||||
|             node_groups = list( |             node_groups = list(bpy.data.node_groups)# Storing original node_geoup to compare with imported | ||||||
|                 bpy.data.node_groups |  | ||||||
|             )  # Storing original node_geoup to compare with imported |  | ||||||
| 
 | 
 | ||||||
|             link = asset_library.import_method == "LINK" |             link = (asset_library.import_method == 'LINK') | ||||||
|             for blend_file in blend_files: |             for blend_file in blend_files: | ||||||
|                 print(blend_file) |                 print(blend_file) | ||||||
|                 with bpy.data.libraries.load( |                 with bpy.data.libraries.load(str(blend_file), assets_only=True, link=link) as (data_from, data_to): | ||||||
|                     str(blend_file), assets_only=True, link=link | 
 | ||||||
|                 ) as (data_from, data_to): |  | ||||||
|                     print(data_from.node_groups) |                     print(data_from.node_groups) | ||||||
|                     import_node_groups = [ |                     import_node_groups = [n for n in data_from.node_groups if n in node_names] | ||||||
|                         n for n in data_from.node_groups if n in node_names |  | ||||||
|                     ] |  | ||||||
|                     print("import_node_groups", import_node_groups) |                     print("import_node_groups", import_node_groups) | ||||||
|                     data_to.node_groups = import_node_groups |                     data_to.node_groups = import_node_groups | ||||||
| 
 | 
 | ||||||
|                     # print(data_from.node_groups) |                     #print(data_from.node_groups) | ||||||
|                     # print("data_to.node_groups", data_to.node_groups) |                     #print("data_to.node_groups", data_to.node_groups) | ||||||
|                     node_names -= set(import_node_groups) # Store already updated nodes |                     node_names -= set(import_node_groups) # Store already updated nodes | ||||||
|                  |                  | ||||||
|                 # new_ntree = data_to.node_groups[0] |                 #new_ntree = data_to.node_groups[0] | ||||||
|         new_node_groups = [n for n in bpy.data.node_groups if n not in node_groups] |         new_node_groups = [n for n in bpy.data.node_groups if n not in node_groups] | ||||||
| 
 | 
 | ||||||
|         # break |                 #break | ||||||
|              |              | ||||||
|         # if new_ntree: |             #if new_ntree: | ||||||
|             #    break |             #    break | ||||||
|         new_node_groups = list(set(new_node_groups)) |         new_node_groups = list(set(new_node_groups)) | ||||||
|         # print(new_node_groups) |         #print(new_node_groups) | ||||||
| 
 | 
 | ||||||
|         # if new_node_groups: |         # if new_node_groups: | ||||||
|         for new_node_group in new_node_groups: |         for new_node_group in new_node_groups: | ||||||
|             new_node_group_name = new_node_group.library_weak_reference.id_name[2:] |             new_node_group_name = new_node_group.library_weak_reference.id_name[2:] | ||||||
|             local_node_group = next( |             local_node_group = next((n for n in bpy.data.node_groups if n.name == new_node_group_name and n != new_node_group), None) | ||||||
|                 ( |  | ||||||
|                     n |  | ||||||
|                     for n in bpy.data.node_groups |  | ||||||
|                     if n.name == new_node_group_name and n != new_node_group |  | ||||||
|                 ), |  | ||||||
|                 None, |  | ||||||
|             ) |  | ||||||
| 
 | 
 | ||||||
|             if not local_node_group: |             if not local_node_group: | ||||||
|                 print(f"No local node_group {new_node_group_name}") |                 print(f'No local node_group {new_node_group_name}') | ||||||
|                 continue |                 continue | ||||||
|              |              | ||||||
|             print(f"Merge node {local_node_group.name} into {new_node_group.name}") |             print(f'Merge node {local_node_group.name} into {new_node_group.name}') | ||||||
| 
 | 
 | ||||||
|             local_node_group.user_remap(new_node_group) |             local_node_group.user_remap(new_node_group) | ||||||
|             new_node_group.interface_update(context) |             new_node_group.interface_update(context) | ||||||
| @ -218,8 +186,8 @@ class NODEKIT_OT_update_nodes(Operator): | |||||||
|             new_node_group.name = new_node_group_name |             new_node_group.name = new_node_group_name | ||||||
|             new_node_group.asset_clear() |             new_node_group.asset_clear() | ||||||
| 
 | 
 | ||||||
|         # self.report({"INFO"}, f"Node updated from {blend_file}") |         #self.report({"INFO"}, f"Node updated from {blend_file}") | ||||||
|         return {"FINISHED"} |         return {'FINISHED'} | ||||||
| 
 | 
 | ||||||
|         # else: |         # else: | ||||||
|         #     self.report({"ERROR"}, f'No Node Group named "{ntree_name}" in the library') |         #     self.report({"ERROR"}, f'No Node Group named "{ntree_name}" in the library') | ||||||
| @ -231,23 +199,23 @@ class NODEKIT_OT_update_nodes(Operator): | |||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class NODEKIT_OT_pack_nodes(Operator): | class NODEKIT_OT_pack_nodes(Operator): | ||||||
|     bl_idname = "node_kit.pack_nodes" |     bl_idname = 'node_kit.pack_nodes' | ||||||
|     bl_label = "Update node" |     bl_label = 'Update node' | ||||||
|     bl_options = {"REGISTER", "UNDO"} |     bl_options = {"REGISTER", "UNDO"} | ||||||
| 
 | 
 | ||||||
|     def execute(self, context): |     def execute(self, context): | ||||||
|         combine_objects(context.selected_objects) |         combine_objects(context.selected_objects) | ||||||
|         return {"FINISHED"} |         return {'FINISHED'} | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class NODEKIT_OT_unpack_nodes(Operator): | class NODEKIT_OT_unpack_nodes(Operator): | ||||||
|     bl_idname = "node_kit.unpack_nodes" |     bl_idname = 'node_kit.unpack_nodes' | ||||||
|     bl_label = "Update node" |     bl_label = 'Update node' | ||||||
|     bl_options = {"REGISTER", "UNDO"} |     bl_options = {"REGISTER", "UNDO"} | ||||||
|      |      | ||||||
|     def execute(self, context): |     def execute(self, context): | ||||||
|         extract_objects(context.active_object) |         extract_objects(context.active_object) | ||||||
|         return {"FINISHED"} |         return {'FINISHED'} | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| classes = ( | classes = ( | ||||||
| @ -256,7 +224,7 @@ classes = ( | |||||||
|     NODEKIT_OT_remap_node_group_duplicates, |     NODEKIT_OT_remap_node_group_duplicates, | ||||||
|     NODEKIT_OT_update_nodes, |     NODEKIT_OT_update_nodes, | ||||||
|     NODEKIT_OT_pack_nodes, |     NODEKIT_OT_pack_nodes, | ||||||
|     NODEKIT_OT_unpack_nodes, |     NODEKIT_OT_unpack_nodes | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | |||||||
							
								
								
									
										27
									
								
								ui.py
									
									
									
									
									
								
							
							
						
						
									
										27
									
								
								ui.py
									
									
									
									
									
								
							| @ -15,29 +15,22 @@ class NODEKIT_MT_node_kit(bpy.types.Menu): | |||||||
|     def draw(self, context): |     def draw(self, context): | ||||||
|         layout = self.layout |         layout = self.layout | ||||||
| 
 | 
 | ||||||
|         layout.operator("node_kit.copy_node_tree", text="Copy Nodes", icon="COPYDOWN") |         layout.operator('node_kit.copy_node_tree', text='Copy Nodes', icon='COPYDOWN') | ||||||
|         layout.operator( |         layout.operator('node_kit.paste_node_tree', text='Paste Nodes', icon='PASTEDOWN') | ||||||
|             "node_kit.paste_node_tree", text="Paste Nodes", icon="PASTEDOWN" |  | ||||||
|         ) |  | ||||||
|         layout.separator() |         layout.separator() | ||||||
|         layout.operator( |         layout.operator('node_kit.remap_node_group_duplicates', text='Remap Node Groups Duplicates', icon='NODE_INSERT_OFF') | ||||||
|             "node_kit.remap_node_group_duplicates", |         layout.operator('node_kit.update_nodes', text='Update Nodes', icon='IMPORT') | ||||||
|             text="Remap Node Groups Duplicates", |  | ||||||
|             icon="NODE_INSERT_OFF", |  | ||||||
|         ) |  | ||||||
|         layout.operator("node_kit.update_nodes", text="Update Nodes", icon="IMPORT") |  | ||||||
|         layout.separator() |         layout.separator() | ||||||
|         layout.operator("node_kit.pack_nodes", text="Pack Nodes", icon="PACKAGE") |         layout.operator('node_kit.pack_nodes', text='Pack Nodes', icon='PACKAGE') | ||||||
|         layout.operator( |         layout.operator('node_kit.unpack_nodes', text='UnPack Nodes', icon='UGLYPACKAGE') | ||||||
|             "node_kit.unpack_nodes", text="UnPack Nodes", icon="UGLYPACKAGE" |  | ||||||
|         ) |  | ||||||
| 
 | 
 | ||||||
| 
 | classes = ( | ||||||
| classes = (NODEKIT_MT_node_kit,) |     NODEKIT_MT_node_kit, | ||||||
|  | ) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def draw_menu(self, context): | def draw_menu(self, context): | ||||||
|     self.layout.menu("NODEKIT_MT_node_kit") |     self.layout.menu('NODEKIT_MT_node_kit') | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def register(): | def register(): | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user