GP: New Curve primitive and other primitive improvements

This commit adds support for new curve tool and adds more functionalities to the existing primitives, including new handles, editing, stroke thickness curve, noise, preview of the real stroke, etc.

Thanks to @charlie for his great contribution to this improvement.
This commit is contained in:
Antonioya 2018-12-15 17:21:47 +01:00
parent f9917a8d43
commit 351f537fa8
22 changed files with 1175 additions and 295 deletions

@ -5709,6 +5709,15 @@ def km_3d_view_tool_paint_gpencil_arc(params):
]}, ]},
) )
def km_3d_view_tool_paint_gpencil_curve(params):
return (
"3D View Tool: Paint Gpencil, Curve",
{"space_type": 'VIEW_3D', "region_type": 'WINDOW'},
{"items": [
("gpencil.primitive", {"type": params.tool_tweak, "value": 'ANY'},
{"properties": [("type", 'CURVE'), ("wait_for_input", False)]}),
]},
)
def km_3d_view_tool_edit_gpencil_select(params): def km_3d_view_tool_edit_gpencil_select(params):
return ( return (
@ -6019,6 +6028,7 @@ def generate_keymaps(params=None):
km_3d_view_tool_paint_gpencil_box(params), km_3d_view_tool_paint_gpencil_box(params),
km_3d_view_tool_paint_gpencil_circle(params), km_3d_view_tool_paint_gpencil_circle(params),
km_3d_view_tool_paint_gpencil_arc(params), km_3d_view_tool_paint_gpencil_arc(params),
km_3d_view_tool_paint_gpencil_curve(params),
km_3d_view_tool_edit_gpencil_select(params), km_3d_view_tool_edit_gpencil_select(params),
km_3d_view_tool_edit_gpencil_select_box(params), km_3d_view_tool_edit_gpencil_select_box(params),
km_3d_view_tool_edit_gpencil_select_circle(params), km_3d_view_tool_edit_gpencil_select_circle(params),

@ -645,6 +645,7 @@ class GPENCIL_MT_gpencil_draw_specials(Menu):
layout.operator("gpencil.primitive", text="Rectangle", icon='UV_FACESEL').type = 'BOX' layout.operator("gpencil.primitive", text="Rectangle", icon='UV_FACESEL').type = 'BOX'
layout.operator("gpencil.primitive", text="Circle", icon='ANTIALIASED').type = 'CIRCLE' layout.operator("gpencil.primitive", text="Circle", icon='ANTIALIASED').type = 'CIRCLE'
layout.operator("gpencil.primitive", text="Arc", icon='SPHERECURVE').type = 'ARC' layout.operator("gpencil.primitive", text="Arc", icon='SPHERECURVE').type = 'ARC'
layout.operator("gpencil.primitive", text="Curve", icon='CURVE_BEZCURVE').type = 'CURVE'
class GPENCIL_MT_gpencil_draw_delete(Menu): class GPENCIL_MT_gpencil_draw_delete(Menu):

@ -1076,6 +1076,17 @@ class _defs_gpencil_paint:
widget=None, widget=None,
keymap=(), keymap=(),
) )
@ToolDef.from_fn
def curve():
return dict(
text="Curve",
icon="ops.gpencil.primitive_curve",
cursor='CROSSHAIR',
widget=None,
keymap=(),
)
class _defs_gpencil_edit: class _defs_gpencil_edit:
@ToolDef.from_fn @ToolDef.from_fn
@ -1583,6 +1594,7 @@ class VIEW3D_PT_tools_active(ToolSelectPanelHelper, Panel):
_defs_gpencil_paint.box, _defs_gpencil_paint.box,
_defs_gpencil_paint.circle, _defs_gpencil_paint.circle,
_defs_gpencil_paint.arc, _defs_gpencil_paint.arc,
_defs_gpencil_paint.curve,
], ],
'EDIT_GPENCIL': [ 'EDIT_GPENCIL': [
*_tools_gpencil_select, *_tools_gpencil_select,

@ -301,7 +301,7 @@ class _draw_left_context_mode:
return return
is_paint = True is_paint = True
if (tool.name in {"Line", "Box", "Circle", "Arc"}): if (tool.name in {"Line", "Box", "Circle", "Arc", "Curve"}):
is_paint = False is_paint = False
elif (not tool.has_datablock): elif (not tool.has_datablock):
return return
@ -375,6 +375,17 @@ class _draw_left_context_mode:
draw_color_selector() draw_color_selector()
if tool.name in {"Arc", "Curve", "Line", "Box", "Circle"}:
settings = context.tool_settings.gpencil_sculpt
row = layout.row(align=True)
row.prop(settings, "use_thickness_curve", text="", icon='CURVE_DATA')
sub = row.row(align=True)
sub.active = settings.use_thickness_curve
sub.popover(
panel="TOPBAR_PT_gpencil_primitive",
text="Thickness Profile"
)
@staticmethod @staticmethod
def SCULPT_GPENCIL(context, layout, tool): def SCULPT_GPENCIL(context, layout, tool):
if (tool is None) or (not tool.has_datablock): if (tool is None) or (not tool.has_datablock):
@ -1039,6 +1050,21 @@ class TOPBAR_PT_active_tool(Panel):
ToolSelectPanelHelper.draw_active_tool_header(context, layout, show_tool_name=True) ToolSelectPanelHelper.draw_active_tool_header(context, layout, show_tool_name=True)
# Grease Pencil Object - Primitive curve
class TOPBAR_PT_gpencil_primitive(Panel):
bl_space_type = 'VIEW_3D'
bl_region_type = 'HEADER'
bl_label = "Primitives"
@staticmethod
def draw(self, context):
settings = context.tool_settings.gpencil_sculpt
layout = self.layout
# Curve
layout.template_curve_mapping(settings, "thickness_primitive_curve", brush=True)
classes = ( classes = (
TOPBAR_HT_upper_bar, TOPBAR_HT_upper_bar,
TOPBAR_HT_lower_bar, TOPBAR_HT_lower_bar,
@ -1059,6 +1085,7 @@ classes = (
TOPBAR_MT_help, TOPBAR_MT_help,
TOPBAR_PT_active_tool, TOPBAR_PT_active_tool,
TOPBAR_PT_gpencil_layers, TOPBAR_PT_gpencil_layers,
TOPBAR_PT_gpencil_primitive,
) )
if __name__ == "__main__": # only for live edit. if __name__ == "__main__": # only for live edit.

@ -283,6 +283,7 @@ void curvemap_reset(CurveMap *cuma, const rctf *clipr, int preset, int slope)
case CURVE_PRESET_ROUND: cuma->totpoint = 4; break; case CURVE_PRESET_ROUND: cuma->totpoint = 4; break;
case CURVE_PRESET_ROOT: cuma->totpoint = 4; break; case CURVE_PRESET_ROOT: cuma->totpoint = 4; break;
case CURVE_PRESET_GAUSS: cuma->totpoint = 7; break; case CURVE_PRESET_GAUSS: cuma->totpoint = 7; break;
case CURVE_PRESET_BELL: cuma->totpoint = 3; break;
} }
cuma->curve = MEM_callocN(cuma->totpoint * sizeof(CurveMapPoint), "curve points"); cuma->curve = MEM_callocN(cuma->totpoint * sizeof(CurveMapPoint), "curve points");
@ -371,6 +372,16 @@ void curvemap_reset(CurveMap *cuma, const rctf *clipr, int preset, int slope)
cuma->curve[6].x = 1.0f; cuma->curve[6].x = 1.0f;
cuma->curve[6].y = 0.025f; cuma->curve[6].y = 0.025f;
break; break;
case CURVE_PRESET_BELL:
cuma->curve[0].x = 0;
cuma->curve[0].y = 0.025f;
cuma->curve[1].x = 0.50f;
cuma->curve[1].y = 1.0f;
cuma->curve[2].x = 1.0f;
cuma->curve[2].y = 0.025f;
break;
} }
/* mirror curve in x direction to have positive slope /* mirror curve in x direction to have positive slope

@ -188,6 +188,7 @@ ToolSettings *BKE_toolsettings_copy(ToolSettings *toolsettings, const int flag)
ts->gp_interpolate.custom_ipo = curvemapping_copy(ts->gp_interpolate.custom_ipo); ts->gp_interpolate.custom_ipo = curvemapping_copy(ts->gp_interpolate.custom_ipo);
/* duplicate Grease Pencil multiframe fallof */ /* duplicate Grease Pencil multiframe fallof */
ts->gp_sculpt.cur_falloff = curvemapping_copy(ts->gp_sculpt.cur_falloff); ts->gp_sculpt.cur_falloff = curvemapping_copy(ts->gp_sculpt.cur_falloff);
ts->gp_sculpt.cur_primitive = curvemapping_copy(ts->gp_sculpt.cur_primitive);
return ts; return ts;
} }
@ -226,6 +227,9 @@ void BKE_toolsettings_free(ToolSettings *toolsettings)
if (toolsettings->gp_sculpt.cur_falloff) { if (toolsettings->gp_sculpt.cur_falloff) {
curvemapping_free(toolsettings->gp_sculpt.cur_falloff); curvemapping_free(toolsettings->gp_sculpt.cur_falloff);
} }
if (toolsettings->gp_sculpt.cur_primitive) {
curvemapping_free(toolsettings->gp_sculpt.cur_primitive);
}
MEM_freeN(toolsettings); MEM_freeN(toolsettings);
} }
@ -699,6 +703,14 @@ void BKE_scene_init(Scene *sce)
CURVE_PRESET_GAUSS, CURVE_PRESET_GAUSS,
CURVEMAP_SLOPE_POSITIVE); CURVEMAP_SLOPE_POSITIVE);
sce->toolsettings->gp_sculpt.cur_primitive = curvemapping_add(1, 0.0f, 0.0f, 1.0f, 1.0f);
CurveMapping *gp_primitive_curve = sce->toolsettings->gp_sculpt.cur_primitive;
curvemapping_initialize(gp_primitive_curve);
curvemap_reset(gp_primitive_curve->cm,
&gp_primitive_curve->clipr,
CURVE_PRESET_BELL,
CURVEMAP_SLOPE_POSITIVE);
sce->physics_settings.gravity[0] = 0.0f; sce->physics_settings.gravity[0] = 0.0f;
sce->physics_settings.gravity[1] = 0.0f; sce->physics_settings.gravity[1] = 0.0f;
sce->physics_settings.gravity[2] = -9.81f; sce->physics_settings.gravity[2] = -9.81f;

