Files
test/source/blender/editors/interface/interface_ops.cc

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

2925 lines
87 KiB
C++
Raw Normal View History

/* SPDX-FileCopyrightText: 2009 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup edinterface
2011-02-27 20:29:51 +00:00
*/
#include <cstring>
#include <fmt/format.h>
#include "MEM_guardedalloc.h"
#include "DNA_armature_types.h"
#include "DNA_material_types.h"
#include "DNA_modifier_types.h" /* for handling geometry nodes properties */
#include "DNA_object_types.h" /* for OB_DATA_SUPPORT_ID */
#include "DNA_screen_types.h"
#include "BLI_blenlib.h"
#include "BLI_math_color.h"
2024-01-31 14:04:56 -05:00
#include "BLF_api.hh"
#include "BLT_lang.hh"
#include "BLT_translation.hh"
#include "BKE_anim_data.hh"
#include "BKE_context.hh"
#include "BKE_fcurve.hh"
#include "BKE_idtype.hh"
2024-01-23 15:18:09 -05:00
#include "BKE_layer.hh"
2024-01-15 12:44:04 -05:00
#include "BKE_lib_id.hh"
#include "BKE_lib_override.hh"
#include "BKE_lib_remap.hh"
#include "BKE_material.h"
#include "BKE_node.hh"
#include "BKE_report.hh"
#include "BKE_screen.hh"
2024-01-18 22:50:23 +02:00
#include "IMB_colormanagement.hh"
#include "DEG_depsgraph.hh"
#include "DEG_depsgraph_build.hh"
#include "RNA_access.hh"
#include "RNA_define.hh"
#include "RNA_path.hh"
#include "RNA_prototypes.h"
#include "UI_abstract_view.hh"
#include "UI_interface.hh"
#include "interface_intern.hh"
#include "WM_api.hh"
#include "WM_types.hh"
#include "ED_object.hh"
#include "ED_paint.hh"
#include "ED_undo.hh"
/* for Copy As Driver */
#include "ED_keyframing.hh"
/* only for UI_OT_editsource */
#include "BLI_ghash.h"
#include "ED_screen.hh"
using namespace blender::ui;
/* -------------------------------------------------------------------- */
/** \name Immediate redraw helper
*
* Generally handlers shouldn't do any redrawing, that includes the layout/button definitions. That
* violates the Model-View-Controller pattern.
*
* But there are some operators which really need to re-run the layout definitions for various
* reasons. For example, "Edit Source" does it to find out which exact Python code added a button.
* Other operators may need to access buttons that aren't currently visible. In Blender's UI code
* design that typically means just not adding the button in the first place, for a particular
* redraw. So the operator needs to change context and re-create the layout, so the button becomes
* available to act on.
*
* \{ */
static void ui_region_redraw_immediately(bContext *C, ARegion *region)
{
ED_region_do_layout(C, region);
WM_draw_region_viewport_bind(region);
ED_region_do_draw(C, region);
WM_draw_region_viewport_unbind(region);
region->do_draw = 0;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Copy Data Path Operator
* \{ */
2018-07-02 11:47:00 +02:00
static bool copy_data_path_button_poll(bContext *C)
{
PointerRNA ptr;
PropertyRNA *prop;
int index;
UI_context_active_but_prop_get(C, &ptr, &prop, &index);
if (ptr.owner_id && ptr.data && prop) {
if (const std::optional<std::string> path = RNA_path_from_ID_to_property(&ptr, prop)) {
2024-04-04 10:55:18 +11:00
UNUSED_VARS(path);
return true;
}
}
return false;
}
static int copy_data_path_button_exec(bContext *C, wmOperator *op)
{
Main *bmain = CTX_data_main(C);
PointerRNA ptr;
PropertyRNA *prop;
int index;
ID *id;
const bool full_path = RNA_boolean_get(op->ptr, "full_path");
/* try to create driver using property retrieved from UI */
UI_context_active_but_prop_get(C, &ptr, &prop, &index);
std::optional<std::string> path;
if (ptr.owner_id != nullptr) {
if (full_path) {
if (prop) {
path = RNA_path_full_property_py_ex(&ptr, prop, index, true);
}
else {
path = RNA_path_full_struct_py(&ptr);
}
}
else {
const int index_dim = (index != -1 && RNA_property_array_check(prop)) ? 1 : 0;
path = RNA_path_from_real_ID_to_property_index(bmain, &ptr, prop, index_dim, index, &id);
if (!path) {
path = RNA_path_from_ID_to_property_index(&ptr, prop, index_dim, index);
}
}
if (path) {
WM_clipboard_text_set(path->c_str(), false);
return OPERATOR_FINISHED;
}
}
return OPERATOR_CANCELLED;
}
static void UI_OT_copy_data_path_button(wmOperatorType *ot)
{
PropertyRNA *prop;
/* identifiers */
ot->name = "Copy Data Path";
ot->idname = "UI_OT_copy_data_path_button";
ot->description = "Copy the RNA data path for this property to the clipboard";
/* callbacks */
ot->exec = copy_data_path_button_exec;
ot->poll = copy_data_path_button_poll;
/* flags */
ot->flag = OPTYPE_REGISTER;
/* properties */
prop = RNA_def_boolean(ot->srna, "full_path", false, "full_path", "Copy full data path");
RNA_def_property_flag(prop, PROP_SKIP_SAVE);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Copy As Driver Operator
* \{ */
static bool copy_as_driver_button_poll(bContext *C)
{
PointerRNA ptr;
PropertyRNA *prop;
int index;
UI_context_active_but_prop_get(C, &ptr, &prop, &index);
if (ptr.owner_id && ptr.data && prop &&
ELEM(RNA_property_type(prop), PROP_BOOLEAN, PROP_INT, PROP_FLOAT, PROP_ENUM) &&
(index >= 0 || !RNA_property_array_check(prop)))
{
if (const std::optional<std::string> path = RNA_path_from_ID_to_property(&ptr, prop)) {
2024-04-04 10:55:18 +11:00
UNUSED_VARS(path);
return true;
}
}
return false;
}
static int copy_as_driver_button_exec(bContext *C, wmOperator *op)
{
Main *bmain = CTX_data_main(C);
PointerRNA ptr;
PropertyRNA *prop;
int index;
/* try to create driver using property retrieved from UI */
UI_context_active_but_prop_get(C, &ptr, &prop, &index);
if (ptr.owner_id && ptr.data && prop) {
ID *id;
const int dim = RNA_property_array_dimension(&ptr, prop, nullptr);
if (const std::optional<std::string> path = RNA_path_from_real_ID_to_property_index(
bmain, &ptr, prop, dim, index, &id))
{
ANIM_copy_as_driver(id, path->c_str(), RNA_property_identifier(prop));
return OPERATOR_FINISHED;
}
BKE_reportf(op->reports, RPT_ERROR, "Could not compute a valid data path");
return OPERATOR_CANCELLED;
}
return OPERATOR_CANCELLED;
}
static void UI_OT_copy_as_driver_button(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Copy as New Driver";
ot->idname = "UI_OT_copy_as_driver_button";
ot->description =
"Create a new driver with this property as input, and copy it to the "
"internal clipboard. Use Paste Driver to add it to the target property, "
"or Paste Driver Variables to extend an existing driver";
/* callbacks */
ot->exec = copy_as_driver_button_exec;
ot->poll = copy_as_driver_button_poll;
/* flags */
ot->flag = OPTYPE_REGISTER;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Copy Python Command Operator
* \{ */
static bool copy_python_command_button_poll(bContext *C)
{
uiBut *but = UI_context_active_but_get(C);
if (but && (but->optype != nullptr)) {
return true;
}
return false;
}
static int copy_python_command_button_exec(bContext *C, wmOperator * /*op*/)
{
uiBut *but = UI_context_active_but_get(C);
if (but && (but->optype != nullptr)) {
/* allocated when needed, the button owns it */
PointerRNA *opptr = UI_but_operator_ptr_ensure(but);
std::string str = WM_operator_pystring_ex(C, nullptr, false, true, but->optype, opptr);
WM_clipboard_text_set(str.c_str(), false);
return OPERATOR_FINISHED;
}
return OPERATOR_CANCELLED;
}
static void UI_OT_copy_python_command_button(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Copy Python Command";
ot->idname = "UI_OT_copy_python_command_button";
ot->description = "Copy the Python command matching this button";
/* callbacks */
ot->exec = copy_python_command_button_exec;
ot->poll = copy_python_command_button_poll;
/* flags */
ot->flag = OPTYPE_REGISTER;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Reset to Default Values Button Operator
* \{ */
Fix #36226, Select Linked works not in touch with Prefs. When setting keymap properties to values equalling the RNA default, they will get "unset" and automatic operator behavior is used. There is no way to explicitly set the default value as a user. 1) To allow distinguishing uninitialized (not set) properties in the keymap items, a few changes to the RNA struct comparison function are needed: Instead of allowing only strict/non-strict comparison of 2 properties A and B in a struct, this now has 3 modes: * STRICT: compare only the actual property values (same as 'strict' before) * UNSET_MATCH_ANY: if either A or B is unset, consider them a match (same as non-strict before) * UNSET_MATCH_NONE: if one property is set and the other not, consider them a mismatch. The new UNSET_MATCH_NONE mode is useful for keymaps, because it allows keeping user-defined property values in the keymap even if they match the default property value (see wm_keymap_diff function in wm_keymap.c) 2) A new operator is added for unsetting ID properties in the RMB context menu and in user preferences next to keymap properties. This only works on ID properties and deletes the ID property storage, so that the default value is used. In the user preferences for keymaps the properties are shown in an inactive layout to indicate that the default value is used (which some operators such as the "select linked" op from the report use to trigger automatic behavior). When the user sets a property it gets set and stays that way until explicitly "unset" using the new operator.
2013-09-20 09:10:17 +00:00
static int operator_button_property_finish(bContext *C, PointerRNA *ptr, PropertyRNA *prop)
{
ID *id = ptr->owner_id;
Fix #36226, Select Linked works not in touch with Prefs. When setting keymap properties to values equalling the RNA default, they will get "unset" and automatic operator behavior is used. There is no way to explicitly set the default value as a user. 1) To allow distinguishing uninitialized (not set) properties in the keymap items, a few changes to the RNA struct comparison function are needed: Instead of allowing only strict/non-strict comparison of 2 properties A and B in a struct, this now has 3 modes: * STRICT: compare only the actual property values (same as 'strict' before) * UNSET_MATCH_ANY: if either A or B is unset, consider them a match (same as non-strict before) * UNSET_MATCH_NONE: if one property is set and the other not, consider them a mismatch. The new UNSET_MATCH_NONE mode is useful for keymaps, because it allows keeping user-defined property values in the keymap even if they match the default property value (see wm_keymap_diff function in wm_keymap.c) 2) A new operator is added for unsetting ID properties in the RMB context menu and in user preferences next to keymap properties. This only works on ID properties and deletes the ID property storage, so that the default value is used. In the user preferences for keymaps the properties are shown in an inactive layout to indicate that the default value is used (which some operators such as the "select linked" op from the report use to trigger automatic behavior). When the user sets a property it gets set and stays that way until explicitly "unset" using the new operator.
2013-09-20 09:10:17 +00:00
/* perform updates required for this property */
RNA_property_update(C, ptr, prop);
Fix #36226, Select Linked works not in touch with Prefs. When setting keymap properties to values equalling the RNA default, they will get "unset" and automatic operator behavior is used. There is no way to explicitly set the default value as a user. 1) To allow distinguishing uninitialized (not set) properties in the keymap items, a few changes to the RNA struct comparison function are needed: Instead of allowing only strict/non-strict comparison of 2 properties A and B in a struct, this now has 3 modes: * STRICT: compare only the actual property values (same as 'strict' before) * UNSET_MATCH_ANY: if either A or B is unset, consider them a match (same as non-strict before) * UNSET_MATCH_NONE: if one property is set and the other not, consider them a mismatch. The new UNSET_MATCH_NONE mode is useful for keymaps, because it allows keeping user-defined property values in the keymap even if they match the default property value (see wm_keymap_diff function in wm_keymap.c) 2) A new operator is added for unsetting ID properties in the RMB context menu and in user preferences next to keymap properties. This only works on ID properties and deletes the ID property storage, so that the default value is used. In the user preferences for keymaps the properties are shown in an inactive layout to indicate that the default value is used (which some operators such as the "select linked" op from the report use to trigger automatic behavior). When the user sets a property it gets set and stays that way until explicitly "unset" using the new operator.
2013-09-20 09:10:17 +00:00
/* as if we pressed the button */
UI_context_active_but_prop_handle(C, false);
Fix #36226, Select Linked works not in touch with Prefs. When setting keymap properties to values equalling the RNA default, they will get "unset" and automatic operator behavior is used. There is no way to explicitly set the default value as a user. 1) To allow distinguishing uninitialized (not set) properties in the keymap items, a few changes to the RNA struct comparison function are needed: Instead of allowing only strict/non-strict comparison of 2 properties A and B in a struct, this now has 3 modes: * STRICT: compare only the actual property values (same as 'strict' before) * UNSET_MATCH_ANY: if either A or B is unset, consider them a match (same as non-strict before) * UNSET_MATCH_NONE: if one property is set and the other not, consider them a mismatch. The new UNSET_MATCH_NONE mode is useful for keymaps, because it allows keeping user-defined property values in the keymap even if they match the default property value (see wm_keymap_diff function in wm_keymap.c) 2) A new operator is added for unsetting ID properties in the RMB context menu and in user preferences next to keymap properties. This only works on ID properties and deletes the ID property storage, so that the default value is used. In the user preferences for keymaps the properties are shown in an inactive layout to indicate that the default value is used (which some operators such as the "select linked" op from the report use to trigger automatic behavior). When the user sets a property it gets set and stays that way until explicitly "unset" using the new operator.
2013-09-20 09:10:17 +00:00
/* Since we don't want to undo _all_ edits to settings, eg window
* edits on the screen or on operator settings.
* it might be better to move undo's inline - campbell */
if (id && ID_CHECK_UNDO(id)) {
/* do nothing, go ahead with undo */
return OPERATOR_FINISHED;
}
return OPERATOR_CANCELLED;
Fix #36226, Select Linked works not in touch with Prefs. When setting keymap properties to values equalling the RNA default, they will get "unset" and automatic operator behavior is used. There is no way to explicitly set the default value as a user. 1) To allow distinguishing uninitialized (not set) properties in the keymap items, a few changes to the RNA struct comparison function are needed: Instead of allowing only strict/non-strict comparison of 2 properties A and B in a struct, this now has 3 modes: * STRICT: compare only the actual property values (same as 'strict' before) * UNSET_MATCH_ANY: if either A or B is unset, consider them a match (same as non-strict before) * UNSET_MATCH_NONE: if one property is set and the other not, consider them a mismatch. The new UNSET_MATCH_NONE mode is useful for keymaps, because it allows keeping user-defined property values in the keymap even if they match the default property value (see wm_keymap_diff function in wm_keymap.c) 2) A new operator is added for unsetting ID properties in the RMB context menu and in user preferences next to keymap properties. This only works on ID properties and deletes the ID property storage, so that the default value is used. In the user preferences for keymaps the properties are shown in an inactive layout to indicate that the default value is used (which some operators such as the "select linked" op from the report use to trigger automatic behavior). When the user sets a property it gets set and stays that way until explicitly "unset" using the new operator.
2013-09-20 09:10:17 +00:00
}
static int operator_button_property_finish_with_undo(bContext *C,
PointerRNA *ptr,
PropertyRNA *prop)
{
/* Perform updates required for this property. */
RNA_property_update(C, ptr, prop);
/* As if we pressed the button. */
UI_context_active_but_prop_handle(C, true);
return OPERATOR_FINISHED;
}
2018-07-02 11:47:00 +02:00
static bool reset_default_button_poll(bContext *C)
{
PointerRNA ptr;
PropertyRNA *prop;
int index;
UI_context_active_but_prop_get(C, &ptr, &prop, &index);
return (ptr.data && prop && RNA_property_editable(&ptr, prop));
}
static int reset_default_button_exec(bContext *C, wmOperator *op)
{
PointerRNA ptr;
PropertyRNA *prop;
2014-02-03 18:55:59 +11:00
int index;
const bool all = RNA_boolean_get(op->ptr, "all");
/* try to reset the nominated setting to its default value */
UI_context_active_but_prop_get(C, &ptr, &prop, &index);
/* if there is a valid property that is editable... */
if (ptr.data && prop && RNA_property_editable(&ptr, prop)) {
2019-03-25 10:15:20 +11:00
if (RNA_property_reset(&ptr, prop, (all) ? -1 : index)) {
return operator_button_property_finish_with_undo(C, &ptr, prop);
2019-03-25 10:15:20 +11:00
}
}
Fix #36226, Select Linked works not in touch with Prefs. When setting keymap properties to values equalling the RNA default, they will get "unset" and automatic operator behavior is used. There is no way to explicitly set the default value as a user. 1) To allow distinguishing uninitialized (not set) properties in the keymap items, a few changes to the RNA struct comparison function are needed: Instead of allowing only strict/non-strict comparison of 2 properties A and B in a struct, this now has 3 modes: * STRICT: compare only the actual property values (same as 'strict' before) * UNSET_MATCH_ANY: if either A or B is unset, consider them a match (same as non-strict before) * UNSET_MATCH_NONE: if one property is set and the other not, consider them a mismatch. The new UNSET_MATCH_NONE mode is useful for keymaps, because it allows keeping user-defined property values in the keymap even if they match the default property value (see wm_keymap_diff function in wm_keymap.c) 2) A new operator is added for unsetting ID properties in the RMB context menu and in user preferences next to keymap properties. This only works on ID properties and deletes the ID property storage, so that the default value is used. In the user preferences for keymaps the properties are shown in an inactive layout to indicate that the default value is used (which some operators such as the "select linked" op from the report use to trigger automatic behavior). When the user sets a property it gets set and stays that way until explicitly "unset" using the new operator.
2013-09-20 09:10:17 +00:00
return OPERATOR_CANCELLED;
}
static void UI_OT_reset_default_button(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Reset to Default Value";
ot->idname = "UI_OT_reset_default_button";
ot->description = "Reset this property's value to its default value";
/* callbacks */
ot->poll = reset_default_button_poll;
ot->exec = reset_default_button_exec;
/* flags */
/* Don't set #OPTYPE_UNDO because #operator_button_property_finish_with_undo
* is responsible for the undo push. */
ot->flag = 0;
/* properties */
RNA_def_boolean(
ot->srna, "all", true, "All", "Reset to default values all elements of the array");
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Assign Value as Default Button Operator
* \{ */
static bool assign_default_button_poll(bContext *C)
{
PointerRNA ptr;
PropertyRNA *prop;
int index;
UI_context_active_but_prop_get(C, &ptr, &prop, &index);
if (ptr.data && prop && RNA_property_editable(&ptr, prop)) {
const PropertyType type = RNA_property_type(prop);
return RNA_property_is_idprop(prop) && !RNA_property_array_check(prop) &&
ELEM(type, PROP_INT, PROP_FLOAT);
}
return false;
}
static int assign_default_button_exec(bContext *C, wmOperator * /*op*/)
{
PointerRNA ptr;
PropertyRNA *prop;
int index;
/* try to reset the nominated setting to its default value */
UI_context_active_but_prop_get(C, &ptr, &prop, &index);
/* if there is a valid property that is editable... */
if (ptr.data && prop && RNA_property_editable(&ptr, prop)) {
2019-03-25 10:15:20 +11:00
if (RNA_property_assign_default(&ptr, prop)) {
return operator_button_property_finish(C, &ptr, prop);
2019-03-25 10:15:20 +11:00
}
}
return OPERATOR_CANCELLED;
}
static void UI_OT_assign_default_button(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Assign Value as Default";
ot->idname = "UI_OT_assign_default_button";
ot->description = "Set this property's current value as the new default";
/* callbacks */
ot->poll = assign_default_button_poll;
ot->exec = assign_default_button_exec;
/* flags */
ot->flag = OPTYPE_UNDO;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Unset Property Button Operator
* \{ */
Fix #36226, Select Linked works not in touch with Prefs. When setting keymap properties to values equalling the RNA default, they will get "unset" and automatic operator behavior is used. There is no way to explicitly set the default value as a user. 1) To allow distinguishing uninitialized (not set) properties in the keymap items, a few changes to the RNA struct comparison function are needed: Instead of allowing only strict/non-strict comparison of 2 properties A and B in a struct, this now has 3 modes: * STRICT: compare only the actual property values (same as 'strict' before) * UNSET_MATCH_ANY: if either A or B is unset, consider them a match (same as non-strict before) * UNSET_MATCH_NONE: if one property is set and the other not, consider them a mismatch. The new UNSET_MATCH_NONE mode is useful for keymaps, because it allows keeping user-defined property values in the keymap even if they match the default property value (see wm_keymap_diff function in wm_keymap.c) 2) A new operator is added for unsetting ID properties in the RMB context menu and in user preferences next to keymap properties. This only works on ID properties and deletes the ID property storage, so that the default value is used. In the user preferences for keymaps the properties are shown in an inactive layout to indicate that the default value is used (which some operators such as the "select linked" op from the report use to trigger automatic behavior). When the user sets a property it gets set and stays that way until explicitly "unset" using the new operator.
2013-09-20 09:10:17 +00:00
static int unset_property_button_exec(bContext *C, wmOperator * /*op*/)
Fix #36226, Select Linked works not in touch with Prefs. When setting keymap properties to values equalling the RNA default, they will get "unset" and automatic operator behavior is used. There is no way to explicitly set the default value as a user. 1) To allow distinguishing uninitialized (not set) properties in the keymap items, a few changes to the RNA struct comparison function are needed: Instead of allowing only strict/non-strict comparison of 2 properties A and B in a struct, this now has 3 modes: * STRICT: compare only the actual property values (same as 'strict' before) * UNSET_MATCH_ANY: if either A or B is unset, consider them a match (same as non-strict before) * UNSET_MATCH_NONE: if one property is set and the other not, consider them a mismatch. The new UNSET_MATCH_NONE mode is useful for keymaps, because it allows keeping user-defined property values in the keymap even if they match the default property value (see wm_keymap_diff function in wm_keymap.c) 2) A new operator is added for unsetting ID properties in the RMB context menu and in user preferences next to keymap properties. This only works on ID properties and deletes the ID property storage, so that the default value is used. In the user preferences for keymaps the properties are shown in an inactive layout to indicate that the default value is used (which some operators such as the "select linked" op from the report use to trigger automatic behavior). When the user sets a property it gets set and stays that way until explicitly "unset" using the new operator.
2013-09-20 09:10:17 +00:00
{
PointerRNA ptr;
PropertyRNA *prop;
int index;
Fix #36226, Select Linked works not in touch with Prefs. When setting keymap properties to values equalling the RNA default, they will get "unset" and automatic operator behavior is used. There is no way to explicitly set the default value as a user. 1) To allow distinguishing uninitialized (not set) properties in the keymap items, a few changes to the RNA struct comparison function are needed: Instead of allowing only strict/non-strict comparison of 2 properties A and B in a struct, this now has 3 modes: * STRICT: compare only the actual property values (same as 'strict' before) * UNSET_MATCH_ANY: if either A or B is unset, consider them a match (same as non-strict before) * UNSET_MATCH_NONE: if one property is set and the other not, consider them a mismatch. The new UNSET_MATCH_NONE mode is useful for keymaps, because it allows keeping user-defined property values in the keymap even if they match the default property value (see wm_keymap_diff function in wm_keymap.c) 2) A new operator is added for unsetting ID properties in the RMB context menu and in user preferences next to keymap properties. This only works on ID properties and deletes the ID property storage, so that the default value is used. In the user preferences for keymaps the properties are shown in an inactive layout to indicate that the default value is used (which some operators such as the "select linked" op from the report use to trigger automatic behavior). When the user sets a property it gets set and stays that way until explicitly "unset" using the new operator.
2013-09-20 09:10:17 +00:00
/* try to unset the nominated property */
UI_context_active_but_prop_get(C, &ptr, &prop, &index);
Fix #36226, Select Linked works not in touch with Prefs. When setting keymap properties to values equalling the RNA default, they will get "unset" and automatic operator behavior is used. There is no way to explicitly set the default value as a user. 1) To allow distinguishing uninitialized (not set) properties in the keymap items, a few changes to the RNA struct comparison function are needed: Instead of allowing only strict/non-strict comparison of 2 properties A and B in a struct, this now has 3 modes: * STRICT: compare only the actual property values (same as 'strict' before) * UNSET_MATCH_ANY: if either A or B is unset, consider them a match (same as non-strict before) * UNSET_MATCH_NONE: if one property is set and the other not, consider them a mismatch. The new UNSET_MATCH_NONE mode is useful for keymaps, because it allows keeping user-defined property values in the keymap even if they match the default property value (see wm_keymap_diff function in wm_keymap.c) 2) A new operator is added for unsetting ID properties in the RMB context menu and in user preferences next to keymap properties. This only works on ID properties and deletes the ID property storage, so that the default value is used. In the user preferences for keymaps the properties are shown in an inactive layout to indicate that the default value is used (which some operators such as the "select linked" op from the report use to trigger automatic behavior). When the user sets a property it gets set and stays that way until explicitly "unset" using the new operator.
2013-09-20 09:10:17 +00:00
/* if there is a valid property that is editable... */
2014-02-28 11:04:15 +11:00
if (ptr.data && prop && RNA_property_editable(&ptr, prop) &&
/* RNA_property_is_idprop(prop) && */
RNA_property_is_set(&ptr, prop))
{
Fix #36226, Select Linked works not in touch with Prefs. When setting keymap properties to values equalling the RNA default, they will get "unset" and automatic operator behavior is used. There is no way to explicitly set the default value as a user. 1) To allow distinguishing uninitialized (not set) properties in the keymap items, a few changes to the RNA struct comparison function are needed: Instead of allowing only strict/non-strict comparison of 2 properties A and B in a struct, this now has 3 modes: * STRICT: compare only the actual property values (same as 'strict' before) * UNSET_MATCH_ANY: if either A or B is unset, consider them a match (same as non-strict before) * UNSET_MATCH_NONE: if one property is set and the other not, consider them a mismatch. The new UNSET_MATCH_NONE mode is useful for keymaps, because it allows keeping user-defined property values in the keymap even if they match the default property value (see wm_keymap_diff function in wm_keymap.c) 2) A new operator is added for unsetting ID properties in the RMB context menu and in user preferences next to keymap properties. This only works on ID properties and deletes the ID property storage, so that the default value is used. In the user preferences for keymaps the properties are shown in an inactive layout to indicate that the default value is used (which some operators such as the "select linked" op from the report use to trigger automatic behavior). When the user sets a property it gets set and stays that way until explicitly "unset" using the new operator.
2013-09-20 09:10:17 +00:00
RNA_property_unset(&ptr, prop);
return operator_button_property_finish(C, &ptr, prop);
}
Fix #36226, Select Linked works not in touch with Prefs. When setting keymap properties to values equalling the RNA default, they will get "unset" and automatic operator behavior is used. There is no way to explicitly set the default value as a user. 1) To allow distinguishing uninitialized (not set) properties in the keymap items, a few changes to the RNA struct comparison function are needed: Instead of allowing only strict/non-strict comparison of 2 properties A and B in a struct, this now has 3 modes: * STRICT: compare only the actual property values (same as 'strict' before) * UNSET_MATCH_ANY: if either A or B is unset, consider them a match (same as non-strict before) * UNSET_MATCH_NONE: if one property is set and the other not, consider them a mismatch. The new UNSET_MATCH_NONE mode is useful for keymaps, because it allows keeping user-defined property values in the keymap even if they match the default property value (see wm_keymap_diff function in wm_keymap.c) 2) A new operator is added for unsetting ID properties in the RMB context menu and in user preferences next to keymap properties. This only works on ID properties and deletes the ID property storage, so that the default value is used. In the user preferences for keymaps the properties are shown in an inactive layout to indicate that the default value is used (which some operators such as the "select linked" op from the report use to trigger automatic behavior). When the user sets a property it gets set and stays that way until explicitly "unset" using the new operator.
2013-09-20 09:10:17 +00:00
return OPERATOR_CANCELLED;
}
static void UI_OT_unset_property_button(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Unset Property";
Fix #36226, Select Linked works not in touch with Prefs. When setting keymap properties to values equalling the RNA default, they will get "unset" and automatic operator behavior is used. There is no way to explicitly set the default value as a user. 1) To allow distinguishing uninitialized (not set) properties in the keymap items, a few changes to the RNA struct comparison function are needed: Instead of allowing only strict/non-strict comparison of 2 properties A and B in a struct, this now has 3 modes: * STRICT: compare only the actual property values (same as 'strict' before) * UNSET_MATCH_ANY: if either A or B is unset, consider them a match (same as non-strict before) * UNSET_MATCH_NONE: if one property is set and the other not, consider them a mismatch. The new UNSET_MATCH_NONE mode is useful for keymaps, because it allows keeping user-defined property values in the keymap even if they match the default property value (see wm_keymap_diff function in wm_keymap.c) 2) A new operator is added for unsetting ID properties in the RMB context menu and in user preferences next to keymap properties. This only works on ID properties and deletes the ID property storage, so that the default value is used. In the user preferences for keymaps the properties are shown in an inactive layout to indicate that the default value is used (which some operators such as the "select linked" op from the report use to trigger automatic behavior). When the user sets a property it gets set and stays that way until explicitly "unset" using the new operator.
2013-09-20 09:10:17 +00:00
ot->idname = "UI_OT_unset_property_button";
ot->description = "Clear the property and use default or generated value in operators";
/* callbacks */
ot->poll = ED_operator_regionactive;
ot->exec = unset_property_button_exec;
/* flags */
ot->flag = OPTYPE_UNDO;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Define Override Type Operator
* \{ */
/* Note that we use different values for UI/UX than 'real' override operations, user does not care
* whether it's added or removed for the differential operation e.g. */
enum {
UIOverride_Type_NOOP = 0,
UIOverride_Type_Replace = 1,
UIOverride_Type_Difference = 2, /* Add/subtract */
UIOverride_Type_Factor = 3, /* Multiply */
/* TODO: should/can we expose insert/remove ones for collections? Doubt it... */
};
static EnumPropertyItem override_type_items[] = {
{UIOverride_Type_NOOP,
"NOOP",
0,
"NoOp",
2018-07-01 20:15:21 +02:00
"'No-Operation', place holder preventing automatic override to ever affect the property"},
{UIOverride_Type_Replace,
"REPLACE",
0,
"Replace",
"Completely replace value from linked data by local one"},
{UIOverride_Type_Difference,
"DIFFERENCE",
0,
"Difference",
"Store difference to linked data value"},
{UIOverride_Type_Factor,
"FACTOR",
0,
"Factor",
"Store factor to linked data value (useful e.g. for scale)"},
{0, nullptr, 0, nullptr, nullptr},
};
2018-07-02 12:03:56 +02:00
static bool override_type_set_button_poll(bContext *C)
{
PointerRNA ptr;
PropertyRNA *prop;
int index;
UI_context_active_but_prop_get(C, &ptr, &prop, &index);
const uint override_status = RNA_property_override_library_status(
CTX_data_main(C), &ptr, prop, index);
return (ptr.data && prop && (override_status & RNA_OVERRIDE_STATUS_OVERRIDABLE));
}
static int override_type_set_button_exec(bContext *C, wmOperator *op)
{
PointerRNA ptr;
PropertyRNA *prop;
int index;
bool created;
const bool all = RNA_boolean_get(op->ptr, "all");
const int op_type = RNA_enum_get(op->ptr, "type");
short operation;
2017-12-04 17:19:34 +11:00
switch (op_type) {
case UIOverride_Type_NOOP:
operation = LIBOVERRIDE_OP_NOOP;
break;
case UIOverride_Type_Replace:
operation = LIBOVERRIDE_OP_REPLACE;
break;
case UIOverride_Type_Difference:
/* override code will automatically switch to subtract if needed. */
operation = LIBOVERRIDE_OP_ADD;
break;
case UIOverride_Type_Factor:
operation = LIBOVERRIDE_OP_MULTIPLY;
break;
default:
operation = LIBOVERRIDE_OP_REPLACE;
BLI_assert(0);
break;
}
/* try to reset the nominated setting to its default value */
UI_context_active_but_prop_get(C, &ptr, &prop, &index);
BLI_assert(ptr.owner_id != nullptr);
if (all) {
index = -1;
}
IDOverrideLibraryPropertyOperation *opop = RNA_property_override_property_operation_get(
CTX_data_main(C), &ptr, prop, operation, index, true, nullptr, &created);
if (opop == nullptr) {
/* Sometimes e.g. RNA cannot generate a path to the given property. */
BKE_reportf(op->reports, RPT_WARNING, "Failed to create the override operation");
return OPERATOR_CANCELLED;
}
if (!created) {
opop->operation = operation;
}
/* Outliner e.g. has to be aware of this change. */
WM_main_add_notifier(NC_WM | ND_LIB_OVERRIDE_CHANGED, nullptr);
return operator_button_property_finish(C, &ptr, prop);
}
static int override_type_set_button_invoke(bContext *C, wmOperator *op, const wmEvent * /*event*/)
{
#if 0 /* Disabled for now */
return WM_menu_invoke_ex(C, op, WM_OP_INVOKE_DEFAULT);
#else
RNA_enum_set(op->ptr, "type", LIBOVERRIDE_OP_REPLACE);
return override_type_set_button_exec(C, op);
#endif
}
static void UI_OT_override_type_set_button(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Define Override Type";
ot->idname = "UI_OT_override_type_set_button";
ot->description = "Create an override operation, or set the type of an existing one";
/* callbacks */
ot->poll = override_type_set_button_poll;
ot->exec = override_type_set_button_exec;
ot->invoke = override_type_set_button_invoke;
/* flags */
ot->flag = OPTYPE_UNDO;
/* properties */
RNA_def_boolean(
ot->srna, "all", true, "All", "Reset to default values all elements of the array");
2018-07-01 20:15:21 +02:00
ot->prop = RNA_def_enum(ot->srna,
"type",
override_type_items,
UIOverride_Type_Replace,
"Type",
"Type of override operation");
/* TODO: add itemf callback, not all options are available for all data types... */
}
2018-07-02 12:03:56 +02:00
static bool override_remove_button_poll(bContext *C)
{
PointerRNA ptr;
PropertyRNA *prop;
int index;
UI_context_active_but_prop_get(C, &ptr, &prop, &index);
const uint override_status = RNA_property_override_library_status(
CTX_data_main(C), &ptr, prop, index);
return (ptr.data && ptr.owner_id && prop && (override_status & RNA_OVERRIDE_STATUS_OVERRIDDEN));
}
static int override_remove_button_exec(bContext *C, wmOperator *op)
{
Main *bmain = CTX_data_main(C);
PointerRNA ptr, src;
PropertyRNA *prop;
int index;
const bool all = RNA_boolean_get(op->ptr, "all");
/* try to reset the nominated setting to its default value */
UI_context_active_but_prop_get(C, &ptr, &prop, &index);
ID *id = ptr.owner_id;
IDOverrideLibraryProperty *oprop = RNA_property_override_property_find(bmain, &ptr, prop, &id);
BLI_assert(oprop != nullptr);
BLI_assert(id != nullptr && id->override_library != nullptr);
/* The source (i.e. linked data) is required to restore values of deleted overrides. */
PropertyRNA *src_prop;
PointerRNA id_refptr = RNA_id_pointer_create(id->override_library->reference);
if (!RNA_path_resolve_property(&id_refptr, oprop->rna_path, &src, &src_prop)) {
BLI_assert_msg(0, "Failed to create matching source (linked data) RNA pointer");
}
if (!all && index != -1) {
bool is_strict_find;
/* Remove override operation for given item,
* add singular operations for the other items as needed. */
IDOverrideLibraryPropertyOperation *opop = BKE_lib_override_library_property_operation_find(
LibOverride: Add ID pointer to operations over ID pointers. In RNA collections storing ID references, the name of the collection item may not always be unique, when several IDs from different libraries are present. While rare, this situation can become deadly to liboverride, by causing random but exponential liboverride hierarchies corruptions. This has already been alleviated by using preferably both name and index in items lookup (a05419f18b) and by reducing the risk of name collision in general between liboverrides and their linked reference (b9becc47de). This commit goes further, by ensuring that references to items of RNA collections of IDs stored in liboverride operations become completely unambiguous. This is achieved by storing an extra pointer to the item's ID itself, when relevant. Lookup then requires a complete match `name + ID` to be successful, which is guaranteed to match at most a single item in the whole RNA collection (since RNA collection of IDs do not allow duplicates, and the ID pointer is always unique). Note that this ID pointer is implemented as an `std::optional` one (either directly in C++ code, or using an new liboverride operation `flag` in DNA). This allows to smoothly transition from existing data to the added ID pointer info (when needed), without needing any dedicated versioning. This solution also preserves forward compatibility as much as possible. It may also provide marginal performances improvements in some cases, as looking up for ID items in RNA collections will first check for the ID pointer, which should be faster than a string comparision. Implements #110421. Pull Request: https://projects.blender.org/blender/blender/pulls/110773
2023-08-03 20:32:36 +02:00
oprop, nullptr, nullptr, {}, {}, index, index, false, &is_strict_find);
BLI_assert(opop != nullptr);
if (!is_strict_find) {
/* No specific override operation, we have to get generic one,
* and create item-specific override operations for all but given index,
* before removing generic one. */
for (int idx = RNA_property_array_length(&ptr, prop); idx--;) {
if (idx != index) {
BKE_lib_override_library_property_operation_get(
LibOverride: Add ID pointer to operations over ID pointers. In RNA collections storing ID references, the name of the collection item may not always be unique, when several IDs from different libraries are present. While rare, this situation can become deadly to liboverride, by causing random but exponential liboverride hierarchies corruptions. This has already been alleviated by using preferably both name and index in items lookup (a05419f18b) and by reducing the risk of name collision in general between liboverrides and their linked reference (b9becc47de). This commit goes further, by ensuring that references to items of RNA collections of IDs stored in liboverride operations become completely unambiguous. This is achieved by storing an extra pointer to the item's ID itself, when relevant. Lookup then requires a complete match `name + ID` to be successful, which is guaranteed to match at most a single item in the whole RNA collection (since RNA collection of IDs do not allow duplicates, and the ID pointer is always unique). Note that this ID pointer is implemented as an `std::optional` one (either directly in C++ code, or using an new liboverride operation `flag` in DNA). This allows to smoothly transition from existing data to the added ID pointer info (when needed), without needing any dedicated versioning. This solution also preserves forward compatibility as much as possible. It may also provide marginal performances improvements in some cases, as looking up for ID items in RNA collections will first check for the ID pointer, which should be faster than a string comparision. Implements #110421. Pull Request: https://projects.blender.org/blender/blender/pulls/110773
2023-08-03 20:32:36 +02:00
oprop, opop->operation, nullptr, nullptr, {}, {}, idx, idx, true, nullptr, nullptr);
}
}
}
BKE_lib_override_library_property_operation_delete(oprop, opop);
RNA_property_copy(bmain, &ptr, &src, prop, index);
if (BLI_listbase_is_empty(&oprop->operations)) {
BKE_lib_override_library_property_delete(id->override_library, oprop);
}
}
else {
/* Just remove whole generic override operation of this property. */
BKE_lib_override_library_property_delete(id->override_library, oprop);
RNA_property_copy(bmain, &ptr, &src, prop, -1);
}
/* Outliner e.g. has to be aware of this change. */
WM_main_add_notifier(NC_WM | ND_LIB_OVERRIDE_CHANGED, nullptr);
return operator_button_property_finish(C, &ptr, prop);
}
static void UI_OT_override_remove_button(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Remove Override";
ot->idname = "UI_OT_override_remove_button";
ot->description = "Remove an override operation";
/* callbacks */
ot->poll = override_remove_button_poll;
ot->exec = override_remove_button_exec;
/* flags */
ot->flag = OPTYPE_UNDO;
/* properties */
RNA_def_boolean(
ot->srna, "all", true, "All", "Reset to default values all elements of the array");
}
static void override_idtemplate_ids_get(
bContext *C, ID **r_owner_id, ID **r_id, PointerRNA *r_owner_ptr, PropertyRNA **r_prop)
{
PointerRNA owner_ptr;
PropertyRNA *prop;
UI_context_active_but_prop_get_templateID(C, &owner_ptr, &prop);
if (owner_ptr.data == nullptr || prop == nullptr) {
*r_owner_id = *r_id = nullptr;
if (r_owner_ptr != nullptr) {
*r_owner_ptr = PointerRNA_NULL;
}
if (r_prop != nullptr) {
*r_prop = nullptr;
}
return;
}
*r_owner_id = owner_ptr.owner_id;
PointerRNA idptr = RNA_property_pointer_get(&owner_ptr, prop);
2022-08-17 21:33:46 +10:00
*r_id = static_cast<ID *>(idptr.data);
if (r_owner_ptr != nullptr) {
*r_owner_ptr = owner_ptr;
}
if (r_prop != nullptr) {
*r_prop = prop;
}
}
static bool override_idtemplate_poll(bContext *C, const bool is_create_op)
{
ID *owner_id, *id;
override_idtemplate_ids_get(C, &owner_id, &id, nullptr, nullptr);
if (owner_id == nullptr || id == nullptr) {
return false;
}
if (is_create_op) {
if (!ID_IS_LINKED(id) && !ID_IS_OVERRIDE_LIBRARY_REAL(id)) {
return false;
}
return true;
}
/* Reset/Clear operations. */
if (ID_IS_LINKED(id) || !ID_IS_OVERRIDE_LIBRARY_REAL(id)) {
return false;
}
return true;
}
static bool override_idtemplate_make_poll(bContext *C)
{
return override_idtemplate_poll(C, true);
}
static int override_idtemplate_make_exec(bContext *C, wmOperator * /*op*/)
{
ID *owner_id, *id;
PointerRNA owner_ptr;
PropertyRNA *prop;
override_idtemplate_ids_get(C, &owner_id, &id, &owner_ptr, &prop);
if (ELEM(nullptr, owner_id, id)) {
return OPERATOR_CANCELLED;
}
ID *id_override = ui_template_id_liboverride_hierarchy_make(
C, CTX_data_main(C), owner_id, id, nullptr);
if (id_override == nullptr) {
return OPERATOR_CANCELLED;
}
/* `idptr` is re-assigned to owner property to ensure proper updates etc. Here we also use it
* to ensure remapping of the owner property from the linked data to the newly created
* liboverride (note that in theory this remapping has already been done by code above), but
* only in case owner ID was already local ID (override or pure local data).
*
* Otherwise, owner ID will also have been overridden, and remapped already to use it's
* override of the data too. */
if (!ID_IS_LINKED(owner_id)) {
PointerRNA idptr = RNA_id_pointer_create(id_override);
RNA_property_pointer_set(&owner_ptr, prop, idptr, nullptr);
}
RNA_property_update(C, &owner_ptr, prop);
/* 'Security' extra tagging, since this process may also affect the owner ID and not only the
* used ID, relying on the property update code only is not always enough. */
DEG_id_tag_update(&CTX_data_scene(C)->id, ID_RECALC_BASE_FLAGS | ID_RECALC_SYNC_TO_EVAL);
WM_event_add_notifier(C, NC_WINDOW, nullptr);
WM_event_add_notifier(C, NC_WM | ND_LIB_OVERRIDE_CHANGED, nullptr);
WM_event_add_notifier(C, NC_SPACE | ND_SPACE_VIEW3D, nullptr);
return OPERATOR_FINISHED;
}
static void UI_OT_override_idtemplate_make(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Make Library Override";
ot->idname = "UI_OT_override_idtemplate_make";
ot->description =
"Create a local override of the selected linked data-block, and its hierarchy of "
"dependencies";
/* callbacks */
ot->poll = override_idtemplate_make_poll;
ot->exec = override_idtemplate_make_exec;
/* flags */
ot->flag = OPTYPE_UNDO;
}
static bool override_idtemplate_reset_poll(bContext *C)
{
return override_idtemplate_poll(C, false);
}
static int override_idtemplate_reset_exec(bContext *C, wmOperator * /*op*/)
{
ID *owner_id, *id;
PointerRNA owner_ptr;
PropertyRNA *prop;
override_idtemplate_ids_get(C, &owner_id, &id, &owner_ptr, &prop);
if (ELEM(nullptr, owner_id, id)) {
return OPERATOR_CANCELLED;
}
if (ID_IS_LINKED(id) || !ID_IS_OVERRIDE_LIBRARY_REAL(id)) {
return OPERATOR_CANCELLED;
}
BKE_lib_override_library_id_reset(CTX_data_main(C), id, false);
/* `idptr` is re-assigned to owner property to ensure proper updates etc. */
PointerRNA idptr = RNA_id_pointer_create(id);
RNA_property_pointer_set(&owner_ptr, prop, idptr, nullptr);
RNA_property_update(C, &owner_ptr, prop);
/* No need for 'security' extra tagging here, since this process will never affect the owner ID.
*/
return OPERATOR_FINISHED;
}
static void UI_OT_override_idtemplate_reset(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Reset Library Override";
ot->idname = "UI_OT_override_idtemplate_reset";
ot->description = "Reset the selected local override to its linked reference values";
/* callbacks */
ot->poll = override_idtemplate_reset_poll;
ot->exec = override_idtemplate_reset_exec;
/* flags */
ot->flag = OPTYPE_UNDO;
}
static bool override_idtemplate_clear_poll(bContext *C)
{
return override_idtemplate_poll(C, false);
}
static int override_idtemplate_clear_exec(bContext *C, wmOperator * /*op*/)
{
ID *owner_id, *id;
PointerRNA owner_ptr;
PropertyRNA *prop;
override_idtemplate_ids_get(C, &owner_id, &id, &owner_ptr, &prop);
if (ELEM(nullptr, owner_id, id)) {
return OPERATOR_CANCELLED;
}
if (ID_IS_LINKED(id)) {
return OPERATOR_CANCELLED;
}
Main *bmain = CTX_data_main(C);
ViewLayer *view_layer = CTX_data_view_layer(C);
Scene *scene = CTX_data_scene(C);
ID *id_new = id;
if (BKE_lib_override_library_is_hierarchy_leaf(bmain, id)) {
id_new = id->override_library->reference;
bool do_remap_active = false;
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(scene, view_layer);
if (BKE_view_layer_active_object_get(view_layer) == (Object *)id) {
BLI_assert(GS(id->name) == ID_OB);
BLI_assert(GS(id_new->name) == ID_OB);
do_remap_active = true;
}
BKE_libblock_remap(bmain, id, id_new, ID_REMAP_SKIP_INDIRECT_USAGE);
if (do_remap_active) {
Object *ref_object = (Object *)id_new;
Base *basact = BKE_view_layer_base_find(view_layer, ref_object);
if (basact != nullptr) {
view_layer->basact = basact;
}
DEG_id_tag_update(&scene->id, ID_RECALC_SELECT);
}
BKE_id_delete(bmain, id);
}
else {
BKE_lib_override_library_id_reset(bmain, id, true);
}
/* Here the affected ID may remain the same, or be replaced by its linked reference. In either
* case, the owner ID remains unchanged, and remapping is already handled by internal code, so
* calling `RNA_property_update` on it is enough to ensure proper notifiers are sent. */
RNA_property_update(C, &owner_ptr, prop);
/* 'Security' extra tagging, since this process may also affect the owner ID and not only the
* used ID, relying on the property update code only is not always enough. */
DEG_id_tag_update(&scene->id, ID_RECALC_BASE_FLAGS | ID_RECALC_SYNC_TO_EVAL);
WM_event_add_notifier(C, NC_WINDOW, nullptr);
WM_event_add_notifier(C, NC_WM | ND_LIB_OVERRIDE_CHANGED, nullptr);
WM_event_add_notifier(C, NC_SPACE | ND_SPACE_VIEW3D, nullptr);
return OPERATOR_FINISHED;
}
static void UI_OT_override_idtemplate_clear(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Clear Library Override";
ot->idname = "UI_OT_override_idtemplate_clear";
ot->description =
"Delete the selected local override and relink its usages to the linked data-block if "
"possible, else reset it and mark it as non editable";
/* callbacks */
ot->poll = override_idtemplate_clear_poll;
ot->exec = override_idtemplate_clear_exec;
/* flags */
ot->flag = OPTYPE_UNDO;
}
static bool override_idtemplate_menu_poll(const bContext *C_const, MenuType * /*mt*/)
{
bContext *C = (bContext *)C_const;
ID *owner_id, *id;
override_idtemplate_ids_get(C, &owner_id, &id, nullptr, nullptr);
if (owner_id == nullptr || id == nullptr) {
return false;
}
if (!(ID_IS_LINKED(id) || ID_IS_OVERRIDE_LIBRARY_REAL(id))) {
return false;
}
return true;
}
static void override_idtemplate_menu_draw(const bContext * /*C*/, Menu *menu)
{
uiLayout *layout = menu->layout;
uiItemO(layout, IFACE_("Make"), ICON_NONE, "UI_OT_override_idtemplate_make");
uiItemO(layout, IFACE_("Reset"), ICON_NONE, "UI_OT_override_idtemplate_reset");
uiItemO(layout, IFACE_("Clear"), ICON_NONE, "UI_OT_override_idtemplate_clear");
}
static void override_idtemplate_menu()
{
MenuType *mt;
2022-08-17 21:33:46 +10:00
mt = MEM_cnew<MenuType>(__func__);
STRNCPY(mt->idname, "UI_MT_idtemplate_liboverride");
STRNCPY(mt->label, N_("Library Override"));
mt->poll = override_idtemplate_menu_poll;
mt->draw = override_idtemplate_menu_draw;
WM_menutype_add(mt);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Copy To Selected Operator
* \{ */
#define NOT_NULL(assignment) ((assignment) != nullptr)
#define NOT_RNA_NULL(assignment) ((assignment).data != nullptr)
static void ui_context_selected_bones_via_pose(bContext *C, blender::Vector<PointerRNA> *r_lb)
{
blender::Vector<PointerRNA> lb = CTX_data_collection_get(C, "selected_pose_bones");
if (!lb.is_empty()) {
for (PointerRNA &ptr : lb) {
bPoseChannel *pchan = static_cast<bPoseChannel *>(ptr.data);
ptr = RNA_pointer_create(ptr.owner_id, &RNA_Bone, pchan->bone);
}
}
*r_lb = std::move(lb);
}
static void ui_context_fcurve_modifiers_via_fcurve(bContext *C,
blender::Vector<PointerRNA> *r_lb,
FModifier *source)
{
blender::Vector<PointerRNA> fcurve_links;
fcurve_links = CTX_data_collection_get(C, "selected_editable_fcurves");
if (fcurve_links.is_empty()) {
return;
}
r_lb->clear();
for (const PointerRNA &ptr : fcurve_links) {
const FCurve *fcu = static_cast<const FCurve *>(ptr.data);
LISTBASE_FOREACH (FModifier *, mod, &fcu->modifiers) {
if (STREQ(mod->name, source->name) && mod->type == source->type) {
r_lb->append(RNA_pointer_create(ptr.owner_id, &RNA_FModifier, mod));
/* Since names are unique it is safe to break here. */
break;
}
}
}
}
bool UI_context_copy_to_selected_list(bContext *C,
PointerRNA *ptr,
PropertyRNA *prop,
blender::Vector<PointerRNA> *r_lb,
bool *r_use_path_from_id,
std::optional<std::string> *r_path)
{
*r_use_path_from_id = false;
*r_path = std::nullopt;
/* special case for bone constraints */
const bool is_rna = !RNA_property_is_idprop(prop);
/* Remove links from the collection list which don't contain 'prop'. */
bool ensure_list_items_contain_prop = false;
/* PropertyGroup objects don't have a reference to the struct that actually owns
* them, so it is normally necessary to do a brute force search to find it. This
* handles the search for non-ID owners by using the 'active' reference as a hint
* to preserve efficiency. Only properties defined through RNA are handled, as
* custom properties cannot be assumed to be valid for all instances.
*
* Properties owned by the ID are handled by the 'if (ptr->owner_id)' case below.
*/
if (is_rna && RNA_struct_is_a(ptr->type, &RNA_PropertyGroup)) {
PointerRNA owner_ptr;
std::optional<std::string> idpath;
/* First, check the active PoseBone and PoseBone->Bone. */
if (NOT_RNA_NULL(owner_ptr = CTX_data_pointer_get_type(C, "active_pose_bone", &RNA_PoseBone)))
{
idpath = RNA_path_from_struct_to_idproperty(&owner_ptr,
static_cast<const IDProperty *>(ptr->data));
if (idpath) {
*r_lb = CTX_data_collection_get(C, "selected_pose_bones");
}
else {
bPoseChannel *pchan = static_cast<bPoseChannel *>(owner_ptr.data);
owner_ptr = RNA_pointer_create(owner_ptr.owner_id, &RNA_Bone, pchan->bone);
idpath = RNA_path_from_struct_to_idproperty(&owner_ptr,
static_cast<const IDProperty *>(ptr->data));
if (idpath) {
ui_context_selected_bones_via_pose(C, r_lb);
}
}
}
if (!idpath) {
/* Check the active EditBone if in edit mode. */
idpath = RNA_path_from_struct_to_idproperty(&owner_ptr,
static_cast<const IDProperty *>(ptr->data));
if (NOT_RNA_NULL(
owner_ptr = CTX_data_pointer_get_type_silent(C, "active_bone", &RNA_EditBone)) &&
idpath)
{
*r_lb = CTX_data_collection_get(C, "selected_editable_bones");
}
/* Add other simple cases here (Node, NodeSocket, Sequence, ViewLayer etc). */
}
if (idpath) {
*r_path = fmt::format("{}.{}", *idpath, RNA_property_identifier(prop));
return true;
}
}
if (RNA_struct_is_a(ptr->type, &RNA_EditBone)) {
*r_lb = CTX_data_collection_get(C, "selected_editable_bones");
}
else if (RNA_struct_is_a(ptr->type, &RNA_PoseBone)) {
*r_lb = CTX_data_collection_get(C, "selected_pose_bones");
}
else if (RNA_struct_is_a(ptr->type, &RNA_Bone)) {
ui_context_selected_bones_via_pose(C, r_lb);
}
else if (RNA_struct_is_a(ptr->type, &RNA_BoneColor)) {
/* Get the things that own the bone color (bones, pose bones, or edit bones). */
/* First this will be bones, then gets remapped to colors. */
blender::Vector<PointerRNA> list_of_things = {};
switch (GS(ptr->owner_id->name)) {
case ID_OB:
list_of_things = CTX_data_collection_get(C, "selected_pose_bones");
break;
case ID_AR: {
/* Armature-owned bones can be accessed from both edit mode and pose mode.
* - Edit mode: visit selected edit bones.
* - Pose mode: visit the armature bones of selected pose bones.
*/
const bArmature *arm = reinterpret_cast<bArmature *>(ptr->owner_id);
if (arm->edbo) {
list_of_things = CTX_data_collection_get(C, "selected_editable_bones");
}
else {
list_of_things = CTX_data_collection_get(C, "selected_pose_bones");
CTX_data_collection_remap_property(list_of_things, "bone");
}
break;
}
default:
printf("BoneColor is unexpectedly owned by %s '%s'\n",
BKE_idtype_idcode_to_name(GS(ptr->owner_id->name)),
ptr->owner_id->name + 2);
BLI_assert_msg(false,
"expected BoneColor to be owned by the Armature "
"(bone & edit bone) or the Object (pose bone)");
return false;
}
/* Remap from some bone to its color, to ensure the items of r_lb are of
* type ptr->type. Since all three structs `bPoseChan`, `Bone`, and
* `EditBone` have the same name for their embedded `BoneColor` struct, this
* code is suitable for all of them. */
CTX_data_collection_remap_property(list_of_things, "color");
*r_lb = list_of_things;
}
else if (RNA_struct_is_a(ptr->type, &RNA_Sequence)) {
/* Special case when we do this for 'Sequence.lock'.
* (if the sequence is locked, it won't be in "selected_editable_sequences"). */
const char *prop_id = RNA_property_identifier(prop);
if (STREQ(prop_id, "lock")) {
*r_lb = CTX_data_collection_get(C, "selected_sequences");
}
else {
*r_lb = CTX_data_collection_get(C, "selected_editable_sequences");
}
if (is_rna) {
/* Account for properties only being available for some sequence types. */
ensure_list_items_contain_prop = true;
}
}
else if (RNA_struct_is_a(ptr->type, &RNA_FCurve)) {
*r_lb = CTX_data_collection_get(C, "selected_editable_fcurves");
}
else if (RNA_struct_is_a(ptr->type, &RNA_FModifier)) {
FModifier *mod = static_cast<FModifier *>(ptr->data);
ui_context_fcurve_modifiers_via_fcurve(C, r_lb, mod);
}
else if (RNA_struct_is_a(ptr->type, &RNA_Keyframe)) {
*r_lb = CTX_data_collection_get(C, "selected_editable_keyframes");
}
else if (RNA_struct_is_a(ptr->type, &RNA_Action)) {
*r_lb = CTX_data_collection_get(C, "selected_editable_actions");
}
else if (RNA_struct_is_a(ptr->type, &RNA_NlaStrip)) {
*r_lb = CTX_data_collection_get(C, "selected_nla_strips");
}
else if (RNA_struct_is_a(ptr->type, &RNA_MovieTrackingTrack)) {
*r_lb = CTX_data_collection_get(C, "selected_movieclip_tracks");
}
else if (const std::optional<std::string> path_from_bone =
RNA_path_resolve_from_type_to_property(ptr, prop, &RNA_PoseBone);
RNA_struct_is_a(ptr->type, &RNA_Constraint) && path_from_bone)
{
*r_lb = CTX_data_collection_get(C, "selected_pose_bones");
*r_path = path_from_bone;
}
else if (RNA_struct_is_a(ptr->type, &RNA_Node) || RNA_struct_is_a(ptr->type, &RNA_NodeSocket)) {
blender::Vector<PointerRNA> lb;
std::optional<std::string> path;
bNode *node = nullptr;
/* Get the node we're editing */
if (RNA_struct_is_a(ptr->type, &RNA_NodeSocket)) {
bNodeTree *ntree = (bNodeTree *)ptr->owner_id;
bNodeSocket *sock = static_cast<bNodeSocket *>(ptr->data);
if (blender::bke::nodeFindNodeTry(ntree, sock, &node, nullptr)) {
path = RNA_path_resolve_from_type_to_property(ptr, prop, &RNA_Node);
if (path) {
/* we're good! */
}
else {
node = nullptr;
}
}
}
else {
node = static_cast<bNode *>(ptr->data);
}
/* Now filter by type */
if (node) {
lb = CTX_data_collection_get(C, "selected_nodes");
lb.remove_if([&](const PointerRNA &link) {
bNode *node_data = static_cast<bNode *>(link.data);
if (node_data->type != node->type) {
return true;
}
return false;
});
}
*r_lb = lb;
*r_path = path;
}
else if (ptr->owner_id) {
ID *id = ptr->owner_id;
if (GS(id->name) == ID_OB) {
*r_lb = CTX_data_collection_get(C, "selected_editable_objects");
*r_use_path_from_id = true;
*r_path = RNA_path_from_ID_to_property(ptr, prop);
}
else if (OB_DATA_SUPPORT_ID(GS(id->name))) {
/* check we're using the active object */
const short id_code = GS(id->name);
blender::Vector<PointerRNA> lb = CTX_data_collection_get(C, "selected_editable_objects");
const std::optional<std::string> path = RNA_path_from_ID_to_property(ptr, prop);
/* de-duplicate obdata */
if (!lb.is_empty()) {
for (const PointerRNA &ob_ptr : lb) {
Object *ob = (Object *)ob_ptr.owner_id;
if (ID *id_data = static_cast<ID *>(ob->data)) {
id_data->tag |= LIB_TAG_DOIT;
}
}
blender::Vector<PointerRNA> new_lb;
for (const PointerRNA &link : lb) {
Object *ob = (Object *)link.owner_id;
ID *id_data = static_cast<ID *>(ob->data);
if ((id_data == nullptr) || (id_data->tag & LIB_TAG_DOIT) == 0 ||
!ID_IS_EDITABLE(id_data) || (GS(id_data->name) != id_code))
{
continue;
}
/* Avoid prepending 'data' to the path. */
new_lb.append(RNA_id_pointer_create(id_data));
if (id_data) {
id_data->tag &= ~LIB_TAG_DOIT;
}
}
lb = std::move(new_lb);
}
*r_lb = lb;
*r_path = path;
}
else if (GS(id->name) == ID_SCE) {
/* Sequencer's ID is scene :/ */
/* Try to recursively find an RNA_Sequence ancestor,
* to handle situations like #41062... */
*r_path = RNA_path_resolve_from_type_to_property(ptr, prop, &RNA_Sequence);
if (r_path->has_value()) {
/* Special case when we do this for 'Sequence.lock'.
* (if the sequence is locked, it won't be in "selected_editable_sequences"). */
const char *prop_id = RNA_property_identifier(prop);
if (is_rna && STREQ(prop_id, "lock")) {
*r_lb = CTX_data_collection_get(C, "selected_sequences");
}
else {
*r_lb = CTX_data_collection_get(C, "selected_editable_sequences");
}
if (is_rna) {
/* Account for properties only being available for some sequence types. */
ensure_list_items_contain_prop = true;
}
}
}
return r_path->has_value();
}
else {
return false;
}
if (RNA_property_is_idprop(prop)) {
if (!r_path->has_value()) {
*r_path = RNA_path_from_ptr_to_property_index(ptr, prop, 0, -1);
BLI_assert(*r_path);
}
/* Always resolve custom-properties because they can always exist per-item. */
ensure_list_items_contain_prop = true;
}
if (ensure_list_items_contain_prop) {
if (is_rna) {
const char *prop_id = RNA_property_identifier(prop);
r_lb->remove_if([&](const PointerRNA &link) {
if ((ptr->type != link.type) &&
(RNA_struct_type_find_property(link.type, prop_id) != prop))
{
return true;
}
return false;
});
}
else {
const bool prop_is_array = RNA_property_array_check(prop);
const int prop_array_len = prop_is_array ? RNA_property_array_length(ptr, prop) : -1;
const PropertyType prop_type = RNA_property_type(prop);
r_lb->remove_if([&](PointerRNA &link) {
PointerRNA lptr;
PropertyRNA *lprop = nullptr;
RNA_path_resolve_property(
&link, r_path->has_value() ? r_path->value().c_str() : nullptr, &lptr, &lprop);
if (lprop == nullptr) {
return true;
}
if (!RNA_property_is_idprop(lprop)) {
return true;
}
if (prop_type != RNA_property_type(lprop)) {
return true;
}
if (prop_is_array != RNA_property_array_check(lprop)) {
return true;
}
if (prop_is_array && (prop_array_len != RNA_property_array_length(&link, lprop))) {
return true;
}
return false;
});
}
}
return true;
}
bool UI_context_copy_to_selected_check(PointerRNA *ptr,
PointerRNA *ptr_link,
PropertyRNA *prop,
const char *path,
bool use_path_from_id,
PointerRNA *r_ptr,
PropertyRNA **r_prop)
{
PropertyRNA *lprop;
PointerRNA lptr;
if (ptr_link->data == ptr->data) {
return false;
}
if (use_path_from_id) {
/* Path relative to ID. */
lprop = nullptr;
PointerRNA idptr = RNA_id_pointer_create(ptr_link->owner_id);
RNA_path_resolve_property(&idptr, path, &lptr, &lprop);
}
else if (path) {
/* Path relative to elements from list. */
lprop = nullptr;
RNA_path_resolve_property(ptr_link, path, &lptr, &lprop);
}
else {
BLI_assert(!RNA_property_is_idprop(prop));
lptr = *ptr_link;
lprop = prop;
}
if (lptr.data == ptr->data) {
2023-08-18 09:10:32 +10:00
/* The source & destination are the same, so there is nothing to copy. */
return false;
}
2021-12-17 15:26:28 +11:00
/* Skip non-existing properties on link. This was previously covered with the `lprop != prop`
* check but we are now more permissive when it comes to ID properties, see below. */
if (lprop == nullptr) {
return false;
}
if (RNA_property_type(lprop) != RNA_property_type(prop)) {
return false;
}
2021-12-17 15:26:28 +11:00
/* Check property pointers matching.
* For ID properties, these pointers match:
* - If the property is API defined on an existing class (and they are equally named).
* - Never for ID properties on specific ID (even if they are equally named).
* - Never for NodesModifierSettings properties (even if they are equally named).
*
* Be permissive on ID properties in the following cases:
2021-12-17 15:26:28 +11:00
* - #NodesModifierSettings properties
* - (special check: only if the node-group matches, since the 'Input_n' properties are name
* based and similar on potentially very different node-groups).
* - ID properties on specific ID
2021-12-17 15:26:28 +11:00
* - (no special check, copying seems OK [even if type does not match -- does not do anything
* then])
*/
bool ignore_prop_eq = RNA_property_is_idprop(lprop) && RNA_property_is_idprop(prop);
if (RNA_struct_is_a(lptr.type, &RNA_NodesModifier) &&
RNA_struct_is_a(ptr->type, &RNA_NodesModifier))
{
ignore_prop_eq = false;
NodesModifierData *nmd_link = (NodesModifierData *)lptr.data;
NodesModifierData *nmd_src = (NodesModifierData *)ptr->data;
if (nmd_link->node_group == nmd_src->node_group) {
ignore_prop_eq = true;
}
}
if ((lprop != prop) && !ignore_prop_eq) {
return false;
}
if (!RNA_property_editable(&lptr, lprop)) {
return false;
}
if (r_ptr) {
*r_ptr = lptr;
}
if (r_prop) {
*r_prop = lprop;
}
return true;
}
/**
* Called from both exec & poll.
*
* \note Normally we wouldn't call a loop from within a poll function,
* however this is a special case, and for regular poll calls, getting
* the context from the button will fail early.
*/
static bool copy_to_selected_button(bContext *C, bool all, bool poll)
{
Main *bmain = CTX_data_main(C);
PointerRNA ptr, lptr;
PropertyRNA *prop, *lprop;
int index;
/* try to reset the nominated setting to its default value */
UI_context_active_but_prop_get(C, &ptr, &prop, &index);
/* if there is a valid property that is editable... */
if (ptr.data == nullptr || prop == nullptr) {
return false;
}
bool success = false;
std::optional<std::string> path;
bool use_path_from_id;
blender::Vector<PointerRNA> lb;
if (UI_context_copy_to_selected_list(C, &ptr, prop, &lb, &use_path_from_id, &path)) {
for (PointerRNA &link : lb) {
if (link.data == ptr.data) {
continue;
}
if (!UI_context_copy_to_selected_check(&ptr,
&link,
prop,
path.has_value() ? path->c_str() : nullptr,
use_path_from_id,
&lptr,
&lprop))
{
continue;
}
if (poll) {
success = true;
break;
}
if (RNA_property_copy(bmain, &lptr, &ptr, prop, (all) ? -1 : index)) {
RNA_property_update(C, &lptr, prop);
success = true;
}
}
}
return success;
}
2018-07-02 11:47:00 +02:00
static bool copy_to_selected_button_poll(bContext *C)
{
return copy_to_selected_button(C, false, true);
}
static int copy_to_selected_button_exec(bContext *C, wmOperator *op)
{
bool success;
const bool all = RNA_boolean_get(op->ptr, "all");
success = copy_to_selected_button(C, all, false);
2012-03-30 01:51:25 +00:00
return (success) ? OPERATOR_FINISHED : OPERATOR_CANCELLED;
}
static void UI_OT_copy_to_selected_button(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Copy to Selected";
ot->idname = "UI_OT_copy_to_selected_button";
ot->description =
"Copy the property's value from the active item to the same property of all selected items "
"if the same property exists";
/* callbacks */
ot->poll = copy_to_selected_button_poll;
ot->exec = copy_to_selected_button_exec;
/* flags */
2012-03-30 01:51:25 +00:00
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
/* properties */
RNA_def_boolean(ot->srna, "all", true, "All", "Copy to selected all elements of the array");
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Copy Driver To Selected Operator
* \{ */
2024-05-02 16:44:10 +10:00
/* Name-spaced for unit testing. Conceptually these functions should be static
* and not be used outside this source file. But they need to be externally
* accessible to add unit tests for them. */
namespace blender::interface::internal {
blender::Vector<FCurve *> get_property_drivers(
PointerRNA *ptr, PropertyRNA *prop, const bool get_all, const int index, bool *r_is_array_prop)
{
BLI_assert(ptr && prop);
const std::optional<std::string> path = RNA_path_from_ID_to_property(ptr, prop);
if (!path.has_value()) {
return {};
}
AnimData *adt = BKE_animdata_from_id(ptr->owner_id);
if (!adt) {
return {};
}
blender::Vector<FCurve *> drivers = {};
const bool is_array_prop = RNA_property_array_check(prop);
if (!is_array_prop) {
/* NOTE: by convention Blender assigns 0 as the index for drivers of
* non-array properties, which is why we search for it here. Values > 0 are
* not recognized by Blender's driver system in that case. Values < 0 are
* recognized for driver evaluation, but `BKE_fcurve_find()` unconditionally
* returns nullptr in that case so it wouldn't matter here anyway. */
drivers.append(BKE_fcurve_find(&adt->drivers, path->c_str(), 0));
}
else {
/* For array properties, we always allocate space for all elements of an
* array property, and the unused ones just remain null. */
drivers.resize(RNA_property_array_length(ptr, prop), nullptr);
for (int i = 0; i < drivers.size(); i++) {
if (get_all || i == index) {
drivers[i] = BKE_fcurve_find(&adt->drivers, path->c_str(), i);
}
}
}
/* If we didn't get any drivers to copy, instead of returning a vector of all
* nullptr, return an empty vector for clarity. That way the caller gets
* either a useful result or an empty one. */
bool fetched_at_least_one = false;
for (const FCurve *driver : drivers) {
fetched_at_least_one |= driver != nullptr;
}
if (!fetched_at_least_one) {
return {};
}
if (r_is_array_prop) {
*r_is_array_prop = is_array_prop;
}
return drivers;
}
int paste_property_drivers(blender::Span<FCurve *> src_drivers,
const bool is_array_prop,
PointerRNA *dst_ptr,
PropertyRNA *dst_prop)
{
BLI_assert(src_drivers.size() > 0);
BLI_assert(is_array_prop || src_drivers.size() == 1);
/* Get the RNA path and relevant animdata for the property we're copying to. */
const std::optional<std::string> dst_path = RNA_path_from_ID_to_property(dst_ptr, dst_prop);
if (!dst_path.has_value()) {
return 0;
}
AnimData *dst_adt = BKE_animdata_ensure_id(dst_ptr->owner_id);
if (!dst_adt) {
return 0;
}
/* Do the copying. */
int paste_count = 0;
for (int i = 0; i < src_drivers.size(); i++) {
if (!src_drivers[i]) {
continue;
}
const int dst_index = is_array_prop ? i : -1;
/* If it's already animated by something other than a driver, skip. This is
* because Blender's UI assumes that properties are either animated *or*
* driven, and things can get confusing for users otherwise. Additionally,
* no other parts of Blender's UI allow users to (at least easily) add
* drivers on already-animated properties, so this keeps things consistent
* across driver-related operators. */
bool driven;
{
const FCurve *fcu = BKE_fcurve_find_by_rna(
dst_ptr, dst_prop, dst_index, nullptr, nullptr, &driven, nullptr);
if (fcu && !driven) {
continue;
}
}
/* If there's an existing matching driver, remove it first.
*
* TODO: in the context of `copy_driver_to_selected_button()` this has
* quadratic complexity when the drivers are within the same ID, due to this
* being inside of a loop and doing a linear scan of the drivers to find one
* that matches. We should be able to make this more efficient with a
* little cleverness .*/
if (driven) {
FCurve *old_driver = BKE_fcurve_find(&dst_adt->drivers, dst_path->c_str(), dst_index);
if (old_driver) {
BLI_remlink(&dst_adt->drivers, old_driver);
BKE_fcurve_free(old_driver);
}
}
/* Create the new driver. */
FCurve *new_driver = BKE_fcurve_copy(src_drivers[i]);
BKE_fcurve_rnapath_set(*new_driver, dst_path.value());
BLI_addtail(&dst_adt->drivers, new_driver);
paste_count++;
}
return paste_count;
}
} // namespace blender::interface::internal
/**
* Called from both exec & poll.
*
* \note We use this function for both poll and exec because the logic for
* whether there is a valid selection to copy to is baked into
* `UI_context_copy_to_selected_list()`, and the setup required to call that
* would either be duplicated or need to be split out into its own awkward
* difficult-to-name function with a large number of parameters. So instead we
* follow the same pattern as `copy_to_selected_button()` further above, with a
* bool to switch between exec and poll behavior. This isn't great, but seems
* like the lesser evil under the circumstances.
*
* \param copy_entire_array: If true, copies drivers of all elements of an array
* property. Otherwise only copies one specific element.
* \param poll: If true, only checks if the driver(s) could be copied rather than
* actually performing the copy.
*
* \returns true in exec mode if any copies were successfully made, and false
* otherwise. Returns true in poll mode if a copy could be successfully made,
* and false otherwise.
*/
static bool copy_driver_to_selected_button(bContext *C, bool copy_entire_array, const bool poll)
{
using namespace blender::interface::internal;
PropertyRNA *prop;
PointerRNA ptr;
int index;
/* Get the property of the clicked button. */
UI_context_active_but_prop_get(C, &ptr, &prop, &index);
if (!ptr.data || !ptr.owner_id || !prop) {
return false;
}
copy_entire_array |= index == -1; /* -1 implies `copy_entire_array` for array properties. */
/* Get the property's driver(s). */
bool is_array_prop = false;
const blender::Vector<FCurve *> src_drivers = get_property_drivers(
&ptr, prop, copy_entire_array, index, &is_array_prop);
if (src_drivers.is_empty()) {
return false;
}
/* Build the list of properties to copy the driver(s) to, along with relevant
* side data. */
std::optional<std::string> path;
bool use_path_from_id;
blender::Vector<PointerRNA> target_properties;
if (!UI_context_copy_to_selected_list(
C, &ptr, prop, &target_properties, &use_path_from_id, &path))
{
return false;
}
/* Copy the driver(s) to the list of target properties. */
int total_copy_count = 0;
for (PointerRNA &target_prop : target_properties) {
if (target_prop.data == ptr.data) {
continue;
}
/* Get the target property and ensure that it's appropriate for adding
* drivers. */
PropertyRNA *dst_prop;
PointerRNA dst_ptr;
if (!UI_context_copy_to_selected_check(&ptr,
&target_prop,
prop,
path.has_value() ? path->c_str() : nullptr,
use_path_from_id,
&dst_ptr,
&dst_prop))
{
continue;
}
if (!RNA_property_driver_editable(&dst_ptr, dst_prop)) {
continue;
}
/* If we're just polling, then we early-out on the first property we would
* be able to copy to. */
if (poll) {
return true;
}
const int paste_count = paste_property_drivers(
src_drivers.as_span(), is_array_prop, &dst_ptr, dst_prop);
if (paste_count == 0) {
continue;
}
RNA_property_update(C, &dst_ptr, dst_prop);
total_copy_count += paste_count;
}
return total_copy_count > 0;
}
static bool copy_driver_to_selected_button_poll(bContext *C)
{
return copy_driver_to_selected_button(C, false, true);
}
static int copy_driver_to_selected_button_exec(bContext *C, wmOperator *op)
{
const bool all = RNA_boolean_get(op->ptr, "all");
if (!copy_driver_to_selected_button(C, all, false)) {
return OPERATOR_CANCELLED;
}
DEG_relations_tag_update(CTX_data_main(C));
WM_event_add_notifier(C, NC_ANIMATION | ND_KEYFRAME_PROP, nullptr);
return OPERATOR_FINISHED;
}
static void UI_OT_copy_driver_to_selected_button(wmOperatorType *ot)
{
/* Identifiers. */
ot->name = "Copy Driver to Selected";
ot->idname = "UI_OT_copy_driver_to_selected_button";
ot->description =
"Copy the property's driver from the active item to the same property "
"of all selected items, if the same property exists";
/* Callbacks. */
ot->poll = copy_driver_to_selected_button_poll;
ot->exec = copy_driver_to_selected_button_exec;
/* Flags. */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
/* Properties. */
RNA_def_boolean(
ot->srna, "all", false, "All", "Copy to selected the drivers of all elements of the array");
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Jump to Target Operator
* \{ */
/** Jump to the object or bone referenced by the pointer, or check if it is possible. */
static bool jump_to_target_ptr(bContext *C, PointerRNA ptr, const bool poll)
{
if (RNA_pointer_is_null(&ptr)) {
return false;
}
/* Verify pointer type. */
char bone_name[MAXBONENAME];
const StructRNA *target_type = nullptr;
if (ELEM(ptr.type, &RNA_EditBone, &RNA_PoseBone, &RNA_Bone)) {
RNA_string_get(&ptr, "name", bone_name);
if (bone_name[0] != '\0') {
target_type = &RNA_Bone;
}
}
else if (RNA_struct_is_a(ptr.type, &RNA_Object)) {
target_type = &RNA_Object;
}
if (target_type == nullptr) {
return false;
}
/* Find the containing Object. */
const Scene *scene = CTX_data_scene(C);
ViewLayer *view_layer = CTX_data_view_layer(C);
Base *base = nullptr;
const short id_type = GS(ptr.owner_id->name);
if (id_type == ID_OB) {
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(scene, view_layer);
base = BKE_view_layer_base_find(view_layer, (Object *)ptr.owner_id);
}
else if (OB_DATA_SUPPORT_ID(id_type)) {
base = blender::ed::object::find_first_by_data_id(scene, view_layer, ptr.owner_id);
}
bool ok = false;
if ((base == nullptr) || ((target_type == &RNA_Bone) && (base->object->type != OB_ARMATURE))) {
/* pass */
}
else if (poll) {
ok = true;
}
else {
/* Make optional. */
const bool reveal_hidden = true;
/* Select and activate the target. */
if (target_type == &RNA_Bone) {
ok = blender::ed::object::jump_to_bone(C, base->object, bone_name, reveal_hidden);
}
else if (target_type == &RNA_Object) {
ok = blender::ed::object::jump_to_object(C, base->object, reveal_hidden);
}
else {
BLI_assert(0);
}
}
return ok;
}
/**
* Jump to the object or bone referred to by the current UI field value.
*
* \note quite heavy for a poll callback, but the operator is only
* used as a right click menu item for certain UI field types, and
* this will fail quickly if the context is completely unsuitable.
*/
static bool jump_to_target_button(bContext *C, bool poll)
{
PointerRNA ptr, target_ptr;
PropertyRNA *prop;
int index;
const uiBut *but = UI_context_active_but_prop_get(C, &ptr, &prop, &index);
/* If there is a valid property... */
if (ptr.data && prop) {
const PropertyType type = RNA_property_type(prop);
/* For pointer properties, use their value directly. */
if (type == PROP_POINTER) {
target_ptr = RNA_property_pointer_get(&ptr, prop);
return jump_to_target_ptr(C, target_ptr, poll);
}
/* For string properties with prop_search, look up the search collection item. */
if (type == PROP_STRING) {
const uiButSearch *search_but = (but->type == UI_BTYPE_SEARCH_MENU) ? (uiButSearch *)but :
nullptr;
if (search_but && search_but->items_update_fn == ui_rna_collection_search_update_fn) {
uiRNACollectionSearch *coll_search = static_cast<uiRNACollectionSearch *>(search_but->arg);
char str_buf[MAXBONENAME];
char *str_ptr = RNA_property_string_get_alloc(
&ptr, prop, str_buf, sizeof(str_buf), nullptr);
bool found = false;
/* Jump to target only works with search properties currently, not search callbacks yet.
* See ui_but_add_search. */
if (coll_search->search_prop != nullptr) {
found = RNA_property_collection_lookup_string(
&coll_search->search_ptr, coll_search->search_prop, str_ptr, &target_ptr);
}
if (str_ptr != str_buf) {
MEM_freeN(str_ptr);
}
if (found) {
return jump_to_target_ptr(C, target_ptr, poll);
}
}
}
}
return false;
}
bool ui_jump_to_target_button_poll(bContext *C)
{
return jump_to_target_button(C, true);
}
static int jump_to_target_button_exec(bContext *C, wmOperator * /*op*/)
{
const bool success = jump_to_target_button(C, false);
return (success) ? OPERATOR_FINISHED : OPERATOR_CANCELLED;
}
static void UI_OT_jump_to_target_button(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Jump to Target";
ot->idname = "UI_OT_jump_to_target_button";
ot->description = "Switch to the target object or bone";
/* callbacks */
ot->poll = ui_jump_to_target_button_poll;
ot->exec = jump_to_target_button_exec;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Edit Python Source Operator
* \{ */
2011-11-03 23:20:54 +00:00
#ifdef WITH_PYTHON
/* ------------------------------------------------------------------------- */
2022-09-19 14:47:27 +10:00
/* EditSource Utility functions and operator,
* NOTE: this includes utility functions and button matching checks. */
struct uiEditSourceStore {
uiBut but_orig;
GHash *hash;
};
struct uiEditSourceButStore {
char py_dbg_fn[FILE_MAX];
int py_dbg_line_number;
};
/* should only ever be set while the edit source operator is running */
static uiEditSourceStore *ui_editsource_info = nullptr;
bool UI_editsource_enable_check()
{
return (ui_editsource_info != nullptr);
}
static void ui_editsource_active_but_set(uiBut *but)
{
BLI_assert(ui_editsource_info == nullptr);
ui_editsource_info = MEM_new<uiEditSourceStore>(__func__);
ui_editsource_info->but_orig = *but;
ui_editsource_info->hash = BLI_ghash_ptr_new(__func__);
}
static void ui_editsource_active_but_clear()
{
BLI_ghash_free(ui_editsource_info->hash, nullptr, MEM_freeN);
MEM_freeN(ui_editsource_info);
ui_editsource_info = nullptr;
}
static bool ui_editsource_uibut_match(uiBut *but_a, uiBut *but_b)
{
# if 0
printf("matching buttons: '%s' == '%s'\n", but_a->drawstr, but_b->drawstr);
# endif
/* this just needs to be a 'good-enough' comparison so we can know beyond
* reasonable doubt that these buttons are the same between redraws.
* if this fails it only means edit-source fails - campbell */
if (BLI_rctf_compare(&but_a->rect, &but_b->rect, FLT_EPSILON) && (but_a->type == but_b->type) &&
2012-03-30 01:51:25 +00:00
(but_a->rnaprop == but_b->rnaprop) && (but_a->optype == but_b->optype) &&
(but_a->unit_type == but_b->unit_type) && but_a->drawstr == but_b->drawstr)
{
return true;
}
return false;
}
extern "C" {
void PyC_FileAndNum_Safe(const char **r_filename, int *r_lineno);
}
void UI_editsource_active_but_test(uiBut *but)
{
uiEditSourceButStore *but_store = MEM_cnew<uiEditSourceButStore>(__func__);
const char *fn;
int line_number = -1;
# if 0
printf("comparing buttons: '%s' == '%s'\n", but->drawstr, ui_editsource_info->but_orig.drawstr);
# endif
PyC_FileAndNum_Safe(&fn, &line_number);
if (line_number != -1) {
2023-05-09 12:50:37 +10:00
STRNCPY(but_store->py_dbg_fn, fn);
but_store->py_dbg_line_number = line_number;
}
else {
2012-03-30 01:51:25 +00:00
but_store->py_dbg_fn[0] = '\0';
but_store->py_dbg_line_number = -1;
}
BLI_ghash_insert(ui_editsource_info->hash, but, but_store);
}
void UI_editsource_but_replace(const uiBut *old_but, uiBut *new_but)
{
uiEditSourceButStore *but_store = static_cast<uiEditSourceButStore *>(
BLI_ghash_lookup(ui_editsource_info->hash, old_but));
if (but_store) {
BLI_ghash_remove(ui_editsource_info->hash, old_but, nullptr, nullptr);
BLI_ghash_insert(ui_editsource_info->hash, new_but, but_store);
}
}
2015-05-05 03:13:47 +10:00
static int editsource_text_edit(bContext *C,
wmOperator * /*op*/,
const char filepath[FILE_MAX],
const int line)
{
wmOperatorType *ot = WM_operatortype_find("TEXT_OT_jump_to_file_at_point", true);
PointerRNA op_props;
WM_operator_properties_create_ptr(&op_props, ot);
RNA_string_set(&op_props, "filepath", filepath);
RNA_int_set(&op_props, "line", line - 1);
RNA_int_set(&op_props, "column", 0);
int result = WM_operator_name_call_ptr(C, ot, WM_OP_EXEC_DEFAULT, &op_props, nullptr);
WM_operator_properties_free(&op_props);
return result;
}
static int editsource_exec(bContext *C, wmOperator *op)
{
uiBut *but = UI_context_active_but_get(C);
if (but) {
GHashIterator ghi;
uiEditSourceButStore *but_store = nullptr;
ARegion *region = CTX_wm_region(C);
int ret;
/* needed else the active button does not get tested */
UI_screen_free_active_but_highlight(C, CTX_wm_screen(C));
// printf("%s: begin\n", __func__);
/* take care not to return before calling ui_editsource_active_but_clear */
ui_editsource_active_but_set(but);
/* redraw and get active button python info */
ui_region_redraw_immediately(C, region);
for (BLI_ghashIterator_init(&ghi, ui_editsource_info->hash);
BLI_ghashIterator_done(&ghi) == false;
2012-03-30 01:51:25 +00:00
BLI_ghashIterator_step(&ghi))
{
uiBut *but_key = static_cast<uiBut *>(BLI_ghashIterator_getKey(&ghi));
2012-10-12 14:35:10 +00:00
if (but_key && ui_editsource_uibut_match(&ui_editsource_info->but_orig, but_key)) {
but_store = static_cast<uiEditSourceButStore *>(BLI_ghashIterator_getValue(&ghi));
break;
}
}
if (but_store) {
if (but_store->py_dbg_line_number != -1) {
ret = editsource_text_edit(C, op, but_store->py_dbg_fn, but_store->py_dbg_line_number);
}
else {
BKE_report(
op->reports, RPT_ERROR, "Active button is not from a script, cannot edit source");
2012-03-30 01:51:25 +00:00
ret = OPERATOR_CANCELLED;
}
}
else {
BKE_report(op->reports, RPT_ERROR, "Active button match cannot be found");
2012-03-30 01:51:25 +00:00
ret = OPERATOR_CANCELLED;
}
ui_editsource_active_but_clear();
// printf("%s: end\n", __func__);
return ret;
}
BKE_report(op->reports, RPT_ERROR, "Active button not found");
return OPERATOR_CANCELLED;
}
static void UI_OT_editsource(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Edit Source";
ot->idname = "UI_OT_editsource";
ot->description = "Edit UI source code of the active button";
/* callbacks */
ot->exec = editsource_exec;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Edit Translation Operator
* \{ */
2015-05-31 14:20:03 +10:00
/**
2022-09-19 14:47:27 +10:00
* EditTranslation utility functions and operator.
*
* \note this includes utility functions and button matching checks.
* this only works in conjunction with a Python operator!
2015-05-31 14:20:03 +10:00
*/
static void edittranslation_find_po_file(const char *root,
const char *uilng,
char *path,
const size_t path_maxncpy)
{
char tstr[32]; /* Should be more than enough! */
/* First, full lang code. */
2023-05-09 12:50:37 +10:00
SNPRINTF(tstr, "%s.po", uilng);
BLI_path_join(path, path_maxncpy, root, uilng, tstr);
2019-03-25 10:15:20 +11:00
if (BLI_is_file(path)) {
return;
2019-03-25 10:15:20 +11:00
}
/* Now try without the second ISO code part (`_BR` in `pt_BR`). */
{
const char *tc = nullptr;
size_t szt = 0;
tstr[0] = '\0';
tc = strchr(uilng, '_');
if (tc) {
szt = tc - uilng;
if (szt < sizeof(tstr)) { /* Paranoid, should always be true! */
BLI_strncpy(tstr, uilng, szt + 1); /* +1 for '\0' char! */
}
}
if (tstr[0]) {
/* Because of some codes like sr_SR@latin... */
tc = strchr(uilng, '@');
2019-03-25 10:15:20 +11:00
if (tc) {
BLI_strncpy(tstr + szt, tc, sizeof(tstr) - szt);
2019-03-25 10:15:20 +11:00
}
BLI_path_join(path, path_maxncpy, root, tstr);
BLI_strncat(tstr, ".po", sizeof(tstr));
BLI_path_append(path, path_maxncpy, tstr);
2019-03-25 10:15:20 +11:00
if (BLI_is_file(path)) {
return;
2019-03-25 10:15:20 +11:00
}
}
}
/* Else no po file! */
path[0] = '\0';
}
static int edittranslation_exec(bContext *C, wmOperator *op)
{
uiBut *but = UI_context_active_but_get(C);
if (but == nullptr) {
BKE_report(op->reports, RPT_ERROR, "Active button not found");
return OPERATOR_CANCELLED;
}
wmOperatorType *ot;
PointerRNA ptr;
char popath[FILE_MAX];
const char *root = U.i18ndir;
const char *uilng = BLT_lang_get();
if (!BLI_is_dir(root)) {
BKE_report(op->reports,
RPT_ERROR,
"Please set your Preferences' 'Translation Branches "
"Directory' path to a valid directory");
return OPERATOR_CANCELLED;
}
ot = WM_operatortype_find(EDTSRC_I18N_OP_NAME, false);
if (ot == nullptr) {
BKE_reportf(op->reports,
RPT_ERROR,
"Could not find operator '%s'! Please enable ui_translate add-on "
"in the User Preferences",
EDTSRC_I18N_OP_NAME);
return OPERATOR_CANCELLED;
}
/* Try to find a valid po file for current language... */
edittranslation_find_po_file(root, uilng, popath, FILE_MAX);
// printf("po path: %s\n", popath);
if (popath[0] == '\0') {
BKE_reportf(
op->reports, RPT_ERROR, "No valid po found for language '%s' under %s", uilng, root);
return OPERATOR_CANCELLED;
}
WM_operator_properties_create_ptr(&ptr, ot);
RNA_string_set(&ptr, "lang", uilng);
RNA_string_set(&ptr, "po_file", popath);
const EnumPropertyItem enum_item = UI_but_rna_enum_item_get(*C, *but).value_or(
EnumPropertyItem{});
RNA_string_set(&ptr, "enum_label", enum_item.name);
RNA_string_set(&ptr, "enum_tip", enum_item.description);
RNA_string_set(&ptr, "rna_enum", enum_item.identifier);
RNA_string_set(&ptr, "but_label", UI_but_string_get_label(*but).c_str());
RNA_string_set(&ptr, "rna_label", UI_but_string_get_rna_label(*but).c_str());
RNA_string_set(&ptr, "but_tip", UI_but_string_get_tooltip(*C, *but).c_str());
RNA_string_set(&ptr, "rna_tip", UI_but_string_get_rna_tooltip(*C, *but).c_str());
RNA_string_set(&ptr, "rna_struct", UI_but_string_get_rna_struct_identifier(*but).c_str());
RNA_string_set(&ptr, "rna_prop", UI_but_string_get_rna_property_identifier(*but).c_str());
RNA_string_set(&ptr, "rna_ctxt", UI_but_string_get_rna_label_context(*but).c_str());
const int ret = WM_operator_name_call_ptr(C, ot, WM_OP_INVOKE_DEFAULT, &ptr, nullptr);
return ret;
}
static void UI_OT_edittranslation_init(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Edit Translation";
ot->idname = "UI_OT_edittranslation_init";
ot->description = "Edit i18n in current language for the active button";
/* callbacks */
ot->exec = edittranslation_exec;
}
2011-11-03 23:20:54 +00:00
#endif /* WITH_PYTHON */
/** \} */
/* -------------------------------------------------------------------- */
/** \name Reload Translation Operator
* \{ */
static int reloadtranslation_exec(bContext * /*C*/, wmOperator * /*op*/)
{
BLT_lang_init();
BLF_cache_clear();
BLT_lang_set(nullptr);
UI_reinit_font();
return OPERATOR_FINISHED;
}
static void UI_OT_reloadtranslation(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Reload Translation";
ot->idname = "UI_OT_reloadtranslation";
ot->description = "Force a full reload of UI translation";
/* callbacks */
ot->exec = reloadtranslation_exec;
}
/** \} */
/* -------------------------------------------------------------------- */
2019-06-16 13:37:21 +10:00
/** \name Press Button Operator
* \{ */
static int ui_button_press_invoke(bContext *C, wmOperator *op, const wmEvent *event)
{
bScreen *screen = CTX_wm_screen(C);
const bool skip_depressed = RNA_boolean_get(op->ptr, "skip_depressed");
ARegion *region_prev = CTX_wm_region(C);
ARegion *region = screen ? BKE_screen_find_region_xy(screen, RGN_TYPE_ANY, event->xy) : nullptr;
if (region == nullptr) {
region = region_prev;
}
if (region == nullptr) {
return OPERATOR_PASS_THROUGH;
}
CTX_wm_region_set(C, region);
uiBut *but = UI_context_active_but_get(C);
CTX_wm_region_set(C, region_prev);
if (but == nullptr) {
return OPERATOR_PASS_THROUGH;
}
if (skip_depressed && (but->flag & (UI_SELECT | UI_SELECT_DRAW))) {
return OPERATOR_PASS_THROUGH;
}
/* Weak, this is a workaround for 'UI_but_is_tool', which checks the operator type,
* having this avoids a minor drawing glitch. */
void *but_optype = but->optype;
UI_but_execute(C, region, but);
but->optype = static_cast<wmOperatorType *>(but_optype);
WM_event_add_mousemove(CTX_wm_window(C));
return OPERATOR_FINISHED;
}
static void UI_OT_button_execute(wmOperatorType *ot)
{
ot->name = "Press Button";
ot->idname = "UI_OT_button_execute";
ot->description = "Presses active button";
ot->invoke = ui_button_press_invoke;
ot->flag = OPTYPE_INTERNAL;
RNA_def_boolean(ot->srna, "skip_depressed", false, "Skip Depressed", "");
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Text Button Clear Operator
* \{ */
static int button_string_clear_exec(bContext *C, wmOperator * /*op*/)
{
uiBut *but = UI_context_active_but_get_respect_popup(C);
if (but) {
ui_but_active_string_clear_and_exit(C, but);
}
return OPERATOR_FINISHED;
}
static void UI_OT_button_string_clear(wmOperatorType *ot)
{
ot->name = "Clear Button String";
ot->idname = "UI_OT_button_string_clear";
ot->description = "Unsets the text of the active button";
ot->poll = ED_operator_regionactive;
ot->exec = button_string_clear_exec;
ot->flag = OPTYPE_UNDO | OPTYPE_INTERNAL;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Drop Color Operator
* \{ */
bool UI_drop_color_poll(bContext *C, wmDrag *drag, const wmEvent * /*event*/)
{
/* should only return true for regions that include buttons, for now
* return true always */
if (drag->type == WM_DRAG_COLOR) {
SpaceImage *sima = CTX_wm_space_image(C);
ARegion *region = CTX_wm_region(C);
2019-03-25 10:15:20 +11:00
if (UI_but_active_drop_color(C)) {
return true;
2019-03-25 10:15:20 +11:00
}
if (sima && (sima->mode == SI_MODE_PAINT) && sima->image &&
(region && region->regiontype == RGN_TYPE_WINDOW))
{
return true;
}
}
return false;
}
void UI_drop_color_copy(bContext * /*C*/, wmDrag *drag, wmDropBox *drop)
{
uiDragColorHandle *drag_info = static_cast<uiDragColorHandle *>(drag->poin);
RNA_float_set_array(drop->ptr, "color", drag_info->color);
RNA_boolean_set(drop->ptr, "gamma", drag_info->gamma_corrected);
}
Add support for tiled images and the UDIM naming scheme This patch contains the work that I did during my week at the Code Quest - adding support for tiled images to Blender. With this patch, images now contain a list of tiles. By default, this just contains one tile, but if the source type is set to Tiled, the user can add additional tiles. When acquiring an ImBuf, the tile to be loaded is specified in the ImageUser. Therefore, code that is not yet aware of tiles will just access the default tile as usual. The filenames of the additional tiles are derived from the original filename according to the UDIM naming scheme - the filename contains an index that is calculated as (1001 + 10*<y coordinate of the tile> + <x coordinate of the tile>), where the x coordinate never goes above 9. Internally, the various tiles are stored in a cache just like sequences. When acquired for the first time, the code will try to load the corresponding file from disk. Alternatively, a new operator can be used to initialize the tile similar to the New Image operator. The following features are supported so far: - Automatic detection and loading of all tiles when opening the first tile (1001) - Saving all tiles - Adding and removing tiles - Filling tiles with generated images - Drawing all tiles in the Image Editor - Viewing a tiled grid even if no image is selected - Rendering tiled images in Eevee - Rendering tiled images in Cycles (in SVM mode) - Automatically skipping loading of unused tiles in Cycles - 2D texture painting (also across tiles) - 3D texture painting (also across tiles, only limitation: individual faces can not cross tile borders) - Assigning custom labels to individual tiles (drawn in the Image Editor instead of the ID) - Different resolutions between tiles There still are some missing features that will be added later (see T72390): - Workbench engine support - Packing/Unpacking support - Baking support - Cycles OSL support - many other Blender features that rely on images Thanks to Brecht for the review and to all who tested the intermediate versions! Differential Revision: https://developer.blender.org/D3509
2019-12-12 16:06:08 +01:00
static int drop_color_invoke(bContext *C, wmOperator *op, const wmEvent *event)
{
ARegion *region = CTX_wm_region(C);
uiBut *but = nullptr;
float color[4];
bool gamma;
RNA_float_get_array(op->ptr, "color", color);
gamma = RNA_boolean_get(op->ptr, "gamma");
/* find button under mouse, check if it has RNA color property and
* if it does copy the data */
but = ui_region_find_active_but(region);
if (but && but->type == UI_BTYPE_COLOR && but->rnaprop) {
const int color_len = RNA_property_array_length(&but->rnapoin, but->rnaprop);
BLI_assert(color_len <= 4);
/* keep alpha channel as-is */
if (color_len == 4) {
color[3] = RNA_property_float_get_index(&but->rnapoin, but->rnaprop, 3);
}
if (RNA_property_subtype(but->rnaprop) == PROP_COLOR_GAMMA) {
2019-03-25 10:15:20 +11:00
if (!gamma) {
IMB_colormanagement_scene_linear_to_srgb_v3(color, color);
2019-03-25 10:15:20 +11:00
}
RNA_property_float_set_array(&but->rnapoin, but->rnaprop, color);
RNA_property_update(C, &but->rnapoin, but->rnaprop);
}
else if (RNA_property_subtype(but->rnaprop) == PROP_COLOR) {
2019-03-25 10:15:20 +11:00
if (gamma) {
IMB_colormanagement_srgb_to_scene_linear_v3(color, color);
2019-03-25 10:15:20 +11:00
}
RNA_property_float_set_array(&but->rnapoin, but->rnaprop, color);
RNA_property_update(C, &but->rnapoin, but->rnaprop);
}
if (UI_but_flag_is_set(but, UI_BUT_UNDO)) {
ED_undo_push(C, RNA_property_ui_name(but->rnaprop));
}
}
else {
if (gamma) {
srgb_to_linearrgb_v3_v3(color, color);
}
Add support for tiled images and the UDIM naming scheme This patch contains the work that I did during my week at the Code Quest - adding support for tiled images to Blender. With this patch, images now contain a list of tiles. By default, this just contains one tile, but if the source type is set to Tiled, the user can add additional tiles. When acquiring an ImBuf, the tile to be loaded is specified in the ImageUser. Therefore, code that is not yet aware of tiles will just access the default tile as usual. The filenames of the additional tiles are derived from the original filename according to the UDIM naming scheme - the filename contains an index that is calculated as (1001 + 10*<y coordinate of the tile> + <x coordinate of the tile>), where the x coordinate never goes above 9. Internally, the various tiles are stored in a cache just like sequences. When acquired for the first time, the code will try to load the corresponding file from disk. Alternatively, a new operator can be used to initialize the tile similar to the New Image operator. The following features are supported so far: - Automatic detection and loading of all tiles when opening the first tile (1001) - Saving all tiles - Adding and removing tiles - Filling tiles with generated images - Drawing all tiles in the Image Editor - Viewing a tiled grid even if no image is selected - Rendering tiled images in Eevee - Rendering tiled images in Cycles (in SVM mode) - Automatically skipping loading of unused tiles in Cycles - 2D texture painting (also across tiles) - 3D texture painting (also across tiles, only limitation: individual faces can not cross tile borders) - Assigning custom labels to individual tiles (drawn in the Image Editor instead of the ID) - Different resolutions between tiles There still are some missing features that will be added later (see T72390): - Workbench engine support - Packing/Unpacking support - Baking support - Cycles OSL support - many other Blender features that rely on images Thanks to Brecht for the review and to all who tested the intermediate versions! Differential Revision: https://developer.blender.org/D3509
2019-12-12 16:06:08 +01:00
ED_imapaint_bucket_fill(C, color, op, event->mval);
}
ED_region_tag_redraw(region);
return OPERATOR_FINISHED;
}
static void UI_OT_drop_color(wmOperatorType *ot)
{
ot->name = "Drop Color";
ot->idname = "UI_OT_drop_color";
ot->description = "Drop colors to buttons";
ot->invoke = drop_color_invoke;
ot->poll = ED_operator_regionactive;
ot->flag = OPTYPE_INTERNAL;
RNA_def_float_color(
ot->srna, "color", 3, nullptr, 0.0, FLT_MAX, "Color", "Source color", 0.0, 1.0);
RNA_def_boolean(
ot->srna, "gamma", false, "Gamma Corrected", "The source color is gamma corrected");
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Drop Name Operator
* \{ */
static bool drop_name_poll(bContext *C)
{
if (!ED_operator_regionactive(C)) {
return false;
}
const uiBut *but = UI_but_active_drop_name_button(C);
if (!but) {
return false;
}
if (but->flag & UI_BUT_DISABLED) {
return false;
}
return true;
}
static int drop_name_invoke(bContext *C, wmOperator *op, const wmEvent * /*event*/)
{
uiBut *but = UI_but_active_drop_name_button(C);
char *str = RNA_string_get_alloc(op->ptr, "string", nullptr, 0, nullptr);
if (str) {
ui_but_set_string_interactive(C, but, str);
MEM_freeN(str);
}
return OPERATOR_FINISHED;
}
static void UI_OT_drop_name(wmOperatorType *ot)
{
ot->name = "Drop Name";
ot->idname = "UI_OT_drop_name";
ot->description = "Drop name to button";
ot->poll = drop_name_poll;
ot->invoke = drop_name_invoke;
ot->flag = OPTYPE_UNDO | OPTYPE_INTERNAL;
RNA_def_string(
ot->srna, "string", nullptr, 0, "String", "The string value to drop into the button");
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name UI List Search Operator
* \{ */
static bool ui_list_focused_poll(bContext *C)
{
const ARegion *region = CTX_wm_region(C);
if (!region) {
return false;
}
const wmWindow *win = CTX_wm_window(C);
const uiList *list = UI_list_find_mouse_over(region, win->eventstate);
return list != nullptr;
}
/**
* Ensure the filter options are set to be visible in the UI list.
* \return if the visibility changed, requiring a redraw.
*/
static bool ui_list_unhide_filter_options(uiList *list)
{
if (list->filter_flag & UILST_FLT_SHOW) {
/* Nothing to be done. */
return false;
}
list->filter_flag |= UILST_FLT_SHOW;
return true;
}
static int ui_list_start_filter_invoke(bContext *C, wmOperator * /*op*/, const wmEvent *event)
{
ARegion *region = CTX_wm_region(C);
uiList *list = UI_list_find_mouse_over(region, event);
/* Poll should check. */
BLI_assert(list != nullptr);
if (ui_list_unhide_filter_options(list)) {
ui_region_redraw_immediately(C, region);
}
if (!UI_textbutton_activate_rna(C, region, list, "filter_name")) {
return OPERATOR_CANCELLED;
}
return OPERATOR_FINISHED;
}
static void UI_OT_list_start_filter(wmOperatorType *ot)
{
ot->name = "List Filter";
ot->idname = "UI_OT_list_start_filter";
ot->description = "Start entering filter text for the list in focus";
ot->invoke = ui_list_start_filter_invoke;
ot->poll = ui_list_focused_poll;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name UI View Start Filter Operator
* \{ */
static bool ui_view_focused_poll(bContext *C)
{
const wmWindow *win = CTX_wm_window(C);
if (!(win && win->eventstate)) {
return false;
}
const ARegion *region = CTX_wm_region(C);
if (!region) {
return false;
}
const blender::ui::AbstractView *view = UI_region_view_find_at(region, win->eventstate->xy, 0);
return view != nullptr;
}
static int ui_view_start_filter_invoke(bContext *C, wmOperator * /*op*/, const wmEvent *event)
{
const ARegion *region = CTX_wm_region(C);
const blender::ui::AbstractView *hovered_view = UI_region_view_find_at(region, event->xy, 0);
if (!hovered_view->begin_filtering(*C)) {
return OPERATOR_CANCELLED | OPERATOR_PASS_THROUGH;
}
return OPERATOR_FINISHED;
}
static void UI_OT_view_start_filter(wmOperatorType *ot)
{
ot->name = "View Filter";
ot->idname = "UI_OT_view_start_filter";
ot->description = "Start entering filter text for the data-set in focus";
ot->invoke = ui_view_start_filter_invoke;
ot->poll = ui_view_focused_poll;
ot->flag = OPTYPE_INTERNAL;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name UI View Drop Operator
* \{ */
static bool ui_view_drop_poll(bContext *C)
{
const wmWindow *win = CTX_wm_window(C);
if (!(win && win->eventstate)) {
return false;
}
const ARegion *region = CTX_wm_region(C);
2022-08-28 20:19:05 +10:00
if (region == nullptr) {
return false;
}
return region_views_find_drop_target_at(region, win->eventstate->xy) != nullptr;
}
static int ui_view_drop_invoke(bContext *C, wmOperator * /*op*/, const wmEvent *event)
{
if (event->custom != EVT_DATA_DRAGDROP) {
return OPERATOR_CANCELLED | OPERATOR_PASS_THROUGH;
}
UI: Basic tree-view drag & drop reordering and inserting support No user visible changes expected, these are just the internal API preparations. Modifies the Drop API for views so that tree-views can choose to insert items before, after and into other items. Note: While there is support for drag-tooltips that can explain how an item will be inserted, there is no drawing yet like in the Outliner, that indicates if an item is inserted before, after or into. There is some work on that but that can be done separately. Changes: - Removes `AbstractViewDropTarget` that was shared between tree- and grid-views, and adds `AbstractTreeViewDropTarget` and `AbstractGridViewDropTarget`. The tree-view needs specialized handling now, and although they could share some code still, it's not worth having another level of inheritance. - Modifies the drop-target API to use `DragInfo` which contains more info about the dragging operation than just the `wmDrag`. - Adds `determine_drop_location()` to the `DropTargetInterface` which drop targets can use to determine when dropping means inserting before, after or into. - Store the block and region in the view. This is needed unfortunately but shouldn't be an issue since the tree view is recreated on redraws, together with the block. - Various smaller tweaks and additions to views as needed. TODO (outside scope of this change): Increase row height so there is no gap between tree view items, but keep things visually the same otherwise. This reduces flickering while dragging. Pull Request: https://projects.blender.org/blender/blender/pulls/109825
2023-07-11 14:30:26 +02:00
ARegion *region = CTX_wm_region(C);
2023-03-23 14:28:50 +11:00
std::unique_ptr<DropTargetInterface> drop_target = region_views_find_drop_target_at(region,
event->xy);
UI: Basic tree-view drag & drop reordering and inserting support No user visible changes expected, these are just the internal API preparations. Modifies the Drop API for views so that tree-views can choose to insert items before, after and into other items. Note: While there is support for drag-tooltips that can explain how an item will be inserted, there is no drawing yet like in the Outliner, that indicates if an item is inserted before, after or into. There is some work on that but that can be done separately. Changes: - Removes `AbstractViewDropTarget` that was shared between tree- and grid-views, and adds `AbstractTreeViewDropTarget` and `AbstractGridViewDropTarget`. The tree-view needs specialized handling now, and although they could share some code still, it's not worth having another level of inheritance. - Modifies the drop-target API to use `DragInfo` which contains more info about the dragging operation than just the `wmDrag`. - Adds `determine_drop_location()` to the `DropTargetInterface` which drop targets can use to determine when dropping means inserting before, after or into. - Store the block and region in the view. This is needed unfortunately but shouldn't be an issue since the tree view is recreated on redraws, together with the block. - Various smaller tweaks and additions to views as needed. TODO (outside scope of this change): Increase row height so there is no gap between tree view items, but keep things visually the same otherwise. This reduces flickering while dragging. Pull Request: https://projects.blender.org/blender/blender/pulls/109825
2023-07-11 14:30:26 +02:00
if (!drop_target_apply_drop(
*C, *region, *event, *drop_target, *static_cast<const ListBase *>(event->customdata)))
{
return OPERATOR_CANCELLED | OPERATOR_PASS_THROUGH;
}
UI: Basic tree-view drag & drop reordering and inserting support No user visible changes expected, these are just the internal API preparations. Modifies the Drop API for views so that tree-views can choose to insert items before, after and into other items. Note: While there is support for drag-tooltips that can explain how an item will be inserted, there is no drawing yet like in the Outliner, that indicates if an item is inserted before, after or into. There is some work on that but that can be done separately. Changes: - Removes `AbstractViewDropTarget` that was shared between tree- and grid-views, and adds `AbstractTreeViewDropTarget` and `AbstractGridViewDropTarget`. The tree-view needs specialized handling now, and although they could share some code still, it's not worth having another level of inheritance. - Modifies the drop-target API to use `DragInfo` which contains more info about the dragging operation than just the `wmDrag`. - Adds `determine_drop_location()` to the `DropTargetInterface` which drop targets can use to determine when dropping means inserting before, after or into. - Store the block and region in the view. This is needed unfortunately but shouldn't be an issue since the tree view is recreated on redraws, together with the block. - Various smaller tweaks and additions to views as needed. TODO (outside scope of this change): Increase row height so there is no gap between tree view items, but keep things visually the same otherwise. This reduces flickering while dragging. Pull Request: https://projects.blender.org/blender/blender/pulls/109825
2023-07-11 14:30:26 +02:00
ED_region_tag_redraw(region);
return OPERATOR_FINISHED;
}
static void UI_OT_view_drop(wmOperatorType *ot)
{
ot->name = "View Drop";
ot->idname = "UI_OT_view_drop";
ot->description = "Drag and drop onto a data-set or item within the data-set";
ot->invoke = ui_view_drop_invoke;
ot->poll = ui_view_drop_poll;
ot->flag = OPTYPE_INTERNAL;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name UI View Item Rename Operator
*
* General purpose renaming operator for views. Thanks to this, to add a rename button to context
* menus for example, view API users don't have to implement their own renaming operators with the
* same logic as they already have for their #ui::AbstractViewItem::rename() override.
*
* \{ */
static bool ui_view_item_rename_poll(bContext *C)
{
const ARegion *region = CTX_wm_region(C);
2022-08-28 20:19:05 +10:00
if (region == nullptr) {
return false;
}
const blender::ui::AbstractViewItem *active_item = UI_region_views_find_active_item(region);
return active_item != nullptr && UI_view_item_can_rename(*active_item);
}
static int ui_view_item_rename_exec(bContext *C, wmOperator * /*op*/)
{
ARegion *region = CTX_wm_region(C);
blender::ui::AbstractViewItem *active_item = UI_region_views_find_active_item(region);
UI_view_item_begin_rename(*active_item);
ED_region_tag_redraw(region);
return OPERATOR_FINISHED;
}
static void UI_OT_view_item_rename(wmOperatorType *ot)
{
ot->name = "Rename View Item";
ot->idname = "UI_OT_view_item_rename";
ot->description = "Rename the active item in the data-set view";
ot->exec = ui_view_item_rename_exec;
ot->poll = ui_view_item_rename_poll;
/* Could get a custom tooltip via the `get_description()` callback and another overridable
* function of the view. */
ot->flag = OPTYPE_INTERNAL;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Material Drag/Drop Operator
*
* \{ */
static bool ui_drop_material_poll(bContext *C)
{
PointerRNA ptr = CTX_data_pointer_get_type(C, "object", &RNA_Object);
const Object *ob = static_cast<const Object *>(ptr.data);
if (ob == nullptr) {
return false;
}
PointerRNA mat_slot = CTX_data_pointer_get_type(C, "material_slot", &RNA_MaterialSlot);
if (RNA_pointer_is_null(&mat_slot)) {
return false;
}
return true;
}
static int ui_drop_material_exec(bContext *C, wmOperator *op)
{
Main *bmain = CTX_data_main(C);
Material *ma = (Material *)WM_operator_properties_id_lookup_from_name_or_session_uid(
bmain, op->ptr, ID_MA);
if (ma == nullptr) {
return OPERATOR_CANCELLED;
}
PointerRNA ptr = CTX_data_pointer_get_type(C, "object", &RNA_Object);
Object *ob = static_cast<Object *>(ptr.data);
BLI_assert(ob);
PointerRNA mat_slot = CTX_data_pointer_get_type(C, "material_slot", &RNA_MaterialSlot);
BLI_assert(mat_slot.data);
const int target_slot = RNA_int_get(&mat_slot, "slot_index") + 1;
/* only drop grease pencil material on grease pencil objects */
if ((ma->gp_style != nullptr) && (ob->type != OB_GPENCIL_LEGACY)) {
return OPERATOR_CANCELLED;
}
BKE_object_material_assign(bmain, ob, ma, target_slot, BKE_MAT_ASSIGN_USERPREF);
WM_event_add_notifier(C, NC_OBJECT | ND_OB_SHADING, ob);
WM_event_add_notifier(C, NC_SPACE | ND_SPACE_VIEW3D, nullptr);
WM_event_add_notifier(C, NC_MATERIAL | ND_SHADING_LINKS, ma);
DEG_id_tag_update(&ob->id, ID_RECALC_TRANSFORM);
return OPERATOR_FINISHED;
}
static void UI_OT_drop_material(wmOperatorType *ot)
{
ot->name = "Drop Material in Material slots";
ot->description = "Drag material to Material slots in Properties";
ot->idname = "UI_OT_drop_material";
ot->poll = ui_drop_material_poll;
ot->exec = ui_drop_material_exec;
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_INTERNAL;
WM_operator_properties_id_lookup(ot, false);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Operator & Keymap Registration
* \{ */
void ED_operatortypes_ui()
{
using namespace blender::ui;
WM_operatortype_append(UI_OT_copy_data_path_button);
WM_operatortype_append(UI_OT_copy_as_driver_button);
WM_operatortype_append(UI_OT_copy_python_command_button);
WM_operatortype_append(UI_OT_reset_default_button);
WM_operatortype_append(UI_OT_assign_default_button);
Fix #36226, Select Linked works not in touch with Prefs. When setting keymap properties to values equalling the RNA default, they will get "unset" and automatic operator behavior is used. There is no way to explicitly set the default value as a user. 1) To allow distinguishing uninitialized (not set) properties in the keymap items, a few changes to the RNA struct comparison function are needed: Instead of allowing only strict/non-strict comparison of 2 properties A and B in a struct, this now has 3 modes: * STRICT: compare only the actual property values (same as 'strict' before) * UNSET_MATCH_ANY: if either A or B is unset, consider them a match (same as non-strict before) * UNSET_MATCH_NONE: if one property is set and the other not, consider them a mismatch. The new UNSET_MATCH_NONE mode is useful for keymaps, because it allows keeping user-defined property values in the keymap even if they match the default property value (see wm_keymap_diff function in wm_keymap.c) 2) A new operator is added for unsetting ID properties in the RMB context menu and in user preferences next to keymap properties. This only works on ID properties and deletes the ID property storage, so that the default value is used. In the user preferences for keymaps the properties are shown in an inactive layout to indicate that the default value is used (which some operators such as the "select linked" op from the report use to trigger automatic behavior). When the user sets a property it gets set and stays that way until explicitly "unset" using the new operator.
2013-09-20 09:10:17 +00:00
WM_operatortype_append(UI_OT_unset_property_button);
WM_operatortype_append(UI_OT_copy_to_selected_button);
WM_operatortype_append(UI_OT_copy_driver_to_selected_button);
WM_operatortype_append(UI_OT_jump_to_target_button);
WM_operatortype_append(UI_OT_drop_color);
WM_operatortype_append(UI_OT_drop_name);
WM_operatortype_append(UI_OT_drop_material);
2011-11-03 23:20:54 +00:00
#ifdef WITH_PYTHON
WM_operatortype_append(UI_OT_editsource);
WM_operatortype_append(UI_OT_edittranslation_init);
2011-11-03 23:20:54 +00:00
#endif
WM_operatortype_append(UI_OT_reloadtranslation);
WM_operatortype_append(UI_OT_button_execute);
WM_operatortype_append(UI_OT_button_string_clear);
WM_operatortype_append(UI_OT_list_start_filter);
WM_operatortype_append(UI_OT_view_start_filter);
WM_operatortype_append(UI_OT_view_drop);
WM_operatortype_append(UI_OT_view_item_rename);
WM_operatortype_append(UI_OT_override_type_set_button);
WM_operatortype_append(UI_OT_override_remove_button);
WM_operatortype_append(UI_OT_override_idtemplate_make);
WM_operatortype_append(UI_OT_override_idtemplate_reset);
WM_operatortype_append(UI_OT_override_idtemplate_clear);
override_idtemplate_menu();
/* external */
WM_operatortype_append(UI_OT_eyedropper_color);
WM_operatortype_append(UI_OT_eyedropper_colorramp);
WM_operatortype_append(UI_OT_eyedropper_colorramp_point);
WM_operatortype_append(UI_OT_eyedropper_id);
WM_operatortype_append(UI_OT_eyedropper_depth);
Driver Setup Workflow Improvement: Property Eyedropper This commit brings some long requested improvements to the workflow for setting up drivers, which should make it easier and faster to set up new drivers in a more interactive fashion. The new workflow is as follows: 1) Hover over the property (e.g. "Lamp Energy" or "Y Location") or properties ("Rotation") you wish to add drivers to. We'll refer to this as the "destination" 2) Ctrl-D to active the new "Add Drivers" eyedropper 3) Click on the property you want to use as the source/target. The property under the mouse will be used to drive the property you invoked Ctrl-D on. For example, to drive the X, Y, and Z location of the Cube using the Y Location of the Lamp, hover over any of the X/Y/Z location buttons, hit Ctrl-D, then click on the Y-Location button of the Lamp object. Drivers will be added to the X, Y, and Z Location properties of the Cube; each driver will have a single variable, which uses the Y-Location Transform Channel of the Lamp. Tips: - Transform properties will automatically create "Transform Channel" driver variables. Everything else will use "Single Property" ones - Due to the way that Blender's UI Context works, you'll need two Properties Panel instances open (and to have pinned one of the two to show the properties for the unselected object). It's slightly clunky, but necessary for implementing a workflow like this, as the UI cannot be manipulated while using eyedroppers to pick data. - The eyedropper operator implemented here actually has three modes of operation. 1) The "1-N" (one to many) mode is the default used for Ctrl-D, and "Add Driver to All" in the RMB Menu. This is the behaviour described above. 2) There's also a "1-1" (one to one) mode that is used for the "Add Single Driver" in the RMB Menu. 3) Finally, there's the "N-N" mode (many to many), which isn't currently exposed. The point of this is to allow mapping XYZ to XYZ elementwise (i.e. direct copying) which is useful for things like locations, rotations, scaling, and colours. Implementation Notes: - The bulk of the driver adding logic is in editors/animation/drivers.c, where most of the Driver UI operators and tools are defined - The property eyedropper code is in interface_eyedropper.c along with all the other eyedroppers (even though they don't share much actual code in common). However, this turns out to be necessary, as we can't get access to many of the low-level buttons API's otherwise. Todo: - It may be necessary to restore a way to access the old behaviour (i.e. "manual setup") in case it is not practical to immediately pick a property. - Other things to investigate here include extra hotkeys (e.g. Ctrl-Shift-D for Add Single?), and to expose the N-N mode. - Other things we could try include interactively applying scaling factors, picking multiple targets (e.g. for location difference and rotation difference drivers), and/or other ways of using these property picking methods.
2016-03-26 17:55:42 +13:00
WM_operatortype_append(UI_OT_eyedropper_driver);
WM_operatortype_append(UI_OT_eyedropper_gpencil_color);
}
void ED_keymap_ui(wmKeyConfig *keyconf)
{
WM_keymap_ensure(keyconf, "User Interface", SPACE_EMPTY, RGN_TYPE_WINDOW);
eyedropper_modal_keymap(keyconf);
eyedropper_colorband_modal_keymap(keyconf);
}
/** \} */