Compare commits
47 Commits
temp-chunk
...
retopo_tra
Author | SHA1 | Date | |
---|---|---|---|
6485941d7a | |||
eee25a175a | |||
4f33dcff78 | |||
fd39da1df6 | |||
dbdab681cf | |||
66c6cf0d71 | |||
8ab91edd91 | |||
3ac5a52d6e | |||
60a8ade18a | |||
d57ce54e30 | |||
a735b2c335 | |||
73aa6b8185 | |||
698efac59e | |||
afe11eff8a | |||
4ebe1c3e69 | |||
887713d08d | |||
cc761cdae6 | |||
a2938c86ca | |||
a66e20f984 | |||
db317f070e | |||
7502bc583c | |||
089870ab3a | |||
298711d158 | |||
8d284d4854 | |||
9e88cfbe0c | |||
405bbb06f2 | |||
ed8f2bbf5c | |||
ddce8e9ea3 | |||
1b9e31f004 | |||
8791762af0 | |||
8d813f2eed | |||
af29d103c6 | |||
ca336c600b | |||
491ada0a38 | |||
62f813754d | |||
cbeb70bdae | |||
139a651434 | |||
add307d429 | |||
59e6dc8a93 | |||
6410fe0492 | |||
b818008ddf | |||
af9c969768 | |||
0b25d923e5 | |||
a863ba191d | |||
f606393522 | |||
f8b389b121 | |||
![]() |
59adee83e7 |
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -619,7 +619,11 @@ class VIEW3D_HT_header(Header):
|
||||
if show_snap:
|
||||
snap_items = bpy.types.ToolSettings.bl_rna.properties["snap_elements"].enum_items
|
||||
snap_elements = tool_settings.snap_elements
|
||||
if len(snap_elements) == 1:
|
||||
active_tool = context.workspace.tools.from_space_view3d_mode(context.mode, create=False)
|
||||
if active_tool.idname.startswith('retopology'):
|
||||
text = "Tool"
|
||||
icon = 'NONE'
|
||||
elif len(snap_elements) == 1:
|
||||
text = ""
|
||||
for elem in snap_elements:
|
||||
icon = snap_items[elem].icon
|
||||
@@ -632,6 +636,14 @@ class VIEW3D_HT_header(Header):
|
||||
row = layout.row(align=True)
|
||||
row.prop(tool_settings, "use_snap", text="")
|
||||
|
||||
if object_mode == 'EDIT' and obj.type == 'MESH':
|
||||
row.prop(
|
||||
tool_settings,
|
||||
"use_snap_retopology_mode",
|
||||
text="",
|
||||
icon='MOD_MESHDEFORM',
|
||||
)
|
||||
|
||||
sub = row.row(align=True)
|
||||
sub.popover(
|
||||
panel="VIEW3D_PT_snapping",
|
||||
@@ -6808,73 +6820,106 @@ class VIEW3D_PT_snapping(Panel):
|
||||
tool_settings = context.tool_settings
|
||||
snap_elements = tool_settings.snap_elements
|
||||
obj = context.active_object
|
||||
object_mode = 'OBJECT' if obj is None else obj.mode
|
||||
object_mode = obj.mode if obj else 'OBJECT'
|
||||
show_target_options = object_mode == 'EDIT' and obj.type not in {'LATTICE', 'META', 'FONT'}
|
||||
multiple_objects = len(context.objects_in_mode) > 1
|
||||
retopo_mode = object_mode == 'EDIT' and tool_settings.use_snap_retopology_mode
|
||||
|
||||
layout = self.layout
|
||||
col = layout.column()
|
||||
col.label(text="Snap To")
|
||||
col.prop(tool_settings, "snap_elements", expand=True)
|
||||
|
||||
col.separator()
|
||||
if 'INCREMENT' in snap_elements:
|
||||
col.prop(tool_settings, "use_snap_grid_absolute")
|
||||
if show_target_options:
|
||||
col.prop(
|
||||
tool_settings,
|
||||
"use_snap_retopology_mode",
|
||||
text="Retopology Mode",
|
||||
# icon='MOD_MESHDEFORM',
|
||||
)
|
||||
|
||||
if not retopo_mode:
|
||||
col_snapto = col.column(align=True, heading="Snap To")
|
||||
col_snapto.prop(tool_settings, "snap_elements", expand=True)
|
||||
else:
|
||||
col_snapto_edited = col.column(align=True, heading="Snap To Edited")
|
||||
col_snapto_edited.prop_enum(tool_settings, "snap_elements", 'VERTEX')
|
||||
col_snapto_edited.prop_enum(tool_settings, "snap_elements", 'EDGE')
|
||||
col_snapto_edited.prop_enum(tool_settings, "snap_elements", 'EDGE_MIDPOINT')
|
||||
col_snapto_edited.prop_enum(tool_settings, "snap_elements", 'EDGE_PERPENDICULAR')
|
||||
|
||||
col_snapto_nonedited = col.column(align=True, heading="Snap To Non-Edited")
|
||||
col_snapto_nonedited.prop_enum(tool_settings, "snap_elements", 'FACE')
|
||||
col_snapto_nonedited.prop_enum(tool_settings, "snap_elements", 'FACE_NEAREST')
|
||||
# row = col_snapto_nonedited.row(align=True)
|
||||
# row.enabled = False
|
||||
# row.prop_enum(tool_settings, "snap_elements", 'FACE_NEAREST')
|
||||
|
||||
if not retopo_mode and snap_elements - {'INCREMENT', 'FACE_NEAREST'}:
|
||||
col_snapwith = col.column(align=True)
|
||||
col_snapwith.label(text='Snap With')
|
||||
col_snapwith.row().prop(tool_settings, "snap_target", expand=True)
|
||||
|
||||
if snap_elements != {'INCREMENT'}:
|
||||
if snap_elements != {'FACE_NEAREST'}:
|
||||
col.label(text="Snap With")
|
||||
row = col.row(align=True)
|
||||
row.prop(tool_settings, "snap_target", expand=True)
|
||||
|
||||
if obj:
|
||||
col.label(text="Target Selection")
|
||||
col_targetsel = col.column(align=True)
|
||||
if object_mode == 'EDIT' and obj.type not in {'LATTICE', 'META', 'FONT'}:
|
||||
col_targetsel = col.column(align=True, heading="Target Selection")
|
||||
if not retopo_mode and show_target_options:
|
||||
if not multiple_objects:
|
||||
col_targetsel.prop(
|
||||
tool_settings,
|
||||
'use_snap_self',
|
||||
text="Include Edited",
|
||||
# description='Snap onto edited objects (Edit Mode Only)',
|
||||
icon='EDITMODE_HLT',
|
||||
)
|
||||
else:
|
||||
col_targetsel.prop(
|
||||
tool_settings,
|
||||
"use_snap_self",
|
||||
text="Include Active",
|
||||
text="Include Active Edited",
|
||||
# description='Snap onto active edited object (Edit Mode Only)',
|
||||
icon='EDITMODE_HLT',
|
||||
)
|
||||
col_targetsel.prop(
|
||||
tool_settings,
|
||||
"use_snap_edit",
|
||||
text="Include Edited",
|
||||
text="Include Other Edited",
|
||||
# description='Snap onto non-active edited object(s) (Edit Mode Only)',
|
||||
icon='OUTLINER_DATA_MESH',
|
||||
)
|
||||
col_targetsel.prop(
|
||||
tool_settings,
|
||||
"use_snap_nonedit",
|
||||
text="Include Non-Edited",
|
||||
icon='OUTLINER_OB_MESH',
|
||||
)
|
||||
col_targetsel.prop(
|
||||
tool_settings,
|
||||
"use_snap_selectable",
|
||||
text="Exclude Non-Selectable",
|
||||
icon='RESTRICT_SELECT_OFF',
|
||||
"use_snap_nonedit",
|
||||
text="Include Non-Edited",
|
||||
icon='OUTLINER_OB_MESH',
|
||||
)
|
||||
col_targetsel.prop(
|
||||
tool_settings,
|
||||
"use_snap_selectable_only",
|
||||
text="Exclude Non-Selectable",
|
||||
icon='RESTRICT_SELECT_OFF',
|
||||
)
|
||||
|
||||
if object_mode in {'OBJECT', 'POSE', 'EDIT', 'WEIGHT_PAINT'}:
|
||||
col.prop(tool_settings, "use_snap_align_rotation")
|
||||
|
||||
col.prop(tool_settings, "use_snap_backface_culling")
|
||||
|
||||
if 'FACE' in snap_elements:
|
||||
col.prop(tool_settings, "use_snap_project")
|
||||
|
||||
col_options = col.column(heading="Options")
|
||||
if 'INCREMENT' in snap_elements:
|
||||
col_options.prop(tool_settings, "use_snap_grid_absolute")
|
||||
if snap_elements != {'INCREMENT'}:
|
||||
# TODO(@gfxcoder): Does WEIGHT_PAINT have any snapping?
|
||||
if object_mode in {'OBJECT', 'POSE', 'EDIT', 'WEIGHT_PAINT'}:
|
||||
col_options.prop(tool_settings, "use_snap_align_rotation")
|
||||
if snap_elements != {'FACE_NEAREST'}:
|
||||
col_options.prop(tool_settings, "use_snap_backface_culling")
|
||||
if 'FACE' in snap_elements and not retopo_mode:
|
||||
col_options.prop(tool_settings, "use_snap_project")
|
||||
if 'FACE_NEAREST' in snap_elements:
|
||||
col.prop(tool_settings, 'use_snap_to_same_target')
|
||||
col_options.prop(tool_settings, 'use_snap_to_same_target')
|
||||
if object_mode == 'EDIT':
|
||||
col.prop(tool_settings, 'snap_face_nearest_steps')
|
||||
|
||||
col_options.prop(tool_settings, 'snap_face_nearest_steps')
|
||||
if 'VOLUME' in snap_elements:
|
||||
col.prop(tool_settings, "use_snap_peel_object")
|
||||
col_options.prop(tool_settings, "use_snap_peel_object")
|
||||
|
||||
col.label(text="Affect")
|
||||
row = col.row(align=True)
|
||||
row.prop(tool_settings, "use_snap_translate", text="Move", toggle=True)
|
||||
row.prop(tool_settings, "use_snap_rotate", text="Rotate", toggle=True)
|
||||
row.prop(tool_settings, "use_snap_scale", text="Scale", toggle=True)
|
||||
if not retopo_mode:
|
||||
row = col.row(align=True, heading="Affect")
|
||||
row.prop(tool_settings, "use_snap_translate", text="Move", toggle=True)
|
||||
row.prop(tool_settings, "use_snap_rotate", text="Rotate", toggle=True)
|
||||
row.prop(tool_settings, "use_snap_scale", text="Scale", toggle=True)
|
||||
|
||||
|
||||
class VIEW3D_PT_proportional_edit(Panel):
|
||||
|
@@ -26,7 +26,6 @@ struct View3D;
|
||||
/* transform_snap_object.cc */
|
||||
|
||||
/* ED_transform_snap_object_*** API */
|
||||
|
||||
typedef enum eSnapEditType {
|
||||
SNAP_GEOM_FINAL = 0,
|
||||
SNAP_GEOM_CAGE = 1,
|
||||
|
@@ -655,6 +655,7 @@ void Transform_Properties(struct wmOperatorType *ot, int flags)
|
||||
}
|
||||
|
||||
if (flags & P_SNAP) {
|
||||
// TODO: rename `snap` to `use_snap`?
|
||||
prop = RNA_def_boolean(ot->srna, "snap", false, "Use Snapping Options", "");
|
||||
RNA_def_property_flag(prop, PROP_HIDDEN);
|
||||
|
||||
@@ -686,6 +687,12 @@ void Transform_Properties(struct wmOperatorType *ot, int flags)
|
||||
prop = RNA_def_boolean(
|
||||
ot->srna, "use_snap_selectable_only", false, "Target: Exclude Non-Selectable", "");
|
||||
RNA_def_property_flag(prop, PROP_HIDDEN);
|
||||
prop = RNA_def_boolean(ot->srna,
|
||||
"use_snap_retopology_mode",
|
||||
true,
|
||||
"Target: Retopology Mode",
|
||||
"Optimize snapping options for retopology work");
|
||||
RNA_def_property_flag(prop, PROP_HIDDEN);
|
||||
|
||||
/* Face Nearest options */
|
||||
prop = RNA_def_boolean(
|
||||
|
@@ -128,11 +128,11 @@ bool activeSnap(const TransInfo *t)
|
||||
|
||||
bool activeSnap_SnappingIndividual(const TransInfo *t)
|
||||
{
|
||||
if (activeSnap(t) && t->tsnap.mode & SCE_SNAP_MODE_FACE_NEAREST) {
|
||||
if (activeSnap(t) && (t->tsnap.mode & SCE_SNAP_MODE_FACE_NEAREST)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!t->tsnap.project) {
|
||||
if ((t->tsnap.mode & SCE_SNAP_MODE_FACE_RAYCAST) && !t->tsnap.project) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -168,9 +168,22 @@ bool activeSnap_SnappingAsGroup(const TransInfo *t)
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool useSnapRetopoMode(const TransInfo *t)
|
||||
{
|
||||
if (t->spacetype != SPACE_VIEW3D || t->obedit_type != OB_MESH) {
|
||||
/* Not the correct context for retopology mode. */
|
||||
return false;
|
||||
}
|
||||
return t->settings->snap_flag & SCE_SNAP_RETOPOLOGY_MODE;
|
||||
}
|
||||
|
||||
bool transformModeUseSnap(const TransInfo *t)
|
||||
{
|
||||
ToolSettings *ts = t->settings;
|
||||
if (useSnapRetopoMode(t)) {
|
||||
/* Always use snap when retopology mode is enabled. */
|
||||
return true;
|
||||
}
|
||||
if (t->mode == TFM_TRANSLATION) {
|
||||
return (ts->snap_transform_mode_flag & SCE_SNAP_TRANSFORM_MODE_TRANSLATE) != 0;
|
||||
}
|
||||
@@ -509,9 +522,13 @@ void applySnappingIndividual(TransInfo *t)
|
||||
continue;
|
||||
}
|
||||
|
||||
/* If both face ray-cast and face nearest methods are enabled, start with face ray-cast and
|
||||
* fallback to face nearest ray-cast does not hit. */
|
||||
bool hit = applyFaceProject(t, tc, td);
|
||||
/* If both face raycast and face nearest methods are enabled, start with face raycast and
|
||||
* fallback to face nearest raycast does not hit. */
|
||||
bool hit = false;
|
||||
if (t->tsnap.flag & SCE_SNAP_PROJECT) {
|
||||
/* Only raycast in this function if projecting individual elements. */
|
||||
hit = applyFaceProject(t, tc, td);
|
||||
}
|
||||
if (!hit) {
|
||||
applyFaceNearest(t, tc, td);
|
||||
}
|
||||
@@ -738,6 +755,7 @@ static eSnapTargetSelect snap_target_select_from_spacetype(TransInfo *t)
|
||||
bool use_snap_edit = (t->tsnap.target_select & SCE_SNAP_TARGET_NOT_EDITED) == 0;
|
||||
bool use_snap_nonedit = (t->tsnap.target_select & SCE_SNAP_TARGET_NOT_NONEDITED) == 0;
|
||||
bool use_snap_selectable_only = (t->tsnap.target_select & SCE_SNAP_TARGET_ONLY_SELECTABLE) != 0;
|
||||
// bool use_retopology_mode = (t->tsnap.target_select & SCE_SNAP_TARGET_RETOPOLOGY_MODE) != 0;
|
||||
|
||||
if (ELEM(t->spacetype, SPACE_VIEW3D, SPACE_IMAGE) && !(t->options & CTX_CAMERA)) {
|
||||
if (base_act && (base_act->object->mode & OB_MODE_PARTICLE_EDIT)) {
|
||||
@@ -840,6 +858,25 @@ static void initSnappingMode(TransInfo *t)
|
||||
}
|
||||
}
|
||||
|
||||
static void initSnappingRetopoMode(TransInfo *t)
|
||||
{
|
||||
if (!useSnapRetopoMode(t)) {
|
||||
/* Not using retopology mode. */
|
||||
return;
|
||||
}
|
||||
|
||||
/* Enable all possible targets. The targets will be filtered based on snap method. */
|
||||
t->tsnap.source_select = SCE_SNAP_SOURCE_ACTIVE;
|
||||
t->tsnap.targetSnap = TargetSnapActive;
|
||||
t->tsnap.target_select &= ~(SCE_SNAP_TARGET_NOT_ACTIVE | SCE_SNAP_TARGET_NOT_EDITED |
|
||||
SCE_SNAP_TARGET_NOT_NONEDITED);
|
||||
t->tsnap.flag |= (SCE_SNAP_TO_INCLUDE_EDITED | SCE_SNAP_TO_INCLUDE_NONEDITED);
|
||||
t->tsnap.flag &= ~(SCE_SNAP_NOT_TO_ACTIVE);
|
||||
t->tsnap.mode &= ~(SCE_SNAP_MODE_INCREMENT | SCE_SNAP_MODE_GRID | SCE_SNAP_MODE_VOLUME);
|
||||
t->tsnap.project = true;
|
||||
t->tsnap.flag |= SCE_SNAP_PROJECT;
|
||||
}
|
||||
|
||||
void initSnapping(TransInfo *t, wmOperator *op)
|
||||
{
|
||||
ToolSettings *ts = t->settings;
|
||||
@@ -912,12 +949,19 @@ void initSnapping(TransInfo *t, wmOperator *op)
|
||||
SCE_SNAP_TARGET_NOT_NONEDITED);
|
||||
}
|
||||
|
||||
if ((prop = RNA_struct_find_property(op->ptr, "use_snap_selectable")) &&
|
||||
if ((prop = RNA_struct_find_property(op->ptr, "use_snap_selectable_only")) &&
|
||||
RNA_property_is_set(op->ptr, prop)) {
|
||||
SET_FLAG_FROM_TEST(t->tsnap.target_select,
|
||||
RNA_property_boolean_get(op->ptr, prop),
|
||||
SCE_SNAP_TARGET_ONLY_SELECTABLE);
|
||||
}
|
||||
|
||||
if ((prop = RNA_struct_find_property(op->ptr, "use_snap_retopology_mode")) &&
|
||||
RNA_property_is_set(op->ptr, prop)) {
|
||||
SET_FLAG_FROM_TEST(t->tsnap.target_select,
|
||||
RNA_property_boolean_get(op->ptr, prop),
|
||||
SCE_SNAP_TARGET_RETOPOLOGY_MODE);
|
||||
}
|
||||
}
|
||||
}
|
||||
/* use scene defaults only when transform is modal */
|
||||
@@ -926,9 +970,10 @@ void initSnapping(TransInfo *t, wmOperator *op)
|
||||
t->modifiers |= MOD_SNAP;
|
||||
}
|
||||
|
||||
t->tsnap.target_select = SCE_SNAP_TARGET_ALL;
|
||||
t->tsnap.align = ((t->tsnap.flag & SCE_SNAP_ROTATE) != 0);
|
||||
t->tsnap.project = ((t->tsnap.flag & SCE_SNAP_PROJECT) != 0);
|
||||
t->tsnap.peel = ((t->tsnap.flag & SCE_SNAP_PROJECT) != 0);
|
||||
t->tsnap.peel = ((t->tsnap.flag & SCE_SNAP_PEEL_OBJECT) != 0);
|
||||
SET_FLAG_FROM_TEST(t->tsnap.target_select,
|
||||
(ts->snap_flag & SCE_SNAP_NOT_TO_ACTIVE),
|
||||
SCE_SNAP_TARGET_NOT_ACTIVE);
|
||||
@@ -941,11 +986,15 @@ void initSnapping(TransInfo *t, wmOperator *op)
|
||||
SET_FLAG_FROM_TEST(t->tsnap.target_select,
|
||||
(ts->snap_flag & SCE_SNAP_TO_ONLY_SELECTABLE),
|
||||
SCE_SNAP_TARGET_ONLY_SELECTABLE);
|
||||
SET_FLAG_FROM_TEST(t->tsnap.target_select,
|
||||
(t->settings->snap_flag & SCE_SNAP_RETOPOLOGY_MODE),
|
||||
SCE_SNAP_TARGET_RETOPOLOGY_MODE);
|
||||
}
|
||||
|
||||
t->tsnap.source_select = snap_source;
|
||||
|
||||
initSnappingMode(t);
|
||||
initSnappingRetopoMode(t);
|
||||
}
|
||||
|
||||
void freeSnapping(TransInfo *t)
|
||||
@@ -1446,16 +1495,28 @@ eSnapMode snapObjectsTransform(
|
||||
TransInfo *t, const float mval[2], float *dist_px, float r_loc[3], float r_no[3])
|
||||
{
|
||||
float *target = (t->tsnap.status & TARGET_INIT) ? t->tsnap.snapTarget : t->center_global;
|
||||
|
||||
// filter mode to group snapping modes
|
||||
eSnapMode mode = t->tsnap.mode;
|
||||
mode &= ~(SCE_SNAP_MODE_FACE_NEAREST);
|
||||
if (t->tsnap.flag & SCE_SNAP_PROJECT) {
|
||||
mode &= ~(SCE_SNAP_MODE_FACE_RAYCAST);
|
||||
}
|
||||
|
||||
const bool use_retopo_mode = (t->tsnap.target_select & SCE_SNAP_TARGET_RETOPOLOGY_MODE);
|
||||
const bool face_raycast_only = t->settings->snap_mode == SCE_SNAP_MODE_FACE_RAYCAST;
|
||||
const bool use_occlusion_test = use_retopo_mode || !face_raycast_only;
|
||||
|
||||
return ED_transform_snap_object_project_view3d(
|
||||
t->tsnap.object_context,
|
||||
t->depsgraph,
|
||||
t->region,
|
||||
t->view,
|
||||
t->tsnap.mode,
|
||||
mode,
|
||||
&(const struct SnapObjectParams){
|
||||
.snap_target_select = t->tsnap.target_select,
|
||||
.edit_mode_type = (t->flag & T_EDIT) != 0 ? SNAP_GEOM_EDIT : SNAP_GEOM_FINAL,
|
||||
.use_occlusion_test = t->settings->snap_mode != SCE_SNAP_MODE_FACE_RAYCAST,
|
||||
.use_occlusion_test = use_occlusion_test,
|
||||
.use_backface_culling = t->tsnap.use_backface_culling,
|
||||
},
|
||||
NULL,
|
||||
|
@@ -1025,6 +1025,12 @@ static void raycast_obj_fn(SnapObjectContext *sctx,
|
||||
/* read/write args */
|
||||
float *ray_depth = dt->ray_depth;
|
||||
|
||||
const bool use_retopo_mode = params->snap_target_select & SCE_SNAP_TARGET_RETOPOLOGY_MODE;
|
||||
const bool is_object_edited = BKE_object_is_in_editmode(ob_eval);
|
||||
if (use_retopo_mode && is_object_edited) {
|
||||
return;
|
||||
}
|
||||
|
||||
bool retval = false;
|
||||
if (use_occlusion_test) {
|
||||
if (ELEM(ob_eval->dt, OB_BOUNDBOX, OB_WIRE)) {
|
||||
@@ -1261,7 +1267,7 @@ static bool nearest_world_tree(SnapObjectContext *UNUSED(sctx),
|
||||
|
||||
/* compute offset between init co and prev co in local space */
|
||||
float init_co_local[3], curr_co_local[3];
|
||||
float delta_local[3];
|
||||
float delta_local[3], delta_step[3];
|
||||
mul_v3_m4v3(init_co_local, imat, init_co);
|
||||
mul_v3_m4v3(curr_co_local, imat, curr_co);
|
||||
sub_v3_v3v3(delta_local, curr_co_local, init_co_local);
|
||||
@@ -1273,8 +1279,8 @@ static bool nearest_world_tree(SnapObjectContext *UNUSED(sctx),
|
||||
}
|
||||
else {
|
||||
/* NOTE: when `params->face_nearest_steps == 1`, the return variables of function below contain
|
||||
* the answer. We could return immediately after updating r_loc, r_no, r_index, but that would
|
||||
* also complicate the code. Foregoing slight optimization for code clarity. */
|
||||
* the answer. We could return immediately after updating 'r_loc', 'r_no', 'r_index', but that
|
||||
* would also complicate the code. Foregoing slight optimization for code clarity. */
|
||||
nearest_world_tree_co(
|
||||
tree, nearest_cb, treedata, curr_co_local, nullptr, nullptr, nullptr, &dist_sq);
|
||||
}
|
||||
@@ -1284,8 +1290,9 @@ static bool nearest_world_tree(SnapObjectContext *UNUSED(sctx),
|
||||
*r_dist_sq = dist_sq;
|
||||
|
||||
/* scale to make `snap_face_nearest_steps` steps */
|
||||
float step_scale_factor = 1.0f / max_ff(1.0f, (float)params->face_nearest_steps);
|
||||
mul_v3_fl(delta_local, step_scale_factor);
|
||||
int steps = max_ii(1, params->face_nearest_steps);
|
||||
float factor = 1.0f / (float)steps;
|
||||
mul_v3_v3fl(delta_step, delta_local, factor);
|
||||
|
||||
float co_local[3];
|
||||
float no_local[3];
|
||||
@@ -1293,8 +1300,8 @@ static bool nearest_world_tree(SnapObjectContext *UNUSED(sctx),
|
||||
|
||||
copy_v3_v3(co_local, init_co_local);
|
||||
|
||||
for (int i = 0; i < params->face_nearest_steps; i++) {
|
||||
add_v3_v3(co_local, delta_local);
|
||||
for (int i = 0; i < steps; i++) {
|
||||
add_v3_v3(co_local, delta_step);
|
||||
nearest_world_tree_co(
|
||||
tree, nearest_cb, treedata, co_local, co_local, no_local, &index, nullptr);
|
||||
}
|
||||
@@ -1384,6 +1391,12 @@ static void nearest_world_object_fn(SnapObjectContext *sctx,
|
||||
{
|
||||
struct NearestWorldObjUserData *dt = static_cast<NearestWorldObjUserData *>(data);
|
||||
|
||||
const bool use_retopo_mode = params->snap_target_select & SCE_SNAP_TARGET_RETOPOLOGY_MODE;
|
||||
const bool is_object_edited = BKE_object_is_in_editmode(ob_eval);
|
||||
if (use_retopo_mode && is_object_edited) {
|
||||
return;
|
||||
}
|
||||
|
||||
bool retval = false;
|
||||
switch (ob_eval->type) {
|
||||
case OB_MESH: {
|
||||
@@ -3055,6 +3068,12 @@ static void snap_obj_fn(SnapObjectContext *sctx,
|
||||
SnapObjUserData *dt = static_cast<SnapObjUserData *>(data);
|
||||
eSnapMode retval = SCE_SNAP_MODE_NONE;
|
||||
|
||||
const bool use_retopo_mode = (params->snap_target_select & SCE_SNAP_TARGET_RETOPOLOGY_MODE);
|
||||
const bool is_object_edited = BKE_object_is_in_editmode(ob_eval);
|
||||
if (use_retopo_mode && !is_object_edited) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (ob_eval->type) {
|
||||
case OB_MESH: {
|
||||
const eSnapEditType edit_mode_type = params->edit_mode_type;
|
||||
@@ -3384,47 +3403,29 @@ static eSnapMode transform_snap_context_project_view3d_mixed_impl(SnapObjectCont
|
||||
sctx->runtime.region = region;
|
||||
sctx->runtime.v3d = v3d;
|
||||
|
||||
BLI_assert((snap_to_flag & SCE_SNAP_MODE_GEOM) != 0);
|
||||
if ((snap_to_flag & SCE_SNAP_MODE_GEOM) == 0) {
|
||||
return SCE_SNAP_MODE_NONE;
|
||||
}
|
||||
|
||||
// BLI_assert((snap_to_flag & SCE_SNAP_MODE_GEOM) != 0);
|
||||
|
||||
eSnapMode retval = SCE_SNAP_MODE_NONE;
|
||||
|
||||
bool has_hit = false;
|
||||
Object *ob_eval = nullptr;
|
||||
Object *ob_ray = nullptr;
|
||||
float loc[3];
|
||||
/* Not all snapping callbacks set the normal,
|
||||
* initialize this since any hit copies both the `loc` and `no`. */
|
||||
float no[3] = {0.0f, 0.0f, 0.0f};
|
||||
float obmat[4][4];
|
||||
float obmat_ray[4][4];
|
||||
int index = -1;
|
||||
|
||||
const RegionView3D *rv3d = static_cast<RegionView3D *>(region->regiondata);
|
||||
|
||||
bool use_occlusion_test = params->use_occlusion_test && !XRAY_ENABLED(v3d);
|
||||
|
||||
/* Note: if both face raycast and face nearest are enabled, first find result of nearest, then
|
||||
* override with raycast. */
|
||||
if ((snap_to_flag & SCE_SNAP_MODE_FACE_NEAREST) && !has_hit) {
|
||||
has_hit = nearestWorldObjects(
|
||||
sctx, params, init_co, prev_co, loc, no, &index, &ob_eval, obmat);
|
||||
|
||||
if (has_hit) {
|
||||
retval = SCE_SNAP_MODE_FACE_NEAREST;
|
||||
|
||||
copy_v3_v3(r_loc, loc);
|
||||
if (r_no) {
|
||||
copy_v3_v3(r_no, no);
|
||||
}
|
||||
if (r_ob) {
|
||||
*r_ob = ob_eval;
|
||||
}
|
||||
if (r_obmat) {
|
||||
copy_m4_m4(r_obmat, obmat);
|
||||
}
|
||||
if (r_index) {
|
||||
*r_index = index;
|
||||
}
|
||||
}
|
||||
}
|
||||
const bool use_retopo_mode = params->snap_target_select & SCE_SNAP_TARGET_RETOPOLOGY_MODE;
|
||||
const bool use_occlusion_test = params->use_occlusion_test && !XRAY_ENABLED(v3d);
|
||||
|
||||
if (snap_to_flag & SCE_SNAP_MODE_FACE_RAYCAST || use_occlusion_test) {
|
||||
float ray_start[3], ray_normal[3];
|
||||
@@ -3443,8 +3444,8 @@ static eSnapMode transform_snap_context_project_view3d_mixed_impl(SnapObjectCont
|
||||
loc,
|
||||
no,
|
||||
&index,
|
||||
&ob_eval,
|
||||
obmat,
|
||||
&ob_ray, // &ob_eval,
|
||||
obmat_ray, // obmat,
|
||||
nullptr);
|
||||
|
||||
if (has_hit) {
|
||||
@@ -3453,6 +3454,7 @@ static eSnapMode transform_snap_context_project_view3d_mixed_impl(SnapObjectCont
|
||||
}
|
||||
|
||||
if ((snap_to_flag & SCE_SNAP_MODE_FACE_RAYCAST)) {
|
||||
/* Record snap results only if face raycast snapping mode is enabled. */
|
||||
retval = SCE_SNAP_MODE_FACE_RAYCAST;
|
||||
|
||||
copy_v3_v3(r_loc, loc);
|
||||
@@ -3460,15 +3462,19 @@ static eSnapMode transform_snap_context_project_view3d_mixed_impl(SnapObjectCont
|
||||
copy_v3_v3(r_no, no);
|
||||
}
|
||||
if (r_ob) {
|
||||
*r_ob = ob_eval;
|
||||
*r_ob = ob_ray; // ob_eval;
|
||||
}
|
||||
if (r_obmat) {
|
||||
copy_m4_m4(r_obmat, obmat);
|
||||
copy_m4_m4(r_obmat, obmat_ray); // obmat
|
||||
}
|
||||
if (r_index) {
|
||||
*r_index = index;
|
||||
}
|
||||
}
|
||||
if (use_occlusion_test && !use_retopo_mode) {
|
||||
ob_eval = ob_ray;
|
||||
copy_m4_m4(obmat, obmat_ray);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3503,7 +3509,7 @@ static eSnapMode transform_snap_context_project_view3d_mixed_impl(SnapObjectCont
|
||||
sctx->runtime.has_occlusion_plane = false;
|
||||
|
||||
/* By convention we only snap to the original elements of a curve. */
|
||||
if (has_hit && ob_eval->type != OB_CURVES_LEGACY) {
|
||||
if (has_hit && ob_ray->type != OB_CURVES_LEGACY) {
|
||||
/* Compute the new clip_pane but do not add it yet. */
|
||||
float new_clipplane[4];
|
||||
BLI_ASSERT_UNIT_V3(no);
|
||||
@@ -3517,8 +3523,9 @@ static eSnapMode transform_snap_context_project_view3d_mixed_impl(SnapObjectCont
|
||||
new_clipplane[3] += 0.01f;
|
||||
|
||||
/* Try to snap only to the polygon. */
|
||||
elem_test = snap_mesh_polygon(sctx, params, ob_eval, obmat, &dist_px_tmp, loc, no, &index);
|
||||
if (elem_test) {
|
||||
elem_test = snap_mesh_polygon(
|
||||
sctx, params, ob_ray, obmat_ray, &dist_px_tmp, loc, no, &index);
|
||||
if (elem_test && !use_retopo_mode) {
|
||||
elem = elem_test;
|
||||
}
|
||||
|
||||
@@ -3565,6 +3572,31 @@ static eSnapMode transform_snap_context_project_view3d_mixed_impl(SnapObjectCont
|
||||
}
|
||||
}
|
||||
|
||||
/* Note: if both face raycast and face nearest are enabled, first find result of nearest, then
|
||||
* override with raycast. */
|
||||
if ((snap_to_flag & SCE_SNAP_MODE_FACE_NEAREST) && !has_hit) {
|
||||
has_hit = nearestWorldObjects(
|
||||
sctx, params, init_co, prev_co, loc, no, &index, &ob_eval, obmat);
|
||||
|
||||
if (has_hit) {
|
||||
retval = SCE_SNAP_MODE_FACE_NEAREST;
|
||||
|
||||
copy_v3_v3(r_loc, loc);
|
||||
if (r_no) {
|
||||
copy_v3_v3(r_no, no);
|
||||
}
|
||||
if (r_ob) {
|
||||
*r_ob = ob_eval;
|
||||
}
|
||||
if (r_obmat) {
|
||||
copy_m4_m4(r_obmat, obmat);
|
||||
}
|
||||
if (r_index) {
|
||||
*r_index = index;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
|
@@ -2095,6 +2095,7 @@ typedef enum eSnapFlag {
|
||||
SCE_SNAP_TO_INCLUDE_EDITED = (1 << 8),
|
||||
SCE_SNAP_TO_INCLUDE_NONEDITED = (1 << 9),
|
||||
SCE_SNAP_TO_ONLY_SELECTABLE = (1 << 10),
|
||||
SCE_SNAP_RETOPOLOGY_MODE = (1 << 11),
|
||||
} eSnapFlag;
|
||||
/* Due to dependency conflicts with Cycles, header cannot directly include `BLI_utildefines.h`. */
|
||||
/* TODO: move this macro to a more general place. */
|
||||
@@ -2119,6 +2120,7 @@ typedef enum eSnapTargetSelect {
|
||||
SCE_SNAP_TARGET_NOT_EDITED = (1 << 2),
|
||||
SCE_SNAP_TARGET_ONLY_SELECTABLE = (1 << 3),
|
||||
SCE_SNAP_TARGET_NOT_NONEDITED = (1 << 4),
|
||||
SCE_SNAP_TARGET_RETOPOLOGY_MODE = (1 << 5),
|
||||
} eSnapTargetSelect;
|
||||
|
||||
/** #ToolSettings.snap_mode */
|
||||
|
@@ -151,6 +151,16 @@ const EnumPropertyItem rna_enum_snap_element_items[] = {
|
||||
"Snap to increments of grid"},
|
||||
{SCE_SNAP_MODE_VERTEX, "VERTEX", ICON_SNAP_VERTEX, "Vertex", "Snap to vertices"},
|
||||
{SCE_SNAP_MODE_EDGE, "EDGE", ICON_SNAP_EDGE, "Edge", "Snap to edges"},
|
||||
{SCE_SNAP_MODE_EDGE_MIDPOINT,
|
||||
"EDGE_MIDPOINT",
|
||||
ICON_SNAP_MIDPOINT,
|
||||
"Edge Center",
|
||||
"Snap to the middle of edges"},
|
||||
{SCE_SNAP_MODE_EDGE_PERPENDICULAR,
|
||||
"EDGE_PERPENDICULAR",
|
||||
ICON_SNAP_PERPENDICULAR,
|
||||
"Edge Perpendicular",
|
||||
"Snap to the nearest point on an edge"},
|
||||
{SCE_SNAP_MODE_FACE_RAYCAST,
|
||||
"FACE", /* TODO(@gfxcoder): replace with "FACE_RAYCAST" as "FACE" is not descriptive. */
|
||||
ICON_SNAP_FACE,
|
||||
@@ -162,16 +172,6 @@ const EnumPropertyItem rna_enum_snap_element_items[] = {
|
||||
"Face Nearest",
|
||||
"Snap to nearest point on faces"},
|
||||
{SCE_SNAP_MODE_VOLUME, "VOLUME", ICON_SNAP_VOLUME, "Volume", "Snap to volume"},
|
||||
{SCE_SNAP_MODE_EDGE_MIDPOINT,
|
||||
"EDGE_MIDPOINT",
|
||||
ICON_SNAP_MIDPOINT,
|
||||
"Edge Center",
|
||||
"Snap to the middle of edges"},
|
||||
{SCE_SNAP_MODE_EDGE_PERPENDICULAR,
|
||||
"EDGE_PERPENDICULAR",
|
||||
ICON_SNAP_PERPENDICULAR,
|
||||
"Edge Perpendicular",
|
||||
"Snap to the nearest point on an edge"},
|
||||
{0, NULL, 0, NULL, NULL},
|
||||
};
|
||||
|
||||
@@ -3313,10 +3313,10 @@ static void rna_def_tool_settings(BlenderRNA *brna)
|
||||
prop = RNA_def_property(srna, "snap_face_nearest_steps", PROP_INT, PROP_FACTOR);
|
||||
RNA_def_property_int_sdna(prop, NULL, "snap_face_nearest_steps");
|
||||
RNA_def_property_range(prop, 1, 100);
|
||||
RNA_def_property_ui_text(
|
||||
prop,
|
||||
"Face Nearest Steps",
|
||||
"Number of steps to break transformation into for face nearest snapping");
|
||||
RNA_def_property_ui_text(prop,
|
||||
"Face Nearest Steps",
|
||||
"Increase precision and smooth large changes by dividing "
|
||||
"transformation into smaller steps (Face Nearest Only)");
|
||||
|
||||
prop = RNA_def_property(srna, "use_snap_to_same_target", PROP_BOOLEAN, PROP_NONE);
|
||||
RNA_def_property_boolean_sdna(prop, NULL, "snap_flag", SCE_SNAP_KEEP_ON_SAME_OBJECT);
|
||||
@@ -3379,13 +3379,13 @@ static void rna_def_tool_settings(BlenderRNA *brna)
|
||||
prop = RNA_def_property(srna, "use_snap_self", PROP_BOOLEAN, PROP_NONE);
|
||||
RNA_def_property_boolean_negative_sdna(prop, NULL, "snap_flag", SCE_SNAP_NOT_TO_ACTIVE);
|
||||
RNA_def_property_ui_text(
|
||||
prop, "Snap onto Active", "Snap onto itself only if enabled (Edit Mode Only)");
|
||||
prop, "Snap onto Active", "Snap onto active edited object (Edit Mode Only)");
|
||||
RNA_def_property_update(prop, NC_SCENE | ND_TOOLSETTINGS, NULL); /* header redraw */
|
||||
|
||||
prop = RNA_def_property(srna, "use_snap_edit", PROP_BOOLEAN, PROP_NONE);
|
||||
RNA_def_property_boolean_sdna(prop, NULL, "snap_flag", SCE_SNAP_TO_INCLUDE_EDITED);
|
||||
RNA_def_property_ui_text(
|
||||
prop, "Snap onto Edited", "Snap onto non-active objects in Edit Mode (Edit Mode Only)");
|
||||
prop, "Snap onto Edited", "Snap onto non-active edited objects (Edit Mode Only)");
|
||||
RNA_def_property_update(prop, NC_SCENE | ND_TOOLSETTINGS, NULL); /* header redraw */
|
||||
|
||||
prop = RNA_def_property(srna, "use_snap_nonedit", PROP_BOOLEAN, PROP_NONE);
|
||||
@@ -3394,12 +3394,18 @@ static void rna_def_tool_settings(BlenderRNA *brna)
|
||||
prop, "Snap onto Non-edited", "Snap onto objects not in Edit Mode (Edit Mode Only)");
|
||||
RNA_def_property_update(prop, NC_SCENE | ND_TOOLSETTINGS, NULL); /* header redraw */
|
||||
|
||||
prop = RNA_def_property(srna, "use_snap_selectable", PROP_BOOLEAN, PROP_NONE);
|
||||
prop = RNA_def_property(srna, "use_snap_selectable_only", PROP_BOOLEAN, PROP_NONE);
|
||||
RNA_def_property_boolean_sdna(prop, NULL, "snap_flag", SCE_SNAP_TO_ONLY_SELECTABLE);
|
||||
RNA_def_property_ui_text(
|
||||
prop, "Snap onto Selectable Only", "Snap only onto objects that are selectable");
|
||||
RNA_def_property_update(prop, NC_SCENE | ND_TOOLSETTINGS, NULL); /* header redraw */
|
||||
|
||||
prop = RNA_def_property(srna, "use_snap_retopology_mode", PROP_BOOLEAN, PROP_NONE);
|
||||
RNA_def_property_boolean_sdna(prop, NULL, "snap_flag", SCE_SNAP_RETOPOLOGY_MODE);
|
||||
RNA_def_property_ui_text(
|
||||
prop, "Retopology Snapping Mode", "Optimize snapping options for retopology work");
|
||||
RNA_def_property_update(prop, NC_SCENE | ND_TOOLSETTINGS, NULL); /* header redraw */
|
||||
|
||||
prop = RNA_def_property(srna, "use_snap_translate", PROP_BOOLEAN, PROP_NONE);
|
||||
RNA_def_property_boolean_sdna(
|
||||
prop, NULL, "snap_transform_mode_flag", SCE_SNAP_TRANSFORM_MODE_TRANSLATE);
|
||||
|
@@ -714,6 +714,11 @@ void RNA_api_window(StructRNA *srna)
|
||||
FunctionRNA *func;
|
||||
PropertyRNA *parm;
|
||||
|
||||
func = RNA_def_function(srna, "is_operator_modal", "WM_active_modal_operator_test");
|
||||
RNA_def_function_ui_description(func, "Test if an operator is actively running modal.");
|
||||
parm = RNA_def_boolean(func, "result", 0, "", "Operator running modal");
|
||||
RNA_def_function_return(func, parm);
|
||||
|
||||
func = RNA_def_function(srna, "cursor_warp", "WM_cursor_warp");
|
||||
parm = RNA_def_int(func, "x", 0, INT_MIN, INT_MAX, "", "", INT_MIN, INT_MAX);
|
||||
RNA_def_parameter_flags(parm, 0, PARM_REQUIRED);
|
||||
@@ -721,6 +726,13 @@ void RNA_api_window(StructRNA *srna)
|
||||
RNA_def_parameter_flags(parm, 0, PARM_REQUIRED);
|
||||
RNA_def_function_ui_description(func, "Set the cursor position");
|
||||
|
||||
func = RNA_def_function(srna, "cursor_warp_relative", "WM_cursor_warp_relative");
|
||||
parm = RNA_def_int(func, "x", 0, INT_MIN, INT_MAX, "", "Offset of x", INT_MIN, INT_MAX);
|
||||
RNA_def_parameter_flags(parm, 0, PARM_REQUIRED);
|
||||
parm = RNA_def_int(func, "y", 0, INT_MIN, INT_MAX, "", "Offset of y", INT_MIN, INT_MAX);
|
||||
RNA_def_parameter_flags(parm, 0, PARM_REQUIRED);
|
||||
RNA_def_function_ui_description(func, "Offset the cursor position");
|
||||
|
||||
func = RNA_def_function(srna, "cursor_set", "WM_cursor_set");
|
||||
parm = RNA_def_property(func, "cursor", PROP_ENUM, PROP_NONE);
|
||||
RNA_def_property_enum_items(parm, rna_enum_window_cursor_items);
|
||||
|
@@ -310,10 +310,20 @@ void WM_paint_cursor_remove_by_type(struct wmWindowManager *wm,
|
||||
void (*free)(void *));
|
||||
void WM_paint_cursor_tag_redraw(struct wmWindow *win, struct ARegion *region);
|
||||
|
||||
/**
|
||||
* Determines whether an operator is actively running modal.
|
||||
*/
|
||||
bool WM_active_modal_operator_test(struct wmWindow *win);
|
||||
|
||||
/**
|
||||
* This function requires access to the GHOST_SystemHandle (g_system).
|
||||
*/
|
||||
void WM_cursor_warp(struct wmWindow *win, int x, int y);
|
||||
void WM_cursor_warp_relative(struct wmWindow *win, int x, int y);
|
||||
/**
|
||||
* Set x, y to values we can actually position the cursor to.
|
||||
*/
|
||||
void WM_cursor_compatible_xy(wmWindow *win, int *x, int *y);
|
||||
|
||||
/* Handlers. */
|
||||
|
||||
|
@@ -278,14 +278,6 @@ void WM_cursor_grab_disable(wmWindow *win, const int mouse_ungrab_xy[2])
|
||||
}
|
||||
}
|
||||
|
||||
static void wm_cursor_warp_relative(wmWindow *win, int x, int y)
|
||||
{
|
||||
/* NOTE: don't use wmEvent coords because of continuous grab T36409. */
|
||||
int cx, cy;
|
||||
wm_cursor_position_get(win, &cx, &cy);
|
||||
WM_cursor_warp(win, cx + x, cy + y);
|
||||
}
|
||||
|
||||
bool wm_cursor_arrow_move(wmWindow *win, const wmEvent *event)
|
||||
{
|
||||
/* TODO: give it a modal keymap? Hard coded for now */
|
||||
@@ -295,19 +287,19 @@ bool wm_cursor_arrow_move(wmWindow *win, const wmEvent *event)
|
||||
float fac = GHOST_GetNativePixelSize(win->ghostwin);
|
||||
|
||||
if (event->type == EVT_UPARROWKEY) {
|
||||
wm_cursor_warp_relative(win, 0, fac);
|
||||
WM_cursor_warp_relative(win, 0, fac);
|
||||
return 1;
|
||||
}
|
||||
if (event->type == EVT_DOWNARROWKEY) {
|
||||
wm_cursor_warp_relative(win, 0, -fac);
|
||||
WM_cursor_warp_relative(win, 0, -fac);
|
||||
return 1;
|
||||
}
|
||||
if (event->type == EVT_LEFTARROWKEY) {
|
||||
wm_cursor_warp_relative(win, -fac, 0);
|
||||
WM_cursor_warp_relative(win, -fac, 0);
|
||||
return 1;
|
||||
}
|
||||
if (event->type == EVT_RIGHTARROWKEY) {
|
||||
wm_cursor_warp_relative(win, fac, 0);
|
||||
WM_cursor_warp_relative(win, fac, 0);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
@@ -2000,6 +2000,22 @@ void WM_init_native_pixels(bool do_it)
|
||||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name Operator API
|
||||
* \{ */
|
||||
|
||||
bool WM_active_modal_operator_test(struct wmWindow *win)
|
||||
{
|
||||
LISTBASE_FOREACH (wmEventHandler *, handler_base, &win->modalhandlers) {
|
||||
if (handler_base->type == WM_HANDLER_TYPE_OP) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name Cursor API
|
||||
* \{ */
|
||||
@@ -2038,6 +2054,25 @@ void WM_cursor_warp(wmWindow *win, int x, int y)
|
||||
}
|
||||
}
|
||||
|
||||
void WM_cursor_warp_relative(wmWindow *win, int x, int y)
|
||||
{
|
||||
if (win && win->ghostwin) {
|
||||
/* NOTE: don't use wmEvent coords because of continuous grab T36409. */
|
||||
int cx, cy;
|
||||
wm_cursor_position_get(win, &cx, &cy);
|
||||
WM_cursor_warp(win, cx + x, cy + y);
|
||||
}
|
||||
}
|
||||
|
||||
void WM_cursor_compatible_xy(wmWindow *win, int *x, int *y)
|
||||
{
|
||||
float f = GHOST_GetNativePixelSize(win->ghostwin);
|
||||
if (f != 1.0f) {
|
||||
*x = (int)(*x / f) * f;
|
||||
*y = (int)(*y / f) * f;
|
||||
}
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
|
Reference in New Issue
Block a user