diff --git a/release/scripts/startup/bl_operators/__init__.py b/release/scripts/startup/bl_operators/__init__.py index d7df29f1769..c39a7afcff9 100644 --- a/release/scripts/startup/bl_operators/__init__.py +++ b/release/scripts/startup/bl_operators/__init__.py @@ -49,6 +49,7 @@ _modules = [ "uvcalc_smart_project", "vertexpaint_dirt", "view3d", + "gpencil_mesh_bake", "wm", ] diff --git a/release/scripts/startup/bl_operators/gpencil_mesh_bake.py b/release/scripts/startup/bl_operators/gpencil_mesh_bake.py new file mode 100644 index 00000000000..ae75fa0e4d9 --- /dev/null +++ b/release/scripts/startup/bl_operators/gpencil_mesh_bake.py @@ -0,0 +1,162 @@ +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + +# + +import bpy +from bpy.types import Operator +from bpy.props import ( + IntProperty, + FloatProperty, + BoolProperty, + EnumProperty, +) + +gp_object_items = [] + + +def my_objlist_callback(scene, context): + gp_object_items.clear() + gp_object_items.append(('*NEW', "New Object", "")) + for o in context.scene.objects: + if o.type == 'GPENCIL': + gp_object_items.append((o.name, o.name, "")) + + return gp_object_items + + +class GPENCIL_OT_mesh_bake(Operator): + """Bake all mesh animation into grease pencil strokes""" + bl_idname = "gpencil.mesh_bake" + bl_label = "Bake Mesh to Grease Pencil" + bl_options = {'REGISTER', 'UNDO'} + + frame_start: IntProperty( + name="Start Frame", + description="Start frame for baking", + min=0, max=300000, + default=1, + ) + frame_end: IntProperty( + name="End Frame", + description="End frame for baking", + min=1, max=300000, + default=250, + ) + step: IntProperty( + name="Frame Step", + description="Frame Step", + min=1, max=120, + default=1, + ) + thickness: IntProperty( + name="Thickness", + description="Thickness of the stroke lines", + min=1, max=100, + default=1, + ) + angle: FloatProperty( + name="Threshold Angle", + description="Threshold to determine ends of the strokes", + min=0, + max=+3.141592, + default=+1.22173, # 70 Degress + subtype='ANGLE', + ) + offset: FloatProperty( + name="Stroke Offset", + description="Offset strokes from fill", + soft_min=0.0, soft_max=100.0, + min=0.0, max=100.0, + default=0.001, + precision=3, + step=1, + subtype='DISTANCE', + unit='LENGTH', + ) + seams: BoolProperty( + name="Only Seam Edges", + description="Convert only seam edges", + default=False, + ) + faces: BoolProperty( + name="Export Faces", + description="Export faces as filled strokes", + default=True, + ) + target: EnumProperty( + name="Target Object", + description="Grease Pencil Object", + items=my_objlist_callback + ) + frame_target: IntProperty( + name="Target Frame", + description="Destination frame for the baked animation", + min=1, max=300000, + default=1, + ) + project_type: EnumProperty( + name="Reproject Type", + description="Type of projection", + items=( + ("KEEP", "No Reproject", ""), + ("FRONT", "Front", "Reproject the strokes using the X-Z plane"), + ("SIDE", "Side", "Reproject the strokes using the Y-Z plane"), + ("TOP", "Top", "Reproject the strokes using the X-Y plane"), + ("VIEW", "View", "Reproject the strokes to current viewpoint"), + ("CURSOR", "Cursor", "Reproject the strokes using the orientation of 3D cursor") + ) + ) + + @classmethod + def poll(self, context): + ob = context.active_object + return ((ob is not None) and + (ob.type in {'EMPTY', 'MESH'}) and + (context.mode == 'OBJECT')) + + def execute(self, context): + bpy.ops.gpencil.bake_mesh_animation( + frame_start=self.frame_start, + frame_end=self.frame_end, + step=self.step, + angle=self.angle, + thickness=self.thickness, + seams=self.seams, + faces=self.faces, + offset=self.offset, + target=self.target, + frame_target=self.frame_target, + project_type=self.project_type + ) + + return {'FINISHED'} + + def invoke(self, context, _event): + scene = context.scene + self.frame_start = scene.frame_start + self.frame_end = scene.frame_end + self.frame_target = scene.frame_start + + wm = context.window_manager + return wm.invoke_props_dialog(self) + + +classes = ( + GPENCIL_OT_mesh_bake, +) diff --git a/release/scripts/startup/bl_ui/space_view3d.py b/release/scripts/startup/bl_ui/space_view3d.py index c982d8e93a9..c4961125a46 100644 --- a/release/scripts/startup/bl_ui/space_view3d.py +++ b/release/scripts/startup/bl_ui/space_view3d.py @@ -2348,6 +2348,7 @@ class VIEW3D_MT_object_animation(Menu): layout.separator() layout.operator("nla.bake", text="Bake Action...") + layout.operator("gpencil.mesh_bake", text="Bake Mesh to Grease Pencil...") class VIEW3D_MT_object_rigid_body(Menu): diff --git a/source/blender/blenkernel/BKE_gpencil_geom.h b/source/blender/blenkernel/BKE_gpencil_geom.h index b26016aa26c..b79bbf3948f 100644 --- a/source/blender/blenkernel/BKE_gpencil_geom.h +++ b/source/blender/blenkernel/BKE_gpencil_geom.h @@ -98,6 +98,19 @@ bool BKE_gpencil_stroke_shrink(struct bGPDstroke *gps, const float dist); float BKE_gpencil_stroke_length(const struct bGPDstroke *gps, bool use_3d); +void BKE_gpencil_convert_mesh(struct Main *bmain, + struct Depsgraph *depsgraph, + struct Scene *scene, + struct Object *ob_gp, + struct Object *ob_mesh, + const float angle, + const int thickness, + const float offset, + const float matrix[4][4], + const int frame_offset, + const bool use_seams, + const bool use_faces); + #ifdef __cplusplus } #endif diff --git a/source/blender/blenkernel/intern/gpencil_geom.c b/source/blender/blenkernel/intern/gpencil_geom.c index d200e4e3a15..a67c36ea713 100644 --- a/source/blender/blenkernel/intern/gpencil_geom.c +++ b/source/blender/blenkernel/intern/gpencil_geom.c @@ -32,15 +32,22 @@ #include "MEM_guardedalloc.h" #include "BLI_blenlib.h" +#include "BLI_ghash.h" #include "BLI_math_vector.h" #include "BLI_polyfill_2d.h" +#include "BLT_translation.h" + #include "DNA_gpencil_types.h" +#include "DNA_mesh_types.h" #include "DNA_meshdata_types.h" +#include "DNA_scene_types.h" #include "BKE_deform.h" #include "BKE_gpencil.h" #include "BKE_gpencil_geom.h" +#include "BKE_main.h" +#include "BKE_material.h" #include "BKE_object.h" #include "DEG_depsgraph_query.h" @@ -1244,7 +1251,8 @@ bool BKE_gpencil_stroke_trim(bGPDstroke *gps) return false; } bool intersect = false; - int start, end; + int start = 0; + int end = 0; float point[3]; /* loop segments from start until we have an intersection */ for (int i = 0; i < gps->totpoints - 2; i++) { @@ -1580,6 +1588,384 @@ void BKE_gpencil_stroke_merge_distance(bGPDframe *gpf, BKE_gpencil_stroke_geometry_update(gps); } +typedef struct GpEdge { + uint v1, v2; + /* Coordinates. */ + float v1_co[3], v2_co[3]; + /* Normals. */ + float n1[3], n2[3]; + /* Direction of the segment. */ + float vec[3]; + int flag; +} GpEdge; + +static int gpencil_next_edge( + GpEdge *gp_edges, int totedges, GpEdge *gped_init, const float threshold, const bool reverse) +{ + int edge = -1; + float last_angle = 999999.0f; + for (int i = 0; i < totedges; i++) { + GpEdge *gped = &gp_edges[i]; + if (gped->flag != 0) { + continue; + } + if (reverse) { + if (gped_init->v1 != gped->v2) { + continue; + } + } + else { + if (gped_init->v2 != gped->v1) { + continue; + } + } + /* Look for straight lines. */ + float angle = angle_v3v3(gped->vec, gped_init->vec); + if ((angle < threshold) && (angle <= last_angle)) { + edge = i; + last_angle = angle; + } + } + + return edge; +} + +static int gpencil_walk_edge(GHash *v_table, + GpEdge *gp_edges, + int totedges, + uint *stroke_array, + int init_idx, + const float angle, + const bool reverse) +{ + GpEdge *gped_init = &gp_edges[init_idx]; + int idx = 1; + int edge = 0; + while (edge > -1) { + edge = gpencil_next_edge(gp_edges, totedges, gped_init, angle, reverse); + if (edge > -1) { + GpEdge *gped = &gp_edges[edge]; + stroke_array[idx] = edge; + gped->flag = 1; + gped_init = &gp_edges[edge]; + idx++; + + /* Avoid to follow already visited vertice. */ + if (reverse) { + if (BLI_ghash_haskey(v_table, POINTER_FROM_INT(gped->v1))) { + edge = -1; + } + else { + BLI_ghash_insert(v_table, POINTER_FROM_INT(gped->v1), POINTER_FROM_INT(gped->v1)); + } + } + else { + if (BLI_ghash_haskey(v_table, POINTER_FROM_INT(gped->v2))) { + edge = -1; + } + else { + BLI_ghash_insert(v_table, POINTER_FROM_INT(gped->v2), POINTER_FROM_INT(gped->v2)); + } + } + } + } + + return idx; +} + +static void gpencil_generate_edgeloops(Object *ob, + bGPDframe *gpf_stroke, + const float angle, + const int thickness, + const float offset, + const float matrix[4][4], + const bool use_seams) +{ + Mesh *me = (Mesh *)ob->data; + if (me->totedge == 0) { + return; + } + + /* Arrays for all edge vertices (forward and backward) that form a edge loop. + * This is reused for each edgeloop to create gpencil stroke. */ + uint *stroke = MEM_callocN(sizeof(uint) * me->totedge * 2, __func__); + uint *stroke_fw = MEM_callocN(sizeof(uint) * me->totedge, __func__); + uint *stroke_bw = MEM_callocN(sizeof(uint) * me->totedge, __func__); + + /* Create array with all edges. */ + GpEdge *gp_edges = MEM_callocN(sizeof(GpEdge) * me->totedge, __func__); + GpEdge *gped = NULL; + for (int i = 0; i < me->totedge; i++) { + MEdge *ed = &me->medge[i]; + gped = &gp_edges[i]; + MVert *mv1 = &me->mvert[ed->v1]; + normal_short_to_float_v3(gped->n1, mv1->no); + + gped->v1 = ed->v1; + copy_v3_v3(gped->v1_co, mv1->co); + + MVert *mv2 = &me->mvert[ed->v2]; + normal_short_to_float_v3(gped->n2, mv2->no); + gped->v2 = ed->v2; + copy_v3_v3(gped->v2_co, mv2->co); + + sub_v3_v3v3(gped->vec, mv1->co, mv2->co); + + /* If use seams, mark as done if not a seam. */ + if ((use_seams) && ((ed->flag & ME_SEAM) == 0)) { + gped->flag = 1; + } + } + + /* Loop edges to find edgeloops */ + bool pending = true; + int e = 0; + while (pending) { + /* Clear arrays of stroke. */ + memset(stroke_fw, 0, sizeof(uint) * me->totedge); + memset(stroke_bw, 0, sizeof(uint) * me->totedge); + memset(stroke, 0, sizeof(uint) * me->totedge * 2); + + gped = &gp_edges[e]; + /* Look first unused edge. */ + if (gped->flag != 0) { + e++; + if (e == me->totedge) { + pending = false; + } + continue; + } + /* Add current edge to arrays. */ + stroke_fw[0] = e; + stroke_bw[0] = e; + gped->flag = 1; + + /* Hash used to avoid loop over same vertice. */ + GHash *v_table = BLI_ghash_int_new(__func__); + /* Look forward edges. */ + int totedges = gpencil_walk_edge(v_table, gp_edges, me->totedge, stroke_fw, e, angle, false); + /* Look backward edges. */ + int totbw = gpencil_walk_edge(v_table, gp_edges, me->totedge, stroke_bw, e, angle, true); + + BLI_ghash_free(v_table, NULL, NULL); + + /* Join both arrays. */ + int array_len = 0; + for (int i = totbw - 1; i > 0; i--) { + stroke[array_len] = stroke_bw[i]; + array_len++; + } + for (int i = 0; i < totedges; i++) { + stroke[array_len] = stroke_fw[i]; + array_len++; + } + + /* Create Stroke. */ + bGPDstroke *gps_stroke = BKE_gpencil_stroke_add( + gpf_stroke, 0, array_len + 1, thickness * thickness, false); + + /* Create first segment. */ + float fpt[3]; + uint v = stroke[0]; + gped = &gp_edges[v]; + bGPDspoint *pt = &gps_stroke->points[0]; + mul_v3_v3fl(fpt, gped->n1, offset); + add_v3_v3v3(&pt->x, gped->v1_co, fpt); + mul_m4_v3(matrix, &pt->x); + + pt->pressure = 1.0f; + pt->strength = 1.0f; + + pt = &gps_stroke->points[1]; + mul_v3_v3fl(fpt, gped->n2, offset); + add_v3_v3v3(&pt->x, gped->v2_co, fpt); + mul_m4_v3(matrix, &pt->x); + + pt->pressure = 1.0f; + pt->strength = 1.0f; + + /* Add next segments. */ + for (int i = 1; i < array_len; i++) { + v = stroke[i]; + gped = &gp_edges[v]; + + pt = &gps_stroke->points[i + 1]; + mul_v3_v3fl(fpt, gped->n2, offset); + add_v3_v3v3(&pt->x, gped->v2_co, fpt); + mul_m4_v3(matrix, &pt->x); + + pt->pressure = 1.0f; + pt->strength = 1.0f; + } + + BKE_gpencil_stroke_geometry_update(gps_stroke); + } + + /* Free memory. */ + MEM_SAFE_FREE(stroke); + MEM_SAFE_FREE(stroke_fw); + MEM_SAFE_FREE(stroke_bw); + MEM_SAFE_FREE(gp_edges); +} + +/* Helper: Add gpencil material using material as base. */ +static Material *gpencil_add_material(Main *bmain, + Object *ob_gp, + char *name, + const float color[4], + const bool use_stroke, + const bool use_fill, + int *r_idx) +{ + Material *mat_gp = BKE_gpencil_object_material_new(bmain, ob_gp, name, r_idx); + MaterialGPencilStyle *gp_style = mat_gp->gp_style; + + /* Stroke color. */ + if (use_stroke) { + ARRAY_SET_ITEMS(gp_style->stroke_rgba, 0.0f, 0.0f, 0.0f, 1.0f); + gp_style->flag |= GP_MATERIAL_STROKE_SHOW; + } + else { + linearrgb_to_srgb_v4(gp_style->stroke_rgba, color); + gp_style->flag &= ~GP_MATERIAL_STROKE_SHOW; + } + + /* Fill color. */ + linearrgb_to_srgb_v4(gp_style->fill_rgba, color); + if (use_fill) { + gp_style->flag |= GP_MATERIAL_FILL_SHOW; + } + + /* Check at least one is enabled. */ + if (((gp_style->flag & GP_MATERIAL_STROKE_SHOW) == 0) && + ((gp_style->flag & GP_MATERIAL_FILL_SHOW) == 0)) { + gp_style->flag |= GP_MATERIAL_STROKE_SHOW; + } + + return mat_gp; +} + +/* Convert a mesh object to grease pencil stroke. + * + * \param bmain: Main thread pointer + * \param depsgraph: Original depsgraph. + * \param scene: Original scene. + * \param ob_gp: Grease pencil object to add strokes. + * \param ob_mesh: Mesh to convert. + * \param angle: Limit angle to consider a edgeloop ends. + * \param thickness: Thickness of the strokes. + * \param offset: Offset along the normals. + * \param matrix: Transformation matrix. + * \param frame_offset: Destination frame number offset. + * \param use_seams: Only export seam edges. + * \param use_faces: Export faces as filled strokes. + */ +void BKE_gpencil_convert_mesh(Main *bmain, + Depsgraph *depsgraph, + Scene *scene, + Object *ob_gp, + Object *ob_mesh, + const float angle, + const int thickness, + const float offset, + const float matrix[4][4], + const int frame_offset, + const bool use_seams, + const bool use_faces) +{ + if (ELEM(NULL, ob_gp, ob_mesh) || (ob_gp->type != OB_GPENCIL) || (ob_gp->data == NULL)) { + return; + } + + bGPdata *gpd = (bGPdata *)ob_gp->data; + + /* Use evaluated data to get mesh with all modifiers on top. */ + Object *ob_eval = (Object *)DEG_get_evaluated_object(depsgraph, ob_mesh); + Mesh *me_eval = BKE_object_get_evaluated_mesh(ob_eval); + MPoly *mp, *mpoly = me_eval->mpoly; + MLoop *mloop = me_eval->mloop; + int mpoly_len = me_eval->totpoly; + int i; + + /* If the object has enough materials means it was created in a previous step. */ + const bool create_mat = ((ob_gp->totcol > 0) && (ob_gp->totcol >= ob_mesh->totcol)) ? false : + true; + + /* Need at least an edge. */ + if (me_eval->totvert < 2) { + return; + } + + int r_idx; + const float default_colors[2][4] = {{0.0f, 0.0f, 0.0f, 1.0f}, {0.7f, 0.7f, 0.7f, 1.0f}}; + /* Create stroke material. */ + if (create_mat) { + gpencil_add_material(bmain, ob_gp, "Stroke", default_colors[0], true, false, &r_idx); + } + /* Export faces as filled strokes. */ + if (use_faces) { + if (create_mat) { + /* If no materials, create a simple fill. */ + if (ob_mesh->totcol == 0) { + gpencil_add_material(bmain, ob_gp, "Fill", default_colors[1], false, true, &r_idx); + } + else { + /* Create all materials for fill. */ + for (i = 0; i < ob_mesh->totcol; i++) { + Material *ma = BKE_object_material_get(ob_mesh, i + 1); + float color[4]; + copy_v3_v3(color, &ma->r); + color[3] = 1.0f; + gpencil_add_material(bmain, ob_gp, ma->id.name + 2, color, false, true, &r_idx); + } + } + } + + /* Read all polygons and create fill for each. */ + if (mpoly_len > 0) { + bGPDlayer *gpl_fill = BKE_gpencil_layer_named_get(gpd, DATA_("Fills")); + if (gpl_fill == NULL) { + gpl_fill = BKE_gpencil_layer_addnew(gpd, DATA_("Fills"), true); + } + bGPDframe *gpf_fill = BKE_gpencil_layer_frame_get( + gpl_fill, CFRA + frame_offset, GP_GETFRAME_ADD_NEW); + for (i = 0, mp = mpoly; i < mpoly_len; i++, mp++) { + MLoop *ml = &mloop[mp->loopstart]; + /* Create fill stroke. */ + bGPDstroke *gps_fill = BKE_gpencil_stroke_add( + gpf_fill, mp->mat_nr + 1, mp->totloop, 10, false); + gps_fill->flag |= GP_STROKE_CYCLIC; + + /* Add points to strokes. */ + int j; + for (j = 0; j < mp->totloop; j++, ml++) { + MVert *mv = &me_eval->mvert[ml->v]; + + bGPDspoint *pt = &gps_fill->points[j]; + copy_v3_v3(&pt->x, mv->co); + mul_m4_v3(matrix, &pt->x); + pt->pressure = 1.0f; + pt->strength = 1.0f; + } + + BKE_gpencil_stroke_geometry_update(gps_fill); + } + } + } + + /* Create stroke from edges. */ + bGPDlayer *gpl_stroke = BKE_gpencil_layer_named_get(gpd, DATA_("Lines")); + if (gpl_stroke == NULL) { + gpl_stroke = BKE_gpencil_layer_addnew(gpd, DATA_("Lines"), true); + } + bGPDframe *gpf_stroke = BKE_gpencil_layer_frame_get( + gpl_stroke, CFRA + frame_offset, GP_GETFRAME_ADD_NEW); + gpencil_generate_edgeloops(ob_eval, gpf_stroke, angle, thickness, offset, matrix, use_seams); + + /* Tag for recalculation */ + DEG_id_tag_update(&gpd->id, ID_RECALC_GEOMETRY | ID_RECALC_COPY_ON_WRITE); +} + /* Apply Transforms */ void BKE_gpencil_transform(bGPdata *gpd, float mat[4][4]) { diff --git a/source/blender/editors/gpencil/CMakeLists.txt b/source/blender/editors/gpencil/CMakeLists.txt index 4083169d65c..735ad8bc039 100644 --- a/source/blender/editors/gpencil/CMakeLists.txt +++ b/source/blender/editors/gpencil/CMakeLists.txt @@ -49,6 +49,7 @@ set(SRC gpencil_fill.c gpencil_interpolate.c gpencil_merge.c + gpencil_mesh.c gpencil_ops.c gpencil_ops_versioning.c gpencil_paint.c diff --git a/source/blender/editors/gpencil/gpencil_convert.c b/source/blender/editors/gpencil/gpencil_convert.c index 78a34cda2f5..f1b8ae61994 100644 --- a/source/blender/editors/gpencil/gpencil_convert.c +++ b/source/blender/editors/gpencil/gpencil_convert.c @@ -54,8 +54,10 @@ #include "BKE_fcurve.h" #include "BKE_global.h" #include "BKE_gpencil.h" +#include "BKE_gpencil_geom.h" #include "BKE_layer.h" #include "BKE_main.h" +#include "BKE_material.h" #include "BKE_object.h" #include "BKE_report.h" #include "BKE_scene.h" diff --git a/source/blender/editors/gpencil/gpencil_intern.h b/source/blender/editors/gpencil/gpencil_intern.h index 473913c5459..bc46842bb3b 100644 --- a/source/blender/editors/gpencil/gpencil_intern.h +++ b/source/blender/editors/gpencil/gpencil_intern.h @@ -485,6 +485,8 @@ void GPENCIL_OT_frame_clean_fill(struct wmOperatorType *ot); void GPENCIL_OT_frame_clean_loose(struct wmOperatorType *ot); void GPENCIL_OT_convert(struct wmOperatorType *ot); +void GPENCIL_OT_bake_mesh_animation(struct wmOperatorType *ot); + void GPENCIL_OT_image_to_grease_pencil(struct wmOperatorType *ot); enum { diff --git a/source/blender/editors/gpencil/gpencil_mesh.c b/source/blender/editors/gpencil/gpencil_mesh.c new file mode 100644 index 00000000000..ffb12404b43 --- /dev/null +++ b/source/blender/editors/gpencil/gpencil_mesh.c @@ -0,0 +1,404 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * The Original Code is Copyright (C) 2008, Blender Foundation + * This is a new part of Blender + * Operator for converting Grease Pencil data to geometry + */ + +/** \file + * \ingroup edgpencil + */ + +#include "MEM_guardedalloc.h" + +#include "BLI_blenlib.h" +#include "BLI_math.h" + +#include "DNA_gpencil_types.h" +#include "DNA_scene_types.h" +#include "DNA_screen_types.h" + +#include "BKE_context.h" +#include "BKE_duplilist.h" +#include "BKE_global.h" +#include "BKE_gpencil.h" +#include "BKE_gpencil_geom.h" +#include "BKE_main.h" +#include "BKE_material.h" +#include "BKE_object.h" +#include "BKE_report.h" +#include "BKE_scene.h" + +#include "DEG_depsgraph.h" +#include "DEG_depsgraph_query.h" + +#include "WM_api.h" +#include "WM_types.h" + +#include "RNA_access.h" +#include "RNA_define.h" + +#include "ED_gpencil.h" +#include "ED_transform_snap_object_context.h" + +#include "gpencil_intern.h" + +/* Check frame_end is always > start frame! */ +static void gp_bake_set_frame_end(struct Main *UNUSED(main), + struct Scene *UNUSED(scene), + struct PointerRNA *ptr) +{ + int frame_start = RNA_int_get(ptr, "frame_start"); + int frame_end = RNA_int_get(ptr, "frame_end"); + + if (frame_end <= frame_start) { + RNA_int_set(ptr, "frame_end", frame_start + 1); + } +} + +/* Extract mesh animation to Grease Pencil. */ +static bool gp_bake_mesh_animation_poll(bContext *C) +{ + if (CTX_data_mode_enum(C) != CTX_MODE_OBJECT) { + return false; + } + + /* Only if the current view is 3D View. */ + ScrArea *area = CTX_wm_area(C); + return (area && area->spacetype); +} + +typedef struct GpBakeOb { + struct GPBakelist *next, *prev; + Object *ob; +} GpBakeOb; + +static void gp_bake_duplilist(Depsgraph *depsgraph, Scene *scene, Object *ob, ListBase *list) +{ + GpBakeOb *elem = NULL; + ListBase *lb; + DupliObject *dob; + lb = object_duplilist(depsgraph, scene, ob); + for (dob = lb->first; dob; dob = dob->next) { + if (dob->ob->type != OB_MESH) { + continue; + } + elem = MEM_callocN(sizeof(GpBakeOb), __func__); + elem->ob = dob->ob; + BLI_addtail(list, elem); + } + + free_object_duplilist(lb); +} + +static void gp_bake_ob_list(bContext *C, Depsgraph *depsgraph, Scene *scene, ListBase *list) +{ + GpBakeOb *elem = NULL; + + /* Add active object. In some files this could not be in selected array. */ + Object *obact = CTX_data_active_object(C); + + if (obact->type == OB_MESH) { + elem = MEM_callocN(sizeof(GpBakeOb), __func__); + elem->ob = obact; + BLI_addtail(list, elem); + } + /* Add duplilist. */ + else if (obact->type == OB_EMPTY) { + gp_bake_duplilist(depsgraph, scene, obact, list); + } + + /* Add other selected objects. */ + CTX_DATA_BEGIN (C, Object *, ob, selected_objects) { + if (ob == obact) { + continue; + } + /* Add selected meshes.*/ + if (ob->type == OB_MESH) { + elem = MEM_callocN(sizeof(GpBakeOb), __func__); + elem->ob = ob; + BLI_addtail(list, elem); + } + + /* Add duplilist. */ + if (ob->type == OB_EMPTY) { + gp_bake_duplilist(depsgraph, scene, obact, list); + } + } + CTX_DATA_END; +} + +static void gp_bake_free_ob_list(ListBase *list) +{ + LISTBASE_FOREACH_MUTABLE (GpBakeOb *, elem, list) { + MEM_SAFE_FREE(elem); + } +} + +static int gp_bake_mesh_animation_exec(bContext *C, wmOperator *op) +{ + Main *bmain = CTX_data_main(C); + Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); + Scene *scene = CTX_data_scene(C); + ARegion *region = CTX_wm_region(C); + View3D *v3d = CTX_wm_view3d(C); + Object *ob_gpencil = NULL; + + ListBase list = {NULL, NULL}; + gp_bake_ob_list(C, depsgraph, scene, &list); + + /* Cannot check this in poll because the active object changes. */ + if (list.first == NULL) { + BKE_report(op->reports, RPT_INFO, "No valid object selected"); + gp_bake_free_ob_list(&list); + return OPERATOR_CANCELLED; + } + + /* Grab all relevant settings. */ + const int step = RNA_int_get(op->ptr, "step"); + + const int frame_start = (scene->r.sfra > RNA_int_get(op->ptr, "frame_start")) ? + scene->r.sfra : + RNA_int_get(op->ptr, "frame_start"); + + const int frame_end = (scene->r.efra < RNA_int_get(op->ptr, "frame_end")) ? + scene->r.efra : + RNA_int_get(op->ptr, "frame_end"); + + const float angle = RNA_float_get(op->ptr, "angle"); + const int thickness = RNA_int_get(op->ptr, "thickness"); + const bool use_seams = RNA_boolean_get(op->ptr, "seams"); + const bool use_faces = RNA_boolean_get(op->ptr, "faces"); + const float offset = RNA_float_get(op->ptr, "offset"); + const int frame_offset = RNA_int_get(op->ptr, "frame_target") - frame_start; + char target[64]; + RNA_string_get(op->ptr, "target", target); + const int project_type = RNA_enum_get(op->ptr, "project_type"); + + /* Create a new grease pencil object in origin. */ + bool newob = false; + if (STREQ(target, "*NEW")) { + ushort local_view_bits = (v3d && v3d->localvd) ? v3d->local_view_uuid : 0; + float loc[3] = {0.0f, 0.0f, 0.0f}; + ob_gpencil = ED_gpencil_add_object(C, loc, local_view_bits); + newob = true; + } + else { + ob_gpencil = BLI_findstring(&bmain->objects, target, offsetof(ID, name) + 2); + } + + if ((ob_gpencil == NULL) || (ob_gpencil->type != OB_GPENCIL)) { + BKE_report(op->reports, RPT_ERROR, "Target grease pencil object not valid"); + gp_bake_free_ob_list(&list); + return OPERATOR_CANCELLED; + } + + bGPdata *gpd = (bGPdata *)ob_gpencil->data; + gpd->draw_mode = (project_type == GP_REPROJECT_KEEP) ? GP_DRAWMODE_3D : GP_DRAWMODE_2D; + + /* Set cursor to indicate working. */ + WM_cursor_wait(1); + + GP_SpaceConversion gsc = {NULL}; + SnapObjectContext *sctx = NULL; + if (project_type != GP_REPROJECT_KEEP) { + /* Init space conversion stuff. */ + gp_point_conversion_init(C, &gsc); + /* Init snap context for geometry projection. */ + sctx = ED_transform_snap_object_context_create_view3d(scene, 0, region, CTX_wm_view3d(C)); + + /* Tag all existing strokes to avoid reprojections. */ + LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) { + LISTBASE_FOREACH (bGPDframe *, gpf, &gpl->frames) { + LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) { + gps->flag |= GP_STROKE_TAG; + } + } + } + } + + /* Loop all frame range. */ + int oldframe = (int)DEG_get_ctime(depsgraph); + int key = -1; + for (int i = frame_start; i < frame_end + 1; i++) { + key++; + /* Jump if not step limit but include last frame always. */ + if ((key % step != 0) && (i != frame_end)) { + continue; + } + + /* Move scene to new frame. */ + CFRA = i; + BKE_scene_graph_update_for_newframe(depsgraph, bmain); + + /* Loop all objects in the list. */ + LISTBASE_FOREACH (GpBakeOb *, elem, &list) { + Object *ob_eval = (Object *)DEG_get_evaluated_object(depsgraph, elem->ob); + + /* Generate strokes. */ + BKE_gpencil_convert_mesh(bmain, + depsgraph, + scene, + ob_gpencil, + elem->ob, + angle, + thickness, + offset, + ob_eval->obmat, + frame_offset, + use_seams, + use_faces); + + /* Reproject all untaged created strokes. */ + if (project_type != GP_REPROJECT_KEEP) { + LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) { + bGPDframe *gpf = gpl->actframe; + if (gpf != NULL) { + LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) { + if ((gps->flag & GP_STROKE_TAG) == 0) { + ED_gpencil_stroke_reproject( + depsgraph, &gsc, sctx, gpl, gpf, gps, project_type, false); + gps->flag |= GP_STROKE_TAG; + } + } + } + } + } + } + } + + /* Return scene frame state and DB to original state. */ + CFRA = oldframe; + BKE_scene_graph_update_for_newframe(depsgraph, bmain); + + /* Remove unused materials. */ + int actcol = ob_gpencil->actcol; + for (int slot = 1; slot <= ob_gpencil->totcol; slot++) { + while (slot <= ob_gpencil->totcol && !BKE_object_material_slot_used(ob_gpencil->data, slot)) { + ob_gpencil->actcol = slot; + BKE_object_material_slot_remove(CTX_data_main(C), ob_gpencil); + + if (actcol >= slot) { + actcol--; + } + } + } + ob_gpencil->actcol = actcol; + + /* Untag all strokes. */ + if (project_type != GP_REPROJECT_KEEP) { + LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) { + LISTBASE_FOREACH (bGPDframe *, gpf, &gpl->frames) { + LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) { + gps->flag &= ~GP_STROKE_TAG; + } + } + } + } + + /* Free memory. */ + gp_bake_free_ob_list(&list); + if (sctx != NULL) { + ED_transform_snap_object_context_destroy(sctx); + } + + /* notifiers */ + if (newob) { + DEG_relations_tag_update(bmain); + } + DEG_id_tag_update(&scene->id, ID_RECALC_SELECT); + WM_event_add_notifier(C, NC_OBJECT | NA_ADDED, NULL); + WM_event_add_notifier(C, NC_SCENE | ND_OB_ACTIVE, scene); + + /* Reset cursor. */ + WM_cursor_wait(0); + + /* done */ + return OPERATOR_FINISHED; +} + +void GPENCIL_OT_bake_mesh_animation(wmOperatorType *ot) +{ + static const EnumPropertyItem reproject_type[] = { + {GP_REPROJECT_KEEP, "KEEP", 0, "No Reproject", ""}, + {GP_REPROJECT_FRONT, "FRONT", 0, "Front", "Reproject the strokes using the X-Z plane"}, + {GP_REPROJECT_SIDE, "SIDE", 0, "Side", "Reproject the strokes using the Y-Z plane"}, + {GP_REPROJECT_TOP, "TOP", 0, "Top", "Reproject the strokes using the X-Y plane"}, + {GP_REPROJECT_VIEW, + "VIEW", + 0, + "View", + "Reproject the strokes to end up on the same plane, as if drawn from the current viewpoint " + "using 'Cursor' Stroke Placement"}, + {GP_REPROJECT_CURSOR, + "CURSOR", + 0, + "Cursor", + "Reproject the strokes using the orientation of 3D cursor"}, + {0, NULL, 0, NULL, NULL}, + }; + + PropertyRNA *prop; + + /* identifiers */ + ot->name = "Bake Mesh Animation to Grease Pencil"; + ot->idname = "GPENCIL_OT_bake_mesh_animation"; + ot->description = "Bake Mesh Animation to Grease Pencil strokes"; + + /* callbacks */ + ot->exec = gp_bake_mesh_animation_exec; + ot->poll = gp_bake_mesh_animation_poll; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + /* properties */ + ot->prop = RNA_def_int( + ot->srna, "frame_start", 1, 1, 100000, "Start Frame", "The start frame", 1, 100000); + + prop = RNA_def_int( + ot->srna, "frame_end", 250, 1, 100000, "End Frame", "The end frame of animation", 1, 100000); + RNA_def_property_update_runtime(prop, gp_bake_set_frame_end); + + prop = RNA_def_int(ot->srna, "step", 1, 1, 100, "Step", "Step between generated frames", 1, 100); + + prop = RNA_def_float_rotation(ot->srna, + "angle", + 0, + NULL, + DEG2RADF(0.0f), + DEG2RADF(180.0f), + "Threshold Angle", + "Threshold to determine ends of the strokes", + DEG2RADF(0.0f), + DEG2RADF(180.0f)); + RNA_def_property_float_default(prop, DEG2RADF(70.0f)); + + RNA_def_int(ot->srna, "thickness", 1, 1, 100, "Thickness", "", 1, 100); + RNA_def_boolean(ot->srna, "seams", 0, "Only Seam Edges", "Convert only seam edges"); + RNA_def_boolean(ot->srna, "faces", 1, "Export Faces", "Export faces as filled strokes"); + RNA_def_float_distance( + ot->srna, "offset", 0.001f, 0.0, 100.0, "Offset", "Offset strokes from fill", 0.0, 100.00); + RNA_def_int(ot->srna, "frame_target", 1, 1, 100000, "Frame Target", "", 1, 100000); + RNA_def_string(ot->srna, + "target", + "*NEW", + 64, + "Target Object", + "Target grease pencil object name. Leave empty for new object"); + + RNA_def_enum(ot->srna, "project_type", reproject_type, GP_REPROJECT_VIEW, "Projection Type", ""); +} diff --git a/source/blender/editors/gpencil/gpencil_ops.c b/source/blender/editors/gpencil/gpencil_ops.c index 94c86572fd3..7b92426efd4 100644 --- a/source/blender/editors/gpencil/gpencil_ops.c +++ b/source/blender/editors/gpencil/gpencil_ops.c @@ -603,6 +603,7 @@ void ED_operatortypes_gpencil(void) WM_operatortype_append(GPENCIL_OT_frame_clean_loose); WM_operatortype_append(GPENCIL_OT_convert); + WM_operatortype_append(GPENCIL_OT_bake_mesh_animation); WM_operatortype_append(GPENCIL_OT_image_to_grease_pencil); diff --git a/source/blender/editors/object/object_add.c b/source/blender/editors/object/object_add.c index 8289f52b0c8..7dfca69d7f0 100644 --- a/source/blender/editors/object/object_add.c +++ b/source/blender/editors/object/object_add.c @@ -65,6 +65,7 @@ #include "BKE_effect.h" #include "BKE_font.h" #include "BKE_gpencil_curve.h" +#include "BKE_gpencil_geom.h" #include "BKE_hair.h" #include "BKE_key.h" #include "BKE_lattice.h" @@ -96,6 +97,8 @@ #include "RNA_define.h" #include "RNA_enum_types.h" +#include "UI_interface.h" + #include "WM_api.h" #include "WM_types.h" @@ -2129,7 +2132,7 @@ void OBJECT_OT_duplicates_make_real(wmOperatorType *ot) static const EnumPropertyItem convert_target_items[] = { {OB_CURVE, "CURVE", ICON_OUTLINER_OB_CURVE, "Curve from Mesh/Text", ""}, {OB_MESH, "MESH", ICON_OUTLINER_OB_MESH, "Mesh from Curve/Meta/Surf/Text", ""}, - {OB_GPENCIL, "GPENCIL", ICON_OUTLINER_OB_GREASEPENCIL, "Grease Pencil from Curve", ""}, + {OB_GPENCIL, "GPENCIL", ICON_OUTLINER_OB_GREASEPENCIL, "Grease Pencil from Curve/Mesh", ""}, {0, NULL, 0, NULL, NULL}, }; @@ -2261,9 +2264,16 @@ static int convert_exec(bContext *C, wmOperator *op) Nurb *nu; MetaBall *mb; Mesh *me; - Object *gpencil_ob = NULL; + Object *ob_gpencil = NULL; const short target = RNA_enum_get(op->ptr, "target"); bool keep_original = RNA_boolean_get(op->ptr, "keep_original"); + + const float angle = RNA_float_get(op->ptr, "angle"); + const int thickness = RNA_int_get(op->ptr, "thickness"); + const bool use_seams = RNA_boolean_get(op->ptr, "seams"); + const bool use_faces = RNA_boolean_get(op->ptr, "faces"); + const float offset = RNA_float_get(op->ptr, "offset"); + int a, mballConverted = 0; bool gpencilConverted = false; @@ -2375,6 +2385,54 @@ static int convert_exec(bContext *C, wmOperator *op) ED_rigidbody_object_remove(bmain, scene, newob); } } + else if (ob->type == OB_MESH && target == OB_GPENCIL) { + ob->flag |= OB_DONE; + + /* Create a new grease pencil object and copy transformations. */ + ushort local_view_bits = (v3d && v3d->localvd) ? v3d->local_view_uuid : 0; + float loc[3], size[3], rot[3][3], eul[3]; + float matrix[4][4]; + mat4_to_loc_rot_size(loc, rot, size, ob->obmat); + mat3_to_eul(eul, rot); + + ob_gpencil = ED_gpencil_add_object(C, loc, local_view_bits); + copy_v3_v3(ob_gpencil->loc, loc); + copy_v3_v3(ob_gpencil->rot, eul); + copy_v3_v3(ob_gpencil->scale, size); + unit_m4(matrix); + /* Set object in 3D mode. */ + bGPdata *gpd = (bGPdata *)ob_gpencil->data; + gpd->draw_mode = GP_DRAWMODE_3D; + + BKE_gpencil_convert_mesh(bmain, + depsgraph, + scene, + ob_gpencil, + ob, + angle, + thickness, + offset, + matrix, + 0, + use_seams, + use_faces); + gpencilConverted = true; + + /* Remove unused materials. */ + int actcol = ob_gpencil->actcol; + for (int slot = 1; slot <= ob_gpencil->totcol; slot++) { + while (slot <= ob_gpencil->totcol && + !BKE_object_material_slot_used(ob_gpencil->data, slot)) { + ob_gpencil->actcol = slot; + BKE_object_material_slot_remove(CTX_data_main(C), ob_gpencil); + + if (actcol >= slot) { + actcol--; + } + } + } + ob_gpencil->actcol = actcol; + } else if (ob->type == OB_MESH) { ob->flag |= OB_DONE; @@ -2509,10 +2567,10 @@ static int convert_exec(bContext *C, wmOperator *op) * Nurbs Surface are not supported. */ ushort local_view_bits = (v3d && v3d->localvd) ? v3d->local_view_uuid : 0; - gpencil_ob = ED_gpencil_add_object(C, ob->loc, local_view_bits); - copy_v3_v3(gpencil_ob->rot, ob->rot); - copy_v3_v3(gpencil_ob->scale, ob->scale); - BKE_gpencil_convert_curve(bmain, scene, gpencil_ob, ob, false, false, true); + ob_gpencil = ED_gpencil_add_object(C, ob->loc, local_view_bits); + copy_v3_v3(ob_gpencil->rot, ob->rot); + copy_v3_v3(ob_gpencil->scale, ob->scale); + BKE_gpencil_convert_curve(bmain, scene, ob_gpencil, ob, false, false, true); gpencilConverted = true; } } @@ -2618,12 +2676,12 @@ static int convert_exec(bContext *C, wmOperator *op) } FOREACH_SCENE_OBJECT_END; } - /* Remove curves converted to Grease Pencil object. */ + /* Remove curves and meshes converted to Grease Pencil object. */ if (gpencilConverted) { - FOREACH_SCENE_OBJECT_BEGIN (scene, ob_curve) { - if (ob_curve->type == OB_CURVE) { - if (ob_curve->flag & OB_DONE) { - ED_object_base_free_and_unlink(bmain, scene, ob_curve); + FOREACH_SCENE_OBJECT_BEGIN (scene, ob_delete) { + if ((ob_delete->type == OB_CURVE) || (ob_delete->type == OB_MESH)) { + if (ob_delete->flag & OB_DONE) { + ED_object_base_free_and_unlink(bmain, scene, ob_delete); } } } @@ -2652,8 +2710,28 @@ static int convert_exec(bContext *C, wmOperator *op) return OPERATOR_FINISHED; } +static void convert_ui(bContext *C, wmOperator *op) +{ + uiLayout *layout = op->layout; + PointerRNA ptr; + + RNA_pointer_create(NULL, op->type->srna, op->properties, &ptr); + uiItemR(layout, &ptr, "target", 0, NULL, ICON_NONE); + uiItemR(layout, &ptr, "keep_original", 0, NULL, ICON_NONE); + + if (RNA_enum_get(&ptr, "target") == OB_GPENCIL) { + uiItemR(layout, &ptr, "thickness", 0, NULL, ICON_NONE); + uiItemR(layout, &ptr, "angle", 0, NULL, ICON_NONE); + uiItemR(layout, &ptr, "offset", 0, NULL, ICON_NONE); + uiItemR(layout, &ptr, "seams", 0, NULL, ICON_NONE); + uiItemR(layout, &ptr, "faces", 0, NULL, ICON_NONE); + } +} + void OBJECT_OT_convert(wmOperatorType *ot) { + PropertyRNA *prop; + /* identifiers */ ot->name = "Convert to"; ot->description = "Convert selected objects to another type"; @@ -2663,6 +2741,7 @@ void OBJECT_OT_convert(wmOperatorType *ot) ot->invoke = WM_menu_invoke; ot->exec = convert_exec; ot->poll = convert_poll; + ot->ui = convert_ui; /* flags */ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; @@ -2675,6 +2754,31 @@ void OBJECT_OT_convert(wmOperatorType *ot) 0, "Keep Original", "Keep original objects instead of replacing them"); + + prop = RNA_def_float_rotation(ot->srna, + "angle", + 0, + NULL, + DEG2RADF(0.0f), + DEG2RADF(180.0f), + "Threshold Angle", + "Threshold to determine ends of the strokes", + DEG2RADF(0.0f), + DEG2RADF(180.0f)); + RNA_def_property_float_default(prop, DEG2RADF(70.0f)); + + RNA_def_int(ot->srna, "thickness", 5, 1, 100, "Thickness", "", 1, 100); + RNA_def_boolean(ot->srna, "seams", 0, "Only Seam Edges", "Convert only seam edges"); + RNA_def_boolean(ot->srna, "faces", 1, "Export Faces", "Export faces as filled strokes"); + RNA_def_float_distance(ot->srna, + "offset", + 0.01f, + 0.0, + OBJECT_ADD_SIZE_MAXF, + "Stroke Offset", + "Offset strokes from fill", + 0.0, + 100.00); } /** \} */