Files
test/source/blender/editors/transform/transform_generics.cc

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

1516 lines
43 KiB
C++
Raw Normal View History

/* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved.
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup edtransform
2011-02-27 20:29:51 +00:00
*/
#include <algorithm>
#include "DNA_brush_types.h"
#include "BLI_listbase.h"
#include "BLI_math_matrix.h"
#include "BLI_math_rotation.h"
#include "BLI_math_vector.h"
#include "BLI_rand.h"
#include "BLI_string_utf8.h"
#include "BLI_time.h"
#include "BLT_translation.hh"
#include "RNA_access.hh"
#include "BKE_brush.hh"
#include "BKE_context.hh"
2024-01-23 15:18:09 -05:00
#include "BKE_layer.hh"
#include "BKE_mask.h"
2023-11-14 09:30:40 +01:00
#include "BKE_modifier.hh"
#include "BKE_paint.hh"
#include "BKE_screen.hh"
#include "SEQ_transform.hh"
#include "ED_clip.hh"
#include "ED_image.hh"
#include "ED_object.hh"
#include "ED_screen.hh"
#include "ED_space_api.hh"
#include "ED_uvedit.hh"
#include "WM_api.hh"
#include "UI_view2d.hh"
#include "SEQ_sequencer.hh"
#include "transform.hh"
#include "transform_convert.hh"
#include "transform_gizmo.hh"
#include "transform_orientations.hh"
#include "transform_snap.hh"
namespace blender::ed::transform {
/* ************************** GENERICS **************************** */
void resetTransModal(TransInfo *t)
{
freeTransCustomDataForMode(t);
}
void resetTransRestrictions(TransInfo *t)
{
t->flag &= ~T_ALL_RESTRICTIONS;
}
static void *t_view_get(TransInfo *t)
{
if (t->spacetype == SPACE_VIEW3D) {
View3D *v3d = static_cast<View3D *>(t->area->spacedata.first);
return (void *)v3d;
}
2021-11-19 23:48:51 -05:00
if (t->region) {
return (void *)&t->region->v2d;
}
return nullptr;
}
static int t_around_get(TransInfo *t)
{
if (t->flag & T_OVERRIDE_CENTER) {
/* Avoid initialization of individual origins (#V3D_AROUND_LOCAL_ORIGINS). */
return V3D_AROUND_CENTER_BOUNDS;
}
ScrArea *area = t->area;
switch (t->spacetype) {
case SPACE_VIEW3D: {
if (t->mode == TFM_BEND) {
/* Bend always uses the cursor. */
return V3D_AROUND_CURSOR;
}
return t->settings->transform_pivot_point;
}
case SPACE_IMAGE: {
SpaceImage *sima = static_cast<SpaceImage *>(area->spacedata.first);
return sima->around;
}
case SPACE_GRAPH: {
SpaceGraph *sipo = static_cast<SpaceGraph *>(area->spacedata.first);
return sipo->around;
}
case SPACE_CLIP: {
SpaceClip *sclip = static_cast<SpaceClip *>(area->spacedata.first);
return sclip->around;
}
case SPACE_SEQ: {
if (t->region->regiontype == RGN_TYPE_PREVIEW) {
return seq::tool_settings_pivot_point_get(t->scene);
}
break;
}
default:
break;
}
return V3D_AROUND_CENTER_BOUNDS;
}
void initTransInfo(bContext *C, TransInfo *t, wmOperator *op, const wmEvent *event)
{
Scene *sce = CTX_data_scene(C);
ViewLayer *view_layer = CTX_data_view_layer(C);
ViewLayer: Lazy sync of scene data. When a change happens which invalidates view layers the syncing will be postponed until the first usage. This will improve importing or adding many objects in a single operation/script. `BKE_view_layer_need_resync_tag` is used to tag the view layer to be out of sync. Before accessing `BKE_view_layer_active_base_get`, `BKE_view_layer_active_object_get`, `BKE_view_layer_active_collection` or `BKE_view_layer_object_bases` the caller should call `BKE_view_layer_synced_ensure`. Having two functions ensures that partial syncing could be added as smaller patches in the future. Tagging a view layer out of sync could be replaced with a partial sync. Eventually the number of full resyncs could be reduced. After all tagging has been replaced with partial syncs the ensure_sync could be phased out. This patch has been added to discuss the details and consequences of the current approach. For clarity the call to BKE_view_layer_ensure_sync is placed close to the getters. In the future this could be placed in more strategical places to reduce the number of calls or improve performance. Finding those strategical places isn't that clear. When multiple operations are grouped in a single script you might want to always check for resync. Some areas found that can be improved. This list isn't complete. These areas aren't addressed by this patch as these changes would be hard to detect to the reviewer. The idea is to add changes to these areas as a separate patch. It might be that the initial commit would reduce performance compared to master, but will be fixed by the additional patches. **Object duplication** During object duplication the syncing is temporarily disabled. With this patch this isn't useful as when disabled the view_layer is accessed to locate bases. This can be improved by first locating the source bases, then duplicate and sync and locate the new bases. Will be solved in a separate patch for clarity reasons ({D15886}). **Object add** `BKE_object_add` not only adds a new object, but also selects and activates the new base. This requires the view_layer to be resynced. Some callers reverse the selection and activation (See `get_new_constraint_target`). We should make the selection and activation optional. This would make it possible to add multiple objects without having to resync per object. **Postpone Activate Base** Setting the basact is done in many locations. They follow a rule as after an action find the base and set the basact. Finding the base could require a resync. The idea is to store in the view_layer the object which base will be set in the basact during the next sync, reducing the times resyncing needs to happen. Reviewed By: mont29 Maniphest Tasks: T73411 Differential Revision: https://developer.blender.org/D15885
2022-09-14 21:33:51 +02:00
BKE_view_layer_synced_ensure(sce, view_layer);
Object *obact = BKE_view_layer_active_object_get(view_layer);
const eObjectMode object_mode = eObjectMode(obact ? obact->mode : OB_MODE_OBJECT);
ToolSettings *ts = CTX_data_tool_settings(C);
ARegion *region = CTX_wm_region(C);
ScrArea *area = CTX_wm_area(C);
VSE: Scene Selector & Scene Time Synchronization Implements the proposed design (with some modifications) in #135058. ## Sequencer Scene This adds a new property called `sequencer_scene` to workspaces. This scene is used by the video sequence editors in the current workspace for their context. This is a first step towards "detaching" the VSE from the active scene in the window. Each sequencer timeline editor shows the sequencer scene that is being used. By default, when no sequencer scene is selected, the timeline and preview are empty. Pressing the "new" button will add a new scene and assign it to the sequencer scene for the current workspace. ## Contextual Playback Pressing `Space` (by default) for starting the animation playback is now contextual: depending on the context (where your mouse cursor is), the scene that is played back might be different. E.g. with a 3D Viewport and a Sequencer open, pressing "play" in the 3D Viewport will play the _active scene_ of the window, while pressing "play" in the sequencer will play the _sequencer scene_. ## Time & Scene Synchronization Additionally, this adds a toggle called "Sync Active Scene". With the property turned on, the active scene & scene time in the window will be synced with the time & scene of the current scene strip in the sequencer. Note that this is _not_ bi-directional. The sequencer can change the active scene and map time, but it's not possible the other way around since it one can have multiple strips using the same scene (+camera, and even time!). Currently this setting is exposed in the footer of the sequencer timeline as well as in the workspace settings. This allows for one of the core concepts that the story tools projects aims at: Working in a scene (e.g. in the 3D viewport) while also working with the edit (in the sequencer timeline). ## Some technical notes * Undoing while playback is running will now cancel playback. This is to avoid the timer, that points to the scene and viewlayer that are playing, to get de-synced after loading the memfile undo step. * When the sequencer scene is not the same as the active scene, we ensure it has a depsgraph. * Normally, when a `NC_SCENE` notifier points to a specific scene, the notifier is dropped if that scene doesn't match the active one in the window. We now also check that it doesn't match the sequencer scene in the active workspace. * When loading older files, we need to make sure that the active workspace in a window uses the active scene as the sequencer scene. This is to make sure that the file opens with the same sequences open. * Tool settings are stored per scene. To make sure the sequencer uses the tool settings for the sequencer scene, the "context.tool_settings" and `CTX_data_tool_settings` members are overridden in the sequence editors. Pull Request: https://projects.blender.org/blender/blender/pulls/140271
2025-08-25 11:58:17 +02:00
const bool is_sequencer = CTX_wm_space_seq(C) != nullptr;
if (!is_sequencer) {
t->scene = sce;
t->view_layer = view_layer;
}
else {
t->scene = CTX_data_sequencer_scene(C);
t->view_layer = t->scene ? BKE_view_layer_default_render(t->scene) : nullptr;
}
PropertyRNA *prop;
t->mbus = CTX_wm_message_bus(C);
Refactor access to dependency graph This change ensures that operators which needs access to evaluated data first makes sure there is a dependency graph. Other accesses to the dependency graph made it more explicit about whether they just need a valid dependency graph pointer or whether they expect the graph to be already evaluated. This replaces OPTYPE_USE_EVAL_DATA which is now removed. Some general rules about usage of accessors: - Drawing is expected to happen from a fully evaluated dependency graph. There is now a function to access it, which will in the future control that dependency graph is actually evaluated. This check is not yet done because there are some things to be taken care about first: for example, post-update hooks might leave scene in a state where something is still tagged for update. - All operators which needs to access evaluated state must use CTX_data_ensure_evaluated_depsgraph(). This function replaces OPTYPE_USE_EVAL_DATA. The call is generally to be done in the very beginning of the operator, prior other logic (unless this is some comprehensive operator which might or might not need access to an evaluated state). This call is never to be used from a loop. If some utility function requires evaluated state of dependency graph the graph is to be passed as an explicit argument. This way it is clear that no evaluation happens in a loop or something like this. - All cases which needs to know dependency graph pointer, but which doesn't want to actually evaluate it can use old-style function CTX_data_depsgraph_pointer(), assuming that underlying code will ensure dependency graph is evaluated prior to accessing it. - The new functions are replacing OPTYPE_USE_EVAL_DATA, so now it is explicit and local about where dependency graph is being ensured. This commit also contains some fixes of wrong usage of evaluation functions on original objects. Ideally should be split out, but in reality with all the APIs being renamed is quite tricky. Fixes T67454: Blender crash on rapid undo and select Speculation here is that sometimes undo and selection operators are sometimes handled in the same event loop iteration, which leaves non-evaluated dependency graph. Fixes T67973: Crash on Fix Deforms operator Fixes T67902: Crash when undo a loop cut Reviewers: brecht Reviewed By: brecht Subscribers: lichtwerk Maniphest Tasks: T67454 Differential Revision: https://developer.blender.org/D5343
2019-07-25 16:36:22 +02:00
t->depsgraph = CTX_data_depsgraph_pointer(C);
VSE: Scene Selector & Scene Time Synchronization Implements the proposed design (with some modifications) in #135058. ## Sequencer Scene This adds a new property called `sequencer_scene` to workspaces. This scene is used by the video sequence editors in the current workspace for their context. This is a first step towards "detaching" the VSE from the active scene in the window. Each sequencer timeline editor shows the sequencer scene that is being used. By default, when no sequencer scene is selected, the timeline and preview are empty. Pressing the "new" button will add a new scene and assign it to the sequencer scene for the current workspace. ## Contextual Playback Pressing `Space` (by default) for starting the animation playback is now contextual: depending on the context (where your mouse cursor is), the scene that is played back might be different. E.g. with a 3D Viewport and a Sequencer open, pressing "play" in the 3D Viewport will play the _active scene_ of the window, while pressing "play" in the sequencer will play the _sequencer scene_. ## Time & Scene Synchronization Additionally, this adds a toggle called "Sync Active Scene". With the property turned on, the active scene & scene time in the window will be synced with the time & scene of the current scene strip in the sequencer. Note that this is _not_ bi-directional. The sequencer can change the active scene and map time, but it's not possible the other way around since it one can have multiple strips using the same scene (+camera, and even time!). Currently this setting is exposed in the footer of the sequencer timeline as well as in the workspace settings. This allows for one of the core concepts that the story tools projects aims at: Working in a scene (e.g. in the 3D viewport) while also working with the edit (in the sequencer timeline). ## Some technical notes * Undoing while playback is running will now cancel playback. This is to avoid the timer, that points to the scene and viewlayer that are playing, to get de-synced after loading the memfile undo step. * When the sequencer scene is not the same as the active scene, we ensure it has a depsgraph. * Normally, when a `NC_SCENE` notifier points to a specific scene, the notifier is dropped if that scene doesn't match the active one in the window. We now also check that it doesn't match the sequencer scene in the active workspace. * When loading older files, we need to make sure that the active workspace in a window uses the active scene as the sequencer scene. This is to make sure that the file opens with the same sequences open. * Tool settings are stored per scene. To make sure the sequencer uses the tool settings for the sequencer scene, the "context.tool_settings" and `CTX_data_tool_settings` members are overridden in the sequence editors. Pull Request: https://projects.blender.org/blender/blender/pulls/140271
2025-08-25 11:58:17 +02:00
t->area = area;
t->region = region;
t->settings = ts;
t->reports = op ? op->reports : nullptr;
t->helpline = HLP_NONE;
t->flag = eTFlag(0);
if (obact && !(t->options & (CTX_CURSOR | CTX_TEXTURE_SPACE)) &&
ELEM(object_mode, OB_MODE_EDIT, OB_MODE_EDIT_GPENCIL_LEGACY))
{
t->obedit_type = obact->type;
}
else {
t->obedit_type = -1;
}
if (t->options & CTX_CURSOR) {
/* Cursor should always use the drag start as the combination of click-drag to place & move
* doesn't work well if the click location isn't used when transforming. */
t->flag |= T_EVENT_DRAG_START;
}
/* Many kinds of transform only use a single handle. */
if (t->data_container == nullptr) {
t->data_container = static_cast<TransDataContainer *>(
MEM_callocN(sizeof(*t->data_container), __func__));
t->data_container_len = 1;
}
t->redraw = TREDRAW_HARD; /* Redraw first time. */
float2 mval;
if (event) {
if (t->flag & T_EVENT_DRAG_START) {
WM_event_drag_start_mval_fl(event, region, mval);
}
else {
mval = float2(event->mval);
}
}
else {
mval = float2(0, 0);
}
t->mval = mval;
/* Initialize this mouse variable in advance as it is required by
* `transform_convert_frame_side_dir_get` which is called before `initMouseInput`. */
t->mouse.imval = mval;
t->mode_info = nullptr;
t->data_len_all = 0;
zero_v3(t->center_global);
unit_m3(t->mat);
/* Default to rotate on the Z axis. */
t->orient_axis = 2;
t->orient_axis_ortho = 1;
/* If there's an event, we're modal. */
if (event) {
t->flag |= T_MODAL;
}
/* Crease needs edge flag. */
if (ELEM(t->mode, TFM_EDGE_CREASE, TFM_BWEIGHT)) {
t->options |= CTX_EDGE_DATA;
}
t->remove_on_cancel = false;
if (op && (prop = RNA_struct_find_property(op->ptr, "remove_on_cancel")) &&
RNA_property_is_set(op->ptr, prop))
{
if (RNA_property_boolean_get(op->ptr, prop)) {
t->remove_on_cancel = true;
}
}
/* Grease Pencil editing context. */
if (t->obedit_type == OB_GREASE_PENCIL && object_mode == OB_MODE_EDIT &&
((area == nullptr) || (area->spacetype == SPACE_VIEW3D)))
{
t->options |= CTX_GPENCIL_STROKES;
}
/* Assign the space type, some exceptions for running in different mode. */
if (area == nullptr) {
/* Background mode. */
2012-06-10 19:59:02 +00:00
t->spacetype = SPACE_EMPTY;
}
else if (((region == nullptr) || (region->regiondata == nullptr)) &&
2024-03-11 11:40:04 +01:00
(area->spacetype == SPACE_VIEW3D))
{
/* Running the operator through the text editor where e.g. `area.type` was
* set to 'VIEW_3D' but the viewport was not updated. */
2012-06-10 19:59:02 +00:00
t->spacetype = SPACE_EMPTY;
}
else {
/* Normal operation. */
t->spacetype = area->spacetype;
}
/* Handle #T_ALT_TRANSFORM initialization, we may use for different operators. */
if (op) {
const char *prop_id = nullptr;
if (t->mode == TFM_SHRINKFATTEN) {
prop_id = "use_even_offset";
}
if (prop_id && (prop = RNA_struct_find_property(op->ptr, prop_id))) {
SET_FLAG_FROM_TEST(t->flag, RNA_property_boolean_get(op->ptr, prop), T_ALT_TRANSFORM);
}
}
if (t->spacetype == SPACE_VIEW3D) {
bScreen *animscreen = ED_screen_animation_playing(CTX_wm_manager(C));
t->animtimer = (animscreen) ? animscreen->animtimer : nullptr;
if (t->scene->toolsettings->transform_flag & SCE_XFORM_AXIS_ALIGN) {
t->flag |= T_V3D_ALIGN;
}
if ((object_mode & OB_MODE_ALL_PAINT) || (object_mode & OB_MODE_SCULPT_CURVES)) {
Paint *paint = BKE_paint_get_active_from_context(C);
Brush *brush = (paint) ? BKE_paint_brush(paint) : nullptr;
if (brush && (brush->flag & BRUSH_CURVE)) {
t->options |= CTX_PAINT_CURVE;
}
}
/* Initialize UV transform from. */
if (op && (prop = RNA_struct_find_property(op->ptr, "correct_uv"))) {
if (RNA_property_is_set(op->ptr, prop)) {
if (RNA_property_boolean_get(op->ptr, prop)) {
t->settings->uvcalc_flag |= UVCALC_TRANSFORM_CORRECT_SLIDE;
2011-08-03 08:02:32 +00:00
}
else {
t->settings->uvcalc_flag &= ~UVCALC_TRANSFORM_CORRECT_SLIDE;
2011-08-03 08:02:32 +00:00
}
}
2011-08-03 08:02:32 +00:00
else {
RNA_property_boolean_set(
op->ptr, prop, (t->settings->uvcalc_flag & UVCALC_TRANSFORM_CORRECT_SLIDE) != 0);
}
}
}
2012-06-10 19:59:02 +00:00
else if (t->spacetype == SPACE_IMAGE) {
SpaceImage *sima = static_cast<SpaceImage *>(area->spacedata.first);
ViewLayer: Lazy sync of scene data. When a change happens which invalidates view layers the syncing will be postponed until the first usage. This will improve importing or adding many objects in a single operation/script. `BKE_view_layer_need_resync_tag` is used to tag the view layer to be out of sync. Before accessing `BKE_view_layer_active_base_get`, `BKE_view_layer_active_object_get`, `BKE_view_layer_active_collection` or `BKE_view_layer_object_bases` the caller should call `BKE_view_layer_synced_ensure`. Having two functions ensures that partial syncing could be added as smaller patches in the future. Tagging a view layer out of sync could be replaced with a partial sync. Eventually the number of full resyncs could be reduced. After all tagging has been replaced with partial syncs the ensure_sync could be phased out. This patch has been added to discuss the details and consequences of the current approach. For clarity the call to BKE_view_layer_ensure_sync is placed close to the getters. In the future this could be placed in more strategical places to reduce the number of calls or improve performance. Finding those strategical places isn't that clear. When multiple operations are grouped in a single script you might want to always check for resync. Some areas found that can be improved. This list isn't complete. These areas aren't addressed by this patch as these changes would be hard to detect to the reviewer. The idea is to add changes to these areas as a separate patch. It might be that the initial commit would reduce performance compared to master, but will be fixed by the additional patches. **Object duplication** During object duplication the syncing is temporarily disabled. With this patch this isn't useful as when disabled the view_layer is accessed to locate bases. This can be improved by first locating the source bases, then duplicate and sync and locate the new bases. Will be solved in a separate patch for clarity reasons ({D15886}). **Object add** `BKE_object_add` not only adds a new object, but also selects and activates the new base. This requires the view_layer to be resynced. Some callers reverse the selection and activation (See `get_new_constraint_target`). We should make the selection and activation optional. This would make it possible to add multiple objects without having to resync per object. **Postpone Activate Base** Setting the basact is done in many locations. They follow a rule as after an action find the base and set the basact. Finding the base could require a resync. The idea is to store in the view_layer the object which base will be set in the basact during the next sync, reducing the times resyncing needs to happen. Reviewed By: mont29 Maniphest Tasks: T73411 Differential Revision: https://developer.blender.org/D15885
2022-09-14 21:33:51 +02:00
BKE_view_layer_synced_ensure(t->scene, t->view_layer);
if (ED_space_image_show_uvedit(sima, BKE_view_layer_active_object_get(t->view_layer))) {
/* UV transform. */
}
else if (sima->mode == SI_MODE_MASK) {
t->options |= CTX_MASK;
}
else if (sima->mode == SI_MODE_PAINT) {
Paint *paint = &sce->toolsettings->imapaint.paint;
Brush *brush = (paint) ? BKE_paint_brush(paint) : nullptr;
if (brush && (brush->flag & BRUSH_CURVE)) {
t->options |= CTX_PAINT_CURVE;
}
}
/* Image not in UV edit, nor in mask mode, can happen for some tools. */
}
2012-06-10 19:59:02 +00:00
else if (t->spacetype == SPACE_CLIP) {
SpaceClip *sclip = static_cast<SpaceClip *>(area->spacedata.first);
2019-04-22 09:19:45 +10:00
if (ED_space_clip_check_show_trackedit(sclip)) {
t->options |= CTX_MOVIECLIP;
2019-04-22 09:19:45 +10:00
}
else if (ED_space_clip_check_show_maskedit(sclip)) {
t->options |= CTX_MASK;
2019-04-22 09:19:45 +10:00
}
}
else if (t->spacetype == SPACE_SEQ && region->regiontype == RGN_TYPE_PREVIEW) {
t->options |= CTX_SEQUENCER_IMAGE;
VSE preview transform autokeying improvements NOTE: this patch originated in T98015 which was split into multiple reports. While it could be split into multiple patches these are very much related so keeping as one for now This patch fixes the following issues: [1] autokeying transforms in preview only creates keyframes if there is an FCurve already [2] autokeying transforms in preview only creates keyframes for rotation/scale if rotating/scaling around cursor (should keyframe position as well) [3] autokeying transforms in preview does not work during animation playback For [1], a param was added to `ED_autokeyframe_property` which can tweak its default behavior of only creating keyframes on already keyed properties (which was fine because this is mostly called from buttons where this behavior is desired). Callers such as gizmos (or the VSE in our case) can use this additional param so that keyframes are also created on "not-yet-keyframed" properties. For [2], the pivot is checked and position properties also keyed if necessary (which is also consistent with the way objects are keyed in the 3DView) For [3], `animrecord_check_state` was changed to be able to work on scenes as well and the transform system in the VSE preview was made aware of the screen's `animtimer`. NOTE: there are still things to be improved for keyframing in the VSE, the most obvious is probably a `keyframe_insert` operator (with keyingsets) Fixes T98429, T98430, T98431 Maniphest Tasks: T98015, T98431, T98430, T98429 Differential Revision: https://developer.blender.org/D15047
2022-05-25 13:00:18 +02:00
2024-01-29 11:47:42 +11:00
/* Needed for auto-keying transforms in preview during playback. */
VSE preview transform autokeying improvements NOTE: this patch originated in T98015 which was split into multiple reports. While it could be split into multiple patches these are very much related so keeping as one for now This patch fixes the following issues: [1] autokeying transforms in preview only creates keyframes if there is an FCurve already [2] autokeying transforms in preview only creates keyframes for rotation/scale if rotating/scaling around cursor (should keyframe position as well) [3] autokeying transforms in preview does not work during animation playback For [1], a param was added to `ED_autokeyframe_property` which can tweak its default behavior of only creating keyframes on already keyed properties (which was fine because this is mostly called from buttons where this behavior is desired). Callers such as gizmos (or the VSE in our case) can use this additional param so that keyframes are also created on "not-yet-keyframed" properties. For [2], the pivot is checked and position properties also keyed if necessary (which is also consistent with the way objects are keyed in the 3DView) For [3], `animrecord_check_state` was changed to be able to work on scenes as well and the transform system in the VSE preview was made aware of the screen's `animtimer`. NOTE: there are still things to be improved for keyframing in the VSE, the most obvious is probably a `keyframe_insert` operator (with keyingsets) Fixes T98429, T98430, T98431 Maniphest Tasks: T98015, T98431, T98430, T98429 Differential Revision: https://developer.blender.org/D15047
2022-05-25 13:00:18 +02:00
bScreen *animscreen = ED_screen_animation_playing(CTX_wm_manager(C));
t->animtimer = (animscreen) ? animscreen->animtimer : nullptr;
}
setTransformViewAspect(t, t->aspect);
if (op && (prop = RNA_struct_find_property(op->ptr, "center_override")) &&
RNA_property_is_set(op->ptr, prop))
{
RNA_property_float_get_array(op->ptr, prop, t->center_global);
mul_v3_v3(t->center_global, t->aspect);
t->flag |= T_OVERRIDE_CENTER;
}
t->view = t_view_get(t);
t->around = t_around_get(t);
/* Exceptional case. */
if (t->around == V3D_AROUND_LOCAL_ORIGINS) {
if (ELEM(t->mode, TFM_ROTATION, TFM_RESIZE, TFM_TRACKBALL)) {
const bool use_island = transdata_check_local_islands(t, t->around);
if ((t->obedit_type != -1) && !use_island) {
t->options |= CTX_NO_PET;
}
}
}
bool t_values_set_is_array = false;
if (op && (prop = RNA_struct_find_property(op->ptr, "value")) &&
RNA_property_is_set(op->ptr, prop))
{
float values[4] = {0}; /* In case value isn't length 4, avoid uninitialized memory. */
if (RNA_property_array_check(prop)) {
RNA_property_float_get_array(op->ptr, prop, values);
t_values_set_is_array = true;
}
else {
values[0] = RNA_property_float_get(op->ptr, prop);
}
if (t->flag & T_MODAL) {
/* Run before init functions so 'values_modal_offset' can be applied on mouse input. */
copy_v4_v4(t->values_modal_offset, values);
}
else {
copy_v4_v4(t->values, values);
t->flag |= T_INPUT_IS_VALUES_FINAL;
}
}
if (op && (prop = RNA_struct_find_property(op->ptr, "constraint_axis"))) {
bool constraint_axis[3] = {false, false, false};
if (t_values_set_is_array && t->flag & T_INPUT_IS_VALUES_FINAL) {
/* For operators whose `t->values` is array (as Move and Scale), set constraint so that the
* orientation is more intuitive in the Redo Panel. */
constraint_axis[0] = constraint_axis[1] = constraint_axis[2] = true;
}
else if (RNA_property_is_set(op->ptr, prop)) {
RNA_property_boolean_get_array(op->ptr, prop, constraint_axis);
}
if (constraint_axis[0] || constraint_axis[1] || constraint_axis[2]) {
t->con.mode |= CON_APPLY;
if (constraint_axis[0]) {
t->con.mode |= CON_AXIS0;
}
if (constraint_axis[1]) {
t->con.mode |= CON_AXIS1;
}
if (constraint_axis[2]) {
t->con.mode |= CON_AXIS2;
}
}
}
{
Refactor: Transform: Unify logic of "orient_axis" and "constraint_axis" In rotation-based transform operations, the rotation axis can be determined in two ways: 1. Through "orient_axis" (X, Y or Z) 2. Through "constraint_axis" When the axis is obtained through the constraint, "orient_axis" is ignored, and the angle may be negated depending on the view orientation to match the mouse movement. However, "orient_axis" never has its angle negated. Since the default orientation is "View", the Z axis is inverted by default, aligning with the mouse movement but not with the constraint axis. This causes problems in the Redo Panel because the constraint fields are hidden in the Rotation operation, so they need to be unset for the Axis field to work. However, if you change the value of the Rotation field, the object may have its rotation negated unexpectedly. This issue was partially shown in #93078. Commit c30e6a37b0 attempted to fix it by unsetting the constraint property when the Axis was changed. However, this solution is incomplete: if the Axis is changed and then reverted, the negative rotation issue reappears. In addition, it has not been implemented in all operations. This commit resolves the issue by reverting c30e6a37b0, aligning the behavior of "orient_axis" and "constraint_axis", and unsetting "constraint_axis" in `saveTranform`. A downside of this solution is that it may break operators invoked from Python that rely on "orient_axis" as the rotation axis, as the rotation value now needs to be negated. Pull Request: https://projects.blender.org/blender/blender/pulls/141101
2025-07-04 20:47:25 +02:00
eTOType orient_types[3];
eTOType orient_type_apply = O_DEFAULT;
float custom_matrix[3][3];
int orient_type_scene = V3D_ORIENT_GLOBAL;
int orient_type_default = -1;
int orient_type_set = -1;
int orient_type_matrix_set = -1;
if ((t->spacetype == SPACE_VIEW3D) && (t->region->regiontype == RGN_TYPE_WINDOW)) {
TransformOrientationSlot *orient_slot = &t->scene->orientation_slots[SCE_ORIENT_DEFAULT];
orient_type_scene = orient_slot->type;
if (orient_type_scene == V3D_ORIENT_CUSTOM) {
const int index_custom = orient_slot->index_custom;
orient_type_scene += index_custom;
}
}
if (op && ((prop = RNA_struct_find_property(op->ptr, "orient_type")) &&
RNA_property_is_set(op->ptr, prop)))
{
orient_type_set = RNA_property_enum_get(op->ptr, prop);
if (orient_type_set >= V3D_ORIENT_CUSTOM + BIF_countTransformOrientation(C)) {
orient_type_set = V3D_ORIENT_GLOBAL;
}
}
if (op && (prop = RNA_struct_find_property(op->ptr, "orient_axis"))) {
t->orient_axis = RNA_property_enum_get(op->ptr, prop);
}
if (op && (prop = RNA_struct_find_property(op->ptr, "orient_axis_ortho"))) {
t->orient_axis_ortho = RNA_property_enum_get(op->ptr, prop);
}
/* The properties "orient_matrix" and "orient_matrix_type" are used to store the orientation
* calculated in the first operator call. This allows for reuse of the orientation during
* subsequent calls of the same operator. When making adjustments through the Redo panel
* (#OP_IS_REPEAT), reusing the orientation prevents unpredictable changes that can occur when
* using #V3D_ORIENT_VIEW. However, when activated by #SCREEN_OT_repeat_last
* (#OP_IS_REPEAT_LAST), it's best to avoid reusing the orientation to prevent unintended
* changes. */
if (op && !(op->flag & OP_IS_REPEAT_LAST) &&
((prop = RNA_struct_find_property(op->ptr, "orient_matrix")) &&
RNA_property_is_set(op->ptr, prop)))
{
RNA_property_float_get_array(op->ptr, prop, &custom_matrix[0][0]);
if ((prop = RNA_struct_find_property(op->ptr, "orient_matrix_type")) &&
RNA_property_is_set(op->ptr, prop))
{
orient_type_matrix_set = RNA_property_enum_get(op->ptr, prop);
}
else if (orient_type_set == -1) {
orient_type_set = V3D_ORIENT_CUSTOM_MATRIX;
}
}
orient_type_default = orient_type_scene;
if (orient_type_set != -1) {
if (!(t->con.mode & CON_APPLY)) {
/* Only overwrite default if not constrained. */
orient_type_default = orient_type_set;
t->is_orient_default_overwrite = true;
}
}
else if (orient_type_matrix_set != -1) {
orient_type_set = orient_type_matrix_set;
if (!(t->con.mode & CON_APPLY)) {
/* Only overwrite default if not constrained. */
orient_type_default = orient_type_set;
t->is_orient_default_overwrite = true;
}
}
if (orient_type_set == -1) {
if (orient_type_scene == V3D_ORIENT_GLOBAL) {
orient_type_set = V3D_ORIENT_LOCAL;
}
else {
orient_type_set = V3D_ORIENT_GLOBAL;
}
if (t->con.mode & CON_APPLY) {
orient_type_apply = O_SCENE;
}
}
else {
Refactor: Transform: Unify logic of "orient_axis" and "constraint_axis" In rotation-based transform operations, the rotation axis can be determined in two ways: 1. Through "orient_axis" (X, Y or Z) 2. Through "constraint_axis" When the axis is obtained through the constraint, "orient_axis" is ignored, and the angle may be negated depending on the view orientation to match the mouse movement. However, "orient_axis" never has its angle negated. Since the default orientation is "View", the Z axis is inverted by default, aligning with the mouse movement but not with the constraint axis. This causes problems in the Redo Panel because the constraint fields are hidden in the Rotation operation, so they need to be unset for the Axis field to work. However, if you change the value of the Rotation field, the object may have its rotation negated unexpectedly. This issue was partially shown in #93078. Commit c30e6a37b0 attempted to fix it by unsetting the constraint property when the Axis was changed. However, this solution is incomplete: if the Axis is changed and then reverted, the negative rotation issue reappears. In addition, it has not been implemented in all operations. This commit resolves the issue by reverting c30e6a37b0, aligning the behavior of "orient_axis" and "constraint_axis", and unsetting "constraint_axis" in `saveTranform`. A downside of this solution is that it may break operators invoked from Python that rely on "orient_axis" as the rotation axis, as the rotation value now needs to be negated. Pull Request: https://projects.blender.org/blender/blender/pulls/141101
2025-07-04 20:47:25 +02:00
orient_type_apply = O_SET;
}
BLI_assert(!ELEM(-1, orient_type_default, orient_type_set));
if (orient_type_matrix_set == orient_type_set) {
/* Constraints are forced to use the custom matrix when redoing. */
orient_type_set = V3D_ORIENT_CUSTOM_MATRIX;
}
Refactor: Transform: Unify logic of "orient_axis" and "constraint_axis" In rotation-based transform operations, the rotation axis can be determined in two ways: 1. Through "orient_axis" (X, Y or Z) 2. Through "constraint_axis" When the axis is obtained through the constraint, "orient_axis" is ignored, and the angle may be negated depending on the view orientation to match the mouse movement. However, "orient_axis" never has its angle negated. Since the default orientation is "View", the Z axis is inverted by default, aligning with the mouse movement but not with the constraint axis. This causes problems in the Redo Panel because the constraint fields are hidden in the Rotation operation, so they need to be unset for the Axis field to work. However, if you change the value of the Rotation field, the object may have its rotation negated unexpectedly. This issue was partially shown in #93078. Commit c30e6a37b0 attempted to fix it by unsetting the constraint property when the Axis was changed. However, this solution is incomplete: if the Axis is changed and then reverted, the negative rotation issue reappears. In addition, it has not been implemented in all operations. This commit resolves the issue by reverting c30e6a37b0, aligning the behavior of "orient_axis" and "constraint_axis", and unsetting "constraint_axis" in `saveTranform`. A downside of this solution is that it may break operators invoked from Python that rely on "orient_axis" as the rotation axis, as the rotation value now needs to be negated. Pull Request: https://projects.blender.org/blender/blender/pulls/141101
2025-07-04 20:47:25 +02:00
orient_types[O_DEFAULT] = eTOType(orient_type_default);
orient_types[O_SCENE] = eTOType(orient_type_scene);
orient_types[O_SET] = eTOType(orient_type_set);
for (int i = 0; i < 3; i++) {
/* For efficiency, avoid calculating the same orientation twice. */
int j;
for (j = 0; j < i; j++) {
if (orient_types[j] == orient_types[i]) {
memcpy(&t->orient[i], &t->orient[j], sizeof(*t->orient));
break;
}
}
if (j == i) {
t->orient[i].type = transform_orientation_matrix_get(
C, t, orient_types[i], custom_matrix, t->orient[i].matrix);
}
}
t->orient_type_mask = 0;
for (int i = 0; i < 3; i++) {
const int type = t->orient[i].type;
if (type < V3D_ORIENT_CUSTOM_MATRIX) {
BLI_assert(type < 32);
t->orient_type_mask |= (1 << type);
}
}
transform_orientations_current_set(t, orient_type_apply);
}
if (op && ((prop = RNA_struct_find_property(op->ptr, "release_confirm")) &&
RNA_property_is_set(op->ptr, prop)))
{
if (RNA_property_boolean_get(op->ptr, prop)) {
t->flag |= T_RELEASE_CONFIRM;
}
}
else {
/* Release confirms preference should not affect node editor (#69288, #70504). */
if (ISMOUSE_BUTTON(t->launch_event) &&
2019-10-07 15:17:44 +11:00
((U.flag & USER_RELEASECONFIRM) || (t->spacetype == SPACE_NODE)))
{
/* Global "release confirm" on mouse bindings. */
t->flag |= T_RELEASE_CONFIRM;
}
}
if (op &&
((prop = RNA_struct_find_property(op->ptr, "mirror")) && RNA_property_is_set(op->ptr, prop)))
{
if (!RNA_property_boolean_get(op->ptr, prop)) {
t->flag |= T_NO_MIRROR;
}
}
else if ((t->spacetype == SPACE_VIEW3D) && (t->obedit_type == OB_MESH)) {
/* Pass. */
}
else {
/* Avoid mirroring for unsupported contexts. */
2021-04-24 11:24:53 -03:00
t->flag |= T_NO_MIRROR;
}
/* Setting proportional editing flag only if property exist in operator. Otherwise, assume it's
* not supported. */
if (op && (prop = RNA_struct_find_property(op->ptr, "use_proportional_edit"))) {
if (RNA_property_is_set(op->ptr, prop)) {
2019-05-01 08:36:36 +10:00
if (RNA_property_boolean_get(op->ptr, prop)) {
t->flag |= T_PROP_EDIT;
if (RNA_boolean_get(op->ptr, "use_proportional_connected")) {
t->flag |= T_PROP_CONNECTED;
}
if (RNA_boolean_get(op->ptr, "use_proportional_projected")) {
t->flag |= T_PROP_PROJECTED;
}
}
}
else {
/* Use settings from scene only if modal. */
if (t->flag & T_MODAL) {
if ((t->options & CTX_NO_PET) == 0) {
bool use_prop_edit = false;
if (t->spacetype == SPACE_GRAPH) {
use_prop_edit = ts->proportional_fcurve;
}
else if (t->spacetype == SPACE_ACTION) {
use_prop_edit = ts->proportional_action;
}
else if (t->options & CTX_MASK) {
use_prop_edit = ts->proportional_mask;
}
else if (obact && obact->mode == OB_MODE_OBJECT) {
use_prop_edit = ts->proportional_objects;
}
else {
use_prop_edit = (ts->proportional_edit & PROP_EDIT_USE) != 0;
}
if (use_prop_edit) {
t->flag |= T_PROP_EDIT;
if (ts->proportional_edit & PROP_EDIT_CONNECTED) {
t->flag |= T_PROP_CONNECTED;
}
if (ts->proportional_edit & PROP_EDIT_PROJECTED) {
t->flag |= T_PROP_PROJECTED;
}
}
}
}
}
if (op && ((prop = RNA_struct_find_property(op->ptr, "proportional_size")) &&
RNA_property_is_set(op->ptr, prop)))
{
t->prop_size = RNA_property_float_get(op->ptr, prop);
}
else {
t->prop_size = ts->proportional_size;
}
/* TRANSFORM_FIX_ME rna restrictions. */
if (t->prop_size <= 0.00001f) {
2012-10-20 20:36:51 +00:00
printf("Proportional size (%f) under 0.00001, resetting to 1!\n", t->prop_size);
t->prop_size = 1.0f;
}
if (op && ((prop = RNA_struct_find_property(op->ptr, "proportional_edit_falloff")) &&
RNA_property_is_set(op->ptr, prop)))
{
t->prop_mode = RNA_property_enum_get(op->ptr, prop);
}
else {
t->prop_mode = ts->prop_mode;
}
}
else { /* Add not pet option to context when not available. */
t->options |= CTX_NO_PET;
}
if (op && (prop = RNA_struct_find_property(op->ptr, "use_automerge_and_split")) &&
RNA_property_is_set(op->ptr, prop))
{
if (RNA_property_boolean_get(op->ptr, prop)) {
t->flag |= T_AUTOMERGE | T_AUTOSPLIT;
}
}
else if (t->obedit_type == OB_MESH) {
char automerge = t->scene->toolsettings->automerge;
if (automerge & AUTO_MERGE) {
t->flag |= T_AUTOMERGE;
if (automerge & AUTO_MERGE_AND_SPLIT) {
t->flag |= T_AUTOSPLIT;
}
}
}
if (op && (prop = RNA_struct_find_property(op->ptr, "use_duplicated_keyframes")) &&
RNA_property_is_set(op->ptr, prop))
{
if (RNA_property_boolean_get(op->ptr, prop)) {
t->flag |= T_DUPLICATED_KEYFRAMES;
}
}
/* Mirror is not supported with proportional editing, turn it off. */
#if 0
if (t->flag & T_PROP_EDIT) {
t->flag &= ~T_MIRROR;
}
#endif
/* Disable cursor wrap when edge panning is enabled. */
if (t->options & CTX_VIEW2D_EDGE_PAN) {
t->flag |= T_NO_CURSOR_WRAP;
}
if (op && (t->flag & T_MODAL) &&
ELEM(t->mode,
TFM_TRANSLATION,
TFM_RESIZE,
TFM_ROTATION,
TFM_SHRINKFATTEN,
TFM_EDGE_SLIDE,
TFM_VERT_SLIDE))
2023-05-19 20:02:11 +10:00
{
wmWindowManager *wm = CTX_wm_manager(C);
wmKeyMap *keymap = WM_keymap_active(wm, op->type->modalkeymap);
const wmKeyMapItem *kmi_passthrough = nullptr;
LISTBASE_FOREACH (const wmKeyMapItem *, kmi, &keymap->items) {
if (kmi->flag & KMI_INACTIVE) {
continue;
}
if (kmi->propvalue == TFM_MODAL_PASSTHROUGH_NAVIGATE) {
kmi_passthrough = kmi;
break;
}
}
t->vod = ED_view3d_navigation_init(C, kmi_passthrough);
}
if (t->mode == TFM_TRANSLATION) {
if ((prop = RNA_struct_find_property(op->ptr, "translate_origin")) &&
RNA_property_is_set(op->ptr, prop))
{
if (RNA_property_boolean_get(op->ptr, prop)) {
t->flag |= T_ORIGIN;
}
}
}
setTransformViewMatrices(t);
calculateCenter2D(t);
calculateCenterLocal(t, t->center_global);
initNumInput(&t->num);
transform_gizmo_3d_model_from_constraint_and_mode_init(t);
}
static void freeTransCustomData(TransInfo *t, TransDataContainer *tc, TransCustomData *custom_data)
{
if (custom_data->free_cb) {
/* Can take over freeing t->data and data_2d etc... */
custom_data->free_cb(t, tc, custom_data);
BLI_assert(custom_data->data == nullptr);
}
else if ((custom_data->data != nullptr) && custom_data->use_free) {
MEM_freeN(custom_data->data);
custom_data->data = nullptr;
}
/* In case modes are switched in the same transform session. */
custom_data->free_cb = nullptr;
custom_data->use_free = false;
}
static void freeTransCustomDataContainer(TransInfo *t,
TransDataContainer *tc,
TransCustomDataContainer *tcdc)
{
TransCustomData *custom_data = &tcdc->first_elem;
for (int i = 0; i < TRANS_CUSTOM_DATA_ELEM_MAX; i++, custom_data++) {
freeTransCustomData(t, tc, custom_data);
}
}
void freeTransCustomDataForMode(TransInfo *t)
{
freeTransCustomData(t, nullptr, &t->custom.mode);
FOREACH_TRANS_DATA_CONTAINER (t, tc) {
freeTransCustomData(t, tc, &tc->custom.mode);
}
}
void postTrans(bContext *C, TransInfo *t)
{
2019-04-22 09:19:45 +10:00
if (t->draw_handle_view) {
ED_region_draw_cb_exit(t->region->runtime->type, t->draw_handle_view);
2019-04-22 09:19:45 +10:00
}
if (t->draw_handle_pixel) {
ED_region_draw_cb_exit(t->region->runtime->type, t->draw_handle_pixel);
2019-04-22 09:19:45 +10:00
}
if (t->draw_handle_cursor) {
WM_paint_cursor_end(static_cast<wmPaintCursor *>(t->draw_handle_cursor));
2019-04-22 09:19:45 +10:00
}
if (t->flag & T_MODAL_CURSOR_SET) {
WM_cursor_modal_restore(CTX_wm_window(C));
}
/* Free all custom-data. */
freeTransCustomDataContainer(t, nullptr, &t->custom);
FOREACH_TRANS_DATA_CONTAINER (t, tc) {
freeTransCustomDataContainer(t, tc, &tc->custom);
}
/* #postTrans can be called when nothing is selected, so data is nullptr already. */
if (t->data_len_all != 0) {
FOREACH_TRANS_DATA_CONTAINER (t, tc) {
/* Free data malloced per trans-data. */
if (ELEM(t->obedit_type, OB_CURVES_LEGACY, OB_SURF) || (t->spacetype == SPACE_GRAPH)) {
TransData *td = tc->data;
for (int a = 0; a < tc->data_len; a++, td++) {
if (td->flag & TD_BEZTRIPLE) {
MEM_freeN(td->hdata);
}
}
}
MEM_freeN(tc->data);
MEM_SAFE_FREE(tc->data_mirror);
MEM_SAFE_FREE(tc->data_ext);
MEM_SAFE_FREE(tc->data_2d);
MEM_SAFE_FREE(tc->sorted_index_map);
}
}
MEM_SAFE_FREE(t->data_container);
t->data_container = nullptr;
BLI_freelistN(&t->tsnap.points);
2012-06-10 19:59:02 +00:00
if (t->spacetype == SPACE_IMAGE) {
if (t->options & (CTX_MASK | CTX_PAINT_CURVE)) {
/* Pass. */
}
else {
SpaceImage *sima = static_cast<SpaceImage *>(t->area->spacedata.first);
2019-04-22 09:19:45 +10:00
if (sima->flag & SI_LIVE_UNWRAP) {
ED_uvedit_live_unwrap_end(t->state == TRANS_CANCEL);
2019-04-22 09:19:45 +10:00
}
}
}
if (t->mouse.data) {
MEM_freeN(t->mouse.data);
}
if (t->rng != nullptr) {
BLI_rng_free(t->rng);
}
freeSnapping(t);
if (t->vod) {
ED_view3d_navigation_free(C, t->vod);
}
}
static void transdata_restore_basic(TransDataBasic *td_basic)
{
if (td_basic->loc) {
copy_v3_v3(td_basic->loc, td_basic->iloc);
}
/* TODO(mano-wii): Only use 3D or larger vectors in `td->loc`.
* If `loc` and `val` point to the same address, it may indicate that `loc` is not 3D which is
* not safe for `copy_v3_v3`. */
if (td_basic->val && td_basic->val != td_basic->loc) {
*td_basic->val = td_basic->ival;
}
}
static void restoreElement(TransData *td)
{
transdata_restore_basic((TransDataBasic *)td);
if (td->flag & TD_BEZTRIPLE) {
*(td->hdata->h1) = td->hdata->ih1;
*(td->hdata->h2) = td->hdata->ih2;
}
}
void restoreTransObjects(TransInfo *t)
{
FOREACH_TRANS_DATA_CONTAINER (t, tc) {
TransData *td;
TransData2D *td2d;
TransDataMirror *tdm;
for (td = tc->data; td < tc->data + tc->data_len; td++) {
restoreElement(td);
}
for (tdm = tc->data_mirror; tdm < tc->data_mirror + tc->data_mirror_len; tdm++) {
transdata_restore_basic((TransDataBasic *)tdm);
}
if (tc->data_ext) {
for (int i = 0; i < tc->data_len; i++) {
if (tc->data[i].flag & TD_NO_EXT) {
continue;
}
TransDataExtension *td_ext = &tc->data_ext[i];
if (td_ext->rot) {
copy_v3_v3(td_ext->rot, td_ext->irot);
}
if (td_ext->rotAngle) {
*td_ext->rotAngle = td_ext->irotAngle;
}
if (td_ext->rotAxis) {
copy_v3_v3(td_ext->rotAxis, td_ext->irotAxis);
}
/* XXX, `drotAngle` & `drotAxis` not used yet. */
if (td_ext->scale) {
copy_v3_v3(td_ext->scale, td_ext->iscale);
}
if (td_ext->quat) {
copy_qt_qt(td_ext->quat, td_ext->iquat);
}
}
}
for (td2d = tc->data_2d; tc->data_2d && td2d < tc->data_2d + tc->data_len; td2d++) {
if (td2d->h1) {
td2d->h1[0] = td2d->ih1[0];
td2d->h1[1] = td2d->ih1[1];
}
if (td2d->h2) {
td2d->h2[0] = td2d->ih2[0];
td2d->h2[1] = td2d->ih2[1];
}
}
unit_m3(t->mat);
}
recalc_data(t);
}
void calculateCenter2D(TransInfo *t)
{
BLI_assert(!is_zero_v3(t->aspect));
projectFloatView(t, t->center_global, t->center2d);
}
void calculateCenterLocal(TransInfo *t, const float center_global[3])
{
/* Setting constraint center. */
/* NOTE: init functions may over-ride `t->center`. */
FOREACH_TRANS_DATA_CONTAINER (t, tc) {
if (tc->use_local_mat) {
mul_v3_m4v3(tc->center_local, tc->imat, center_global);
}
else {
copy_v3_v3(tc->center_local, center_global);
}
}
}
void calculateCenterCursor(TransInfo *t, float r_center[3])
{
const float *cursor = t->scene->cursor.location;
copy_v3_v3(r_center, cursor);
/* If edit or pose mode, move cursor in local space. */
if (t->options & CTX_PAINT_CURVE) {
if (ED_view3d_project_float_global(t->region, cursor, r_center, V3D_PROJ_TEST_NOP) !=
V3D_PROJ_RET_OK)
{
projectFloatViewCenterFallback(t, r_center);
}
r_center[2] = 0.0f;
}
}
void calculateCenterCursor2D(TransInfo *t, float r_center[2])
{
float cursor_local_buf[2];
const float *cursor = nullptr;
2012-06-10 19:59:02 +00:00
if (t->spacetype == SPACE_IMAGE) {
SpaceImage *sima = (SpaceImage *)t->area->spacedata.first;
cursor = sima->cursor;
}
if (t->spacetype == SPACE_SEQ) {
SpaceSeq *sseq = (SpaceSeq *)t->area->spacedata.first;
const float2 cursor_pixel = seq::image_preview_unit_to_px(t->scene, sseq->cursor);
copy_v2_v2(cursor_local_buf, cursor_pixel);
cursor = cursor_local_buf;
}
else if (t->spacetype == SPACE_CLIP) {
SpaceClip *space_clip = (SpaceClip *)t->area->spacedata.first;
cursor = space_clip->cursor;
}
2010-03-30 11:38:06 +00:00
if (cursor) {
if (t->options & CTX_MASK) {
float co[2];
if (t->spacetype == SPACE_IMAGE) {
SpaceImage *sima = (SpaceImage *)t->area->spacedata.first;
BKE_mask_coord_from_image(sima->image, &sima->iuser, co, cursor);
}
else if (t->spacetype == SPACE_CLIP) {
SpaceClip *space_clip = (SpaceClip *)t->area->spacedata.first;
BKE_mask_coord_from_movieclip(space_clip->clip, &space_clip->user, co, cursor);
}
else {
BLI_assert_msg(0, "Shall not happen");
}
r_center[0] = co[0] * t->aspect[0];
r_center[1] = co[1] * t->aspect[1];
}
else if (t->options & CTX_PAINT_CURVE) {
if (t->spacetype == SPACE_IMAGE) {
r_center[0] = UI_view2d_view_to_region_x(&t->region->v2d, cursor[0]);
r_center[1] = UI_view2d_view_to_region_y(&t->region->v2d, cursor[1]);
}
}
else {
r_center[0] = cursor[0] * t->aspect[0];
r_center[1] = cursor[1] * t->aspect[1];
}
}
}
void calculateCenterCursorGraph2D(TransInfo *t, float r_center[2])
{
SpaceGraph *sipo = (SpaceGraph *)t->area->spacedata.first;
2012-06-10 19:59:02 +00:00
Scene *scene = t->scene;
/* Cursor is combination of current frame, and graph-editor cursor value. */
if (sipo->mode == SIPO_MODE_DRIVERS) {
r_center[0] = sipo->cursorTime;
r_center[1] = sipo->cursorVal;
}
else {
r_center[0] = float(scene->r.cfra);
r_center[1] = sipo->cursorVal;
}
}
static bool transdata_center_global_get(const TransDataContainer *tc,
const TransDataBasic *td_basic,
float r_vec[3])
{
if (td_basic->flag & TD_SELECTED) {
if (!(td_basic->flag & TD_NOCENTER)) {
if (tc->use_local_mat) {
mul_v3_m4v3(r_vec, tc->mat, td_basic->center);
}
else {
copy_v3_v3(r_vec, td_basic->center);
}
return true;
}
}
return false;
}
void calculateCenterMedian(TransInfo *t, float r_center[3])
{
float partial[3] = {0.0f, 0.0f, 0.0f};
int total = 0;
FOREACH_TRANS_DATA_CONTAINER (t, tc) {
float center[3];
for (int i = 0; i < tc->data_len; i++) {
if (transdata_center_global_get(tc, (TransDataBasic *)&tc->data[i], center)) {
add_v3_v3(partial, center);
total++;
}
}
for (int i = 0; i < tc->data_mirror_len; i++) {
if (transdata_center_global_get(tc, (TransDataBasic *)&tc->data_mirror[i], center)) {
add_v3_v3(partial, center);
total++;
}
}
}
if (total) {
mul_v3_fl(partial, 1.0f / float(total));
}
copy_v3_v3(r_center, partial);
}
void calculateCenterBound(TransInfo *t, float r_center[3])
{
float max[3], min[3];
bool changed = false;
INIT_MINMAX(min, max);
FOREACH_TRANS_DATA_CONTAINER (t, tc) {
float center[3];
for (int i = 0; i < tc->data_len; i++) {
if (transdata_center_global_get(tc, (TransDataBasic *)&tc->data[i], center)) {
minmax_v3v3_v3(min, max, center);
changed = true;
}
}
for (int i = 0; i < tc->data_mirror_len; i++) {
if (transdata_center_global_get(tc, (TransDataBasic *)&tc->data_mirror[i], center)) {
minmax_v3v3_v3(min, max, center);
changed = true;
}
}
}
if (changed) {
mid_v3_v3v3(r_center, min, max);
}
}
bool calculateCenterActive(TransInfo *t, bool select_only, float r_center[3])
{
TransDataContainer *tc = TRANS_DATA_CONTAINER_FIRST_OK(t);
if (t->spacetype != SPACE_VIEW3D) {
return false;
}
if (tc->obedit) {
if (object::calc_active_center_for_editmode(tc->obedit, select_only, r_center)) {
mul_m4_v3(tc->obedit->object_to_world().ptr(), r_center);
return true;
}
}
else if (t->options & CTX_POSE_BONE) {
ViewLayer: Lazy sync of scene data. When a change happens which invalidates view layers the syncing will be postponed until the first usage. This will improve importing or adding many objects in a single operation/script. `BKE_view_layer_need_resync_tag` is used to tag the view layer to be out of sync. Before accessing `BKE_view_layer_active_base_get`, `BKE_view_layer_active_object_get`, `BKE_view_layer_active_collection` or `BKE_view_layer_object_bases` the caller should call `BKE_view_layer_synced_ensure`. Having two functions ensures that partial syncing could be added as smaller patches in the future. Tagging a view layer out of sync could be replaced with a partial sync. Eventually the number of full resyncs could be reduced. After all tagging has been replaced with partial syncs the ensure_sync could be phased out. This patch has been added to discuss the details and consequences of the current approach. For clarity the call to BKE_view_layer_ensure_sync is placed close to the getters. In the future this could be placed in more strategical places to reduce the number of calls or improve performance. Finding those strategical places isn't that clear. When multiple operations are grouped in a single script you might want to always check for resync. Some areas found that can be improved. This list isn't complete. These areas aren't addressed by this patch as these changes would be hard to detect to the reviewer. The idea is to add changes to these areas as a separate patch. It might be that the initial commit would reduce performance compared to master, but will be fixed by the additional patches. **Object duplication** During object duplication the syncing is temporarily disabled. With this patch this isn't useful as when disabled the view_layer is accessed to locate bases. This can be improved by first locating the source bases, then duplicate and sync and locate the new bases. Will be solved in a separate patch for clarity reasons ({D15886}). **Object add** `BKE_object_add` not only adds a new object, but also selects and activates the new base. This requires the view_layer to be resynced. Some callers reverse the selection and activation (See `get_new_constraint_target`). We should make the selection and activation optional. This would make it possible to add multiple objects without having to resync per object. **Postpone Activate Base** Setting the basact is done in many locations. They follow a rule as after an action find the base and set the basact. Finding the base could require a resync. The idea is to store in the view_layer the object which base will be set in the basact during the next sync, reducing the times resyncing needs to happen. Reviewed By: mont29 Maniphest Tasks: T73411 Differential Revision: https://developer.blender.org/D15885
2022-09-14 21:33:51 +02:00
BKE_view_layer_synced_ensure(t->scene, t->view_layer);
Object *ob = BKE_view_layer_active_object_get(t->view_layer);
if (object::calc_active_center_for_posemode(ob, select_only, r_center)) {
mul_m4_v3(ob->object_to_world().ptr(), r_center);
return true;
}
}
else if (t->options & CTX_PAINT_CURVE) {
Paint *paint = BKE_paint_get_active(t->scene, t->view_layer);
Brush *br = BKE_paint_brush(paint);
PaintCurve *pc = br->paint_curve;
copy_v3_v3(r_center, pc->points[pc->add_index - 1].bez.vec[1]);
BKE_brush_tag_unsaved_changes(br);
r_center[2] = 0.0f;
return true;
}
else {
/* Object mode. */
ViewLayer: Lazy sync of scene data. When a change happens which invalidates view layers the syncing will be postponed until the first usage. This will improve importing or adding many objects in a single operation/script. `BKE_view_layer_need_resync_tag` is used to tag the view layer to be out of sync. Before accessing `BKE_view_layer_active_base_get`, `BKE_view_layer_active_object_get`, `BKE_view_layer_active_collection` or `BKE_view_layer_object_bases` the caller should call `BKE_view_layer_synced_ensure`. Having two functions ensures that partial syncing could be added as smaller patches in the future. Tagging a view layer out of sync could be replaced with a partial sync. Eventually the number of full resyncs could be reduced. After all tagging has been replaced with partial syncs the ensure_sync could be phased out. This patch has been added to discuss the details and consequences of the current approach. For clarity the call to BKE_view_layer_ensure_sync is placed close to the getters. In the future this could be placed in more strategical places to reduce the number of calls or improve performance. Finding those strategical places isn't that clear. When multiple operations are grouped in a single script you might want to always check for resync. Some areas found that can be improved. This list isn't complete. These areas aren't addressed by this patch as these changes would be hard to detect to the reviewer. The idea is to add changes to these areas as a separate patch. It might be that the initial commit would reduce performance compared to master, but will be fixed by the additional patches. **Object duplication** During object duplication the syncing is temporarily disabled. With this patch this isn't useful as when disabled the view_layer is accessed to locate bases. This can be improved by first locating the source bases, then duplicate and sync and locate the new bases. Will be solved in a separate patch for clarity reasons ({D15886}). **Object add** `BKE_object_add` not only adds a new object, but also selects and activates the new base. This requires the view_layer to be resynced. Some callers reverse the selection and activation (See `get_new_constraint_target`). We should make the selection and activation optional. This would make it possible to add multiple objects without having to resync per object. **Postpone Activate Base** Setting the basact is done in many locations. They follow a rule as after an action find the base and set the basact. Finding the base could require a resync. The idea is to store in the view_layer the object which base will be set in the basact during the next sync, reducing the times resyncing needs to happen. Reviewed By: mont29 Maniphest Tasks: T73411 Differential Revision: https://developer.blender.org/D15885
2022-09-14 21:33:51 +02:00
BKE_view_layer_synced_ensure(t->scene, t->view_layer);
Base *base = BKE_view_layer_active_base_get(t->view_layer);
if (base && ((!select_only) || ((base->flag & BASE_SELECTED) != 0))) {
copy_v3_v3(r_center, base->object->object_to_world().location());
return true;
}
}
return false;
}
static void calculateCenter_FromAround(TransInfo *t, int around, float r_center[3])
{
switch (around) {
case V3D_AROUND_CENTER_BOUNDS:
calculateCenterBound(t, r_center);
2012-06-10 19:59:02 +00:00
break;
2018-12-14 11:01:01 +11:00
case V3D_AROUND_CENTER_MEDIAN:
calculateCenterMedian(t, r_center);
2012-06-10 19:59:02 +00:00
break;
case V3D_AROUND_CURSOR:
if (ELEM(t->spacetype, SPACE_IMAGE, SPACE_SEQ, SPACE_CLIP)) {
calculateCenterCursor2D(t, r_center);
2019-04-22 09:19:45 +10:00
}
else if (t->spacetype == SPACE_GRAPH) {
calculateCenterCursorGraph2D(t, r_center);
2019-04-22 09:19:45 +10:00
}
else {
calculateCenterCursor(t, r_center);
2019-04-22 09:19:45 +10:00
}
2012-06-10 19:59:02 +00:00
break;
case V3D_AROUND_LOCAL_ORIGINS:
/* Individual element center uses median center for helpline and such. */
calculateCenterMedian(t, r_center);
2012-06-10 19:59:02 +00:00
break;
case V3D_AROUND_ACTIVE: {
if (calculateCenterActive(t, false, r_center)) {
/* Pass. */
}
else {
/* Fallback. */
calculateCenterMedian(t, r_center);
}
break;
}
}
}
static void calculateZfac(TransInfo *t)
{
/* #ED_view3d_calc_zfac() defines a factor for perspective depth correction,
* used in #ED_view3d_win_to_delta(). */
/* `zfac` is only used #convertViewVec only in cases operator was invoked in #RGN_TYPE_WINDOW
* and never used in other cases.
*
* We need special case here as well, since #ED_view3d_calc_zfac will crash when called
* for a region different from #RGN_TYPE_WINDOW.
*/
if ((t->spacetype == SPACE_VIEW3D) && (t->region->regiontype == RGN_TYPE_WINDOW)) {
t->zfac = ED_view3d_calc_zfac(static_cast<const RegionView3D *>(t->region->regiondata),
t->center_global);
}
else if (t->spacetype == SPACE_IMAGE) {
SpaceImage *sima = static_cast<SpaceImage *>(t->area->spacedata.first);
t->zfac = 1.0f / sima->zoom;
}
else if (t->region) {
View2D *v2d = &t->region->v2d;
/* Get zoom factor the same way as in
* #ui_view2d_curRect_validate_resize - better keep in sync! */
const float zoomx = float(BLI_rcti_size_x(&v2d->mask) + 1) / BLI_rctf_size_x(&v2d->cur);
t->zfac = 1.0f / zoomx;
}
}
void calculateCenter(TransInfo *t)
{
if ((t->flag & T_OVERRIDE_CENTER) == 0) {
calculateCenter_FromAround(t, t->around, t->center_global);
}
calculateCenterLocal(t, t->center_global);
calculateCenter2D(t);
2021-02-05 16:23:34 +11:00
/* For panning from the camera-view. */
if ((t->options & CTX_OBJECT) && (t->flag & T_OVERRIDE_CENTER) == 0) {
if (t->spacetype == SPACE_VIEW3D && t->region && t->region->regiontype == RGN_TYPE_WINDOW) {
if (t->options & CTX_CAMERA) {
float axis[3];
/* `persinv` is nasty, use `viewinv` instead, always right. */
copy_v3_v3(axis, t->viewinv[2]);
normalize_v3(axis);
/* 6.0 = 6 grid units. */
axis[0] = t->center_global[0] - 6.0f * axis[0];
axis[1] = t->center_global[1] - 6.0f * axis[1];
axis[2] = t->center_global[2] - 6.0f * axis[2];
projectFloatView(t, axis, t->center2d);
/* Rotate only needs correct 2d center, grab needs #ED_view3d_calc_zfac() value. */
2012-06-10 19:59:02 +00:00
if (t->mode == TFM_TRANSLATION) {
copy_v3_v3(t->center_global, axis);
}
}
}
}
calculateZfac(t);
}
void transformViewUpdate(TransInfo *t)
{
float zoom_prev = t->zfac;
float zoom_new;
if ((t->spacetype == SPACE_VIEW3D) && (t->region->regiontype == RGN_TYPE_WINDOW)) {
if (!t->persp) {
zoom_prev *= len_v3(t->persinv[0]);
}
setTransformViewMatrices(t);
calculateZfac(t);
2021-11-18 14:19:59 -03:00
zoom_new = t->zfac;
if (!t->persp) {
zoom_new *= len_v3(t->persinv[0]);
2021-11-18 14:19:59 -03:00
}
for (int i = 0; i < ARRAY_SIZE(t->orient); i++) {
if (t->orient[i].type == V3D_ORIENT_VIEW) {
copy_m3_m4(t->orient[i].matrix, t->viewinv);
normalize_m3(t->orient[i].matrix);
if (t->orient_curr == i) {
copy_m3_m3(t->spacemtx, t->orient[i].matrix);
invert_m3_m3_safe_ortho(t->spacemtx_inv, t->spacemtx);
}
}
}
}
else {
calculateZfac(t);
zoom_new = t->zfac;
}
calculateCenter2D(t);
transform_snap_grid_init(t, t->snap_spatial, &t->snap_spatial_precision);
transform_input_update(t, zoom_prev / zoom_new);
}
void calculatePropRatio(TransInfo *t)
{
int i;
float dist;
const bool connected = (t->flag & T_PROP_CONNECTED) != 0;
t->proptext[0] = '\0';
if (t->flag & T_PROP_EDIT) {
const char *pet_id = nullptr;
FOREACH_TRANS_DATA_CONTAINER (t, tc) {
TransData *td = tc->data;
for (i = 0; i < tc->data_len; i++, td++) {
if (td->flag & TD_SELECTED) {
td->factor = 1.0f;
}
else if ((connected ? td->dist : td->rdist) > t->prop_size) {
td->factor = 0.0f;
restoreElement(td);
}
else {
/* Use `rdist` for falloff calculations, it is the real distance. */
2019-04-22 09:19:45 +10:00
if (connected) {
dist = (t->prop_size - td->dist) / t->prop_size;
2019-04-22 09:19:45 +10:00
}
else {
dist = (t->prop_size - td->rdist) / t->prop_size;
2019-04-22 09:19:45 +10:00
}
/*
* Clamp to positive numbers.
* Certain corner cases with connectivity and individual centers
* can give values of rdist larger than propsize.
*/
dist = std::max(dist, 0.0f);
switch (t->prop_mode) {
case PROP_SHARP:
td->factor = dist * dist;
break;
case PROP_SMOOTH:
td->factor = 3.0f * dist * dist - 2.0f * dist * dist * dist;
break;
case PROP_ROOT:
td->factor = sqrtf(dist);
break;
case PROP_LIN:
td->factor = dist;
break;
case PROP_CONST:
td->factor = 1.0f;
break;
case PROP_SPHERE:
td->factor = sqrtf(2 * dist - dist * dist);
break;
case PROP_RANDOM:
if (t->rng == nullptr) {
/* Lazy initialization. */
uint rng_seed = uint(BLI_time_now_seconds_i() & UINT_MAX);
t->rng = BLI_rng_new(rng_seed);
}
td->factor = BLI_rng_get_float(t->rng) * dist;
break;
case PROP_INVSQUARE:
td->factor = dist * (2.0f - dist);
break;
default:
td->factor = 1;
break;
}
}
}
}
switch (t->prop_mode) {
2012-06-10 19:59:02 +00:00
case PROP_SHARP:
pet_id = N_("(Sharp)");
2012-06-10 19:59:02 +00:00
break;
case PROP_SMOOTH:
pet_id = N_("(Smooth)");
2012-06-10 19:59:02 +00:00
break;
case PROP_ROOT:
pet_id = N_("(Root)");
2012-06-10 19:59:02 +00:00
break;
case PROP_LIN:
pet_id = N_("(Linear)");
2012-06-10 19:59:02 +00:00
break;
case PROP_CONST:
pet_id = N_("(Constant)");
2012-06-10 19:59:02 +00:00
break;
case PROP_SPHERE:
pet_id = N_("(Sphere)");
2012-06-10 19:59:02 +00:00
break;
case PROP_RANDOM:
pet_id = N_("(Random)");
2012-06-10 19:59:02 +00:00
break;
case PROP_INVSQUARE:
pet_id = N_("(InvSquare)");
break;
2012-06-10 19:59:02 +00:00
default:
break;
}
if (pet_id) {
STRNCPY_UTF8(t->proptext, IFACE_(pet_id));
}
}
else {
FOREACH_TRANS_DATA_CONTAINER (t, tc) {
TransData *td = tc->data;
for (i = 0; i < tc->data_len; i++, td++) {
td->factor = 1.0;
}
}
}
}
void transform_data_ext_rotate(TransData *td,
TransDataExtension *td_ext,
float mat[3][3],
bool use_drot)
{
float totmat[3][3];
float smat[3][3];
float fmat[3][3];
float obmat[3][3];
float dmat[3][3]; /* Delta rotation. */
float dmat_inv[3][3];
mul_m3_m3m3(totmat, mat, td->mtx);
mul_m3_m3m3(smat, td->smtx, mat);
/* Logic from #BKE_object_rot_to_mat3. */
if (use_drot) {
if (td_ext->rotOrder > 0) {
eulO_to_mat3(dmat, td_ext->drot, td_ext->rotOrder);
}
else if (td_ext->rotOrder == ROT_MODE_AXISANGLE) {
#if 0
axis_angle_to_mat3(dmat, td_ext->drotAxis, td_ext->drotAngle);
#else
unit_m3(dmat);
#endif
}
else {
float tquat[4];
normalize_qt_qt(tquat, td_ext->dquat);
quat_to_mat3(dmat, tquat);
}
invert_m3_m3(dmat_inv, dmat);
}
if (td_ext->rotOrder == ROT_MODE_QUAT) {
float quat[4];
2021-02-05 16:23:34 +11:00
/* Calculate the total rotation. */
quat_to_mat3(obmat, td_ext->iquat);
if (use_drot) {
mul_m3_m3m3(obmat, dmat, obmat);
}
/* `mat = transform`, `obmat = object rotation`. */
mul_m3_m3m3(fmat, smat, obmat);
if (use_drot) {
mul_m3_m3m3(fmat, dmat_inv, fmat);
}
mat3_to_quat(quat, fmat);
/* Apply. */
copy_qt_qt(td_ext->quat, quat);
}
else if (td_ext->rotOrder == ROT_MODE_AXISANGLE) {
float axis[3], angle;
2021-02-05 16:23:34 +11:00
/* Calculate the total rotation. */
axis_angle_to_mat3(obmat, td_ext->irotAxis, td_ext->irotAngle);
if (use_drot) {
mul_m3_m3m3(obmat, dmat, obmat);
}
/* `mat = transform`, `obmat = object rotation`. */
mul_m3_m3m3(fmat, smat, obmat);
if (use_drot) {
mul_m3_m3m3(fmat, dmat_inv, fmat);
}
mat3_to_axis_angle(axis, &angle, fmat);
/* Apply. */
copy_v3_v3(td_ext->rotAxis, axis);
*td_ext->rotAngle = angle;
}
else {
float eul[3];
2021-02-05 16:23:34 +11:00
/* Calculate the total rotation. */
eulO_to_mat3(obmat, td_ext->irot, td_ext->rotOrder);
if (use_drot) {
mul_m3_m3m3(obmat, dmat, obmat);
}
/* `mat = transform`, `obmat = object rotation`. */
mul_m3_m3m3(fmat, smat, obmat);
if (use_drot) {
mul_m3_m3m3(fmat, dmat_inv, fmat);
}
mat3_to_compatible_eulO(eul, td_ext->rot, td_ext->rotOrder, fmat);
/* Apply. */
copy_v3_v3(td_ext->rot, eul);
}
}
Object *transform_object_deform_pose_armature_get(const TransInfo *t, Object *ob)
{
if (!(ob->mode & OB_MODE_ALL_WEIGHT_PAINT)) {
return nullptr;
}
/* Important that ob_armature can be set even when its not selected #23412.
* Lines below just check is also visible. */
Object *ob_armature = BKE_modifiers_is_deformed_by_armature(ob);
if (ob_armature && ob_armature->mode & OB_MODE_POSE) {
ViewLayer: Lazy sync of scene data. When a change happens which invalidates view layers the syncing will be postponed until the first usage. This will improve importing or adding many objects in a single operation/script. `BKE_view_layer_need_resync_tag` is used to tag the view layer to be out of sync. Before accessing `BKE_view_layer_active_base_get`, `BKE_view_layer_active_object_get`, `BKE_view_layer_active_collection` or `BKE_view_layer_object_bases` the caller should call `BKE_view_layer_synced_ensure`. Having two functions ensures that partial syncing could be added as smaller patches in the future. Tagging a view layer out of sync could be replaced with a partial sync. Eventually the number of full resyncs could be reduced. After all tagging has been replaced with partial syncs the ensure_sync could be phased out. This patch has been added to discuss the details and consequences of the current approach. For clarity the call to BKE_view_layer_ensure_sync is placed close to the getters. In the future this could be placed in more strategical places to reduce the number of calls or improve performance. Finding those strategical places isn't that clear. When multiple operations are grouped in a single script you might want to always check for resync. Some areas found that can be improved. This list isn't complete. These areas aren't addressed by this patch as these changes would be hard to detect to the reviewer. The idea is to add changes to these areas as a separate patch. It might be that the initial commit would reduce performance compared to master, but will be fixed by the additional patches. **Object duplication** During object duplication the syncing is temporarily disabled. With this patch this isn't useful as when disabled the view_layer is accessed to locate bases. This can be improved by first locating the source bases, then duplicate and sync and locate the new bases. Will be solved in a separate patch for clarity reasons ({D15886}). **Object add** `BKE_object_add` not only adds a new object, but also selects and activates the new base. This requires the view_layer to be resynced. Some callers reverse the selection and activation (See `get_new_constraint_target`). We should make the selection and activation optional. This would make it possible to add multiple objects without having to resync per object. **Postpone Activate Base** Setting the basact is done in many locations. They follow a rule as after an action find the base and set the basact. Finding the base could require a resync. The idea is to store in the view_layer the object which base will be set in the basact during the next sync, reducing the times resyncing needs to happen. Reviewed By: mont29 Maniphest Tasks: T73411 Differential Revision: https://developer.blender.org/D15885
2022-09-14 21:33:51 +02:00
BKE_view_layer_synced_ensure(t->scene, t->view_layer);
Base *base_arm = BKE_view_layer_base_find(t->view_layer, ob_armature);
if (base_arm) {
View3D *v3d = static_cast<View3D *>(t->view);
if (BASE_VISIBLE(v3d, base_arm)) {
return ob_armature;
}
}
}
return nullptr;
}
} // namespace blender::ed::transform