procfunc 0.30.0__py3-none-any.whl
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.
- procfunc/__init__.py +87 -0
- procfunc/color.py +57 -0
- procfunc/compute_graph/__init__.py +28 -0
- procfunc/compute_graph/compute_graph.py +115 -0
- procfunc/compute_graph/node.py +200 -0
- procfunc/compute_graph/operators_info.py +92 -0
- procfunc/compute_graph/proxy.py +173 -0
- procfunc/compute_graph/util.py +282 -0
- procfunc/context.py +115 -0
- procfunc/control.py +174 -0
- procfunc/nodes/__init__.py +66 -0
- procfunc/nodes/bindings_util.py +196 -0
- procfunc/nodes/bpy_node_info.py +280 -0
- procfunc/nodes/compositor.py +2242 -0
- procfunc/nodes/execute/construct_nodes.py +571 -0
- procfunc/nodes/execute/construct_special_cases.py +246 -0
- procfunc/nodes/execute/execute.py +548 -0
- procfunc/nodes/execute/infer_runtime_data_type.py +195 -0
- procfunc/nodes/execute/util.py +247 -0
- procfunc/nodes/func.py +1417 -0
- procfunc/nodes/geo.py +4240 -0
- procfunc/nodes/manifest.json +8769 -0
- procfunc/nodes/math.py +644 -0
- procfunc/nodes/node_function.py +160 -0
- procfunc/nodes/shader.py +2359 -0
- procfunc/nodes/types.py +347 -0
- procfunc/ops/__init__.py +35 -0
- procfunc/ops/_util.py +275 -0
- procfunc/ops/addons.py +59 -0
- procfunc/ops/attr.py +426 -0
- procfunc/ops/collection.py +90 -0
- procfunc/ops/curve.py +18 -0
- procfunc/ops/file.py +126 -0
- procfunc/ops/manifest.json +39149 -0
- procfunc/ops/mesh.py +1510 -0
- procfunc/ops/modifier.py +603 -0
- procfunc/ops/object.py +258 -0
- procfunc/ops/primitives/__init__.py +31 -0
- procfunc/ops/primitives/camera.py +45 -0
- procfunc/ops/primitives/curve.py +71 -0
- procfunc/ops/primitives/light.py +114 -0
- procfunc/ops/primitives/mesh.py +358 -0
- procfunc/ops/uv.py +271 -0
- procfunc/random.py +247 -0
- procfunc/tracer/__init__.py +43 -0
- procfunc/tracer/decorator.py +121 -0
- procfunc/tracer/patch.py +494 -0
- procfunc/tracer/proxy.py +127 -0
- procfunc/tracer/trace.py +222 -0
- procfunc/transforms/__init__.py +49 -0
- procfunc/transforms/cleanup.py +214 -0
- procfunc/transforms/convert.py +20 -0
- procfunc/transforms/distribution.py +191 -0
- procfunc/transforms/extract_materials.py +116 -0
- procfunc/transforms/infer_distribution.py +326 -0
- procfunc/transforms/parameters.py +15 -0
- procfunc/transforms/util.py +35 -0
- procfunc/transpiler/__init__.py +24 -0
- procfunc/transpiler/bpy_to_computegraph.py +1348 -0
- procfunc/transpiler/codegen.py +919 -0
- procfunc/transpiler/identifiers.py +595 -0
- procfunc/transpiler/main.py +299 -0
- procfunc/types.py +380 -0
- procfunc/util/__init__.py +0 -0
- procfunc/util/bpy_info.py +145 -0
- procfunc/util/camera.py +0 -0
- procfunc/util/keyframe.py +70 -0
- procfunc/util/log.py +96 -0
- procfunc/util/manifest.py +121 -0
- procfunc/util/pytree.py +343 -0
- procfunc/util/teardown.py +37 -0
- procfunc-0.30.0.dist-info/METADATA +120 -0
- procfunc-0.30.0.dist-info/RECORD +76 -0
- procfunc-0.30.0.dist-info/WHEEL +5 -0
- procfunc-0.30.0.dist-info/licenses/LICENSE.md +11 -0
- procfunc-0.30.0.dist-info/top_level.txt +1 -0
procfunc/control.py
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import functools
|
|
2
|
+
import logging
|
|
3
|
+
from typing import Callable, TypeVar
|
|
4
|
+
|
|
5
|
+
import numpy as np
|
|
6
|
+
|
|
7
|
+
import procfunc.compute_graph as cg
|
|
8
|
+
from procfunc import context
|
|
9
|
+
from procfunc import types as t
|
|
10
|
+
from procfunc.tracer import (
|
|
11
|
+
PatchFunctionTarget,
|
|
12
|
+
RngProxy,
|
|
13
|
+
TraceLevel,
|
|
14
|
+
register_trace_target,
|
|
15
|
+
)
|
|
16
|
+
from procfunc.util import pytree
|
|
17
|
+
|
|
18
|
+
logger = logging.getLogger(__name__)
|
|
19
|
+
|
|
20
|
+
T = TypeVar("T")
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _unwrap_proxy(x):
|
|
24
|
+
return x.node if isinstance(x, cg.Proxy) else x
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def choice_idx(rng: np.random.Generator, weights: list[float]) -> int:
|
|
28
|
+
weights = np.array(weights)
|
|
29
|
+
weights = weights / weights.sum()
|
|
30
|
+
return rng.choice(len(weights), p=weights)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _peekthrough_execute_all_choices(
|
|
34
|
+
choice_options: list[tuple[Callable[..., T], float]],
|
|
35
|
+
chosen_idx: int,
|
|
36
|
+
args: tuple,
|
|
37
|
+
kwargs: dict,
|
|
38
|
+
) -> cg.Proxy:
|
|
39
|
+
evald = []
|
|
40
|
+
for func, weight in choice_options:
|
|
41
|
+
res = func(*args, **kwargs)
|
|
42
|
+
res = pytree.PyTree(res).map(_unwrap_proxy).obj()
|
|
43
|
+
evald.append((res, weight))
|
|
44
|
+
|
|
45
|
+
new_kwargs = dict(
|
|
46
|
+
choice_options=evald,
|
|
47
|
+
chosen_idx=chosen_idx,
|
|
48
|
+
)
|
|
49
|
+
node = cg.FunctionCallNode(
|
|
50
|
+
func=choice,
|
|
51
|
+
args=(),
|
|
52
|
+
kwargs=new_kwargs,
|
|
53
|
+
)
|
|
54
|
+
return cg.Proxy(node)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class ChoiceResultProxy(cg.Proxy):
|
|
58
|
+
def __init__(self, node: cg.FunctionCallNode):
|
|
59
|
+
assert node.func is choice
|
|
60
|
+
super().__init__(node)
|
|
61
|
+
|
|
62
|
+
def __call__(self, *args, **kwargs) -> cg.Proxy:
|
|
63
|
+
current_level = context.globals.current_trace_level
|
|
64
|
+
if current_level is None:
|
|
65
|
+
raise ValueError(f"Executed {self} while not in a tracing context?")
|
|
66
|
+
if current_level >= TraceLevel.RANDOM_CONTROL:
|
|
67
|
+
return _peekthrough_execute_all_choices(
|
|
68
|
+
self.node.kwargs["choice_options"],
|
|
69
|
+
self.node.kwargs["chosen_idx"],
|
|
70
|
+
args,
|
|
71
|
+
kwargs,
|
|
72
|
+
)
|
|
73
|
+
else:
|
|
74
|
+
idx = self.node.kwargs["chosen_idx"]
|
|
75
|
+
chosen_value = self.node.kwargs["choice_options"][idx][0]
|
|
76
|
+
return chosen_value(*args, **kwargs)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def _choice_create_custom_tracer_wrapper(
|
|
80
|
+
target: PatchFunctionTarget,
|
|
81
|
+
patcher,
|
|
82
|
+
**_kwargs,
|
|
83
|
+
):
|
|
84
|
+
"""
|
|
85
|
+
choice needs a custom tracing wrapper since we want to trace what happens inside its sub-options.
|
|
86
|
+
|
|
87
|
+
Behavior depends on trace_level vs RANDOM_CONTROL:
|
|
88
|
+
- >= RANDOM_CONTROL: peekthrough all options exhaustively
|
|
89
|
+
- < RANDOM_CONTROL: trace only the branch that would actually be chosen (active resolution)
|
|
90
|
+
"""
|
|
91
|
+
|
|
92
|
+
@functools.wraps(choice)
|
|
93
|
+
def wrapper(
|
|
94
|
+
choice_rng: RngProxy,
|
|
95
|
+
choice_options: list[tuple[T, float]] | None = None,
|
|
96
|
+
chosen_idx: int | None = None,
|
|
97
|
+
):
|
|
98
|
+
choice_values, choice_weights = zip(*choice_options)
|
|
99
|
+
if chosen_idx is None:
|
|
100
|
+
chosen_idx = choice_idx(choice_rng.rng, choice_weights)
|
|
101
|
+
|
|
102
|
+
unwrapped_options = [
|
|
103
|
+
(pytree.PyTree(v).map(_unwrap_proxy).obj(), w) for v, w in choice_options
|
|
104
|
+
]
|
|
105
|
+
new_kwargs = dict(
|
|
106
|
+
choice_rng=choice_rng.node,
|
|
107
|
+
choice_options=unwrapped_options,
|
|
108
|
+
chosen_idx=chosen_idx,
|
|
109
|
+
)
|
|
110
|
+
node = cg.FunctionCallNode(
|
|
111
|
+
func=choice,
|
|
112
|
+
args=(),
|
|
113
|
+
kwargs=new_kwargs,
|
|
114
|
+
)
|
|
115
|
+
return ChoiceResultProxy(node)
|
|
116
|
+
|
|
117
|
+
return wrapper
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
TArgs = TypeVar("TArgs")
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def choice(
|
|
124
|
+
choice_rng: np.random.Generator,
|
|
125
|
+
choice_options: list[tuple[T, float]],
|
|
126
|
+
chosen_idx: int | None = None,
|
|
127
|
+
) -> T:
|
|
128
|
+
"""
|
|
129
|
+
Args:
|
|
130
|
+
rng: random number generator
|
|
131
|
+
weights: list of weights for each option - will be normalized to sum to 1 as a probability distribution
|
|
132
|
+
options: list of callables to choose from
|
|
133
|
+
chosen: if not None, use this value instead of choosing randomly. Cannot be traced, but may appear in the output of the tracer.
|
|
134
|
+
chosen_idx: if not None, use this index instead of choosing randomly. Cannot be traced, but may appear in the output of the tracer.
|
|
135
|
+
**child_kwargs: keyword arguments to pass to the chosen callable
|
|
136
|
+
|
|
137
|
+
TODO: this function should really take args and kwargs as tuple & dict, not via expansion
|
|
138
|
+
but this requires our Node to handle cases with pytrees as inputs. Currently it would fail to execute the children if they are hidden in a pytree
|
|
139
|
+
"""
|
|
140
|
+
|
|
141
|
+
choice_values, choice_weights = zip(*choice_options)
|
|
142
|
+
if chosen_idx is None:
|
|
143
|
+
chosen_idx = choice_idx(choice_rng, choice_weights)
|
|
144
|
+
option = choice_values[chosen_idx]
|
|
145
|
+
return option
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
_ORIG_EXECUTE_CHOICE = choice
|
|
149
|
+
register_trace_target(
|
|
150
|
+
func=choice,
|
|
151
|
+
trace_level=TraceLevel.RANDOM_CONTROL,
|
|
152
|
+
allow_exec=False,
|
|
153
|
+
custom_trace_wrapper_create=_choice_create_custom_tracer_wrapper,
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def sample_collection(
|
|
158
|
+
rng: np.random.Generator,
|
|
159
|
+
func: Callable[[np.random.Generator], T],
|
|
160
|
+
n: int,
|
|
161
|
+
skip_none: bool = False,
|
|
162
|
+
) -> t.Collection:
|
|
163
|
+
rngs = rng.spawn(n)
|
|
164
|
+
objects = [func(rng) for rng in rngs]
|
|
165
|
+
if skip_none:
|
|
166
|
+
objects = [obj for obj in objects if obj is not None]
|
|
167
|
+
return t.Collection(objects, name=func.__name__)
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
__all__ = [
|
|
171
|
+
"choice",
|
|
172
|
+
"choice_idx",
|
|
173
|
+
"choice",
|
|
174
|
+
]
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
from pandas import read_json as _read_json
|
|
2
|
+
|
|
3
|
+
from procfunc.tracer import autowrap_module as _autowrap
|
|
4
|
+
from procfunc.util.manifest import module_path
|
|
5
|
+
|
|
6
|
+
from . import compositor, func, geo, math, shader
|
|
7
|
+
from .bpy_node_info import NodeDataType, NodeGroupType, SocketType
|
|
8
|
+
|
|
9
|
+
# ruff: noqa: E402
|
|
10
|
+
_autowrap(compositor, allow_exec=False)
|
|
11
|
+
_autowrap(func, allow_exec=False)
|
|
12
|
+
_autowrap(geo, allow_exec=False)
|
|
13
|
+
_autowrap(math, allow_exec=False)
|
|
14
|
+
_autowrap(shader, allow_exec=False)
|
|
15
|
+
|
|
16
|
+
from .execute.execute import (
|
|
17
|
+
as_nodegroup,
|
|
18
|
+
to_aliases,
|
|
19
|
+
to_compositor,
|
|
20
|
+
to_curve_object,
|
|
21
|
+
to_environment,
|
|
22
|
+
to_light,
|
|
23
|
+
to_mesh_object,
|
|
24
|
+
to_mesh_object_with_attributes,
|
|
25
|
+
to_objects_multi,
|
|
26
|
+
)
|
|
27
|
+
from .execute.util import NODE_OPERATOR_TABLE
|
|
28
|
+
from .node_function import node_function
|
|
29
|
+
from .types import (
|
|
30
|
+
ProcNode,
|
|
31
|
+
Shader,
|
|
32
|
+
SocketOrVal,
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
NODES_MANIFEST_PATH = module_path() / "nodes" / "manifest.json"
|
|
36
|
+
assert NODES_MANIFEST_PATH.exists(), f"Manifest not found at {NODES_MANIFEST_PATH}"
|
|
37
|
+
NODES_MANIFEST = _read_json(NODES_MANIFEST_PATH)
|
|
38
|
+
|
|
39
|
+
__all__ = [
|
|
40
|
+
# Node category submodules
|
|
41
|
+
"compositor",
|
|
42
|
+
"func",
|
|
43
|
+
"geo",
|
|
44
|
+
"math",
|
|
45
|
+
"shader",
|
|
46
|
+
# Core types
|
|
47
|
+
"ProcNode",
|
|
48
|
+
"Shader",
|
|
49
|
+
"SocketOrVal",
|
|
50
|
+
"NodeDataType",
|
|
51
|
+
"NodeGroupType",
|
|
52
|
+
"SocketType",
|
|
53
|
+
# Graph execution
|
|
54
|
+
"as_nodegroup",
|
|
55
|
+
"to_aliases",
|
|
56
|
+
"to_compositor",
|
|
57
|
+
"to_curve_object",
|
|
58
|
+
"to_environment",
|
|
59
|
+
"to_light",
|
|
60
|
+
"to_material",
|
|
61
|
+
"to_mesh_object",
|
|
62
|
+
"to_mesh_object_with_attributes",
|
|
63
|
+
"to_objects_multi",
|
|
64
|
+
# User-facing decorator for custom node functions
|
|
65
|
+
"node_function",
|
|
66
|
+
]
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
from enum import Enum
|
|
4
|
+
from typing import Self
|
|
5
|
+
|
|
6
|
+
import procfunc as pf
|
|
7
|
+
from procfunc.nodes.bpy_node_info import NodeDataType, NodeGroupType
|
|
8
|
+
from procfunc.util.log import raise_error_or_warn
|
|
9
|
+
|
|
10
|
+
logger = logging.getLogger(__name__)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@dataclass
|
|
14
|
+
class RuntimeResolveDataType:
|
|
15
|
+
data_types: list[NodeDataType]
|
|
16
|
+
dependent_input_names: list[str]
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class ContextualNode(Enum):
|
|
20
|
+
"""Special node types that map to different implementations based on context."""
|
|
21
|
+
|
|
22
|
+
COMBINE_COLOR = "ContextualCombineColor"
|
|
23
|
+
SEPARATE_COLOR = "ContextualSeparateColor"
|
|
24
|
+
GROUP = "ContextualGroup"
|
|
25
|
+
MIX_RGB = "ContextualMixRGB"
|
|
26
|
+
MATH = "ContextualMath"
|
|
27
|
+
|
|
28
|
+
@classmethod
|
|
29
|
+
def parse_name(cls, from_name: str) -> Self | None:
|
|
30
|
+
try:
|
|
31
|
+
return cls(from_name)
|
|
32
|
+
except ValueError:
|
|
33
|
+
return None
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@dataclass
|
|
37
|
+
class NodeContextResolution:
|
|
38
|
+
contextual_node: ContextualNode
|
|
39
|
+
node_group_type: NodeGroupType
|
|
40
|
+
node_type: str
|
|
41
|
+
input_keys_map: dict[str, tuple[str, int] | str | None] | None
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
# Mapping to map unified version of nodes to context-specific blender versions
|
|
45
|
+
# Input keys are (ContextualNode, NodeGroupType) - the unifiied version + the context we need to use it in
|
|
46
|
+
# Output is the (NodeType, input map) - tells what specific node to construct, then how to change the inputs for that context, if applicable
|
|
47
|
+
|
|
48
|
+
CONTEXTUAL_NODE_MAPPING = [
|
|
49
|
+
NodeContextResolution(
|
|
50
|
+
contextual_node=ContextualNode.COMBINE_COLOR,
|
|
51
|
+
node_group_type=NodeGroupType.SHADER,
|
|
52
|
+
node_type="ShaderNodeCombineColor",
|
|
53
|
+
input_keys_map=None,
|
|
54
|
+
),
|
|
55
|
+
NodeContextResolution(
|
|
56
|
+
contextual_node=ContextualNode.COMBINE_COLOR,
|
|
57
|
+
node_group_type=NodeGroupType.TEXTURE,
|
|
58
|
+
node_type="TextureNodeCombineColor",
|
|
59
|
+
input_keys_map=None,
|
|
60
|
+
),
|
|
61
|
+
NodeContextResolution(
|
|
62
|
+
contextual_node=ContextualNode.COMBINE_COLOR,
|
|
63
|
+
node_group_type=NodeGroupType.GEOMETRY,
|
|
64
|
+
node_type="FunctionNodeCombineColor",
|
|
65
|
+
input_keys_map=None,
|
|
66
|
+
),
|
|
67
|
+
NodeContextResolution(
|
|
68
|
+
contextual_node=ContextualNode.SEPARATE_COLOR,
|
|
69
|
+
node_group_type=NodeGroupType.SHADER,
|
|
70
|
+
node_type="ShaderNodeSeparateColor",
|
|
71
|
+
input_keys_map=None,
|
|
72
|
+
),
|
|
73
|
+
NodeContextResolution(
|
|
74
|
+
contextual_node=ContextualNode.SEPARATE_COLOR,
|
|
75
|
+
node_group_type=NodeGroupType.TEXTURE,
|
|
76
|
+
node_type="TextureNodeSeparateColor",
|
|
77
|
+
input_keys_map=None,
|
|
78
|
+
),
|
|
79
|
+
NodeContextResolution(
|
|
80
|
+
contextual_node=ContextualNode.SEPARATE_COLOR,
|
|
81
|
+
node_group_type=NodeGroupType.GEOMETRY,
|
|
82
|
+
node_type="FunctionNodeSeparateColor",
|
|
83
|
+
input_keys_map=None,
|
|
84
|
+
),
|
|
85
|
+
NodeContextResolution(
|
|
86
|
+
contextual_node=ContextualNode.SEPARATE_COLOR,
|
|
87
|
+
node_group_type=NodeGroupType.COMPOSITOR,
|
|
88
|
+
node_type="CompositorNodeSeparateColor",
|
|
89
|
+
input_keys_map={"Color": "Image"},
|
|
90
|
+
),
|
|
91
|
+
NodeContextResolution(
|
|
92
|
+
contextual_node=ContextualNode.GROUP,
|
|
93
|
+
node_group_type=NodeGroupType.COMPOSITOR,
|
|
94
|
+
node_type="CompositorNodeGroup",
|
|
95
|
+
input_keys_map=None,
|
|
96
|
+
),
|
|
97
|
+
NodeContextResolution(
|
|
98
|
+
contextual_node=ContextualNode.GROUP,
|
|
99
|
+
node_group_type=NodeGroupType.TEXTURE,
|
|
100
|
+
node_type="TextureNodeGroup",
|
|
101
|
+
input_keys_map=None,
|
|
102
|
+
),
|
|
103
|
+
NodeContextResolution(
|
|
104
|
+
contextual_node=ContextualNode.GROUP,
|
|
105
|
+
node_group_type=NodeGroupType.GEOMETRY,
|
|
106
|
+
node_type="GeometryNodeGroup",
|
|
107
|
+
input_keys_map=None,
|
|
108
|
+
),
|
|
109
|
+
NodeContextResolution(
|
|
110
|
+
contextual_node=ContextualNode.MIX_RGB,
|
|
111
|
+
node_group_type=NodeGroupType.SHADER,
|
|
112
|
+
node_type="ShaderNodeMixRGB",
|
|
113
|
+
input_keys_map=None,
|
|
114
|
+
),
|
|
115
|
+
NodeContextResolution(
|
|
116
|
+
contextual_node=ContextualNode.MIX_RGB,
|
|
117
|
+
node_group_type=NodeGroupType.TEXTURE,
|
|
118
|
+
node_type="TextureNodeMixRGB",
|
|
119
|
+
input_keys_map={"Fac": "Factor"},
|
|
120
|
+
),
|
|
121
|
+
NodeContextResolution(
|
|
122
|
+
contextual_node=ContextualNode.MIX_RGB,
|
|
123
|
+
node_group_type=NodeGroupType.GEOMETRY,
|
|
124
|
+
node_type="ShaderNodeMixRGB",
|
|
125
|
+
input_keys_map=None,
|
|
126
|
+
),
|
|
127
|
+
NodeContextResolution(
|
|
128
|
+
contextual_node=ContextualNode.MATH,
|
|
129
|
+
node_group_type=NodeGroupType.SHADER,
|
|
130
|
+
node_type="ShaderNodeMath",
|
|
131
|
+
input_keys_map=None,
|
|
132
|
+
),
|
|
133
|
+
NodeContextResolution(
|
|
134
|
+
contextual_node=ContextualNode.MATH,
|
|
135
|
+
node_group_type=NodeGroupType.COMPOSITOR,
|
|
136
|
+
node_type="CompositorNodeMath",
|
|
137
|
+
input_keys_map=None,
|
|
138
|
+
),
|
|
139
|
+
NodeContextResolution(
|
|
140
|
+
contextual_node=ContextualNode.MATH,
|
|
141
|
+
node_group_type=NodeGroupType.TEXTURE,
|
|
142
|
+
node_type="TextureNodeMath",
|
|
143
|
+
input_keys_map=None,
|
|
144
|
+
),
|
|
145
|
+
]
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def resolve_contextual_node(
|
|
149
|
+
node: list[NodeContextResolution], group: NodeGroupType
|
|
150
|
+
) -> tuple[str, dict[str, str]]:
|
|
151
|
+
item = next(
|
|
152
|
+
(
|
|
153
|
+
item
|
|
154
|
+
for item in CONTEXTUAL_NODE_MAPPING
|
|
155
|
+
if (item.node_group_type == group and item.contextual_node == node)
|
|
156
|
+
),
|
|
157
|
+
None,
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
if item is None:
|
|
161
|
+
raise ValueError(f"No contextual node found for {node} in {group}")
|
|
162
|
+
|
|
163
|
+
return item.node_type, item.input_keys_map
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def raise_shader_normal_error(node_func_name: str, logger: logging.Logger):
|
|
167
|
+
"""Helper to handle normal socket usage in shaders based on context setting."""
|
|
168
|
+
|
|
169
|
+
message = (
|
|
170
|
+
f"Using 'normal' input in {node_func_name} is not recommended. "
|
|
171
|
+
f"Use displacement instead. To suppress this message, set "
|
|
172
|
+
f"pf.context.globals.warn_mode_avoid_normal_bump = 'ignore'"
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
raise_error_or_warn(message, pf.context.globals.warn_mode_avoid_normal_bump, logger)
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def raise_explicit_noise_vector_error(node_func_name: str, logger: logging.Logger):
|
|
179
|
+
"""Helper to handle missing vector input in noise functions based on context setting."""
|
|
180
|
+
message = (
|
|
181
|
+
f"The 'vector' input is required for {node_func_name}. "
|
|
182
|
+
f"Infinigen requires an explicit vector input - node will not default to using texture coordinate or world coordinate like blender. "
|
|
183
|
+
f"To suppress this message, set pf.context.globals.warn_mode_avoid_implicit_vector = 'ignore'"
|
|
184
|
+
)
|
|
185
|
+
raise_error_or_warn(
|
|
186
|
+
message, pf.context.globals.warn_mode_avoid_implicit_vector, logger
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def raise_io_error(node_func_name: str, logger: logging.Logger):
|
|
191
|
+
"""Helper to handle IO nodes based on context setting."""
|
|
192
|
+
message = (
|
|
193
|
+
f"IO nodes are not allowed in nodegroups but we found {node_func_name} in the nodegroup. Use the nodegroup interface instead. "
|
|
194
|
+
"To suppress this message, set pf.context.globals.warn_mode_avoid_io_nodes = 'ignore'"
|
|
195
|
+
)
|
|
196
|
+
raise_error_or_warn(message, pf.context.globals.warn_mode_avoid_io_nodes, logger)
|