parent
1f79132287
commit
517dc644f1
|
@ -2365,6 +2365,7 @@ void uiTemplateEditModeSelection(uiLayout *layout, struct bContext *C);
|
|||
void uiTemplateReportsBanner(uiLayout *layout, struct bContext *C);
|
||||
void uiTemplateInputStatus(uiLayout *layout, struct bContext *C);
|
||||
void uiTemplateKeymapItemProperties(uiLayout *layout, struct PointerRNA *ptr);
|
||||
void uiTemplateXrActionmapItemProperties(uiLayout *layout, struct PointerRNA *ptr);
|
||||
|
||||
bool uiTemplateEventFromKeymapItem(struct uiLayout *layout,
|
||||
const char *text,
|
||||
|
|
|
@ -102,5 +102,8 @@ if(WIN32 OR APPLE)
|
|||
endif()
|
||||
endif()
|
||||
|
||||
if(WITH_XR_OPENXR)
|
||||
add_definitions(-DWITH_XR_OPENXR)
|
||||
endif()
|
||||
|
||||
blender_add_lib(bf_editor_interface "${SRC}" "${INC}" "${INC_SYS}" "${LIB}")
|
||||
|
|
|
@ -6151,6 +6151,44 @@ void uiTemplateKeymapItemProperties(uiLayout *layout, PointerRNA *ptr)
|
|||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name XR Actionmap Template
|
||||
* \{ */
|
||||
|
||||
#ifdef WITH_XR_OPENXR
|
||||
static void xr_actionmap_item_modified(bContext *UNUSED(C),
|
||||
void *UNUSED(ami_p),
|
||||
void *UNUSED(unused))
|
||||
{
|
||||
}
|
||||
#endif
|
||||
|
||||
void uiTemplateXrActionmapItemProperties(uiLayout *layout, PointerRNA *ptr)
|
||||
{
|
||||
#ifdef WITH_XR_OPENXR
|
||||
PointerRNA propptr = RNA_pointer_get(ptr, "op_properties");
|
||||
|
||||
if (propptr.data) {
|
||||
uiBut *but = uiLayoutGetBlock(layout)->buttons.last;
|
||||
|
||||
WM_operator_properties_sanitize(&propptr, false);
|
||||
/* Use same template as keymap item properties. */
|
||||
template_keymap_item_properties(layout, NULL, &propptr);
|
||||
|
||||
for (; but; but = but->next) {
|
||||
if (but->rnaprop) {
|
||||
UI_but_func_set(but, xr_actionmap_item_modified, ptr->data, NULL);
|
||||
UI_but_flag_enable(but, UI_BUT_UPDATE_DELAY);
|
||||
}
|
||||
}
|
||||
}
|
||||
#else
|
||||
UNUSED_VARS(layout, ptr);
|
||||
#endif
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name Event Icon Template
|
||||
* \{ */
|
||||
|
|
|
@ -36,6 +36,11 @@ typedef struct XrSessionSettings {
|
|||
float clip_start, clip_end;
|
||||
|
||||
int flag;
|
||||
|
||||
ListBase actionmaps; /* XrActionMap */
|
||||
short actactionmap;
|
||||
short selactionmap;
|
||||
char _pad3[4];
|
||||
} XrSessionSettings;
|
||||
|
||||
typedef enum eXrSessionFlag {
|
||||
|
@ -137,7 +142,8 @@ typedef struct XrActionMapBinding {
|
|||
/** Input threshold/region. */
|
||||
float float_threshold;
|
||||
short axis_flag; /* eXrAxisFlag */
|
||||
char _pad[2];
|
||||
|
||||
short sel_component_path;
|
||||
|
||||
/** Pose action properties. */
|
||||
float pose_location[3];
|
||||
|
@ -183,8 +189,8 @@ typedef struct XrActionMapItem {
|
|||
float haptic_frequency;
|
||||
float haptic_amplitude;
|
||||
|
||||
short selbinding;
|
||||
char _pad3[2];
|
||||
short sel_user_path;
|
||||
short sel_binding;
|
||||
ListBase bindings; /* XrActionMapBinding */
|
||||
} XrActionMapItem;
|
||||
|
||||
|
@ -197,7 +203,7 @@ typedef struct XrActionMap {
|
|||
char name[64]; /* MAX_NAME */
|
||||
|
||||
ListBase items; /* XrActionMapItem */
|
||||
short selitem;
|
||||
short sel_item;
|
||||
char _pad[6];
|
||||
} XrActionMap;
|
||||
|
||||
|
|
|
@ -1789,6 +1789,11 @@ void RNA_api_ui_layout(StructRNA *srna)
|
|||
parm = RNA_def_pointer(func, "item", "KeyMapItem", "", "");
|
||||
RNA_def_parameter_flags(parm, PROP_NEVER_NULL, PARM_REQUIRED | PARM_RNAPTR);
|
||||
|
||||
func = RNA_def_function(
|
||||
srna, "template_xr_actionmap_item_properties", "uiTemplateXrActionmapItemProperties");
|
||||
parm = RNA_def_pointer(func, "item", "XrActionMapItem", "", "");
|
||||
RNA_def_parameter_flags(parm, PROP_NEVER_NULL, PARM_REQUIRED | PARM_RNAPTR);
|
||||
|
||||
func = RNA_def_function(srna, "template_component_menu", "uiTemplateComponentMenu");
|
||||
RNA_def_function_ui_description(func, "Item. Display expanded property in a popup menu");
|
||||
parm = RNA_def_pointer(func, "data", "AnyType", "", "Data from which to take property");
|
||||
|
|
|
@ -66,6 +66,12 @@ static void rna_XrComponentPath_remove(XrActionMapBinding *amb, PointerRNA *comp
|
|||
int idx = BLI_findindex(&amb->component_paths, component_path);
|
||||
if (idx != -1) {
|
||||
BLI_freelinkN(&amb->component_paths, component_path);
|
||||
|
||||
if (idx <= amb->sel_component_path) {
|
||||
if (--amb->sel_component_path < 0) {
|
||||
amb->sel_component_path = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
RNA_POINTER_INVALIDATE(component_path_ptr);
|
||||
# else
|
||||
|
@ -216,12 +222,11 @@ static void rna_XrActionMapBinding_name_update(Main *bmain, Scene *UNUSED(scene)
|
|||
{
|
||||
# ifdef WITH_XR_OPENXR
|
||||
wmWindowManager *wm = bmain->wm.first;
|
||||
if (wm && wm->xr.runtime) {
|
||||
ListBase *actionmaps = WM_xr_actionmaps_get(wm->xr.runtime);
|
||||
short idx = WM_xr_actionmap_selected_index_get(wm->xr.runtime);
|
||||
XrActionMap *actionmap = BLI_findlink(actionmaps, idx);
|
||||
if (wm) {
|
||||
XrActionMap *actionmap = BLI_findlink(&wm->xr.session_settings.actionmaps,
|
||||
wm->xr.session_settings.selactionmap);
|
||||
if (actionmap) {
|
||||
XrActionMapItem *ami = BLI_findlink(&actionmap->items, actionmap->selitem);
|
||||
XrActionMapItem *ami = BLI_findlink(&actionmap->items, actionmap->sel_item);
|
||||
if (ami) {
|
||||
XrActionMapBinding *amb = ptr->data;
|
||||
WM_xr_actionmap_binding_ensure_unique(ami, amb);
|
||||
|
@ -253,6 +258,12 @@ static void rna_XrUserPath_remove(XrActionMapItem *ami, PointerRNA *user_path_pt
|
|||
int idx = BLI_findindex(&ami->user_paths, user_path);
|
||||
if (idx != -1) {
|
||||
BLI_freelinkN(&ami->user_paths, user_path);
|
||||
|
||||
if (idx <= ami->sel_user_path) {
|
||||
if (--ami->sel_user_path < 0) {
|
||||
ami->sel_user_path = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
RNA_POINTER_INVALIDATE(user_path_ptr);
|
||||
# else
|
||||
|
@ -537,10 +548,9 @@ static void rna_XrActionMapItem_name_update(Main *bmain, Scene *UNUSED(scene), P
|
|||
{
|
||||
# ifdef WITH_XR_OPENXR
|
||||
wmWindowManager *wm = bmain->wm.first;
|
||||
if (wm && wm->xr.runtime) {
|
||||
ListBase *actionmaps = WM_xr_actionmaps_get(wm->xr.runtime);
|
||||
short idx = WM_xr_actionmap_selected_index_get(wm->xr.runtime);
|
||||
XrActionMap *actionmap = BLI_findlink(actionmaps, idx);
|
||||
if (wm) {
|
||||
XrActionMap *actionmap = BLI_findlink(&wm->xr.session_settings.actionmaps,
|
||||
wm->xr.session_settings.selactionmap);
|
||||
if (actionmap) {
|
||||
XrActionMapItem *ami = ptr->data;
|
||||
WM_xr_actionmap_item_ensure_unique(actionmap, ami);
|
||||
|
@ -561,50 +571,51 @@ static void rna_XrActionMapItem_update(Main *UNUSED(bmain), Scene *UNUSED(scene)
|
|||
# endif
|
||||
}
|
||||
|
||||
static XrActionMap *rna_XrActionMap_new(PointerRNA *ptr, const char *name, bool replace_existing)
|
||||
static XrActionMap *rna_XrActionMap_new(XrSessionSettings *settings,
|
||||
const char *name,
|
||||
bool replace_existing)
|
||||
{
|
||||
# ifdef WITH_XR_OPENXR
|
||||
wmXrData *xr = rna_XrSession_wm_xr_data_get(ptr);
|
||||
return WM_xr_actionmap_new(xr->runtime, name, replace_existing);
|
||||
return WM_xr_actionmap_new(settings, name, replace_existing);
|
||||
# else
|
||||
UNUSED_VARS(ptr, name, replace_existing);
|
||||
UNUSED_VARS(settings, name, replace_existing);
|
||||
return NULL;
|
||||
# endif
|
||||
}
|
||||
|
||||
static XrActionMap *rna_XrActionMap_new_from_actionmap(PointerRNA *ptr, XrActionMap *am_src)
|
||||
static XrActionMap *rna_XrActionMap_new_from_actionmap(XrSessionSettings *settings,
|
||||
XrActionMap *am_src)
|
||||
{
|
||||
# ifdef WITH_XR_OPENXR
|
||||
wmXrData *xr = rna_XrSession_wm_xr_data_get(ptr);
|
||||
return WM_xr_actionmap_add_copy(xr->runtime, am_src);
|
||||
return WM_xr_actionmap_add_copy(settings, am_src);
|
||||
# else
|
||||
UNUSED_VARS(ptr, am_src);
|
||||
UNUSED_VARS(settings, am_src);
|
||||
return NULL;
|
||||
# endif
|
||||
}
|
||||
|
||||
static void rna_XrActionMap_remove(ReportList *reports, PointerRNA *ptr, PointerRNA *actionmap_ptr)
|
||||
static void rna_XrActionMap_remove(XrSessionSettings *settings,
|
||||
ReportList *reports,
|
||||
PointerRNA *actionmap_ptr)
|
||||
{
|
||||
# ifdef WITH_XR_OPENXR
|
||||
wmXrData *xr = rna_XrSession_wm_xr_data_get(ptr);
|
||||
XrActionMap *actionmap = actionmap_ptr->data;
|
||||
if (WM_xr_actionmap_remove(xr->runtime, actionmap) == false) {
|
||||
if (WM_xr_actionmap_remove(settings, actionmap) == false) {
|
||||
BKE_reportf(reports, RPT_ERROR, "ActionMap '%s' cannot be removed", actionmap->name);
|
||||
return;
|
||||
}
|
||||
RNA_POINTER_INVALIDATE(actionmap_ptr);
|
||||
# else
|
||||
UNUSED_VARS(ptr, reports, actionmap_ptr);
|
||||
UNUSED_VARS(settings, reports, actionmap_ptr);
|
||||
# endif
|
||||
}
|
||||
|
||||
static XrActionMap *rna_XrActionMap_find(PointerRNA *ptr, const char *name)
|
||||
static XrActionMap *rna_XrActionMap_find(XrSessionSettings *settings, const char *name)
|
||||
{
|
||||
# ifdef WITH_XR_OPENXR
|
||||
wmXrData *xr = rna_XrSession_wm_xr_data_get(ptr);
|
||||
return WM_xr_actionmap_find(xr->runtime, name);
|
||||
return WM_xr_actionmap_find(settings, name);
|
||||
# else
|
||||
UNUSED_VARS(ptr, name);
|
||||
UNUSED_VARS(settings, name);
|
||||
return NULL;
|
||||
# endif
|
||||
}
|
||||
|
@ -634,9 +645,9 @@ static void rna_XrActionMap_name_update(Main *bmain, Scene *UNUSED(scene), Point
|
|||
{
|
||||
# ifdef WITH_XR_OPENXR
|
||||
wmWindowManager *wm = bmain->wm.first;
|
||||
if (wm && wm->xr.runtime) {
|
||||
if (wm) {
|
||||
XrActionMap *actionmap = ptr->data;
|
||||
WM_xr_actionmap_ensure_unique(wm->xr.runtime, actionmap);
|
||||
WM_xr_actionmap_ensure_unique(&wm->xr.session_settings, actionmap);
|
||||
}
|
||||
# else
|
||||
UNUSED_VARS(bmain, ptr);
|
||||
|
@ -691,6 +702,28 @@ static void rna_XrSessionSettings_use_absolute_tracking_set(PointerRNA *ptr, boo
|
|||
# endif
|
||||
}
|
||||
|
||||
static void rna_XrSessionSettings_actionmaps_begin(CollectionPropertyIterator *iter,
|
||||
PointerRNA *ptr)
|
||||
{
|
||||
# ifdef WITH_XR_OPENXR
|
||||
wmXrData *xr = rna_XrSession_wm_xr_data_get(ptr);
|
||||
rna_iterator_listbase_begin(iter, &xr->session_settings.actionmaps, NULL);
|
||||
# else
|
||||
UNUSED_VARS(iter, ptr);
|
||||
# endif
|
||||
}
|
||||
|
||||
static int rna_XrSessionSettings_actionmaps_length(PointerRNA *ptr)
|
||||
{
|
||||
# ifdef WITH_XR_OPENXR
|
||||
wmXrData *xr = rna_XrSession_wm_xr_data_get(ptr);
|
||||
return BLI_listbase_count(&xr->session_settings.actionmaps);
|
||||
# else
|
||||
UNUSED_VARS(ptr);
|
||||
return 0;
|
||||
# endif
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
|
@ -1062,71 +1095,6 @@ static void rna_XrSessionState_nav_scale_set(PointerRNA *ptr, float value)
|
|||
# endif
|
||||
}
|
||||
|
||||
static void rna_XrSessionState_actionmaps_begin(CollectionPropertyIterator *iter, PointerRNA *ptr)
|
||||
{
|
||||
# ifdef WITH_XR_OPENXR
|
||||
wmXrData *xr = rna_XrSession_wm_xr_data_get(ptr);
|
||||
ListBase *lb = WM_xr_actionmaps_get(xr->runtime);
|
||||
rna_iterator_listbase_begin(iter, lb, NULL);
|
||||
# else
|
||||
UNUSED_VARS(iter, ptr);
|
||||
# endif
|
||||
}
|
||||
|
||||
static int rna_XrSessionState_actionmaps_length(PointerRNA *ptr)
|
||||
{
|
||||
# ifdef WITH_XR_OPENXR
|
||||
wmXrData *xr = rna_XrSession_wm_xr_data_get(ptr);
|
||||
ListBase *lb = WM_xr_actionmaps_get(xr->runtime);
|
||||
return BLI_listbase_count(lb);
|
||||
# else
|
||||
UNUSED_VARS(ptr);
|
||||
return 0;
|
||||
# endif
|
||||
}
|
||||
|
||||
static int rna_XrSessionState_active_actionmap_get(PointerRNA *ptr)
|
||||
{
|
||||
# ifdef WITH_XR_OPENXR
|
||||
const wmXrData *xr = rna_XrSession_wm_xr_data_get(ptr);
|
||||
return WM_xr_actionmap_active_index_get(xr->runtime);
|
||||
# else
|
||||
UNUSED_VARS(ptr);
|
||||
return -1;
|
||||
# endif
|
||||
}
|
||||
|
||||
static void rna_XrSessionState_active_actionmap_set(PointerRNA *ptr, int value)
|
||||
{
|
||||
# ifdef WITH_XR_OPENXR
|
||||
wmXrData *xr = rna_XrSession_wm_xr_data_get(ptr);
|
||||
WM_xr_actionmap_active_index_set(xr->runtime, (short)value);
|
||||
# else
|
||||
UNUSED_VARS(ptr, value);
|
||||
# endif
|
||||
}
|
||||
|
||||
static int rna_XrSessionState_selected_actionmap_get(PointerRNA *ptr)
|
||||
{
|
||||
# ifdef WITH_XR_OPENXR
|
||||
const wmXrData *xr = rna_XrSession_wm_xr_data_get(ptr);
|
||||
return WM_xr_actionmap_selected_index_get(xr->runtime);
|
||||
# else
|
||||
UNUSED_VARS(ptr);
|
||||
return -1;
|
||||
# endif
|
||||
}
|
||||
|
||||
static void rna_XrSessionState_selected_actionmap_set(PointerRNA *ptr, int value)
|
||||
{
|
||||
# ifdef WITH_XR_OPENXR
|
||||
wmXrData *xr = rna_XrSession_wm_xr_data_get(ptr);
|
||||
WM_xr_actionmap_selected_index_set(xr->runtime, (short)value);
|
||||
# else
|
||||
UNUSED_VARS(ptr, value);
|
||||
# endif
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
|
@ -1537,12 +1505,10 @@ static void rna_def_xr_actionmaps(BlenderRNA *brna, PropertyRNA *cprop)
|
|||
|
||||
RNA_def_property_srna(cprop, "XrActionMaps");
|
||||
srna = RNA_def_struct(brna, "XrActionMaps", NULL);
|
||||
RNA_def_struct_sdna(srna, "XrSessionSettings");
|
||||
RNA_def_struct_ui_text(srna, "XR Action Maps", "Collection of XR action maps");
|
||||
|
||||
func = RNA_def_function(srna, "new", "rna_XrActionMap_new");
|
||||
RNA_def_function_flag(func, FUNC_NO_SELF);
|
||||
parm = RNA_def_pointer(func, "xr_session_state", "XrSessionState", "XR Session State", "");
|
||||
RNA_def_parameter_flags(parm, PROP_NEVER_NULL, PARM_REQUIRED | PARM_RNAPTR);
|
||||
parm = RNA_def_string(func, "name", NULL, MAX_NAME, "Name", "");
|
||||
RNA_def_parameter_flags(parm, 0, PARM_REQUIRED);
|
||||
parm = RNA_def_boolean(func,
|
||||
|
@ -1555,9 +1521,6 @@ static void rna_def_xr_actionmaps(BlenderRNA *brna, PropertyRNA *cprop)
|
|||
RNA_def_function_return(func, parm);
|
||||
|
||||
func = RNA_def_function(srna, "new_from_actionmap", "rna_XrActionMap_new_from_actionmap");
|
||||
RNA_def_function_flag(func, FUNC_NO_SELF);
|
||||
parm = RNA_def_pointer(func, "xr_session_state", "XrSessionState", "XR Session State", "");
|
||||
RNA_def_parameter_flags(parm, PROP_NEVER_NULL, PARM_REQUIRED | PARM_RNAPTR);
|
||||
parm = RNA_def_pointer(
|
||||
func, "actionmap", "XrActionMap", "Action Map", "Action map to use as a reference");
|
||||
RNA_def_parameter_flags(parm, PROP_NEVER_NULL, PARM_REQUIRED);
|
||||
|
@ -1565,17 +1528,12 @@ static void rna_def_xr_actionmaps(BlenderRNA *brna, PropertyRNA *cprop)
|
|||
RNA_def_function_return(func, parm);
|
||||
|
||||
func = RNA_def_function(srna, "remove", "rna_XrActionMap_remove");
|
||||
RNA_def_function_flag(func, FUNC_NO_SELF | FUNC_USE_REPORTS);
|
||||
parm = RNA_def_pointer(func, "xr_session_state", "XrSessionState", "XR Session State", "");
|
||||
RNA_def_parameter_flags(parm, PROP_NEVER_NULL, PARM_REQUIRED | PARM_RNAPTR);
|
||||
RNA_def_function_flag(func, FUNC_USE_REPORTS);
|
||||
parm = RNA_def_pointer(func, "actionmap", "XrActionMap", "Action Map", "Removed action map");
|
||||
RNA_def_parameter_flags(parm, PROP_NEVER_NULL, PARM_REQUIRED | PARM_RNAPTR);
|
||||
RNA_def_parameter_clear_flags(parm, PROP_THICK_WRAP, 0);
|
||||
|
||||
func = RNA_def_function(srna, "find", "rna_XrActionMap_find");
|
||||
RNA_def_function_flag(func, FUNC_NO_SELF);
|
||||
parm = RNA_def_pointer(func, "xr_session_state", "XrSessionState", "XR Session State", "");
|
||||
RNA_def_parameter_flags(parm, PROP_NEVER_NULL, PARM_REQUIRED | PARM_RNAPTR);
|
||||
parm = RNA_def_string(func, "name", NULL, MAX_NAME, "Name", "");
|
||||
RNA_def_parameter_flags(parm, 0, PARM_REQUIRED);
|
||||
parm = RNA_def_pointer(
|
||||
|
@ -1617,7 +1575,7 @@ static void rna_def_xr_actionmap(BlenderRNA *brna)
|
|||
rna_def_xr_actionmap_items(brna, prop);
|
||||
|
||||
prop = RNA_def_property(srna, "selected_item", PROP_INT, PROP_NONE);
|
||||
RNA_def_property_int_sdna(prop, NULL, "selitem");
|
||||
RNA_def_property_int_sdna(prop, NULL, "sel_item");
|
||||
RNA_def_property_ui_text(prop, "Selected Item", "");
|
||||
|
||||
/* XrUserPath */
|
||||
|
@ -1658,6 +1616,10 @@ static void rna_def_xr_actionmap(BlenderRNA *brna)
|
|||
RNA_def_property_ui_text(prop, "User Paths", "OpenXR user paths");
|
||||
rna_def_xr_user_paths(brna, prop);
|
||||
|
||||
prop = RNA_def_property(srna, "selected_user_path", PROP_INT, PROP_NONE);
|
||||
RNA_def_property_int_sdna(prop, NULL, "sel_user_path");
|
||||
RNA_def_property_ui_text(prop, "Selected User Path", "Currently selected user path");
|
||||
|
||||
prop = RNA_def_property(srna, "op", PROP_STRING, PROP_NONE);
|
||||
RNA_def_property_string_maxlength(prop, OP_MAX_TYPENAME);
|
||||
RNA_def_property_ui_text(prop, "Operator", "Identifier of operator to call on action event");
|
||||
|
@ -1755,7 +1717,7 @@ static void rna_def_xr_actionmap(BlenderRNA *brna)
|
|||
rna_def_xr_actionmap_bindings(brna, prop);
|
||||
|
||||
prop = RNA_def_property(srna, "selected_binding", PROP_INT, PROP_NONE);
|
||||
RNA_def_property_int_sdna(prop, NULL, "selbinding");
|
||||
RNA_def_property_int_sdna(prop, NULL, "sel_binding");
|
||||
RNA_def_property_ui_text(prop, "Selected Binding", "Currently selected binding");
|
||||
|
||||
/* XrComponentPath */
|
||||
|
@ -1795,6 +1757,10 @@ static void rna_def_xr_actionmap(BlenderRNA *brna)
|
|||
RNA_def_property_ui_text(prop, "Component Paths", "OpenXR component paths");
|
||||
rna_def_xr_component_paths(brna, prop);
|
||||
|
||||
prop = RNA_def_property(srna, "selected_component_path", PROP_INT, PROP_NONE);
|
||||
RNA_def_property_int_sdna(prop, NULL, "sel_component_path");
|
||||
RNA_def_property_ui_text(prop, "Selected Component Path", "Currently selected component path");
|
||||
|
||||
prop = RNA_def_property(srna, "threshold", PROP_FLOAT, PROP_NONE);
|
||||
RNA_def_property_float_sdna(prop, NULL, "float_threshold");
|
||||
RNA_def_property_range(prop, 0.0, 1.0);
|
||||
|
@ -1982,6 +1948,28 @@ static void rna_def_xr_session_settings(BlenderRNA *brna)
|
|||
"Absolute Tracking",
|
||||
"Allow the VR tracking origin to be defined independently of the headset location");
|
||||
RNA_def_property_update(prop, NC_WM | ND_XR_DATA_CHANGED, NULL);
|
||||
|
||||
prop = RNA_def_property(srna, "actionmaps", PROP_COLLECTION, PROP_NONE);
|
||||
RNA_def_property_collection_funcs(prop,
|
||||
"rna_XrSessionSettings_actionmaps_begin",
|
||||
"rna_iterator_listbase_next",
|
||||
"rna_iterator_listbase_end",
|
||||
"rna_iterator_listbase_get",
|
||||
"rna_XrSessionSettings_actionmaps_length",
|
||||
NULL,
|
||||
NULL,
|
||||
NULL);
|
||||
RNA_def_property_struct_type(prop, "XrActionMap");
|
||||
RNA_def_property_ui_text(prop, "XR Action Maps", "");
|
||||
rna_def_xr_actionmaps(brna, prop);
|
||||
|
||||
prop = RNA_def_property(srna, "active_actionmap", PROP_INT, PROP_NONE);
|
||||
RNA_def_property_int_sdna(prop, NULL, "actactionmap");
|
||||
RNA_def_property_ui_text(prop, "Active Action Map", "");
|
||||
|
||||
prop = RNA_def_property(srna, "selected_actionmap", PROP_INT, PROP_NONE);
|
||||
RNA_def_property_int_sdna(prop, NULL, "selactionmap");
|
||||
RNA_def_property_ui_text(prop, "Selected Action Map", "");
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
@ -2312,34 +2300,6 @@ static void rna_def_xr_session_state(BlenderRNA *brna)
|
|||
prop,
|
||||
"Navigation Scale",
|
||||
"Additional scale multiplier to apply to base scale when determining viewer scale");
|
||||
|
||||
prop = RNA_def_property(srna, "actionmaps", PROP_COLLECTION, PROP_NONE);
|
||||
RNA_def_property_struct_type(prop, "XrActionMap");
|
||||
RNA_def_property_collection_funcs(prop,
|
||||
"rna_XrSessionState_actionmaps_begin",
|
||||
"rna_iterator_listbase_next",
|
||||
"rna_iterator_listbase_end",
|
||||
"rna_iterator_listbase_get",
|
||||
"rna_XrSessionState_actionmaps_length",
|
||||
NULL,
|
||||
NULL,
|
||||
NULL);
|
||||
RNA_def_property_ui_text(prop, "XR Action Maps", "");
|
||||
rna_def_xr_actionmaps(brna, prop);
|
||||
|
||||
prop = RNA_def_property(srna, "active_actionmap", PROP_INT, PROP_NONE);
|
||||
RNA_def_property_int_funcs(prop,
|
||||
"rna_XrSessionState_active_actionmap_get",
|
||||
"rna_XrSessionState_active_actionmap_set",
|
||||
NULL);
|
||||
RNA_def_property_ui_text(prop, "Active Action Map", "");
|
||||
|
||||
prop = RNA_def_property(srna, "selected_actionmap", PROP_INT, PROP_NONE);
|
||||
RNA_def_property_int_funcs(prop,
|
||||
"rna_XrSessionState_selected_actionmap_get",
|
||||
"rna_XrSessionState_selected_actionmap_set",
|
||||
NULL);
|
||||
RNA_def_property_ui_text(prop, "Selected Action Map", "");
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
|
|
@ -1642,23 +1642,17 @@ void WM_xr_haptic_action_stop(wmXrData *xr,
|
|||
|
||||
/* wm_xr_actionmap.c */
|
||||
|
||||
XrActionMap *WM_xr_actionmap_new(struct wmXrRuntimeData *runtime,
|
||||
XrActionMap *WM_xr_actionmap_new(XrSessionSettings *settings,
|
||||
const char *name,
|
||||
bool replace_existing);
|
||||
/**
|
||||
* Ensure unique name among all action maps.
|
||||
*/
|
||||
void WM_xr_actionmap_ensure_unique(struct wmXrRuntimeData *runtime, XrActionMap *actionmap);
|
||||
XrActionMap *WM_xr_actionmap_add_copy(struct wmXrRuntimeData *runtime, XrActionMap *am_src);
|
||||
bool WM_xr_actionmap_remove(struct wmXrRuntimeData *runtime, XrActionMap *actionmap);
|
||||
XrActionMap *WM_xr_actionmap_find(struct wmXrRuntimeData *runtime, const char *name);
|
||||
void WM_xr_actionmap_clear(XrActionMap *actionmap);
|
||||
void WM_xr_actionmaps_clear(struct wmXrRuntimeData *runtime);
|
||||
ListBase *WM_xr_actionmaps_get(struct wmXrRuntimeData *runtime);
|
||||
short WM_xr_actionmap_active_index_get(const struct wmXrRuntimeData *runtime);
|
||||
void WM_xr_actionmap_active_index_set(struct wmXrRuntimeData *runtime, short idx);
|
||||
short WM_xr_actionmap_selected_index_get(const struct wmXrRuntimeData *runtime);
|
||||
void WM_xr_actionmap_selected_index_set(struct wmXrRuntimeData *runtime, short idx);
|
||||
void WM_xr_actionmap_ensure_unique(XrSessionSettings *settings, XrActionMap *actionmap);
|
||||
XrActionMap *WM_xr_actionmap_add_copy(XrSessionSettings *settings, XrActionMap *am_src);
|
||||
bool WM_xr_actionmap_remove(XrSessionSettings *settings, XrActionMap *actionmap);
|
||||
XrActionMap *WM_xr_actionmap_find(XrSessionSettings *settings, const char *name);
|
||||
void WM_xr_actionmaps_free(XrSessionSettings *settings);
|
||||
|
||||
XrActionMapItem *WM_xr_actionmap_item_new(XrActionMap *actionmap,
|
||||
const char *name,
|
||||
|
|
|
@ -95,6 +95,29 @@ static void window_manager_foreach_id(ID *id, LibraryForeachIDData *data)
|
|||
static void write_wm_xr_data(BlendWriter *writer, wmXrData *xr_data)
|
||||
{
|
||||
BKE_screen_view3d_shading_blend_write(writer, &xr_data->session_settings.shading);
|
||||
|
||||
LISTBASE_FOREACH (XrActionMap *, am, &xr_data->session_settings.actionmaps) {
|
||||
BLO_write_struct(writer, XrActionMap, am);
|
||||
|
||||
LISTBASE_FOREACH (XrActionMapItem *, ami, &am->items) {
|
||||
BLO_write_struct(writer, XrActionMapItem, ami);
|
||||
if (ami->op[0] && ami->op_properties) {
|
||||
IDP_BlendWrite(writer, ami->op_properties);
|
||||
}
|
||||
|
||||
LISTBASE_FOREACH (XrUserPath *, user_path, &ami->user_paths) {
|
||||
BLO_write_struct(writer, XrUserPath, user_path);
|
||||
}
|
||||
|
||||
LISTBASE_FOREACH (XrActionMapBinding *, amb, &ami->bindings) {
|
||||
BLO_write_struct(writer, XrActionMapBinding, amb);
|
||||
|
||||
LISTBASE_FOREACH (XrComponentPath *, component_path, &amb->component_paths) {
|
||||
BLO_write_struct(writer, XrComponentPath, component_path);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void window_manager_blend_write(BlendWriter *writer, ID *id, const void *id_address)
|
||||
|
@ -123,6 +146,34 @@ static void window_manager_blend_write(BlendWriter *writer, ID *id, const void *
|
|||
static void direct_link_wm_xr_data(BlendDataReader *reader, wmXrData *xr_data)
|
||||
{
|
||||
BKE_screen_view3d_shading_blend_read_data(reader, &xr_data->session_settings.shading);
|
||||
|
||||
BLO_read_list(reader, &xr_data->session_settings.actionmaps);
|
||||
|
||||
LISTBASE_FOREACH (XrActionMap *, am, &xr_data->session_settings.actionmaps) {
|
||||
BLO_read_list(reader, &am->items);
|
||||
|
||||
LISTBASE_FOREACH (XrActionMapItem *, ami, &am->items) {
|
||||
if (ami->op[0] && ami->op_properties) {
|
||||
BLO_read_data_address(reader, &ami->op_properties);
|
||||
IDP_BlendDataRead(reader, &ami->op_properties);
|
||||
|
||||
ami->op_properties_ptr = MEM_callocN(sizeof(PointerRNA), "wmOpItemPtr");
|
||||
WM_operator_properties_create(ami->op_properties_ptr, ami->op);
|
||||
ami->op_properties_ptr->data = ami->op_properties;
|
||||
}
|
||||
else {
|
||||
ami->op_properties = NULL;
|
||||
ami->op_properties_ptr = NULL;
|
||||
}
|
||||
|
||||
BLO_read_list(reader, &ami->user_paths);
|
||||
BLO_read_list(reader, &ami->bindings);
|
||||
|
||||
LISTBASE_FOREACH (XrActionMapBinding *, amb, &ami->bindings) {
|
||||
BLO_read_list(reader, &amb->component_paths);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void window_manager_blend_read_data(BlendDataReader *reader, ID *id)
|
||||
|
|
|
@ -201,6 +201,11 @@ static void wm_window_match_init(bContext *C, ListBase *wmlist)
|
|||
WM_msgbus_destroy(wm->message_bus);
|
||||
wm->message_bus = NULL;
|
||||
}
|
||||
|
||||
#ifdef WITH_XR_OPENXR
|
||||
/* Free XR action maps. */
|
||||
WM_xr_actionmaps_free(&wm->xr.session_settings);
|
||||
#endif
|
||||
}
|
||||
|
||||
BLI_listbase_clear(&G_MAIN->wm);
|
||||
|
|
|
@ -117,6 +117,7 @@ void wm_xr_exit(wmWindowManager *wm)
|
|||
IDP_FreeProperty(wm->xr.session_settings.shading.prop);
|
||||
wm->xr.session_settings.shading.prop = NULL;
|
||||
}
|
||||
WM_xr_actionmaps_free(&wm->xr.session_settings);
|
||||
}
|
||||
|
||||
bool wm_xr_events_handle(wmWindowManager *wm)
|
||||
|
@ -168,7 +169,6 @@ void wm_xr_runtime_data_free(wmXrRuntimeData **runtime)
|
|||
(*runtime)->area = NULL;
|
||||
}
|
||||
wm_xr_session_data_free(&(*runtime)->session_state);
|
||||
WM_xr_actionmaps_clear(*runtime);
|
||||
|
||||
GHOST_XrContextDestroy(context);
|
||||
}
|
||||
|
|
|
@ -127,6 +127,7 @@ XrActionMapBinding *WM_xr_actionmap_binding_add_copy(XrActionMapItem *ami,
|
|||
static void wm_xr_actionmap_binding_clear(XrActionMapBinding *amb)
|
||||
{
|
||||
BLI_freelistN(&amb->component_paths);
|
||||
amb->sel_component_path = 0;
|
||||
}
|
||||
|
||||
bool WM_xr_actionmap_binding_remove(XrActionMapItem *ami, XrActionMapBinding *amb)
|
||||
|
@ -137,9 +138,9 @@ bool WM_xr_actionmap_binding_remove(XrActionMapItem *ami, XrActionMapBinding *am
|
|||
wm_xr_actionmap_binding_clear(amb);
|
||||
BLI_freelinkN(&ami->bindings, amb);
|
||||
|
||||
if (idx <= ami->selbinding) {
|
||||
if (--ami->selbinding < 0) {
|
||||
ami->selbinding = 0;
|
||||
if (idx <= ami->sel_binding) {
|
||||
if (--ami->sel_binding < 0) {
|
||||
ami->sel_binding = 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -192,11 +193,12 @@ static void wm_xr_actionmap_item_clear(XrActionMapItem *ami)
|
|||
wm_xr_actionmap_binding_clear(amb);
|
||||
}
|
||||
BLI_freelistN(&ami->bindings);
|
||||
ami->selbinding = 0;
|
||||
|
||||
wm_xr_actionmap_item_properties_free(ami);
|
||||
ami->sel_binding = 0;
|
||||
|
||||
BLI_freelistN(&ami->user_paths);
|
||||
ami->sel_user_path = 0;
|
||||
|
||||
wm_xr_actionmap_item_properties_free(ami);
|
||||
}
|
||||
|
||||
void WM_xr_actionmap_item_properties_update_ot(XrActionMapItem *ami)
|
||||
|
@ -313,6 +315,12 @@ static XrActionMapItem *wm_xr_actionmap_item_copy(XrActionMapItem *ami_src)
|
|||
BLI_addtail(&ami_dst->bindings, amb_new);
|
||||
}
|
||||
|
||||
BLI_listbase_clear(&ami_dst->user_paths);
|
||||
LISTBASE_FOREACH (XrUserPath *, path, &ami_src->user_paths) {
|
||||
XrUserPath *path_new = MEM_dupallocN(path);
|
||||
BLI_addtail(&ami_dst->user_paths, path_new);
|
||||
}
|
||||
|
||||
if (ami_dst->op_properties) {
|
||||
ami_dst->op_properties_ptr = MEM_callocN(sizeof(PointerRNA), "wmOpItemPtr");
|
||||
WM_operator_properties_create(ami_dst->op_properties_ptr, ami_dst->op);
|
||||
|
@ -324,12 +332,6 @@ static XrActionMapItem *wm_xr_actionmap_item_copy(XrActionMapItem *ami_src)
|
|||
ami_dst->op_properties_ptr = NULL;
|
||||
}
|
||||
|
||||
BLI_listbase_clear(&ami_dst->user_paths);
|
||||
LISTBASE_FOREACH (XrUserPath *, path, &ami_src->user_paths) {
|
||||
XrUserPath *path_new = MEM_dupallocN(path);
|
||||
BLI_addtail(&ami_dst->user_paths, path_new);
|
||||
}
|
||||
|
||||
return ami_dst;
|
||||
}
|
||||
|
||||
|
@ -352,9 +354,9 @@ bool WM_xr_actionmap_item_remove(XrActionMap *actionmap, XrActionMapItem *ami)
|
|||
wm_xr_actionmap_item_clear(ami);
|
||||
BLI_freelinkN(&actionmap->items, ami);
|
||||
|
||||
if (idx <= actionmap->selitem) {
|
||||
if (--actionmap->selitem < 0) {
|
||||
actionmap->selitem = 0;
|
||||
if (idx <= actionmap->sel_item) {
|
||||
if (--actionmap->sel_item < 0) {
|
||||
actionmap->sel_item = 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -382,39 +384,49 @@ XrActionMapItem *WM_xr_actionmap_item_find(XrActionMap *actionmap, const char *n
|
|||
* List of XR action map items.
|
||||
* \{ */
|
||||
|
||||
XrActionMap *WM_xr_actionmap_new(wmXrRuntimeData *runtime, const char *name, bool replace_existing)
|
||||
static void wm_xr_actionmap_clear(XrActionMap *actionmap)
|
||||
{
|
||||
XrActionMap *am_prev = WM_xr_actionmap_find(runtime, name);
|
||||
LISTBASE_FOREACH (XrActionMapItem *, ami, &actionmap->items) {
|
||||
wm_xr_actionmap_item_clear(ami);
|
||||
}
|
||||
BLI_freelistN(&actionmap->items);
|
||||
actionmap->sel_item = 0;
|
||||
}
|
||||
|
||||
XrActionMap *WM_xr_actionmap_new(XrSessionSettings *settings,
|
||||
const char *name,
|
||||
bool replace_existing)
|
||||
{
|
||||
XrActionMap *am_prev = WM_xr_actionmap_find(settings, name);
|
||||
if (am_prev && replace_existing) {
|
||||
WM_xr_actionmap_clear(am_prev);
|
||||
wm_xr_actionmap_clear(am_prev);
|
||||
return am_prev;
|
||||
}
|
||||
|
||||
XrActionMap *am = MEM_callocN(sizeof(struct XrActionMap), __func__);
|
||||
BLI_strncpy(am->name, name, MAX_NAME);
|
||||
if (am_prev) {
|
||||
WM_xr_actionmap_ensure_unique(runtime, am);
|
||||
WM_xr_actionmap_ensure_unique(settings, am);
|
||||
}
|
||||
|
||||
BLI_addtail(&runtime->actionmaps, am);
|
||||
BLI_addtail(&settings->actionmaps, am);
|
||||
|
||||
return am;
|
||||
}
|
||||
|
||||
static XrActionMap *wm_xr_actionmap_find_except(wmXrRuntimeData *runtime,
|
||||
static XrActionMap *wm_xr_actionmap_find_except(XrSessionSettings *settings,
|
||||
const char *name,
|
||||
const XrActionMap *am_except)
|
||||
{
|
||||
LISTBASE_FOREACH (XrActionMap *, am, &runtime->actionmaps) {
|
||||
LISTBASE_FOREACH (XrActionMap *, am, &settings->actionmaps) {
|
||||
if (STREQLEN(name, am->name, MAX_NAME) && (am != am_except)) {
|
||||
return am;
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void WM_xr_actionmap_ensure_unique(wmXrRuntimeData *runtime, XrActionMap *actionmap)
|
||||
void WM_xr_actionmap_ensure_unique(XrSessionSettings *settings, XrActionMap *actionmap)
|
||||
{
|
||||
char name[MAX_NAME];
|
||||
char *suffix;
|
||||
|
@ -425,7 +437,7 @@ void WM_xr_actionmap_ensure_unique(wmXrRuntimeData *runtime, XrActionMap *action
|
|||
baselen = BLI_strnlen(name, MAX_NAME);
|
||||
suffix = &name[baselen];
|
||||
|
||||
while (wm_xr_actionmap_find_except(runtime, name, actionmap)) {
|
||||
while (wm_xr_actionmap_find_except(settings, name, actionmap)) {
|
||||
if ((baselen + 1) + (log10(++idx) + 1) > MAX_NAME) {
|
||||
/* Use default base name. */
|
||||
BLI_strncpy(name, WM_XR_ACTIONMAP_STR_DEFAULT, MAX_NAME);
|
||||
|
@ -455,33 +467,33 @@ static XrActionMap *wm_xr_actionmap_copy(XrActionMap *am_src)
|
|||
return am_dst;
|
||||
}
|
||||
|
||||
XrActionMap *WM_xr_actionmap_add_copy(wmXrRuntimeData *runtime, XrActionMap *am_src)
|
||||
XrActionMap *WM_xr_actionmap_add_copy(XrSessionSettings *settings, XrActionMap *am_src)
|
||||
{
|
||||
XrActionMap *am_dst = wm_xr_actionmap_copy(am_src);
|
||||
|
||||
WM_xr_actionmap_ensure_unique(runtime, am_dst);
|
||||
WM_xr_actionmap_ensure_unique(settings, am_dst);
|
||||
|
||||
BLI_addtail(&runtime->actionmaps, am_dst);
|
||||
BLI_addtail(&settings->actionmaps, am_dst);
|
||||
|
||||
return am_dst;
|
||||
}
|
||||
|
||||
bool WM_xr_actionmap_remove(wmXrRuntimeData *runtime, XrActionMap *actionmap)
|
||||
bool WM_xr_actionmap_remove(XrSessionSettings *settings, XrActionMap *actionmap)
|
||||
{
|
||||
int idx = BLI_findindex(&runtime->actionmaps, actionmap);
|
||||
int idx = BLI_findindex(&settings->actionmaps, actionmap);
|
||||
|
||||
if (idx != -1) {
|
||||
WM_xr_actionmap_clear(actionmap);
|
||||
BLI_freelinkN(&runtime->actionmaps, actionmap);
|
||||
wm_xr_actionmap_clear(actionmap);
|
||||
BLI_freelinkN(&settings->actionmaps, actionmap);
|
||||
|
||||
if (idx <= runtime->actactionmap) {
|
||||
if (--runtime->actactionmap < 0) {
|
||||
runtime->actactionmap = 0;
|
||||
if (idx <= settings->actactionmap) {
|
||||
if (--settings->actactionmap < 0) {
|
||||
settings->actactionmap = 0;
|
||||
}
|
||||
}
|
||||
if (idx <= runtime->selactionmap) {
|
||||
if (--runtime->selactionmap < 0) {
|
||||
runtime->selactionmap = 0;
|
||||
if (idx <= settings->selactionmap) {
|
||||
if (--settings->selactionmap < 0) {
|
||||
settings->selactionmap = 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -491,9 +503,9 @@ bool WM_xr_actionmap_remove(wmXrRuntimeData *runtime, XrActionMap *actionmap)
|
|||
return false;
|
||||
}
|
||||
|
||||
XrActionMap *WM_xr_actionmap_find(wmXrRuntimeData *runtime, const char *name)
|
||||
XrActionMap *WM_xr_actionmap_find(XrSessionSettings *settings, const char *name)
|
||||
{
|
||||
LISTBASE_FOREACH (XrActionMap *, am, &runtime->actionmaps) {
|
||||
LISTBASE_FOREACH (XrActionMap *, am, &settings->actionmaps) {
|
||||
if (STREQLEN(name, am->name, MAX_NAME)) {
|
||||
return am;
|
||||
}
|
||||
|
@ -501,47 +513,13 @@ XrActionMap *WM_xr_actionmap_find(wmXrRuntimeData *runtime, const char *name)
|
|||
return NULL;
|
||||
}
|
||||
|
||||
void WM_xr_actionmap_clear(XrActionMap *actionmap)
|
||||
void WM_xr_actionmaps_free(XrSessionSettings *settings)
|
||||
{
|
||||
LISTBASE_FOREACH (XrActionMapItem *, ami, &actionmap->items) {
|
||||
wm_xr_actionmap_item_clear(ami);
|
||||
LISTBASE_FOREACH (XrActionMap *, am, &settings->actionmaps) {
|
||||
wm_xr_actionmap_clear(am);
|
||||
}
|
||||
BLI_freelistN(&actionmap->items);
|
||||
actionmap->selitem = 0;
|
||||
}
|
||||
|
||||
void WM_xr_actionmaps_clear(wmXrRuntimeData *runtime)
|
||||
{
|
||||
LISTBASE_FOREACH (XrActionMap *, am, &runtime->actionmaps) {
|
||||
WM_xr_actionmap_clear(am);
|
||||
}
|
||||
BLI_freelistN(&runtime->actionmaps);
|
||||
runtime->actactionmap = runtime->selactionmap = 0;
|
||||
}
|
||||
|
||||
ListBase *WM_xr_actionmaps_get(wmXrRuntimeData *runtime)
|
||||
{
|
||||
return &runtime->actionmaps;
|
||||
}
|
||||
|
||||
short WM_xr_actionmap_active_index_get(const wmXrRuntimeData *runtime)
|
||||
{
|
||||
return runtime->actactionmap;
|
||||
}
|
||||
|
||||
void WM_xr_actionmap_active_index_set(wmXrRuntimeData *runtime, short idx)
|
||||
{
|
||||
runtime->actactionmap = idx;
|
||||
}
|
||||
|
||||
short WM_xr_actionmap_selected_index_get(const wmXrRuntimeData *runtime)
|
||||
{
|
||||
return runtime->selactionmap;
|
||||
}
|
||||
|
||||
void WM_xr_actionmap_selected_index_set(wmXrRuntimeData *runtime, short idx)
|
||||
{
|
||||
runtime->selactionmap = idx;
|
||||
BLI_freelistN(&settings->actionmaps);
|
||||
settings->actactionmap = settings->selactionmap = 0;
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
|
|
@ -72,10 +72,6 @@ typedef struct wmXrRuntimeData {
|
|||
/** Although this struct is internal, RNA gets a handle to this for state information queries. */
|
||||
wmXrSessionState session_state;
|
||||
wmXrSessionExitFn exit_fn;
|
||||
|
||||
ListBase actionmaps; /* #XrActionMap */
|
||||
short actactionmap;
|
||||
short selactionmap;
|
||||
} wmXrRuntimeData;
|
||||
|
||||
typedef struct wmXrViewportPair {
|
||||
|
|
Loading…
Reference in New Issue