Blender V4.5
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.ext = nullptr;
78 td.val = nullptr;
79
80 td.flag = TD_SELECTED;
81 td.dist = 0.0f;
82
83 unit_m3(td.mtx);
84 unit_m3(td.smtx);
85
86 td.extra = &node;
87}
88
89static bool is_node_parent_select(const bNode *node)
90{
91 while ((node = node->parent)) {
92 if (node->flag & NODE_SELECT) {
93 return true;
94 }
95 }
96 return false;
97}
98
105static bool transform_tied_to_other_node(bNode *node, VectorSet<bNode *> transformed_nodes)
106{
107 /* Check for frame nodes that adjust their size based on the contained child nodes. */
108 if (node->is_frame()) {
109 const NodeFrame *data = static_cast<const NodeFrame *>(node->storage);
110 const bool shrinking = data->flag & NODE_FRAME_SHRINK;
111 const bool is_parent = !(node->direct_children_in_frame().is_empty());
112
113 if (is_parent && shrinking) {
114 return true;
115 }
116 }
117
118 /* Now check for child nodes of manually resized frames. */
119 while ((node = node->parent)) {
120 const NodeFrame *parent_data = (const NodeFrame *)node->storage;
121 const bool parent_shrinking = parent_data->flag & NODE_FRAME_SHRINK;
122 const bool parent_transformed = transformed_nodes.contains(node);
123
124 if (parent_transformed && !parent_shrinking) {
125 return true;
126 }
127 }
128
129 return false;
130}
131
133{
134 VectorSet<bNode *> nodes = node_tree.all_nodes();
135
136 /* Keep only nodes that are selected or inside a frame that is selected. */
137 nodes.remove_if([&](bNode *node) {
138 const bool node_selected = node->flag & NODE_SELECT;
139 const bool parent_selected = is_node_parent_select(node);
140 return (!node_selected && !parent_selected);
141 });
142
143 /* Remove nodes that are transformed together with their parent or child nodes. */
144 nodes.remove_if([&](bNode *node) { return transform_tied_to_other_node(node, nodes); });
145
146 return nodes;
147}
148
150{
151 SpaceNode *snode = static_cast<SpaceNode *>(t->area->spacedata.first);
152 bNodeTree *node_tree = snode->edittree;
153 if (!node_tree) {
154 return;
155 }
156
157 /* Custom data to enable edge panning during the node transform. */
158 TransCustomDataNode *customdata = MEM_new<TransCustomDataNode>(__func__);
160 &customdata->edgepan_data,
167 customdata->viewrect_prev = customdata->edgepan_data.initial_rect;
168 customdata->is_new_node = t->remove_on_cancel;
169
171 *snode, *t->region, t->modifiers & MOD_NODE_ATTACH, customdata->is_new_node);
173
174 t->custom.type.data = customdata;
175 t->custom.type.free_cb = [](TransInfo *, TransDataContainer *, TransCustomData *custom_data) {
176 TransCustomDataNode *data = static_cast<TransCustomDataNode *>(custom_data->data);
177 MEM_delete(data);
178 custom_data->data = nullptr;
179 };
180
182
183 /* Nodes don't support proportional editing and probably never will. */
184 t->flag = t->flag & ~T_PROP_EDIT_ALL;
185
187 if (nodes.is_empty()) {
188 return;
189 }
190
191 tc->data_len = nodes.size();
192 tc->data = MEM_calloc_arrayN<TransData>(tc->data_len, __func__);
194
195 for (const int i : nodes.index_range()) {
197 }
198}
199
201
202/* -------------------------------------------------------------------- */
205
207{
208 if (!(transform_snap_is_active(t) &&
210 {
211 return;
212 }
213
214 float2 grid_size = t->snap_spatial;
215 if (t->modifiers & MOD_PRECISION) {
216 grid_size *= t->snap_spatial_precision;
217 }
218
219 /* Early exit on unusable grid size. */
220 if (math::is_zero(grid_size)) {
221 return;
222 }
223
225 for (const int i : IndexRange(tc->data_len)) {
226 TransData &td = tc->data[i];
227 if (td.flag & TD_SKIP) {
228 continue;
229 }
230
231 if ((t->flag & T_PROP_EDIT) && (td.factor == 0.0f)) {
232 continue;
233 }
234
235 /* Nodes are snapped to the grid by first aligning their inital position to the grid and then
236 * offsetting them in grid increments.
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()) {
322 WM_operator_name_call(t->context, "NODE_OT_attach", WM_OP_INVOKE_DEFAULT, nullptr, nullptr);
323 }
324 else {
325 for (bNode *node : nodes_to_detach) {
326 bke::node_detach_node(*snode->edittree, *node);
327 }
328 }
329 }
330
333
334 /* Flush to 2d vector from internally used 3d vector. */
335 for (int i = 0; i < tc->data_len; i++) {
336 TransData *td = &tc->data[i];
337 TransData2D *td2d = &tc->data_2d[i];
338 bNode *node = static_cast<bNode *>(td->extra);
339
340 float2 loc = float2(td2d->loc) + offset;
341
342 /* Weirdo - but the node system is a mix of free 2d elements and DPI sensitive UI. */
343 loc /= dpi_fac;
344
345 if (node->is_frame()) {
346 const float2 delta = loc - float2(node->location);
347 move_child_nodes(*node, delta);
348 }
349
350 node->location[0] = loc.x;
351 node->location[1] = loc.y;
352 }
353
354 /* Handle intersection with noodles. */
355 if (tc->data_len == 1) {
357 *snode, *t->region, t->modifiers & MOD_NODE_ATTACH, customdata->is_new_node);
358 }
360 }
361}
362
364
365/* -------------------------------------------------------------------- */
368
370{
371 Main *bmain = CTX_data_main(C);
372 SpaceNode *snode = (SpaceNode *)t->area->spacedata.first;
373 bNodeTree *ntree = snode->edittree;
374 const TransCustomDataNode &customdata = *(TransCustomDataNode *)t->custom.type.data;
375
376 const bool canceled = (t->state == TRANS_CANCEL);
377
378 if (canceled) {
379 for (auto &&[node, parent] : customdata.old_parent_by_detached_node.items()) {
380 bke::node_attach_node(*ntree, *node, *parent);
381 }
382 }
383 if (canceled && t->remove_on_cancel) {
384 /* Remove selected nodes on cancel. */
385 if (ntree) {
386 LISTBASE_FOREACH_MUTABLE (bNode *, node, &ntree->nodes) {
387 if (node->flag & NODE_SELECT) {
388 bke::node_remove_node(bmain, *ntree, *node, true);
389 }
390 }
391 BKE_main_ensure_invariants(*bmain, ntree->id);
392 }
393 }
394
395 if (!canceled) {
397 if (t->modifiers & MOD_NODE_ATTACH) {
398 space_node::node_insert_on_link_flags(*bmain, *snode, customdata.is_new_node);
399 }
400 }
401
404
405 wmOperatorType *ot = WM_operatortype_find("NODE_OT_insert_offset", true);
406 BLI_assert(ot);
411}
412
414
416 /*flags*/ (T_POINTS | T_2D_EDIT),
417 /*create_trans_data*/ createTransNodeData,
418 /*recalc_data*/ flushTransNodes,
419 /*special_aftertrans_update*/ special_aftertrans_update__node,
420};
421
422} // 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:920
#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
@ WM_OP_INVOKE_DEFAULT
Definition WM_types.hh:238
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:4255
void node_remove_node(Main *bmain, bNodeTree &ntree, bNode &node, bool do_id_user, bool remove_animation=true)
Definition node.cc:4649
void node_detach_node(bNodeTree &ntree, bNode &node)
Definition node.cc:4263
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:628
TransCustomDataContainer custom
Definition transform.hh:968
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:174
wmOperatorStatus WM_operator_name_call_ptr(bContext *C, wmOperatorType *ot, wmOperatorCallContext context, PointerRNA *properties, const wmEvent *event)
wmOperatorStatus WM_operator_name_call(bContext *C, const char *opstring, wmOperatorCallContext context, PointerRNA *properties, const wmEvent *event)
PointerRNA * ptr
Definition wm_files.cc:4227
wmOperatorType * ot
Definition wm_files.cc:4226
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)