2023-08-16 00:20:26 +10:00
|
|
|
# SPDX-FileCopyrightText: 2012-2023 Blender Authors
|
2023-06-15 13:09:04 +10:00
|
|
|
#
|
2022-02-11 09:07:11 +11:00
|
|
|
# SPDX-License-Identifier: GPL-2.0-or-later
|
2023-06-15 13:09:04 +10:00
|
|
|
|
2021-02-21 21:21:18 +11:00
|
|
|
from __future__ import annotations
|
2012-08-08 16:44:16 +00:00
|
|
|
|
2013-06-27 03:05:19 +00:00
|
|
|
import bpy
|
2015-01-27 17:46:07 +11:00
|
|
|
from bpy.types import (
|
2024-04-26 16:40:16 +02:00
|
|
|
FileHandler,
|
2017-03-25 11:07:05 +11:00
|
|
|
Operator,
|
|
|
|
|
PropertyGroup,
|
|
|
|
|
)
|
2015-01-27 17:46:07 +11:00
|
|
|
from bpy.props import (
|
2017-03-25 11:07:05 +11:00
|
|
|
BoolProperty,
|
|
|
|
|
CollectionProperty,
|
2023-06-14 18:02:40 +02:00
|
|
|
EnumProperty,
|
Geometry Nodes: add simulation support
This adds support for building simulations with geometry nodes. A new
`Simulation Input` and `Simulation Output` node allow maintaining a
simulation state across multiple frames. Together these two nodes form
a `simulation zone` which contains all the nodes that update the simulation
state from one frame to the next.
A new simulation zone can be added via the menu
(`Simulation > Simulation Zone`) or with the node add search.
The simulation state contains a geometry by default. However, it is possible
to add multiple geometry sockets as well as other socket types. Currently,
field inputs are evaluated and stored for the preceding geometry socket in
the order that the sockets are shown. Simulation state items can be added
by linking one of the empty sockets to something else. In the sidebar, there
is a new panel that allows adding, removing and reordering these sockets.
The simulation nodes behave as follows:
* On the first frame, the inputs of the `Simulation Input` node are evaluated
to initialize the simulation state. In later frames these sockets are not
evaluated anymore. The `Delta Time` at the first frame is zero, but the
simulation zone is still evaluated.
* On every next frame, the `Simulation Input` node outputs the simulation
state of the previous frame. Nodes in the simulation zone can edit that
data in arbitrary ways, also taking into account the `Delta Time`. The new
simulation state has to be passed to the `Simulation Output` node where it
is cached and forwarded.
* On a frame that is already cached or baked, the nodes in the simulation
zone are not evaluated, because the `Simulation Output` node can return
the previously cached data directly.
It is not allowed to connect sockets from inside the simulation zone to the
outside without going through the `Simulation Output` node. This is a necessary
restriction to make caching and sub-frame interpolation work. Links can go into
the simulation zone without problems though.
Anonymous attributes are not propagated by the simulation nodes unless they
are explicitly stored in the simulation state. This is unfortunate, but
currently there is no practical and reliable alternative. The core problem
is detecting which anonymous attributes will be required for the simulation
and afterwards. While we can detect this for the current evaluation, we can't
look into the future in time to see what data will be necessary. We intend to
make it easier to explicitly pass data through a simulation in the future,
even if the simulation is in a nested node group.
There is a new `Simulation Nodes` panel in the physics tab in the properties
editor. It allows baking all simulation zones on the selected objects. The
baking options are intentially kept at a minimum for this MVP. More features
for simulation baking as well as baking in general can be expected to be added
separately.
All baked data is stored on disk in a folder next to the .blend file. #106937
describes how baking is implemented in more detail. Volumes can not be baked
yet and materials are lost during baking for now. Packing the baked data into
the .blend file is not yet supported.
The timeline indicates which frames are currently cached, baked or cached but
invalidated by user-changes.
Simulation input and output nodes are internally linked together by their
`bNode.identifier` which stays the same even if the node name changes. They
are generally added and removed together. However, there are still cases where
"dangling" simulation nodes can be created currently. Those generally don't
cause harm, but would be nice to avoid this in more cases in the future.
Co-authored-by: Hans Goudey <h.goudey@me.com>
Co-authored-by: Lukas Tönne <lukas@blender.org>
Pull Request: https://projects.blender.org/blender/blender/pulls/104924
2023-05-03 13:18:51 +02:00
|
|
|
FloatVectorProperty,
|
2017-03-25 11:07:05 +11:00
|
|
|
StringProperty,
|
2025-01-29 18:35:26 +01:00
|
|
|
IntProperty,
|
2017-03-25 11:07:05 +11:00
|
|
|
)
|
Geometry Nodes: add simulation support
This adds support for building simulations with geometry nodes. A new
`Simulation Input` and `Simulation Output` node allow maintaining a
simulation state across multiple frames. Together these two nodes form
a `simulation zone` which contains all the nodes that update the simulation
state from one frame to the next.
A new simulation zone can be added via the menu
(`Simulation > Simulation Zone`) or with the node add search.
The simulation state contains a geometry by default. However, it is possible
to add multiple geometry sockets as well as other socket types. Currently,
field inputs are evaluated and stored for the preceding geometry socket in
the order that the sockets are shown. Simulation state items can be added
by linking one of the empty sockets to something else. In the sidebar, there
is a new panel that allows adding, removing and reordering these sockets.
The simulation nodes behave as follows:
* On the first frame, the inputs of the `Simulation Input` node are evaluated
to initialize the simulation state. In later frames these sockets are not
evaluated anymore. The `Delta Time` at the first frame is zero, but the
simulation zone is still evaluated.
* On every next frame, the `Simulation Input` node outputs the simulation
state of the previous frame. Nodes in the simulation zone can edit that
data in arbitrary ways, also taking into account the `Delta Time`. The new
simulation state has to be passed to the `Simulation Output` node where it
is cached and forwarded.
* On a frame that is already cached or baked, the nodes in the simulation
zone are not evaluated, because the `Simulation Output` node can return
the previously cached data directly.
It is not allowed to connect sockets from inside the simulation zone to the
outside without going through the `Simulation Output` node. This is a necessary
restriction to make caching and sub-frame interpolation work. Links can go into
the simulation zone without problems though.
Anonymous attributes are not propagated by the simulation nodes unless they
are explicitly stored in the simulation state. This is unfortunate, but
currently there is no practical and reliable alternative. The core problem
is detecting which anonymous attributes will be required for the simulation
and afterwards. While we can detect this for the current evaluation, we can't
look into the future in time to see what data will be necessary. We intend to
make it easier to explicitly pass data through a simulation in the future,
even if the simulation is in a nested node group.
There is a new `Simulation Nodes` panel in the physics tab in the properties
editor. It allows baking all simulation zones on the selected objects. The
baking options are intentially kept at a minimum for this MVP. More features
for simulation baking as well as baking in general can be expected to be added
separately.
All baked data is stored on disk in a folder next to the .blend file. #106937
describes how baking is implemented in more detail. Volumes can not be baked
yet and materials are lost during baking for now. Packing the baked data into
the .blend file is not yet supported.
The timeline indicates which frames are currently cached, baked or cached but
invalidated by user-changes.
Simulation input and output nodes are internally linked together by their
`bNode.identifier` which stays the same even if the node name changes. They
are generally added and removed together. However, there are still cases where
"dangling" simulation nodes can be created currently. Those generally don't
cause harm, but would be nice to avoid this in more cases in the future.
Co-authored-by: Hans Goudey <h.goudey@me.com>
Co-authored-by: Lukas Tönne <lukas@blender.org>
Pull Request: https://projects.blender.org/blender/blender/pulls/104924
2023-05-03 13:18:51 +02:00
|
|
|
from mathutils import (
|
|
|
|
|
Vector,
|
|
|
|
|
)
|
2012-12-12 12:50:43 +00:00
|
|
|
|
2024-01-11 19:49:03 +01:00
|
|
|
from bpy.app.translations import (
|
|
|
|
|
pgettext_tip as tip_,
|
|
|
|
|
pgettext_rpt as rpt_,
|
|
|
|
|
)
|
2022-07-18 15:09:37 +02:00
|
|
|
|
2013-01-15 23:15:32 +00:00
|
|
|
|
2013-05-08 15:40:53 +00:00
|
|
|
class NodeSetting(PropertyGroup):
|
2025-07-16 06:58:45 +00:00
|
|
|
__slots__ = ()
|
|
|
|
|
|
2018-07-11 22:18:09 +02:00
|
|
|
value: StringProperty(
|
2018-06-26 19:41:37 +02:00
|
|
|
name="Value",
|
|
|
|
|
description="Python expression to be evaluated "
|
|
|
|
|
"as the initial node setting",
|
|
|
|
|
default="",
|
|
|
|
|
)
|
2013-05-08 15:40:53 +00:00
|
|
|
|
2013-06-27 03:05:19 +00:00
|
|
|
|
2023-04-18 10:42:00 +10:00
|
|
|
# Base class for node "Add" operators.
|
2015-01-29 15:35:06 +11:00
|
|
|
class NodeAddOperator:
|
2013-05-08 15:40:53 +00:00
|
|
|
|
2018-07-11 22:18:09 +02:00
|
|
|
use_transform: BoolProperty(
|
2018-06-26 19:41:37 +02:00
|
|
|
name="Use Transform",
|
|
|
|
|
description="Start transform operator after inserting the node",
|
|
|
|
|
default=False,
|
|
|
|
|
)
|
2018-07-11 22:18:09 +02:00
|
|
|
settings: CollectionProperty(
|
2018-06-26 19:41:37 +02:00
|
|
|
name="Settings",
|
|
|
|
|
description="Settings to be applied on the newly created node",
|
|
|
|
|
type=NodeSetting,
|
|
|
|
|
options={'SKIP_SAVE'},
|
|
|
|
|
)
|
2013-05-08 15:40:53 +00:00
|
|
|
|
2012-12-12 12:50:43 +00:00
|
|
|
@staticmethod
|
|
|
|
|
def store_mouse_cursor(context, event):
|
|
|
|
|
space = context.space_data
|
2013-04-04 15:10:52 +00:00
|
|
|
tree = space.edit_tree
|
2012-12-12 12:50:43 +00:00
|
|
|
|
|
|
|
|
# convert mouse position to the View2D for later node placement
|
2013-04-04 15:10:52 +00:00
|
|
|
if context.region.type == 'WINDOW':
|
2025-07-24 07:43:48 +02:00
|
|
|
area = context.area
|
|
|
|
|
horizontal_pad = int(area.width / 10)
|
|
|
|
|
vertical_pad = int(area.height / 10)
|
|
|
|
|
|
|
|
|
|
inspace_x = min(max(horizontal_pad, event.mouse_region_x), area.width - horizontal_pad)
|
|
|
|
|
inspace_y = min(max(vertical_pad, event.mouse_region_y), area.height - vertical_pad)
|
2013-09-05 13:03:03 +00:00
|
|
|
# convert mouse position to the View2D for later node placement
|
2025-07-24 07:43:48 +02:00
|
|
|
space.cursor_location_from_region(inspace_x, inspace_y)
|
2013-04-04 15:10:52 +00:00
|
|
|
else:
|
|
|
|
|
space.cursor_location = tree.view_center
|
2012-12-12 12:50:43 +00:00
|
|
|
|
Geometry Nodes: add simulation support
This adds support for building simulations with geometry nodes. A new
`Simulation Input` and `Simulation Output` node allow maintaining a
simulation state across multiple frames. Together these two nodes form
a `simulation zone` which contains all the nodes that update the simulation
state from one frame to the next.
A new simulation zone can be added via the menu
(`Simulation > Simulation Zone`) or with the node add search.
The simulation state contains a geometry by default. However, it is possible
to add multiple geometry sockets as well as other socket types. Currently,
field inputs are evaluated and stored for the preceding geometry socket in
the order that the sockets are shown. Simulation state items can be added
by linking one of the empty sockets to something else. In the sidebar, there
is a new panel that allows adding, removing and reordering these sockets.
The simulation nodes behave as follows:
* On the first frame, the inputs of the `Simulation Input` node are evaluated
to initialize the simulation state. In later frames these sockets are not
evaluated anymore. The `Delta Time` at the first frame is zero, but the
simulation zone is still evaluated.
* On every next frame, the `Simulation Input` node outputs the simulation
state of the previous frame. Nodes in the simulation zone can edit that
data in arbitrary ways, also taking into account the `Delta Time`. The new
simulation state has to be passed to the `Simulation Output` node where it
is cached and forwarded.
* On a frame that is already cached or baked, the nodes in the simulation
zone are not evaluated, because the `Simulation Output` node can return
the previously cached data directly.
It is not allowed to connect sockets from inside the simulation zone to the
outside without going through the `Simulation Output` node. This is a necessary
restriction to make caching and sub-frame interpolation work. Links can go into
the simulation zone without problems though.
Anonymous attributes are not propagated by the simulation nodes unless they
are explicitly stored in the simulation state. This is unfortunate, but
currently there is no practical and reliable alternative. The core problem
is detecting which anonymous attributes will be required for the simulation
and afterwards. While we can detect this for the current evaluation, we can't
look into the future in time to see what data will be necessary. We intend to
make it easier to explicitly pass data through a simulation in the future,
even if the simulation is in a nested node group.
There is a new `Simulation Nodes` panel in the physics tab in the properties
editor. It allows baking all simulation zones on the selected objects. The
baking options are intentially kept at a minimum for this MVP. More features
for simulation baking as well as baking in general can be expected to be added
separately.
All baked data is stored on disk in a folder next to the .blend file. #106937
describes how baking is implemented in more detail. Volumes can not be baked
yet and materials are lost during baking for now. Packing the baked data into
the .blend file is not yet supported.
The timeline indicates which frames are currently cached, baked or cached but
invalidated by user-changes.
Simulation input and output nodes are internally linked together by their
`bNode.identifier` which stays the same even if the node name changes. They
are generally added and removed together. However, there are still cases where
"dangling" simulation nodes can be created currently. Those generally don't
cause harm, but would be nice to avoid this in more cases in the future.
Co-authored-by: Hans Goudey <h.goudey@me.com>
Co-authored-by: Lukas Tönne <lukas@blender.org>
Pull Request: https://projects.blender.org/blender/blender/pulls/104924
2023-05-03 13:18:51 +02:00
|
|
|
# Deselect all nodes in the tree.
|
|
|
|
|
@staticmethod
|
|
|
|
|
def deselect_nodes(context):
|
2012-12-12 12:50:43 +00:00
|
|
|
space = context.space_data
|
|
|
|
|
tree = space.edit_tree
|
|
|
|
|
for n in tree.nodes:
|
2012-12-12 15:41:15 +00:00
|
|
|
n.select = False
|
|
|
|
|
|
Geometry Nodes: add simulation support
This adds support for building simulations with geometry nodes. A new
`Simulation Input` and `Simulation Output` node allow maintaining a
simulation state across multiple frames. Together these two nodes form
a `simulation zone` which contains all the nodes that update the simulation
state from one frame to the next.
A new simulation zone can be added via the menu
(`Simulation > Simulation Zone`) or with the node add search.
The simulation state contains a geometry by default. However, it is possible
to add multiple geometry sockets as well as other socket types. Currently,
field inputs are evaluated and stored for the preceding geometry socket in
the order that the sockets are shown. Simulation state items can be added
by linking one of the empty sockets to something else. In the sidebar, there
is a new panel that allows adding, removing and reordering these sockets.
The simulation nodes behave as follows:
* On the first frame, the inputs of the `Simulation Input` node are evaluated
to initialize the simulation state. In later frames these sockets are not
evaluated anymore. The `Delta Time` at the first frame is zero, but the
simulation zone is still evaluated.
* On every next frame, the `Simulation Input` node outputs the simulation
state of the previous frame. Nodes in the simulation zone can edit that
data in arbitrary ways, also taking into account the `Delta Time`. The new
simulation state has to be passed to the `Simulation Output` node where it
is cached and forwarded.
* On a frame that is already cached or baked, the nodes in the simulation
zone are not evaluated, because the `Simulation Output` node can return
the previously cached data directly.
It is not allowed to connect sockets from inside the simulation zone to the
outside without going through the `Simulation Output` node. This is a necessary
restriction to make caching and sub-frame interpolation work. Links can go into
the simulation zone without problems though.
Anonymous attributes are not propagated by the simulation nodes unless they
are explicitly stored in the simulation state. This is unfortunate, but
currently there is no practical and reliable alternative. The core problem
is detecting which anonymous attributes will be required for the simulation
and afterwards. While we can detect this for the current evaluation, we can't
look into the future in time to see what data will be necessary. We intend to
make it easier to explicitly pass data through a simulation in the future,
even if the simulation is in a nested node group.
There is a new `Simulation Nodes` panel in the physics tab in the properties
editor. It allows baking all simulation zones on the selected objects. The
baking options are intentially kept at a minimum for this MVP. More features
for simulation baking as well as baking in general can be expected to be added
separately.
All baked data is stored on disk in a folder next to the .blend file. #106937
describes how baking is implemented in more detail. Volumes can not be baked
yet and materials are lost during baking for now. Packing the baked data into
the .blend file is not yet supported.
The timeline indicates which frames are currently cached, baked or cached but
invalidated by user-changes.
Simulation input and output nodes are internally linked together by their
`bNode.identifier` which stays the same even if the node name changes. They
are generally added and removed together. However, there are still cases where
"dangling" simulation nodes can be created currently. Those generally don't
cause harm, but would be nice to avoid this in more cases in the future.
Co-authored-by: Hans Goudey <h.goudey@me.com>
Co-authored-by: Lukas Tönne <lukas@blender.org>
Pull Request: https://projects.blender.org/blender/blender/pulls/104924
2023-05-03 13:18:51 +02:00
|
|
|
def create_node(self, context, node_type):
|
|
|
|
|
space = context.space_data
|
|
|
|
|
tree = space.edit_tree
|
|
|
|
|
|
2022-04-07 10:31:52 +02:00
|
|
|
try:
|
|
|
|
|
node = tree.nodes.new(type=node_type)
|
2023-07-30 16:14:13 +10:00
|
|
|
except RuntimeError as ex:
|
|
|
|
|
self.report({'ERROR'}, str(ex))
|
2022-04-07 10:31:52 +02:00
|
|
|
return None
|
2013-05-08 15:40:53 +00:00
|
|
|
|
|
|
|
|
for setting in self.settings:
|
|
|
|
|
# XXX catch exceptions here?
|
|
|
|
|
value = eval(setting.value)
|
2020-06-30 18:00:03 +02:00
|
|
|
node_data = node
|
|
|
|
|
node_attr_name = setting.name
|
|
|
|
|
|
|
|
|
|
# Support path to nested data.
|
|
|
|
|
if '.' in node_attr_name:
|
|
|
|
|
node_data_path, node_attr_name = node_attr_name.rsplit(".", 1)
|
|
|
|
|
node_data = node.path_resolve(node_data_path)
|
2013-06-27 03:05:19 +00:00
|
|
|
|
2013-05-08 15:40:53 +00:00
|
|
|
try:
|
2020-06-30 18:00:03 +02:00
|
|
|
setattr(node_data, node_attr_name, value)
|
2023-07-30 16:14:13 +10:00
|
|
|
except AttributeError as ex:
|
2016-03-23 04:25:08 +11:00
|
|
|
self.report(
|
2018-06-26 19:41:37 +02:00
|
|
|
{'ERROR_INVALID_INPUT'},
|
2024-04-27 16:02:37 +10:00
|
|
|
rpt_("Node has no attribute {:s}").format(setting.name))
|
2023-07-30 16:14:13 +10:00
|
|
|
print(str(ex))
|
2013-05-08 15:40:53 +00:00
|
|
|
# Continue despite invalid attribute
|
2012-12-12 15:41:15 +00:00
|
|
|
|
|
|
|
|
node.select = True
|
2012-12-12 12:50:43 +00:00
|
|
|
tree.nodes.active = node
|
|
|
|
|
node.location = space.cursor_location
|
|
|
|
|
return node
|
|
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
|
def poll(cls, context):
|
|
|
|
|
space = context.space_data
|
|
|
|
|
# needs active node editor and a tree to add nodes to
|
2021-05-18 20:00:51 +10:00
|
|
|
return (space and (space.type == 'NODE_EDITOR') and
|
2024-05-16 14:53:09 +02:00
|
|
|
space.edit_tree and space.edit_tree.is_editable)
|
2012-12-12 12:50:43 +00:00
|
|
|
|
|
|
|
|
# Default invoke stores the mouse position to place the node correctly
|
2013-05-08 15:40:53 +00:00
|
|
|
# and optionally invokes the transform operator
|
2012-12-12 12:50:43 +00:00
|
|
|
def invoke(self, context, event):
|
|
|
|
|
self.store_mouse_cursor(context, event)
|
2013-05-08 15:40:53 +00:00
|
|
|
result = self.execute(context)
|
2012-12-12 12:50:43 +00:00
|
|
|
|
2013-05-08 15:40:53 +00:00
|
|
|
if self.use_transform and ('FINISHED' in result):
|
2015-02-27 15:41:50 +11:00
|
|
|
# removes the node again if transform is canceled
|
2015-08-01 17:39:48 +02:00
|
|
|
bpy.ops.node.translate_attach_remove_on_cancel('INVOKE_DEFAULT')
|
2013-05-08 15:40:53 +00:00
|
|
|
|
|
|
|
|
return result
|
2012-12-12 12:50:43 +00:00
|
|
|
|
Geometry Nodes: add simulation support
This adds support for building simulations with geometry nodes. A new
`Simulation Input` and `Simulation Output` node allow maintaining a
simulation state across multiple frames. Together these two nodes form
a `simulation zone` which contains all the nodes that update the simulation
state from one frame to the next.
A new simulation zone can be added via the menu
(`Simulation > Simulation Zone`) or with the node add search.
The simulation state contains a geometry by default. However, it is possible
to add multiple geometry sockets as well as other socket types. Currently,
field inputs are evaluated and stored for the preceding geometry socket in
the order that the sockets are shown. Simulation state items can be added
by linking one of the empty sockets to something else. In the sidebar, there
is a new panel that allows adding, removing and reordering these sockets.
The simulation nodes behave as follows:
* On the first frame, the inputs of the `Simulation Input` node are evaluated
to initialize the simulation state. In later frames these sockets are not
evaluated anymore. The `Delta Time` at the first frame is zero, but the
simulation zone is still evaluated.
* On every next frame, the `Simulation Input` node outputs the simulation
state of the previous frame. Nodes in the simulation zone can edit that
data in arbitrary ways, also taking into account the `Delta Time`. The new
simulation state has to be passed to the `Simulation Output` node where it
is cached and forwarded.
* On a frame that is already cached or baked, the nodes in the simulation
zone are not evaluated, because the `Simulation Output` node can return
the previously cached data directly.
It is not allowed to connect sockets from inside the simulation zone to the
outside without going through the `Simulation Output` node. This is a necessary
restriction to make caching and sub-frame interpolation work. Links can go into
the simulation zone without problems though.
Anonymous attributes are not propagated by the simulation nodes unless they
are explicitly stored in the simulation state. This is unfortunate, but
currently there is no practical and reliable alternative. The core problem
is detecting which anonymous attributes will be required for the simulation
and afterwards. While we can detect this for the current evaluation, we can't
look into the future in time to see what data will be necessary. We intend to
make it easier to explicitly pass data through a simulation in the future,
even if the simulation is in a nested node group.
There is a new `Simulation Nodes` panel in the physics tab in the properties
editor. It allows baking all simulation zones on the selected objects. The
baking options are intentially kept at a minimum for this MVP. More features
for simulation baking as well as baking in general can be expected to be added
separately.
All baked data is stored on disk in a folder next to the .blend file. #106937
describes how baking is implemented in more detail. Volumes can not be baked
yet and materials are lost during baking for now. Packing the baked data into
the .blend file is not yet supported.
The timeline indicates which frames are currently cached, baked or cached but
invalidated by user-changes.
Simulation input and output nodes are internally linked together by their
`bNode.identifier` which stays the same even if the node name changes. They
are generally added and removed together. However, there are still cases where
"dangling" simulation nodes can be created currently. Those generally don't
cause harm, but would be nice to avoid this in more cases in the future.
Co-authored-by: Hans Goudey <h.goudey@me.com>
Co-authored-by: Lukas Tönne <lukas@blender.org>
Pull Request: https://projects.blender.org/blender/blender/pulls/104924
2023-05-03 13:18:51 +02:00
|
|
|
|
|
|
|
|
# Simple basic operator for adding a node.
|
|
|
|
|
class NODE_OT_add_node(NodeAddOperator, Operator):
|
|
|
|
|
"""Add a node to the active tree"""
|
|
|
|
|
bl_idname = "node.add_node"
|
|
|
|
|
bl_label = "Add Node"
|
|
|
|
|
bl_options = {'REGISTER', 'UNDO'}
|
|
|
|
|
|
|
|
|
|
type: StringProperty(
|
|
|
|
|
name="Node Type",
|
|
|
|
|
description="Node type",
|
|
|
|
|
)
|
|
|
|
|
|
Nodes: support searching for outputs of various input nodes directly
Previously, one had to search for the name of an input node (Geometry, Light
Path, etc.) instead of for the actual desired values.
This patch makes it possible to search for the output names of various input
nodes directly. All other outputs of the input node are hidden automatically.
This was partially support for the Scene Time before.
Supported nodes:
* Compositor: Scene Time
* Geometry Nodes: Camera Info, Mouse Position, Scene Time, Viewport Transform
* Shader Nodes: Camera Data, Curves Info, Geometry, Volume Info, Light Path,
Object Info, Particle Info
Right now, the output names are hardcoded in the menu. We don't have a great way
to access those without an actual node instance currently. For that we'll need
to make the node declarations available in Python, which is a good project but
out of scope for this this feature. It also does not seem too bad to have more
explicit control over what's shown in the search.
Pull Request: https://projects.blender.org/blender/blender/pulls/139477
2025-05-28 05:41:37 +02:00
|
|
|
visible_output: StringProperty(
|
|
|
|
|
name="Output Name",
|
|
|
|
|
description="If provided, all outputs that are named differently will be hidden",
|
|
|
|
|
options={'SKIP_SAVE'},
|
|
|
|
|
)
|
|
|
|
|
|
Geometry Nodes: add simulation support
This adds support for building simulations with geometry nodes. A new
`Simulation Input` and `Simulation Output` node allow maintaining a
simulation state across multiple frames. Together these two nodes form
a `simulation zone` which contains all the nodes that update the simulation
state from one frame to the next.
A new simulation zone can be added via the menu
(`Simulation > Simulation Zone`) or with the node add search.
The simulation state contains a geometry by default. However, it is possible
to add multiple geometry sockets as well as other socket types. Currently,
field inputs are evaluated and stored for the preceding geometry socket in
the order that the sockets are shown. Simulation state items can be added
by linking one of the empty sockets to something else. In the sidebar, there
is a new panel that allows adding, removing and reordering these sockets.
The simulation nodes behave as follows:
* On the first frame, the inputs of the `Simulation Input` node are evaluated
to initialize the simulation state. In later frames these sockets are not
evaluated anymore. The `Delta Time` at the first frame is zero, but the
simulation zone is still evaluated.
* On every next frame, the `Simulation Input` node outputs the simulation
state of the previous frame. Nodes in the simulation zone can edit that
data in arbitrary ways, also taking into account the `Delta Time`. The new
simulation state has to be passed to the `Simulation Output` node where it
is cached and forwarded.
* On a frame that is already cached or baked, the nodes in the simulation
zone are not evaluated, because the `Simulation Output` node can return
the previously cached data directly.
It is not allowed to connect sockets from inside the simulation zone to the
outside without going through the `Simulation Output` node. This is a necessary
restriction to make caching and sub-frame interpolation work. Links can go into
the simulation zone without problems though.
Anonymous attributes are not propagated by the simulation nodes unless they
are explicitly stored in the simulation state. This is unfortunate, but
currently there is no practical and reliable alternative. The core problem
is detecting which anonymous attributes will be required for the simulation
and afterwards. While we can detect this for the current evaluation, we can't
look into the future in time to see what data will be necessary. We intend to
make it easier to explicitly pass data through a simulation in the future,
even if the simulation is in a nested node group.
There is a new `Simulation Nodes` panel in the physics tab in the properties
editor. It allows baking all simulation zones on the selected objects. The
baking options are intentially kept at a minimum for this MVP. More features
for simulation baking as well as baking in general can be expected to be added
separately.
All baked data is stored on disk in a folder next to the .blend file. #106937
describes how baking is implemented in more detail. Volumes can not be baked
yet and materials are lost during baking for now. Packing the baked data into
the .blend file is not yet supported.
The timeline indicates which frames are currently cached, baked or cached but
invalidated by user-changes.
Simulation input and output nodes are internally linked together by their
`bNode.identifier` which stays the same even if the node name changes. They
are generally added and removed together. However, there are still cases where
"dangling" simulation nodes can be created currently. Those generally don't
cause harm, but would be nice to avoid this in more cases in the future.
Co-authored-by: Hans Goudey <h.goudey@me.com>
Co-authored-by: Lukas Tönne <lukas@blender.org>
Pull Request: https://projects.blender.org/blender/blender/pulls/104924
2023-05-03 13:18:51 +02:00
|
|
|
# Default execute simply adds a node.
|
|
|
|
|
def execute(self, context):
|
|
|
|
|
if self.properties.is_property_set("type"):
|
|
|
|
|
self.deselect_nodes(context)
|
Nodes: support searching for outputs of various input nodes directly
Previously, one had to search for the name of an input node (Geometry, Light
Path, etc.) instead of for the actual desired values.
This patch makes it possible to search for the output names of various input
nodes directly. All other outputs of the input node are hidden automatically.
This was partially support for the Scene Time before.
Supported nodes:
* Compositor: Scene Time
* Geometry Nodes: Camera Info, Mouse Position, Scene Time, Viewport Transform
* Shader Nodes: Camera Data, Curves Info, Geometry, Volume Info, Light Path,
Object Info, Particle Info
Right now, the output names are hardcoded in the menu. We don't have a great way
to access those without an actual node instance currently. For that we'll need
to make the node declarations available in Python, which is a good project but
out of scope for this this feature. It also does not seem too bad to have more
explicit control over what's shown in the search.
Pull Request: https://projects.blender.org/blender/blender/pulls/139477
2025-05-28 05:41:37 +02:00
|
|
|
if node := self.create_node(context, self.type):
|
|
|
|
|
if self.visible_output:
|
|
|
|
|
for socket in node.outputs:
|
|
|
|
|
if socket.name != self.visible_output:
|
|
|
|
|
socket.hide = True
|
Geometry Nodes: add simulation support
This adds support for building simulations with geometry nodes. A new
`Simulation Input` and `Simulation Output` node allow maintaining a
simulation state across multiple frames. Together these two nodes form
a `simulation zone` which contains all the nodes that update the simulation
state from one frame to the next.
A new simulation zone can be added via the menu
(`Simulation > Simulation Zone`) or with the node add search.
The simulation state contains a geometry by default. However, it is possible
to add multiple geometry sockets as well as other socket types. Currently,
field inputs are evaluated and stored for the preceding geometry socket in
the order that the sockets are shown. Simulation state items can be added
by linking one of the empty sockets to something else. In the sidebar, there
is a new panel that allows adding, removing and reordering these sockets.
The simulation nodes behave as follows:
* On the first frame, the inputs of the `Simulation Input` node are evaluated
to initialize the simulation state. In later frames these sockets are not
evaluated anymore. The `Delta Time` at the first frame is zero, but the
simulation zone is still evaluated.
* On every next frame, the `Simulation Input` node outputs the simulation
state of the previous frame. Nodes in the simulation zone can edit that
data in arbitrary ways, also taking into account the `Delta Time`. The new
simulation state has to be passed to the `Simulation Output` node where it
is cached and forwarded.
* On a frame that is already cached or baked, the nodes in the simulation
zone are not evaluated, because the `Simulation Output` node can return
the previously cached data directly.
It is not allowed to connect sockets from inside the simulation zone to the
outside without going through the `Simulation Output` node. This is a necessary
restriction to make caching and sub-frame interpolation work. Links can go into
the simulation zone without problems though.
Anonymous attributes are not propagated by the simulation nodes unless they
are explicitly stored in the simulation state. This is unfortunate, but
currently there is no practical and reliable alternative. The core problem
is detecting which anonymous attributes will be required for the simulation
and afterwards. While we can detect this for the current evaluation, we can't
look into the future in time to see what data will be necessary. We intend to
make it easier to explicitly pass data through a simulation in the future,
even if the simulation is in a nested node group.
There is a new `Simulation Nodes` panel in the physics tab in the properties
editor. It allows baking all simulation zones on the selected objects. The
baking options are intentially kept at a minimum for this MVP. More features
for simulation baking as well as baking in general can be expected to be added
separately.
All baked data is stored on disk in a folder next to the .blend file. #106937
describes how baking is implemented in more detail. Volumes can not be baked
yet and materials are lost during baking for now. Packing the baked data into
the .blend file is not yet supported.
The timeline indicates which frames are currently cached, baked or cached but
invalidated by user-changes.
Simulation input and output nodes are internally linked together by their
`bNode.identifier` which stays the same even if the node name changes. They
are generally added and removed together. However, there are still cases where
"dangling" simulation nodes can be created currently. Those generally don't
cause harm, but would be nice to avoid this in more cases in the future.
Co-authored-by: Hans Goudey <h.goudey@me.com>
Co-authored-by: Lukas Tönne <lukas@blender.org>
Pull Request: https://projects.blender.org/blender/blender/pulls/104924
2023-05-03 13:18:51 +02:00
|
|
|
return {'FINISHED'}
|
|
|
|
|
else:
|
|
|
|
|
return {'CANCELLED'}
|
|
|
|
|
|
2022-06-07 15:40:20 +02:00
|
|
|
@classmethod
|
2022-06-14 14:30:09 +10:00
|
|
|
def description(cls, _context, properties):
|
2024-10-30 15:16:25 +11:00
|
|
|
from nodeitems_builtins import node_tree_group_type
|
|
|
|
|
|
2022-06-07 15:40:20 +02:00
|
|
|
nodetype = properties["type"]
|
2024-05-08 11:25:00 +02:00
|
|
|
if nodetype in node_tree_group_type.values():
|
|
|
|
|
for setting in properties.settings:
|
|
|
|
|
if setting.name == "node_tree":
|
|
|
|
|
node_group = eval(setting.value)
|
|
|
|
|
if node_group.description:
|
|
|
|
|
return node_group.description
|
2022-06-07 15:40:20 +02:00
|
|
|
bl_rna = bpy.types.Node.bl_rna_get_subclass(nodetype)
|
|
|
|
|
if bl_rna is not None:
|
2022-07-18 15:09:37 +02:00
|
|
|
return tip_(bl_rna.description)
|
2022-06-07 15:40:20 +02:00
|
|
|
else:
|
|
|
|
|
return ""
|
|
|
|
|
|
Replacing the node Add menu and making the toolbar useful
As some people have already noticed, the "Add" menu for nodes is a bit messy since pynodes merge. The reason for this is that the order of nodes in submenus (categories) was previously defined by the order in which all nodes are registered (at the bottom of blenkernel/intern/node.c). For the dynamic registration of node types now possible this system of defining node order along with registration is no longer viable: while it would still sort of work for C nodes, it is completely meaningless for dynamic (python) nodes, which are basically registered automatically in whatever order modules and addons are loaded, with the added complexity of unloading and reloading.
To fix this problem and add a bunch of desirable features this commit replaces the C menu with a python implementation. The new menu does not rely on any particular order of types in the node registry, but instead uses a simple explicit list of all the available nodes, grouped by categories (in scripts/nodeitems_builtins.py).
There are a number of additional features that become possible with this implementation:
1) Node Toolbar can be populated!
The list of nodes is used to create 2 UI items for each node: 1 entry in a submenu of "Add" menu and 1 item in a node toolbar panel with basically the same functionality. Clicking a button in the toolbar will add a new node of this type, just like selecting an item in the menu. The toolbar has the advantage of having collapsible panels for each category, so users can decide if they don't need certain nodes categories and have the rest more easily accessible.
2) Each node item is a true operator call.
The old Add menu is a pretty old piece of C code which doesn't even use proper operator buttons. Now there is a generic node_add operator which can be used very flexibly for adding any of the available nodes.
3) Node Items support additional settings.
Each "NodeItem" consists of the basic node type plus an optional list of initial settings that shall be applied to a new instance. This gives additional flexibility for creating variants of the same node or for defining preferred initial settings. E.g. it has been requested to disable previews for all nodes except inputs, this would be simple change in the py code and much less intrusive than in C.
4) Node items can be generated with a function.
A callback can be used in any category instead of the fixed list, which generates a set of items based on the context (much like dynamic enum items in bpy.props). Originally this was implemented for group nodes, because these nodes only make sense when linked to a node tree from the library data. This principle could come in handy for a number of other nodes, e.g. Image nodes could provide a similar list of node variants based on images in the library - no need to first add node, then select an image.
WARNING: pynodes scripters will have to rework their "draw_add_menu" callback in node tree types, this has been removed now! It was already pretty redundant, since one can add draw functions to the Add menu just like for any other menu. In the future i'd like to improve the categories system further so scripters can use it for custom node systems too, for now just make a draw callback and attach it to the Add menu.
2013-04-13 15:38:02 +00:00
|
|
|
|
2025-05-08 10:17:56 +02:00
|
|
|
class NODE_OT_add_empty_group(NodeAddOperator, bpy.types.Operator):
|
|
|
|
|
bl_idname = "node.add_empty_group"
|
|
|
|
|
bl_label = "Add Empty Group"
|
|
|
|
|
bl_description = "Add a group node with an empty group"
|
|
|
|
|
bl_options = {'REGISTER', 'UNDO'}
|
|
|
|
|
|
|
|
|
|
def execute(self, context):
|
|
|
|
|
from nodeitems_builtins import node_tree_group_type
|
|
|
|
|
tree = context.space_data.edit_tree
|
|
|
|
|
group = self.create_empty_group(tree.bl_idname)
|
|
|
|
|
self.deselect_nodes(context)
|
|
|
|
|
node = self.create_node(context, node_tree_group_type[tree.bl_idname])
|
|
|
|
|
node.node_tree = group
|
|
|
|
|
return {"FINISHED"}
|
|
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
|
def create_empty_group(idname):
|
|
|
|
|
group = bpy.data.node_groups.new(name="NodeGroup", type=idname)
|
|
|
|
|
input_node = group.nodes.new('NodeGroupInput')
|
|
|
|
|
input_node.select = False
|
|
|
|
|
input_node.location.x = -200 - input_node.width
|
|
|
|
|
|
|
|
|
|
output_node = group.nodes.new('NodeGroupOutput')
|
|
|
|
|
output_node.is_active_output = True
|
|
|
|
|
output_node.select = False
|
|
|
|
|
output_node.location.x = 200
|
|
|
|
|
return group
|
|
|
|
|
|
|
|
|
|
|
2023-07-11 22:36:10 +02:00
|
|
|
class NodeAddZoneOperator(NodeAddOperator):
|
Geometry Nodes: add simulation support
This adds support for building simulations with geometry nodes. A new
`Simulation Input` and `Simulation Output` node allow maintaining a
simulation state across multiple frames. Together these two nodes form
a `simulation zone` which contains all the nodes that update the simulation
state from one frame to the next.
A new simulation zone can be added via the menu
(`Simulation > Simulation Zone`) or with the node add search.
The simulation state contains a geometry by default. However, it is possible
to add multiple geometry sockets as well as other socket types. Currently,
field inputs are evaluated and stored for the preceding geometry socket in
the order that the sockets are shown. Simulation state items can be added
by linking one of the empty sockets to something else. In the sidebar, there
is a new panel that allows adding, removing and reordering these sockets.
The simulation nodes behave as follows:
* On the first frame, the inputs of the `Simulation Input` node are evaluated
to initialize the simulation state. In later frames these sockets are not
evaluated anymore. The `Delta Time` at the first frame is zero, but the
simulation zone is still evaluated.
* On every next frame, the `Simulation Input` node outputs the simulation
state of the previous frame. Nodes in the simulation zone can edit that
data in arbitrary ways, also taking into account the `Delta Time`. The new
simulation state has to be passed to the `Simulation Output` node where it
is cached and forwarded.
* On a frame that is already cached or baked, the nodes in the simulation
zone are not evaluated, because the `Simulation Output` node can return
the previously cached data directly.
It is not allowed to connect sockets from inside the simulation zone to the
outside without going through the `Simulation Output` node. This is a necessary
restriction to make caching and sub-frame interpolation work. Links can go into
the simulation zone without problems though.
Anonymous attributes are not propagated by the simulation nodes unless they
are explicitly stored in the simulation state. This is unfortunate, but
currently there is no practical and reliable alternative. The core problem
is detecting which anonymous attributes will be required for the simulation
and afterwards. While we can detect this for the current evaluation, we can't
look into the future in time to see what data will be necessary. We intend to
make it easier to explicitly pass data through a simulation in the future,
even if the simulation is in a nested node group.
There is a new `Simulation Nodes` panel in the physics tab in the properties
editor. It allows baking all simulation zones on the selected objects. The
baking options are intentially kept at a minimum for this MVP. More features
for simulation baking as well as baking in general can be expected to be added
separately.
All baked data is stored on disk in a folder next to the .blend file. #106937
describes how baking is implemented in more detail. Volumes can not be baked
yet and materials are lost during baking for now. Packing the baked data into
the .blend file is not yet supported.
The timeline indicates which frames are currently cached, baked or cached but
invalidated by user-changes.
Simulation input and output nodes are internally linked together by their
`bNode.identifier` which stays the same even if the node name changes. They
are generally added and removed together. However, there are still cases where
"dangling" simulation nodes can be created currently. Those generally don't
cause harm, but would be nice to avoid this in more cases in the future.
Co-authored-by: Hans Goudey <h.goudey@me.com>
Co-authored-by: Lukas Tönne <lukas@blender.org>
Pull Request: https://projects.blender.org/blender/blender/pulls/104924
2023-05-03 13:18:51 +02:00
|
|
|
offset: FloatVectorProperty(
|
|
|
|
|
name="Offset",
|
|
|
|
|
description="Offset of nodes from the cursor when added",
|
|
|
|
|
size=2,
|
|
|
|
|
default=(150, 0),
|
|
|
|
|
)
|
|
|
|
|
|
2024-09-24 11:52:02 +02:00
|
|
|
add_default_geometry_link = True
|
|
|
|
|
|
Geometry Nodes: add simulation support
This adds support for building simulations with geometry nodes. A new
`Simulation Input` and `Simulation Output` node allow maintaining a
simulation state across multiple frames. Together these two nodes form
a `simulation zone` which contains all the nodes that update the simulation
state from one frame to the next.
A new simulation zone can be added via the menu
(`Simulation > Simulation Zone`) or with the node add search.
The simulation state contains a geometry by default. However, it is possible
to add multiple geometry sockets as well as other socket types. Currently,
field inputs are evaluated and stored for the preceding geometry socket in
the order that the sockets are shown. Simulation state items can be added
by linking one of the empty sockets to something else. In the sidebar, there
is a new panel that allows adding, removing and reordering these sockets.
The simulation nodes behave as follows:
* On the first frame, the inputs of the `Simulation Input` node are evaluated
to initialize the simulation state. In later frames these sockets are not
evaluated anymore. The `Delta Time` at the first frame is zero, but the
simulation zone is still evaluated.
* On every next frame, the `Simulation Input` node outputs the simulation
state of the previous frame. Nodes in the simulation zone can edit that
data in arbitrary ways, also taking into account the `Delta Time`. The new
simulation state has to be passed to the `Simulation Output` node where it
is cached and forwarded.
* On a frame that is already cached or baked, the nodes in the simulation
zone are not evaluated, because the `Simulation Output` node can return
the previously cached data directly.
It is not allowed to connect sockets from inside the simulation zone to the
outside without going through the `Simulation Output` node. This is a necessary
restriction to make caching and sub-frame interpolation work. Links can go into
the simulation zone without problems though.
Anonymous attributes are not propagated by the simulation nodes unless they
are explicitly stored in the simulation state. This is unfortunate, but
currently there is no practical and reliable alternative. The core problem
is detecting which anonymous attributes will be required for the simulation
and afterwards. While we can detect this for the current evaluation, we can't
look into the future in time to see what data will be necessary. We intend to
make it easier to explicitly pass data through a simulation in the future,
even if the simulation is in a nested node group.
There is a new `Simulation Nodes` panel in the physics tab in the properties
editor. It allows baking all simulation zones on the selected objects. The
baking options are intentially kept at a minimum for this MVP. More features
for simulation baking as well as baking in general can be expected to be added
separately.
All baked data is stored on disk in a folder next to the .blend file. #106937
describes how baking is implemented in more detail. Volumes can not be baked
yet and materials are lost during baking for now. Packing the baked data into
the .blend file is not yet supported.
The timeline indicates which frames are currently cached, baked or cached but
invalidated by user-changes.
Simulation input and output nodes are internally linked together by their
`bNode.identifier` which stays the same even if the node name changes. They
are generally added and removed together. However, there are still cases where
"dangling" simulation nodes can be created currently. Those generally don't
cause harm, but would be nice to avoid this in more cases in the future.
Co-authored-by: Hans Goudey <h.goudey@me.com>
Co-authored-by: Lukas Tönne <lukas@blender.org>
Pull Request: https://projects.blender.org/blender/blender/pulls/104924
2023-05-03 13:18:51 +02:00
|
|
|
def execute(self, context):
|
|
|
|
|
space = context.space_data
|
|
|
|
|
tree = space.edit_tree
|
|
|
|
|
|
|
|
|
|
self.deselect_nodes(context)
|
|
|
|
|
input_node = self.create_node(context, self.input_node_type)
|
|
|
|
|
output_node = self.create_node(context, self.output_node_type)
|
|
|
|
|
if input_node is None or output_node is None:
|
|
|
|
|
return {'CANCELLED'}
|
|
|
|
|
|
|
|
|
|
# Simulation input must be paired with the output.
|
|
|
|
|
input_node.pair_with_output(output_node)
|
|
|
|
|
|
|
|
|
|
input_node.location -= Vector(self.offset)
|
|
|
|
|
output_node.location += Vector(self.offset)
|
|
|
|
|
|
2024-09-24 11:52:02 +02:00
|
|
|
if self.add_default_geometry_link:
|
|
|
|
|
# Connect geometry sockets by default if available.
|
|
|
|
|
# Get the sockets by their types, because the name is not guaranteed due to i18n.
|
|
|
|
|
from_socket = next(s for s in input_node.outputs if s.type == 'GEOMETRY')
|
|
|
|
|
to_socket = next(s for s in output_node.inputs if s.type == 'GEOMETRY')
|
|
|
|
|
tree.links.new(to_socket, from_socket)
|
Geometry Nodes: add simulation support
This adds support for building simulations with geometry nodes. A new
`Simulation Input` and `Simulation Output` node allow maintaining a
simulation state across multiple frames. Together these two nodes form
a `simulation zone` which contains all the nodes that update the simulation
state from one frame to the next.
A new simulation zone can be added via the menu
(`Simulation > Simulation Zone`) or with the node add search.
The simulation state contains a geometry by default. However, it is possible
to add multiple geometry sockets as well as other socket types. Currently,
field inputs are evaluated and stored for the preceding geometry socket in
the order that the sockets are shown. Simulation state items can be added
by linking one of the empty sockets to something else. In the sidebar, there
is a new panel that allows adding, removing and reordering these sockets.
The simulation nodes behave as follows:
* On the first frame, the inputs of the `Simulation Input` node are evaluated
to initialize the simulation state. In later frames these sockets are not
evaluated anymore. The `Delta Time` at the first frame is zero, but the
simulation zone is still evaluated.
* On every next frame, the `Simulation Input` node outputs the simulation
state of the previous frame. Nodes in the simulation zone can edit that
data in arbitrary ways, also taking into account the `Delta Time`. The new
simulation state has to be passed to the `Simulation Output` node where it
is cached and forwarded.
* On a frame that is already cached or baked, the nodes in the simulation
zone are not evaluated, because the `Simulation Output` node can return
the previously cached data directly.
It is not allowed to connect sockets from inside the simulation zone to the
outside without going through the `Simulation Output` node. This is a necessary
restriction to make caching and sub-frame interpolation work. Links can go into
the simulation zone without problems though.
Anonymous attributes are not propagated by the simulation nodes unless they
are explicitly stored in the simulation state. This is unfortunate, but
currently there is no practical and reliable alternative. The core problem
is detecting which anonymous attributes will be required for the simulation
and afterwards. While we can detect this for the current evaluation, we can't
look into the future in time to see what data will be necessary. We intend to
make it easier to explicitly pass data through a simulation in the future,
even if the simulation is in a nested node group.
There is a new `Simulation Nodes` panel in the physics tab in the properties
editor. It allows baking all simulation zones on the selected objects. The
baking options are intentially kept at a minimum for this MVP. More features
for simulation baking as well as baking in general can be expected to be added
separately.
All baked data is stored on disk in a folder next to the .blend file. #106937
describes how baking is implemented in more detail. Volumes can not be baked
yet and materials are lost during baking for now. Packing the baked data into
the .blend file is not yet supported.
The timeline indicates which frames are currently cached, baked or cached but
invalidated by user-changes.
Simulation input and output nodes are internally linked together by their
`bNode.identifier` which stays the same even if the node name changes. They
are generally added and removed together. However, there are still cases where
"dangling" simulation nodes can be created currently. Those generally don't
cause harm, but would be nice to avoid this in more cases in the future.
Co-authored-by: Hans Goudey <h.goudey@me.com>
Co-authored-by: Lukas Tönne <lukas@blender.org>
Pull Request: https://projects.blender.org/blender/blender/pulls/104924
2023-05-03 13:18:51 +02:00
|
|
|
|
|
|
|
|
return {'FINISHED'}
|
|
|
|
|
|
2023-07-12 11:55:18 +02:00
|
|
|
|
2023-07-11 22:36:10 +02:00
|
|
|
class NODE_OT_add_simulation_zone(NodeAddZoneOperator, Operator):
|
|
|
|
|
"""Add simulation zone input and output nodes to the active tree"""
|
|
|
|
|
bl_idname = "node.add_simulation_zone"
|
|
|
|
|
bl_label = "Add Simulation Zone"
|
|
|
|
|
bl_options = {'REGISTER', 'UNDO'}
|
|
|
|
|
|
|
|
|
|
input_node_type = "GeometryNodeSimulationInput"
|
|
|
|
|
output_node_type = "GeometryNodeSimulationOutput"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class NODE_OT_add_repeat_zone(NodeAddZoneOperator, Operator):
|
|
|
|
|
"""Add a repeat zone that allows executing nodes a dynamic number of times"""
|
|
|
|
|
bl_idname = "node.add_repeat_zone"
|
|
|
|
|
bl_label = "Add Repeat Zone"
|
|
|
|
|
bl_options = {'REGISTER', 'UNDO'}
|
|
|
|
|
|
|
|
|
|
input_node_type = "GeometryNodeRepeatInput"
|
|
|
|
|
output_node_type = "GeometryNodeRepeatOutput"
|
|
|
|
|
|
|
|
|
|
|
2024-09-24 11:52:02 +02:00
|
|
|
class NODE_OT_add_foreach_geometry_element_zone(NodeAddZoneOperator, Operator):
|
|
|
|
|
"""Add a For Each Geometry Element zone that allows executing nodes e.g. for each vertex separately"""
|
|
|
|
|
bl_idname = "node.add_foreach_geometry_element_zone"
|
|
|
|
|
bl_label = "Add For Each Geometry Element Zone"
|
|
|
|
|
bl_options = {'REGISTER', 'UNDO'}
|
|
|
|
|
|
|
|
|
|
input_node_type = "GeometryNodeForeachGeometryElementInput"
|
|
|
|
|
output_node_type = "GeometryNodeForeachGeometryElementOutput"
|
|
|
|
|
add_default_geometry_link = False
|
|
|
|
|
|
|
|
|
|
|
2025-04-03 15:44:06 +02:00
|
|
|
class NODE_OT_add_closure_zone(NodeAddZoneOperator, Operator):
|
|
|
|
|
"""Add a Closure zone"""
|
|
|
|
|
bl_idname = "node.add_closure_zone"
|
|
|
|
|
bl_label = "Add Closure Zone"
|
|
|
|
|
bl_options = {'REGISTER', 'UNDO'}
|
|
|
|
|
|
2025-08-02 10:17:39 +02:00
|
|
|
input_node_type = "NodeClosureInput"
|
|
|
|
|
output_node_type = "NodeClosureOutput"
|
2025-04-03 15:44:06 +02:00
|
|
|
add_default_geometry_link = False
|
|
|
|
|
|
|
|
|
|
|
2012-08-14 17:56:33 +00:00
|
|
|
class NODE_OT_collapse_hide_unused_toggle(Operator):
|
2023-04-18 10:42:00 +10:00
|
|
|
"""Toggle collapsed nodes and hide unused sockets"""
|
2012-08-14 17:56:33 +00:00
|
|
|
bl_idname = "node.collapse_hide_unused_toggle"
|
|
|
|
|
bl_label = "Collapse and Hide Unused Sockets"
|
|
|
|
|
bl_options = {'REGISTER', 'UNDO'}
|
|
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
|
def poll(cls, context):
|
|
|
|
|
space = context.space_data
|
|
|
|
|
# needs active node editor and a tree
|
2021-05-18 20:00:51 +10:00
|
|
|
return (space and (space.type == 'NODE_EDITOR') and
|
2024-05-16 14:53:09 +02:00
|
|
|
(space.edit_tree and space.edit_tree.is_editable))
|
2012-08-14 17:56:33 +00:00
|
|
|
|
|
|
|
|
def execute(self, context):
|
|
|
|
|
space = context.space_data
|
|
|
|
|
tree = space.edit_tree
|
|
|
|
|
|
|
|
|
|
for node in tree.nodes:
|
|
|
|
|
if node.select:
|
2012-08-14 18:43:15 +00:00
|
|
|
hide = (not node.hide)
|
|
|
|
|
|
2012-08-14 17:56:33 +00:00
|
|
|
node.hide = hide
|
|
|
|
|
# Note: connected sockets are ignored internally
|
|
|
|
|
for socket in node.inputs:
|
|
|
|
|
socket.hide = hide
|
|
|
|
|
for socket in node.outputs:
|
|
|
|
|
socket.hide = hide
|
|
|
|
|
|
2012-08-14 18:43:15 +00:00
|
|
|
return {'FINISHED'}
|
2013-03-18 16:34:57 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
class NODE_OT_tree_path_parent(Operator):
|
2023-04-18 10:42:00 +10:00
|
|
|
"""Go to parent node tree"""
|
2013-03-18 16:34:57 +00:00
|
|
|
bl_idname = "node.tree_path_parent"
|
|
|
|
|
bl_label = "Parent Node Tree"
|
|
|
|
|
bl_options = {'REGISTER', 'UNDO'}
|
|
|
|
|
|
2025-08-20 17:53:34 +02:00
|
|
|
parent_tree_index: IntProperty(
|
|
|
|
|
name="Parent Index",
|
|
|
|
|
description="Parent index in context path",
|
|
|
|
|
default=0,
|
|
|
|
|
)
|
|
|
|
|
|
2013-03-18 16:34:57 +00:00
|
|
|
@classmethod
|
|
|
|
|
def poll(cls, context):
|
|
|
|
|
space = context.space_data
|
|
|
|
|
# needs active node editor and a tree
|
2021-05-18 20:00:51 +10:00
|
|
|
return (space and (space.type == 'NODE_EDITOR') and len(space.path) > 1)
|
2013-03-18 16:34:57 +00:00
|
|
|
|
|
|
|
|
def execute(self, context):
|
|
|
|
|
space = context.space_data
|
|
|
|
|
|
2025-08-20 17:53:34 +02:00
|
|
|
parent_number_to_pop = len(space.path) - 1 - self.parent_tree_index
|
|
|
|
|
for _ in range(parent_number_to_pop):
|
|
|
|
|
space.path.pop()
|
2013-03-18 16:34:57 +00:00
|
|
|
|
|
|
|
|
return {'FINISHED'}
|
2017-03-18 20:03:24 +11:00
|
|
|
|
|
|
|
|
|
2023-08-30 12:37:21 +02:00
|
|
|
class NodeInterfaceOperator():
|
|
|
|
|
@classmethod
|
|
|
|
|
def poll(cls, context):
|
|
|
|
|
space = context.space_data
|
|
|
|
|
if not space or space.type != 'NODE_EDITOR' or not space.edit_tree:
|
|
|
|
|
return False
|
|
|
|
|
if space.edit_tree.is_embedded_data:
|
|
|
|
|
return False
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class NODE_OT_interface_item_new(NodeInterfaceOperator, Operator):
|
2024-10-16 14:45:08 +11:00
|
|
|
"""Add a new item to the interface"""
|
2023-08-30 12:37:21 +02:00
|
|
|
bl_idname = "node.interface_item_new"
|
|
|
|
|
bl_label = "New Item"
|
|
|
|
|
bl_options = {'REGISTER', 'UNDO'}
|
|
|
|
|
|
2025-02-28 19:07:02 +01:00
|
|
|
def get_items(_self, context):
|
|
|
|
|
snode = context.space_data
|
|
|
|
|
tree = snode.edit_tree
|
|
|
|
|
interface = tree.interface
|
|
|
|
|
|
|
|
|
|
items = [
|
2023-08-30 12:37:21 +02:00
|
|
|
('INPUT', "Input", ""),
|
|
|
|
|
('OUTPUT', "Output", ""),
|
2023-09-29 14:32:54 +10:00
|
|
|
('PANEL', "Panel", ""),
|
2025-02-28 19:07:02 +01:00
|
|
|
]
|
|
|
|
|
|
|
|
|
|
active_item = interface.active
|
|
|
|
|
# Panels have the extra option to add a toggle.
|
2025-03-24 10:20:39 +02:00
|
|
|
if active_item and active_item.item_type == 'PANEL':
|
2025-02-28 19:07:02 +01:00
|
|
|
items.append(('PANEL_TOGGLE', "Panel Toggle", ""))
|
|
|
|
|
|
|
|
|
|
return items
|
|
|
|
|
|
|
|
|
|
item_type: EnumProperty(
|
|
|
|
|
name="Item Type",
|
|
|
|
|
description="Type of the item to create",
|
|
|
|
|
items=get_items,
|
|
|
|
|
default=0,
|
2023-08-30 12:37:21 +02:00
|
|
|
)
|
|
|
|
|
|
2023-10-06 14:33:35 +02:00
|
|
|
# Returns a valid socket type for the given tree or None.
|
|
|
|
|
@staticmethod
|
|
|
|
|
def find_valid_socket_type(tree):
|
|
|
|
|
socket_type = 'NodeSocketFloat'
|
2023-10-11 15:41:41 +02:00
|
|
|
# Socket type validation function is only available for custom
|
|
|
|
|
# node trees. Assume that 'NodeSocketFloat' is valid for
|
|
|
|
|
# built-in node tree types.
|
|
|
|
|
if not hasattr(tree, "valid_socket_type") or tree.valid_socket_type(socket_type):
|
2023-10-06 14:33:35 +02:00
|
|
|
return socket_type
|
|
|
|
|
# Custom nodes may not support float sockets, search all
|
|
|
|
|
# registered socket subclasses.
|
|
|
|
|
types_to_check = [bpy.types.NodeSocket]
|
|
|
|
|
while types_to_check:
|
|
|
|
|
t = types_to_check.pop()
|
|
|
|
|
idname = getattr(t, "bl_idname", "")
|
|
|
|
|
if tree.valid_socket_type(idname):
|
|
|
|
|
return idname
|
|
|
|
|
# Test all subclasses
|
|
|
|
|
types_to_check.extend(t.__subclasses__())
|
2023-08-30 12:37:21 +02:00
|
|
|
|
|
|
|
|
def execute(self, context):
|
|
|
|
|
snode = context.space_data
|
|
|
|
|
tree = snode.edit_tree
|
|
|
|
|
interface = tree.interface
|
|
|
|
|
|
|
|
|
|
# Remember active item and position to determine target position.
|
|
|
|
|
active_item = interface.active
|
|
|
|
|
active_pos = active_item.position if active_item else -1
|
|
|
|
|
|
|
|
|
|
if self.item_type == 'INPUT':
|
2023-10-06 14:33:35 +02:00
|
|
|
item = interface.new_socket("Socket", socket_type=self.find_valid_socket_type(tree), in_out='INPUT')
|
2023-08-30 12:37:21 +02:00
|
|
|
elif self.item_type == 'OUTPUT':
|
2023-10-06 14:33:35 +02:00
|
|
|
item = interface.new_socket("Socket", socket_type=self.find_valid_socket_type(tree), in_out='OUTPUT')
|
2023-08-30 12:37:21 +02:00
|
|
|
elif self.item_type == 'PANEL':
|
|
|
|
|
item = interface.new_panel("Panel")
|
2025-02-28 19:07:02 +01:00
|
|
|
elif self.item_type == 'PANEL_TOGGLE':
|
|
|
|
|
active_panel = active_item
|
|
|
|
|
if len(active_panel.interface_items) > 0:
|
|
|
|
|
first_item = active_panel.interface_items[0]
|
|
|
|
|
if type(first_item) is bpy.types.NodeTreeInterfaceSocketBool and first_item.is_panel_toggle:
|
|
|
|
|
self.report({'INFO'}, "Panel already has a toggle")
|
|
|
|
|
return {'CANCELLED'}
|
|
|
|
|
item = interface.new_socket(active_panel.name, socket_type='NodeSocketBool', in_out='INPUT')
|
|
|
|
|
item.is_panel_toggle = True
|
|
|
|
|
interface.move_to_parent(item, active_panel, 0)
|
|
|
|
|
# Return in this case because we don't want to move the item.
|
|
|
|
|
return {'FINISHED'}
|
2023-08-30 12:37:21 +02:00
|
|
|
else:
|
|
|
|
|
return {'CANCELLED'}
|
|
|
|
|
|
|
|
|
|
if active_item:
|
|
|
|
|
# Insert into active panel if possible, otherwise insert after active item.
|
|
|
|
|
if active_item.item_type == 'PANEL' and item.item_type != 'PANEL':
|
|
|
|
|
interface.move_to_parent(item, active_item, len(active_item.interface_items))
|
|
|
|
|
else:
|
|
|
|
|
interface.move_to_parent(item, active_item.parent, active_pos + 1)
|
|
|
|
|
interface.active = item
|
|
|
|
|
|
|
|
|
|
return {'FINISHED'}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class NODE_OT_interface_item_duplicate(NodeInterfaceOperator, Operator):
|
2024-10-16 14:45:08 +11:00
|
|
|
"""Add a copy of the active item to the interface"""
|
2023-08-30 12:37:21 +02:00
|
|
|
bl_idname = "node.interface_item_duplicate"
|
|
|
|
|
bl_label = "Duplicate Item"
|
|
|
|
|
bl_options = {'REGISTER', 'UNDO'}
|
|
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
|
def poll(cls, context):
|
|
|
|
|
if not super().poll(context):
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
snode = context.space_data
|
|
|
|
|
tree = snode.edit_tree
|
|
|
|
|
interface = tree.interface
|
|
|
|
|
return interface.active is not None
|
|
|
|
|
|
|
|
|
|
def execute(self, context):
|
|
|
|
|
snode = context.space_data
|
|
|
|
|
tree = snode.edit_tree
|
|
|
|
|
interface = tree.interface
|
|
|
|
|
item = interface.active
|
|
|
|
|
|
|
|
|
|
if item:
|
|
|
|
|
item_copy = interface.copy(item)
|
|
|
|
|
interface.active = item_copy
|
|
|
|
|
|
|
|
|
|
return {'FINISHED'}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class NODE_OT_interface_item_remove(NodeInterfaceOperator, Operator):
|
2024-10-16 14:45:08 +11:00
|
|
|
"""Remove active item from the interface"""
|
2023-08-30 12:37:21 +02:00
|
|
|
bl_idname = "node.interface_item_remove"
|
|
|
|
|
bl_label = "Remove Item"
|
|
|
|
|
bl_options = {'REGISTER', 'UNDO'}
|
|
|
|
|
|
|
|
|
|
def execute(self, context):
|
|
|
|
|
snode = context.space_data
|
|
|
|
|
tree = snode.edit_tree
|
|
|
|
|
interface = tree.interface
|
|
|
|
|
item = interface.active
|
|
|
|
|
|
|
|
|
|
if item:
|
2025-04-16 12:09:40 +02:00
|
|
|
if item.item_type == 'PANEL':
|
2025-06-11 14:25:56 +02:00
|
|
|
children = item.interface_items
|
|
|
|
|
if len(children) > 0:
|
|
|
|
|
first_child = children[0]
|
|
|
|
|
if isinstance(first_child, bpy.types.NodeTreeInterfaceSocket) and first_child.is_panel_toggle:
|
|
|
|
|
interface.remove(first_child)
|
2023-08-30 12:37:21 +02:00
|
|
|
interface.remove(item)
|
2023-09-18 18:07:26 +02:00
|
|
|
interface.active_index = min(interface.active_index, len(interface.items_tree) - 1)
|
2023-08-30 12:37:21 +02:00
|
|
|
|
2025-07-16 07:11:14 +02:00
|
|
|
# If the active selection lands on internal toggle socket, move selection to parent instead.
|
|
|
|
|
new_active = interface.active
|
|
|
|
|
if isinstance(new_active, bpy.types.NodeTreeInterfaceSocket) and new_active.is_panel_toggle:
|
|
|
|
|
interface.active_index = new_active.parent.index
|
|
|
|
|
|
2023-08-30 12:37:21 +02:00
|
|
|
return {'FINISHED'}
|
|
|
|
|
|
|
|
|
|
|
2025-02-28 19:07:02 +01:00
|
|
|
class NODE_OT_interface_item_make_panel_toggle(NodeInterfaceOperator, Operator):
|
|
|
|
|
"""Make the active boolean socket a toggle for its parent panel"""
|
|
|
|
|
bl_idname = "node.interface_item_make_panel_toggle"
|
|
|
|
|
bl_label = "Make Panel Toggle"
|
|
|
|
|
bl_options = {'REGISTER', 'UNDO'}
|
|
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
|
def poll(cls, context):
|
|
|
|
|
if not super().poll(context):
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
snode = context.space_data
|
|
|
|
|
tree = snode.edit_tree
|
|
|
|
|
interface = tree.interface
|
|
|
|
|
active_item = interface.active
|
|
|
|
|
if not active_item:
|
|
|
|
|
return False
|
2025-04-15 10:05:27 -04:00
|
|
|
|
2025-04-15 15:26:44 +02:00
|
|
|
if type(active_item) is not bpy.types.NodeTreeInterfaceSocketBool or active_item.in_out != 'INPUT':
|
|
|
|
|
cls.poll_message_set("Only boolean input sockets are supported")
|
2025-02-28 19:07:02 +01:00
|
|
|
return False
|
2025-04-15 15:26:44 +02:00
|
|
|
|
2025-02-28 19:07:02 +01:00
|
|
|
parent_panel = active_item.parent
|
|
|
|
|
if parent_panel.parent is None:
|
|
|
|
|
cls.poll_message_set("Socket must be in a panel")
|
|
|
|
|
return False
|
|
|
|
|
if len(parent_panel.interface_items) > 0:
|
|
|
|
|
first_item = parent_panel.interface_items[0]
|
|
|
|
|
if first_item.is_panel_toggle:
|
|
|
|
|
cls.poll_message_set("Panel already has a toggle")
|
|
|
|
|
return False
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
def execute(self, context):
|
|
|
|
|
snode = context.space_data
|
|
|
|
|
tree = snode.edit_tree
|
|
|
|
|
interface = tree.interface
|
|
|
|
|
active_item = interface.active
|
|
|
|
|
|
|
|
|
|
parent_panel = active_item.parent
|
|
|
|
|
if not parent_panel:
|
|
|
|
|
return {'CANCELLED'}
|
|
|
|
|
|
2025-03-04 16:00:54 +11:00
|
|
|
if type(active_item) is not bpy.types.NodeTreeInterfaceSocketBool:
|
2025-02-28 19:07:02 +01:00
|
|
|
return {'CANCELLED'}
|
|
|
|
|
|
|
|
|
|
active_item.is_panel_toggle = True
|
|
|
|
|
# Use the same name as the panel in the UI for clarity.
|
|
|
|
|
active_item.name = parent_panel.name
|
|
|
|
|
|
|
|
|
|
# Move the socket to the first position.
|
|
|
|
|
interface.move_to_parent(active_item, parent_panel, 0)
|
|
|
|
|
# Make the panel active.
|
|
|
|
|
interface.active = parent_panel
|
|
|
|
|
|
|
|
|
|
return {'FINISHED'}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class NODE_OT_interface_item_unlink_panel_toggle(NodeInterfaceOperator, Operator):
|
|
|
|
|
"""Make the panel toggle a stand-alone socket"""
|
|
|
|
|
bl_idname = "node.interface_item_unlink_panel_toggle"
|
|
|
|
|
bl_label = "Unlink Panel Toggle"
|
|
|
|
|
bl_options = {'REGISTER', 'UNDO'}
|
|
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
|
def poll(cls, context):
|
|
|
|
|
if not super().poll(context):
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
snode = context.space_data
|
|
|
|
|
tree = snode.edit_tree
|
|
|
|
|
interface = tree.interface
|
|
|
|
|
active_item = interface.active
|
|
|
|
|
if not active_item or active_item.item_type != 'PANEL':
|
|
|
|
|
return False
|
|
|
|
|
if len(active_item.interface_items) == 0:
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
first_item = active_item.interface_items[0]
|
|
|
|
|
return first_item.is_panel_toggle
|
|
|
|
|
|
|
|
|
|
def execute(self, context):
|
|
|
|
|
snode = context.space_data
|
|
|
|
|
tree = snode.edit_tree
|
|
|
|
|
interface = tree.interface
|
|
|
|
|
active_item = interface.active
|
|
|
|
|
|
|
|
|
|
if not active_item or active_item.item_type != 'PANEL':
|
|
|
|
|
return {'CANCELLED'}
|
|
|
|
|
|
|
|
|
|
if len(active_item.interface_items) == 0:
|
|
|
|
|
return {'CANCELLED'}
|
|
|
|
|
|
|
|
|
|
first_item = active_item.interface_items[0]
|
2025-03-04 16:00:54 +11:00
|
|
|
if type(first_item) is not bpy.types.NodeTreeInterfaceSocketBool or not first_item.is_panel_toggle:
|
2025-02-28 19:07:02 +01:00
|
|
|
return {'CANCELLED'}
|
|
|
|
|
|
|
|
|
|
first_item.is_panel_toggle = False
|
|
|
|
|
first_item.name = active_item.name
|
|
|
|
|
|
|
|
|
|
# Make the socket active.
|
|
|
|
|
interface.active = first_item
|
|
|
|
|
|
|
|
|
|
return {'FINISHED'}
|
|
|
|
|
|
|
|
|
|
|
2025-01-29 18:35:26 +01:00
|
|
|
class NODE_OT_viewer_shortcut_set(Operator):
|
2025-09-02 16:19:30 +02:00
|
|
|
"""Create a viewer shortcut for the selected node by pressing ctrl+1,2,..9"""
|
2025-01-29 18:35:26 +01:00
|
|
|
bl_idname = "node.viewer_shortcut_set"
|
|
|
|
|
bl_label = "Fast Preview"
|
|
|
|
|
bl_options = {'REGISTER', 'UNDO'}
|
|
|
|
|
|
|
|
|
|
viewer_index: IntProperty(
|
|
|
|
|
name="Viewer Index",
|
|
|
|
|
description="Index corresponding to the shortcut, e.g. number key 1 corresponds to index 1 etc..")
|
|
|
|
|
|
|
|
|
|
def get_connected_viewer(self, node):
|
|
|
|
|
for out in node.outputs:
|
|
|
|
|
for link in out.links:
|
|
|
|
|
nv = link.to_node
|
|
|
|
|
if nv.type == 'VIEWER':
|
|
|
|
|
return nv
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
@classmethod
|
2025-01-31 13:18:41 +11:00
|
|
|
def poll(cls, context):
|
|
|
|
|
del cls
|
2025-01-30 00:28:22 +01:00
|
|
|
space = context.space_data
|
2025-01-31 13:18:41 +11:00
|
|
|
return (
|
2025-03-06 15:24:52 +11:00
|
|
|
(space is not None) and
|
2025-01-31 13:18:41 +11:00
|
|
|
space.type == 'NODE_EDITOR' and
|
|
|
|
|
space.node_tree is not None and
|
2025-03-14 11:26:57 +01:00
|
|
|
space.tree_type in {'CompositorNodeTree', 'GeometryNodeTree'}
|
2025-01-31 13:18:41 +11:00
|
|
|
)
|
2025-01-29 18:35:26 +01:00
|
|
|
|
|
|
|
|
def execute(self, context):
|
|
|
|
|
selected_nodes = context.selected_nodes
|
|
|
|
|
|
|
|
|
|
if len(selected_nodes) == 0:
|
|
|
|
|
self.report({'ERROR'}, "Select a node to assign a shortcut")
|
|
|
|
|
return {'CANCELLED'}
|
|
|
|
|
|
|
|
|
|
fav_node = selected_nodes[0]
|
|
|
|
|
|
|
|
|
|
# Only viewer nodes can be set to favorites. However, the user can
|
|
|
|
|
# create a new favorite viewer by selecting any node and pressing ctrl+1.
|
|
|
|
|
if fav_node.type == 'VIEWER':
|
|
|
|
|
viewer_node = fav_node
|
|
|
|
|
else:
|
|
|
|
|
viewer_node = self.get_connected_viewer(fav_node)
|
|
|
|
|
if not viewer_node:
|
2025-01-31 13:18:41 +11:00
|
|
|
# Calling `link_viewer()` if a viewer node is connected
|
|
|
|
|
# will connect the next available socket to the viewer node.
|
2025-01-30 14:17:31 +11:00
|
|
|
# This behavior is not desired as we want to create a shortcut to the existing connected viewer node.
|
2025-01-31 13:18:41 +11:00
|
|
|
# Therefore `link_viewer()` is called only when no viewer node is connected.
|
2025-01-29 18:35:26 +01:00
|
|
|
bpy.ops.node.link_viewer()
|
|
|
|
|
viewer_node = self.get_connected_viewer(fav_node)
|
|
|
|
|
|
|
|
|
|
if not viewer_node:
|
2025-01-31 13:18:41 +11:00
|
|
|
self.report(
|
|
|
|
|
{'ERROR'},
|
|
|
|
|
"Unable to set shortcut, selected node is not a viewer node or does not support viewing",
|
|
|
|
|
)
|
2025-01-29 18:35:26 +01:00
|
|
|
return {'CANCELLED'}
|
|
|
|
|
|
2025-03-14 11:26:57 +01:00
|
|
|
with bpy.context.temp_override(node=viewer_node):
|
|
|
|
|
bpy.ops.node.activate_viewer()
|
2025-01-29 18:35:26 +01:00
|
|
|
|
|
|
|
|
viewer_node.ui_shortcut = self.viewer_index
|
2025-05-31 13:19:13 +02:00
|
|
|
self.report({'INFO'}, rpt_("Assigned shortcut {:d} to {:s}").format(self.viewer_index, viewer_node.name))
|
2025-01-29 18:35:26 +01:00
|
|
|
|
|
|
|
|
return {'FINISHED'}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class NODE_OT_viewer_shortcut_get(Operator):
|
2025-09-02 16:19:30 +02:00
|
|
|
"""Activate a specific viewer node using 1,2,..,9 keys"""
|
2025-01-29 18:35:26 +01:00
|
|
|
bl_idname = "node.viewer_shortcut_get"
|
|
|
|
|
bl_label = "Fast Preview"
|
|
|
|
|
bl_options = {'REGISTER', 'UNDO'}
|
|
|
|
|
|
|
|
|
|
viewer_index: IntProperty(
|
|
|
|
|
name="Viewer Index",
|
|
|
|
|
description="Index corresponding to the shortcut, e.g. number key 1 corresponds to index 1 etc..")
|
|
|
|
|
|
|
|
|
|
@classmethod
|
2025-01-31 13:18:41 +11:00
|
|
|
def poll(cls, context):
|
|
|
|
|
del cls
|
2025-01-30 00:28:22 +01:00
|
|
|
space = context.space_data
|
2025-01-31 13:18:41 +11:00
|
|
|
return (
|
2025-03-06 15:24:52 +11:00
|
|
|
(space is not None) and
|
2025-01-31 13:18:41 +11:00
|
|
|
space.type == 'NODE_EDITOR' and
|
|
|
|
|
space.node_tree is not None and
|
2025-03-14 11:26:57 +01:00
|
|
|
space.tree_type in {'CompositorNodeTree', 'GeometryNodeTree'}
|
2025-01-31 13:18:41 +11:00
|
|
|
)
|
2025-01-29 18:35:26 +01:00
|
|
|
|
|
|
|
|
def execute(self, context):
|
|
|
|
|
nodes = context.space_data.edit_tree.nodes
|
|
|
|
|
|
2025-01-30 14:17:31 +11:00
|
|
|
# Get viewer node with existing shortcut.
|
2025-01-29 18:35:26 +01:00
|
|
|
viewer_node = None
|
|
|
|
|
for n in nodes:
|
|
|
|
|
if n.type == 'VIEWER' and n.ui_shortcut == self.viewer_index:
|
|
|
|
|
viewer_node = n
|
|
|
|
|
|
|
|
|
|
if not viewer_node:
|
2025-05-31 13:19:13 +02:00
|
|
|
self.report({'INFO'}, rpt_("Shortcut {:d} is not assigned to a Viewer node yet").format(self.viewer_index))
|
2025-01-29 18:35:26 +01:00
|
|
|
return {'CANCELLED'}
|
|
|
|
|
|
2025-03-14 11:26:57 +01:00
|
|
|
with bpy.context.temp_override(node=viewer_node):
|
|
|
|
|
bpy.ops.node.activate_viewer()
|
2025-01-29 18:35:26 +01:00
|
|
|
|
|
|
|
|
return {'FINISHED'}
|
|
|
|
|
|
|
|
|
|
|
2024-04-26 16:40:16 +02:00
|
|
|
class NODE_FH_image_node(FileHandler):
|
|
|
|
|
bl_idname = "NODE_FH_image_node"
|
|
|
|
|
bl_label = "Image node"
|
2025-03-21 22:49:13 +01:00
|
|
|
bl_import_operator = "node.add_image"
|
2024-04-26 16:40:16 +02:00
|
|
|
bl_file_extensions = ";".join((*bpy.path.extensions_image, *bpy.path.extensions_movie))
|
|
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
|
def poll_drop(cls, context):
|
|
|
|
|
return (
|
|
|
|
|
(context.area is not None) and
|
|
|
|
|
(context.area.type == 'NODE_EDITOR') and
|
|
|
|
|
(context.region is not None) and
|
|
|
|
|
(context.region.type == 'WINDOW')
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
2017-03-18 20:03:24 +11:00
|
|
|
classes = (
|
|
|
|
|
NodeSetting,
|
|
|
|
|
|
2024-04-26 16:40:16 +02:00
|
|
|
NODE_FH_image_node,
|
|
|
|
|
|
2025-05-08 10:17:56 +02:00
|
|
|
NODE_OT_add_empty_group,
|
2017-03-18 20:03:24 +11:00
|
|
|
NODE_OT_add_node,
|
Geometry Nodes: add simulation support
This adds support for building simulations with geometry nodes. A new
`Simulation Input` and `Simulation Output` node allow maintaining a
simulation state across multiple frames. Together these two nodes form
a `simulation zone` which contains all the nodes that update the simulation
state from one frame to the next.
A new simulation zone can be added via the menu
(`Simulation > Simulation Zone`) or with the node add search.
The simulation state contains a geometry by default. However, it is possible
to add multiple geometry sockets as well as other socket types. Currently,
field inputs are evaluated and stored for the preceding geometry socket in
the order that the sockets are shown. Simulation state items can be added
by linking one of the empty sockets to something else. In the sidebar, there
is a new panel that allows adding, removing and reordering these sockets.
The simulation nodes behave as follows:
* On the first frame, the inputs of the `Simulation Input` node are evaluated
to initialize the simulation state. In later frames these sockets are not
evaluated anymore. The `Delta Time` at the first frame is zero, but the
simulation zone is still evaluated.
* On every next frame, the `Simulation Input` node outputs the simulation
state of the previous frame. Nodes in the simulation zone can edit that
data in arbitrary ways, also taking into account the `Delta Time`. The new
simulation state has to be passed to the `Simulation Output` node where it
is cached and forwarded.
* On a frame that is already cached or baked, the nodes in the simulation
zone are not evaluated, because the `Simulation Output` node can return
the previously cached data directly.
It is not allowed to connect sockets from inside the simulation zone to the
outside without going through the `Simulation Output` node. This is a necessary
restriction to make caching and sub-frame interpolation work. Links can go into
the simulation zone without problems though.
Anonymous attributes are not propagated by the simulation nodes unless they
are explicitly stored in the simulation state. This is unfortunate, but
currently there is no practical and reliable alternative. The core problem
is detecting which anonymous attributes will be required for the simulation
and afterwards. While we can detect this for the current evaluation, we can't
look into the future in time to see what data will be necessary. We intend to
make it easier to explicitly pass data through a simulation in the future,
even if the simulation is in a nested node group.
There is a new `Simulation Nodes` panel in the physics tab in the properties
editor. It allows baking all simulation zones on the selected objects. The
baking options are intentially kept at a minimum for this MVP. More features
for simulation baking as well as baking in general can be expected to be added
separately.
All baked data is stored on disk in a folder next to the .blend file. #106937
describes how baking is implemented in more detail. Volumes can not be baked
yet and materials are lost during baking for now. Packing the baked data into
the .blend file is not yet supported.
The timeline indicates which frames are currently cached, baked or cached but
invalidated by user-changes.
Simulation input and output nodes are internally linked together by their
`bNode.identifier` which stays the same even if the node name changes. They
are generally added and removed together. However, there are still cases where
"dangling" simulation nodes can be created currently. Those generally don't
cause harm, but would be nice to avoid this in more cases in the future.
Co-authored-by: Hans Goudey <h.goudey@me.com>
Co-authored-by: Lukas Tönne <lukas@blender.org>
Pull Request: https://projects.blender.org/blender/blender/pulls/104924
2023-05-03 13:18:51 +02:00
|
|
|
NODE_OT_add_simulation_zone,
|
2023-07-11 22:36:10 +02:00
|
|
|
NODE_OT_add_repeat_zone,
|
2024-09-24 11:52:02 +02:00
|
|
|
NODE_OT_add_foreach_geometry_element_zone,
|
2025-04-03 15:44:06 +02:00
|
|
|
NODE_OT_add_closure_zone,
|
2017-03-18 20:03:24 +11:00
|
|
|
NODE_OT_collapse_hide_unused_toggle,
|
2023-08-30 12:37:21 +02:00
|
|
|
NODE_OT_interface_item_new,
|
|
|
|
|
NODE_OT_interface_item_duplicate,
|
|
|
|
|
NODE_OT_interface_item_remove,
|
2025-02-28 19:07:02 +01:00
|
|
|
NODE_OT_interface_item_make_panel_toggle,
|
|
|
|
|
NODE_OT_interface_item_unlink_panel_toggle,
|
2017-03-18 20:03:24 +11:00
|
|
|
NODE_OT_tree_path_parent,
|
2025-01-29 18:35:26 +01:00
|
|
|
NODE_OT_viewer_shortcut_get,
|
|
|
|
|
NODE_OT_viewer_shortcut_set,
|
2017-03-18 20:03:24 +11:00
|
|
|
)
|