nodebpy 0.1.0__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.1.0/PKG-INFO +160 -0
- nodebpy-0.1.0/README.md +136 -0
- nodebpy-0.1.0/pyproject.toml +38 -0
- nodebpy-0.1.0/src/nodebpy/__init__.py +12 -0
- nodebpy-0.1.0/src/nodebpy/arrange.py +362 -0
- nodebpy-0.1.0/src/nodebpy/builder.py +931 -0
- nodebpy-0.1.0/src/nodebpy/nodes/__init__.py +12 -0
- nodebpy-0.1.0/src/nodebpy/nodes/attribute.py +580 -0
- nodebpy-0.1.0/src/nodebpy/nodes/curve.py +2006 -0
- nodebpy-0.1.0/src/nodebpy/nodes/geometry.py +7304 -0
- nodebpy-0.1.0/src/nodebpy/nodes/input.py +762 -0
- nodebpy-0.1.0/src/nodebpy/nodes/manually_specified.py +1356 -0
- nodebpy-0.1.0/src/nodebpy/nodes/mesh.py +1408 -0
- nodebpy-0.1.0/src/nodebpy/nodes/types.py +119 -0
- nodebpy-0.1.0/src/nodebpy/nodes/utilities.py +2344 -0
- nodebpy-0.1.0/src/nodebpy/screenshot.py +531 -0
- nodebpy-0.1.0/src/nodebpy/screenshot_subprocess.py +422 -0
- nodebpy-0.1.0/src/nodebpy/sockets.py +46 -0
nodebpy-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: nodebpy
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Build nodes in Blender with code
|
|
5
|
+
Author: Brady Johnston
|
|
6
|
+
Author-email: Brady Johnston <brady.johnston@me.com>
|
|
7
|
+
Requires-Dist: arrangebpy>=0.1.0
|
|
8
|
+
Requires-Dist: jsondiff>=2.2.1
|
|
9
|
+
Requires-Dist: syrupy>=5.0.0
|
|
10
|
+
Requires-Dist: tree-clipper>=0.1.1
|
|
11
|
+
Requires-Dist: bpy>=5.0.0 ; extra == 'bpy'
|
|
12
|
+
Requires-Dist: fake-bpy-module>=20260113 ; extra == 'dev'
|
|
13
|
+
Requires-Dist: pytest>=9.0.2 ; extra == 'dev'
|
|
14
|
+
Requires-Dist: pytest-cov>=7.0.0 ; extra == 'dev'
|
|
15
|
+
Requires-Dist: quarto-cli>=1.8.26 ; extra == 'dev'
|
|
16
|
+
Requires-Dist: quartodoc>=0.11.1 ; extra == 'dev'
|
|
17
|
+
Requires-Dist: ruff>=0.14.11 ; extra == 'dev'
|
|
18
|
+
Requires-Dist: ipython>=8.0.0 ; extra == 'jupyter'
|
|
19
|
+
Requires-Python: >=3.11
|
|
20
|
+
Provides-Extra: bpy
|
|
21
|
+
Provides-Extra: dev
|
|
22
|
+
Provides-Extra: jupyter
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
# nodebpy
|
|
28
|
+
|
|
29
|
+
[](https://github.com/BradyAJohnston/nodebpy/actions/workflows/tests.yml)
|
|
31
|
+
[](https://codecov.io/gh/BradyAJohnston/nodebpy)
|
|
32
|
+
|
|
33
|
+
A package to help build node trees in blender more elegantly with python
|
|
34
|
+
code.
|
|
35
|
+
|
|
36
|
+
## The Design Idea
|
|
37
|
+
|
|
38
|
+
Other projects have attempted similar but none quite handled the API how
|
|
39
|
+
I felt it should be done. Notable existing projects are
|
|
40
|
+
[geometry-script](https://github.com/carson-katri/geometry-script),
|
|
41
|
+
[geonodes](https://github.com/al1brn/geonodes),
|
|
42
|
+
[NodeToPython](https://github.com/BrendanParmer/NodeToPython).
|
|
43
|
+
|
|
44
|
+
Other projects implement chaining of nodes mostly as dot methos of nodes
|
|
45
|
+
to chain them (`InstanceOnPoints().set_position()`). This has the
|
|
46
|
+
potential to crowd the API for individual nodes and easy chaining is
|
|
47
|
+
instead approached via overriding the `>>` operator.
|
|
48
|
+
|
|
49
|
+
### Chain Nodes with `>>`
|
|
50
|
+
|
|
51
|
+
By default the operator attempts to link the first output of the
|
|
52
|
+
previous node with the first input of the next. You can override this
|
|
53
|
+
behaviour by being explicit with the socket you are passing out
|
|
54
|
+
(`AccumulateField().o_total`) or using the `...` for the inputs into the
|
|
55
|
+
next node. The dots can appear at multiple locations and each input will
|
|
56
|
+
be linked to the previous node via the inferred or specified socket.
|
|
57
|
+
|
|
58
|
+
# Example Node Tree
|
|
59
|
+
|
|
60
|
+
``` python
|
|
61
|
+
from nodebpy import TreeBuilder, nodes as n, sockets as s
|
|
62
|
+
|
|
63
|
+
with TreeBuilder("AnotherTree") as tree:
|
|
64
|
+
with tree.inputs:
|
|
65
|
+
count = s.SocketInt("Count", 10)
|
|
66
|
+
with tree.outputs:
|
|
67
|
+
instances = s.SocketGeometry("Instances")
|
|
68
|
+
|
|
69
|
+
rotation = (
|
|
70
|
+
n.RandomValue.vector(min=(-1, -1, -1), seed=2)
|
|
71
|
+
>> n.AlignRotationToVector()
|
|
72
|
+
>> n.RotateRotation(
|
|
73
|
+
rotate_by=n.AxisAngleToRotation(angle=0.3), rotation_space="LOCAL"
|
|
74
|
+
)
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
_ = (
|
|
78
|
+
count
|
|
79
|
+
>> n.Points(position=n.RandomValue.vector(min=(-1, -1, -1)))
|
|
80
|
+
>> n.InstanceOnPoints(instance=n.Cube(), rotation=rotation)
|
|
81
|
+
>> n.SetPosition(
|
|
82
|
+
position=n.Position() * 2.0 + (0, 0.2, 0.3),
|
|
83
|
+
offset=(0, 0, 0.1),
|
|
84
|
+
)
|
|
85
|
+
>> n.RealizeInstances()
|
|
86
|
+
>> n.InstanceOnPoints(n.Cube(), instance=...)
|
|
87
|
+
>> instances
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
tree
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
``` mermaid
|
|
94
|
+
graph LR
|
|
95
|
+
N0("NodeGroupInput"):::default-node
|
|
96
|
+
N1("RandomValue<br/><small>(-1,-1,-1) seed:2</small>"):::converter-node
|
|
97
|
+
N2("RandomValue<br/><small>(-1,-1,-1) seed:1</small>"):::converter-node
|
|
98
|
+
N3("AlignRotationToVector<br/><small>(0,0,1)</small>"):::converter-node
|
|
99
|
+
N4("AxisAngleToRotation<br/><small>(0,0,1)</small>"):::converter-node
|
|
100
|
+
N5("InputPosition"):::input-node
|
|
101
|
+
N6("Points"):::geometry-node
|
|
102
|
+
N7("MeshCube"):::geometry-node
|
|
103
|
+
N8("RotateRotation"):::converter-node
|
|
104
|
+
N9("VectorMath<br/><small>×2</small>"):::vector-node
|
|
105
|
+
N10("InstanceOnPoints"):::geometry-node
|
|
106
|
+
N11("VectorMath<br/><small>(0,0.2,0.3)</small>"):::vector-node
|
|
107
|
+
N12("SetPosition<br/><small>+(0,0,0.1)</small>"):::geometry-node
|
|
108
|
+
N13("MeshCube"):::geometry-node
|
|
109
|
+
N14("RealizeInstances"):::geometry-node
|
|
110
|
+
N15("InstanceOnPoints"):::geometry-node
|
|
111
|
+
N16("NodeGroupOutput"):::default-node
|
|
112
|
+
N1 -->|"Value>>Rotation"| N3
|
|
113
|
+
N4 -->|"Rotation>>Rotate By"| N8
|
|
114
|
+
N3 -->|"Rotation>>Rotation"| N8
|
|
115
|
+
N2 -->|"Value>>Position"| N6
|
|
116
|
+
N0 -->|"Count>>Count"| N6
|
|
117
|
+
N7 -->|"Mesh>>Instance"| N10
|
|
118
|
+
N8 -->|"Rotation>>Rotation"| N10
|
|
119
|
+
N6 -->|"Points>>Points"| N10
|
|
120
|
+
N5 -->|"Position>>Vector"| N9
|
|
121
|
+
N9 -->|"Vector>>Vector"| N11
|
|
122
|
+
N11 -->|"Vector>>Position"| N12
|
|
123
|
+
N10 -->|"Instances>>Geometry"| N12
|
|
124
|
+
N12 -->|"Geometry>>Geometry"| N14
|
|
125
|
+
N13 -->|"Mesh>>Points"| N15
|
|
126
|
+
N14 -->|"Geometry>>Instance"| N15
|
|
127
|
+
N15 -->|"Instances>>Instances"| N16
|
|
128
|
+
|
|
129
|
+
classDef geometry-node fill:#e8f5f1,stroke:#3a7c49,stroke-width:2px
|
|
130
|
+
classDef converter-node fill:#e6f1f7,stroke:#246283,stroke-width:2px
|
|
131
|
+
classDef vector-node fill:#e9e9f5,stroke:#3C3C83,stroke-width:2px
|
|
132
|
+
classDef texture-node fill:#fef3e6,stroke:#E66800,stroke-width:2px
|
|
133
|
+
classDef shader-node fill:#fef0eb,stroke:#e67c52,stroke-width:2px
|
|
134
|
+
classDef input-node fill:#f1f8ed,stroke:#7fb069,stroke-width:2px
|
|
135
|
+
classDef output-node fill:#faf0ed,stroke:#c97659,stroke-width:2px
|
|
136
|
+
classDef default-node fill:#f0f0f0,stroke:#5a5a5a,stroke-width:2px
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+

|
|
140
|
+
|
|
141
|
+
# Design Considerations
|
|
142
|
+
|
|
143
|
+
Whenever possible, support IDE auto-complete and have useful types. We
|
|
144
|
+
should know as much ahead of time as possible if our network will
|
|
145
|
+
actually build.
|
|
146
|
+
|
|
147
|
+
- Stick as closely to Geometry Nodes naming as possible
|
|
148
|
+
- `RandomValue` creates a random value node
|
|
149
|
+
- `RandomValue.vector()` creates it set to `"VECTOR"` data type and
|
|
150
|
+
provides arguments for IDE auto-complete
|
|
151
|
+
- Inputs and outputs from a node are prefixed with `i_*` and `o_`:
|
|
152
|
+
- `AccumulateField().o_total` returns the output `Total` socket
|
|
153
|
+
- `AccumulateField().i_value` returns the input `Value` socket
|
|
154
|
+
- If inputs are subject to change depending on enums, provide separate
|
|
155
|
+
constructor methods that provide related inputs as arguments. There
|
|
156
|
+
should be no guessing involved and IDEs should provide documentation
|
|
157
|
+
for what is required:
|
|
158
|
+
- `TransformGeometry.matrix(CombineTrasnsform(translation=(0, 0, 1))`
|
|
159
|
+
- `TransformGeoemtry.components(translation=(0, 0, 1))`
|
|
160
|
+
- `TransformGeometry(translation=(0, 0, 1))`
|
nodebpy-0.1.0/README.md
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
# nodebpy
|
|
4
|
+
|
|
5
|
+
[](https://github.com/BradyAJohnston/nodebpy/actions/workflows/tests.yml)
|
|
7
|
+
[](https://codecov.io/gh/BradyAJohnston/nodebpy)
|
|
8
|
+
|
|
9
|
+
A package to help build node trees in blender more elegantly with python
|
|
10
|
+
code.
|
|
11
|
+
|
|
12
|
+
## The Design Idea
|
|
13
|
+
|
|
14
|
+
Other projects have attempted similar but none quite handled the API how
|
|
15
|
+
I felt it should be done. Notable existing projects are
|
|
16
|
+
[geometry-script](https://github.com/carson-katri/geometry-script),
|
|
17
|
+
[geonodes](https://github.com/al1brn/geonodes),
|
|
18
|
+
[NodeToPython](https://github.com/BrendanParmer/NodeToPython).
|
|
19
|
+
|
|
20
|
+
Other projects implement chaining of nodes mostly as dot methos of nodes
|
|
21
|
+
to chain them (`InstanceOnPoints().set_position()`). This has the
|
|
22
|
+
potential to crowd the API for individual nodes and easy chaining is
|
|
23
|
+
instead approached via overriding the `>>` operator.
|
|
24
|
+
|
|
25
|
+
### Chain Nodes with `>>`
|
|
26
|
+
|
|
27
|
+
By default the operator attempts to link the first output of the
|
|
28
|
+
previous node with the first input of the next. You can override this
|
|
29
|
+
behaviour by being explicit with the socket you are passing out
|
|
30
|
+
(`AccumulateField().o_total`) or using the `...` for the inputs into the
|
|
31
|
+
next node. The dots can appear at multiple locations and each input will
|
|
32
|
+
be linked to the previous node via the inferred or specified socket.
|
|
33
|
+
|
|
34
|
+
# Example Node Tree
|
|
35
|
+
|
|
36
|
+
``` python
|
|
37
|
+
from nodebpy import TreeBuilder, nodes as n, sockets as s
|
|
38
|
+
|
|
39
|
+
with TreeBuilder("AnotherTree") as tree:
|
|
40
|
+
with tree.inputs:
|
|
41
|
+
count = s.SocketInt("Count", 10)
|
|
42
|
+
with tree.outputs:
|
|
43
|
+
instances = s.SocketGeometry("Instances")
|
|
44
|
+
|
|
45
|
+
rotation = (
|
|
46
|
+
n.RandomValue.vector(min=(-1, -1, -1), seed=2)
|
|
47
|
+
>> n.AlignRotationToVector()
|
|
48
|
+
>> n.RotateRotation(
|
|
49
|
+
rotate_by=n.AxisAngleToRotation(angle=0.3), rotation_space="LOCAL"
|
|
50
|
+
)
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
_ = (
|
|
54
|
+
count
|
|
55
|
+
>> n.Points(position=n.RandomValue.vector(min=(-1, -1, -1)))
|
|
56
|
+
>> n.InstanceOnPoints(instance=n.Cube(), rotation=rotation)
|
|
57
|
+
>> n.SetPosition(
|
|
58
|
+
position=n.Position() * 2.0 + (0, 0.2, 0.3),
|
|
59
|
+
offset=(0, 0, 0.1),
|
|
60
|
+
)
|
|
61
|
+
>> n.RealizeInstances()
|
|
62
|
+
>> n.InstanceOnPoints(n.Cube(), instance=...)
|
|
63
|
+
>> instances
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
tree
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
``` mermaid
|
|
70
|
+
graph LR
|
|
71
|
+
N0("NodeGroupInput"):::default-node
|
|
72
|
+
N1("RandomValue<br/><small>(-1,-1,-1) seed:2</small>"):::converter-node
|
|
73
|
+
N2("RandomValue<br/><small>(-1,-1,-1) seed:1</small>"):::converter-node
|
|
74
|
+
N3("AlignRotationToVector<br/><small>(0,0,1)</small>"):::converter-node
|
|
75
|
+
N4("AxisAngleToRotation<br/><small>(0,0,1)</small>"):::converter-node
|
|
76
|
+
N5("InputPosition"):::input-node
|
|
77
|
+
N6("Points"):::geometry-node
|
|
78
|
+
N7("MeshCube"):::geometry-node
|
|
79
|
+
N8("RotateRotation"):::converter-node
|
|
80
|
+
N9("VectorMath<br/><small>×2</small>"):::vector-node
|
|
81
|
+
N10("InstanceOnPoints"):::geometry-node
|
|
82
|
+
N11("VectorMath<br/><small>(0,0.2,0.3)</small>"):::vector-node
|
|
83
|
+
N12("SetPosition<br/><small>+(0,0,0.1)</small>"):::geometry-node
|
|
84
|
+
N13("MeshCube"):::geometry-node
|
|
85
|
+
N14("RealizeInstances"):::geometry-node
|
|
86
|
+
N15("InstanceOnPoints"):::geometry-node
|
|
87
|
+
N16("NodeGroupOutput"):::default-node
|
|
88
|
+
N1 -->|"Value>>Rotation"| N3
|
|
89
|
+
N4 -->|"Rotation>>Rotate By"| N8
|
|
90
|
+
N3 -->|"Rotation>>Rotation"| N8
|
|
91
|
+
N2 -->|"Value>>Position"| N6
|
|
92
|
+
N0 -->|"Count>>Count"| N6
|
|
93
|
+
N7 -->|"Mesh>>Instance"| N10
|
|
94
|
+
N8 -->|"Rotation>>Rotation"| N10
|
|
95
|
+
N6 -->|"Points>>Points"| N10
|
|
96
|
+
N5 -->|"Position>>Vector"| N9
|
|
97
|
+
N9 -->|"Vector>>Vector"| N11
|
|
98
|
+
N11 -->|"Vector>>Position"| N12
|
|
99
|
+
N10 -->|"Instances>>Geometry"| N12
|
|
100
|
+
N12 -->|"Geometry>>Geometry"| N14
|
|
101
|
+
N13 -->|"Mesh>>Points"| N15
|
|
102
|
+
N14 -->|"Geometry>>Instance"| N15
|
|
103
|
+
N15 -->|"Instances>>Instances"| N16
|
|
104
|
+
|
|
105
|
+
classDef geometry-node fill:#e8f5f1,stroke:#3a7c49,stroke-width:2px
|
|
106
|
+
classDef converter-node fill:#e6f1f7,stroke:#246283,stroke-width:2px
|
|
107
|
+
classDef vector-node fill:#e9e9f5,stroke:#3C3C83,stroke-width:2px
|
|
108
|
+
classDef texture-node fill:#fef3e6,stroke:#E66800,stroke-width:2px
|
|
109
|
+
classDef shader-node fill:#fef0eb,stroke:#e67c52,stroke-width:2px
|
|
110
|
+
classDef input-node fill:#f1f8ed,stroke:#7fb069,stroke-width:2px
|
|
111
|
+
classDef output-node fill:#faf0ed,stroke:#c97659,stroke-width:2px
|
|
112
|
+
classDef default-node fill:#f0f0f0,stroke:#5a5a5a,stroke-width:2px
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+

|
|
116
|
+
|
|
117
|
+
# Design Considerations
|
|
118
|
+
|
|
119
|
+
Whenever possible, support IDE auto-complete and have useful types. We
|
|
120
|
+
should know as much ahead of time as possible if our network will
|
|
121
|
+
actually build.
|
|
122
|
+
|
|
123
|
+
- Stick as closely to Geometry Nodes naming as possible
|
|
124
|
+
- `RandomValue` creates a random value node
|
|
125
|
+
- `RandomValue.vector()` creates it set to `"VECTOR"` data type and
|
|
126
|
+
provides arguments for IDE auto-complete
|
|
127
|
+
- Inputs and outputs from a node are prefixed with `i_*` and `o_`:
|
|
128
|
+
- `AccumulateField().o_total` returns the output `Total` socket
|
|
129
|
+
- `AccumulateField().i_value` returns the input `Value` socket
|
|
130
|
+
- If inputs are subject to change depending on enums, provide separate
|
|
131
|
+
constructor methods that provide related inputs as arguments. There
|
|
132
|
+
should be no guessing involved and IDEs should provide documentation
|
|
133
|
+
for what is required:
|
|
134
|
+
- `TransformGeometry.matrix(CombineTrasnsform(translation=(0, 0, 1))`
|
|
135
|
+
- `TransformGeoemtry.components(translation=(0, 0, 1))`
|
|
136
|
+
- `TransformGeometry(translation=(0, 0, 1))`
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "nodebpy"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Build nodes in Blender with code"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
authors = [
|
|
7
|
+
{ name = "Brady Johnston", email = "brady.johnston@me.com" }
|
|
8
|
+
]
|
|
9
|
+
requires-python = ">=3.11"
|
|
10
|
+
dependencies = [
|
|
11
|
+
"arrangebpy>=0.1.0",
|
|
12
|
+
"jsondiff>=2.2.1",
|
|
13
|
+
"syrupy>=5.0.0",
|
|
14
|
+
"tree-clipper>=0.1.1",
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
[project.scripts]
|
|
18
|
+
nodebpy = "nodebpy:main"
|
|
19
|
+
|
|
20
|
+
[project.optional-dependencies]
|
|
21
|
+
bpy = [
|
|
22
|
+
"bpy>=5.0.0",
|
|
23
|
+
]
|
|
24
|
+
jupyter = [
|
|
25
|
+
"ipython>=8.0.0",
|
|
26
|
+
]
|
|
27
|
+
dev = [
|
|
28
|
+
"fake-bpy-module>=20260113",
|
|
29
|
+
"pytest>=9.0.2",
|
|
30
|
+
"pytest-cov>=7.0.0",
|
|
31
|
+
"quarto-cli>=1.8.26",
|
|
32
|
+
"quartodoc>=0.11.1",
|
|
33
|
+
"ruff>=0.14.11",
|
|
34
|
+
]
|
|
35
|
+
|
|
36
|
+
[build-system]
|
|
37
|
+
requires = ["uv_build>=0.8.15,<0.9.0"]
|
|
38
|
+
build-backend = "uv_build"
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
from . import nodes, sockets, screenshot
|
|
2
|
+
from .builder import TreeBuilder
|
|
3
|
+
from .screenshot import generate_mermaid_diagram, save_mermaid_diagram
|
|
4
|
+
|
|
5
|
+
__all__ = [
|
|
6
|
+
"nodes",
|
|
7
|
+
"sockets",
|
|
8
|
+
"screenshot",
|
|
9
|
+
"TreeBuilder",
|
|
10
|
+
"generate_mermaid_diagram",
|
|
11
|
+
"save_mermaid_diagram",
|
|
12
|
+
]
|