kopia lustrzana https://github.com/carson-katri/geometry-script
Improve code generation and documentation
rodzic
58bc0b635a
commit
100368d198
|
@ -1,4 +1,6 @@
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
docs/
|
||||||
|
typeshed/
|
||||||
|
|
||||||
# Byte-compiled / optimized / DLL files
|
# Byte-compiled / optimized / DLL files
|
||||||
__pycache__/
|
__pycache__/
|
||||||
|
@ -16,6 +18,7 @@ dist/
|
||||||
downloads/
|
downloads/
|
||||||
eggs/
|
eggs/
|
||||||
.eggs/
|
.eggs/
|
||||||
|
lib/
|
||||||
lib64/
|
lib64/
|
||||||
parts/
|
parts/
|
||||||
sdist/
|
sdist/
|
||||||
|
|
|
@ -13,7 +13,8 @@
|
||||||
|
|
||||||
import bpy
|
import bpy
|
||||||
import os
|
import os
|
||||||
from .lib import *
|
|
||||||
|
from .tree import *
|
||||||
|
|
||||||
bl_info = {
|
bl_info = {
|
||||||
"name" : "Geometry Script",
|
"name" : "Geometry Script",
|
||||||
|
|
3348
documentation.html
3348
documentation.html
Plik diff jest za duży
Load Diff
|
@ -0,0 +1,13 @@
|
||||||
|
from geometry_script import *
|
||||||
|
|
||||||
|
@tree("Jellyfish")
|
||||||
|
def jellyfish(geometry: Geometry, head_radius: Float):
|
||||||
|
curve_points = geometry.curve_to_points(mode='EVALUATED').points
|
||||||
|
for i, points in curve_points:
|
||||||
|
return instance_on_points()
|
||||||
|
head = ico_sphere(radius=head_radius).transform(
|
||||||
|
translation=head_transform.position,
|
||||||
|
rotation=rotate_euler(space='LOCAL', rotation=align_euler_to_vector(vector=head_transform.tangent), rotate_by=(90, 0, 0)),
|
||||||
|
scale=(1, 1, 0.5)
|
||||||
|
)
|
||||||
|
return join_geometry(geometry=[head, geometry])
|
|
@ -1,3 +0,0 @@
|
||||||
from .tree import *
|
|
||||||
from .types import *
|
|
||||||
from .node_mapper import *
|
|
|
@ -1,208 +0,0 @@
|
||||||
import bpy
|
|
||||||
from .state import State
|
|
||||||
from .types import Type
|
|
||||||
|
|
||||||
class OutputsList(dict):
|
|
||||||
__getattr__ = dict.get
|
|
||||||
__setattr__ = dict.__setitem__
|
|
||||||
__delattr__ = dict.__delitem__
|
|
||||||
|
|
||||||
def build_node(node_type):
|
|
||||||
def build(_primary_arg=None, **kwargs):
|
|
||||||
node = State.current_node_tree.nodes.new(node_type.__name__)
|
|
||||||
if _primary_arg is not None:
|
|
||||||
State.current_node_tree.links.new(_primary_arg._socket, node.inputs[0])
|
|
||||||
for prop in node.bl_rna.properties:
|
|
||||||
argname = prop.name.lower().replace(' ', '_')
|
|
||||||
if argname in kwargs:
|
|
||||||
setattr(node, prop.identifier, kwargs[argname])
|
|
||||||
for node_input in (node.inputs[1:] if _primary_arg is not None else node.inputs):
|
|
||||||
argname = node_input.name.lower().replace(' ', '_')
|
|
||||||
if argname in kwargs:
|
|
||||||
if node_input.is_multi_input and hasattr(kwargs[argname], '__iter__') and len(kwargs[argname]) > 0 and issubclass(type(next(iter(kwargs[argname]))), Type):
|
|
||||||
for x in kwargs[argname]:
|
|
||||||
State.current_node_tree.links.new(x._socket, node_input)
|
|
||||||
elif issubclass(type(kwargs[argname]), Type):
|
|
||||||
State.current_node_tree.links.new(kwargs[argname]._socket, node_input)
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
node_input.default_value = kwargs[argname]
|
|
||||||
except:
|
|
||||||
constant = Type(value=kwargs[argname])
|
|
||||||
State.current_node_tree.links.new(constant._socket, node_input)
|
|
||||||
outputs = {}
|
|
||||||
for node_output in node.outputs:
|
|
||||||
outputs[node_output.name.lower().replace(' ', '_')] = Type(node_output)
|
|
||||||
if len(outputs) == 1:
|
|
||||||
return list(outputs.values())[0]
|
|
||||||
else:
|
|
||||||
return OutputsList(outputs)
|
|
||||||
return build
|
|
||||||
|
|
||||||
documentation = {}
|
|
||||||
registered_nodes = set()
|
|
||||||
def register_node(node_type, category_path=None):
|
|
||||||
if node_type in registered_nodes:
|
|
||||||
return
|
|
||||||
snake_case_name = node_type.bl_rna.name.lower().replace(' ', '_')
|
|
||||||
globals()[snake_case_name] = build_node(node_type)
|
|
||||||
globals()[snake_case_name].bl_category_path = category_path
|
|
||||||
globals()[snake_case_name].bl_node_type = node_type
|
|
||||||
documentation[snake_case_name] = globals()[snake_case_name]
|
|
||||||
def build_node_method(node_type):
|
|
||||||
def build(self, *args, **kwargs):
|
|
||||||
return build_node(node_type)(self, *args, **kwargs)
|
|
||||||
return build
|
|
||||||
setattr(Type, snake_case_name, build_node_method(node_type))
|
|
||||||
registered_nodes.add(node_type)
|
|
||||||
for category_name in list(filter(lambda x: x.startswith('NODE_MT_category_GEO_'), dir(bpy.types))):
|
|
||||||
category = getattr(bpy.types, category_name)
|
|
||||||
category_path = category.category.name.lower().replace(' ', '_')
|
|
||||||
for node in category.category.items(None):
|
|
||||||
node_type = getattr(bpy.types, node.nodetype)
|
|
||||||
register_node(node_type, category_path)
|
|
||||||
for node_type in bpy.types.GeometryNode.__subclasses__():
|
|
||||||
register_node(node_type)
|
|
||||||
|
|
||||||
def create_documentation():
|
|
||||||
temp_node_group = bpy.data.node_groups.new('temp_node_group', 'GeometryNodeTree')
|
|
||||||
color_mappings = {
|
|
||||||
'INT': '#598C5C',
|
|
||||||
'FLOAT': '#A1A1A1',
|
|
||||||
'BOOLEAN': '#CCA6D6',
|
|
||||||
'GEOMETRY': '#00D6A3',
|
|
||||||
'VALUE': '#A1A1A1',
|
|
||||||
'VECTOR': '#6363C7',
|
|
||||||
'MATERIAL': '#EB7582',
|
|
||||||
'TEXTURE': '#9E4FA3',
|
|
||||||
'COLLECTION': '#F5F5F5',
|
|
||||||
'OBJECT': '#ED9E5C',
|
|
||||||
'STRING': '#70B2FF',
|
|
||||||
'RGBA': '#C7C729',
|
|
||||||
}
|
|
||||||
default_color = '#A1A1A1'
|
|
||||||
docstrings = []
|
|
||||||
symbols = []
|
|
||||||
for func in sorted(documentation.keys()):
|
|
||||||
method = documentation[func]
|
|
||||||
link = f"https://docs.blender.org/manual/en/latest/modeling/geometry_nodes/{method.bl_category_path}/{func}.html"
|
|
||||||
image = f"https://docs.blender.org/manual/en/latest/_images/node-types_{method.bl_node_type.__name__}"
|
|
||||||
node_instance = temp_node_group.nodes.new(method.bl_node_type.__name__)
|
|
||||||
props_inputs = {}
|
|
||||||
symbol_inputs = {}
|
|
||||||
parent_props = [prop.identifier for base in method.bl_node_type.__bases__ for prop in base.bl_rna.properties]
|
|
||||||
for prop in method.bl_node_type.bl_rna.properties:
|
|
||||||
if not prop.identifier in parent_props:
|
|
||||||
if prop.type == 'ENUM':
|
|
||||||
enum_items = 'Literal[' + ', '.join(map(lambda i: f"'{i.identifier}'", prop.enum_items)) + ']'
|
|
||||||
props_inputs[prop.identifier] = f"<span style=\"color: {color_mappings['STRING']};\">{enum_items}</span>"
|
|
||||||
symbol_inputs[prop.identifier] = enum_items
|
|
||||||
else:
|
|
||||||
props_inputs[prop.identifier] = f"<span style=\"color: {color_mappings.get(prop.type, default_color)};\">{prop.type.title()}</span>"
|
|
||||||
symbol_inputs[prop.identifier] = prop.type.title()
|
|
||||||
primary_arg = None
|
|
||||||
for node_input in node_instance.inputs:
|
|
||||||
name = node_input.name.lower().replace(' ', '_')
|
|
||||||
typename = type(node_input).__name__.replace('NodeSocket', '')
|
|
||||||
if node_input.is_multi_input:
|
|
||||||
typename = f"List[{typename}]"
|
|
||||||
type_str = f"<span style=\"color: {color_mappings.get(node_input.type, default_color)};\">{typename}</span>"
|
|
||||||
if name in props_inputs:
|
|
||||||
props_inputs[name] = props_inputs[name] + f' | {type_str}'
|
|
||||||
symbol_inputs[name] = symbol_inputs[name] + f' | {typename}'
|
|
||||||
else:
|
|
||||||
props_inputs[name] = type_str
|
|
||||||
symbol_inputs[name] = typename
|
|
||||||
if primary_arg is None:
|
|
||||||
primary_arg = (name, props_inputs[name])
|
|
||||||
arg_docs = []
|
|
||||||
symbol_args = []
|
|
||||||
for name, value in props_inputs.items():
|
|
||||||
arg_docs.append(f"{name}: {value}")
|
|
||||||
symbol_args.append(f"{name}: {symbol_inputs[name]}")
|
|
||||||
outputs = {}
|
|
||||||
symbol_outputs = {}
|
|
||||||
for node_output in node_instance.outputs:
|
|
||||||
output_name = node_output.name.lower().replace(' ', '_')
|
|
||||||
output_type = type(node_output).__name__.replace('NodeSocket', '')
|
|
||||||
outputs[output_name] = f"<span style=\"color: {color_mappings.get(node_output.type, default_color)};\">{output_type}</span>"
|
|
||||||
symbol_outputs[output_name] = output_type
|
|
||||||
output_docs = []
|
|
||||||
output_symbols = []
|
|
||||||
for name, value in outputs.items():
|
|
||||||
output_docs.append(f"{name}: {value}")
|
|
||||||
output_symbols.append(f"{name}: {symbol_outputs[name]}")
|
|
||||||
outputs_doc = f"{{ {', '.join(output_docs)} }}" if len(output_docs) > 1 else ''.join(output_docs)
|
|
||||||
arg_separator = ',\n '
|
|
||||||
def primary_arg_docs():
|
|
||||||
return f"""
|
|
||||||
<h4>Chain Syntax</h4>
|
|
||||||
<pre><code>{primary_arg[0]}: {primary_arg[1]} = ...
|
|
||||||
{primary_arg[0]}.{func}(...)</code></pre>
|
|
||||||
"""
|
|
||||||
docstrings.append(f"""
|
|
||||||
<details style="margin: 10px 0;">
|
|
||||||
<summary><code>{func}</code> - <a href="{link}">{method.bl_node_type.bl_rna.name}</a></summary>
|
|
||||||
<div style="margin-top: 5px;">
|
|
||||||
<img src="{image}.webp" onerror="if (this.src != '{image}.png') this.src = '{image}.png'" />
|
|
||||||
<h4>Signature</h4>
|
|
||||||
<pre><code>{func}(
|
|
||||||
{arg_separator.join(arg_docs)}
|
|
||||||
)</code></pre>
|
|
||||||
<h4>Result</h4>
|
|
||||||
<pre><code>{outputs_doc}</code></pre>
|
|
||||||
{primary_arg_docs() if primary_arg is not None else ""}
|
|
||||||
</div>
|
|
||||||
</details>
|
|
||||||
""")
|
|
||||||
output_symbol_separator = '\n '
|
|
||||||
symbol_return_type = f"_{func}_result"
|
|
||||||
if len(output_symbols) > 1:
|
|
||||||
symbols.append(f"""class {symbol_return_type}:
|
|
||||||
{output_symbol_separator.join(output_symbols)}""")
|
|
||||||
symbols.append(f"""def {func}({', '.join(symbol_args)}) -> {list(symbol_outputs.values())[0] if len(output_symbols) == 1 else symbol_return_type}: pass""")
|
|
||||||
bpy.data.node_groups.remove(temp_node_group)
|
|
||||||
html = f"""
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<style>
|
|
||||||
html {{
|
|
||||||
background-color: #1D1D1D;
|
|
||||||
color: #FFFFFF;
|
|
||||||
}}
|
|
||||||
a {{
|
|
||||||
color: #4772B3;
|
|
||||||
}}
|
|
||||||
body {{
|
|
||||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
|
|
||||||
max-width: 60em;
|
|
||||||
margin: 0 auto;
|
|
||||||
}}
|
|
||||||
pre {{
|
|
||||||
overflow: scroll;
|
|
||||||
padding: 16px;
|
|
||||||
background-color: #303030;
|
|
||||||
border-radius: 5px;
|
|
||||||
}}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<h1>Geometry Script</h1>
|
|
||||||
<h3>Nodes</h3>
|
|
||||||
{''.join(docstrings)}
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
"""
|
|
||||||
with open('documentation.html', 'w') as f:
|
|
||||||
f.write(html)
|
|
||||||
with open('geometry_script.py', 'w') as f:
|
|
||||||
newline = '\n'
|
|
||||||
def type_symbol(t):
|
|
||||||
return f"class {t.__name__}: pass"
|
|
||||||
f.write(f"""from typing import *
|
|
||||||
{newline.join(map(type_symbol, Type.__subclasses__()))}
|
|
||||||
{newline.join(symbols)}""")
|
|
||||||
|
|
||||||
def create_docs():
|
|
||||||
create_documentation()
|
|
||||||
bpy.app.timers.register(create_docs)
|
|
53
lib/types.py
53
lib/types.py
|
@ -1,53 +0,0 @@
|
||||||
import bpy
|
|
||||||
from .state import State
|
|
||||||
|
|
||||||
# The base class all exposed socket types conform to.
|
|
||||||
class Type:
|
|
||||||
socket_type: str
|
|
||||||
|
|
||||||
def __init__(self, socket: bpy.types.NodeSocket = None, value = None):
|
|
||||||
if value is not None:
|
|
||||||
input_nodes = {
|
|
||||||
int: ('FunctionNodeInputInt', 'integer'),
|
|
||||||
bool: ('FunctionNodeInputBool', 'boolean'),
|
|
||||||
str: ('FunctionNodeInputString', 'string'),
|
|
||||||
tuple: ('FunctionNodeInputVector', 'vector'),
|
|
||||||
float: ('ShaderNodeValue', None),
|
|
||||||
}
|
|
||||||
if not type(value) in input_nodes:
|
|
||||||
raise Exception(f"'{value}' cannot be expressed as a node.")
|
|
||||||
input_node_info = input_nodes[type(value)]
|
|
||||||
value_node = State.current_node_tree.nodes.new(input_node_info[0])
|
|
||||||
if input_node_info[1] is None:
|
|
||||||
value_node.outputs[0].default_value = value
|
|
||||||
else:
|
|
||||||
setattr(value_node, input_node_info[1], value)
|
|
||||||
socket = value_node.outputs[0]
|
|
||||||
self._socket = socket
|
|
||||||
self.socket_type = type(socket).__name__
|
|
||||||
|
|
||||||
def _math(self, other, operation):
|
|
||||||
math_node = State.current_node_tree.nodes.new('ShaderNodeVectorMath' if self._socket.type else 'ShaderNodeMath')
|
|
||||||
math_node.operation = operation
|
|
||||||
State.current_node_tree.links.new(self._socket, math_node.inputs[0])
|
|
||||||
if issubclass(type(other), Type):
|
|
||||||
State.current_node_tree.links.new(other._socket, math_node.inputs[1])
|
|
||||||
else:
|
|
||||||
math_node.inputs[1].default_value = other
|
|
||||||
return Type(math_node.outputs[0])
|
|
||||||
|
|
||||||
def __add__(self, other):
|
|
||||||
return self._math(other, 'ADD')
|
|
||||||
|
|
||||||
def __sub__(self, other):
|
|
||||||
return self._math(other, 'SUBTRACT')
|
|
||||||
|
|
||||||
def __mul__(self, other):
|
|
||||||
return self._math(other, 'SUBTRACT')
|
|
||||||
|
|
||||||
def __truediv__(self, other):
|
|
||||||
return self._math(other, 'DIVIDE')
|
|
||||||
|
|
||||||
for standard_socket in bpy.types.NodeSocketStandard.__subclasses__():
|
|
||||||
name = standard_socket.__name__.replace('NodeSocket', '')
|
|
||||||
globals()[name] = type(name, (Type,), { 'socket_type': standard_socket.__name__ })
|
|
|
@ -0,0 +1,222 @@
|
||||||
|
import bpy
|
||||||
|
from bpy.types import GeometryNodeCurveToMesh
|
||||||
|
from .state import State
|
||||||
|
from .types import *
|
||||||
|
|
||||||
|
class OutputsList(dict):
|
||||||
|
__getattr__ = dict.get
|
||||||
|
__setattr__ = dict.__setitem__
|
||||||
|
__delattr__ = dict.__delitem__
|
||||||
|
|
||||||
|
def build_node(node_type):
|
||||||
|
def build(_primary_arg=None, **kwargs):
|
||||||
|
node = State.current_node_tree.nodes.new(node_type.__name__)
|
||||||
|
if _primary_arg is not None:
|
||||||
|
State.current_node_tree.links.new(_primary_arg._socket, node.inputs[0])
|
||||||
|
for prop in node.bl_rna.properties:
|
||||||
|
argname = prop.name.lower().replace(' ', '_')
|
||||||
|
if argname in kwargs:
|
||||||
|
setattr(node, prop.identifier, kwargs[argname])
|
||||||
|
for node_input in (node.inputs[1:] if _primary_arg is not None else node.inputs):
|
||||||
|
argname = node_input.name.lower().replace(' ', '_')
|
||||||
|
if argname in kwargs:
|
||||||
|
if node_input.is_multi_input and hasattr(kwargs[argname], '__iter__') and len(kwargs[argname]) > 0 and issubclass(type(next(iter(kwargs[argname]))), Type):
|
||||||
|
for x in kwargs[argname]:
|
||||||
|
State.current_node_tree.links.new(x._socket, node_input)
|
||||||
|
elif issubclass(type(kwargs[argname]), Type):
|
||||||
|
State.current_node_tree.links.new(kwargs[argname]._socket, node_input)
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
node_input.default_value = kwargs[argname]
|
||||||
|
except:
|
||||||
|
constant = Type(value=kwargs[argname])
|
||||||
|
State.current_node_tree.links.new(constant._socket, node_input)
|
||||||
|
outputs = {}
|
||||||
|
for node_output in node.outputs:
|
||||||
|
outputs[node_output.name.lower().replace(' ', '_')] = Type(node_output)
|
||||||
|
if len(outputs) == 1:
|
||||||
|
return list(outputs.values())[0]
|
||||||
|
else:
|
||||||
|
return OutputsList(outputs)
|
||||||
|
return build
|
||||||
|
|
||||||
|
documentation = {}
|
||||||
|
registered_nodes = set()
|
||||||
|
def register_node(node_type, category_path=None):
|
||||||
|
if node_type in registered_nodes:
|
||||||
|
return
|
||||||
|
snake_case_name = node_type.bl_rna.name.lower().replace(' ', '_')
|
||||||
|
globals()[snake_case_name] = build_node(node_type)
|
||||||
|
globals()[snake_case_name].bl_category_path = category_path
|
||||||
|
globals()[snake_case_name].bl_node_type = node_type
|
||||||
|
documentation[snake_case_name] = globals()[snake_case_name]
|
||||||
|
def build_node_method(node_type):
|
||||||
|
def build(self, *args, **kwargs):
|
||||||
|
return build_node(node_type)(self, *args, **kwargs)
|
||||||
|
return build
|
||||||
|
setattr(Type, snake_case_name, build_node_method(node_type))
|
||||||
|
registered_nodes.add(node_type)
|
||||||
|
for category_name in list(filter(lambda x: x.startswith('NODE_MT_category_GEO_'), dir(bpy.types))):
|
||||||
|
category = getattr(bpy.types, category_name)
|
||||||
|
category_path = category.category.name.lower().replace(' ', '_')
|
||||||
|
for node in category.category.items(None):
|
||||||
|
node_type = getattr(bpy.types, node.nodetype)
|
||||||
|
register_node(node_type, category_path)
|
||||||
|
for node_type_name in list(filter(lambda x: 'GeometryNode' in x, dir(bpy.types))):
|
||||||
|
node_type = getattr(bpy.types, node_type_name)
|
||||||
|
if issubclass(node_type, bpy.types.GeometryNode):
|
||||||
|
register_node(node_type)
|
||||||
|
|
||||||
|
def create_documentation():
|
||||||
|
temp_node_group = bpy.data.node_groups.new('temp_node_group', 'GeometryNodeTree')
|
||||||
|
color_mappings = {
|
||||||
|
'INT': '#598C5C',
|
||||||
|
'FLOAT': '#A1A1A1',
|
||||||
|
'BOOLEAN': '#CCA6D6',
|
||||||
|
'GEOMETRY': '#00D6A3',
|
||||||
|
'VALUE': '#A1A1A1',
|
||||||
|
'VECTOR': '#6363C7',
|
||||||
|
'MATERIAL': '#EB7582',
|
||||||
|
'TEXTURE': '#9E4FA3',
|
||||||
|
'COLLECTION': '#F5F5F5',
|
||||||
|
'OBJECT': '#ED9E5C',
|
||||||
|
'STRING': '#70B2FF',
|
||||||
|
'RGBA': '#C7C729',
|
||||||
|
}
|
||||||
|
default_color = '#A1A1A1'
|
||||||
|
docstrings = []
|
||||||
|
symbols = []
|
||||||
|
for func in sorted(documentation.keys()):
|
||||||
|
try:
|
||||||
|
method = documentation[func]
|
||||||
|
link = f"https://docs.blender.org/manual/en/latest/modeling/geometry_nodes/{method.bl_category_path}/{func}.html"
|
||||||
|
image = f"https://docs.blender.org/manual/en/latest/_images/node-types_{method.bl_node_type.__name__}"
|
||||||
|
node_instance = temp_node_group.nodes.new(method.bl_node_type.__name__)
|
||||||
|
props_inputs = {}
|
||||||
|
symbol_inputs = {}
|
||||||
|
parent_props = [prop.identifier for base in method.bl_node_type.__bases__ for prop in base.bl_rna.properties]
|
||||||
|
for prop in method.bl_node_type.bl_rna.properties:
|
||||||
|
if not prop.identifier in parent_props:
|
||||||
|
if prop.type == 'ENUM':
|
||||||
|
enum_items = 'Literal[' + ', '.join(map(lambda i: f"'{i.identifier}'", prop.enum_items)) + ']'
|
||||||
|
props_inputs[prop.identifier] = f"<span style=\"color: {color_mappings['STRING']};\">{enum_items}</span>"
|
||||||
|
symbol_inputs[prop.identifier] = enum_items
|
||||||
|
else:
|
||||||
|
props_inputs[prop.identifier] = f"<span style=\"color: {color_mappings.get(prop.type, default_color)};\">{prop.type.title()}</span>"
|
||||||
|
symbol_inputs[prop.identifier] = prop.type.title()
|
||||||
|
primary_arg = None
|
||||||
|
for node_input in node_instance.inputs:
|
||||||
|
name = node_input.name.lower().replace(' ', '_')
|
||||||
|
typename = type(node_input).__name__.replace('NodeSocket', '')
|
||||||
|
if node_input.is_multi_input:
|
||||||
|
typename = f"List[{typename}]"
|
||||||
|
type_str = f"<span style=\"color: {color_mappings.get(node_input.type, default_color)};\">{typename}</span>"
|
||||||
|
if name in props_inputs:
|
||||||
|
props_inputs[name] = props_inputs[name] + f' | {type_str}'
|
||||||
|
symbol_inputs[name] = symbol_inputs[name] + f' | {typename}'
|
||||||
|
else:
|
||||||
|
props_inputs[name] = type_str
|
||||||
|
symbol_inputs[name] = typename
|
||||||
|
if primary_arg is None:
|
||||||
|
primary_arg = (name, props_inputs[name])
|
||||||
|
arg_docs = []
|
||||||
|
symbol_args = []
|
||||||
|
for name, value in props_inputs.items():
|
||||||
|
arg_docs.append(f"{name}: {value}")
|
||||||
|
symbol_args.append(f"{name}: {symbol_inputs[name]} | None = None")
|
||||||
|
outputs = {}
|
||||||
|
symbol_outputs = {}
|
||||||
|
for node_output in node_instance.outputs:
|
||||||
|
output_name = node_output.name.lower().replace(' ', '_')
|
||||||
|
output_type = type(node_output).__name__.replace('NodeSocket', '')
|
||||||
|
outputs[output_name] = f"<span style=\"color: {color_mappings.get(node_output.type, default_color)};\">{output_type}</span>"
|
||||||
|
symbol_outputs[output_name] = output_type
|
||||||
|
output_docs = []
|
||||||
|
output_symbols = []
|
||||||
|
for name, value in outputs.items():
|
||||||
|
output_docs.append(f"{name}: {value}")
|
||||||
|
output_symbols.append(f"{name}: {symbol_outputs[name]}")
|
||||||
|
outputs_doc = f"{{ {', '.join(output_docs)} }}" if len(output_docs) > 1 else ''.join(output_docs)
|
||||||
|
arg_separator = ',\n '
|
||||||
|
def primary_arg_docs():
|
||||||
|
return f"""
|
||||||
|
<h4>Chain Syntax</h4>
|
||||||
|
<pre><code>{primary_arg[0]}: {primary_arg[1]} = ...
|
||||||
|
{primary_arg[0]}.{func}(...)</code></pre>
|
||||||
|
"""
|
||||||
|
docstrings.append(f"""
|
||||||
|
<details style="margin: 10px 0;">
|
||||||
|
<summary><code>{func}</code> - <a href="{link}">{method.bl_node_type.bl_rna.name}</a></summary>
|
||||||
|
<div style="margin-top: 5px;">
|
||||||
|
<img src="{image}.webp" onerror="if (this.src != '{image}.png') this.src = '{image}.png'" />
|
||||||
|
<h4>Signature</h4>
|
||||||
|
<pre><code>{func}(
|
||||||
|
{arg_separator.join(arg_docs)}
|
||||||
|
)</code></pre>
|
||||||
|
<h4>Result</h4>
|
||||||
|
<pre><code>{outputs_doc}</code></pre>
|
||||||
|
{primary_arg_docs() if primary_arg is not None else ""}
|
||||||
|
</div>
|
||||||
|
</details>
|
||||||
|
""")
|
||||||
|
output_symbol_separator = '\n '
|
||||||
|
symbol_return_type = f"_{func}_result"
|
||||||
|
if len(output_symbols) > 1:
|
||||||
|
symbols.append(f"""class {symbol_return_type}:
|
||||||
|
{output_symbol_separator.join(output_symbols)}""")
|
||||||
|
return_type_hint = list(symbol_outputs.values())[0] if len(output_symbols) == 1 else symbol_return_type
|
||||||
|
symbols.append(f"""def {func}({', '.join(symbol_args)}) -> {return_type_hint}: \"\"\"![]({image}.webp)\"\"\"""")
|
||||||
|
except:
|
||||||
|
continue
|
||||||
|
bpy.data.node_groups.remove(temp_node_group)
|
||||||
|
html = f"""
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<style>
|
||||||
|
html {{
|
||||||
|
background-color: #1D1D1D;
|
||||||
|
color: #FFFFFF;
|
||||||
|
}}
|
||||||
|
a {{
|
||||||
|
color: #4772B3;
|
||||||
|
}}
|
||||||
|
body {{
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
|
||||||
|
max-width: 60em;
|
||||||
|
margin: 0 auto;
|
||||||
|
}}
|
||||||
|
pre {{
|
||||||
|
overflow: scroll;
|
||||||
|
padding: 16px;
|
||||||
|
background-color: #303030;
|
||||||
|
border-radius: 5px;
|
||||||
|
}}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Geometry Script</h1>
|
||||||
|
<h3>Nodes</h3>
|
||||||
|
{''.join(docstrings)}
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
"""
|
||||||
|
with open('docs/documentation.html', 'w') as f:
|
||||||
|
f.write(html)
|
||||||
|
with open('typeshed/geometry_script.pyi', 'w') as f:
|
||||||
|
newline = '\n'
|
||||||
|
def type_symbol(t):
|
||||||
|
return f"class {t.__name__}(Type): pass"
|
||||||
|
f.write(f"""from typing import *
|
||||||
|
def tree(builder):
|
||||||
|
\"\"\"
|
||||||
|
Marks a function as a node tree.
|
||||||
|
\"\"\"
|
||||||
|
pass
|
||||||
|
class Type:
|
||||||
|
{(newline + ' ').join(filter(lambda x: x.startswith('def'), symbols))}
|
||||||
|
{newline.join(map(type_symbol, Type.__subclasses__()))}
|
||||||
|
{newline.join(symbols)}""")
|
||||||
|
|
||||||
|
def create_docs():
|
||||||
|
create_documentation()
|
||||||
|
bpy.app.timers.register(create_docs)
|
|
@ -6,14 +6,14 @@ try:
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
from .state import State
|
from .state import State
|
||||||
from .types import Type
|
from .types import *
|
||||||
from .node_mapper import *
|
from .node_mapper import *
|
||||||
|
|
||||||
def _as_iterable(input):
|
def _as_iterable(x):
|
||||||
try:
|
try:
|
||||||
return iter(input)
|
return iter(x)
|
||||||
except TypeError:
|
except TypeError:
|
||||||
return {input}
|
return [x,]
|
||||||
|
|
||||||
def tree(name):
|
def tree(name):
|
||||||
tree_name = name
|
tree_name = name
|
|
@ -0,0 +1,115 @@
|
||||||
|
import bpy
|
||||||
|
from bpy.types import NodeSocketStandard
|
||||||
|
import nodeitems_utils
|
||||||
|
from .state import State
|
||||||
|
|
||||||
|
# The base class all exposed socket types conform to.
|
||||||
|
class Type:
|
||||||
|
socket_type: str
|
||||||
|
|
||||||
|
def __init__(self, socket: bpy.types.NodeSocket = None, value = None):
|
||||||
|
if value is not None:
|
||||||
|
input_nodes = {
|
||||||
|
int: ('FunctionNodeInputInt', 'integer'),
|
||||||
|
bool: ('FunctionNodeInputBool', 'boolean'),
|
||||||
|
str: ('FunctionNodeInputString', 'string'),
|
||||||
|
tuple: ('FunctionNodeInputVector', 'vector'),
|
||||||
|
float: ('ShaderNodeValue', None),
|
||||||
|
}
|
||||||
|
if not type(value) in input_nodes:
|
||||||
|
raise Exception(f"'{value}' cannot be expressed as a node.")
|
||||||
|
input_node_info = input_nodes[type(value)]
|
||||||
|
value_node = State.current_node_tree.nodes.new(input_node_info[0])
|
||||||
|
if input_node_info[1] is None:
|
||||||
|
value_node.outputs[0].default_value = value
|
||||||
|
else:
|
||||||
|
setattr(value_node, input_node_info[1], value)
|
||||||
|
socket = value_node.outputs[0]
|
||||||
|
self._socket = socket
|
||||||
|
self.socket_type = type(socket).__name__
|
||||||
|
|
||||||
|
def _math(self, other, operation):
|
||||||
|
math_node = State.current_node_tree.nodes.new('ShaderNodeVectorMath' if self._socket.type == 'VECTOR' else 'ShaderNodeMath')
|
||||||
|
math_node.operation = operation
|
||||||
|
State.current_node_tree.links.new(self._socket, math_node.inputs[0])
|
||||||
|
if issubclass(type(other), Type):
|
||||||
|
State.current_node_tree.links.new(other._socket, math_node.inputs[1])
|
||||||
|
else:
|
||||||
|
math_node.inputs[1].default_value = other
|
||||||
|
return Type(math_node.outputs[0])
|
||||||
|
|
||||||
|
def __add__(self, other):
|
||||||
|
return self._math(other, 'ADD')
|
||||||
|
|
||||||
|
def __sub__(self, other):
|
||||||
|
return self._math(other, 'SUBTRACT')
|
||||||
|
|
||||||
|
def __mul__(self, other):
|
||||||
|
return self._math(other, 'MULTIPLY')
|
||||||
|
|
||||||
|
def __truediv__(self, other):
|
||||||
|
return self._math(other, 'DIVIDE')
|
||||||
|
|
||||||
|
def __mod__(self, other):
|
||||||
|
return self._math(other, 'MODULO')
|
||||||
|
|
||||||
|
def _compare(self, other, operation):
|
||||||
|
compare_node = State.current_node_tree.nodes.new('FunctionNodeCompare')
|
||||||
|
compare_node.data_type = 'FLOAT' if self._socket.type == 'VALUE' else self._socket.type
|
||||||
|
compare_node.operation = operation
|
||||||
|
State.current_node_tree.links.new(self._socket, compare_node.inputs[0])
|
||||||
|
if issubclass(type(other), Type):
|
||||||
|
State.current_node_tree.links.new(other._socket, compare_node.inputs[1])
|
||||||
|
else:
|
||||||
|
compare_node.inputs[1].default_value = other
|
||||||
|
return Type(compare_node.outputs[0])
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
return self._compare(other, 'EQUAL')
|
||||||
|
|
||||||
|
def __ne__(self, other):
|
||||||
|
return self._compare(other, 'NOT_EQUAL')
|
||||||
|
|
||||||
|
def __lt__(self, other):
|
||||||
|
return self._compare(other, 'LESS_THAN')
|
||||||
|
|
||||||
|
def __le__(self, other):
|
||||||
|
return self._compare(other, 'LESS_EQUAL')
|
||||||
|
|
||||||
|
def __gt__(self, other):
|
||||||
|
return self._compare(other, 'GREATER_THAN')
|
||||||
|
|
||||||
|
def __ge__(self, other):
|
||||||
|
return self._compare(other, 'GREATER_EQUAL')
|
||||||
|
|
||||||
|
for standard_socket in list(filter(lambda x: 'NodeSocket' in x, dir(bpy.types))):
|
||||||
|
name = standard_socket.replace('NodeSocket', '')
|
||||||
|
if len(name) < 1:
|
||||||
|
continue
|
||||||
|
globals()[name] = type(name, (Type,), { 'socket_type': standard_socket, '__module__': Type.__module__ })
|
||||||
|
if name == 'Vector':
|
||||||
|
def get_component(component):
|
||||||
|
@property
|
||||||
|
def get(self):
|
||||||
|
separate_node = State.current_node_tree.nodes.new('ShaderNodeSeparateXYZ')
|
||||||
|
State.current_node_tree.links.new(self._socket, separate_node.inputs[0])
|
||||||
|
return Type(separate_node.outputs[component])
|
||||||
|
return get
|
||||||
|
globals()[name].x = get_component(0)
|
||||||
|
globals()[name].y = get_component(1)
|
||||||
|
globals()[name].z = get_component(2)
|
||||||
|
if name == 'Int':
|
||||||
|
class IntIterator:
|
||||||
|
def __init__(self, integer):
|
||||||
|
self.integer = integer
|
||||||
|
self.points = State.current_node_tree.nodes.new('GeometryNodePoints')
|
||||||
|
State.current_node_tree.links.new(self.integer._socket, self.points.inputs[0])
|
||||||
|
self.index = State.current_node_tree.nodes.new('GeometryNodeInputIndex')
|
||||||
|
self._did_iterate = False
|
||||||
|
def __next__(self):
|
||||||
|
if not self._did_iterate:
|
||||||
|
self._did_iterate = True
|
||||||
|
return Type(self.index.outputs[0]), Type(self.points.outputs[0])
|
||||||
|
else:
|
||||||
|
raise StopIteration()
|
||||||
|
globals()[name].__iter__ = lambda self: IntIterator(self)
|
Ładowanie…
Reference in New Issue