nodebpy 0.9.1__tar.gz → 0.10.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {nodebpy-0.9.1 → nodebpy-0.10.1}/PKG-INFO +7 -11
- {nodebpy-0.9.1 → nodebpy-0.10.1}/README.md +6 -10
- {nodebpy-0.9.1 → nodebpy-0.10.1}/pyproject.toml +1 -1
- {nodebpy-0.9.1 → nodebpy-0.10.1}/src/nodebpy/__init__.py +2 -4
- nodebpy-0.10.1/src/nodebpy/builder/__init__.py +142 -0
- nodebpy-0.10.1/src/nodebpy/builder/_registry.py +19 -0
- nodebpy-0.10.1/src/nodebpy/builder/_utils.py +81 -0
- nodebpy-0.10.1/src/nodebpy/builder/accessor.py +188 -0
- nodebpy-0.10.1/src/nodebpy/builder/interface.py +571 -0
- nodebpy-0.10.1/src/nodebpy/builder/mixins.py +305 -0
- nodebpy-0.10.1/src/nodebpy/builder/node.py +233 -0
- nodebpy-0.10.1/src/nodebpy/builder/socket.py +508 -0
- nodebpy-0.10.1/src/nodebpy/builder/tree.py +851 -0
- {nodebpy-0.9.1 → nodebpy-0.10.1}/src/nodebpy/nodes/compositor/__init__.py +2 -0
- nodebpy-0.10.1/src/nodebpy/nodes/compositor/color.py +1396 -0
- nodebpy-0.10.1/src/nodebpy/nodes/compositor/converter.py +1451 -0
- nodebpy-0.10.1/src/nodebpy/nodes/compositor/distort.py +1287 -0
- nodebpy-0.10.1/src/nodebpy/nodes/compositor/filter.py +1966 -0
- {nodebpy-0.9.1 → nodebpy-0.10.1}/src/nodebpy/nodes/compositor/group.py +15 -1
- nodebpy-0.10.1/src/nodebpy/nodes/compositor/input.py +585 -0
- {nodebpy-0.9.1 → nodebpy-0.10.1}/src/nodebpy/nodes/compositor/interface.py +59 -33
- {nodebpy-0.9.1 → nodebpy-0.10.1}/src/nodebpy/nodes/compositor/manual.py +15 -0
- nodebpy-0.10.1/src/nodebpy/nodes/compositor/matte.py +1176 -0
- {nodebpy-0.9.1 → nodebpy-0.10.1}/src/nodebpy/nodes/compositor/output.py +41 -8
- nodebpy-0.10.1/src/nodebpy/nodes/compositor/vector.py +56 -0
- {nodebpy-0.9.1 → nodebpy-0.10.1}/src/nodebpy/nodes/geometry/__init__.py +3 -1
- {nodebpy-0.9.1 → nodebpy-0.10.1}/src/nodebpy/nodes/geometry/attribute.py +322 -232
- nodebpy-0.10.1/src/nodebpy/nodes/geometry/color.py +127 -0
- nodebpy-0.10.1/src/nodebpy/nodes/geometry/converter.py +5076 -0
- nodebpy-0.10.1/src/nodebpy/nodes/geometry/geometry.py +9316 -0
- nodebpy-0.10.1/src/nodebpy/nodes/geometry/grid.py +2504 -0
- nodebpy-0.10.1/src/nodebpy/nodes/geometry/group.py +35 -0
- nodebpy-0.10.1/src/nodebpy/nodes/geometry/groups.py +104 -0
- nodebpy-0.10.1/src/nodebpy/nodes/geometry/input.py +3567 -0
- {nodebpy-0.9.1 → nodebpy-0.10.1}/src/nodebpy/nodes/geometry/interface.py +307 -146
- {nodebpy-0.9.1 → nodebpy-0.10.1}/src/nodebpy/nodes/geometry/manual.py +780 -703
- {nodebpy-0.9.1 → nodebpy-0.10.1}/src/nodebpy/nodes/geometry/output.py +22 -9
- nodebpy-0.10.1/src/nodebpy/nodes/geometry/texture.py +1425 -0
- nodebpy-0.10.1/src/nodebpy/nodes/geometry/vector.py +716 -0
- {nodebpy-0.9.1 → nodebpy-0.10.1}/src/nodebpy/nodes/geometry/zone.py +85 -78
- {nodebpy-0.9.1 → nodebpy-0.10.1}/src/nodebpy/nodes/shader/__init__.py +9 -3
- nodebpy-0.10.1/src/nodebpy/nodes/shader/color.py +279 -0
- nodebpy-0.10.1/src/nodebpy/nodes/shader/converter.py +686 -0
- nodebpy-0.10.1/src/nodebpy/nodes/shader/grid.py +534 -0
- nodebpy-0.10.1/src/nodebpy/nodes/shader/group.py +35 -0
- nodebpy-0.10.1/src/nodebpy/nodes/shader/input.py +1077 -0
- {nodebpy-0.9.1 → nodebpy-0.10.1}/src/nodebpy/nodes/shader/interface.py +34 -7
- nodebpy-0.10.1/src/nodebpy/nodes/shader/manual.py +140 -0
- {nodebpy-0.9.1 → nodebpy-0.10.1}/src/nodebpy/nodes/shader/output.py +194 -84
- {nodebpy-0.9.1 → nodebpy-0.10.1}/src/nodebpy/nodes/shader/script.py +25 -2
- nodebpy-0.10.1/src/nodebpy/nodes/shader/shader.py +2136 -0
- {nodebpy-0.9.1 → nodebpy-0.10.1}/src/nodebpy/nodes/shader/texture.py +176 -56
- nodebpy-0.10.1/src/nodebpy/nodes/shader/vector.py +629 -0
- {nodebpy-0.9.1 → nodebpy-0.10.1}/src/nodebpy/screenshot.py +59 -27
- {nodebpy-0.9.1 → nodebpy-0.10.1}/src/nodebpy/sockets.py +2 -2
- nodebpy-0.10.1/src/nodebpy/types.py +245 -0
- nodebpy-0.9.1/src/nodebpy/builder.py +0 -1775
- nodebpy-0.9.1/src/nodebpy/nodes/compositor/color.py +0 -923
- nodebpy-0.9.1/src/nodebpy/nodes/compositor/converter.py +0 -1044
- nodebpy-0.9.1/src/nodebpy/nodes/compositor/distort.py +0 -844
- nodebpy-0.9.1/src/nodebpy/nodes/compositor/filter.py +0 -1102
- nodebpy-0.9.1/src/nodebpy/nodes/compositor/input.py +0 -476
- nodebpy-0.9.1/src/nodebpy/nodes/compositor/matte.py +0 -873
- nodebpy-0.9.1/src/nodebpy/nodes/compositor/texture.py +0 -886
- nodebpy-0.9.1/src/nodebpy/nodes/compositor/vector.py +0 -35
- nodebpy-0.9.1/src/nodebpy/nodes/geometry/color.py +0 -79
- nodebpy-0.9.1/src/nodebpy/nodes/geometry/converter.py +0 -3835
- nodebpy-0.9.1/src/nodebpy/nodes/geometry/geometry.py +0 -6617
- nodebpy-0.9.1/src/nodebpy/nodes/geometry/grid.py +0 -1815
- nodebpy-0.9.1/src/nodebpy/nodes/geometry/group.py +0 -21
- nodebpy-0.9.1/src/nodebpy/nodes/geometry/input.py +0 -2345
- nodebpy-0.9.1/src/nodebpy/nodes/geometry/texture.py +0 -953
- nodebpy-0.9.1/src/nodebpy/nodes/geometry/vector.py +0 -568
- nodebpy-0.9.1/src/nodebpy/nodes/shader/color.py +0 -187
- nodebpy-0.9.1/src/nodebpy/nodes/shader/converter.py +0 -503
- nodebpy-0.9.1/src/nodebpy/nodes/shader/grid.py +0 -406
- nodebpy-0.9.1/src/nodebpy/nodes/shader/group.py +0 -21
- nodebpy-0.9.1/src/nodebpy/nodes/shader/input.py +0 -898
- nodebpy-0.9.1/src/nodebpy/nodes/shader/manual.py +0 -15
- nodebpy-0.9.1/src/nodebpy/nodes/shader/shader.py +0 -1557
- nodebpy-0.9.1/src/nodebpy/nodes/shader/vector.py +0 -402
- nodebpy-0.9.1/src/nodebpy/nodes/shader/zone.py +0 -58
- nodebpy-0.9.1/src/nodebpy/types.py +0 -446
- {nodebpy-0.9.1 → nodebpy-0.10.1}/src/nodebpy/arrange.py +0 -0
- {nodebpy-0.9.1 → nodebpy-0.10.1}/src/nodebpy/lib/nodearrange/__init__.py +0 -0
- {nodebpy-0.9.1 → nodebpy-0.10.1}/src/nodebpy/lib/nodearrange/arrange/graph.py +0 -0
- {nodebpy-0.9.1 → nodebpy-0.10.1}/src/nodebpy/lib/nodearrange/arrange/ordering.py +0 -0
- {nodebpy-0.9.1 → nodebpy-0.10.1}/src/nodebpy/lib/nodearrange/arrange/ranking.py +0 -0
- {nodebpy-0.9.1 → nodebpy-0.10.1}/src/nodebpy/lib/nodearrange/arrange/realize.py +0 -0
- {nodebpy-0.9.1 → nodebpy-0.10.1}/src/nodebpy/lib/nodearrange/arrange/stacking.py +0 -0
- {nodebpy-0.9.1 → nodebpy-0.10.1}/src/nodebpy/lib/nodearrange/arrange/structs.py +0 -0
- {nodebpy-0.9.1 → nodebpy-0.10.1}/src/nodebpy/lib/nodearrange/arrange/sugiyama.py +0 -0
- {nodebpy-0.9.1 → nodebpy-0.10.1}/src/nodebpy/lib/nodearrange/arrange/x_coords.py +0 -0
- {nodebpy-0.9.1 → nodebpy-0.10.1}/src/nodebpy/lib/nodearrange/arrange/y_coords.py +0 -0
- {nodebpy-0.9.1 → nodebpy-0.10.1}/src/nodebpy/lib/nodearrange/config.py +0 -0
- {nodebpy-0.9.1 → nodebpy-0.10.1}/src/nodebpy/lib/nodearrange/utils.py +0 -0
- {nodebpy-0.9.1 → nodebpy-0.10.1}/src/nodebpy/nodes/__init__.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: nodebpy
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.10.1
|
|
4
4
|
Summary: Build nodes trees in Blender more elegantly with code
|
|
5
5
|
Author: Brady Johnston
|
|
6
6
|
Author-email: Brady Johnston <brady.johnston@me.com>
|
|
@@ -46,7 +46,6 @@ In `nodebpy` we use the `>>` operator to link from one node or socket into anoth
|
|
|
46
46
|
This should feel and behave much like the <kbd>Alt</kbd> + <kbd>Right Click</kbd> drag between nodes in [Node Wrangler](https://docs.blender.org/manual/en/latest/addons/node/node_wrangler.html). It will use some smart logic to match the most compatible sockets between the nodes, but if you ever want to be explicit you do so. The input and output sockets of a node are accessible as properties via the `i_*` and `o_*` prefixes, or you can use the `...` placeholder to specify the particular input to be user, or pass in the previous node as a named argument.
|
|
47
47
|
|
|
48
48
|
```py
|
|
49
|
-
g.Vector() >> g.SetPosition().i_offset
|
|
50
49
|
g.Vector() >> g.SetPosition(offset=...)
|
|
51
50
|
g.SetPosition(offset=g.Vector())
|
|
52
51
|
```
|
|
@@ -75,14 +74,9 @@ with TreeBuilder("MyTree") as tree:
|
|
|
75
74
|
The node tree below creates a integer input and geometry output to the node group. We create a `rotation` variable that can be used later on as an argument, then construct a longer chain of nodes being created and linked together. The nodes are added and linked as each node is instantiated. After we exit the tree context, the nodes are automatically arranged.
|
|
76
75
|
|
|
77
76
|
``` python
|
|
78
|
-
from nodebpy import
|
|
79
|
-
|
|
80
|
-
with TreeBuilder("AnotherTree", collapse=True) as tree:
|
|
81
|
-
with tree.inputs:
|
|
82
|
-
count = s.SocketInt("Count", 10)
|
|
83
|
-
with tree.outputs:
|
|
84
|
-
instances = s.SocketGeometry("Instances")
|
|
77
|
+
from nodebpy import geometry as g
|
|
85
78
|
|
|
79
|
+
with g.tree("AnotherTree", collapse=True) as tree:
|
|
86
80
|
rotation = (
|
|
87
81
|
g.RandomValue.vector(min=-1, seed=2)
|
|
88
82
|
>> g.AlignRotationToVector()
|
|
@@ -90,7 +84,7 @@ with TreeBuilder("AnotherTree", collapse=True) as tree:
|
|
|
90
84
|
)
|
|
91
85
|
|
|
92
86
|
_ = (
|
|
93
|
-
|
|
87
|
+
tree.inputs.integer("Count", 10)
|
|
94
88
|
>> g.Points(position=g.RandomValue.vector(min=-1))
|
|
95
89
|
>> g.InstanceOnPoints(instance=g.Cube(), rotation=rotation)
|
|
96
90
|
>> g.SetPosition(
|
|
@@ -99,7 +93,7 @@ with TreeBuilder("AnotherTree", collapse=True) as tree:
|
|
|
99
93
|
)
|
|
100
94
|
>> g.RealizeInstances()
|
|
101
95
|
>> g.InstanceOnPoints(g.Cube(), instance=...)
|
|
102
|
-
>>
|
|
96
|
+
>> tree.outputs.geometry("Instances")
|
|
103
97
|
)
|
|
104
98
|
```
|
|
105
99
|
|
|
@@ -120,7 +114,9 @@ math.operation = "SUBTRACT"
|
|
|
120
114
|
|
|
121
115
|
# operation can be chose as a class method
|
|
122
116
|
math = g.Math.subtract(1.0, 2.0)
|
|
117
|
+
math = g.Value(1.0) - 2.0
|
|
123
118
|
math = g.Math.add(1.0, 2.0)
|
|
119
|
+
math = g.Value(1.0) + 2.0
|
|
124
120
|
|
|
125
121
|
# these are equivalent, the g.Math.multiply is automatically added
|
|
126
122
|
g.Value(1.0) * 2
|
|
@@ -33,7 +33,6 @@ In `nodebpy` we use the `>>` operator to link from one node or socket into anoth
|
|
|
33
33
|
This should feel and behave much like the <kbd>Alt</kbd> + <kbd>Right Click</kbd> drag between nodes in [Node Wrangler](https://docs.blender.org/manual/en/latest/addons/node/node_wrangler.html). It will use some smart logic to match the most compatible sockets between the nodes, but if you ever want to be explicit you do so. The input and output sockets of a node are accessible as properties via the `i_*` and `o_*` prefixes, or you can use the `...` placeholder to specify the particular input to be user, or pass in the previous node as a named argument.
|
|
34
34
|
|
|
35
35
|
```py
|
|
36
|
-
g.Vector() >> g.SetPosition().i_offset
|
|
37
36
|
g.Vector() >> g.SetPosition(offset=...)
|
|
38
37
|
g.SetPosition(offset=g.Vector())
|
|
39
38
|
```
|
|
@@ -62,14 +61,9 @@ with TreeBuilder("MyTree") as tree:
|
|
|
62
61
|
The node tree below creates a integer input and geometry output to the node group. We create a `rotation` variable that can be used later on as an argument, then construct a longer chain of nodes being created and linked together. The nodes are added and linked as each node is instantiated. After we exit the tree context, the nodes are automatically arranged.
|
|
63
62
|
|
|
64
63
|
``` python
|
|
65
|
-
from nodebpy import
|
|
66
|
-
|
|
67
|
-
with TreeBuilder("AnotherTree", collapse=True) as tree:
|
|
68
|
-
with tree.inputs:
|
|
69
|
-
count = s.SocketInt("Count", 10)
|
|
70
|
-
with tree.outputs:
|
|
71
|
-
instances = s.SocketGeometry("Instances")
|
|
64
|
+
from nodebpy import geometry as g
|
|
72
65
|
|
|
66
|
+
with g.tree("AnotherTree", collapse=True) as tree:
|
|
73
67
|
rotation = (
|
|
74
68
|
g.RandomValue.vector(min=-1, seed=2)
|
|
75
69
|
>> g.AlignRotationToVector()
|
|
@@ -77,7 +71,7 @@ with TreeBuilder("AnotherTree", collapse=True) as tree:
|
|
|
77
71
|
)
|
|
78
72
|
|
|
79
73
|
_ = (
|
|
80
|
-
|
|
74
|
+
tree.inputs.integer("Count", 10)
|
|
81
75
|
>> g.Points(position=g.RandomValue.vector(min=-1))
|
|
82
76
|
>> g.InstanceOnPoints(instance=g.Cube(), rotation=rotation)
|
|
83
77
|
>> g.SetPosition(
|
|
@@ -86,7 +80,7 @@ with TreeBuilder("AnotherTree", collapse=True) as tree:
|
|
|
86
80
|
)
|
|
87
81
|
>> g.RealizeInstances()
|
|
88
82
|
>> g.InstanceOnPoints(g.Cube(), instance=...)
|
|
89
|
-
>>
|
|
83
|
+
>> tree.outputs.geometry("Instances")
|
|
90
84
|
)
|
|
91
85
|
```
|
|
92
86
|
|
|
@@ -107,7 +101,9 @@ math.operation = "SUBTRACT"
|
|
|
107
101
|
|
|
108
102
|
# operation can be chose as a class method
|
|
109
103
|
math = g.Math.subtract(1.0, 2.0)
|
|
104
|
+
math = g.Value(1.0) - 2.0
|
|
110
105
|
math = g.Math.add(1.0, 2.0)
|
|
106
|
+
math = g.Value(1.0) + 2.0
|
|
111
107
|
|
|
112
108
|
# these are equivalent, the g.Math.multiply is automatically added
|
|
113
109
|
g.Value(1.0) * 2
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
from . import nodes, screenshot, sockets
|
|
2
|
-
from .builder import TreeBuilder
|
|
2
|
+
from .builder import TreeBuilder, NodeGroupBuilder
|
|
3
3
|
from .nodes import compositor, geometry, shader
|
|
4
|
-
from .screenshot import generate_mermaid_diagram, save_mermaid_diagram
|
|
5
4
|
|
|
6
5
|
__all__ = [
|
|
7
6
|
"nodes",
|
|
@@ -11,6 +10,5 @@ __all__ = [
|
|
|
11
10
|
"sockets",
|
|
12
11
|
"screenshot",
|
|
13
12
|
"TreeBuilder",
|
|
14
|
-
"
|
|
15
|
-
"save_mermaid_diagram",
|
|
13
|
+
"NodeGroupBuilder",
|
|
16
14
|
]
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
"""nodebpy.builder — node tree construction API.
|
|
2
|
+
|
|
3
|
+
Public names are re-exported here. Old names (NodeBuilder, SocketLinker,
|
|
4
|
+
SocketBase) are kept as aliases for backward compatibility.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from ._utils import SocketError, denormalize_name, normalize_name
|
|
8
|
+
from .accessor import SocketAccessor
|
|
9
|
+
from .interface import (
|
|
10
|
+
InterfaceSocket,
|
|
11
|
+
SocketBoolean,
|
|
12
|
+
SocketBundle,
|
|
13
|
+
SocketClosure,
|
|
14
|
+
SocketCollection,
|
|
15
|
+
SocketColor,
|
|
16
|
+
SocketFloat,
|
|
17
|
+
SocketGeometry,
|
|
18
|
+
SocketImage,
|
|
19
|
+
SocketInteger,
|
|
20
|
+
SocketMaterial,
|
|
21
|
+
SocketMatrix,
|
|
22
|
+
SocketMenu,
|
|
23
|
+
SocketObject,
|
|
24
|
+
SocketRotation,
|
|
25
|
+
SocketShader,
|
|
26
|
+
SocketString,
|
|
27
|
+
SocketVector,
|
|
28
|
+
)
|
|
29
|
+
from .mixins import LinkingMixin, OperatorMixin
|
|
30
|
+
from .node import BaseNode, DynamicInputsMixin, NodeGroupBuilder
|
|
31
|
+
from .socket import (
|
|
32
|
+
BooleanSocket,
|
|
33
|
+
BundleSocket,
|
|
34
|
+
ClosureSocket,
|
|
35
|
+
CollectionSocket,
|
|
36
|
+
ColorSocket,
|
|
37
|
+
FloatSocket,
|
|
38
|
+
GeometrySocket,
|
|
39
|
+
ImageSocket,
|
|
40
|
+
IntegerSocket,
|
|
41
|
+
MaterialSocket,
|
|
42
|
+
MatrixSocket,
|
|
43
|
+
MenuSocket,
|
|
44
|
+
ObjectSocket,
|
|
45
|
+
RotationSocket,
|
|
46
|
+
ShaderSocket,
|
|
47
|
+
Socket,
|
|
48
|
+
StringSocket,
|
|
49
|
+
VectorSocket,
|
|
50
|
+
_BooleanMixin,
|
|
51
|
+
_ColorMixin,
|
|
52
|
+
_IntegerMixin,
|
|
53
|
+
_MatrixMixin,
|
|
54
|
+
_RotationMixin,
|
|
55
|
+
_VectorMixin,
|
|
56
|
+
)
|
|
57
|
+
from .tree import (
|
|
58
|
+
InputInterfaceContext,
|
|
59
|
+
MaterialBuilder,
|
|
60
|
+
OutputInterfaceContext,
|
|
61
|
+
PanelContext,
|
|
62
|
+
SocketContext,
|
|
63
|
+
TreeBuilder,
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
# Backward-compatible aliases for hand-written code that uses the old names.
|
|
67
|
+
NodeBuilder = BaseNode
|
|
68
|
+
SocketLinker = Socket
|
|
69
|
+
SocketBase = InterfaceSocket
|
|
70
|
+
|
|
71
|
+
__all__ = [
|
|
72
|
+
# Core
|
|
73
|
+
"TreeBuilder",
|
|
74
|
+
"MaterialBuilder",
|
|
75
|
+
"BaseNode",
|
|
76
|
+
"Socket",
|
|
77
|
+
"SocketAccessor",
|
|
78
|
+
# Mixins
|
|
79
|
+
"OperatorMixin",
|
|
80
|
+
"LinkingMixin",
|
|
81
|
+
"DynamicInputsMixin",
|
|
82
|
+
# Node groups
|
|
83
|
+
"NodeGroupBuilder",
|
|
84
|
+
# Type-specific socket classes (runtime)
|
|
85
|
+
"FloatSocket",
|
|
86
|
+
"VectorSocket",
|
|
87
|
+
"ColorSocket",
|
|
88
|
+
"IntegerSocket",
|
|
89
|
+
"BooleanSocket",
|
|
90
|
+
"RotationSocket",
|
|
91
|
+
"MatrixSocket",
|
|
92
|
+
"StringSocket",
|
|
93
|
+
"MenuSocket",
|
|
94
|
+
"GeometrySocket",
|
|
95
|
+
"ObjectSocket",
|
|
96
|
+
"MaterialSocket",
|
|
97
|
+
"ImageSocket",
|
|
98
|
+
"CollectionSocket",
|
|
99
|
+
"BundleSocket",
|
|
100
|
+
"ClosureSocket",
|
|
101
|
+
"ShaderSocket",
|
|
102
|
+
# Type-specific behaviour mixins
|
|
103
|
+
"_VectorMixin",
|
|
104
|
+
"_ColorMixin",
|
|
105
|
+
"_IntegerMixin",
|
|
106
|
+
"_BooleanMixin",
|
|
107
|
+
"_RotationMixin",
|
|
108
|
+
"_MatrixMixin",
|
|
109
|
+
# Interface socket base
|
|
110
|
+
"InterfaceSocket",
|
|
111
|
+
# Interface socket types
|
|
112
|
+
"SocketFloat",
|
|
113
|
+
"SocketInteger",
|
|
114
|
+
"SocketBoolean",
|
|
115
|
+
"SocketVector",
|
|
116
|
+
"SocketColor",
|
|
117
|
+
"SocketRotation",
|
|
118
|
+
"SocketMatrix",
|
|
119
|
+
"SocketString",
|
|
120
|
+
"SocketMenu",
|
|
121
|
+
"SocketObject",
|
|
122
|
+
"SocketGeometry",
|
|
123
|
+
"SocketCollection",
|
|
124
|
+
"SocketImage",
|
|
125
|
+
"SocketMaterial",
|
|
126
|
+
"SocketBundle",
|
|
127
|
+
"SocketClosure",
|
|
128
|
+
"SocketShader",
|
|
129
|
+
# Tree context helpers
|
|
130
|
+
"SocketContext",
|
|
131
|
+
"PanelContext",
|
|
132
|
+
"InputInterfaceContext",
|
|
133
|
+
"OutputInterfaceContext",
|
|
134
|
+
# Utilities
|
|
135
|
+
"SocketError",
|
|
136
|
+
"normalize_name",
|
|
137
|
+
"denormalize_name",
|
|
138
|
+
# Backward-compatible aliases
|
|
139
|
+
"NodeBuilder",
|
|
140
|
+
"SocketLinker",
|
|
141
|
+
"SocketBase",
|
|
142
|
+
]
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
4
|
+
|
|
5
|
+
from bpy.types import NodeSocket
|
|
6
|
+
|
|
7
|
+
if TYPE_CHECKING:
|
|
8
|
+
from .socket import Socket
|
|
9
|
+
|
|
10
|
+
_SOCKET_LINKER_REGISTRY: dict[str, "type[Socket]"] = {}
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _get_socket_linker(socket: NodeSocket) -> "Socket":
|
|
14
|
+
for key, cls in _SOCKET_LINKER_REGISTRY.items():
|
|
15
|
+
if key in socket.bl_idname:
|
|
16
|
+
return cls(socket)
|
|
17
|
+
from .socket import Socket
|
|
18
|
+
|
|
19
|
+
return Socket(socket)
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
import bpy
|
|
6
|
+
from bpy.types import NodeSocket
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class SocketError(Exception):
|
|
10
|
+
"""Raised when a socket operation fails."""
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
# Type precedence for mixed-type operator dispatch (higher = dominant).
|
|
14
|
+
_TYPE_PRECEDENCE: dict[str, int] = {
|
|
15
|
+
"INT": 0,
|
|
16
|
+
"VALUE": 1,
|
|
17
|
+
"FLOAT": 1,
|
|
18
|
+
"VECTOR": 2,
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
GEO_NODE_NAMES = (
|
|
22
|
+
f"GeometryNode{name}"
|
|
23
|
+
for name in (
|
|
24
|
+
"SetPosition",
|
|
25
|
+
"TransformGeometry",
|
|
26
|
+
"GroupInput",
|
|
27
|
+
"GroupOutput",
|
|
28
|
+
"MeshToPoints",
|
|
29
|
+
"PointsToVertices",
|
|
30
|
+
)
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def normalize_name(name: str) -> str:
|
|
35
|
+
"""Convert 'Geometry' or 'My Socket' to 'geometry' or 'my_socket'."""
|
|
36
|
+
return name.lower().replace(" ", "_").replace("é", "e")
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def denormalize_name(attr_name: str) -> str:
|
|
40
|
+
"""Convert 'geometry' or 'my_socket' to 'Geometry' or 'My Socket'."""
|
|
41
|
+
return attr_name.replace("_", " ").title()
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _allow_innactive_sockets(node: bpy.types.Node) -> bool:
|
|
45
|
+
"""Returns True if we should allow inactive sockets to be linked for this node type"""
|
|
46
|
+
return node.bl_idname in (
|
|
47
|
+
"GeometryNodeIndexSwitch",
|
|
48
|
+
"GeometryNodeMenuSwitch",
|
|
49
|
+
"ShaderNodeMixShader",
|
|
50
|
+
"GeometryNodeSwitch",
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _resolve_promotion(
|
|
55
|
+
self_socket: NodeSocket, other: Any, reverse: bool
|
|
56
|
+
) -> "tuple[NodeSocket, Any, bool]":
|
|
57
|
+
"""Determine the dominant socket for operator dispatch.
|
|
58
|
+
|
|
59
|
+
When both operands have a socket type, the higher-precedence type wins.
|
|
60
|
+
If `other` is dominant, the operands are swapped and `reverse` is flipped.
|
|
61
|
+
|
|
62
|
+
Returns (dominant_socket, effective_other, effective_reverse).
|
|
63
|
+
"""
|
|
64
|
+
other_type = getattr(other, "type", None)
|
|
65
|
+
self_prec = _TYPE_PRECEDENCE.get(self_socket.type, 1)
|
|
66
|
+
other_prec = _TYPE_PRECEDENCE.get(other_type, -1) # type: ignore[arg-type]
|
|
67
|
+
|
|
68
|
+
if other_prec > self_prec:
|
|
69
|
+
# Other side is dominant — swap so the linker wraps the vector/higher socket
|
|
70
|
+
other_socket = other._default_output_socket
|
|
71
|
+
return other_socket, self_socket, not reverse
|
|
72
|
+
|
|
73
|
+
return self_socket, other, reverse
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class _NodeLike:
|
|
77
|
+
"""Marker base for objects that wrap a Blender node (have .node, .inputs, .outputs)."""
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class _SocketLike:
|
|
81
|
+
"""Marker base for objects that wrap a single Blender NodeSocket (have .socket)."""
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING, Literal
|
|
4
|
+
|
|
5
|
+
import bpy
|
|
6
|
+
from bpy.types import NodeSocket
|
|
7
|
+
|
|
8
|
+
from ._registry import _get_socket_linker
|
|
9
|
+
from ._utils import SocketError, _allow_innactive_sockets, denormalize_name
|
|
10
|
+
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
from .socket import Socket
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class SocketAccessor:
|
|
16
|
+
"""Unified accessor for a node's input or output socket collection.
|
|
17
|
+
|
|
18
|
+
Supports identifier/name lookup, dict-style ``[]`` access, availability
|
|
19
|
+
filtering, and type-compatible matching — replacing the former pairs of
|
|
20
|
+
``_input_idx``/``_output_idx``, ``_input``/``_output``,
|
|
21
|
+
``_available_inputs``/``_available_outputs``, and ``_best_output_socket``.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
def __init__(
|
|
25
|
+
self,
|
|
26
|
+
collection: bpy.types.NodeInputs | bpy.types.NodeOutputs,
|
|
27
|
+
direction: Literal["input", "output"],
|
|
28
|
+
):
|
|
29
|
+
self._direction = direction
|
|
30
|
+
self._collection = collection
|
|
31
|
+
|
|
32
|
+
def _index(self, key: str | int) -> int:
|
|
33
|
+
"""Find socket index by identifier, falling back to name.
|
|
34
|
+
|
|
35
|
+
Tries identifier match first. If no identifier matches, falls back to
|
|
36
|
+
name lookup — but raises if the name is duplicated (ambiguous).
|
|
37
|
+
Integer keys are returned directly.
|
|
38
|
+
"""
|
|
39
|
+
if isinstance(key, int):
|
|
40
|
+
return key
|
|
41
|
+
ids = [s.identifier for s in self._collection]
|
|
42
|
+
denorm = denormalize_name(key)
|
|
43
|
+
for candidate in (key, denorm):
|
|
44
|
+
if candidate in ids:
|
|
45
|
+
return ids.index(candidate)
|
|
46
|
+
names = [s.name for s in self._collection]
|
|
47
|
+
for key in (key, denorm):
|
|
48
|
+
if key in names:
|
|
49
|
+
if names.count(key) > 1:
|
|
50
|
+
raise RuntimeError(
|
|
51
|
+
f"{self._direction.title()} name '{key}' is ambiguous on "
|
|
52
|
+
f"{self._node.bl_idname} (appears {names.count(key)} times). "
|
|
53
|
+
f"Use the socket identifier instead."
|
|
54
|
+
)
|
|
55
|
+
return names.index(key)
|
|
56
|
+
raise RuntimeError(
|
|
57
|
+
f"{self._direction.title()} '{key}' not found on "
|
|
58
|
+
f"{self._node.bl_idname}. Available sockets (id: name): {list(zip(ids, names))}"
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
def _get(self, key: str | int) -> "Socket":
|
|
62
|
+
"""Get a Socket for a socket by identifier, name, or index."""
|
|
63
|
+
return _get_socket_linker(self._collection[self._index(key)])
|
|
64
|
+
|
|
65
|
+
def __getitem__(self, key: str | int) -> "Socket":
|
|
66
|
+
"""Access by identifier, name, or integer index."""
|
|
67
|
+
return self._get(key)
|
|
68
|
+
|
|
69
|
+
@property
|
|
70
|
+
def _node(self) -> bpy.types.Node:
|
|
71
|
+
"""The node this accessor is associated with."""
|
|
72
|
+
if isinstance(self._collection, list):
|
|
73
|
+
return self._collection[0].node
|
|
74
|
+
# bpy NodeInputs/NodeOutputs.id_data returns the NodeTree (top-level ID),
|
|
75
|
+
# not the Node. Retrieve the node via the first socket instead.
|
|
76
|
+
for s in self._collection:
|
|
77
|
+
return s.node
|
|
78
|
+
return self._collection.data # empty collection fallback
|
|
79
|
+
|
|
80
|
+
@property
|
|
81
|
+
def _ignore_visibility(self) -> bool:
|
|
82
|
+
"""Whether to ignore socket visibility when selecting available sockets.
|
|
83
|
+
|
|
84
|
+
Only affects ``available`` / ``best_match`` (the auto-linking heuristics).
|
|
85
|
+
``values()`` / ``items()`` always respect node-level visibility so that
|
|
86
|
+
iteration over a node's sockets stays predictable regardless of context.
|
|
87
|
+
Returns False when called outside a tree context (e.g. from a bare
|
|
88
|
+
Socket that was created outside a ``with tree:`` block).
|
|
89
|
+
"""
|
|
90
|
+
from .tree import TreeBuilder
|
|
91
|
+
|
|
92
|
+
if not TreeBuilder._tree_contexts:
|
|
93
|
+
return False
|
|
94
|
+
return TreeBuilder._tree_contexts[-1].ignore_visibility
|
|
95
|
+
|
|
96
|
+
def _visible_sockets(self) -> list[NodeSocket]:
|
|
97
|
+
"""Sockets that should appear in iteration (values/items/keys).
|
|
98
|
+
|
|
99
|
+
Uses the per-node allowlist (``_allow_innactive_sockets``) rather than
|
|
100
|
+
the tree-level ``ignore_visibility`` flag — enumeration should always
|
|
101
|
+
reflect what is meaningfully present on the node, not the linking context.
|
|
102
|
+
"""
|
|
103
|
+
if self._direction == "input":
|
|
104
|
+
return [
|
|
105
|
+
s
|
|
106
|
+
for s in self._collection
|
|
107
|
+
if _allow_innactive_sockets(self._node)
|
|
108
|
+
or (not s.is_inactive and s.is_icon_visible)
|
|
109
|
+
]
|
|
110
|
+
return [s for s in self._collection if s.is_icon_visible]
|
|
111
|
+
|
|
112
|
+
@property
|
|
113
|
+
def _available(self) -> list[NodeSocket]:
|
|
114
|
+
"""Sockets eligible for automatic linking.
|
|
115
|
+
|
|
116
|
+
Respects ``ignore_visibility`` on the active ``TreeBuilder`` context, so
|
|
117
|
+
nodes with normally-hidden sockets can still be auto-linked when that flag
|
|
118
|
+
is set (e.g. during ``test_add_all_nodes``).
|
|
119
|
+
"""
|
|
120
|
+
if self._direction == "input":
|
|
121
|
+
return [
|
|
122
|
+
s
|
|
123
|
+
for s in self._collection
|
|
124
|
+
if (
|
|
125
|
+
self._ignore_visibility or (not s.is_inactive and s.is_icon_visible)
|
|
126
|
+
)
|
|
127
|
+
and (not s.links or s.is_multi_input)
|
|
128
|
+
]
|
|
129
|
+
return [
|
|
130
|
+
s for s in self._collection if self._ignore_visibility or s.is_icon_visible
|
|
131
|
+
]
|
|
132
|
+
|
|
133
|
+
def _best_match(self, socket_type: str) -> NodeSocket:
|
|
134
|
+
"""Find the best compatible socket for the given type."""
|
|
135
|
+
from ..types import SOCKET_COMPATIBILITY
|
|
136
|
+
|
|
137
|
+
compatible = SOCKET_COMPATIBILITY.get(socket_type, ())
|
|
138
|
+
possible = [s for s in self._available if s.type in compatible]
|
|
139
|
+
if possible:
|
|
140
|
+
possible.sort(key=lambda x: compatible.index(x.type))
|
|
141
|
+
return possible[0]
|
|
142
|
+
raise SocketError(
|
|
143
|
+
f"No compatible {self._direction} socket found for type "
|
|
144
|
+
f"{socket_type} on {self._node.name}"
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
def _values(self) -> "list[Socket]":
|
|
148
|
+
"""All visible sockets as Sockets.
|
|
149
|
+
|
|
150
|
+
Uses node-level visibility rules regardless of ``ignore_visibility`` —
|
|
151
|
+
see ``_visible_sockets`` for rationale.
|
|
152
|
+
"""
|
|
153
|
+
return [_get_socket_linker(s) for s in self._visible_sockets()]
|
|
154
|
+
|
|
155
|
+
def _items(self) -> "list[tuple[str, Socket]]":
|
|
156
|
+
"""All visible sockets as (name, Socket) pairs.
|
|
157
|
+
|
|
158
|
+
Uses node-level visibility rules regardless of ``ignore_visibility`` —
|
|
159
|
+
see ``_visible_sockets`` for rationale.
|
|
160
|
+
"""
|
|
161
|
+
return [(s.name, _get_socket_linker(s)) for s in self._visible_sockets()]
|
|
162
|
+
|
|
163
|
+
def _keys(self) -> list[str]:
|
|
164
|
+
"""All visible socket names."""
|
|
165
|
+
return [name for name, _ in self._items()]
|
|
166
|
+
|
|
167
|
+
def __len__(self) -> int:
|
|
168
|
+
return len(self._items())
|
|
169
|
+
|
|
170
|
+
def __iter__(self):
|
|
171
|
+
"""Iterate over socket names (enables ``**node.outputs`` unpacking)."""
|
|
172
|
+
return iter(self._keys())
|
|
173
|
+
|
|
174
|
+
def __getattr__(self, name: str) -> "Socket":
|
|
175
|
+
"""Dynamic socket access by normalised attribute name.
|
|
176
|
+
|
|
177
|
+
Converts ``node.o.base_color`` to ``self._get("Base Color")``.
|
|
178
|
+
Raises ``AttributeError`` (not ``RuntimeError``) so that ``hasattr``
|
|
179
|
+
and ``getattr(..., default)`` behave correctly.
|
|
180
|
+
"""
|
|
181
|
+
if name.startswith("_"):
|
|
182
|
+
raise AttributeError(name)
|
|
183
|
+
try:
|
|
184
|
+
return self._get(name)
|
|
185
|
+
except RuntimeError:
|
|
186
|
+
raise AttributeError(
|
|
187
|
+
f"Socket '{name}' not found on {self._direction} accessor"
|
|
188
|
+
)
|