Sculpt-dev: Roll brush tests.

Wrote test code for a "roll" texture mapping
mode.  This is purely a development test, there
is no user-visible functionality here.
This commit is contained in:
Joseph Eagar 2022-05-17 00:12:44 -07:00
parent 7e969000a2
commit d6d1ddbf0b
7 changed files with 378 additions and 36 deletions

View File

@ -2307,6 +2307,13 @@ float BKE_brush_sample_tex_3d(const Scene *scene,
invradius = 1.0f / ups->pixel_radius;
}
else if (mtex->brush_map_mode == MTEX_MAP_MODE_ROLL) {
// XXX implement me
x = point_2d[0] - ups->tex_mouse[0];
y = point_2d[1] - ups->tex_mouse[1];
invradius = 1.0f / ups->pixel_radius;
}
x *= invradius;
y *= invradius;

View File

@ -9,9 +9,9 @@
#include "BKE_paint.h"
#include "BLI_rect.h"
#include "BLI_compiler_compat.h"
#include "BLI_math.h"
#include "BLI_rect.h"
#include "DNA_scene_types.h"
@ -87,8 +87,16 @@ typedef struct PaintStroke {
float last_mouse_position[2];
float last_world_space_position[3];
float mouse_cubic[4][2];
float world_cubic[4][3];
bool has_cubic_stroke;
bool stroke_over_mesh;
/* space distance covered so far */
int stroke_sample_index;
float stroke_distance;
float stroke_distance_t; // divided by brush radius
@ -109,6 +117,8 @@ typedef struct PaintStroke {
float cached_size_pressure;
/* last pressure will store last pressure value for use in interpolation for space strokes */
float last_pressure;
float last_pressure2;
int stroke_mode;
float last_tablet_event_pressure;
@ -608,6 +618,8 @@ void paint_delete_blur_kernel(BlurKernel *);
/* paint curve defines */
#define PAINT_CURVE_NUM_SEGMENTS 40
bool paint_stroke_has_cubic(const PaintStroke *stroke);
#ifdef __cplusplus
}
#endif

View File

@ -166,8 +166,9 @@ static bool paint_stroke_use_scene_spacing(ToolSettings *ts, Brush *brush, ePain
{
switch (mode) {
case PAINT_MODE_SCULPT:
return BRUSHSET_GET_FINAL_BOOL(brush->channels, ts->sculpt->channels, use_scene_spacing, NULL);
//return brush->flag & BRUSH_SCENE_SPACING;
return BRUSHSET_GET_FINAL_BOOL(
brush->channels, ts->sculpt->channels, use_scene_spacing, NULL);
// return brush->flag & BRUSH_SCENE_SPACING;
default:
break;
}
@ -460,7 +461,7 @@ static bool paint_stroke_use_jitter(ePaintMode mode, Brush *brush, bool invert)
}
/* Put the location of the next stroke dot into the stroke RNA and apply it to the mesh */
static void paint_brush_stroke_add_step(
ATTR_NO_OPT static void paint_brush_stroke_add_step(
bContext *C, wmOperator *op, PaintStroke *stroke, const float mouse_in[2], float pressure)
{
Scene *scene = CTX_data_scene(C);
@ -472,6 +473,8 @@ static void paint_brush_stroke_add_step(
PointerRNA itemptr;
float location[3];
stroke->stroke_sample_index++;
/* the following code is adapted from texture paint. It may not be needed but leaving here
* just in case for reference (code in texpaint removed as part of refactoring).
* It's strange that only texpaint had these guards. */
@ -502,6 +505,7 @@ static void paint_brush_stroke_add_step(
/* copy last position -before- jittering, or space fill code
* will create too many dabs */
copy_v2_v2(stroke->last_mouse_position, mouse_in);
stroke->last_pressure2 = stroke->last_pressure;
stroke->last_pressure = pressure;
if (paint_stroke_use_scene_spacing(scene->toolsettings, brush, mode)) {
@ -566,6 +570,10 @@ static void paint_brush_stroke_add_step(
RNA_float_set(&itemptr, "x_tilt", stroke->x_tilt);
RNA_float_set(&itemptr, "y_tilt", stroke->y_tilt);
if (stroke->has_cubic_stroke) {
RNA_float_set_array(&itemptr, "mouse_cubic", (float *)stroke->mouse_cubic);
}
stroke->update_step(C, op, stroke, &itemptr);
/* don't record this for now, it takes up a lot of memory when doing long
@ -656,7 +664,7 @@ static float paint_space_stroke_spacing(bContext *C,
}
/* apply spacing pressure */
//mapping.pressure = 1.5f - spacing_pressure;
// mapping.pressure = 1.5f - spacing_pressure;
mapping.pressure = spacing_pressure;
if (ss->cache && ss->cache->channels_final) {
@ -830,11 +838,11 @@ static float paint_space_stroke_spacing_variable(bContext *C,
/* For brushes with stroke spacing enabled, moves mouse in steps
* towards the final mouse location. */
static int paint_space_stroke(bContext *C,
wmOperator *op,
PaintStroke *stroke,
const float final_mouse[2],
float final_pressure)
ATTR_NO_OPT static int paint_space_stroke(bContext *C,
wmOperator *op,
PaintStroke *stroke,
const float final_mouse[2],
float final_pressure)
{
const Scene *scene = CTX_data_scene(C);
ARegion *region = CTX_wm_region(C);
@ -855,6 +863,11 @@ static int paint_space_stroke(bContext *C,
sub_v2_v2v2(dmouse, final_mouse, stroke->last_mouse_position);
float length = normalize_v2(dmouse);
/* normalize_v2 is giving inf on negative zero*/
if (isinf(length)) {
length = 0.0f;
}
if (use_scene_spacing) {
float world_space_position[3];
bool hit = SCULPT_stroke_get_location(C, world_space_position, final_mouse);
@ -927,14 +940,14 @@ static int paint_space_stroke(bContext *C,
/**** Public API ****/
PaintStroke *paint_stroke_new(bContext *C,
wmOperator *op,
StrokeGetLocation get_location,
StrokeTestStart test_start,
StrokeUpdateStep update_step,
StrokeRedraw redraw,
StrokeDone done,
int event_type)
ATTR_NO_OPT PaintStroke *paint_stroke_new(bContext *C,
wmOperator *op,
StrokeGetLocation get_location,
StrokeTestStart test_start,
StrokeUpdateStep update_step,
StrokeRedraw redraw,
StrokeDone done,
int event_type)
{
struct Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
PaintStroke *stroke = MEM_callocN(sizeof(PaintStroke), "PaintStroke");
@ -959,6 +972,10 @@ PaintStroke *paint_stroke_new(bContext *C,
get_imapaint_zoom(C, &zoomx, &zoomy);
stroke->zoom_2d = max_ff(zoomx, zoomy);
stroke->has_cubic_stroke = ELEM(
MTEX_MAP_MODE_ROLL, br->mtex.brush_map_mode, br->mask_mtex.brush_map_mode);
stroke->stroke_sample_index = 0;
if (stroke->stroke_mode == BRUSH_STROKE_INVERT) {
if (br->flag & BRUSH_CURVE) {
RNA_enum_set(op->ptr, "mode", BRUSH_STROKE_NORMAL);
@ -1184,12 +1201,21 @@ struct wmKeyMap *paint_stroke_modal_keymap(struct wmKeyConfig *keyconf)
return keymap;
}
bool paint_stroke_has_cubic(const PaintStroke *stroke)
{
return stroke->has_cubic_stroke;
}
static void paint_stroke_add_sample(
const Paint *paint, PaintStroke *stroke, float x, float y, float pressure)
{
PaintSample *sample = &stroke->samples[stroke->cur_sample];
int max_samples = CLAMPIS(paint->num_input_samples, 1, PAINT_MAX_INPUT_SAMPLES);
if (stroke->has_cubic_stroke) {
max_samples = max_ii(max_samples, 5);
}
sample->mouse[0] = x;
sample->mouse[1] = y;
sample->pressure = pressure;
@ -1238,7 +1264,8 @@ static void paint_line_strokes_spacing(bContext *C,
ePaintMode mode = BKE_paintmode_get_active_from_context(C);
ARegion *region = CTX_wm_region(C);
const bool use_scene_spacing = paint_stroke_use_scene_spacing(CTX_data_scene(C)->toolsettings, brush, mode);
const bool use_scene_spacing = paint_stroke_use_scene_spacing(
CTX_data_scene(C)->toolsettings, brush, mode);
float mouse[2], dmouse[2];
float length;
@ -1395,7 +1422,8 @@ static bool paint_stroke_curve_end(bContext *C, wmOperator *op, PaintStroke *str
stroke->last_pressure = 1.0;
copy_v2_v2(stroke->last_mouse_position, data + 2 * j);
if (paint_stroke_use_scene_spacing(scene->toolsettings, br, BKE_paintmode_get_active_from_context(C))) {
if (paint_stroke_use_scene_spacing(
scene->toolsettings, br, BKE_paintmode_get_active_from_context(C))) {
stroke->stroke_over_mesh = SCULPT_stroke_get_location(
C, stroke->last_world_space_position, data + 2 * j);
mul_m4_v3(stroke->vc.obact->obmat, stroke->last_world_space_position);
@ -1405,6 +1433,11 @@ static bool paint_stroke_curve_end(bContext *C, wmOperator *op, PaintStroke *str
if (stroke->stroke_started) {
paint_brush_stroke_add_step(C, op, stroke, data + 2 * j, 1.0);
if (stroke->has_cubic_stroke) {
paint_brush_stroke_add_step(C, op, stroke, data + 2 * j, 1.0);
}
paint_line_strokes_spacing(
C, op, stroke, spacing, &length_residue, data + 2 * j, data + 2 * (j + 1));
}
@ -1457,7 +1490,10 @@ static void paint_stroke_line_constrain(PaintStroke *stroke, float mouse[2])
}
}
int paint_stroke_modal(bContext *C, wmOperator *op, const wmEvent *event, PaintStroke **stroke_p)
ATTR_NO_OPT int paint_stroke_modal(bContext *C,
wmOperator *op,
const wmEvent *event,
PaintStroke **stroke_p)
{
Paint *p = BKE_paint_get_active_from_context(C);
ePaintMode mode = BKE_paintmode_get_active_from_context(C);
@ -1491,12 +1527,59 @@ int paint_stroke_modal(bContext *C, wmOperator *op, const wmEvent *event, PaintS
paint_stroke_add_sample(p, stroke, event->mval[0], event->mval[1], pressure);
paint_stroke_sample_average(stroke, &sample_average);
if (stroke->stroke_sample_index == 0) {
for (int i = 0; i < 3; i++) {
paint_stroke_add_sample(p, stroke, event->mval[0], event->mval[1], pressure);
}
}
if (stroke->has_cubic_stroke) {
float a[2], b[2], c[2], d[2];
int ia = (stroke->cur_sample - 3 + stroke->num_samples) % stroke->num_samples;
int id = (stroke->cur_sample - 1 + stroke->num_samples) % stroke->num_samples;
int ib = (stroke->cur_sample - 2 + stroke->num_samples) % stroke->num_samples;
int ic = (stroke->cur_sample - 0 + stroke->num_samples) % stroke->num_samples;
copy_v2_v2(a, stroke->samples[ia].mouse);
copy_v2_v2(b, stroke->samples[ib].mouse);
copy_v2_v2(c, stroke->samples[ic].mouse);
copy_v2_v2(d, stroke->samples[id].mouse);
float tmp[3];
sub_v2_v2v2(tmp, d, a);
sub_v2_v2(b, a);
negate_v2(b);
interp_v2_v2v2(b, b, tmp, 0.5f);
add_v2_v2(b, a);
sub_v2_v2v2(tmp, d, a);
sub_v2_v2(c, d);
interp_v2_v2v2(c, c, tmp, 0.5f);
negate_v2(c);
add_v2_v2(c, d);
copy_v2_v2(stroke->mouse_cubic[0], a);
copy_v2_v2(stroke->mouse_cubic[1], b);
copy_v2_v2(stroke->mouse_cubic[2], c);
copy_v2_v2(stroke->mouse_cubic[3], d);
printf("\n");
printf("a: %.2f: %.2f\n", a[0], a[1]);
printf("b: %.2f: %.2f\n", b[0], b[1]);
printf("c: %.2f: %.2f\n", c[0], c[1]);
printf("d: %.2f: %.2f\n", d[0], d[1]);
}
/* Tilt. */
if (WM_event_is_tablet(event)) {
stroke->x_tilt = event->tablet.x_tilt;
stroke->y_tilt = event->tablet.y_tilt;
}
stroke->stroke_sample_index++;
#ifdef WITH_INPUT_NDOF
/* let NDOF motion pass through to the 3D view so we can paint and rotate simultaneously!
* this isn't perfect... even when an extra MOUSEMOVE is spoofed, the stroke discards it
@ -1651,7 +1734,7 @@ int paint_stroke_modal(bContext *C, wmOperator *op, const wmEvent *event, PaintS
return OPERATOR_RUNNING_MODAL;
}
int paint_stroke_exec(bContext *C, wmOperator *op, PaintStroke *stroke)
ATTR_NO_OPT int paint_stroke_exec(bContext *C, wmOperator *op, PaintStroke *stroke)
{
/* only when executed for the first time */
if (stroke->stroke_started == 0) {

View File

@ -3663,6 +3663,174 @@ void SCULPT_calc_area_normal_and_center(
/** \} */
/*
off period;
procedure bez(a, b);
a + (b - a) * t;
lin := bez(k1, k2);
quad := bez(lin, sub(k2=k3, k1=k2, lin));
cubic := bez(quad, sub(k3=k4, k2=k3, k1=k2, quad));
dcubic := df(cubic, t);
icubic := int(cubic, t);
dx := sub(k1=x1, k2=x2, k3=x3, k4=x4, dcubic);
dy := sub(k1=y1, k2=y2, k3=y3, k4=y4, dcubic);
dz := sub(k1=z1, k2=z2, k3=z3, k4=z4, dcubic);
darc := sqrt(dx**2 + dy**2 + dz**2);
arcstep := darc*dt + 0.5*df(darc, t)*dt*dt;
gentran
begin
declare <<
x1,x2,x3,x4 : float;
y1,y2,y3,y4 : float;
z1,z2,z3,z4 : float;
dt,t : float;
>>;
return eval(dcubic)
end;
on fort;
cubic;
dcubic;
icubic;
arcstep;
off fort;
*/
ATTR_NO_OPT float dcubic(float k1, float k2, float k3, float k4, float t)
{
return -3.0f * ((t - 1.0f) * (t - 1.0f) * k1 - k4 * t * t + (3.0f * t - 2.0f) * k3 * t -
(3.0f * t - 1.0f) * (t - 1.0f) * k2);
}
ATTR_NO_OPT float cubic_arclen(const float control[4][3])
{
const int steps = 2048;
float t = 0.0f, dt = 1.0f / (float)steps;
float arc = 0.0f;
for (int i = 0; i < steps; i++, t += dt) {
float dx = dcubic(control[0][0], control[1][0], control[2][0], control[3][0], t);
float dy = dcubic(control[0][1], control[1][1], control[2][1], control[3][1], t);
float dz = dcubic(control[0][2], control[1][2], control[2][2], control[3][2], t);
arc += sqrtf(dx * dx + dy * dy + dz * dz);
}
return arc;
}
/* Evaluate bezier position and tangent at a specific parameter value
* using the De Casteljau algorithm. */
ATTR_NO_OPT static void evaluate_cubic_bezier(const float control[4][3],
float t,
float r_pos[3],
float r_tangent[3])
{
float layer1[3][3];
interp_v3_v3v3(layer1[0], control[0], control[1], t);
interp_v3_v3v3(layer1[1], control[1], control[2], t);
interp_v3_v3v3(layer1[2], control[2], control[3], t);
float layer2[2][3];
interp_v3_v3v3(layer2[0], layer1[0], layer1[1], t);
interp_v3_v3v3(layer2[1], layer1[1], layer1[2], t);
sub_v3_v3v3(r_tangent, layer2[1], layer2[0]);
madd_v3_v3v3fl(r_pos, layer2[0], r_tangent, t);
r_tangent[0] = dcubic(control[0][0], control[1][0], control[2][0], control[3][0], t);
r_tangent[1] = dcubic(control[0][1], control[1][1], control[2][1], control[3][1], t);
r_tangent[2] = dcubic(control[0][2], control[1][2], control[2][2], control[3][2], t);
}
ATTR_NO_OPT static float cubic_uv_test(const float co[3], const float p[3], const float tan[3])
{
float tmp[3];
sub_v3_v3v3(tmp, co, p);
return dot_v3v3(tmp, tan);
}
ATTR_NO_OPT static void calc_cubic_uv_v3(const float cubic[4][3],
const float co[3],
float r_out[2])
{
const int steps = 5;
const int binary_steps = 5;
float dt = 1.0f / (float)steps, t = dt;
float lastp[3];
float p[3];
float tan[3];
float lasttan[3];
evaluate_cubic_bezier(cubic, 0.0f, p, tan);
float mindis = len_v3v3(co, cubic[0]);
float dis = len_v3v3(co, cubic[3]);
if (dis < mindis) {
mindis = dis;
r_out[0] = 1.0f;
r_out[1] = mindis;
}
else {
r_out[0] = 0.0f;
r_out[1] = mindis;
}
for (int i = 0; i < steps; i++, t += dt) {
copy_v3_v3(lastp, p);
copy_v3_v3(lasttan, tan);
evaluate_cubic_bezier(cubic, t, p, tan);
float f1 = cubic_uv_test(co, lastp, lasttan);
float f2 = cubic_uv_test(co, p, tan);
if ((f1 < 0.0f) == (f2 < 0.0f)) {
continue;
}
float midp[3], midtan[3];
float start = t - dt;
float end = t;
float mid;
for (int j = 0; j < binary_steps; j++) {
mid = (start + end) * 0.5f;
evaluate_cubic_bezier(cubic, mid, midp, midtan);
float fmid = cubic_uv_test(co, midp, midtan);
if ((fmid < 0.0f) == (f1 < 0.0f)) {
start = mid;
f1 = fmid;
}
else {
end = mid;
f2 = fmid;
}
}
dis = len_v3v3(midp, co);
if (dis < mindis) {
mindis = dis;
r_out[0] = mid;
r_out[1] = dis;
}
}
}
/* -------------------------------------------------------------------- */
/** \name Generic Brush Utilities
* \{ */
@ -3850,15 +4018,15 @@ static float brush_strength(const Sculpt *sd,
}
}
float SCULPT_brush_strength_factor(SculptSession *ss,
const Brush *br,
const float brush_point[3],
const float len,
const float vno[3],
const float fno[3],
const float mask,
const SculptVertRef vertex_index,
const int thread_id)
ATTR_NO_OPT float SCULPT_brush_strength_factor(SculptSession *ss,
const Brush *br,
const float brush_point[3],
const float len,
const float vno[3],
const float fno[3],
const float mask,
const SculptVertRef vertex_index,
const int thread_id)
{
StrokeCache *cache = ss->cache;
const Scene *scene = cache->vc->scene;
@ -3913,6 +4081,26 @@ float SCULPT_brush_strength_factor(SculptSession *ss,
avg += br->texture_sample_bias;
}
else if (mtex->brush_map_mode == MTEX_MAP_MODE_ROLL) {
float point_3d[3];
point_3d[2] = 0.0f;
calc_cubic_uv_v3(ss->cache->world_cubic, SCULPT_vertex_co_get(ss, vertex_index), point_3d);
if (point_3d[0] == 0.0f || point_3d[0] == 1.0f) {
return 0.0f;
}
point_3d[1] /= ss->cache->radius;
//point_3d[0] = ss->cache->input_mapping.stroke_t + point_3d[0] * ss->cache->world_cubic_arclength;
point_3d[0] -= floorf(point_3d[0]);
float color[4] = {point_3d[0], point_3d[1], 0.0f, 1.0f};
SCULPT_vertex_color_set(ss, vertex_index, color);
avg = BKE_brush_sample_tex_3d(scene, br, point_3d, rgba, 0, ss->tex_pool);
}
else {
const float point_3d[3] = {point_2d[0], point_2d[1], 0.0f};
avg = BKE_brush_sample_tex_3d(scene, br, point_3d, rgba, 0, ss->tex_pool);
@ -6937,7 +7125,10 @@ static float sculpt_update_speed_average(SculptSession *ss, float speed)
return speed / (float)tot;
}
/* Initialize the stroke cache variants from operator properties. */
static void sculpt_update_cache_variants(bContext *C, Sculpt *sd, Object *ob, PointerRNA *ptr)
ATTR_NO_OPT static void sculpt_update_cache_variants(bContext *C,
Sculpt *sd,
Object *ob,
PointerRNA *ptr)
{
Scene *scene = CTX_data_scene(C);
UnifiedPaintSettings *ups = &scene->toolsettings->unified_paint_settings;
@ -7082,6 +7273,36 @@ static void sculpt_update_cache_variants(bContext *C, Sculpt *sd, Object *ob, Po
cache->input_mapping.stroke_t = cache->stroke_distance_t /
10.0f; /*scale to a more user-friendly value*/
if (cache->has_cubic) {
RNA_float_get_array(ptr, "mouse_cubic", (float *)cache->mouse_cubic);
printf("\n");
/* Project mouse cubic into 3d space. */
for (int i = 0; i < 4; i++) {
if (!SCULPT_stroke_get_location(C, cache->world_cubic[i], cache->mouse_cubic[i])) {
float loc[3];
mul_v3_m4v3(loc, ob->obmat, cache->true_location);
ED_view3d_win_to_3d(CTX_wm_view3d(C),
CTX_wm_region(C),
cache->true_location,
cache->mouse_cubic[i],
cache->world_cubic[i]);
}
#if 1
printf("%.2f, %.2f %.2f\n",
cache->world_cubic[i][0],
cache->world_cubic[i][1],
cache->world_cubic[i][2]);
#endif
}
cache->world_cubic_arclength = cubic_arclen(cache->world_cubic);
}
}
/* Returns true if any of the smoothing modes are active (currently
@ -7876,10 +8097,10 @@ static void sculpt_cache_dyntopo_settings(BrushChannelSet *chset,
r_settings->constant_detail = BRUSHSET_GET_FLOAT(chset, dyntopo_constant_detail, input_data);
};
static void sculpt_stroke_update_step(bContext *C,
wmOperator *UNUSED(op),
struct PaintStroke *stroke,
PointerRNA *itemptr)
ATTR_NO_OPT static void sculpt_stroke_update_step(bContext *C,
wmOperator *UNUSED(op),
struct PaintStroke *stroke,
PointerRNA *itemptr)
{
@ -7892,6 +8113,8 @@ static void sculpt_stroke_update_step(bContext *C,
SculptSession *ss = ob->sculpt;
Brush *brush = BKE_paint_brush(&sd->paint);
ss->cache->has_cubic = paint_stroke_has_cubic(stroke);
if (ss->cache->channels_final) {
BKE_brush_channelset_free(ss->cache->channels_final);
}

View File

@ -714,6 +714,11 @@ typedef struct StrokeCache {
// if nonzero, override brush sculpt tool
int tool_override;
BrushChannelSet *tool_override_channels;
float mouse_cubic[4][2];
float world_cubic[4][3];
float world_cubic_arclength;
bool has_cubic;
} StrokeCache;
/* Sculpt Filters */

View File

@ -438,6 +438,7 @@ typedef struct ColorMapping {
#define MTEX_MAP_MODE_AREA 3
#define MTEX_MAP_MODE_RANDOM 4
#define MTEX_MAP_MODE_STENCIL 5
#define MTEX_MAP_MODE_ROLL 6
/* brush_angle_mode */
#define MTEX_ANGLE_RANDOM 1

View File

@ -73,6 +73,7 @@ static const EnumPropertyItem rna_enum_brush_texture_slot_map_all_mode_items[] =
{MTEX_MAP_MODE_3D, "3D", 0, "3D", ""},
{MTEX_MAP_MODE_RANDOM, "RANDOM", 0, "Random", ""},
{MTEX_MAP_MODE_STENCIL, "STENCIL", 0, "Stencil", ""},
//{MTEX_MAP_MODE_ROLL, "ROLL", 0, "Roll", ""},
{0, NULL, 0, NULL, NULL},
};
@ -4097,6 +4098,16 @@ static void rna_def_operator_stroke_element(BlenderRNA *brna)
/* XXX: i don't think blender currently supports the ability to properly do a remappable modifier
* in the middle of a stroke */
prop = RNA_def_property(srna, "mouse_cubic", PROP_FLOAT, PROP_COORDS);
RNA_def_property_flag(prop, PROP_IDPROPERTY);
RNA_def_property_array(prop, 8);
RNA_def_property_ui_text(prop, "Mouse", "");
prop = RNA_def_property(srna, "world_cubic", PROP_FLOAT, PROP_COORDS);
RNA_def_property_flag(prop, PROP_IDPROPERTY);
RNA_def_property_array(prop, 12);
RNA_def_property_ui_text(prop, "Mouse", "");
}
void RNA_def_brush(BlenderRNA *brna)