Blender V5.0
transform_convert_node.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved.
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
8
9#include "DNA_space_types.h"
10#include "DNA_userdef_types.h"
11
12#include "MEM_guardedalloc.h"
13
14#include "BLI_listbase.h"
15#include "BLI_math_matrix.h"
16#include "BLI_math_vector.h"
17#include "BLI_math_vector.hh"
18#include "BLI_rect.h"
19
20#include "BKE_context.hh"
22#include "BKE_node.hh"
23#include "BKE_node_runtime.hh"
24
25#include "ED_node.hh"
26
27#include "UI_view2d.hh"
28
29#include "transform.hh"
30#include "transform_convert.hh"
31#include "transform_snap.hh"
32
33#include "WM_api.hh"
34
35namespace blender::ed::transform {
36
39
40 /* Compare if the view has changed so we can update with `transformViewUpdate`. */
42
43 bool is_new_node = false;
44
46};
47
48/* -------------------------------------------------------------------- */
51
53 TransData2D &td2d,
54 bNode &node,
55 const float dpi_fac)
56{
57 /* Account for parents (nested nodes). */
58 float2 loc = float2(node.location) * dpi_fac;
59
60 /* Use top-left corner as the transform origin for nodes. */
61 /* Weirdo - but the node system is a mix of free 2d elements and DPI sensitive UI. */
62 td2d.loc[0] = loc.x;
63 td2d.loc[1] = loc.y;
64 td2d.loc[2] = 0.0f;
65 td2d.loc2d = td2d.loc; /* Current location. */
66
67 td.loc = td2d.loc;
68 copy_v3_v3(td.iloc, td.loc);
69 /* Use node center instead of origin (top-left corner). */
70 td.center[0] = td2d.loc[0];
71 td.center[1] = td2d.loc[1];
72 td.center[2] = 0.0f;
73
74 memset(td.axismtx, 0, sizeof(td.axismtx));
75 td.axismtx[2][2] = 1.0f;
76
77 td.val = nullptr;
78
79 td.flag = TD_SELECTED;
80 td.dist = 0.0f;
81
82 unit_m3(td.mtx);
83 unit_m3(td.smtx);
84
85 td.extra = &node;
86}
87
88static bool is_node_parent_select(const bNode *node)
89{
90 while ((node = node->parent)) {
91 if (node->flag & NODE_SELECT) {
92 return true;
93 }
94 }
95 return false;
96}
97
104static bool transform_tied_to_other_node(bNode *node, VectorSet<bNode *> transformed_nodes)
105{
106 /* Check for frame nodes that adjust their size based on the contained child nodes. */
107 if (node->is_frame()) {
108 const NodeFrame *data = static_cast<const NodeFrame *>(node->storage);
109 const bool shrinking = data->flag & NODE_FRAME_SHRINK;
110 const bool is_parent = !node->direct_children_in_frame().is_empty();
111
112 if (is_parent && shrinking) {
113 return true;
114 }
115 }
116
117 /* Now check for child nodes of manually resized frames. */
118 while ((node = node->parent)) {
119 const NodeFrame *parent_data = (const NodeFrame *)node->storage;
120 const bool parent_shrinking = parent_data->flag & NODE_FRAME_SHRINK;
121 const bool parent_transformed = transformed_nodes.contains(node);
122
123 if (parent_transformed && !parent_shrinking) {
124 return true;
125 }
126 }
127
128 return false;
129}
130
132{
133 VectorSet<bNode *> nodes = node_tree.all_nodes();
134
135 /* Keep only nodes that are selected or inside a frame that is selected. */
136 nodes.remove_if([&](bNode *node) {
137 const bool node_selected = node->flag & NODE_SELECT;
138 const bool parent_selected = is_node_parent_select(node);
139 return (!node_selected && !parent_selected);
140 });
141
142 /* Remove nodes that are transformed together with their parent or child nodes. */
143 nodes.remove_if([&](bNode *node) { return transform_tied_to_other_node(node, nodes); });
144
145 return nodes;
146}
147
149{
150 SpaceNode *snode = static_cast<SpaceNode *>(t->area->spacedata.first);
151 bNodeTree *node_tree = snode->edittree;
152 if (!node_tree) {
153 return;
154 }
155
156 /* Custom data to enable edge panning during the node transform. */
157 TransCustomDataNode *customdata = MEM_new<TransCustomDataNode>(__func__);
159 &customdata->edgepan_data,
166 customdata->viewrect_prev = customdata->edgepan_data.initial_rect;
167 customdata->is_new_node = t->remove_on_cancel;
168
170 *snode, *t->region, t->modifiers & MOD_NODE_ATTACH, customdata->is_new_node);
172
173 t->custom.type.data = customdata;
174 t->custom.type.free_cb = [](TransInfo *, TransDataContainer *, TransCustomData *custom_data) {
175 TransCustomDataNode *data = static_cast<TransCustomDataNode *>(custom_data->data);
176 MEM_delete(data);
177 custom_data->data = nullptr;
178 };
179
181
182 /* Nodes don't support proportional editing and probably never will. */
183 t->flag = t->flag & ~T_PROP_EDIT_ALL;
184
186 if (nodes.is_empty()) {
187 return;
188 }
189
190 tc->data_len = nodes.size();
191 tc->data = MEM_calloc_arrayN<TransData>(tc->data_len, __func__);
193
194 for (const int i : nodes.index_range()) {
196 }
197}
198
200
201/* -------------------------------------------------------------------- */
204
206{
207 if (!(transform_snap_is_active(t) &&
209 {
210 return;
211 }
212
213 float2 grid_size = t->snap_spatial;
214 if (t->modifiers & MOD_PRECISION) {
215 grid_size *= t->snap_spatial_precision;
216 }
217
218 /* Early exit on unusable grid size. */
219 if (math::is_zero(grid_size)) {
220 return;
221 }
222
224 for (const int i : IndexRange(tc->data_len)) {
225 TransData &td = tc->data[i];
226 if (td.flag & TD_SKIP) {
227 continue;
228 }
229
230 if ((t->flag & T_PROP_EDIT) && (td.factor == 0.0f)) {
231 continue;
232 }
233
234 /* Nodes are snapped to the grid by first aligning their initial position to the grid and
235 * then offsetting them in grid increments.
236 *
237 * This ensures that multiple unsnapped nodes snap to the grid in sync while moving.
238 */
239
240 const float2 inital_location = td.iloc;
241 const float2 target_location = td.loc;
242 const float2 offset = target_location - inital_location;
243
244 const float2 snapped_inital_location = math::round(inital_location / grid_size) * grid_size;
245 const float2 snapped_offset = math::round(offset / grid_size) * grid_size;
246 const float2 snapped_target_location = snapped_inital_location + snapped_offset;
247
248 copy_v2_v2(td.loc, snapped_target_location);
249 }
250 }
251}
252
253static void move_child_nodes(bNode &node, const float2 &delta)
254{
255 for (bNode *child : node.direct_children_in_frame()) {
256 child->location[0] += delta.x;
257 child->location[1] += delta.y;
258 if (child->is_frame()) {
259 move_child_nodes(*child, delta);
260 }
261 }
262}
263
264static bool has_selected_parent(const bNode &node)
265{
266 for (bNode *parent = node.parent; parent; parent = parent->parent) {
267 if (parent->flag & NODE_SELECT) {
268 return true;
269 }
270 }
271 return false;
272}
273
275{
276 const float dpi_fac = UI_SCALE_FAC;
277 SpaceNode *snode = static_cast<SpaceNode *>(t->area->spacedata.first);
278
280
281 if (t->options & CTX_VIEW2D_EDGE_PAN) {
282 if (t->state == TRANS_CANCEL) {
284 }
285 else {
286 /* Edge panning functions expect window coordinates, mval is relative to region. */
287 const int xy[2] = {
288 t->region->winrct.xmin + int(t->mval[0]),
289 t->region->winrct.ymin + int(t->mval[1]),
290 };
292 }
293 }
294
295 float offset[2] = {0.0f, 0.0f};
296 if (t->state != TRANS_CANCEL) {
297 if (!BLI_rctf_compare(&customdata->viewrect_prev, &t->region->v2d.cur, FLT_EPSILON)) {
298 /* Additional offset due to change in view2D rect. */
299 BLI_rctf_transform_pt_v(&t->region->v2d.cur, &customdata->viewrect_prev, offset, offset);
301 customdata->viewrect_prev = t->region->v2d.cur;
302 }
303 }
304
305 if (t->modifiers & MOD_NODE_FRAME) {
307 Vector<bNode *> nodes_to_detach;
308 for (bNode *node : snode->edittree->all_nodes()) {
309 if (!(node->flag & NODE_SELECT)) {
310 continue;
311 }
312 if (has_selected_parent(*node)) {
313 continue;
314 }
315 if (!node->parent) {
316 continue;
317 }
318 customdata->old_parent_by_detached_node.add(node, node->parent);
319 nodes_to_detach.append(node);
320 }
321 if (nodes_to_detach.is_empty()) {
323 t->context, "NODE_OT_attach", wm::OpCallContext::InvokeDefault, nullptr, nullptr);
324 }
325 else {
326 for (bNode *node : nodes_to_detach) {
327 bke::node_detach_node(*snode->edittree, *node);
328 }
329 }
330 }
331
334
335 /* Flush to 2d vector from internally used 3d vector. */
336 for (int i = 0; i < tc->data_len; i++) {
337 TransData *td = &tc->data[i];
338 TransData2D *td2d = &tc->data_2d[i];
339 bNode *node = static_cast<bNode *>(td->extra);
340
341 float2 loc = float2(td2d->loc) + offset;
342
343 /* Weirdo - but the node system is a mix of free 2d elements and DPI sensitive UI. */
344 loc /= dpi_fac;
345
346 if (node->is_frame()) {
347 const float2 delta = loc - float2(node->location);
348 move_child_nodes(*node, delta);
349 }
350
351 node->location[0] = loc.x;
352 node->location[1] = loc.y;
353 }
354
355 /* Handle intersection with noodles. */
356 if (tc->data_len == 1) {
358 *snode, *t->region, t->modifiers & MOD_NODE_ATTACH, customdata->is_new_node);
359 }
361 }
362}
363
365
366/* -------------------------------------------------------------------- */
369
371{
372 Main *bmain = CTX_data_main(C);
373 SpaceNode *snode = (SpaceNode *)t->area->spacedata.first;
374 bNodeTree *ntree = snode->edittree;
375 const TransCustomDataNode &customdata = *(TransCustomDataNode *)t->custom.type.data;
376
377 const bool canceled = (t->state == TRANS_CANCEL);
378
379 if (canceled) {
380 for (auto &&[node, parent] : customdata.old_parent_by_detached_node.items()) {
381 bke::node_attach_node(*ntree, *node, *parent);
382 }
383 }
384 if (canceled && t->remove_on_cancel) {
385 /* Remove selected nodes on cancel. */
386 if (ntree) {
387 LISTBASE_FOREACH_MUTABLE (bNode *, node, &ntree->nodes) {
388 if (node->flag & NODE_SELECT) {
389 bke::node_remove_node(bmain, *ntree, *node, true);
390 }
391 }
392 BKE_main_ensure_invariants(*bmain, ntree->id);
393 }
394 }
395
396 if (!canceled) {
398 if (t->modifiers & MOD_NODE_ATTACH) {
399 space_node::node_insert_on_link_flags(*bmain, *snode, customdata.is_new_node);
400 }
401 }
402
405
406 wmOperatorType *ot = WM_operatortype_find("NODE_OT_insert_offset", true);
407 BLI_assert(ot);
412}
413
415
417 /*flags*/ (T_POINTS | T_2D_EDIT),
418 /*create_trans_data*/ createTransNodeData,
419 /*recalc_data*/ flushTransNodes,
420 /*special_aftertrans_update*/ special_aftertrans_update__node,
421};
422
423} // namespace blender::ed::transform
Main * CTX_data_main(const bContext *C)
void BKE_main_ensure_invariants(Main &bmain, std::optional< blender::Span< ID * > > modified_ids=std::nullopt)
#define BLI_assert(a)
Definition BLI_assert.h:46
#define LISTBASE_FOREACH_MUTABLE(type, var, list)
void unit_m3(float m[3][3])
MINLINE void copy_v2_v2(float r[2], const float a[2])
MINLINE void copy_v3_v3(float r[3], const float a[3])
void BLI_rctf_transform_pt_v(const rctf *dst, const rctf *src, float xy_dst[2], const float xy_src[2])
Definition rct.cc:526
bool BLI_rctf_compare(const struct rctf *rect_a, const struct rctf *rect_b, float limit)
@ NODE_SELECT
@ NODE_FRAME_SHRINK
@ SCE_SNAP_TO_INCREMENT
@ SCE_SNAP_TO_GRID
#define UI_SCALE_FAC
#define NODE_EDGE_PAN_OUTSIDE_PAD
Definition ED_node_c.hh:29
#define NODE_EDGE_PAN_INSIDE_PAD
Definition ED_node_c.hh:28
#define NODE_EDGE_PAN_MAX_SPEED
Definition ED_node_c.hh:31
void ED_node_post_apply_transform(bContext *C, bNodeTree *ntree)
Definition node_edit.cc:856
#define NODE_EDGE_PAN_DELAY
Definition ED_node_c.hh:32
#define NODE_EDGE_PAN_ZOOM_INFLUENCE
Definition ED_node_c.hh:33
#define NODE_EDGE_PAN_SPEED_RAMP
Definition ED_node_c.hh:30
Read Guarded memory(de)allocation.
#define C
Definition RandGen.cpp:29
void UI_view2d_edge_pan_cancel(bContext *C, View2DEdgePanData *vpd)
void UI_view2d_edge_pan_init(bContext *C, View2DEdgePanData *vpd, float inside_pad, float outside_pad, float speed_ramp, float max_speed, float delay, float zoom_influence)
void UI_view2d_edge_pan_apply(bContext *C, View2DEdgePanData *vpd, const int xy[2]) ATTR_NONNULL(1
BMesh const char void * data
bool add(const Key &key, const Value &value)
Definition BLI_map.hh:295
ItemIterator items() const &
Definition BLI_map.hh:902
bool contains(const Key &key) const
void append(const T &value)
bool is_empty() const
void * MEM_calloc_arrayN(size_t len, size_t size, const char *str)
Definition mallocn.cc:123
void node_attach_node(bNodeTree &ntree, bNode &node, bNode &parent)
Definition node.cc:3978
void node_remove_node(Main *bmain, bNodeTree &ntree, bNode &node, bool do_id_user, bool remove_animation=true)
Definition node.cc:4386
void node_detach_node(bNodeTree &ntree, bNode &node)
Definition node.cc:3986
void node_insert_on_link_flags_set(SpaceNode &snode, const ARegion &region, bool attach_enabled, bool is_new_node)
void node_insert_on_frame_flag_set(bContext &C, SpaceNode &snode, const int2 &cursor)
void node_insert_on_link_flags_clear(bNodeTree &node_tree)
void node_insert_on_frame_flag_clear(SpaceNode &snode)
void node_insert_on_link_flags(Main &bmain, SpaceNode &snode, bool is_new_node)
TransConvertTypeInfo TransConvertType_Node
static bool has_selected_parent(const bNode &node)
static void special_aftertrans_update__node(bContext *C, TransInfo *t)
static void node_snap_grid_apply(TransInfo *t)
static void createTransNodeData(bContext *C, TransInfo *t)
static void flushTransNodes(TransInfo *t)
static void create_transform_data_for_node(TransData &td, TransData2D &td2d, bNode &node, const float dpi_fac)
void transformViewUpdate(TransInfo *t)
bool transform_snap_is_active(const TransInfo *t)
static bool transform_tied_to_other_node(bNode *node, VectorSet< bNode * > transformed_nodes)
static VectorSet< bNode * > get_transformed_nodes(bNodeTree &node_tree)
static void move_child_nodes(bNode &node, const float2 &delta)
static bool is_node_parent_select(const bNode *node)
bool is_zero(const T &a)
T round(const T &a)
VecBase< int32_t, 2 > int2
VecBase< float, 2 > float2
void * first
ListBase spacedata
struct bNodeTree * edittree
ListBase nodes
float location[2]
struct bNode * parent
void * storage
void(* free_cb)(TransInfo *, TransDataContainer *tc, TransCustomData *custom_data)
Definition transform.hh:633
TransCustomDataContainer custom
Definition transform.hh:974
int ymin
int xmin
i
Definition text_draw.cc:230
#define TRANS_DATA_CONTAINER_FIRST_SINGLE(t)
Definition transform.hh:39
#define T_PROP_EDIT_ALL
Definition transform.hh:28
#define FOREACH_TRANS_DATA_CONTAINER(t, th)
Definition transform.hh:42
conversion and adaptation of different datablocks to a common struct.
int xy[2]
Definition wm_draw.cc:178
wmOperatorStatus WM_operator_name_call_ptr(bContext *C, wmOperatorType *ot, blender::wm::OpCallContext context, PointerRNA *properties, const wmEvent *event)
wmOperatorStatus WM_operator_name_call(bContext *C, const char *opstring, blender::wm::OpCallContext context, PointerRNA *properties, const wmEvent *event)
PointerRNA * ptr
Definition wm_files.cc:4238
wmOperatorType * ot
Definition wm_files.cc:4237
wmOperatorType * WM_operatortype_find(const char *idname, bool quiet)
void WM_operator_properties_create_ptr(PointerRNA *ptr, wmOperatorType *ot)
void WM_operator_properties_free(PointerRNA *ptr)