1
1

Compare commits

...

15 Commits

15 changed files with 421 additions and 2 deletions

View File

@@ -1831,6 +1831,7 @@ def km_node_editor(params):
])
items.extend([
("node.follow_portal", {"type": 'TAB', "value": 'PRESS'}, None),
("node.select_box", {"type": params.select_tweak, "value": 'ANY'},
{"properties": [("tweak", True)]}),
("node.select_lasso", {"type": 'EVT_TWEAK_L', "value": 'ANY', "ctrl": True, "alt": True},

View File

@@ -21,6 +21,7 @@ from __future__ import annotations
import bpy
import nodeitems_utils
from mathutils import Vector
from bpy.types import (
Operator,
PropertyGroup,
@@ -303,6 +304,187 @@ class NODE_OT_tree_path_parent(Operator):
return {'FINISHED'}
class NODE_OT_follow_portal(Operator):
'''Follow portal'''
bl_idname = "node.follow_portal"
bl_label = "Follow Portal"
@classmethod
def poll(cls, context):
space = context.space_data
return (space.type == 'NODE_EDITOR'
and space.node_tree
and space.node_tree.nodes.active.bl_idname in ('NodePortalIn', 'NodePortalOut'))
def execute(self, context):
space = context.space_data
ntree = space.node_tree
old_active_node = ntree.nodes.active
portal_id = old_active_node.portal_id
if old_active_node.bl_idname == 'NodePortalIn':
out_nodes = [n for n in ntree.nodes if n.bl_idname == 'NodePortalOut' and n.portal_id == portal_id]
if len(out_nodes) != 1:
return {'CANCELLED'}
out_node = out_nodes[0]
for node in ntree.nodes:
node.select = False
out_node.select = True
ntree.nodes.active = out_node
if old_active_node.bl_idname == 'NodePortalOut':
in_nodes = [n for n in ntree.nodes if n.bl_idname == 'NodePortalIn' and n.portal_id == portal_id]
if len(in_nodes) != 1:
return {'CANCELLED'}
in_node = in_nodes[0]
for node in ntree.nodes:
node.select = False
in_node.select = True
ntree.nodes.active = in_node
bpy.ops.node.view_selected()
return {'FINISHED'}
PAGE_SIZE = 20000
def get_page_center(page):
return Vector(((page - 1) * PAGE_SIZE, 0))
def position_is_on_page(pos, page):
page_center = get_page_center(page)
return (abs(page_center.x - pos[0]) < PAGE_SIZE / 2
and abs(page_center.y - pos[1]) < PAGE_SIZE / 2)
def node_is_on_page(node, page):
return position_is_on_page(node.location, page)
def get_nodes_on_page(ntree, page):
return [n for n in ntree.nodes if node_is_on_page(n, page)]
def get_current_page(context):
for region in context.area.regions:
if region.type != 'WINDOW':
continue
pos = region.view2d.region_to_view(0, 0)
for page in range(10):
if position_is_on_page(pos, page):
return page
return None
class NODE_OT_view_page(Operator):
'''View page'''
bl_idname = "node.view_page"
bl_label = "View Page"
page: bpy.props.IntProperty(default=0)
@classmethod
def poll(cls, context):
space = context.space_data
return space.type == 'NODE_EDITOR'
def invoke(self, context, event):
if event.ctrl:
bpy.ops.node.move_to_page('INVOKE_DEFAULT', page=self.page)
return {'FINISHED'}
return self.execute(context)
def execute(self, context):
space = context.space_data
ntree = space.node_tree
for node in ntree.nodes:
node.select = False
nodes_on_page = get_nodes_on_page(ntree, self.page)
if len(nodes_on_page) > 0:
for node in nodes_on_page:
node.select = True
bpy.ops.node.view_selected()
for node in nodes_on_page:
node.select = False
ntree.nodes.active = None
else:
page_center = get_page_center(self.page)
new_node = ntree.nodes.new('NodeReroute')
new_node.select = True
new_node.location = page_center
context_copy = context.copy()
def update_after_draw():
bpy.ops.node.view_selected(context_copy)
ntree.nodes.remove(new_node)
bpy.app.timers.register(update_after_draw, first_interval=0.01)
return {'FINISHED'}
class NODE_OT_move_to_page(Operator):
'''Move to page'''
bl_idname = "node.move_to_page"
bl_label = "Move to Page"
page: IntProperty()
@classmethod
def poll(cls, context):
space = context.space_data
return space.type == 'NODE_EDITOR'
def execute(self, context):
ntree = context.space_data.node_tree
nodes_to_move = [n for n in ntree.nodes if n.select]
if len(nodes_to_move) == 0:
return {'CANCELLED'}
old_center = sum((n.location for n in nodes_to_move), Vector((0, 0))) / len(nodes_to_move)
new_center = get_page_center(self.page)
offset = new_center - old_center
view2d = context.region.view2d
old_center_region = Vector(view2d.view_to_region(old_center.x, old_center.y, clip=False))
new_center_region = Vector(view2d.view_to_region(new_center.x, new_center.y, clip=False))
offset_region = new_center_region - old_center_region
for node in nodes_to_move:
node.location += offset
bpy.ops.view2d.pan(deltax=offset_region.x, deltay=offset_region.y)
return {'FINISHED'}
class NODE_OT_add_portal(Operator):
'''Add portal'''
bl_idname = "node.add_portal"
bl_label = "Add Portal"
@classmethod
def poll(cls, context):
if context.space_data.type != 'NODE_EDITOR':
return False
ntree = context.space_data.node_tree
if ntree is None:
return False
return True
def invoke(self, context, event):
ntree = context.space_data.node_tree
bpy.ops.node.add_and_link_node(type="NodePortalIn")
portal_in = ntree.nodes[-1]
bpy.ops.node.add_and_link_node(type="NodePortalOut")
portal_out = ntree.nodes[-1]
portal_in.location.x -= 200
portal_out.location.x += 60
for node in ntree.nodes:
node.select = False
portal_in.select = True
portal_out.select = True
portal_out.portal_id = portal_in.portal_id
bpy.ops.node.translate_attach("INVOKE_DEFAULT")
return {'FINISHED'}
classes = (
NodeSetting,
@@ -311,4 +493,8 @@ classes = (
NODE_OT_add_search,
NODE_OT_collapse_hide_unused_toggle,
NODE_OT_tree_path_parent,
NODE_OT_follow_portal,
NODE_OT_view_page,
NODE_OT_add_portal,
NODE_OT_move_to_page,
)

View File

@@ -171,6 +171,16 @@ class NODE_HT_header(Header):
else:
row.template_ID(snode, "node_tree", new="node.new_geometry_nodes_modifier")
import bl_operators.node as node_module
current_page = node_module.get_current_page(context)
row = layout.row(align=True)
for page in range(1, 6):
subrow = row.row(align=True)
subrow.enabled = page != current_page
props = subrow.operator("node.view_page", text=str(page))
props.page = page
else:
# Custom node tree is edited as independent ID block
NODE_MT_editor_menus.draw_collapsible(context, layout)
@@ -238,6 +248,8 @@ class NODE_MT_add(bpy.types.Menu):
# actual node submenus are defined by draw functions from node categories
nodeitems_utils.draw_node_categories_menu(self, context)
layout.operator("node.add_portal")
class NODE_MT_view(Menu):
bl_label = "View"

View File

@@ -564,6 +564,8 @@ geometry_node_categories = [
GeometryNodeCategory("GEO_LAYOUT", "Layout", items=[
NodeItem("NodeFrame"),
NodeItem("NodeReroute"),
NodeItem("NodePortalIn"),
NodeItem("NodePortalOut"),
]),
]

View File

@@ -860,6 +860,8 @@ bool BKE_node_is_connected_to_output(struct bNodeTree *ntree, struct bNode *node
#define NODE_GROUP_INPUT 7
#define NODE_GROUP_OUTPUT 8
#define NODE_CUSTOM_GROUP 9
#define NODE_PORTAL_IN 10
#define NODE_PORTAL_OUT 11
void BKE_node_tree_unlink_id(ID *id, struct bNodeTree *ntree);

View File

@@ -4963,6 +4963,8 @@ static void registerGeometryNodes()
register_node_type_geo_transform();
register_node_type_geo_triangulate();
register_node_type_geo_volume_to_mesh();
register_node_type_portal_in();
register_node_type_portal_out();
}
static void registerFunctionNodes()

View File

@@ -137,6 +137,7 @@ bool node_connected_to_output(Main *bmain, bNodeTree *ntree, bNode *node)
if (ntree_has_drivers(ntree)) {
return true;
}
return true;
LISTBASE_FOREACH (bNode *, current_node, &ntree->nodes) {
/* Special case for group nodes -- if modified node connected to a group
* with active output inside we consider refresh is needed.

View File

@@ -1298,6 +1298,14 @@ typedef struct NodeGeometryMeshCone {
uint8_t fill_type;
} NodeGeometryMeshCone;
typedef struct NodePortalIn {
int portal_id;
} NodePortalIn;
typedef struct NodePortalOut {
int portal_id;
} NodePortalOut;
typedef struct NodeGeometryMeshLine {
/* GeometryNodeMeshLineMode. */
uint8_t mode;

View File

@@ -9574,6 +9574,28 @@ static void def_geo_mesh_line(StructRNA *srna)
RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_socket_update");
}
static void def_portal_in(StructRNA *srna)
{
PropertyRNA *prop;
RNA_def_struct_sdna_from(srna, "NodePortalIn", "storage");
prop = RNA_def_property(srna, "portal_id", PROP_INT, PROP_NONE);
RNA_def_property_ui_text(prop, "Portal ID", "");
RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update");
}
static void def_portal_out(StructRNA *srna)
{
PropertyRNA *prop;
RNA_def_struct_sdna_from(srna, "NodePortalOut", "storage");
prop = RNA_def_property(srna, "portal_id", PROP_INT, PROP_NONE);
RNA_def_property_ui_text(prop, "Portal ID", "");
RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update");
}
/* -------------------------------------------------------------------------- */
static void rna_def_shader_node(BlenderRNA *brna)

View File

@@ -38,6 +38,7 @@ set(INC
../makesdna
../makesrna
../render
../windowmanager
../../../intern/glew-mx
../../../intern/guardedalloc
../../../intern/sky/include
@@ -177,6 +178,7 @@ set(SRC
geometry/nodes/node_geo_point_separate.cc
geometry/nodes/node_geo_point_translate.cc
geometry/nodes/node_geo_points_to_volume.cc
geometry/nodes/node_geo_portals.cc
geometry/nodes/node_geo_subdivide.cc
geometry/nodes/node_geo_subdivision_surface.cc
geometry/nodes/node_geo_transform.cc

View File

@@ -68,6 +68,8 @@ void register_node_type_geo_subdivision_surface(void);
void register_node_type_geo_transform(void);
void register_node_type_geo_triangulate(void);
void register_node_type_geo_volume_to_mesh(void);
void register_node_type_portal_in(void);
void register_node_type_portal_out(void);
#ifdef __cplusplus
}

View File

@@ -178,6 +178,8 @@ class NodeRef : NonCopyable, NonMovable {
bool is_group_output_node() const;
bool is_muted() const;
bool is_frame() const;
bool is_portal_in() const;
bool is_portal_out() const;
void *storage() const;
template<typename T> T *storage() const;
@@ -225,6 +227,8 @@ class NodeTreeRef : NonCopyable, NonMovable {
Vector<OutputSocketRef *> output_sockets_;
Vector<LinkRef *> links_;
MultiValueMap<const bNodeType *, NodeRef *> nodes_by_type_;
MultiValueMap<int, NodeRef *> portal_in_nodes_by_id_;
MultiValueMap<int, NodeRef *> portal_out_nodes_by_id_;
public:
NodeTreeRef(bNodeTree *btree);
@@ -240,6 +244,9 @@ class NodeTreeRef : NonCopyable, NonMovable {
Span<const LinkRef *> links() const;
Span<const NodeRef *> portal_in_nodes_by_id(int portal_id) const;
Span<const NodeRef *> portal_out_nodes_by_id(int portal_id) const;
bool has_link_cycles() const;
bNodeTree *btree() const;
@@ -541,6 +548,16 @@ inline bool NodeRef::is_muted() const
return (bnode_->flag & NODE_MUTED) != 0;
}
inline bool NodeRef::is_portal_in() const
{
return bnode_->type == NODE_PORTAL_IN;
}
inline bool NodeRef::is_portal_out() const
{
return bnode_->type == NODE_PORTAL_OUT;
}
inline void *NodeRef::storage() const
{
return bnode_->storage;
@@ -634,6 +651,16 @@ inline Span<const LinkRef *> NodeTreeRef::links() const
return links_;
}
inline Span<const NodeRef *> NodeTreeRef::portal_in_nodes_by_id(int portal_id) const
{
return portal_in_nodes_by_id_.lookup(portal_id);
}
inline Span<const NodeRef *> NodeTreeRef::portal_out_nodes_by_id(int portal_id) const
{
return portal_out_nodes_by_id_.lookup(portal_id);
}
inline bNodeTree *NodeTreeRef::btree() const
{
return btree_;

View File

@@ -36,6 +36,9 @@ DefNode(Node, NODE_GROUP, def_group, "GROUP"
DefNode(Node, NODE_GROUP_INPUT, def_group_input, "GROUP_INPUT", GroupInput, "Group Input", "" )
DefNode(Node, NODE_GROUP_OUTPUT, def_group_output, "GROUP_OUTPUT", GroupOutput, "Group Output", "" )
DefNode(Node, NODE_REROUTE, 0, "REROUTE", Reroute, "Reroute", "" )
DefNode(Node, NODE_PORTAL_IN, def_portal_in, "PORTAL_IN", PortalIn, "Portal In", "" )
DefNode(Node, NODE_PORTAL_OUT, def_portal_out, "PORTAL_OUT", PortalOut, "Portal Out", "" )
DefNode(ShaderNode, SH_NODE_RGB, 0, "RGB", RGB, "RGB", "" )
DefNode(ShaderNode, SH_NODE_VALUE, 0, "VALUE", Value, "Value", "" )

View File

@@ -0,0 +1,128 @@
/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "node_geometry_util.hh"
#include "UI_interface.h"
#include "UI_resources.h"
#include "WM_types.h"
#include "RNA_access.h"
static bNodeSocketTemplate node_portal_sockets[] = {
{SOCK_GEOMETRY, N_("Geometry")},
{SOCK_FLOAT, N_("Float"), 0.0f, 0.0f, 0.0f, 0.0f, -FLT_MAX, FLT_MAX},
{SOCK_VECTOR, N_("Vector"), 0.0f, 0.0f, 0.0f, 0.0f, -FLT_MAX, FLT_MAX},
{SOCK_RGBA, N_("Color"), 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f},
{SOCK_INT, N_("Integer"), 0, 0, 0, 0, -10000, 10000},
{SOCK_BOOLEAN, N_("Boolean")},
{SOCK_STRING, N_("String")},
{SOCK_OBJECT, N_("Object")},
{SOCK_COLLECTION, N_("Collection")},
{-1, ""},
};
static void node_portal_in_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr)
{
uiItemR(layout, ptr, "name", 0, "", ICON_NONE);
}
static void node_portal_out_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr)
{
const int portal_id = RNA_int_get(ptr, "portal_id");
bNodeTree &ntree = *(bNodeTree *)ptr->owner_id;
LISTBASE_FOREACH (bNode *, node, &ntree.nodes) {
if (node->type == NODE_PORTAL_IN) {
NodePortalIn *storage = (NodePortalIn *)node->storage;
if (storage->portal_id == portal_id) {
PointerRNA other_ptr;
RNA_pointer_create(ptr->owner_id, &RNA_Node, node, &other_ptr);
uiItemR(layout, &other_ptr, "name", 0, "", ICON_NONE);
break;
}
}
}
}
namespace blender::nodes {
static void node_portal_in_init(bNodeTree *UNUSED(tree), bNode *node)
{
NodePortalIn *data = (NodePortalIn *)MEM_callocN(sizeof(NodePortalIn), __func__);
data->portal_id = rand();
node->storage = data;
LISTBASE_FOREACH (bNodeSocket *, socket, &node->inputs) {
if (!STREQ(socket->name, "Geometry")) {
socket->flag |= SOCK_HIDDEN;
}
}
}
static void node_portal_out_init(bNodeTree *UNUSED(tree), bNode *node)
{
NodePortalOut *data = (NodePortalOut *)MEM_callocN(sizeof(NodePortalOut), __func__);
node->storage = data;
LISTBASE_FOREACH (bNodeSocket *, socket, &node->outputs) {
if (!STREQ(socket->name, "Geometry")) {
socket->flag |= SOCK_HIDDEN;
}
}
}
static void node_portal_in_update(bNodeTree *UNUSED(ntree), bNode *node)
{
NodePortalIn &node_storage = *(NodePortalIn *)node->storage;
UNUSED_VARS(node_storage);
}
static void node_portal_out_update(bNodeTree *UNUSED(ntree), bNode *node)
{
NodePortalOut &node_storage = *(NodePortalOut *)node->storage;
UNUSED_VARS(node_storage);
}
} // namespace blender::nodes
void register_node_type_portal_in()
{
static bNodeType ntype;
geo_node_type_base(&ntype, NODE_PORTAL_IN, "Portal In", NODE_CLASS_LAYOUT, 0);
node_type_socket_templates(&ntype, node_portal_sockets, nullptr);
node_type_init(&ntype, blender::nodes::node_portal_in_init);
node_type_update(&ntype, blender::nodes::node_portal_in_update);
node_type_storage(
&ntype, "NodePortalIn", node_free_standard_storage, node_copy_standard_storage);
ntype.draw_buttons = node_portal_in_layout;
nodeRegisterType(&ntype);
}
void register_node_type_portal_out()
{
static bNodeType ntype;
geo_node_type_base(&ntype, NODE_PORTAL_OUT, "Portal Out", NODE_CLASS_LAYOUT, 0);
node_type_socket_templates(&ntype, nullptr, node_portal_sockets);
node_type_init(&ntype, blender::nodes::node_portal_out_init);
node_type_update(&ntype, blender::nodes::node_portal_out_update);
node_type_storage(
&ntype, "NodePortalOut", node_free_standard_storage, node_copy_standard_storage);
ntype.draw_buttons = node_portal_out_layout;
nodeRegisterType(&ntype);
}

View File

@@ -105,12 +105,21 @@ NodeTreeRef::NodeTreeRef(bNodeTree *btree) : btree_(btree)
}
}
this->create_linked_socket_caches();
for (NodeRef *node : nodes_by_id_) {
const bNodeType *nodetype = node->bnode_->typeinfo;
nodes_by_type_.add(nodetype, node);
if (node->is_portal_in()) {
NodePortalIn *storage = (NodePortalIn *)node->bnode_->storage;
portal_in_nodes_by_id_.add(storage->portal_id, node);
}
else if (node->is_portal_out()) {
NodePortalOut *storage = (NodePortalOut *)node->bnode_->storage;
portal_out_nodes_by_id_.add(storage->portal_id, node);
}
}
this->create_linked_socket_caches();
}
NodeTreeRef::~NodeTreeRef()
@@ -223,6 +232,12 @@ void NodeTreeRef::foreach_logical_origin(InputSocketRef &socket,
if (origin_node->is_reroute_node()) {
this->foreach_logical_origin(*origin_node->inputs_[0], callback, false);
}
else if (origin_node->is_portal_out()) {
NodePortalOut *storage = origin_node->storage<NodePortalOut>();
for (NodeRef *input_node : portal_in_nodes_by_id_.lookup(storage->portal_id)) {
this->foreach_logical_origin(*input_node->inputs_[0], callback);
}
}
else if (origin_node->is_muted()) {
for (InternalLinkRef *internal_link : origin_node->internal_links_) {
if (internal_link->to_ == origin) {
@@ -249,6 +264,12 @@ void NodeTreeRef::foreach_logical_target(OutputSocketRef &socket,
if (target_node->is_reroute_node()) {
this->foreach_logical_target(*target_node->outputs_[0], callback);
}
else if (target_node->is_portal_in()) {
NodePortalIn *storage = target_node->storage<NodePortalIn>();
for (NodeRef *output_node : portal_out_nodes_by_id_.lookup(storage->portal_id)) {
this->foreach_logical_target(*output_node->outputs_[0], callback);
}
}
else if (target_node->is_muted()) {
for (InternalLinkRef *internal_link : target_node->internal_links_) {
if (internal_link->from_ == target) {