@ -6275,6 +6275,11 @@ static void direct_link_scene(FileData *fd, Scene *sce)
if (sce->toolsettings->gp_sculpt.cur_falloff) { if (sce->toolsettings->gp_sculpt.cur_falloff) {
direct_link_curvemapping(fd, sce->toolsettings->gp_sculpt.cur_falloff); direct_link_curvemapping(fd, sce->toolsettings->gp_sculpt.cur_falloff);
} }
/* relink grease pencil primitive curve */
sce->toolsettings->gp_sculpt.cur_primitive = newdataadr(fd, sce->toolsettings->gp_sculpt.cur_primitive);
if (sce->toolsettings->gp_sculpt.cur_primitive) {
direct_link_curvemapping(fd, sce->toolsettings->gp_sculpt.cur_primitive);
}
} }
if (sce->ed) { if (sce->ed) {

@ -2516,6 +2516,21 @@ void blo_do_versions_280(FileData *fd, Library *UNUSED(lib), Main *bmain)
} }
} }
} }
/* Grease pencil primitive curve */
if (!DNA_struct_elem_find(fd->filesdna, "GP_Sculpt_Settings", "CurveMapping", "cur_primitive")) {
for (Scene *scene = bmain->scene.first; scene; scene = scene->id.next) {
GP_Sculpt_Settings *gset = &scene->toolsettings->gp_sculpt;
if ((gset) && (gset->cur_primitive == NULL)) {
gset->cur_primitive = curvemapping_add(1, 0.0f, 0.0f, 1.0f, 1.0f);
curvemapping_initialize(gset->cur_primitive);
curvemap_reset(gset->cur_primitive->cm,
&gset->cur_primitive->clipr,
CURVE_PRESET_BELL,
CURVEMAP_SLOPE_POSITIVE);
}
}
}
} }
{ {

@ -187,7 +187,7 @@ void BLO_update_defaults_startup_blend(Main *bmain, const char *app_template)
} }
} }
/* Be sure curfalloff is initializated */ /* Be sure curfalloff and primitive are initializated */
for (Scene *scene = bmain->scene.first; scene; scene = scene->id.next) { for (Scene *scene = bmain->scene.first; scene; scene = scene->id.next) {
ToolSettings *ts = scene->toolsettings; ToolSettings *ts = scene->toolsettings;
if (ts->gp_sculpt.cur_falloff == NULL) { if (ts->gp_sculpt.cur_falloff == NULL) {
@ -199,6 +199,15 @@ void BLO_update_defaults_startup_blend(Main *bmain, const char *app_template)
CURVE_PRESET_GAUSS, CURVE_PRESET_GAUSS,
CURVEMAP_SLOPE_POSITIVE); CURVEMAP_SLOPE_POSITIVE);
} }
if (ts->gp_sculpt.cur_primitive == NULL) {
ts->gp_sculpt.cur_primitive = curvemapping_add(1, 0.0f, 0.0f, 1.0f, 1.0f);
CurveMapping *gp_primitive_curve = ts->gp_sculpt.cur_primitive;
curvemapping_initialize(gp_primitive_curve);
curvemap_reset(gp_primitive_curve->cm,
&gp_primitive_curve->clipr,
CURVE_PRESET_BELL,
CURVEMAP_SLOPE_POSITIVE);
}
} }
} }

