nodebpy 520.28.0__tar.gz → 520.30.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-520.28.0 → nodebpy-520.30.0}/PKG-INFO +1 -1
- {nodebpy-520.28.0 → nodebpy-520.30.0}/pyproject.toml +1 -4
- {nodebpy-520.28.0 → nodebpy-520.30.0}/src/nodebpy/__init__.py +2 -0
- {nodebpy-520.28.0 → nodebpy-520.30.0}/src/nodebpy/assets/_codegen.py +81 -6
- {nodebpy-520.28.0 → nodebpy-520.30.0}/src/nodebpy/builder/_utils.py +3 -2
- {nodebpy-520.28.0 → nodebpy-520.30.0}/src/nodebpy/builder/asset.py +1 -1
- {nodebpy-520.28.0 → nodebpy-520.30.0}/src/nodebpy/builder/items.py +1 -1
- {nodebpy-520.28.0 → nodebpy-520.30.0}/src/nodebpy/builder/layout.py +9 -7
- {nodebpy-520.28.0 → nodebpy-520.30.0}/src/nodebpy/builder/mixins.py +37 -66
- {nodebpy-520.28.0 → nodebpy-520.30.0}/src/nodebpy/builder/node.py +11 -5
- {nodebpy-520.28.0 → nodebpy-520.30.0}/src/nodebpy/builder/socket.py +45 -11
- {nodebpy-520.28.0 → nodebpy-520.30.0}/src/nodebpy/builder/tree.py +44 -21
- nodebpy-520.30.0/src/nodebpy/export/_floats.py +114 -0
- {nodebpy-520.28.0 → nodebpy-520.30.0}/src/nodebpy/export/codegen.py +122 -121
- {nodebpy-520.28.0 → nodebpy-520.30.0}/src/nodebpy/export/diagram.py +6 -3
- {nodebpy-520.28.0 → nodebpy-520.30.0}/src/nodebpy/export/parity.py +5 -5
- {nodebpy-520.28.0 → nodebpy-520.30.0}/src/nodebpy/export/web_render.py +3 -3
- {nodebpy-520.28.0 → nodebpy-520.30.0}/src/nodebpy/lib/nodearrange/VENDORED.md +8 -0
- {nodebpy-520.28.0 → nodebpy-520.30.0}/src/nodebpy/lib/nodearrange/arrange/graph.py +20 -4
- {nodebpy-520.28.0 → nodebpy-520.30.0}/src/nodebpy/lib/nodearrange/arrange/realize.py +5 -1
- {nodebpy-520.28.0 → nodebpy-520.30.0}/src/nodebpy/lib/nodearrange/arrange/sugiyama.py +4 -2
- {nodebpy-520.28.0 → nodebpy-520.30.0}/src/nodebpy/lib/nodearrange/config.py +4 -2
- {nodebpy-520.28.0 → nodebpy-520.30.0}/src/nodebpy/live.py +2 -0
- {nodebpy-520.28.0 → nodebpy-520.30.0}/src/nodebpy/nodes/_mixins.py +8 -5
- {nodebpy-520.28.0 → nodebpy-520.30.0}/src/nodebpy/nodes/compositor/color.py +43 -28
- {nodebpy-520.28.0 → nodebpy-520.30.0}/src/nodebpy/nodes/compositor/converter.py +128 -56
- {nodebpy-520.28.0 → nodebpy-520.30.0}/src/nodebpy/nodes/compositor/distort.py +12 -12
- {nodebpy-520.28.0 → nodebpy-520.30.0}/src/nodebpy/nodes/compositor/filter.py +34 -34
- {nodebpy-520.28.0 → nodebpy-520.30.0}/src/nodebpy/nodes/compositor/group.py +1 -1
- {nodebpy-520.28.0 → nodebpy-520.30.0}/src/nodebpy/nodes/compositor/input.py +12 -11
- {nodebpy-520.28.0 → nodebpy-520.30.0}/src/nodebpy/nodes/compositor/interface.py +6 -6
- {nodebpy-520.28.0 → nodebpy-520.30.0}/src/nodebpy/nodes/compositor/manual.py +22 -34
- {nodebpy-520.28.0 → nodebpy-520.30.0}/src/nodebpy/nodes/compositor/matte.py +16 -15
- {nodebpy-520.28.0 → nodebpy-520.30.0}/src/nodebpy/nodes/compositor/output.py +6 -6
- {nodebpy-520.28.0 → nodebpy-520.30.0}/src/nodebpy/nodes/geometry/assets.py +37 -35
- {nodebpy-520.28.0 → nodebpy-520.30.0}/src/nodebpy/nodes/geometry/attribute.py +7 -5
- {nodebpy-520.28.0 → nodebpy-520.30.0}/src/nodebpy/nodes/geometry/color.py +1 -1
- {nodebpy-520.28.0 → nodebpy-520.30.0}/src/nodebpy/nodes/geometry/converter.py +2013 -484
- {nodebpy-520.28.0 → nodebpy-520.30.0}/src/nodebpy/nodes/geometry/geometry.py +449 -339
- {nodebpy-520.28.0 → nodebpy-520.30.0}/src/nodebpy/nodes/geometry/grid.py +27 -25
- {nodebpy-520.28.0 → nodebpy-520.30.0}/src/nodebpy/nodes/geometry/group.py +1 -1
- {nodebpy-520.28.0 → nodebpy-520.30.0}/src/nodebpy/nodes/geometry/groups.py +7 -7
- {nodebpy-520.28.0 → nodebpy-520.30.0}/src/nodebpy/nodes/geometry/input.py +75 -75
- {nodebpy-520.28.0 → nodebpy-520.30.0}/src/nodebpy/nodes/geometry/interface.py +33 -33
- {nodebpy-520.28.0 → nodebpy-520.30.0}/src/nodebpy/nodes/geometry/manual.py +172 -419
- {nodebpy-520.28.0 → nodebpy-520.30.0}/src/nodebpy/nodes/geometry/output.py +18 -18
- {nodebpy-520.28.0 → nodebpy-520.30.0}/src/nodebpy/nodes/geometry/texture.py +60 -32
- {nodebpy-520.28.0 → nodebpy-520.30.0}/src/nodebpy/nodes/geometry/utilities.py +1 -1
- {nodebpy-520.28.0 → nodebpy-520.30.0}/src/nodebpy/nodes/geometry/vector.py +123 -60
- {nodebpy-520.28.0 → nodebpy-520.30.0}/src/nodebpy/nodes/geometry/zone.py +10 -7
- {nodebpy-520.28.0 → nodebpy-520.30.0}/src/nodebpy/nodes/shader/color.py +3 -3
- {nodebpy-520.28.0 → nodebpy-520.30.0}/src/nodebpy/nodes/shader/converter.py +150 -29
- {nodebpy-520.28.0 → nodebpy-520.30.0}/src/nodebpy/nodes/shader/grid.py +12 -12
- {nodebpy-520.28.0 → nodebpy-520.30.0}/src/nodebpy/nodes/shader/group.py +1 -1
- {nodebpy-520.28.0 → nodebpy-520.30.0}/src/nodebpy/nodes/shader/input.py +23 -23
- {nodebpy-520.28.0 → nodebpy-520.30.0}/src/nodebpy/nodes/shader/manual.py +7 -17
- {nodebpy-520.28.0 → nodebpy-520.30.0}/src/nodebpy/nodes/shader/output.py +15 -15
- {nodebpy-520.28.0 → nodebpy-520.30.0}/src/nodebpy/nodes/shader/script.py +22 -9
- {nodebpy-520.28.0 → nodebpy-520.30.0}/src/nodebpy/nodes/shader/shader.py +55 -49
- {nodebpy-520.28.0 → nodebpy-520.30.0}/src/nodebpy/nodes/shader/texture.py +112 -33
- {nodebpy-520.28.0 → nodebpy-520.30.0}/src/nodebpy/nodes/shader/vector.py +74 -39
- nodebpy-520.30.0/src/nodebpy/py.typed +0 -0
- {nodebpy-520.28.0 → nodebpy-520.30.0}/src/nodebpy/types.py +70 -2
- {nodebpy-520.28.0 → nodebpy-520.30.0}/README.md +0 -0
- {nodebpy-520.28.0 → nodebpy-520.30.0}/src/nodebpy/assets/__init__.py +0 -0
- {nodebpy-520.28.0 → nodebpy-520.30.0}/src/nodebpy/assets/__main__.py +0 -0
- {nodebpy-520.28.0 → nodebpy-520.30.0}/src/nodebpy/assets/_library.py +0 -0
- {nodebpy-520.28.0 → nodebpy-520.30.0}/src/nodebpy/assets/_pipeline.py +0 -0
- {nodebpy-520.28.0 → nodebpy-520.30.0}/src/nodebpy/builder/__init__.py +0 -0
- {nodebpy-520.28.0 → nodebpy-520.30.0}/src/nodebpy/builder/_registry.py +0 -0
- {nodebpy-520.28.0 → nodebpy-520.30.0}/src/nodebpy/builder/_socket_order.py +0 -0
- {nodebpy-520.28.0 → nodebpy-520.30.0}/src/nodebpy/builder/accessor.py +0 -0
- {nodebpy-520.28.0 → nodebpy-520.30.0}/src/nodebpy/export/__init__.py +0 -0
- {nodebpy-520.28.0 → nodebpy-520.30.0}/src/nodebpy/export/plot.py +0 -0
- {nodebpy-520.28.0 → nodebpy-520.30.0}/src/nodebpy/lib/nodearrange/__init__.py +0 -0
- {nodebpy-520.28.0 → nodebpy-520.30.0}/src/nodebpy/lib/nodearrange/arrange/__init__.py +0 -0
- {nodebpy-520.28.0 → nodebpy-520.30.0}/src/nodebpy/lib/nodearrange/arrange/balancing.py +0 -0
- {nodebpy-520.28.0 → nodebpy-520.30.0}/src/nodebpy/lib/nodearrange/arrange/ordering.py +0 -0
- {nodebpy-520.28.0 → nodebpy-520.30.0}/src/nodebpy/lib/nodearrange/arrange/ranking.py +0 -0
- {nodebpy-520.28.0 → nodebpy-520.30.0}/src/nodebpy/lib/nodearrange/arrange/stacking.py +0 -0
- {nodebpy-520.28.0 → nodebpy-520.30.0}/src/nodebpy/lib/nodearrange/arrange/structs.py +0 -0
- {nodebpy-520.28.0 → nodebpy-520.30.0}/src/nodebpy/lib/nodearrange/arrange/x_coords.py +0 -0
- {nodebpy-520.28.0 → nodebpy-520.30.0}/src/nodebpy/lib/nodearrange/arrange/y_coords.py +0 -0
- {nodebpy-520.28.0 → nodebpy-520.30.0}/src/nodebpy/lib/nodearrange/utils.py +0 -0
- {nodebpy-520.28.0 → nodebpy-520.30.0}/src/nodebpy/nodes/__init__.py +0 -0
- {nodebpy-520.28.0 → nodebpy-520.30.0}/src/nodebpy/nodes/compositor/__init__.py +1 -1
- {nodebpy-520.28.0 → nodebpy-520.30.0}/src/nodebpy/nodes/compositor/assets.py +0 -0
- {nodebpy-520.28.0 → nodebpy-520.30.0}/src/nodebpy/nodes/compositor/vector.py +0 -0
- {nodebpy-520.28.0 → nodebpy-520.30.0}/src/nodebpy/nodes/geometry/__init__.py +1 -1
- {nodebpy-520.28.0 → nodebpy-520.30.0}/src/nodebpy/nodes/shader/__init__.py +0 -0
- {nodebpy-520.28.0 → nodebpy-520.30.0}/src/nodebpy/nodes/shader/assets.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "nodebpy"
|
|
3
|
-
version = "520.
|
|
3
|
+
version = "520.30.0"
|
|
4
4
|
description = "Build nodes trees in Blender more elegantly with code"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
authors = [
|
|
@@ -9,9 +9,6 @@ authors = [
|
|
|
9
9
|
requires-python = ">=3.13"
|
|
10
10
|
license = { text = "GPL-3.0-or-later" }
|
|
11
11
|
|
|
12
|
-
[project.scripts]
|
|
13
|
-
nodebpy = "nodebpy:main"
|
|
14
|
-
|
|
15
12
|
[project.optional-dependencies]
|
|
16
13
|
networkx = [
|
|
17
14
|
"networkx>=3.6.1",
|
|
@@ -24,6 +24,7 @@ import bpy
|
|
|
24
24
|
from ..builder import AssetLibrary, BundledLibrary, asset_group_base
|
|
25
25
|
from ..builder._utils import normalize_name, typed_param_names
|
|
26
26
|
from ..export.codegen import GroupInterface, _fmt
|
|
27
|
+
from ..types import Default
|
|
27
28
|
|
|
28
29
|
# bl_socket_type substring → (Socket accessor class, Input* parameter type).
|
|
29
30
|
# Order matters: more specific keys (IntVector before Int) come first.
|
|
@@ -143,12 +144,29 @@ class _Socket:
|
|
|
143
144
|
attr: str # normalized accessor/param name
|
|
144
145
|
description: str = "" # interface tooltip, if the asset author set one
|
|
145
146
|
menu_items: tuple[str, ...] = () # menu sockets only: the selectable items
|
|
147
|
+
fallback: str = "" # what an unconnected input reads, for the docstring
|
|
148
|
+
modifier_attribute: str = "" # attribute a modifier input defaults to reading
|
|
146
149
|
|
|
147
150
|
@property
|
|
148
151
|
def doc(self) -> str:
|
|
149
152
|
"""Documentation line for this socket — its tooltip, else its name."""
|
|
150
153
|
return _clean_doc(self.description or self.name)
|
|
151
154
|
|
|
155
|
+
@property
|
|
156
|
+
def param_doc(self) -> str:
|
|
157
|
+
"""The parameter's documentation line: the tooltip plus, for an input
|
|
158
|
+
with a fallback, what it reads when nothing is connected, and for one
|
|
159
|
+
with a default attribute, what a modifier input reads by default."""
|
|
160
|
+
doc = self.doc
|
|
161
|
+
if self.fallback:
|
|
162
|
+
doc = f"{doc.rstrip('.')}. When unconnected: {self.fallback}"
|
|
163
|
+
if self.modifier_attribute:
|
|
164
|
+
doc = (
|
|
165
|
+
f"{doc.rstrip('.')}. As a modifier input, reads the "
|
|
166
|
+
f'"{self.modifier_attribute}" attribute by default.'
|
|
167
|
+
)
|
|
168
|
+
return doc
|
|
169
|
+
|
|
152
170
|
@property
|
|
153
171
|
def param_type(self) -> str:
|
|
154
172
|
"""Type hint for the ``__init__`` parameter.
|
|
@@ -179,13 +197,22 @@ def _collect(
|
|
|
179
197
|
sockets,
|
|
180
198
|
descriptions: dict[str, str] | None = None,
|
|
181
199
|
menus: bool = False,
|
|
200
|
+
fallbacks: dict[str, Default] | None = None,
|
|
201
|
+
modifier_attributes: dict[str, str] | None = None,
|
|
182
202
|
) -> list[_Socket]:
|
|
183
203
|
"""Introspect ``sockets`` into records.
|
|
184
204
|
|
|
185
205
|
``menus`` resolves menu sockets to their items — only worth doing for the
|
|
186
|
-
group's *inputs*, whose parameters are typed from them.
|
|
206
|
+
group's *inputs*, whose parameters are typed from them. ``fallbacks`` maps
|
|
207
|
+
an input's identifier to what it reads when unconnected (the interface's
|
|
208
|
+
``default_input`` field); such a parameter defaults to the matching
|
|
209
|
+
``Default`` member instead of a stored value. ``modifier_attributes``
|
|
210
|
+
maps an input to its default attribute name, which only a Geometry Nodes
|
|
211
|
+
modifier uses, so it is documented but does not change the default.
|
|
187
212
|
"""
|
|
188
213
|
descriptions = descriptions or {}
|
|
214
|
+
fallbacks = fallbacks or {}
|
|
215
|
+
modifier_attributes = modifier_attributes or {}
|
|
189
216
|
# Keep inactive sockets: socket-usage inference deactivates inputs that the
|
|
190
217
|
# current node options (e.g. a menu selection) leave unused, but a caller
|
|
191
218
|
# may set those options differently, so the API must expose every input.
|
|
@@ -202,21 +229,58 @@ def _collect(
|
|
|
202
229
|
attr = (
|
|
203
230
|
norm_name if name_counts[norm_name] == 1 else normalize_name(s.identifier)
|
|
204
231
|
)
|
|
232
|
+
fallback = fallbacks.get(s.identifier)
|
|
205
233
|
out.append(
|
|
206
234
|
_Socket(
|
|
207
235
|
name=s.name,
|
|
208
236
|
identifier=s.identifier,
|
|
209
237
|
socket_class=socket_class,
|
|
210
238
|
input_type=input_type,
|
|
211
|
-
default=_format_default(s),
|
|
239
|
+
default=repr(fallback) if fallback else _format_default(s),
|
|
212
240
|
attr=attr,
|
|
213
241
|
description=descriptions.get(s.identifier, ""),
|
|
214
242
|
menu_items=menu_items,
|
|
243
|
+
fallback=fallback.description if fallback else "",
|
|
244
|
+
modifier_attribute=modifier_attributes.get(s.identifier, ""),
|
|
215
245
|
)
|
|
216
246
|
)
|
|
217
247
|
return out
|
|
218
248
|
|
|
219
249
|
|
|
250
|
+
def _interface_fallbacks(group) -> dict[str, Default]:
|
|
251
|
+
"""What each group input reads when unconnected, by socket identifier.
|
|
252
|
+
|
|
253
|
+
An interface input with a ``default_input`` other than ``VALUE`` reads an
|
|
254
|
+
implicit field or context value instead of its stored default, whether
|
|
255
|
+
the group is a modifier or a node in another tree. Other inputs are left
|
|
256
|
+
out.
|
|
257
|
+
"""
|
|
258
|
+
fallbacks: dict[str, Default] = {}
|
|
259
|
+
for item in group.interface.items_tree:
|
|
260
|
+
if item.item_type != "SOCKET" or item.in_out != "INPUT":
|
|
261
|
+
continue
|
|
262
|
+
default_input = getattr(item, "default_input", "VALUE")
|
|
263
|
+
if default_input != "VALUE":
|
|
264
|
+
fallbacks[item.identifier] = Default(default_input)
|
|
265
|
+
return fallbacks
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
def _interface_modifier_attributes(group) -> dict[str, str]:
|
|
269
|
+
"""Each group input's default attribute name, by socket identifier.
|
|
270
|
+
|
|
271
|
+
Blender only applies it when the group is a Geometry Nodes modifier (the
|
|
272
|
+
input starts in attribute mode reading that name); as a node inside
|
|
273
|
+
another tree the input still uses its stored value.
|
|
274
|
+
"""
|
|
275
|
+
return {
|
|
276
|
+
item.identifier: item.default_attribute_name
|
|
277
|
+
for item in group.interface.items_tree
|
|
278
|
+
if item.item_type == "SOCKET"
|
|
279
|
+
and item.in_out == "INPUT"
|
|
280
|
+
and getattr(item, "default_attribute_name", "")
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
|
|
220
284
|
def _introspect_group(group, name: str, library_source: str) -> _AssetClass:
|
|
221
285
|
"""Introspect the node group ``group`` (a ``bpy.types.NodeTree``) into an
|
|
222
286
|
:class:`_AssetClass` record by instantiating it on a throwaway host node.
|
|
@@ -253,7 +317,13 @@ def _introspect_group(group, name: str, library_source: str) -> _AssetClass:
|
|
|
253
317
|
description=(group.description or name).strip(),
|
|
254
318
|
library_source=library_source,
|
|
255
319
|
tree_idname=group.bl_idname,
|
|
256
|
-
inputs=_collect(
|
|
320
|
+
inputs=_collect(
|
|
321
|
+
node.inputs,
|
|
322
|
+
descriptions,
|
|
323
|
+
menus=True,
|
|
324
|
+
fallbacks=_interface_fallbacks(group),
|
|
325
|
+
modifier_attributes=_interface_modifier_attributes(group),
|
|
326
|
+
),
|
|
257
327
|
outputs=_collect(node.outputs, descriptions),
|
|
258
328
|
)
|
|
259
329
|
finally:
|
|
@@ -320,7 +390,7 @@ def _class_docstring(cls: _AssetClass) -> str:
|
|
|
320
390
|
if cls.inputs:
|
|
321
391
|
lines += ["Parameters", "----------"]
|
|
322
392
|
for s in cls.inputs:
|
|
323
|
-
lines += [f"{s.attr} : {s.param_type}", f" {s.
|
|
393
|
+
lines += [f"{s.attr} : {s.param_type}", f" {s.param_doc}"]
|
|
324
394
|
lines.append("")
|
|
325
395
|
lines += ["Inputs", "------"]
|
|
326
396
|
for s in cls.inputs:
|
|
@@ -369,7 +439,7 @@ def _render_class(cls: _AssetClass, docstrings: bool = False) -> str:
|
|
|
369
439
|
@property
|
|
370
440
|
def o(self) -> _Outputs: ...
|
|
371
441
|
|
|
372
|
-
def __init__{signature}:
|
|
442
|
+
def __init__{signature}{" -> None" if signature == "(self)" else ""}:
|
|
373
443
|
super().__init__(**{{{key_args}}})
|
|
374
444
|
"""
|
|
375
445
|
|
|
@@ -428,7 +498,8 @@ def interface_parts(
|
|
|
428
498
|
args.append("**{" + ", ".join(keyed) + "}")
|
|
429
499
|
if pairs:
|
|
430
500
|
args.append("_named_links=[" + ", ".join(pairs) + "]")
|
|
431
|
-
|
|
501
|
+
init_return = " -> None" if signature == "(self)" else ""
|
|
502
|
+
init = f" def __init__{signature}{init_return}:\n super().__init__({', '.join(args)})"
|
|
432
503
|
|
|
433
504
|
type_checking = (
|
|
434
505
|
" if TYPE_CHECKING:\n"
|
|
@@ -469,6 +540,8 @@ def interface_parts(
|
|
|
469
540
|
]
|
|
470
541
|
if any("math." in s.default for s in cls.inputs):
|
|
471
542
|
import_lines.insert(0, "import math")
|
|
543
|
+
if any(s.default.startswith("Default.") for s in cls.inputs):
|
|
544
|
+
input_types.insert(0, "Default") # sorts before the Input* names
|
|
472
545
|
if input_types:
|
|
473
546
|
import_lines.append(f"from {nodebpy_pkg}.types import {', '.join(input_types)}")
|
|
474
547
|
|
|
@@ -489,6 +562,8 @@ def _render_module(
|
|
|
489
562
|
{s.socket_class for c in classes for s in c.inputs + c.outputs}
|
|
490
563
|
)
|
|
491
564
|
input_types = sorted({s.input_type for c in classes for s in c.inputs})
|
|
565
|
+
if any(s.default.startswith("Default.") for c in classes for s in c.inputs):
|
|
566
|
+
input_types.insert(0, "Default")
|
|
492
567
|
bases = sorted({asset_group_base(c.tree_idname).__name__ for c in classes})
|
|
493
568
|
libraries = sorted(
|
|
494
569
|
{
|
|
@@ -178,9 +178,10 @@ def _resolve_promotion(
|
|
|
178
178
|
|
|
179
179
|
@runtime_checkable
|
|
180
180
|
class _NodeLike(Protocol):
|
|
181
|
-
"""Protocol for objects that wrap a Blender node and expose an ``
|
|
181
|
+
"""Protocol for objects that wrap a Blender node and expose an ``o`` output accessor."""
|
|
182
182
|
|
|
183
|
-
|
|
183
|
+
@property
|
|
184
|
+
def o(self) -> Any: ... # SocketAccessor at runtime; Any avoids a circular import
|
|
184
185
|
|
|
185
186
|
|
|
186
187
|
@runtime_checkable
|
|
@@ -111,7 +111,7 @@ class _AssetGroupMixin:
|
|
|
111
111
|
return cls._build_group is not _AssetGroupMixin._build_group
|
|
112
112
|
|
|
113
113
|
@classmethod
|
|
114
|
-
def create_group(cls):
|
|
114
|
+
def create_group(cls) -> Any:
|
|
115
115
|
"""Append the named asset group from the library and return its tree,
|
|
116
116
|
reusing a previously-appended group of the same name and tree type.
|
|
117
117
|
|
|
@@ -39,7 +39,9 @@ def _is_layoutable(node: bpy.types.Node) -> bool:
|
|
|
39
39
|
|
|
40
40
|
def build_dependency_graph(
|
|
41
41
|
tree: bpy.types.NodeTree,
|
|
42
|
-
) -> tuple[
|
|
42
|
+
) -> tuple[
|
|
43
|
+
dict[bpy.types.Node, set[bpy.types.Node]], Counter[bpy.types.NodeSocket | None]
|
|
44
|
+
]:
|
|
43
45
|
"""Build a graph of node dependencies and count input connections.
|
|
44
46
|
|
|
45
47
|
Only layoutable nodes (excluding frames and reroutes) are included.
|
|
@@ -48,7 +50,7 @@ def build_dependency_graph(
|
|
|
48
50
|
dependency_graph: dict[bpy.types.Node, set[bpy.types.Node]] = {
|
|
49
51
|
node: set() for node in layoutable
|
|
50
52
|
}
|
|
51
|
-
socket_input_connection_count: Counter = Counter()
|
|
53
|
+
socket_input_connection_count: Counter[bpy.types.NodeSocket | None] = Counter()
|
|
52
54
|
|
|
53
55
|
for link in tree.links:
|
|
54
56
|
if link.from_node in layoutable and link.to_node in layoutable:
|
|
@@ -181,7 +183,7 @@ def _socket_visible(socket: bpy.types.NodeSocket) -> bool:
|
|
|
181
183
|
|
|
182
184
|
def _is_expanded_vector(
|
|
183
185
|
socket: bpy.types.NodeSocket,
|
|
184
|
-
socket_input_connection_count: Counter | None,
|
|
186
|
+
socket_input_connection_count: Counter[bpy.types.NodeSocket | None] | None,
|
|
185
187
|
) -> bool:
|
|
186
188
|
"""Whether an input draws the expanded 3-component vector widget: an
|
|
187
189
|
unlinked vector whose value is shown (``hide_value`` sockets, such as
|
|
@@ -266,7 +268,7 @@ def _interface_panels(node: bpy.types.Node) -> tuple[list[Any], dict[int, bool]]
|
|
|
266
268
|
|
|
267
269
|
def node_rows(
|
|
268
270
|
node: bpy.types.Node,
|
|
269
|
-
socket_input_connection_count: Counter | None = None,
|
|
271
|
+
socket_input_connection_count: Counter[bpy.types.NodeSocket | None] | None = None,
|
|
270
272
|
) -> list[NodeRow]:
|
|
271
273
|
"""The rows Blender draws below an expanded node's header, top to bottom.
|
|
272
274
|
|
|
@@ -376,7 +378,7 @@ def node_rows(
|
|
|
376
378
|
def _rows_from_order(
|
|
377
379
|
node: bpy.types.Node,
|
|
378
380
|
order: tuple[tuple, ...],
|
|
379
|
-
socket_input_connection_count: Counter | None,
|
|
381
|
+
socket_input_connection_count: Counter[bpy.types.NodeSocket | None] | None,
|
|
380
382
|
) -> list[NodeRow]:
|
|
381
383
|
"""Rows of a node drawn from its declaration (see ``_socket_order``)."""
|
|
382
384
|
rows: list[NodeRow] = []
|
|
@@ -584,7 +586,7 @@ def _rows_from_order(
|
|
|
584
586
|
|
|
585
587
|
def calculate_node_dimensions(
|
|
586
588
|
node: bpy.types.Node,
|
|
587
|
-
socket_input_connection_count: Counter | None = None,
|
|
589
|
+
socket_input_connection_count: Counter[bpy.types.NodeSocket | None] | None = None,
|
|
588
590
|
interface_scale: float = 1.0,
|
|
589
591
|
) -> tuple[float, float]:
|
|
590
592
|
"""Calculate the visual dimensions of a node.
|
|
@@ -744,7 +746,7 @@ def _reduce_crossings(
|
|
|
744
746
|
|
|
745
747
|
def position_nodes_in_columns(
|
|
746
748
|
columns: list[list[bpy.types.Node]],
|
|
747
|
-
connection_counts: Counter,
|
|
749
|
+
connection_counts: Counter[bpy.types.NodeSocket | None],
|
|
748
750
|
spacing: tuple[float, float] = (50, 25),
|
|
749
751
|
) -> None:
|
|
750
752
|
"""Position nodes column-by-column with the given spacing.
|
|
@@ -5,6 +5,7 @@ from typing import TYPE_CHECKING, Any, Self, TypeVar, cast, overload
|
|
|
5
5
|
|
|
6
6
|
from bpy.types import NodeLink, NodeSocket
|
|
7
7
|
|
|
8
|
+
from ..types import Default
|
|
8
9
|
from ._registry import _wrap_socket
|
|
9
10
|
from ._utils import SocketError, _resolve_promotion, _SocketLike
|
|
10
11
|
|
|
@@ -12,13 +13,13 @@ _RShiftT = TypeVar("_RShiftT")
|
|
|
12
13
|
|
|
13
14
|
if TYPE_CHECKING:
|
|
14
15
|
from ..nodes.geometry import CombineTransform
|
|
16
|
+
from ..nodes.geometry.converter import BooleanMath
|
|
15
17
|
from ..types import InputLinkable
|
|
16
18
|
from .accessor import SocketAccessor
|
|
17
19
|
from .node import BaseNode
|
|
18
20
|
from .socket import (
|
|
19
21
|
BooleanSocket,
|
|
20
22
|
FloatSocket,
|
|
21
|
-
IntegerSocket,
|
|
22
23
|
MatrixSocket,
|
|
23
24
|
Position,
|
|
24
25
|
Socket,
|
|
@@ -44,74 +45,65 @@ class OperatorMixin:
|
|
|
44
45
|
|
|
45
46
|
def _apply_math_operation(
|
|
46
47
|
self, other: Any, operation: str, reverse: bool = False
|
|
47
|
-
) ->
|
|
48
|
+
) -> Socket:
|
|
48
49
|
socket, other, reverse = _resolve_promotion(
|
|
49
50
|
self._default_output_socket,
|
|
50
51
|
other,
|
|
51
52
|
reverse,
|
|
52
53
|
)
|
|
53
|
-
return
|
|
54
|
-
"FloatSocket | VectorSocket | IntegerSocket",
|
|
55
|
-
_wrap_socket(socket)._dispatch_math(other, operation, reverse),
|
|
56
|
-
)
|
|
54
|
+
return _wrap_socket(socket)._dispatch_math(other, operation, reverse)
|
|
57
55
|
|
|
58
|
-
def __mul__(self, other: Any) ->
|
|
56
|
+
def __mul__(self, other: Any) -> Socket:
|
|
59
57
|
return self._apply_math_operation(other, "multiply")
|
|
60
58
|
|
|
61
|
-
def __rmul__(self, other: Any) ->
|
|
59
|
+
def __rmul__(self, other: Any) -> Socket:
|
|
62
60
|
return self._apply_math_operation(other, "multiply", reverse=True)
|
|
63
61
|
|
|
64
|
-
def __truediv__(self, other: Any) ->
|
|
62
|
+
def __truediv__(self, other: Any) -> Socket:
|
|
65
63
|
return self._apply_math_operation(other, "divide")
|
|
66
64
|
|
|
67
|
-
def __rtruediv__(self, other: Any) ->
|
|
65
|
+
def __rtruediv__(self, other: Any) -> Socket:
|
|
68
66
|
return self._apply_math_operation(other, "divide", reverse=True)
|
|
69
67
|
|
|
70
|
-
def __add__(self, other: Any) ->
|
|
68
|
+
def __add__(self, other: Any) -> Socket:
|
|
71
69
|
return self._apply_math_operation(other, "add")
|
|
72
70
|
|
|
73
|
-
def __radd__(self, other: Any) ->
|
|
71
|
+
def __radd__(self, other: Any) -> Socket:
|
|
74
72
|
return self._apply_math_operation(other, "add", reverse=True)
|
|
75
73
|
|
|
76
|
-
def __sub__(self, other: Any) ->
|
|
74
|
+
def __sub__(self, other: Any) -> Socket:
|
|
77
75
|
return self._apply_math_operation(other, "subtract")
|
|
78
76
|
|
|
79
|
-
def __rsub__(self, other: Any) ->
|
|
77
|
+
def __rsub__(self, other: Any) -> Socket:
|
|
80
78
|
return self._apply_math_operation(other, "subtract", reverse=True)
|
|
81
79
|
|
|
82
|
-
def __pow__(self, other: Any) ->
|
|
80
|
+
def __pow__(self, other: Any) -> Socket:
|
|
83
81
|
return self._apply_math_operation(other, "power")
|
|
84
82
|
|
|
85
|
-
def __rpow__(self, other: Any) ->
|
|
83
|
+
def __rpow__(self, other: Any) -> Socket:
|
|
86
84
|
return self._apply_math_operation(other, "power", reverse=True)
|
|
87
85
|
|
|
88
|
-
def __mod__(self, other: Any) ->
|
|
86
|
+
def __mod__(self, other: Any) -> Socket:
|
|
89
87
|
return self._apply_math_operation(other, "modulo")
|
|
90
88
|
|
|
91
|
-
def __rmod__(self, other: Any) ->
|
|
89
|
+
def __rmod__(self, other: Any) -> Socket:
|
|
92
90
|
return self._apply_math_operation(other, "modulo", reverse=True)
|
|
93
91
|
|
|
94
|
-
def __floordiv__(self, other: Any) ->
|
|
92
|
+
def __floordiv__(self, other: Any) -> Socket:
|
|
95
93
|
socket, other, reverse = _resolve_promotion(
|
|
96
94
|
self._default_output_socket,
|
|
97
95
|
other,
|
|
98
96
|
False,
|
|
99
97
|
)
|
|
100
|
-
return
|
|
101
|
-
"FloatSocket | VectorSocket | IntegerSocket",
|
|
102
|
-
_wrap_socket(socket)._dispatch_floordiv(other, reverse),
|
|
103
|
-
)
|
|
98
|
+
return _wrap_socket(socket)._dispatch_floordiv(other, reverse)
|
|
104
99
|
|
|
105
|
-
def __rfloordiv__(self, other: Any) ->
|
|
100
|
+
def __rfloordiv__(self, other: Any) -> Socket:
|
|
106
101
|
socket, other, reverse = _resolve_promotion(
|
|
107
102
|
self._default_output_socket,
|
|
108
103
|
other,
|
|
109
104
|
True,
|
|
110
105
|
)
|
|
111
|
-
return
|
|
112
|
-
"FloatSocket | VectorSocket | IntegerSocket",
|
|
113
|
-
_wrap_socket(socket)._dispatch_floordiv(other, reverse),
|
|
114
|
-
)
|
|
106
|
+
return _wrap_socket(socket)._dispatch_floordiv(other, reverse)
|
|
115
107
|
|
|
116
108
|
def __matmul__(
|
|
117
109
|
self, other: Position | CombineTransform | VectorSocket | MatrixSocket
|
|
@@ -143,36 +135,11 @@ class OperatorMixin:
|
|
|
143
135
|
|
|
144
136
|
return MultiplyMatrices(other, self).o.matrix # ty: ignore[invalid-argument-type]
|
|
145
137
|
|
|
146
|
-
def __neg__(self) ->
|
|
147
|
-
return
|
|
148
|
-
"FloatSocket | VectorSocket | IntegerSocket",
|
|
149
|
-
_wrap_socket(self._default_output_socket)._dispatch_unary("negate"),
|
|
150
|
-
)
|
|
151
|
-
|
|
152
|
-
def __abs__(self) -> FloatSocket | VectorSocket | IntegerSocket:
|
|
153
|
-
return cast(
|
|
154
|
-
"FloatSocket | VectorSocket | IntegerSocket",
|
|
155
|
-
_wrap_socket(self._default_output_socket)._dispatch_unary("absolute"),
|
|
156
|
-
)
|
|
157
|
-
|
|
158
|
-
if TYPE_CHECKING:
|
|
138
|
+
def __neg__(self) -> Socket:
|
|
139
|
+
return _wrap_socket(self._default_output_socket)._dispatch_unary("negate")
|
|
159
140
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
def __truediv__(self, other: Any) -> Self: ...
|
|
163
|
-
def __rtruediv__(self, other: Any) -> Self: ...
|
|
164
|
-
def __add__(self, other: Any) -> Self: ...
|
|
165
|
-
def __radd__(self, other: Any) -> Self: ...
|
|
166
|
-
def __sub__(self, other: Any) -> Self: ...
|
|
167
|
-
def __rsub__(self, other: Any) -> Self: ...
|
|
168
|
-
def __pow__(self, other: Any) -> Self: ...
|
|
169
|
-
def __rpow__(self, other: Any) -> Self: ...
|
|
170
|
-
def __mod__(self, other: Any) -> Self: ...
|
|
171
|
-
def __rmod__(self, other: Any) -> Self: ...
|
|
172
|
-
def __floordiv__(self, other: Any) -> Self: ...
|
|
173
|
-
def __rfloordiv__(self, other: Any) -> Self: ...
|
|
174
|
-
def __neg__(self) -> Self: ...
|
|
175
|
-
def __abs__(self) -> Self: ...
|
|
141
|
+
def __abs__(self) -> Socket:
|
|
142
|
+
return _wrap_socket(self._default_output_socket)._dispatch_unary("absolute")
|
|
176
143
|
|
|
177
144
|
def _apply_compare_operation(
|
|
178
145
|
self, other: Any, operation: str
|
|
@@ -202,36 +169,36 @@ class OperatorMixin:
|
|
|
202
169
|
def __ne__(self, other: object) -> FloatSocket | BooleanSocket: # type: ignore
|
|
203
170
|
return self._apply_compare_operation(other, "not_equal")
|
|
204
171
|
|
|
205
|
-
def _apply_boolean_operation(self, other: Any, operation: str):
|
|
172
|
+
def _apply_boolean_operation(self, other: Any, operation: str) -> BooleanMath:
|
|
206
173
|
from ..nodes.geometry.converter import BooleanMath
|
|
207
174
|
|
|
208
175
|
return getattr(BooleanMath, operation)(self, other)
|
|
209
176
|
|
|
210
|
-
def __and__(self, other: Any):
|
|
177
|
+
def __and__(self, other: Any) -> BooleanMath | Socket:
|
|
211
178
|
return self._apply_boolean_operation(other, "l_and")
|
|
212
179
|
|
|
213
|
-
def __rand__(self, other: Any):
|
|
180
|
+
def __rand__(self, other: Any) -> BooleanMath | Socket:
|
|
214
181
|
from ..nodes.geometry.converter import BooleanMath
|
|
215
182
|
|
|
216
183
|
return BooleanMath.l_and(other, cast(Any, self))
|
|
217
184
|
|
|
218
|
-
def __or__(self, other: Any):
|
|
185
|
+
def __or__(self, other: Any) -> BooleanMath | Socket:
|
|
219
186
|
return self._apply_boolean_operation(other, "l_or")
|
|
220
187
|
|
|
221
|
-
def __ror__(self, other: Any):
|
|
188
|
+
def __ror__(self, other: Any) -> BooleanMath | Socket:
|
|
222
189
|
from ..nodes.geometry.converter import BooleanMath
|
|
223
190
|
|
|
224
191
|
return BooleanMath.l_or(other, cast(Any, self))
|
|
225
192
|
|
|
226
|
-
def __xor__(self, other: Any):
|
|
193
|
+
def __xor__(self, other: Any) -> BooleanMath | Socket:
|
|
227
194
|
return self._apply_boolean_operation(other, "not_equal")
|
|
228
195
|
|
|
229
|
-
def __rxor__(self, other: Any):
|
|
196
|
+
def __rxor__(self, other: Any) -> BooleanMath | Socket:
|
|
230
197
|
from ..nodes.geometry.converter import BooleanMath
|
|
231
198
|
|
|
232
199
|
return BooleanMath.not_equal(other, cast(Any, self))
|
|
233
200
|
|
|
234
|
-
def __invert__(self):
|
|
201
|
+
def __invert__(self) -> BooleanMath | Socket:
|
|
235
202
|
from ..nodes.geometry.converter import BooleanMath
|
|
236
203
|
|
|
237
204
|
return BooleanMath.l_not(cast(Any, self))
|
|
@@ -244,7 +211,7 @@ class LinkingMixin:
|
|
|
244
211
|
and ``_default_input_socket`` on the concrete class.
|
|
245
212
|
"""
|
|
246
213
|
|
|
247
|
-
tree: TreeBuilder
|
|
214
|
+
tree: TreeBuilder[Any]
|
|
248
215
|
|
|
249
216
|
if TYPE_CHECKING:
|
|
250
217
|
import bpy
|
|
@@ -262,6 +229,8 @@ class LinkingMixin:
|
|
|
262
229
|
|
|
263
230
|
def _source_socket(self, node: InputLinkable | Socket | NodeSocket) -> NodeSocket:
|
|
264
231
|
assert node is not None
|
|
232
|
+
if isinstance(node, Default):
|
|
233
|
+
raise TypeError(f"{node} marks an input's fallback, it cannot be linked")
|
|
265
234
|
if isinstance(node, NodeSocket):
|
|
266
235
|
return node
|
|
267
236
|
elif hasattr(node, "_default_output_socket"):
|
|
@@ -271,6 +240,8 @@ class LinkingMixin:
|
|
|
271
240
|
|
|
272
241
|
def _target_socket(self, node: InputLinkable | Socket | NodeSocket) -> NodeSocket:
|
|
273
242
|
assert node is not None
|
|
243
|
+
if isinstance(node, Default):
|
|
244
|
+
raise TypeError(f"{node} marks an input's fallback, it cannot be linked")
|
|
274
245
|
if isinstance(node, NodeSocket):
|
|
275
246
|
return node
|
|
276
247
|
elif hasattr(node, "_default_input_socket"):
|
|
@@ -25,7 +25,7 @@ from bpy.types import (
|
|
|
25
25
|
ShaderNodeTree,
|
|
26
26
|
)
|
|
27
27
|
|
|
28
|
-
from ..types import SOCKET_COMPATIBILITY, InputAny
|
|
28
|
+
from ..types import SOCKET_COMPATIBILITY, Default, InputAny
|
|
29
29
|
from ._utils import SocketError, _NodeLike, _SocketLike
|
|
30
30
|
from .accessor import SocketAccessor
|
|
31
31
|
from .mixins import LinkingMixin, OperatorMixin
|
|
@@ -91,7 +91,8 @@ class BaseNode(_NodeLike, OperatorMixin, LinkingMixin):
|
|
|
91
91
|
"""Base class for all node wrappers."""
|
|
92
92
|
|
|
93
93
|
_bl_idname: str
|
|
94
|
-
_tree: TreeBuilder
|
|
94
|
+
_tree: TreeBuilder[Any]
|
|
95
|
+
node: Node
|
|
95
96
|
_default_input_id: str | None = None
|
|
96
97
|
_default_output_id: str | None = None
|
|
97
98
|
_placeholder_inputs: list[str]
|
|
@@ -113,7 +114,7 @@ class BaseNode(_NodeLike, OperatorMixin, LinkingMixin):
|
|
|
113
114
|
self.node = node if node else self._tree.add(self.__class__._bl_idname)
|
|
114
115
|
|
|
115
116
|
@property
|
|
116
|
-
def tree(self) -> TreeBuilder:
|
|
117
|
+
def tree(self) -> TreeBuilder[Any]:
|
|
117
118
|
"""The `TreeBuilder` instance this node belongs to and is being built within."""
|
|
118
119
|
return self._tree
|
|
119
120
|
|
|
@@ -207,6 +208,11 @@ class BaseNode(_NodeLike, OperatorMixin, LinkingMixin):
|
|
|
207
208
|
lets callers address one of several same-named sockets unambiguously.
|
|
208
209
|
"""
|
|
209
210
|
named = isinstance(target, str)
|
|
211
|
+
# A ``Default`` member stands for the socket's own fallback (an implicit
|
|
212
|
+
# field or context value Blender reads when nothing is linked): nothing to
|
|
213
|
+
# set or link, the same as ``None``.
|
|
214
|
+
if isinstance(value, Default):
|
|
215
|
+
return
|
|
210
216
|
# TODO: don't like these manual overrides for particular nodes, but best I can do for now
|
|
211
217
|
if value is None or (
|
|
212
218
|
named
|
|
@@ -231,7 +237,7 @@ class BaseNode(_NodeLike, OperatorMixin, LinkingMixin):
|
|
|
231
237
|
self._link_from(value, target)
|
|
232
238
|
elif isinstance(value, _NodeLike):
|
|
233
239
|
target_type = target.type if not named else self.i._get(target).type
|
|
234
|
-
self._link_from(value.o._best_match(target_type), target)
|
|
240
|
+
self._link_from(value.o._best_match(target_type), target)
|
|
235
241
|
else:
|
|
236
242
|
# TODO: explicitly skipping the sockets for BooleanMath as they are default false,
|
|
237
243
|
# but this needs to be a more generic solution for sockets which aren't available
|
|
@@ -422,7 +428,7 @@ class NodeGroupBuilder[T: bpy.types.NodeTree](BaseNode, ABC):
|
|
|
422
428
|
# fresh tree's defaults belong here; codegen fills it when exporting.
|
|
423
429
|
_tree_properties: ClassVar[dict[str, Any]] = {}
|
|
424
430
|
|
|
425
|
-
def __init__(self, **kwargs):
|
|
431
|
+
def __init__(self, **kwargs: Any):
|
|
426
432
|
super().__init__()
|
|
427
433
|
self._setup_node_group()
|
|
428
434
|
self.node.show_options = False
|