nodebpy 520.21.0__tar.gz → 520.23.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.21.0 → nodebpy-520.23.0}/PKG-INFO +1 -1
- {nodebpy-520.21.0 → nodebpy-520.23.0}/pyproject.toml +1 -1
- {nodebpy-520.21.0 → nodebpy-520.23.0}/src/nodebpy/__init__.py +2 -0
- {nodebpy-520.21.0 → nodebpy-520.23.0}/src/nodebpy/assets/_library.py +24 -4
- {nodebpy-520.21.0 → nodebpy-520.23.0}/src/nodebpy/assets/_pipeline.py +5 -0
- {nodebpy-520.21.0 → nodebpy-520.23.0}/src/nodebpy/builder/__init__.py +2 -0
- {nodebpy-520.21.0 → nodebpy-520.23.0}/src/nodebpy/builder/layout.py +26 -0
- {nodebpy-520.21.0 → nodebpy-520.23.0}/src/nodebpy/builder/tree.py +49 -17
- {nodebpy-520.21.0 → nodebpy-520.23.0}/src/nodebpy/export/codegen.py +14 -3
- {nodebpy-520.21.0 → nodebpy-520.23.0}/README.md +0 -0
- {nodebpy-520.21.0 → nodebpy-520.23.0}/src/nodebpy/assets/__init__.py +0 -0
- {nodebpy-520.21.0 → nodebpy-520.23.0}/src/nodebpy/assets/__main__.py +0 -0
- {nodebpy-520.21.0 → nodebpy-520.23.0}/src/nodebpy/assets/_codegen.py +0 -0
- {nodebpy-520.21.0 → nodebpy-520.23.0}/src/nodebpy/builder/_registry.py +0 -0
- {nodebpy-520.21.0 → nodebpy-520.23.0}/src/nodebpy/builder/_utils.py +0 -0
- {nodebpy-520.21.0 → nodebpy-520.23.0}/src/nodebpy/builder/accessor.py +0 -0
- {nodebpy-520.21.0 → nodebpy-520.23.0}/src/nodebpy/builder/asset.py +0 -0
- {nodebpy-520.21.0 → nodebpy-520.23.0}/src/nodebpy/builder/items.py +0 -0
- {nodebpy-520.21.0 → nodebpy-520.23.0}/src/nodebpy/builder/mixins.py +0 -0
- {nodebpy-520.21.0 → nodebpy-520.23.0}/src/nodebpy/builder/node.py +0 -0
- {nodebpy-520.21.0 → nodebpy-520.23.0}/src/nodebpy/builder/socket.py +0 -0
- {nodebpy-520.21.0 → nodebpy-520.23.0}/src/nodebpy/export/__init__.py +0 -0
- {nodebpy-520.21.0 → nodebpy-520.23.0}/src/nodebpy/export/diagram.py +0 -0
- {nodebpy-520.21.0 → nodebpy-520.23.0}/src/nodebpy/export/parity.py +0 -0
- {nodebpy-520.21.0 → nodebpy-520.23.0}/src/nodebpy/export/plot.py +0 -0
- {nodebpy-520.21.0 → nodebpy-520.23.0}/src/nodebpy/export/web_render.py +0 -0
- {nodebpy-520.21.0 → nodebpy-520.23.0}/src/nodebpy/lib/nodearrange/VENDORED.md +0 -0
- {nodebpy-520.21.0 → nodebpy-520.23.0}/src/nodebpy/lib/nodearrange/__init__.py +0 -0
- {nodebpy-520.21.0 → nodebpy-520.23.0}/src/nodebpy/lib/nodearrange/arrange/__init__.py +0 -0
- {nodebpy-520.21.0 → nodebpy-520.23.0}/src/nodebpy/lib/nodearrange/arrange/graph.py +0 -0
- {nodebpy-520.21.0 → nodebpy-520.23.0}/src/nodebpy/lib/nodearrange/arrange/ordering.py +0 -0
- {nodebpy-520.21.0 → nodebpy-520.23.0}/src/nodebpy/lib/nodearrange/arrange/ranking.py +0 -0
- {nodebpy-520.21.0 → nodebpy-520.23.0}/src/nodebpy/lib/nodearrange/arrange/realize.py +0 -0
- {nodebpy-520.21.0 → nodebpy-520.23.0}/src/nodebpy/lib/nodearrange/arrange/stacking.py +0 -0
- {nodebpy-520.21.0 → nodebpy-520.23.0}/src/nodebpy/lib/nodearrange/arrange/structs.py +0 -0
- {nodebpy-520.21.0 → nodebpy-520.23.0}/src/nodebpy/lib/nodearrange/arrange/sugiyama.py +0 -0
- {nodebpy-520.21.0 → nodebpy-520.23.0}/src/nodebpy/lib/nodearrange/arrange/x_coords.py +0 -0
- {nodebpy-520.21.0 → nodebpy-520.23.0}/src/nodebpy/lib/nodearrange/arrange/y_coords.py +0 -0
- {nodebpy-520.21.0 → nodebpy-520.23.0}/src/nodebpy/lib/nodearrange/config.py +0 -0
- {nodebpy-520.21.0 → nodebpy-520.23.0}/src/nodebpy/lib/nodearrange/utils.py +0 -0
- {nodebpy-520.21.0 → nodebpy-520.23.0}/src/nodebpy/nodes/__init__.py +0 -0
- {nodebpy-520.21.0 → nodebpy-520.23.0}/src/nodebpy/nodes/_mixins.py +0 -0
- {nodebpy-520.21.0 → nodebpy-520.23.0}/src/nodebpy/nodes/compositor/__init__.py +0 -0
- {nodebpy-520.21.0 → nodebpy-520.23.0}/src/nodebpy/nodes/compositor/assets.py +0 -0
- {nodebpy-520.21.0 → nodebpy-520.23.0}/src/nodebpy/nodes/compositor/color.py +0 -0
- {nodebpy-520.21.0 → nodebpy-520.23.0}/src/nodebpy/nodes/compositor/converter.py +0 -0
- {nodebpy-520.21.0 → nodebpy-520.23.0}/src/nodebpy/nodes/compositor/distort.py +0 -0
- {nodebpy-520.21.0 → nodebpy-520.23.0}/src/nodebpy/nodes/compositor/filter.py +0 -0
- {nodebpy-520.21.0 → nodebpy-520.23.0}/src/nodebpy/nodes/compositor/group.py +0 -0
- {nodebpy-520.21.0 → nodebpy-520.23.0}/src/nodebpy/nodes/compositor/input.py +0 -0
- {nodebpy-520.21.0 → nodebpy-520.23.0}/src/nodebpy/nodes/compositor/interface.py +0 -0
- {nodebpy-520.21.0 → nodebpy-520.23.0}/src/nodebpy/nodes/compositor/manual.py +0 -0
- {nodebpy-520.21.0 → nodebpy-520.23.0}/src/nodebpy/nodes/compositor/matte.py +0 -0
- {nodebpy-520.21.0 → nodebpy-520.23.0}/src/nodebpy/nodes/compositor/output.py +0 -0
- {nodebpy-520.21.0 → nodebpy-520.23.0}/src/nodebpy/nodes/compositor/vector.py +0 -0
- {nodebpy-520.21.0 → nodebpy-520.23.0}/src/nodebpy/nodes/geometry/__init__.py +0 -0
- {nodebpy-520.21.0 → nodebpy-520.23.0}/src/nodebpy/nodes/geometry/assets.py +0 -0
- {nodebpy-520.21.0 → nodebpy-520.23.0}/src/nodebpy/nodes/geometry/attribute.py +0 -0
- {nodebpy-520.21.0 → nodebpy-520.23.0}/src/nodebpy/nodes/geometry/color.py +0 -0
- {nodebpy-520.21.0 → nodebpy-520.23.0}/src/nodebpy/nodes/geometry/converter.py +0 -0
- {nodebpy-520.21.0 → nodebpy-520.23.0}/src/nodebpy/nodes/geometry/geometry.py +0 -0
- {nodebpy-520.21.0 → nodebpy-520.23.0}/src/nodebpy/nodes/geometry/grid.py +0 -0
- {nodebpy-520.21.0 → nodebpy-520.23.0}/src/nodebpy/nodes/geometry/group.py +0 -0
- {nodebpy-520.21.0 → nodebpy-520.23.0}/src/nodebpy/nodes/geometry/groups.py +0 -0
- {nodebpy-520.21.0 → nodebpy-520.23.0}/src/nodebpy/nodes/geometry/input.py +0 -0
- {nodebpy-520.21.0 → nodebpy-520.23.0}/src/nodebpy/nodes/geometry/interface.py +0 -0
- {nodebpy-520.21.0 → nodebpy-520.23.0}/src/nodebpy/nodes/geometry/manual.py +0 -0
- {nodebpy-520.21.0 → nodebpy-520.23.0}/src/nodebpy/nodes/geometry/output.py +0 -0
- {nodebpy-520.21.0 → nodebpy-520.23.0}/src/nodebpy/nodes/geometry/texture.py +0 -0
- {nodebpy-520.21.0 → nodebpy-520.23.0}/src/nodebpy/nodes/geometry/utilities.py +0 -0
- {nodebpy-520.21.0 → nodebpy-520.23.0}/src/nodebpy/nodes/geometry/vector.py +0 -0
- {nodebpy-520.21.0 → nodebpy-520.23.0}/src/nodebpy/nodes/geometry/zone.py +0 -0
- {nodebpy-520.21.0 → nodebpy-520.23.0}/src/nodebpy/nodes/shader/__init__.py +0 -0
- {nodebpy-520.21.0 → nodebpy-520.23.0}/src/nodebpy/nodes/shader/assets.py +0 -0
- {nodebpy-520.21.0 → nodebpy-520.23.0}/src/nodebpy/nodes/shader/color.py +0 -0
- {nodebpy-520.21.0 → nodebpy-520.23.0}/src/nodebpy/nodes/shader/converter.py +0 -0
- {nodebpy-520.21.0 → nodebpy-520.23.0}/src/nodebpy/nodes/shader/grid.py +0 -0
- {nodebpy-520.21.0 → nodebpy-520.23.0}/src/nodebpy/nodes/shader/group.py +0 -0
- {nodebpy-520.21.0 → nodebpy-520.23.0}/src/nodebpy/nodes/shader/input.py +0 -0
- {nodebpy-520.21.0 → nodebpy-520.23.0}/src/nodebpy/nodes/shader/manual.py +0 -0
- {nodebpy-520.21.0 → nodebpy-520.23.0}/src/nodebpy/nodes/shader/output.py +0 -0
- {nodebpy-520.21.0 → nodebpy-520.23.0}/src/nodebpy/nodes/shader/script.py +0 -0
- {nodebpy-520.21.0 → nodebpy-520.23.0}/src/nodebpy/nodes/shader/shader.py +0 -0
- {nodebpy-520.21.0 → nodebpy-520.23.0}/src/nodebpy/nodes/shader/texture.py +0 -0
- {nodebpy-520.21.0 → nodebpy-520.23.0}/src/nodebpy/nodes/shader/vector.py +0 -0
- {nodebpy-520.21.0 → nodebpy-520.23.0}/src/nodebpy/types.py +0 -0
|
@@ -5,6 +5,7 @@ from .builder import (
|
|
|
5
5
|
SugiyamaOptions,
|
|
6
6
|
TreeBuilder,
|
|
7
7
|
arrange,
|
|
8
|
+
default_split_inputs,
|
|
8
9
|
default_sugiyama_options,
|
|
9
10
|
)
|
|
10
11
|
from .nodes import compositor, geometry, shader
|
|
@@ -17,6 +18,7 @@ __all__ = [
|
|
|
17
18
|
"arrange",
|
|
18
19
|
"builder",
|
|
19
20
|
"compositor",
|
|
21
|
+
"default_split_inputs",
|
|
20
22
|
"default_sugiyama_options",
|
|
21
23
|
"export",
|
|
22
24
|
"geometry",
|
|
@@ -88,6 +88,7 @@ from ..builder import (
|
|
|
88
88
|
SugiyamaOptions,
|
|
89
89
|
TreeBuilder,
|
|
90
90
|
build_from_source,
|
|
91
|
+
default_split_inputs,
|
|
91
92
|
default_sugiyama_options,
|
|
92
93
|
)
|
|
93
94
|
from ..builder._utils import normalize_name
|
|
@@ -1062,6 +1063,7 @@ def build_library(
|
|
|
1062
1063
|
on_missing: str = "error",
|
|
1063
1064
|
add_reroutes: bool = False,
|
|
1064
1065
|
arrange: SugiyamaOptions | None = None,
|
|
1066
|
+
split_inputs: bool = False,
|
|
1065
1067
|
) -> list[str]:
|
|
1066
1068
|
"""Rebuild a ``.blend`` asset library from sources written by
|
|
1067
1069
|
:func:`dump_library`.
|
|
@@ -1094,9 +1096,14 @@ def build_library(
|
|
|
1094
1096
|
:func:`nodebpy.builder.default_sugiyama_options`. ``add_reroutes=True``
|
|
1095
1097
|
additionally inserts reroute nodes to route long links around nodes
|
|
1096
1098
|
(the node-arrange addon's behaviour); it composes with ``arrange``.
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1099
|
+
``split_inputs=True`` gives each consumer node its own Group Input
|
|
1100
|
+
instance — named and labelled after the interface sockets it carries,
|
|
1101
|
+
with unused sockets hidden — instead of a single Group Input trailing
|
|
1102
|
+
long noodles (scoped via
|
|
1103
|
+
:func:`nodebpy.builder.default_split_inputs`). All three only affect
|
|
1104
|
+
modules that leave the arrangement at its default — sources dumped with
|
|
1105
|
+
``snapshot_positions`` disable arrangement and keep their authored
|
|
1106
|
+
layout, authored Group Input splits included.
|
|
1100
1107
|
|
|
1101
1108
|
The built trees stay in the current session afterwards. Because
|
|
1102
1109
|
``create_group()`` reuses an existing tree of the same name (that is what
|
|
@@ -1193,7 +1200,8 @@ def build_library(
|
|
|
1193
1200
|
arrange_override = (
|
|
1194
1201
|
default_sugiyama_options(options) if options is not None else nullcontext()
|
|
1195
1202
|
)
|
|
1196
|
-
|
|
1203
|
+
split_override = default_split_inputs() if split_inputs else nullcontext()
|
|
1204
|
+
with arrange_override, split_override:
|
|
1197
1205
|
# Materials first: asset trees look them up by name while building.
|
|
1198
1206
|
built_materials = []
|
|
1199
1207
|
for file, module in material_modules:
|
|
@@ -1521,6 +1529,17 @@ def _add_build_flags(parser) -> None: # pragma: no cover
|
|
|
1521
1529
|
"defaults apply where unset). Sources dumped with "
|
|
1522
1530
|
"--snapshot-positions keep their authored layout regardless.",
|
|
1523
1531
|
)
|
|
1532
|
+
parser.add_argument(
|
|
1533
|
+
"--split-inputs",
|
|
1534
|
+
action="store_true",
|
|
1535
|
+
help=(
|
|
1536
|
+
"Give each consumer node its own Group Input instance, named "
|
|
1537
|
+
"after the interface sockets it uses (unused sockets hidden), "
|
|
1538
|
+
"instead of one Group Input trailing long noodles. Sources "
|
|
1539
|
+
"dumped with --snapshot-positions keep their authored layout "
|
|
1540
|
+
"regardless."
|
|
1541
|
+
),
|
|
1542
|
+
)
|
|
1524
1543
|
parser.add_argument(
|
|
1525
1544
|
"--drop-missing",
|
|
1526
1545
|
dest="on_missing",
|
|
@@ -1784,6 +1803,7 @@ def _build_command(args) -> None:
|
|
|
1784
1803
|
on_missing=args.on_missing,
|
|
1785
1804
|
add_reroutes=args.add_reroutes,
|
|
1786
1805
|
arrange=_arrange_options_from_args(args),
|
|
1806
|
+
split_inputs=args.split_inputs,
|
|
1787
1807
|
)
|
|
1788
1808
|
print(f"Built {args.blend} with {len(names)} assets: {', '.join(names)}")
|
|
1789
1809
|
write_stamp(args.blend, args.source, args.resources, stamp_options(args))
|
|
@@ -88,6 +88,7 @@ _CONFIG_KEYS: dict[str, tuple[str | None, str]] = {
|
|
|
88
88
|
"no-compress": ("compress", "off"),
|
|
89
89
|
"drop-missing": ("on_missing", "drop"),
|
|
90
90
|
"add-reroutes": ("add_reroutes", "flag"),
|
|
91
|
+
"split-inputs": ("split_inputs", "flag"),
|
|
91
92
|
"spacing": ("spacing", "value"),
|
|
92
93
|
"iterations": ("iterations", "value"),
|
|
93
94
|
"direction": ("direction", "value"),
|
|
@@ -122,6 +123,7 @@ _OPTION_DEFAULTS: dict[str, object] = {
|
|
|
122
123
|
"compress": True,
|
|
123
124
|
"on_missing": "error",
|
|
124
125
|
"add_reroutes": False,
|
|
126
|
+
"split_inputs": False,
|
|
125
127
|
"spacing": None,
|
|
126
128
|
"iterations": None,
|
|
127
129
|
"direction": None,
|
|
@@ -150,6 +152,7 @@ _STAMP_DESTS = (
|
|
|
150
152
|
"snapshot_positions",
|
|
151
153
|
"socket_alignment",
|
|
152
154
|
"spacing",
|
|
155
|
+
"split_inputs",
|
|
153
156
|
"stack_collapsed",
|
|
154
157
|
"stack_margin_y_fac",
|
|
155
158
|
"typed_api",
|
|
@@ -345,6 +348,8 @@ def _build_stage_flags(args: Namespace) -> list[str]:
|
|
|
345
348
|
flags.append("--no-compress")
|
|
346
349
|
if args.add_reroutes:
|
|
347
350
|
flags.append("--add-reroutes")
|
|
351
|
+
if args.split_inputs:
|
|
352
|
+
flags.append("--split-inputs")
|
|
348
353
|
if args.spacing is not None:
|
|
349
354
|
flags += ["--spacing", *(str(value) for value in args.spacing)]
|
|
350
355
|
if args.iterations is not None:
|
|
@@ -17,6 +17,7 @@ from .layout import (
|
|
|
17
17
|
SugiyamaOptions,
|
|
18
18
|
arrange,
|
|
19
19
|
arrange_tree,
|
|
20
|
+
default_split_inputs,
|
|
20
21
|
default_sugiyama_options,
|
|
21
22
|
)
|
|
22
23
|
from .mixins import LinkingMixin, OperatorMixin
|
|
@@ -160,6 +161,7 @@ __all__ = [
|
|
|
160
161
|
"arrange_tree",
|
|
161
162
|
"asset_group_base",
|
|
162
163
|
"build_from_source",
|
|
164
|
+
"default_split_inputs",
|
|
163
165
|
"default_sugiyama_options",
|
|
164
166
|
"denormalize_name",
|
|
165
167
|
"normalize_name",
|
|
@@ -512,6 +512,32 @@ def default_sugiyama_options(options: SugiyamaOptions) -> Iterator[None]:
|
|
|
512
512
|
_DEFAULT_SUGIYAMA.reset(token)
|
|
513
513
|
|
|
514
514
|
|
|
515
|
+
# What TreeBuilder's split_inputs=None resolves to. Overridable per scope so
|
|
516
|
+
# a batch build can split the Group Input of trees whose recipes leave
|
|
517
|
+
# TreeBuilder at its default — see :func:`default_split_inputs`.
|
|
518
|
+
_DEFAULT_SPLIT_INPUTS: ContextVar[bool] = ContextVar(
|
|
519
|
+
"nodebpy_default_split_inputs", default=False
|
|
520
|
+
)
|
|
521
|
+
|
|
522
|
+
|
|
523
|
+
@contextmanager
|
|
524
|
+
def default_split_inputs(split: bool = True) -> Iterator[None]:
|
|
525
|
+
"""Scope in which every ``TreeBuilder`` left at its default
|
|
526
|
+
``split_inputs`` splits the Group Input node into one instance per
|
|
527
|
+
consumer node (with unused sockets hidden) on context exit.
|
|
528
|
+
|
|
529
|
+
An explicit ``split_inputs=True/False`` is unaffected, and so are trees
|
|
530
|
+
that disable auto-arrangement (as ``snapshot_positions`` dumps do) —
|
|
531
|
+
their authored layout, including any authored Group Input splits, must
|
|
532
|
+
survive untouched.
|
|
533
|
+
"""
|
|
534
|
+
token = _DEFAULT_SPLIT_INPUTS.set(split)
|
|
535
|
+
try:
|
|
536
|
+
yield
|
|
537
|
+
finally:
|
|
538
|
+
_DEFAULT_SPLIT_INPUTS.reset(token)
|
|
539
|
+
|
|
540
|
+
|
|
515
541
|
def _arrange_sugiyama(tree: bpy.types.NodeTree, options: SugiyamaOptions) -> None:
|
|
516
542
|
from mathutils import Vector
|
|
517
543
|
|
|
@@ -31,7 +31,7 @@ from ._utils import (
|
|
|
31
31
|
resolve_socket_key,
|
|
32
32
|
socket_key,
|
|
33
33
|
)
|
|
34
|
-
from .layout import ArrangeMethod
|
|
34
|
+
from .layout import _DEFAULT_SPLIT_INPUTS, ArrangeMethod
|
|
35
35
|
from .layout import arrange as _arrange_nodes
|
|
36
36
|
from .socket import (
|
|
37
37
|
BooleanSocket,
|
|
@@ -1106,7 +1106,7 @@ class TreeBuilder[TreeT: NodeTree]:
|
|
|
1106
1106
|
arrange: ArrangeMethod = "sugiyama",
|
|
1107
1107
|
fake_user: bool = False,
|
|
1108
1108
|
ignore_visibility: bool = False,
|
|
1109
|
-
split_inputs: bool =
|
|
1109
|
+
split_inputs: bool | None = None,
|
|
1110
1110
|
):
|
|
1111
1111
|
if isinstance(tree, str):
|
|
1112
1112
|
self.tree = bpy.data.node_groups.new(tree, tree_type) # ty: ignore[invalid-assignment]
|
|
@@ -1131,7 +1131,7 @@ class TreeBuilder[TreeT: NodeTree]:
|
|
|
1131
1131
|
collapse: bool = False,
|
|
1132
1132
|
arrange: ArrangeMethod = "sugiyama",
|
|
1133
1133
|
fake_user: bool = False,
|
|
1134
|
-
split_inputs: bool =
|
|
1134
|
+
split_inputs: bool | None = None,
|
|
1135
1135
|
) -> TreeBuilder[GeometryNodeTree]:
|
|
1136
1136
|
"""Create a geometry node tree."""
|
|
1137
1137
|
return cast(
|
|
@@ -1154,7 +1154,7 @@ class TreeBuilder[TreeT: NodeTree]:
|
|
|
1154
1154
|
collapse: bool = False,
|
|
1155
1155
|
arrange: ArrangeMethod = "sugiyama",
|
|
1156
1156
|
fake_user: bool = False,
|
|
1157
|
-
split_inputs: bool =
|
|
1157
|
+
split_inputs: bool | None = None,
|
|
1158
1158
|
) -> TreeBuilder[ShaderNodeTree]:
|
|
1159
1159
|
"""Create a shader node tree."""
|
|
1160
1160
|
return cast(
|
|
@@ -1177,7 +1177,7 @@ class TreeBuilder[TreeT: NodeTree]:
|
|
|
1177
1177
|
collapse: bool = False,
|
|
1178
1178
|
arrange: ArrangeMethod = "sugiyama",
|
|
1179
1179
|
fake_user: bool = False,
|
|
1180
|
-
split_inputs: bool =
|
|
1180
|
+
split_inputs: bool | None = None,
|
|
1181
1181
|
) -> TreeBuilder[CompositorNodeTree]:
|
|
1182
1182
|
"""Create a compositor node tree."""
|
|
1183
1183
|
return cast(
|
|
@@ -1268,7 +1268,14 @@ class TreeBuilder[TreeT: NodeTree]:
|
|
|
1268
1268
|
def __exit__(self, *args):
|
|
1269
1269
|
# Split before auto-layout, so the created instances get arranged
|
|
1270
1270
|
# next to their consumers.
|
|
1271
|
-
|
|
1271
|
+
split = self._split_inputs
|
|
1272
|
+
if split is None:
|
|
1273
|
+
# The ambient default (see default_split_inputs) only reaches
|
|
1274
|
+
# auto-arranged trees: snapshot-positions dumps disable
|
|
1275
|
+
# arrangement and their authored layout — including any authored
|
|
1276
|
+
# Group Input splits — must survive untouched.
|
|
1277
|
+
split = self._arrange is not None and _DEFAULT_SPLIT_INPUTS.get()
|
|
1278
|
+
if split:
|
|
1272
1279
|
self.split_group_inputs()
|
|
1273
1280
|
if self._arrange is not None:
|
|
1274
1281
|
self.arrange()
|
|
@@ -1425,10 +1432,11 @@ class TreeBuilder[TreeT: NodeTree]:
|
|
|
1425
1432
|
@property
|
|
1426
1433
|
def group_input_splits(self) -> list[dict]:
|
|
1427
1434
|
"""The extra Group Input instances beyond the primary one, each as
|
|
1428
|
-
``{"name": ..., "links": [(interface input name,
|
|
1429
|
-
consumer socket key), ...], "location": ...,
|
|
1430
|
-
editor convention of several input nodes near
|
|
1431
|
-
instead of one node trailing long noodles. See the
|
|
1435
|
+
``{"name": ..., "label": ..., "links": [(interface input name,
|
|
1436
|
+
consumer node name, consumer socket key), ...], "location": ...,
|
|
1437
|
+
"parent": ...}`` — the editor convention of several input nodes near
|
|
1438
|
+
their consumers instead of one node trailing long noodles. See the
|
|
1439
|
+
setter."""
|
|
1432
1440
|
splits: list[dict] = []
|
|
1433
1441
|
for node in self.tree.nodes:
|
|
1434
1442
|
if node.bl_idname != "NodeGroupInput" or node.name == "Group Input":
|
|
@@ -1442,6 +1450,7 @@ class TreeBuilder[TreeT: NodeTree]:
|
|
|
1442
1450
|
splits.append(
|
|
1443
1451
|
{
|
|
1444
1452
|
"name": node.name,
|
|
1453
|
+
"label": node.label,
|
|
1445
1454
|
"links": links,
|
|
1446
1455
|
"location": (node.location.x, node.location.y),
|
|
1447
1456
|
"parent": node.parent.name if node.parent is not None else None,
|
|
@@ -1469,6 +1478,8 @@ class TreeBuilder[TreeT: NodeTree]:
|
|
|
1469
1478
|
instance = self.tree.nodes.new("NodeGroupInput")
|
|
1470
1479
|
assert instance is not None
|
|
1471
1480
|
instance.name = split["name"]
|
|
1481
|
+
if label := split.get("label"):
|
|
1482
|
+
instance.label = label
|
|
1472
1483
|
# Authored placement (parent before location — locations are
|
|
1473
1484
|
# parent-relative); older snapshots without these keys leave the
|
|
1474
1485
|
# instance where auto-layout or the caller puts it.
|
|
@@ -1533,10 +1544,15 @@ class TreeBuilder[TreeT: NodeTree]:
|
|
|
1533
1544
|
def split_group_inputs(self) -> None:
|
|
1534
1545
|
"""Split the Group Input node into one instance per consumer node,
|
|
1535
1546
|
with unused sockets hidden — regenerating the editor style that
|
|
1536
|
-
avoids a single input node trailing long noodles.
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
``
|
|
1547
|
+
avoids a single input node trailing long noodles. Each instance is
|
|
1548
|
+
named (and labelled, so the editor header shows it) after the
|
|
1549
|
+
interface sockets it carries, making it easy to spot when scanning
|
|
1550
|
+
the tree (Blender de-duplicates repeated names with ``.001``
|
|
1551
|
+
suffixes as usual), and parented into its consumer's frame so the
|
|
1552
|
+
arranger's frame clustering keeps the pair together. Runs automatically on context exit (before auto-layout, so
|
|
1553
|
+
the instances are arranged next to their consumers) when the builder
|
|
1554
|
+
was created with ``split_inputs=True``, or inside a
|
|
1555
|
+
:func:`~nodebpy.builder.default_split_inputs` scope."""
|
|
1540
1556
|
primary = self.tree.nodes.get("Group Input")
|
|
1541
1557
|
if primary is None:
|
|
1542
1558
|
return
|
|
@@ -1546,11 +1562,27 @@ class TreeBuilder[TreeT: NodeTree]:
|
|
|
1546
1562
|
by_consumer.setdefault(link.to_node.name, []).append(
|
|
1547
1563
|
(socket.name, link.to_node.name, socket_key(link.to_socket))
|
|
1548
1564
|
)
|
|
1565
|
+
|
|
1566
|
+
def socket_names(links: list[tuple[str, str, str]]) -> str:
|
|
1567
|
+
return ", ".join(dict.fromkeys(name for name, _, _ in links))
|
|
1568
|
+
|
|
1569
|
+
def parent_frame(consumer: str) -> str | None:
|
|
1570
|
+
parent = self.tree.nodes[consumer].parent
|
|
1571
|
+
return parent.name if parent is not None else None
|
|
1572
|
+
|
|
1549
1573
|
# The first consumer keeps the primary node; each further consumer
|
|
1550
|
-
# gets its own instance
|
|
1574
|
+
# gets its own instance, named after the sockets it carries and
|
|
1575
|
+
# parented into the consumer's frame — frame clustering would
|
|
1576
|
+
# otherwise push an unparented instance away from its consumer when
|
|
1577
|
+
# the tree is arranged.
|
|
1551
1578
|
self.group_input_splits = [
|
|
1552
|
-
{
|
|
1553
|
-
|
|
1579
|
+
{
|
|
1580
|
+
"name": socket_names(by_consumer[consumer]),
|
|
1581
|
+
"label": socket_names(by_consumer[consumer]),
|
|
1582
|
+
"parent": parent_frame(consumer),
|
|
1583
|
+
"links": by_consumer[consumer],
|
|
1584
|
+
}
|
|
1585
|
+
for consumer in list(by_consumer)[1:]
|
|
1554
1586
|
]
|
|
1555
1587
|
self._hide_unused_input_sockets()
|
|
1556
1588
|
|
|
@@ -812,6 +812,15 @@ def _make_var(label: str, counter: dict[str, int]) -> str:
|
|
|
812
812
|
return f"{base}_{counter[base]}"
|
|
813
813
|
|
|
814
814
|
|
|
815
|
+
def _node_var_label(node) -> str:
|
|
816
|
+
"""The stem for a node's generated variable name. A group node's
|
|
817
|
+
``bl_label`` is just "Group", so it reads as its group's name instead
|
|
818
|
+
(``inverse_mass = InverseMass()``)."""
|
|
819
|
+
if node.bl_idname in _GROUP_BASES and node.node_tree is not None:
|
|
820
|
+
return node.node_tree.name
|
|
821
|
+
return node.bl_label or "node"
|
|
822
|
+
|
|
823
|
+
|
|
815
824
|
# ---------------------------------------------------------------------------
|
|
816
825
|
# Graph utilities
|
|
817
826
|
# ---------------------------------------------------------------------------
|
|
@@ -1203,7 +1212,7 @@ class EmitContext:
|
|
|
1203
1212
|
vector feeding ``vec.x`` / ``vec.y``). The assignment is queued on
|
|
1204
1213
|
``pending_lines`` and flushed before the consuming statement.
|
|
1205
1214
|
"""
|
|
1206
|
-
label = link.from_socket.name or link.from_node
|
|
1215
|
+
label = link.from_socket.name or _node_var_label(link.from_node)
|
|
1207
1216
|
var = _make_var(label, self.counter)
|
|
1208
1217
|
self.pending_lines.append(f" {var} = {expr.render()}")
|
|
1209
1218
|
ref = Ref(var)
|
|
@@ -3930,6 +3939,8 @@ def _group_input_split_lines(
|
|
|
3930
3939
|
parent = _fmt(node.parent.name) if node.parent is not None else "None"
|
|
3931
3940
|
lines.append(f"{indent} {{")
|
|
3932
3941
|
lines.append(f'{indent} "name": {_fmt(node.name)},')
|
|
3942
|
+
if node.label:
|
|
3943
|
+
lines.append(f'{indent} "label": {_fmt(node.label)},')
|
|
3933
3944
|
lines.append(f'{indent} "location": {_fmt(loc)},')
|
|
3934
3945
|
lines.append(f'{indent} "parent": {parent},')
|
|
3935
3946
|
lines.append(f'{indent} "links": [')
|
|
@@ -4489,7 +4500,7 @@ def _emit_tree(node_tree, collector: _GroupCollector) -> _TreeEmission:
|
|
|
4489
4500
|
f"Cannot generate code: {message}. Register an emitter "
|
|
4490
4501
|
"with register_emitter() or pass strict=False."
|
|
4491
4502
|
)
|
|
4492
|
-
var = _make_var(node
|
|
4503
|
+
var = _make_var(_node_var_label(node), ctx.counter)
|
|
4493
4504
|
if not ctx.outgoing.get(name):
|
|
4494
4505
|
var = "_" + var
|
|
4495
4506
|
ctx.var_map[name] = _Val(Ref(var))
|
|
@@ -4575,7 +4586,7 @@ def _emit_tree(node_tree, collector: _GroupCollector) -> _TreeEmission:
|
|
|
4575
4586
|
consumed_out_links.add(id(link))
|
|
4576
4587
|
continue
|
|
4577
4588
|
|
|
4578
|
-
var = _make_var(node
|
|
4589
|
+
var = _make_var(_node_var_label(node), ctx.counter)
|
|
4579
4590
|
# A node created purely for its side effect (a gizmo, a dangling
|
|
4580
4591
|
# node) is never referenced again — prefix its variable so the
|
|
4581
4592
|
# generated module passes lint (F841).
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|