@ -2555,6 +2555,10 @@ static void write_scene(WriteData *wd, Scene *sce)
if (tos->gp_sculpt.cur_falloff) { if (tos->gp_sculpt.cur_falloff) {
write_curvemapping(wd, tos->gp_sculpt.cur_falloff); write_curvemapping(wd, tos->gp_sculpt.cur_falloff);
} }
/* write grease-pencil primitive curve to file */
if (tos->gp_sculpt.cur_primitive) {
write_curvemapping(wd, tos->gp_sculpt.cur_primitive);
}
write_paint(wd, &tos->imapaint.paint); write_paint(wd, &tos->imapaint.paint);

@ -252,6 +252,9 @@ GPUBatch *DRW_gpencil_get_buffer_stroke_geom(bGPdata *gpd, short thickness)
tGPspoint *points = gpd->runtime.sbuffer; tGPspoint *points = gpd->runtime.sbuffer;
int totpoints = gpd->runtime.sbuffer_size; int totpoints = gpd->runtime.sbuffer_size;
/* if cyclic needs more vertex */
int cyclic_add = (gpd->runtime.sbuffer_sflag & GP_STROKE_CYCLIC) ? 1 : 0;
int totvertex = totpoints + cyclic_add + 2;
static GPUVertFormat format = { 0 }; static GPUVertFormat format = { 0 };
static uint pos_id, color_id, thickness_id, uvdata_id; static uint pos_id, color_id, thickness_id, uvdata_id;
@ -263,11 +266,11 @@ GPUBatch *DRW_gpencil_get_buffer_stroke_geom(bGPdata *gpd, short thickness)
} }
GPUVertBuf *vbo = GPU_vertbuf_create_with_format(&format); GPUVertBuf *vbo = GPU_vertbuf_create_with_format(&format);
GPU_vertbuf_data_alloc(vbo, totpoints + 2); GPU_vertbuf_data_alloc(vbo, totvertex);
/* draw stroke curve */ /* draw stroke curve */
const tGPspoint *tpt = points; const tGPspoint *tpt = points;
bGPDspoint pt, pt2; bGPDspoint pt, pt2, pt3;
int idx = 0; int idx = 0;
/* get origin to reproject point */ /* get origin to reproject point */
@ -281,19 +284,22 @@ GPUBatch *DRW_gpencil_get_buffer_stroke_geom(bGPdata *gpd, short thickness)
/* first point for adjacency (not drawn) */ /* first point for adjacency (not drawn) */
if (i == 0) { if (i == 0) {
if (totpoints > 1) { if (gpd->runtime.sbuffer_sflag & GP_STROKE_CYCLIC && totpoints > 2) {
ED_gpencil_tpoint_to_point(ar, origin, &points[1], &pt2); ED_gpencil_tpoint_to_point(ar, origin, &points[totpoints - 1], &pt2);
gpencil_set_stroke_point( gpencil_set_stroke_point(
vbo, &pt2, idx, vbo, &pt2, idx,
pos_id, color_id, thickness_id, uvdata_id, thickness, gpd->runtime.scolor); pos_id, color_id, thickness_id, uvdata_id, thickness, gpd->runtime.scolor);
idx++;
} }
else { else {
ED_gpencil_tpoint_to_point(ar, origin, &points[1], &pt2);
gpencil_set_stroke_point( gpencil_set_stroke_point(
vbo, &pt, idx, vbo, &pt2, idx,
pos_id, color_id, thickness_id, uvdata_id, thickness, gpd->runtime.scolor); pos_id, color_id, thickness_id, uvdata_id, thickness, gpd->runtime.scolor);
idx++;
} }
idx++;
} }
/* set point */ /* set point */
gpencil_set_stroke_point( gpencil_set_stroke_point(
vbo, &pt, idx, vbo, &pt, idx,
@ -302,16 +308,27 @@ GPUBatch *DRW_gpencil_get_buffer_stroke_geom(bGPdata *gpd, short thickness)
} }
/* last adjacency point (not drawn) */ /* last adjacency point (not drawn) */
if (totpoints > 2) { if (gpd->runtime.sbuffer_sflag & GP_STROKE_CYCLIC && totpoints > 2) {
/* draw line to first point to complete the cycle */
ED_gpencil_tpoint_to_point(ar, origin, &points[0], &pt2);
gpencil_set_stroke_point(
vbo, &pt2, idx,
pos_id, color_id, thickness_id, uvdata_id, thickness, gpd->runtime.scolor);
idx++;
/* now add adjacency point (not drawn) */
ED_gpencil_tpoint_to_point(ar, origin, &points[1], &pt3);
gpencil_set_stroke_point(
vbo, &pt3, idx,
pos_id, color_id, thickness_id, uvdata_id, thickness, gpd->runtime.scolor);
idx++;
}
/* last adjacency point (not drawn) */
else {
ED_gpencil_tpoint_to_point(ar, origin, &points[totpoints - 2], &pt2); ED_gpencil_tpoint_to_point(ar, origin, &points[totpoints - 2], &pt2);
gpencil_set_stroke_point( gpencil_set_stroke_point(
vbo, &pt2, idx, vbo, &pt2, idx,
pos_id, color_id, thickness_id, uvdata_id, thickness, gpd->runtime.scolor); pos_id, color_id, thickness_id, uvdata_id, thickness, gpd->runtime.scolor);
} idx++;
else {
gpencil_set_stroke_point(
vbo, &pt, idx,
pos_id, color_id, thickness_id, uvdata_id, thickness, gpd->runtime.scolor);
} }
return GPU_batch_create_ex(GPU_PRIM_LINE_STRIP_ADJ, vbo, NULL, GPU_BATCH_OWNS_VBO); return GPU_batch_create_ex(GPU_PRIM_LINE_STRIP_ADJ, vbo, NULL, GPU_BATCH_OWNS_VBO);
@ -367,6 +384,42 @@ GPUBatch *DRW_gpencil_get_buffer_point_geom(bGPdata *gpd, short thickness)
return GPU_batch_create_ex(GPU_PRIM_POINTS, vbo, NULL, GPU_BATCH_OWNS_VBO); return GPU_batch_create_ex(GPU_PRIM_POINTS, vbo, NULL, GPU_BATCH_OWNS_VBO);
} }
/* create batch geometry data for current buffer control point shader */
GPUBatch *DRW_gpencil_get_buffer_ctrlpoint_geom(bGPdata *gpd)
{
bGPDcontrolpoint *cps = gpd->runtime.cp_points;
int totpoints = gpd->runtime.tot_cp_points;
static GPUVertFormat format = { 0 };
static uint pos_id, color_id, size_id;
if (format.attr_len == 0) {
pos_id = GPU_vertformat_attr_add(&format, "pos", GPU_COMP_F32, 3, GPU_FETCH_FLOAT);
size_id = GPU_vertformat_attr_add(&format, "size", GPU_COMP_F32, 1, GPU_FETCH_FLOAT);
color_id = GPU_vertformat_attr_add(&format, "color", GPU_COMP_F32, 4, GPU_FETCH_FLOAT);
}
GPUVertBuf *vbo = GPU_vertbuf_create_with_format(&format);
GPU_vertbuf_data_alloc(vbo, totpoints);
int idx = 0;
for (int i = 0; i < gpd->runtime.tot_cp_points; i++) {
bGPDcontrolpoint *cp = &cps[i];
float color[4];
copy_v3_v3(color, cp->color);
color[3] = 0.5f;
GPU_vertbuf_attr_set(vbo, color_id, idx, color);
/* scale size */
float size = cp->size * 0.8f;
GPU_vertbuf_attr_set(vbo, size_id, idx, &size);
GPU_vertbuf_attr_set(vbo, pos_id, idx, &cp->x);
idx++;
}
return GPU_batch_create_ex(GPU_PRIM_POINTS, vbo, NULL, GPU_BATCH_OWNS_VBO);
}
/* create batch geometry data for current buffer fill shader */ /* create batch geometry data for current buffer fill shader */
GPUBatch *DRW_gpencil_get_buffer_fill_geom(bGPdata *gpd) GPUBatch *DRW_gpencil_get_buffer_fill_geom(bGPdata *gpd)
{ {

@ -434,7 +434,7 @@ DRWShadingGroup *DRW_gpencil_shgroup_stroke_create(
DRW_shgroup_uniform_int(grp, "xraymode", (const int *) &gpd->xray_mode, 1); DRW_shgroup_uniform_int(grp, "xraymode", (const int *) &gpd->xray_mode, 1);
} }
else { else {
/* for drawing always on front */ /* for drawing always on predefined z-depth */
DRW_shgroup_uniform_int(grp, "xraymode", &stl->storage->xray, 1); DRW_shgroup_uniform_int(grp, "xraymode", &stl->storage->xray, 1);
} }
@ -527,7 +527,7 @@ static DRWShadingGroup *DRW_gpencil_shgroup_point_create(
DRW_shgroup_uniform_int(grp, "xraymode", (const int *)&gpd->xray_mode, 1); DRW_shgroup_uniform_int(grp, "xraymode", (const int *)&gpd->xray_mode, 1);
} }
else { else {
/* for drawing always on front */ /* for drawing always on on predefined z-depth */
DRW_shgroup_uniform_int(grp, "xraymode", &stl->storage->xray, 1); DRW_shgroup_uniform_int(grp, "xraymode", &stl->storage->xray, 1);
} }
@ -1161,6 +1161,9 @@ void DRW_gpencil_populate_buffer_strokes(GPENCIL_e_data *e_data, void *vedata, T
{ {
GPENCIL_PassList *psl = ((GPENCIL_Data *)vedata)->psl; GPENCIL_PassList *psl = ((GPENCIL_Data *)vedata)->psl;
GPENCIL_StorageList *stl = ((GPENCIL_Data *)vedata)->stl; GPENCIL_StorageList *stl = ((GPENCIL_Data *)vedata)->stl;
const DRWContextState *draw_ctx = DRW_context_state_get();
View3D *v3d = draw_ctx->v3d;
const bool overlay = v3d != NULL ? (bool)((v3d->flag2 & V3D_RENDER_OVERRIDE) == 0) : true;
Brush *brush = BKE_paint_brush(&ts->gp_paint->paint); Brush *brush = BKE_paint_brush(&ts->gp_paint->paint);
bGPdata *gpd_eval = ob->data; bGPdata *gpd_eval = ob->data;
/* need the original to avoid cow overhead while drawing */ /* need the original to avoid cow overhead while drawing */
@ -1184,7 +1187,7 @@ void DRW_gpencil_populate_buffer_strokes(GPENCIL_e_data *e_data, void *vedata, T
/* Check if may need to draw the active stroke cache, only if this layer is the active layer /* Check if may need to draw the active stroke cache, only if this layer is the active layer
* that is being edited. (Stroke buffer is currently stored in gp-data) * that is being edited. (Stroke buffer is currently stored in gp-data)
*/ */
if (ED_gpencil_session_active() && (gpd->runtime.sbuffer_size > 0)) { if (gpd->runtime.sbuffer_size > 0) {
if ((gpd->runtime.sbuffer_sflag & GP_STROKE_ERASER) == 0) { if ((gpd->runtime.sbuffer_sflag & GP_STROKE_ERASER) == 0) {
/* It should also be noted that sbuffer contains temporary point types /* It should also be noted that sbuffer contains temporary point types
* i.e. tGPspoints NOT bGPDspoints * i.e. tGPspoints NOT bGPDspoints
@ -1194,11 +1197,11 @@ void DRW_gpencil_populate_buffer_strokes(GPENCIL_e_data *e_data, void *vedata, T
if (gpd->runtime.sbuffer_size > 1) { if (gpd->runtime.sbuffer_size > 1) {
if ((gp_style) && (gp_style->mode == GP_STYLE_MODE_LINE)) { if ((gp_style) && (gp_style->mode == GP_STYLE_MODE_LINE)) {
stl->g_data->shgrps_drawing_stroke = DRW_gpencil_shgroup_stroke_create( stl->g_data->shgrps_drawing_stroke = DRW_gpencil_shgroup_stroke_create(
e_data, vedata, psl->drawing_pass, e_data->gpencil_stroke_sh, NULL, gpd, gp_style, -1, false); e_data, vedata, psl->drawing_pass, e_data->gpencil_stroke_sh, NULL, gpd, gp_style, -1, false);
} }
else { else {
stl->g_data->shgrps_drawing_stroke = DRW_gpencil_shgroup_point_create( stl->g_data->shgrps_drawing_stroke = DRW_gpencil_shgroup_point_create(
e_data, vedata, psl->drawing_pass, e_data->gpencil_point_sh, NULL, gpd, gp_style, -1, false); e_data, vedata, psl->drawing_pass, e_data->gpencil_point_sh, NULL, gpd, gp_style, -1, false);
} }
/* clean previous version of the batch */ /* clean previous version of the batch */
@ -1211,32 +1214,32 @@ void DRW_gpencil_populate_buffer_strokes(GPENCIL_e_data *e_data, void *vedata, T
/* use unit matrix because the buffer is in screen space and does not need conversion */ /* use unit matrix because the buffer is in screen space and does not need conversion */
if (gpd->runtime.mode == GP_STYLE_MODE_LINE) { if (gpd->runtime.mode == GP_STYLE_MODE_LINE) {
e_data->batch_buffer_stroke = DRW_gpencil_get_buffer_stroke_geom( e_data->batch_buffer_stroke = DRW_gpencil_get_buffer_stroke_geom(
gpd, lthick); gpd, lthick);
} }
else { else {
e_data->batch_buffer_stroke = DRW_gpencil_get_buffer_point_geom( e_data->batch_buffer_stroke = DRW_gpencil_get_buffer_point_geom(
gpd, lthick); gpd, lthick);
} }
if (gp_style->flag & GP_STYLE_STROKE_SHOW) { if (gp_style->flag & GP_STYLE_STROKE_SHOW) {
DRW_shgroup_call_add( DRW_shgroup_call_add(
stl->g_data->shgrps_drawing_stroke, stl->g_data->shgrps_drawing_stroke,
e_data->batch_buffer_stroke, e_data->batch_buffer_stroke,
stl->storage->unit_matrix); stl->storage->unit_matrix);
} }
if ((gpd->runtime.sbuffer_size >= 3) && if ((gpd->runtime.sbuffer_size >= 3) &&
(gpd->runtime.sfill[3] > GPENCIL_ALPHA_OPACITY_THRESH) && (gpd->runtime.sfill[3] > GPENCIL_ALPHA_OPACITY_THRESH) &&
((gpd->runtime.sbuffer_sflag & GP_STROKE_NOFILL) == 0) && ((gpd->runtime.sbuffer_sflag & GP_STROKE_NOFILL) == 0) &&
((brush->gpencil_settings->flag & GP_BRUSH_DISSABLE_LASSO) == 0) && ((brush->gpencil_settings->flag & GP_BRUSH_DISSABLE_LASSO) == 0) &&
(gp_style->flag & GP_STYLE_FILL_SHOW)) (gp_style->flag & GP_STYLE_FILL_SHOW))
{ {
/* if not solid, fill is simulated with solid color */ /* if not solid, fill is simulated with solid color */
if (gpd->runtime.bfill_style > 0) { if (gpd->runtime.bfill_style > 0) {
gpd->runtime.sfill[3] = 0.5f; gpd->runtime.sfill[3] = 0.5f;
} }
stl->g_data->shgrps_drawing_fill = DRW_shgroup_create( stl->g_data->shgrps_drawing_fill = DRW_shgroup_create(
e_data->gpencil_drawing_fill_sh, psl->drawing_pass); e_data->gpencil_drawing_fill_sh, psl->drawing_pass);
/* clean previous version of the batch */ /* clean previous version of the batch */
if (stl->storage->buffer_fill) { if (stl->storage->buffer_fill) {
@ -1247,15 +1250,44 @@ void DRW_gpencil_populate_buffer_strokes(GPENCIL_e_data *e_data, void *vedata, T
e_data->batch_buffer_fill = DRW_gpencil_get_buffer_fill_geom(gpd); e_data->batch_buffer_fill = DRW_gpencil_get_buffer_fill_geom(gpd);
DRW_shgroup_call_add( DRW_shgroup_call_add(
stl->g_data->shgrps_drawing_fill, stl->g_data->shgrps_drawing_fill,
e_data->batch_buffer_fill, e_data->batch_buffer_fill,
stl->storage->unit_matrix); stl->storage->unit_matrix);
stl->storage->buffer_fill = true; stl->storage->buffer_fill = true;
} }
stl->storage->buffer_stroke = true; stl->storage->buffer_stroke = true;
} }
} }
} }
/* control points */
if ((overlay) && (gpd->runtime.tot_cp_points > 0) &&
((gpd->runtime.sbuffer_sflag & GP_STROKE_ERASER) == 0) &&
((v3d->gizmo_flag & V3D_GIZMO_HIDE) == 0) &&
((v3d->gizmo_flag & V3D_GIZMO_HIDE_TOOL) == 0))
{
DRWShadingGroup *shgrp = DRW_shgroup_create(
e_data->gpencil_edit_point_sh, psl->drawing_pass);
const float *viewport_size = DRW_viewport_size_get();
DRW_shgroup_uniform_vec2(shgrp, "Viewport", viewport_size, 1);
/* clean previous version of the batch */
if (stl->storage->buffer_ctrlpoint) {
GPU_BATCH_DISCARD_SAFE(e_data->batch_buffer_ctrlpoint);
MEM_SAFE_FREE(e_data->batch_buffer_ctrlpoint);
stl->storage->buffer_ctrlpoint = false;
}
e_data->batch_buffer_ctrlpoint = DRW_gpencil_get_buffer_ctrlpoint_geom(gpd);
DRW_shgroup_call_add(
shgrp,
e_data->batch_buffer_ctrlpoint,
stl->storage->unit_matrix);
stl->storage->buffer_ctrlpoint = true;
}
} }
/* create all missing batches */ /* create all missing batches */

@ -289,6 +289,9 @@ static void GPENCIL_engine_free(void)
GPU_BATCH_DISCARD_SAFE(e_data.batch_buffer_fill); GPU_BATCH_DISCARD_SAFE(e_data.batch_buffer_fill);
MEM_SAFE_FREE(e_data.batch_buffer_fill); MEM_SAFE_FREE(e_data.batch_buffer_fill);
GPU_BATCH_DISCARD_SAFE(e_data.batch_buffer_ctrlpoint);
MEM_SAFE_FREE(e_data.batch_buffer_ctrlpoint);
GPU_BATCH_DISCARD_SAFE(e_data.batch_grid); GPU_BATCH_DISCARD_SAFE(e_data.batch_grid);
MEM_SAFE_FREE(e_data.batch_grid); MEM_SAFE_FREE(e_data.batch_grid);

@ -133,6 +133,7 @@ typedef struct GPENCIL_Storage {
bool reset_cache; bool reset_cache;
bool buffer_stroke; bool buffer_stroke;
bool buffer_fill; bool buffer_fill;
bool buffer_ctrlpoint;
const float *pixsize; const float *pixsize;
float render_pixsize; float render_pixsize;
int tonemapping; int tonemapping;
@ -299,6 +300,7 @@ typedef struct GPENCIL_e_data {
/* for buffer only one batch is nedeed because the drawing is only of one stroke */ /* for buffer only one batch is nedeed because the drawing is only of one stroke */
GPUBatch *batch_buffer_stroke; GPUBatch *batch_buffer_stroke;
GPUBatch *batch_buffer_fill; GPUBatch *batch_buffer_fill;
GPUBatch *batch_buffer_ctrlpoint;
/* grid geometry */ /* grid geometry */
GPUBatch *batch_grid; GPUBatch *batch_grid;
@ -386,6 +388,7 @@ void DRW_gpencil_get_edlin_geom(struct GpencilBatchCacheElem *be, struct bGPDstr
struct GPUBatch *DRW_gpencil_get_buffer_stroke_geom(struct bGPdata *gpd, short thickness); struct GPUBatch *DRW_gpencil_get_buffer_stroke_geom(struct bGPdata *gpd, short thickness);
struct GPUBatch *DRW_gpencil_get_buffer_fill_geom(struct bGPdata *gpd); struct GPUBatch *DRW_gpencil_get_buffer_fill_geom(struct bGPdata *gpd);
struct GPUBatch *DRW_gpencil_get_buffer_point_geom(struct bGPdata *gpd, short thickness); struct GPUBatch *DRW_gpencil_get_buffer_point_geom(struct bGPdata *gpd, short thickness);
struct GPUBatch *DRW_gpencil_get_buffer_ctrlpoint_geom(struct bGPdata *gpd);
struct GPUBatch *DRW_gpencil_get_grid(Object *ob); struct GPUBatch *DRW_gpencil_get_grid(Object *ob);
/* object cache functions */ /* object cache functions */

@ -1430,63 +1430,6 @@ void ED_gp_draw_interpolation(const bContext *C, tGPDinterpolate *tgpi, const in
glDisable(GL_BLEND); glDisable(GL_BLEND);
} }
/* draw interpolate strokes (used only while operator is running) */
void ED_gp_draw_primitives(const bContext *C, tGPDprimitive *tgpi, const int type)
{
tGPDdraw tgpw;
ARegion *ar = CTX_wm_region(C);
RegionView3D *rv3d = ar->regiondata;
/* if idle, do not draw */
if (tgpi->flag == 0) {
return;
}
Object *obact = CTX_data_active_object(C);
Depsgraph *depsgraph = CTX_data_depsgraph(C);
float color[4];
UI_GetThemeColor3fv(TH_GP_VERTEX_SELECT, color);
color[3] = 0.6f;
int dflag = 0;
/* if 3d stuff, enable flags */
if (type == REGION_DRAW_POST_VIEW) {
dflag |= (GP_DRAWDATA_ONLY3D | GP_DRAWDATA_NOSTATUS);
}
tgpw.rv3d = rv3d;
tgpw.depsgraph = depsgraph;
tgpw.ob = obact;
tgpw.gpd = tgpi->gpd;
tgpw.offsx = 0;
tgpw.offsy = 0;
tgpw.winx = tgpi->ar->winx;
tgpw.winy = tgpi->ar->winy;
tgpw.dflag = dflag;
/* turn on alpha-blending */
GPU_blend(true);
/* calculate parent position */
ED_gpencil_parent_location(depsgraph, obact, tgpi->gpd, tgpi->gpl, tgpw.diff_mat);
if (tgpi->gpf) {
tgpw.gps = tgpi->gpf->strokes.first;
if (tgpw.gps->totpoints > 0) {
tgpw.gpl = tgpi->gpl;
tgpw.gpf = tgpi->gpf;
tgpw.t_gpf = tgpi->gpf;
tgpw.lthick = tgpi->gpl->line_change;
tgpw.opacity = 1.0;
copy_v4_v4(tgpw.tintcolor, color);
tgpw.onion = true;
tgpw.custonion = true;
gp_draw_strokes(&tgpw);
}
}
GPU_blend(false);
}
/* wrapper to draw strokes for filling operator */ /* wrapper to draw strokes for filling operator */
void ED_gp_draw_fill(tGPDdraw *tgpw) void ED_gp_draw_fill(tGPDdraw *tgpw)
{ {

@ -138,6 +138,7 @@ typedef struct tGPDinterpolate {
/* Temporary primitive operation data */ /* Temporary primitive operation data */
typedef struct tGPDprimitive { typedef struct tGPDprimitive {
struct Main *bmain; /* main database pointer */
struct Depsgraph *depsgraph; struct Depsgraph *depsgraph;
struct wmWindow *win; /* window where painting originated */ struct wmWindow *win; /* window where painting originated */
struct Scene *scene; /* current scene from context */ struct Scene *scene; /* current scene from context */
@ -154,25 +155,34 @@ typedef struct tGPDprimitive {
struct bGPDlayer *gpl; /* layer */ struct bGPDlayer *gpl; /* layer */
struct bGPDframe *gpf; /* frame */ struct bGPDframe *gpf; /* frame */
int type; /* type of primitive */ int type; /* type of primitive */
short cyclic; /* cyclic option */ int orign_type; /* original type of primitive */
short flip; /* flip option */ bool curve; /* type of primitive is a curve */
short flip; /* flip option */
tGPspoint *points; /* array of data-points for stroke */
int point_count; /* number of edges allocated */
int tot_stored_edges; /* stored number of polygon edges */
int tot_edges; /* number of polygon edges */ int tot_edges; /* number of polygon edges */
int top[2]; /* first box corner */ float origin[2]; /* initial box corner */
int bottom[2]; /* last box corner */ float start[2]; /* first box corner */
int origin[2]; /* initial box corner */ float end[2]; /* last box corner */
float midpoint[2]; /* midpoint box corner */
float cp1[2]; /* first control point */
float cp2[2]; /* second control point */
int sel_cp; /* flag to determine control point is selected */
int flag; /* flag to determine operations in progress */ int flag; /* flag to determine operations in progress */
float mval[2]; /* recorded mouse-position */
float mvalo[2]; /* previous recorded mouse-position */
int lock_axis; /* lock to viewport axis */ int lock_axis; /* lock to viewport axis */
struct RNG *rng;
NumInput num; /* numeric input */ NumInput num; /* numeric input */
void *draw_handle_3d; /* handle for drawing strokes while operator is running 3d stuff */
} tGPDprimitive; } tGPDprimitive;
/* Modal Operator Drawing Callbacks ------------------------ */ /* Modal Operator Drawing Callbacks ------------------------ */
void ED_gp_draw_interpolation(const struct bContext *C, struct tGPDinterpolate *tgpi, const int type); void ED_gp_draw_interpolation(const struct bContext *C, struct tGPDinterpolate *tgpi, const int type);
void ED_gp_draw_primitives(const struct bContext *C, struct tGPDprimitive *tgpi, const int type);
void ED_gp_draw_fill(struct tGPDdraw *tgpw); void ED_gp_draw_fill(struct tGPDdraw *tgpw);
/* ***************************************************** */ /* ***************************************************** */
@ -369,7 +379,8 @@ enum {
GP_STROKE_BOX = -1, GP_STROKE_BOX = -1,
GP_STROKE_LINE = 1, GP_STROKE_LINE = 1,
GP_STROKE_CIRCLE = 2, GP_STROKE_CIRCLE = 2,
GP_STROKE_ARC = 3 GP_STROKE_ARC = 3,
GP_STROKE_CURVE = 4
}; };

@ -1066,7 +1066,7 @@ static void gp_stroke_newfrombuffer(tGPsdata *p)
} }
last_valid = i; last_valid = i;
} }
/* invalidate any point other point, to interpolate between /* invalidate any other point, to interpolate between
* first and last contact in an imaginary line between them */ * first and last contact in an imaginary line between them */
for (i = 0; i < gpd->runtime.sbuffer_size; i++) { for (i = 0; i < gpd->runtime.sbuffer_size; i++) {
if ((i != first_valid) && (i != last_valid)) { if ((i != first_valid) && (i != last_valid)) {

File diff suppressed because it is too large Load Diff

@ -103,6 +103,7 @@ typedef enum eCurveMappingPreset {
CURVE_PRESET_ROUND = 5, CURVE_PRESET_ROUND = 5,
CURVE_PRESET_ROOT = 6, CURVE_PRESET_ROOT = 6,
CURVE_PRESET_GAUSS = 7, CURVE_PRESET_GAUSS = 7,
CURVE_PRESET_BELL = 8,
} eCurveMappingPreset; } eCurveMappingPreset;
/* CurveMapping->tone */ /* CurveMapping->tone */

@ -48,6 +48,13 @@ struct MDeformVert;
/* ***************************************** */ /* ***************************************** */
/* GP Stroke Points */ /* GP Stroke Points */
/* 'Control Point' data for primitives and curves */
typedef struct bGPDcontrolpoint {
float x, y, z; /* x and y coordinates of control point */
float color[4]; /* point color */
int size; /* radius */
} bGPDcontrolpoint;
/* Grease-Pencil Annotations - 'Stroke Point' /* Grease-Pencil Annotations - 'Stroke Point'
* -> Coordinates may either be 2d or 3d depending on settings at the time * -> Coordinates may either be 2d or 3d depending on settings at the time
* -> Coordinates of point on stroke, in proportions of window size * -> Coordinates of point on stroke, in proportions of window size
@ -345,6 +352,10 @@ typedef struct bGPdata_Runtime {
short sbuffer_size; /* number of elements currently in cache */ short sbuffer_size; /* number of elements currently in cache */
short sbuffer_sflag; /* flags for stroke that cache represents */ short sbuffer_sflag; /* flags for stroke that cache represents */
char pad_[6]; char pad_[6];
int tot_cp_points; /* number of control-points for stroke */
char pad1_[4];
bGPDcontrolpoint *cp_points; /* array of control-points for stroke */
} bGPdata_Runtime; } bGPdata_Runtime;
/* grid configuration */ /* grid configuration */

@ -1035,6 +1035,7 @@ typedef struct GP_Sculpt_Settings {
int weighttype; /* eGP_Sculpt_Types (weight paint) */ int weighttype; /* eGP_Sculpt_Types (weight paint) */
char pad[4]; char pad[4];
struct CurveMapping *cur_falloff; /* multiframe edit falloff effect by frame */ struct CurveMapping *cur_falloff; /* multiframe edit falloff effect by frame */
struct CurveMapping *cur_primitive; /* Curve used for primitve tools */
} GP_Sculpt_Settings; } GP_Sculpt_Settings;
/* GP_Sculpt_Settings.flag */ /* GP_Sculpt_Settings.flag */
@ -1053,6 +1054,8 @@ typedef enum eGP_Sculpt_SettingsFlag {
GP_SCULPT_SETT_FLAG_FRAME_FALLOFF = (1 << 5), GP_SCULPT_SETT_FLAG_FRAME_FALLOFF = (1 << 5),
/* apply brush to uv data */ /* apply brush to uv data */
GP_SCULPT_SETT_FLAG_APPLY_UV = (1 << 6), GP_SCULPT_SETT_FLAG_APPLY_UV = (1 << 6),
/* apply primitve curve */
GP_SCULPT_SETT_FLAG_PRIMITIVE_CURVE = (1 << 7),
} eGP_Sculpt_SettingsFlag; } eGP_Sculpt_SettingsFlag;
/* Settings for GP Interpolation Operators */ /* Settings for GP Interpolation Operators */

@ -69,11 +69,11 @@ const EnumPropertyItem rna_enum_gpencil_sculpt_brush_items[] = {
{GP_SCULPT_TYPE_SMOOTH, "SMOOTH", ICON_GPBRUSH_SMOOTH, "Smooth", "Smooth stroke points"}, {GP_SCULPT_TYPE_SMOOTH, "SMOOTH", ICON_GPBRUSH_SMOOTH, "Smooth", "Smooth stroke points"},
{GP_SCULPT_TYPE_THICKNESS, "THICKNESS", ICON_GPBRUSH_THICKNESS, "Thickness", "Adjust thickness of strokes"}, {GP_SCULPT_TYPE_THICKNESS, "THICKNESS", ICON_GPBRUSH_THICKNESS, "Thickness", "Adjust thickness of strokes"},
{GP_SCULPT_TYPE_STRENGTH, "STRENGTH", ICON_GPBRUSH_STRENGTH, "Strength", "Adjust color strength of strokes" }, {GP_SCULPT_TYPE_STRENGTH, "STRENGTH", ICON_GPBRUSH_STRENGTH, "Strength", "Adjust color strength of strokes" },
{GP_SCULPT_TYPE_RANDOMIZE, "RANDOMIZE", ICON_GPBRUSH_RANDOMIZE, "Randomize", "Introduce jitter/randomness into strokes"},
{GP_SCULPT_TYPE_GRAB, "GRAB", ICON_GPBRUSH_GRAB, "Grab", "Translate the set of points initially within the brush circle" }, {GP_SCULPT_TYPE_GRAB, "GRAB", ICON_GPBRUSH_GRAB, "Grab", "Translate the set of points initially within the brush circle" },
{GP_SCULPT_TYPE_PUSH, "PUSH", ICON_GPBRUSH_PUSH, "Push", "Move points out of the way, as if combing them"}, {GP_SCULPT_TYPE_PUSH, "PUSH", ICON_GPBRUSH_PUSH, "Push", "Move points out of the way, as if combing them"},
{GP_SCULPT_TYPE_TWIST, "TWIST", ICON_GPBRUSH_TWIST, "Twist", "Rotate points around the midpoint of the brush"}, {GP_SCULPT_TYPE_TWIST, "TWIST", ICON_GPBRUSH_TWIST, "Twist", "Rotate points around the midpoint of the brush"},
{GP_SCULPT_TYPE_PINCH, "PINCH", ICON_GPBRUSH_PINCH, "Pinch", "Pull points towards the midpoint of the brush"}, {GP_SCULPT_TYPE_PINCH, "PINCH", ICON_GPBRUSH_PINCH, "Pinch", "Pull points towards the midpoint of the brush"},
{GP_SCULPT_TYPE_RANDOMIZE, "RANDOMIZE", ICON_GPBRUSH_RANDOMIZE, "Randomize", "Introduce jitter/randomness into strokes"},
{GP_SCULPT_TYPE_CLONE, "CLONE", ICON_GPBRUSH_CLONE, "Clone", "Paste copies of the strokes stored on the clipboard"}, {GP_SCULPT_TYPE_CLONE, "CLONE", ICON_GPBRUSH_CLONE, "Clone", "Paste copies of the strokes stored on the clipboard"},
{ 0, NULL, 0, NULL, NULL } { 0, NULL, 0, NULL, NULL }
}; };
@ -1250,6 +1250,12 @@ static void rna_def_gpencil_sculpt(BlenderRNA *brna)
RNA_def_parameter_clear_flags(prop, PROP_ANIMATABLE, 0); RNA_def_parameter_clear_flags(prop, PROP_ANIMATABLE, 0);
RNA_def_property_update(prop, NC_SCENE | ND_TOOLSETTINGS, NULL); RNA_def_property_update(prop, NC_SCENE | ND_TOOLSETTINGS, NULL);
prop = RNA_def_property(srna, "use_thickness_curve", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, NULL, "flag", GP_SCULPT_SETT_FLAG_PRIMITIVE_CURVE);
RNA_def_property_ui_text(prop, "Use Curve", "Use curve to define primitive stroke thickness");
RNA_def_parameter_clear_flags(prop, PROP_ANIMATABLE, 0);
RNA_def_property_update(prop, NC_SCENE | ND_TOOLSETTINGS, NULL);
/* custom falloff curve */ /* custom falloff curve */
prop = RNA_def_property(srna, "multiframe_falloff_curve", PROP_POINTER, PROP_NONE); prop = RNA_def_property(srna, "multiframe_falloff_curve", PROP_POINTER, PROP_NONE);
RNA_def_property_pointer_sdna(prop, NULL, "cur_falloff"); RNA_def_property_pointer_sdna(prop, NULL, "cur_falloff");
@ -1259,6 +1265,15 @@ static void rna_def_gpencil_sculpt(BlenderRNA *brna)
RNA_def_parameter_clear_flags(prop, PROP_ANIMATABLE, 0); RNA_def_parameter_clear_flags(prop, PROP_ANIMATABLE, 0);
RNA_def_property_update(prop, NC_SCENE | ND_TOOLSETTINGS, NULL); RNA_def_property_update(prop, NC_SCENE | ND_TOOLSETTINGS, NULL);
/* custom primitive curve */
prop = RNA_def_property(srna, "thickness_primitive_curve", PROP_POINTER, PROP_NONE);
RNA_def_property_pointer_sdna(prop, NULL, "cur_primitive");
RNA_def_property_struct_type(prop, "CurveMapping");
RNA_def_property_ui_text(prop, "Curve",
"Custom curve to control primitive thickness");
RNA_def_parameter_clear_flags(prop, PROP_ANIMATABLE, 0);
RNA_def_property_update(prop, NC_SCENE | ND_TOOLSETTINGS, NULL);
/* lock axis */ /* lock axis */
prop = RNA_def_property(srna, "lock_axis", PROP_ENUM, PROP_NONE); prop = RNA_def_property(srna, "lock_axis", PROP_ENUM, PROP_NONE);
RNA_def_property_enum_sdna(prop, NULL, "lock_axis"); RNA_def_property_enum_sdna(prop, NULL, "lock_axis");