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
|
|
@ -1,17 +1,138 @@
|
|||
# Python Artifacts
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
# Python virtualenvs
|
||||
.venv/
|
||||
.env/
|
||||
|
||||
# Editor config files
|
||||
.idea/
|
||||
*.code-workspace
|
||||
*.vscode
|
||||
test_*
|
||||
|
||||
# Poetry build artifacts
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Distribution / packaging
|
||||
/dist/
|
||||
/release/
|
||||
.Python
|
||||
#build/
|
||||
develop-eggs/
|
||||
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/
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
Blender Node Toolkit - Collection of node-related tools (copy-pasting, packing, updating, etc..) all accessible from a simple menu.
|
||||
# NODE_KIT
|
||||
> Blender addon to handle some operations on nodes
|
||||
|
||||
## Development
|
||||
### Cloning
|
||||
1. Create a development directory, such as:
|
||||
Node kit is a blender addon creating a menu to execute operations on nodes like copy/pasting or exporting node_tree
|
||||
|
||||
<!-- 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
|
||||
/home/<USER>/dev
|
||||
```
|
||||
2. Clone the repository with the following command:
|
||||
2. Create your own local directory in
|
||||
```sh
|
||||
git clone ssh://git@git.autourdeminuit.com:222/autour_de_minuit/node_kit.git
|
||||
```
|
||||
|
||||
### Branches
|
||||
The main branch is **master**.
|
||||
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...
|
||||
<!-- CONTENTS -->
|
||||
## Contents
|
||||
|
|
23
__init__.py
23
__init__.py
|
@ -1,13 +1,14 @@
|
|||
bl_info = {
|
||||
"name": "Node Kit",
|
||||
"author": "Florentin Luce, Christophe Seux, Jonas Holzman",
|
||||
"author": "Florentin Luce",
|
||||
"version": (0, 1),
|
||||
"blender": (4, 3, 2),
|
||||
"location": "Node Editor -> Node Kit",
|
||||
"description": "Collection of node-related tools",
|
||||
"doc_url": "https://git.autourdeminuit.com/autour_de_minuit/node_kit",
|
||||
"category": "Node",
|
||||
}
|
||||
"blender": (4, 0, 2),
|
||||
"category": "Node"}
|
||||
|
||||
|
||||
import sys
|
||||
import importlib
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
from . import ui, operators
|
||||
|
@ -18,7 +19,15 @@ modules = (
|
|||
)
|
||||
|
||||
|
||||
if "bpy" in locals():
|
||||
import importlib
|
||||
|
||||
for mod in modules:
|
||||
importlib.reload(mod)
|
||||
|
||||
|
||||
def register():
|
||||
print('Register Node kit')
|
||||
for mod in modules:
|
||||
mod.register()
|
||||
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
|
||||
import bpy
|
||||
from mathutils import Color, Vector
|
||||
|
||||
|
@ -8,6 +9,7 @@ class Node:
|
|||
"""Blender Node abstraction."""
|
||||
|
||||
def __init__(self, bl_node, parent):
|
||||
|
||||
self.bl_node = bl_node
|
||||
self.tree = parent
|
||||
self.id = hex(id(self.bl_node))
|
||||
|
@ -65,12 +67,12 @@ class Node:
|
|||
self._parent = value
|
||||
|
||||
# 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:
|
||||
if node.id == value:
|
||||
self._parent = node
|
||||
else:
|
||||
print("Cannot find parent")
|
||||
print('Cannot find parent')
|
||||
|
||||
# blender node case
|
||||
elif isinstance(value, bpy.types.Node):
|
||||
|
@ -92,10 +94,10 @@ class Node:
|
|||
Returns:
|
||||
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)
|
||||
|
||||
elif bl_node.bl_idname == "CompositorNodeValToRGB":
|
||||
elif bl_node.bl_idname == 'CompositorNodeValToRGB':
|
||||
return ColorRampNode(bl_node, tree)
|
||||
|
||||
else:
|
||||
|
@ -113,23 +115,19 @@ class 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.id = data["id"]
|
||||
node.id = data['id']
|
||||
for p in node.parameters:
|
||||
setattr(node, p, data[p])
|
||||
|
||||
# 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))
|
||||
|
||||
node.inputs = [
|
||||
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.inputs = [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()]
|
||||
return node
|
||||
|
||||
def dump(self):
|
||||
|
@ -140,6 +138,7 @@ class Node:
|
|||
"""
|
||||
|
||||
for prop_id in self.parameters:
|
||||
|
||||
if not hasattr(self, prop_id):
|
||||
continue
|
||||
|
||||
|
@ -155,9 +154,9 @@ class Node:
|
|||
|
||||
self.data[prop_id] = attr_value
|
||||
|
||||
self.data["id"] = self.id
|
||||
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['id'] = self.id
|
||||
self.data['inputs'] = {ipt.id: ipt.dump() for ipt in self.inputs}
|
||||
self.data['outputs'] = {opt.id: opt.dump() for opt in self.outputs}
|
||||
|
||||
return self.data
|
||||
|
||||
|
@ -196,6 +195,7 @@ class Link:
|
|||
"""Blender Link abstraction."""
|
||||
|
||||
def __init__(self, bl_link, parent):
|
||||
|
||||
self.bl_link = bl_link
|
||||
self.tree = parent
|
||||
self.id = hex(id(self.bl_link))
|
||||
|
@ -206,6 +206,7 @@ class Link:
|
|||
self.data = {}
|
||||
|
||||
def dump(self):
|
||||
self.data["id"] = self.id
|
||||
|
||||
self.data['id'] = self.id
|
||||
|
||||
return self.data
|
||||
|
|
|
@ -8,6 +8,7 @@ class NodeTree:
|
|||
"""Blender node tree abstraction."""
|
||||
|
||||
def __init__(self, bl_node_tree):
|
||||
|
||||
self.bl_node_tree = bl_node_tree
|
||||
|
||||
self.data = {}
|
||||
|
@ -27,12 +28,8 @@ class NodeTree:
|
|||
Returns:
|
||||
dict: Nodes and links as dict.
|
||||
"""
|
||||
self.data["nodes"] = {
|
||||
n.id: n.dump()
|
||||
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]
|
||||
self.data['nodes'] = {n.id: n.dump() 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
|
||||
|
||||
|
@ -49,7 +46,8 @@ class NodeTree:
|
|||
|
||||
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)
|
||||
self.nodes.append(new_node)
|
||||
|
||||
|
@ -57,15 +55,15 @@ class NodeTree:
|
|||
|
||||
for ipt in new_node.inputs:
|
||||
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:
|
||||
if opt.is_linked:
|
||||
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"]:
|
||||
ipt = connections[link_id]["input"]
|
||||
opt = connections[link_id]["output"]
|
||||
for link_id in self.data['links']:
|
||||
ipt = connections[link_id]['input']
|
||||
opt = connections[link_id]['output']
|
||||
|
||||
self.bl_node_tree.links.new(ipt, opt)
|
||||
|
|
|
@ -1,5 +1,9 @@
|
|||
|
||||
|
||||
class Socket:
|
||||
|
||||
def __init__(self, bl_socket, tree):
|
||||
|
||||
self.tree = tree
|
||||
self.bl_socket = bl_socket
|
||||
self.data = {}
|
||||
|
@ -10,11 +14,12 @@ class Socket:
|
|||
|
||||
self._value = None
|
||||
|
||||
if hasattr(bl_socket, "default_value"):
|
||||
if hasattr(bl_socket, 'default_value'):
|
||||
self._value = bl_socket.default_value
|
||||
|
||||
@property
|
||||
def value(self):
|
||||
|
||||
if not isinstance(self._value, (str, int, float, bool)):
|
||||
self._value = [v for v in self._value]
|
||||
|
||||
|
@ -27,15 +32,16 @@ class Socket:
|
|||
return self._value
|
||||
|
||||
def to_dict(self):
|
||||
self.data["id"] = self.id
|
||||
self.data["value"] = self.value
|
||||
self.data["identifier"] = self.identifier
|
||||
self.data["is_linked"] = self.is_linked
|
||||
self.data["link"] = self.get_link()
|
||||
self.data['id'] = self.id
|
||||
self.data['value'] = self.value
|
||||
self.data['identifier'] = self.identifier
|
||||
self.data['is_linked'] = self.is_linked
|
||||
self.data['link'] = self.get_link()
|
||||
return self.data
|
||||
|
||||
|
||||
class Input(Socket):
|
||||
|
||||
def __init__(self, bl_input, tree):
|
||||
super().__init__(bl_input, tree)
|
||||
|
||||
|
@ -43,8 +49,10 @@ class Input(Socket):
|
|||
|
||||
@classmethod
|
||||
def from_dict(cls, data, node):
|
||||
|
||||
for bl_ipt in node.bl_node.inputs:
|
||||
if bl_ipt.identifier != data["identifier"]:
|
||||
|
||||
if bl_ipt.identifier != data['identifier']:
|
||||
continue
|
||||
|
||||
new_ipt = cls(bl_ipt, node.tree)
|
||||
|
@ -55,6 +63,7 @@ class Input(Socket):
|
|||
return new_ipt
|
||||
|
||||
def get_link(self):
|
||||
|
||||
if not self.is_linked:
|
||||
return None
|
||||
|
||||
|
@ -65,6 +74,7 @@ class Input(Socket):
|
|||
|
||||
|
||||
class Output(Socket):
|
||||
|
||||
def __init__(self, bl_output, tree):
|
||||
super().__init__(bl_output, tree)
|
||||
|
||||
|
@ -72,8 +82,10 @@ class Output(Socket):
|
|||
|
||||
@classmethod
|
||||
def from_dict(cls, data, node):
|
||||
|
||||
for bl_opt in node.bl_node.outputs:
|
||||
if bl_opt.identifier != data["identifier"]:
|
||||
|
||||
if bl_opt.identifier != data['identifier']:
|
||||
continue
|
||||
|
||||
new_opt = cls(bl_opt, node.tree)
|
||||
|
@ -84,6 +96,7 @@ class Output(Socket):
|
|||
return new_opt
|
||||
|
||||
def get_link(self):
|
||||
|
||||
links = []
|
||||
|
||||
if not self.is_linked:
|
||||
|
|
326
core/dumper.py
326
core/dumper.py
|
@ -7,12 +7,13 @@ from copy import copy
|
|||
from os.path import abspath
|
||||
|
||||
|
||||
|
||||
def get_default(prop):
|
||||
"""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)
|
||||
elif hasattr(prop, "default"):
|
||||
elif hasattr(prop, 'default'):
|
||||
return prop.default
|
||||
|
||||
|
||||
|
@ -27,7 +28,7 @@ def get_dumper(bl_object, fallback=None):
|
|||
|
||||
def get_bl_object(data):
|
||||
"""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
|
||||
|
||||
|
||||
|
@ -49,7 +50,7 @@ def load(data, bl_object=None):
|
|||
"""Generic Load to create an object from a dict"""
|
||||
|
||||
Dumper.pointers.clear()
|
||||
# print(Dumper.pointers)
|
||||
#print(Dumper.pointers)
|
||||
|
||||
if bl_object is None:
|
||||
bl_object = get_bl_object(data)
|
||||
|
@ -70,23 +71,19 @@ def set_attribute(bl_object, attr, value):
|
|||
class Dumper:
|
||||
pointers = {}
|
||||
includes = []
|
||||
excludes = ["rna_type", "bl_rna", "id_data", "depsgraph"]
|
||||
excludes = ["rna_type", "bl_rna", 'id_data', 'depsgraph']
|
||||
|
||||
@classmethod
|
||||
def properties(cls, bl_object):
|
||||
if cls.includes and not cls.excludes:
|
||||
return [bl_object.bl_rna.properties[p] for p in cls.includes]
|
||||
else:
|
||||
return [
|
||||
p
|
||||
for p in bl_object.bl_rna.properties
|
||||
if not p.identifier.startswith("bl_")
|
||||
and p.identifier not in cls.excludes
|
||||
]
|
||||
return [ p for p in bl_object.bl_rna.properties if not
|
||||
p.identifier.startswith('bl_') and p.identifier not in cls.excludes]
|
||||
|
||||
@classmethod
|
||||
def new(cls, data):
|
||||
print(f"New not implemented for data {data}")
|
||||
print(f'New not implemented for data {data}')
|
||||
|
||||
@classmethod
|
||||
def load(cls, data, bl_object=None):
|
||||
|
@ -96,36 +93,34 @@ class Dumper:
|
|||
if bl_object is None:
|
||||
return
|
||||
|
||||
# pprint(data)
|
||||
if bl_pointer := data.get("bl_pointer"):
|
||||
#pprint(data)
|
||||
if bl_pointer := data.get('bl_pointer'):
|
||||
cls.pointers[bl_pointer] = bl_object
|
||||
|
||||
props = cls.properties(bl_object)
|
||||
for key, value in sorted(
|
||||
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:
|
||||
for key, value in sorted(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:
|
||||
continue
|
||||
|
||||
prop = bl_object.bl_rna.properties[key]
|
||||
attr = getattr(bl_object, key)
|
||||
|
||||
if prop.type == "COLLECTION":
|
||||
if prop.type == 'COLLECTION':
|
||||
dumper = PropCollection
|
||||
if hasattr(attr, "bl_rna"):
|
||||
if hasattr(attr, 'bl_rna'):
|
||||
bl_type = attr.bl_rna.type_recast()
|
||||
dumper = get_dumper(bl_type, fallback=PropCollection)
|
||||
|
||||
dumper.load(value, attr)
|
||||
continue
|
||||
|
||||
elif prop.type == "POINTER":
|
||||
elif prop.type == 'POINTER':
|
||||
# if key == 'node_tree':
|
||||
# print('--------------')
|
||||
# print(bl_object, value)
|
||||
# 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:
|
||||
print(bl_object, "not loaded yet", prop)
|
||||
value = cls.pointers[value]
|
||||
|
@ -138,17 +133,17 @@ class Dumper:
|
|||
dumper = get_dumper(bl_type)
|
||||
|
||||
# If the pointer exist register the pointer then load data
|
||||
# print('-----', value)
|
||||
# pointer =
|
||||
#print('-----', value)
|
||||
#pointer =
|
||||
if attr is None:
|
||||
attr = dumper.new(value)
|
||||
|
||||
dumper.load(value, attr)
|
||||
# attr = getattr(bl_object, key)
|
||||
# if not attr:
|
||||
cls.pointers[value["bl_pointer"]] = attr
|
||||
#attr = getattr(bl_object, key)
|
||||
#if not attr:
|
||||
cls.pointers[value['bl_pointer']] = attr
|
||||
|
||||
if hasattr(attr, "update"):
|
||||
if hasattr(attr, 'update'):
|
||||
attr.update()
|
||||
|
||||
value = attr
|
||||
|
@ -157,38 +152,39 @@ class Dumper:
|
|||
set_attribute(bl_object, key, value)
|
||||
|
||||
# Some coll needs a manual update like curve mapping
|
||||
if hasattr(attr, "update"):
|
||||
if hasattr(attr, 'update'):
|
||||
attr.update()
|
||||
|
||||
elif not prop.is_readonly:
|
||||
# print(key, value)
|
||||
#print(key, value)
|
||||
set_attribute(bl_object, key, value)
|
||||
continue
|
||||
|
||||
# return bl_object
|
||||
#return bl_object
|
||||
|
||||
@classmethod
|
||||
def dump(cls, bl_object):
|
||||
if isinstance(bl_object, (str, int, float, dict, list, type(None))):
|
||||
return bl_object
|
||||
|
||||
# print('Dumping object', bl_object)
|
||||
#print('Dumping object', bl_object)
|
||||
|
||||
data = {"bl_pointer": bl_object.as_pointer()}
|
||||
cls.pointers[bl_object.as_pointer()] = bl_object
|
||||
|
||||
|
||||
for prop in cls.properties(bl_object):
|
||||
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
|
||||
|
||||
# print(prop.identifier)
|
||||
#print(prop.identifier)
|
||||
|
||||
value = getattr(bl_object, prop.identifier)
|
||||
|
||||
# Not storing default value
|
||||
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
|
||||
if isinstance(value, (str, int, float)) and value == prop.default:
|
||||
continue
|
||||
|
@ -196,11 +192,11 @@ class Dumper:
|
|||
if getattr(prop, "is_array", False):
|
||||
value = PropArray.dump(value)
|
||||
|
||||
elif prop.type == "COLLECTION":
|
||||
elif prop.type == 'COLLECTION':
|
||||
value = PropCollection.dump(value)
|
||||
|
||||
elif prop.type == "POINTER" and value:
|
||||
# if prop.identifier == 'image':
|
||||
elif prop.type == 'POINTER' and value:
|
||||
#if prop.identifier == 'image':
|
||||
# print(bl_object, cls.pointers)
|
||||
if value.as_pointer() in cls.pointers:
|
||||
value = value.as_pointer()
|
||||
|
@ -238,6 +234,7 @@ class PropCollection(Dumper):
|
|||
# Value cannot be None
|
||||
return [v for v in values if v is not None]
|
||||
|
||||
|
||||
@classmethod
|
||||
def load(cls, values, coll):
|
||||
if not values:
|
||||
|
@ -245,54 +242,53 @@ class PropCollection(Dumper):
|
|||
|
||||
dumper = None
|
||||
|
||||
if not hasattr(coll, "new"): # Static collection
|
||||
if not hasattr(coll, 'new'): # Static collection
|
||||
for item, value in zip(coll, values):
|
||||
dumper = dumper or get_dumper(item)
|
||||
dumper.load(value, item)
|
||||
|
||||
return
|
||||
|
||||
new_func = coll.bl_rna.functions["new"]
|
||||
new_func = coll.bl_rna.functions['new']
|
||||
for i, value in enumerate(values):
|
||||
if value.get("_new"):
|
||||
params = value["_new"]
|
||||
|
||||
if value.get('_new'):
|
||||
params = value['_new']
|
||||
else:
|
||||
params = {
|
||||
k: value.get(k, get_default(v))
|
||||
for k, v in new_func.parameters.items()[:-1]
|
||||
}
|
||||
params = {k: value.get(k, get_default(v)) for k, v in new_func.parameters.items()[:-1]}
|
||||
|
||||
# Replace arg pointer with bl object
|
||||
valid_pointers = True
|
||||
for param in coll.bl_rna.functions["new"].parameters:
|
||||
if param.identifier not in params or param.type != "POINTER":
|
||||
for param in coll.bl_rna.functions['new'].parameters:
|
||||
if param.identifier not in params or param.type != 'POINTER':
|
||||
continue
|
||||
|
||||
pointer_id = params[param.identifier]
|
||||
if bl_object := cls.pointers.get(pointer_id):
|
||||
params[param.identifier] = bl_object
|
||||
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
|
||||
|
||||
if not valid_pointers:
|
||||
continue
|
||||
|
||||
# print(param.identifier, cls.pointers[pointer_id])
|
||||
#print(param.identifier, cls.pointers[pointer_id])
|
||||
|
||||
try:
|
||||
|
||||
item = coll.new(**params)
|
||||
except RuntimeError as e:
|
||||
# print(e, coll.data)
|
||||
# print()
|
||||
#print(e, coll.data)
|
||||
#print()
|
||||
try:
|
||||
item = coll[i]
|
||||
except IndexError as e:
|
||||
# print(e, coll.data)
|
||||
#print(e, coll.data)
|
||||
break
|
||||
|
||||
dumper = get_dumper(item)
|
||||
dumper.load(value, item) # (item, value)
|
||||
dumper.load(value, item)#(item, value)
|
||||
|
||||
|
||||
class PropArray(Dumper):
|
||||
|
@ -311,25 +307,19 @@ class PropArray(Dumper):
|
|||
|
||||
class NodeSocket(Dumper):
|
||||
bl_type = bpy.types.NodeSocket
|
||||
excludes = Dumper.excludes + [
|
||||
"node",
|
||||
"links",
|
||||
"display_shape",
|
||||
"rna_type",
|
||||
"link_limit",
|
||||
]
|
||||
excludes = Dumper.excludes + ["node", "links", "display_shape", "rna_type", "link_limit"]
|
||||
|
||||
@classmethod
|
||||
def dump(cls, socket):
|
||||
if socket.is_unavailable:
|
||||
return None
|
||||
|
||||
# cls.pointers[socket.as_pointer()] = socket
|
||||
#cls.pointers[socket.as_pointer()] = socket
|
||||
|
||||
data = super().dump(socket)
|
||||
|
||||
# data["_id"] = socket.as_pointer()
|
||||
# data.pop('name', '')
|
||||
#data["_id"] = socket.as_pointer()
|
||||
#data.pop('name', '')
|
||||
|
||||
return data
|
||||
|
||||
|
@ -349,12 +339,11 @@ class NodeLink(Dumper):
|
|||
|
||||
@classmethod
|
||||
def dump(cls, link):
|
||||
return {
|
||||
"_new": {
|
||||
"input": link.from_socket.as_pointer(),
|
||||
"output": link.to_socket.as_pointer(),
|
||||
}
|
||||
}
|
||||
return {"_new": {
|
||||
"input": link.from_socket.as_pointer(),
|
||||
"output": link.to_socket.as_pointer()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class NodeTreeInterfaceSocket(Dumper):
|
||||
|
@ -363,31 +352,34 @@ class NodeTreeInterfaceSocket(Dumper):
|
|||
|
||||
@classmethod
|
||||
def dump(cls, socket):
|
||||
# cls.pointers[socket.as_pointer()] = socket
|
||||
#cls.pointers[socket.as_pointer()] = 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
|
||||
if socket.parent.parent:
|
||||
data["parent"] = socket.parent.as_pointer()
|
||||
data['parent'] = socket.parent.as_pointer()
|
||||
|
||||
return data
|
||||
|
||||
|
||||
class NodeSockets(PropCollection):
|
||||
|
||||
@classmethod
|
||||
def load(cls, values, coll):
|
||||
# return
|
||||
|
||||
#return
|
||||
|
||||
node_sockets = [s for s in coll if not s.is_unavailable]
|
||||
for socket, value in zip(node_sockets, values):
|
||||
cls.pointers[value["bl_pointer"]] = socket
|
||||
cls.pointers[value['bl_pointer']] = socket
|
||||
Dumper.load(value, socket)
|
||||
# for k, v in value.items():
|
||||
# if k not in socket.bl_rna.properties:
|
||||
|
@ -427,27 +419,20 @@ class NodeOutputs(NodeSockets):
|
|||
|
||||
class Node(Dumper):
|
||||
bl_type = bpy.types.Node
|
||||
excludes = Dumper.excludes + [
|
||||
"dimensions",
|
||||
"height",
|
||||
"internal_links",
|
||||
"paired_output",
|
||||
]
|
||||
excludes = Dumper.excludes + ["dimensions", "height", "internal_links", "paired_output"]
|
||||
|
||||
@classmethod
|
||||
def dump(cls, node=None):
|
||||
# cls.pointers[node.as_pointer()] = node
|
||||
#cls.pointers[node.as_pointer()] = node
|
||||
|
||||
data = super().dump(node)
|
||||
# data["_id"] = node.as_pointer()
|
||||
data["_new"] = {
|
||||
"type": node.bl_rna.identifier
|
||||
} # 'node_tree': node.id_data.as_pointer()
|
||||
#data["_id"] = node.as_pointer()
|
||||
data["_new"] = {"type": node.bl_rna.identifier} # 'node_tree': node.id_data.as_pointer()
|
||||
|
||||
if paired_output := getattr(node, "paired_output", None):
|
||||
data["_pair_with_output"] = paired_output.as_pointer()
|
||||
|
||||
# if node.parent:
|
||||
#if node.parent:
|
||||
# data['location'] -= Vector()node.parent.location
|
||||
|
||||
return data
|
||||
|
@ -456,15 +441,15 @@ class Node(Dumper):
|
|||
def load(cls, data, node):
|
||||
if node is None:
|
||||
return
|
||||
# cls.pointers[data['bl_pointer']] = node
|
||||
#cls.pointers[data['bl_pointer']] = node
|
||||
|
||||
inputs = copy(data.pop("inputs", []))
|
||||
outputs = copy(data.pop("outputs", []))
|
||||
inputs = copy(data.pop('inputs', []))
|
||||
outputs = copy(data.pop('outputs', []))
|
||||
|
||||
super().load(data, node)
|
||||
|
||||
data["inputs"] = inputs
|
||||
data["outputs"] = outputs
|
||||
data['inputs'] = inputs
|
||||
data['outputs'] = outputs
|
||||
|
||||
# Loading input and outputs after the properties
|
||||
super().load({"inputs": inputs, "outputs": outputs}, node)
|
||||
|
@ -472,7 +457,7 @@ class Node(Dumper):
|
|||
if node.parent:
|
||||
node.location += node.parent.location
|
||||
|
||||
# if node.type != 'FRAME':
|
||||
#if node.type != 'FRAME':
|
||||
# node.location.y -= 500
|
||||
|
||||
|
||||
|
@ -487,19 +472,20 @@ class NodeTreeInterface(Dumper):
|
|||
|
||||
@classmethod
|
||||
def load(cls, data, interface):
|
||||
print("Load Interface")
|
||||
|
||||
for value in data.get("items_tree", []):
|
||||
item_type = value.get("item_type", "SOCKET")
|
||||
if item_type == "SOCKET":
|
||||
item = interface.new_socket(**value["_new"])
|
||||
elif item_type == "PANEL":
|
||||
# print(value['_new'])
|
||||
item = interface.new_panel(**value["_new"])
|
||||
print('Load Interface')
|
||||
|
||||
for value in data.get('items_tree', []):
|
||||
item_type = value.get('item_type', 'SOCKET')
|
||||
if item_type == 'SOCKET':
|
||||
item = interface.new_socket(**value['_new'])
|
||||
elif item_type == 'PANEL':
|
||||
#print(value['_new'])
|
||||
item = interface.new_panel(**value['_new'])
|
||||
|
||||
NodeTreeInterfaceSocket.load(value, item)
|
||||
|
||||
interface.active_index = data.get("active_index", 0)
|
||||
interface.active_index = data.get('active_index', 0)
|
||||
|
||||
|
||||
class Nodes(PropCollection):
|
||||
|
@ -511,16 +497,13 @@ class Nodes(PropCollection):
|
|||
|
||||
# Pair zone input and output
|
||||
for node_data in values:
|
||||
if paired_output_id := node_data.get("_pair_with_output", None):
|
||||
node = cls.pointers[node_data["bl_pointer"]]
|
||||
if paired_output_id := node_data.get('_pair_with_output', None):
|
||||
node = cls.pointers[node_data['bl_pointer']]
|
||||
node.pair_with_output(cls.pointers[paired_output_id])
|
||||
|
||||
# print(node, node_data['outputs'])
|
||||
#print(node, node_data['outputs'])
|
||||
|
||||
Dumper.load(
|
||||
{"inputs": node_data["inputs"], "outputs": node_data["outputs"]},
|
||||
node,
|
||||
)
|
||||
Dumper.load({"inputs": node_data['inputs'], "outputs": node_data['outputs']}, node)
|
||||
|
||||
|
||||
class NodeTree(Dumper):
|
||||
|
@ -530,42 +513,32 @@ class NodeTree(Dumper):
|
|||
|
||||
@classmethod
|
||||
def new(cls, data):
|
||||
if link := data.get("_link"):
|
||||
with bpy.data.libraries.load(link["filepath"], link=True) as (
|
||||
data_from,
|
||||
data_to,
|
||||
):
|
||||
setattr(data_to, link["data_type"], [link["name"]])
|
||||
return getattr(data_to, link["data_type"])[0]
|
||||
if link := data.get('_link'):
|
||||
with bpy.data.libraries.load(link['filepath'], link=True) as (data_from, data_to):
|
||||
setattr(data_to, link['data_type'], [link['name']])
|
||||
return getattr(data_to, link['data_type'])[0]
|
||||
|
||||
return bpy.data.node_groups.new(**data["_new"])
|
||||
|
||||
@classmethod
|
||||
def dump(cls, node_tree):
|
||||
if node_tree.library:
|
||||
data = {"bl_pointer": node_tree.as_pointer()}
|
||||
filepath = abspath(
|
||||
bpy.path.abspath(
|
||||
node_tree.library.filepath, library=node_tree.library.library
|
||||
)
|
||||
)
|
||||
data["_link"] = {
|
||||
"filepath": filepath,
|
||||
"data_type": "node_groups",
|
||||
"name": node_tree.name,
|
||||
}
|
||||
data = {'bl_pointer': node_tree.as_pointer()}
|
||||
filepath = abspath(bpy.path.abspath(node_tree.library.filepath, library=node_tree.library.library))
|
||||
data["_link"] = {"filepath": filepath, "data_type": 'node_groups', 'name': node_tree.name}
|
||||
else:
|
||||
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
|
||||
|
||||
|
||||
class Points(PropCollection):
|
||||
|
||||
@classmethod
|
||||
def load(cls, values, coll):
|
||||
new_func = coll.bl_rna.functions["new"]
|
||||
params = {k: get_default(v) + 1.1 for k, v in new_func.parameters.items()[:-1]}
|
||||
new_func = coll.bl_rna.functions['new']
|
||||
params = {k: get_default(v)+1.1 for k, v in new_func.parameters.items()[:-1]}
|
||||
|
||||
# Match the same number of elements in collection
|
||||
if len(values) > len(coll):
|
||||
|
@ -574,8 +547,8 @@ class Points(PropCollection):
|
|||
|
||||
for i, value in enumerate(values):
|
||||
Dumper.load(value, coll[i])
|
||||
# for k, v in value.items():
|
||||
# setattr(coll[i], k, v)
|
||||
#for k, v in value.items():
|
||||
#setattr(coll[i], k, v)
|
||||
|
||||
|
||||
class CurveMapPoints(Points):
|
||||
|
@ -610,7 +583,7 @@ class AOVs(PropCollection):
|
|||
@classmethod
|
||||
def load(cls, values, coll):
|
||||
for value in values:
|
||||
aov = coll.get(value["name"])
|
||||
aov = coll.get(value['name'])
|
||||
|
||||
if not aov:
|
||||
aov = coll.add()
|
||||
|
@ -622,7 +595,7 @@ class Image(Dumper):
|
|||
bl_type = bpy.types.Image
|
||||
|
||||
excludes = []
|
||||
includes = ["name", "filepath"]
|
||||
includes = ['name', 'filepath']
|
||||
|
||||
@classmethod
|
||||
def new(cls, data):
|
||||
|
@ -632,20 +605,20 @@ class Image(Dumper):
|
|||
# if image is None:
|
||||
# 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):
|
||||
bl_type = bpy.types.Material
|
||||
|
||||
excludes = Dumper.excludes + ["preview", "original"]
|
||||
excludes = Dumper.excludes + ['preview', "original"]
|
||||
|
||||
@classmethod
|
||||
def new(cls, data):
|
||||
material = bpy.data.materials.get(data.get("name", ""))
|
||||
material = bpy.data.materials.get(data.get('name', ''))
|
||||
|
||||
if material is None:
|
||||
material = bpy.data.materials.new(data["name"])
|
||||
material = bpy.data.materials.new(data['name'])
|
||||
|
||||
return material
|
||||
|
||||
|
@ -653,25 +626,26 @@ class Material(Dumper):
|
|||
class Object(Dumper):
|
||||
bl_type = bpy.types.Object
|
||||
excludes = []
|
||||
includes = ["name"]
|
||||
includes = ['name']
|
||||
|
||||
@classmethod
|
||||
def new(cls, data):
|
||||
if name := data.get("name"):
|
||||
if name := data.get('name'):
|
||||
return bpy.data.objects.get(name)
|
||||
|
||||
|
||||
class Scene(Dumper):
|
||||
bl_type = bpy.types.Scene
|
||||
excludes = []
|
||||
includes = ["name"]
|
||||
includes = ['name']
|
||||
|
||||
|
||||
@classmethod
|
||||
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 bpy.data.scenes.new(name=data.get("name", ""))
|
||||
return bpy.data.scenes.new(name=data.get('name', ''))
|
||||
|
||||
"""
|
||||
@classmethod
|
||||
|
@ -687,15 +661,14 @@ class Scene(Dumper):
|
|||
}
|
||||
"""
|
||||
|
||||
|
||||
class Collection(Dumper):
|
||||
bl_type = bpy.types.Collection
|
||||
includes = ["name"]
|
||||
includes = ['name']
|
||||
excludes = []
|
||||
|
||||
@classmethod
|
||||
def new(cls, data):
|
||||
if name := data.get("name"):
|
||||
if name := data.get('name'):
|
||||
return bpy.data.collections.get(name)
|
||||
|
||||
# @classmethod
|
||||
|
@ -709,15 +682,16 @@ class Collection(Dumper):
|
|||
class CompositorNodeRLayers(Node):
|
||||
bl_type = bpy.types.CompositorNodeRLayers
|
||||
|
||||
excludes = Dumper.excludes + ["scene"]
|
||||
excludes = Dumper.excludes + ['scene']
|
||||
|
||||
@classmethod
|
||||
def load(cls, data, node):
|
||||
# print('load CompositorNodeRLayers')
|
||||
|
||||
scene_data = data.pop("scene")
|
||||
# print(scene_data)
|
||||
layer = data.pop("layer")
|
||||
#print('load CompositorNodeRLayers')
|
||||
|
||||
scene_data = data.pop('scene')
|
||||
#print(scene_data)
|
||||
layer = data.pop('layer')
|
||||
scene = Scene.new(scene_data)
|
||||
Scene.load(scene_data, scene)
|
||||
|
||||
|
@ -729,37 +703,36 @@ class CompositorNodeRLayers(Node):
|
|||
# Resetter the view_layer because it might have been created
|
||||
# with the scene attr in the dictionnary and nor available yet
|
||||
|
||||
# print(bpy.)
|
||||
#print(bpy.)
|
||||
|
||||
|
||||
|
||||
@classmethod
|
||||
def dump(cls, node):
|
||||
# Add scene and viewlayer passes
|
||||
data = super().dump(node)
|
||||
|
||||
# if
|
||||
#if
|
||||
|
||||
view_layer = node.scene.view_layers[node.layer]
|
||||
view_layer_data = ViewLayer.dump(view_layer)
|
||||
|
||||
"""
|
||||
'''
|
||||
view_layer_data = {
|
||||
"name": view_layer.name}
|
||||
properties = {p.name: p for p in view_layer.bl_rna.properties}
|
||||
for prop in view_layer.bl_rna:
|
||||
if prop.identifier.startswith('use_pass'):
|
||||
view_layer_data[prop.identifier]
|
||||
"""
|
||||
'''
|
||||
|
||||
# cls.pointers[bl_object.as_pointer()] = bl_object
|
||||
#cls.pointers[bl_object.as_pointer()] = bl_object
|
||||
|
||||
data["scene"] = {
|
||||
"bl_pointer": node.scene.as_pointer(),
|
||||
"name": node.scene.name,
|
||||
"render": {
|
||||
"bl_pointer": node.scene.render.as_pointer(),
|
||||
"engine": node.scene.render.engine,
|
||||
},
|
||||
"view_layers": [view_layer_data],
|
||||
data['scene'] = {
|
||||
'bl_pointer': node.scene.as_pointer(),
|
||||
'name': node.scene.name,
|
||||
'render' : {'bl_pointer': node.scene.render.as_pointer(), "engine": node.scene.render.engine},
|
||||
'view_layers': [view_layer_data]
|
||||
}
|
||||
|
||||
return data
|
||||
|
@ -767,16 +740,9 @@ class CompositorNodeRLayers(Node):
|
|||
|
||||
class ViewLayer(Dumper):
|
||||
bl_type = bpy.types.ViewLayer
|
||||
excludes = Dumper.excludes + [
|
||||
"freestyle_settings",
|
||||
"eevee",
|
||||
"cycles",
|
||||
"active_layer_collection",
|
||||
"active_aov",
|
||||
"active_lightgroup_index",
|
||||
"active_lightgroup",
|
||||
]
|
||||
# includes = ['name']
|
||||
excludes = Dumper.excludes + ['freestyle_settings', 'eevee', 'cycles', 'active_layer_collection',
|
||||
'active_aov', 'active_lightgroup_index', 'active_lightgroup']
|
||||
#includes = ['name']
|
||||
|
||||
|
||||
class ViewLayers(PropCollection):
|
||||
|
@ -784,12 +750,12 @@ class ViewLayers(PropCollection):
|
|||
|
||||
@classmethod
|
||||
def load(cls, values, coll):
|
||||
# print('LOAD VIEWLAYERS', values)
|
||||
#print('LOAD VIEWLAYERS', values)
|
||||
for value in values:
|
||||
view_layer = coll.get(value["name"])
|
||||
view_layer = coll.get(value['name'])
|
||||
|
||||
if view_layer is None:
|
||||
view_layer = coll.new(value["name"])
|
||||
view_layer = coll.new(value['name'])
|
||||
|
||||
Dumper.load(value, view_layer)
|
||||
|
||||
|
|
|
@ -1,18 +1,18 @@
|
|||
|
||||
import bpy
|
||||
import re
|
||||
|
||||
|
||||
def clean_name(name):
|
||||
if re.match(r"(.*)\.\d{3}$", name):
|
||||
if re.match(r'(.*)\.\d{3}$', name):
|
||||
return name[:-4]
|
||||
return name
|
||||
|
||||
|
||||
def is_node_groups_duplicate(node_groups):
|
||||
node_group_types = sorted([n.type for n in node_groups[0].nodes])
|
||||
return all(
|
||||
sorted([n.type for n in ng.nodes]) == node_group_types for ng in node_groups[1:]
|
||||
)
|
||||
return all( sorted([n.type for n in ng.nodes]) ==
|
||||
node_group_types for ng in node_groups[1:])
|
||||
|
||||
|
||||
def remap_node_group_duplicates(nodes=None, force=False):
|
||||
|
@ -41,7 +41,7 @@ def remap_node_group_duplicates(nodes=None, force=False):
|
|||
continue
|
||||
|
||||
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)
|
||||
|
||||
|
@ -50,13 +50,11 @@ def remap_node_group_duplicates(nodes=None, force=False):
|
|||
|
||||
if not is_duplicate and not force:
|
||||
failed.append((node_group.name, node_groups[0].name))
|
||||
print(
|
||||
f"Cannot merge Nodegroup {node_group.name} with {node_groups[0].name} they are different"
|
||||
)
|
||||
print(f'Cannot merge Nodegroup {node_group.name} with {node_groups[0].name} they are different')
|
||||
continue
|
||||
|
||||
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])
|
||||
bpy.data.node_groups.remove(node_group)
|
||||
|
|
|
@ -1,17 +1,16 @@
|
|||
import bpy
|
||||
|
||||
|
||||
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
|
||||
if mod_to_node: # syntax for node and modifier are slightly different
|
||||
if mod_to_node: # syntax for node and modifier are slightly different
|
||||
tree = src.node_group.interface.items_tree
|
||||
else:
|
||||
tree = src.node_tree.interface.items_tree
|
||||
|
||||
for param in tree:
|
||||
if param.socket_type == "NodeSocketGeometry":
|
||||
if param.socket_type == 'NodeSocketGeometry':
|
||||
continue
|
||||
if param.in_out == "OUTPUT":
|
||||
if param.in_out == 'OUTPUT':
|
||||
continue
|
||||
|
||||
# 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:
|
||||
tgt[identifier] = src.inputs[identifier].default_value
|
||||
|
||||
|
||||
def set_group_inputs(target, objects, group):
|
||||
mod = target.modifiers[0]
|
||||
node_dct = {} # used for cleanup
|
||||
node_dct = {} # used for cleanup
|
||||
for key, inp in get_node_inputs(objects).items():
|
||||
# add the socket to the node group / modifier pannel
|
||||
sock = group.interface.new_socket(
|
||||
inp["label"], in_out="INPUT", socket_type=inp["socket"]
|
||||
)
|
||||
sock = group.interface.new_socket(inp["label"],in_out="INPUT",socket_type=inp["socket"])
|
||||
mod[sock.identifier] = inp["data"]
|
||||
|
||||
# inspect all nodes and add a group input node when that socket is used
|
||||
for node in parse_nodes(objects):
|
||||
for param in node.node_tree.interface.items_tree:
|
||||
nkey = get_input_socket_key(node, param)
|
||||
if not nkey:
|
||||
continue
|
||||
if not nkey: continue
|
||||
if nkey == key:
|
||||
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)
|
||||
|
||||
# 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)
|
||||
if not nkey:
|
||||
continue
|
||||
if not nkey: continue
|
||||
if nkey == key:
|
||||
input_node = add_input_node(group, node, "Object", sock)
|
||||
node.inputs["Object"].default_value = None
|
||||
input_node = add_input_node(group, node, 'Object', sock)
|
||||
node.inputs['Object'].default_value = None
|
||||
|
||||
# add to dict for cleanup
|
||||
if not node in node_dct.keys():
|
||||
|
@ -78,7 +72,6 @@ def set_group_inputs(target, objects, group):
|
|||
input_node.location[1] += 50 * offset
|
||||
hide_sockets(input_node)
|
||||
|
||||
|
||||
def get_node_link_value(node, param_name, org_mod):
|
||||
if not org_mod:
|
||||
return
|
||||
|
@ -89,25 +82,20 @@ def get_node_link_value(node, param_name, org_mod):
|
|||
|
||||
return org_mod[socket_id]
|
||||
|
||||
|
||||
def get_geo_socket(node, input=True):
|
||||
if node.type != "GROUP":
|
||||
return "Geometry"
|
||||
return('Geometry')
|
||||
for param in node.node_tree.interface.items_tree:
|
||||
if param.socket_type != "NodeSocketGeometry":
|
||||
if param.socket_type != 'NodeSocketGeometry':
|
||||
continue
|
||||
if input and param.in_out == "INPUT":
|
||||
return param.identifier
|
||||
if not input and param.in_out == "OUTPUT":
|
||||
return param.identifier
|
||||
if input and param.in_out == 'INPUT' : return param.identifier
|
||||
if not input and param.in_out == 'OUTPUT' : return param.identifier
|
||||
return None
|
||||
|
||||
|
||||
def get_input_socket_key(node, param):
|
||||
if node.type == "GROUP":
|
||||
if param.in_out != "INPUT":
|
||||
if param.in_out != 'INPUT':
|
||||
return False
|
||||
if not param.socket_type in ["NodeSocketObject", "NodeSocketCollection"]:
|
||||
if not param.socket_type in ['NodeSocketObject','NodeSocketCollection']:
|
||||
return False
|
||||
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}"
|
||||
|
||||
if node.type == "OBJECT_INFO":
|
||||
tgt = node.inputs["Object"].default_value
|
||||
tgt = node.inputs['Object'].default_value
|
||||
if not tgt:
|
||||
return False
|
||||
return f"Object {tgt.name}"
|
||||
|
||||
|
||||
def get_node_inputs(combined_nodes):
|
||||
# inputs["Col COL.name"] = {name = COL.name, data = COL, socket = "COLLECTION"}
|
||||
# inputs["Obj OBJ.name"] = {name = OBJ.name, data = OBJ, socket = "OBJECT"}
|
||||
|
@ -132,28 +119,17 @@ def get_node_inputs(combined_nodes):
|
|||
if not key:
|
||||
continue
|
||||
tgt = node.inputs[param.identifier].default_value
|
||||
inputs[key] = {
|
||||
"name": tgt.name,
|
||||
"data": tgt,
|
||||
"label": param.name,
|
||||
"socket": param.socket_type,
|
||||
}
|
||||
inputs[key] = {'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)
|
||||
if not key:
|
||||
continue
|
||||
tgt = node.inputs["Object"].default_value
|
||||
inputs[key] = {
|
||||
"name": tgt.name,
|
||||
"data": tgt,
|
||||
"label": "Source OB",
|
||||
"socket": "NodeSocketObject",
|
||||
}
|
||||
tgt = node.inputs['Object'].default_value
|
||||
inputs[key] = {'name': tgt.name, 'data': tgt, 'label': 'Source OB' , 'socket': "NodeSocketObject"}
|
||||
|
||||
return inputs
|
||||
|
||||
|
||||
def get_node_bounds(objects, mode=0, x=0, y=0):
|
||||
min_x = min_y = 10000000
|
||||
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 node in ob:
|
||||
co = node.location
|
||||
min_x = min(co[0], min_x)
|
||||
max_x = max(co[0], max_x)
|
||||
min_x = min(co[0],min_x)
|
||||
max_x = max(co[0],max_x)
|
||||
|
||||
min_y = min(co[1], min_y)
|
||||
max_y = max(co[1], max_y)
|
||||
min_y = min(co[1],min_y)
|
||||
max_y = max(co[1],max_y)
|
||||
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):
|
||||
scn = bpy.context.scene
|
||||
|
@ -177,29 +152,23 @@ def get_collection(name):
|
|||
|
||||
# look for existing
|
||||
for c in bpy.data.collections:
|
||||
if c.name == name:
|
||||
col = c
|
||||
if c.name == name: col = c
|
||||
|
||||
# create if needed
|
||||
if not col:
|
||||
col = bpy.data.collections.new(name)
|
||||
if not col: col = bpy.data.collections.new(name)
|
||||
|
||||
# link to scene if needed
|
||||
for c in scn.collection.children_recursive:
|
||||
if c.name == col.name:
|
||||
link = True
|
||||
if c.name == col.name: link = True
|
||||
if not link:
|
||||
scn.collection.children.link(col)
|
||||
return col
|
||||
|
||||
|
||||
def get_mod_frames(grp):
|
||||
frames = []
|
||||
for node in grp.nodes:
|
||||
if node.type == "FRAME":
|
||||
frames.append(node)
|
||||
return frames
|
||||
|
||||
if node.type == "FRAME": frames.append(node)
|
||||
return(frames)
|
||||
|
||||
def get_frame_childrens(frame):
|
||||
childrens = []
|
||||
|
@ -214,16 +183,13 @@ def get_frame_childrens(frame):
|
|||
childrens = [locs[x] for x in entries]
|
||||
return childrens
|
||||
|
||||
|
||||
def parse_nodes(combined_nodes, type="GROUP"):
|
||||
def parse_nodes(combined_nodes, type = "GROUP"):
|
||||
nodes = []
|
||||
for frame in combined_nodes:
|
||||
for node in frame:
|
||||
if node.type == type:
|
||||
nodes.append(node)
|
||||
if node.type == type: nodes.append(node)
|
||||
return nodes
|
||||
|
||||
|
||||
def copy_source_ob(ob, col):
|
||||
# est-ce que l'objet a des data ? si oui on cree une copie ,
|
||||
# si non on renvois None
|
||||
|
@ -244,8 +210,7 @@ def copy_source_ob(ob, col):
|
|||
col.objects.link(new_ob)
|
||||
return new_ob
|
||||
|
||||
|
||||
def hide_sockets(node, collapse=True):
|
||||
def hide_sockets(node,collapse = True):
|
||||
for socket in node.outputs:
|
||||
if not socket.links:
|
||||
socket.hide = True
|
||||
|
@ -255,15 +220,14 @@ def hide_sockets(node, collapse=True):
|
|||
if collapse:
|
||||
node.hide = True
|
||||
|
||||
|
||||
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[1] += 70
|
||||
group_input_node.label = socket.name
|
||||
group.links.new(group_input_node.outputs[socket.identifier], node.inputs[param_id])
|
||||
return group_input_node
|
||||
|
||||
group.links.new(group_input_node.outputs[socket.identifier],
|
||||
node.inputs[param_id])
|
||||
return(group_input_node)
|
||||
|
||||
def add_material_node(ob, group, nodes):
|
||||
if not ob.material_slots:
|
||||
|
@ -271,59 +235,54 @@ def add_material_node(ob, group, nodes):
|
|||
if not ob.material_slots[0].material:
|
||||
return nodes
|
||||
last_node = nodes[-1:][0]
|
||||
node = group.nodes.new("GeometryNodeSetMaterial")
|
||||
node.inputs["Material"].default_value = ob.material_slots[0].material
|
||||
node = group.nodes.new('GeometryNodeSetMaterial')
|
||||
node.inputs['Material'].default_value = ob.material_slots[0].material
|
||||
node.location = last_node.location
|
||||
node.location[0] += 300
|
||||
nodes.append(node)
|
||||
return nodes
|
||||
|
||||
|
||||
def join_nodes(group, nodes):
|
||||
prev = None
|
||||
for i, node in enumerate(nodes):
|
||||
for i , node in enumerate(nodes):
|
||||
if not prev:
|
||||
prev = node
|
||||
continue
|
||||
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:
|
||||
continue
|
||||
group.links.new(prev.outputs[geo_out], node.inputs[geo_in])
|
||||
prev = node
|
||||
|
||||
|
||||
def frame_nodes(group, nodes, ob):
|
||||
nd = group.nodes.new("NodeFrame")
|
||||
nd = group.nodes.new('NodeFrame')
|
||||
# frame = nodes.new(type='NodeFrame')
|
||||
for n in nodes:
|
||||
n.parent = nd
|
||||
|
||||
nd.label = ob.name
|
||||
|
||||
|
||||
def combine_ob(ob, group, y=0, col=None):
|
||||
nodes = []
|
||||
|
||||
# object info node
|
||||
nd = group.nodes.new("GeometryNodeObjectInfo")
|
||||
nd = group.nodes.new('GeometryNodeObjectInfo')
|
||||
nd.location[0] -= 300
|
||||
nd.location[1] = y * 800
|
||||
nd.transform_space = "RELATIVE"
|
||||
nd.inputs["Object"].default_value = copy_source_ob(
|
||||
ob, col
|
||||
) # si l'objet contient des data on crée une copie
|
||||
nd.inputs['Object'].default_value = copy_source_ob(ob, col) # si l'objet contient des data on crée une copie
|
||||
nodes.append(nd)
|
||||
|
||||
# ob modifiers
|
||||
for x, md in enumerate(ob.modifiers):
|
||||
if md.type != "NODES":
|
||||
for x,md in enumerate(ob.modifiers):
|
||||
if md.type != "NODES" :
|
||||
print(abordage)
|
||||
if md.node_group == group:
|
||||
continue
|
||||
|
||||
nd = group.nodes.new("GeometryNodeGroup")
|
||||
nd = group.nodes.new('GeometryNodeGroup')
|
||||
nd.label = md.name
|
||||
nd.width = 230
|
||||
nd.location[0] = x * 300
|
||||
|
@ -337,7 +296,6 @@ def combine_ob(ob, group, y=0, col=None):
|
|||
frame_nodes(group, nodes, ob)
|
||||
return nodes
|
||||
|
||||
|
||||
def gen_target_ob(group, col=None):
|
||||
ob = gen_empty_ob(group.name, col=col)
|
||||
mod = ob.modifiers.new(group.name, "NODES")
|
||||
|
@ -345,22 +303,20 @@ def gen_target_ob(group, col=None):
|
|||
|
||||
ob.show_name = True
|
||||
bpy.context.view_layer.objects.active = ob
|
||||
return ob
|
||||
|
||||
return(ob)
|
||||
|
||||
def gen_empty_ob(name, col=None):
|
||||
scn = bpy.context.scene
|
||||
ob = bpy.data.objects.new(name, object_data=bpy.data.meshes.new(name))
|
||||
|
||||
ob.data.materials.append(None)
|
||||
ob.material_slots[0].link = "OBJECT"
|
||||
ob.material_slots[0].link = 'OBJECT'
|
||||
|
||||
if not col:
|
||||
scn.collection.objects.link(ob)
|
||||
else:
|
||||
col.objects.link(ob)
|
||||
return ob
|
||||
|
||||
return(ob)
|
||||
|
||||
def assign_modifiers(ob, frame, org_modifier):
|
||||
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)
|
||||
mod.node_group.interface_update(bpy.context)
|
||||
|
||||
|
||||
def join_branches(objects, group):
|
||||
# join all trees and add an output node
|
||||
join = group.nodes.new("GeometryNodeJoinGeometry")
|
||||
out = group.nodes.new("NodeGroupOutput")
|
||||
out_sock = group.interface.new_socket(
|
||||
"Geometry", in_out="OUTPUT", socket_type="NodeSocketGeometry"
|
||||
)
|
||||
join = group.nodes.new('GeometryNodeJoinGeometry')
|
||||
out = group.nodes.new('NodeGroupOutput')
|
||||
out_sock = group.interface.new_socket("Geometry",in_out="OUTPUT",socket_type="NodeSocketGeometry")
|
||||
|
||||
loc = get_node_bounds(objects, x=500)
|
||||
join.location = loc
|
||||
|
@ -388,52 +341,45 @@ def join_branches(objects, group):
|
|||
|
||||
for ob in objects:
|
||||
node = ob[-1:][0]
|
||||
group.links.new(
|
||||
node.outputs[get_geo_socket(node, input=False)],
|
||||
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(node.outputs[get_geo_socket(node, input=False)],
|
||||
join.inputs[get_geo_socket(join)])
|
||||
|
||||
group.links.new(join.outputs[get_geo_socket(join, input=False)],
|
||||
out.inputs[out_sock.identifier])
|
||||
|
||||
def gen_extracted_ob(name, frame, col, mod):
|
||||
ob = None
|
||||
for node in get_frame_childrens(frame):
|
||||
if node.type != "OBJECT_INFO":
|
||||
continue
|
||||
target = get_node_link_value(node, "Object", mod)
|
||||
target = get_node_link_value(node, 'Object', mod)
|
||||
|
||||
if target:
|
||||
ob = target.copy()
|
||||
ob.data = ob.data.copy()
|
||||
col.objects.link(ob)
|
||||
if not ob:
|
||||
ob = gen_empty_ob(name, col=col)
|
||||
if not ob: ob = gen_empty_ob(name , col = col)
|
||||
|
||||
# assign material
|
||||
for node in get_frame_childrens(frame):
|
||||
if node.type != "SET_MATERIAL":
|
||||
continue
|
||||
ob.material_slots[0].material = node.inputs["Material"].default_value
|
||||
ob.material_slots[0].material = node.inputs['Material'].default_value
|
||||
return ob
|
||||
|
||||
|
||||
def combine_objects(objs):
|
||||
name = f"NODEGROUP_combined"
|
||||
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 = []
|
||||
for y, ob in enumerate(objs):
|
||||
for y , ob in enumerate(objs):
|
||||
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)
|
||||
join_branches(objects, group)
|
||||
|
||||
|
||||
def extract_objects(object):
|
||||
mod = object.modifiers[0]
|
||||
grp = mod.node_group
|
||||
|
@ -444,9 +390,8 @@ def extract_objects(object):
|
|||
ob = gen_extracted_ob(name, frame, col, mod)
|
||||
assign_modifiers(ob, frame, mod)
|
||||
|
||||
|
||||
# combine_objects(bpy.context.selected_objects)
|
||||
# extract_objects(bpy.context.active_object)
|
||||
#combine_objects(bpy.context.selected_objects)
|
||||
#extract_objects(bpy.context.active_object)
|
||||
"""
|
||||
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
|
||||
|
|
|
@ -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'))
|
176
operators.py
176
operators.py
|
@ -14,29 +14,26 @@ import bpy
|
|||
from bpy.props import BoolProperty, EnumProperty
|
||||
from bpy.types import Operator
|
||||
|
||||
# from node_kit.core.node_tree import NodeTree
|
||||
from .core.dumper import dump, load
|
||||
#from node_kit.core.node_tree import NodeTree
|
||||
from . core.dumper import dump, load
|
||||
from .core.node_utils import remap_node_group_duplicates
|
||||
from .core.pack_nodes import combine_objects, extract_objects
|
||||
|
||||
|
||||
class NODEKIT_OT_copy(Operator):
|
||||
bl_idname = "node_kit.copy_node_tree"
|
||||
bl_label = "Copy nodes"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_idname = 'node_kit.copy_node_tree'
|
||||
bl_label = 'Copy nodes'
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
select_only: BoolProperty(default=True)
|
||||
|
||||
def execute(self, context):
|
||||
|
||||
ntree = context.space_data.edit_tree
|
||||
if self.select_only:
|
||||
ntree_data = {
|
||||
"nodes": dump(
|
||||
[n for n in ntree.nodes if n.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]
|
||||
),
|
||||
"nodes" : dump([n for n in ntree.nodes if n.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:
|
||||
ntree_data = dump(ntree)
|
||||
|
@ -45,91 +42,76 @@ class NODEKIT_OT_copy(Operator):
|
|||
|
||||
context.window_manager.clipboard = json.dumps(ntree_data)
|
||||
|
||||
return {"FINISHED"}
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
class NODEKIT_OT_paste(Operator):
|
||||
bl_idname = "node_kit.paste_node_tree"
|
||||
bl_label = "Paste nodes"
|
||||
bl_idname = 'node_kit.paste_node_tree'
|
||||
bl_label = 'Paste nodes'
|
||||
|
||||
def execute(self, context):
|
||||
|
||||
ntree_data = json.loads(context.window_manager.clipboard)
|
||||
load(ntree_data, context.space_data.edit_tree)
|
||||
|
||||
return {"FINISHED"}
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
class NODEKIT_OT_remap_node_group_duplicates(Operator):
|
||||
bl_idname = "node_kit.remap_node_group_duplicates"
|
||||
bl_label = "Clean nodes"
|
||||
bl_idname = 'node_kit.remap_node_group_duplicates'
|
||||
bl_label = 'Clean nodes'
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
selection: EnumProperty(
|
||||
items=[(s, s.title(), "") for s in ("ALL", "SELECTED", "CURRENT")],
|
||||
default="CURRENT",
|
||||
name="All Nodes",
|
||||
)
|
||||
force: BoolProperty(
|
||||
default=False,
|
||||
description="Remap nodes even if there are different",
|
||||
name="Force",
|
||||
)
|
||||
selection : EnumProperty(items=[(s, s.title(), '') for s in ('ALL', 'SELECTED', 'CURRENT')], default="CURRENT", name='All Nodes')
|
||||
force : BoolProperty(default=False, description='Remap nodes even if there are different', name='Force')
|
||||
|
||||
def invoke(self, context, event):
|
||||
return context.window_manager.invoke_props_dialog(self)
|
||||
|
||||
def execute(self, context):
|
||||
nodes = None
|
||||
if self.selection == "SELECTED":
|
||||
nodes = [
|
||||
n.node_tree
|
||||
for n in context.space_data.edit_tree.nodes
|
||||
if n.type == "GROUP" and n.select
|
||||
]
|
||||
elif self.selection == "ACTIVE":
|
||||
if self.selection == 'SELECTED':
|
||||
nodes = [ n.node_tree for n in context.space_data.edit_tree.nodes
|
||||
if n.type == "GROUP" and n.select]
|
||||
elif self.selection == 'ACTIVE':
|
||||
active_node = context.space_data.edit_tree
|
||||
nodes = [active_node]
|
||||
|
||||
merged, failed = remap_node_group_duplicates(nodes=nodes, force=self.force)
|
||||
|
||||
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"}
|
||||
|
||||
self.report(
|
||||
{"INFO"},
|
||||
f"{len(merged)} Node Groups Remapped, {len(failed)} Node Groups failed",
|
||||
)
|
||||
self.report({"INFO"}, f'{len(merged)} Node Groups Remapped, {len(failed)} Node Groups failed')
|
||||
|
||||
return {"FINISHED"}
|
||||
return {'FINISHED'}
|
||||
|
||||
def draw(self, context):
|
||||
|
||||
|
||||
layout = self.layout
|
||||
layout.prop(self, "selection", expand=True)
|
||||
layout.prop(self, "force")
|
||||
if self.force and self.selection == "CURRENT":
|
||||
if self.force and self.selection == 'CURRENT':
|
||||
ntree = context.space_data.edit_tree
|
||||
layout.label(text=f"Remap node {ntree.name} to others")
|
||||
elif self.force and self.selection == "SELECTED":
|
||||
layout.label(text="Selected nodes will override others")
|
||||
elif self.selection == "SELECTED":
|
||||
layout.label(text="Remap last .*** nodes")
|
||||
layout.label(text="Ex: Node.001 will override Node")
|
||||
elif self.selection in ("CURRENT", "ALL"):
|
||||
layout.label(text="Remap last .*** nodes")
|
||||
layout.label(text="Ex: Node.001 will override Node")
|
||||
layout.label(text=f'Remap node {ntree.name} to others')
|
||||
elif self.force and self.selection == 'SELECTED':
|
||||
layout.label(text='Selected nodes will override others')
|
||||
elif self.selection == 'SELECTED':
|
||||
layout.label(text='Remap last .*** nodes')
|
||||
layout.label(text='Ex: Node.001 will override Node')
|
||||
elif self.selection in ('CURRENT', 'ALL'):
|
||||
layout.label(text='Remap last .*** nodes')
|
||||
layout.label(text='Ex: Node.001 will override Node')
|
||||
|
||||
|
||||
class NODEKIT_OT_update_nodes(Operator):
|
||||
bl_idname = "node_kit.update_nodes"
|
||||
bl_label = "Update node"
|
||||
bl_idname = 'node_kit.update_nodes'
|
||||
bl_label = 'Update node'
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
selection: EnumProperty(
|
||||
items=[(s, s.title(), "") for s in ("ALL", "SELECTED", "ACTIVE")],
|
||||
default="ACTIVE",
|
||||
name="All Nodes",
|
||||
)
|
||||
selection : EnumProperty(items=[(s, s.title(), '') for s in ('ALL', 'SELECTED', 'ACTIVE')], default="ACTIVE", name='All Nodes')
|
||||
|
||||
def invoke(self, context, event):
|
||||
return context.window_manager.invoke_props_dialog(self)
|
||||
|
@ -141,75 +123,61 @@ class NODEKIT_OT_update_nodes(Operator):
|
|||
ntree_name = ntree.name
|
||||
new_ntree = None
|
||||
|
||||
if self.selection == "SELECTED":
|
||||
nodes = [
|
||||
n.node_tree
|
||||
for n in context.space_data.edit_tree.nodes
|
||||
if n.type == "GROUP" and n.select
|
||||
]
|
||||
elif self.selection == "ACTIVE":
|
||||
if self.selection == 'SELECTED':
|
||||
nodes = [ n.node_tree for n in context.space_data.edit_tree.nodes
|
||||
if n.type == "GROUP" and n.select]
|
||||
elif self.selection == 'ACTIVE':
|
||||
active_node = context.space_data.edit_tree
|
||||
nodes = [active_node]
|
||||
else:
|
||||
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:
|
||||
library_path = Path(asset_library.path)
|
||||
blend_files = [fp for fp in library_path.glob("**/*.blend") if fp.is_file()]
|
||||
|
||||
node_groups = list(
|
||||
bpy.data.node_groups
|
||||
) # Storing original node_geoup to compare with imported
|
||||
node_groups = list(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:
|
||||
print(blend_file)
|
||||
with bpy.data.libraries.load(
|
||||
str(blend_file), assets_only=True, link=link
|
||||
) as (data_from, data_to):
|
||||
with bpy.data.libraries.load(str(blend_file), assets_only=True, link=link) as (data_from, data_to):
|
||||
|
||||
print(data_from.node_groups)
|
||||
import_node_groups = [
|
||||
n for n in data_from.node_groups if n in node_names
|
||||
]
|
||||
import_node_groups = [n for n in data_from.node_groups if n in node_names]
|
||||
print("import_node_groups", import_node_groups)
|
||||
data_to.node_groups = import_node_groups
|
||||
|
||||
# print(data_from.node_groups)
|
||||
# print("data_to.node_groups", data_to.node_groups)
|
||||
node_names -= set(import_node_groups) # Store already updated nodes
|
||||
#print(data_from.node_groups)
|
||||
#print("data_to.node_groups", data_to.node_groups)
|
||||
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]
|
||||
|
||||
# break
|
||||
#break
|
||||
|
||||
# if new_ntree:
|
||||
# break
|
||||
#if new_ntree:
|
||||
# break
|
||||
new_node_groups = list(set(new_node_groups))
|
||||
# print(new_node_groups)
|
||||
#print(new_node_groups)
|
||||
|
||||
# if new_node_groups:
|
||||
for new_node_group in new_node_groups:
|
||||
new_node_group_name = new_node_group.library_weak_reference.id_name[2:]
|
||||
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,
|
||||
)
|
||||
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)
|
||||
|
||||
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
|
||||
|
||||
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)
|
||||
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.asset_clear()
|
||||
|
||||
# self.report({"INFO"}, f"Node updated from {blend_file}")
|
||||
return {"FINISHED"}
|
||||
#self.report({"INFO"}, f"Node updated from {blend_file}")
|
||||
return {'FINISHED'}
|
||||
|
||||
# else:
|
||||
# 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):
|
||||
bl_idname = "node_kit.pack_nodes"
|
||||
bl_label = "Update node"
|
||||
bl_idname = 'node_kit.pack_nodes'
|
||||
bl_label = 'Update node'
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
combine_objects(context.selected_objects)
|
||||
return {"FINISHED"}
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
class NODEKIT_OT_unpack_nodes(Operator):
|
||||
bl_idname = "node_kit.unpack_nodes"
|
||||
bl_label = "Update node"
|
||||
bl_idname = 'node_kit.unpack_nodes'
|
||||
bl_label = 'Update node'
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
extract_objects(context.active_object)
|
||||
return {"FINISHED"}
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
classes = (
|
||||
|
@ -256,7 +224,7 @@ classes = (
|
|||
NODEKIT_OT_remap_node_group_duplicates,
|
||||
NODEKIT_OT_update_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):
|
||||
layout = self.layout
|
||||
|
||||
layout.operator("node_kit.copy_node_tree", text="Copy Nodes", icon="COPYDOWN")
|
||||
layout.operator(
|
||||
"node_kit.paste_node_tree", text="Paste Nodes", icon="PASTEDOWN"
|
||||
)
|
||||
layout.operator('node_kit.copy_node_tree', text='Copy Nodes', icon='COPYDOWN')
|
||||
layout.operator('node_kit.paste_node_tree', text='Paste Nodes', icon='PASTEDOWN')
|
||||
layout.separator()
|
||||
layout.operator(
|
||||
"node_kit.remap_node_group_duplicates",
|
||||
text="Remap Node Groups Duplicates",
|
||||
icon="NODE_INSERT_OFF",
|
||||
)
|
||||
layout.operator("node_kit.update_nodes", text="Update Nodes", icon="IMPORT")
|
||||
layout.operator('node_kit.remap_node_group_duplicates', text='Remap Node Groups Duplicates', icon='NODE_INSERT_OFF')
|
||||
layout.operator('node_kit.update_nodes', text='Update Nodes', icon='IMPORT')
|
||||
layout.separator()
|
||||
layout.operator("node_kit.pack_nodes", text="Pack Nodes", icon="PACKAGE")
|
||||
layout.operator(
|
||||
"node_kit.unpack_nodes", text="UnPack Nodes", icon="UGLYPACKAGE"
|
||||
)
|
||||
layout.operator('node_kit.pack_nodes', text='Pack Nodes', icon='PACKAGE')
|
||||
layout.operator('node_kit.unpack_nodes', text='UnPack Nodes', icon='UGLYPACKAGE')
|
||||
|
||||
|
||||
classes = (NODEKIT_MT_node_kit,)
|
||||
classes = (
|
||||
NODEKIT_MT_node_kit,
|
||||
)
|
||||
|
||||
|
||||
def draw_menu(self, context):
|
||||
self.layout.menu("NODEKIT_MT_node_kit")
|
||||
self.layout.menu('NODEKIT_MT_node_kit')
|
||||
|
||||
|
||||
def register():
|
||||
|
|
Loading…
Reference in New Issue