Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAntonio Vazquez <blendergit@gmail.com>2020-06-16 16:28:46 +0300
committerAntonio Vazquez <blendergit@gmail.com>2020-06-16 16:28:46 +0300
commitbc7a4b126afb6789e301c148d8f07e38365c1545 (patch)
treea82f5af312381b55d355dab2af39b73503696b15 /source/blender
parente54058b1212202f5386ab53336e32ed01c441126 (diff)
GPencil: Convert and Bake mesh animation to grease pencil strokes
This patch adds two options: - Convert a mesh to grease pencil strokes. - Bake the mesh animation into grease pencil strokes. Both are related and must be included in the same patch. Related to tasks: T77629 and T77630 Notice: The conversion is done for mesh edges and it's not considering any visibility clipping. All edges are exported, no matters if it's visible or not. Example of Convert a Mesh to Grease Pencil strokes: {F8606028} This conversion was inspired by the technique used by @luamono in this tweet: https://twitter.com/luamono/status/1239983662176841730 Example of Bake Animation (the video is a little outdate, but the basic functionality is the same, only small changes in UI): {F8606032} Reviewed By: mendio, pepeland Maniphest Tasks: T77629, T77630 Differential Revision: https://developer.blender.org/D7983
Diffstat (limited to 'source/blender')
-rw-r--r--source/blender/blenkernel/BKE_gpencil_geom.h13
-rw-r--r--source/blender/blenkernel/intern/gpencil_geom.c388
-rw-r--r--source/blender/editors/gpencil/CMakeLists.txt1
-rw-r--r--source/blender/editors/gpencil/gpencil_convert.c2
-rw-r--r--source/blender/editors/gpencil/gpencil_intern.h2
-rw-r--r--source/blender/editors/gpencil/gpencil_mesh.c404
-rw-r--r--source/blender/editors/gpencil/gpencil_ops.c1
-rw-r--r--source/blender/editors/object/object_add.c126
8 files changed, 925 insertions, 12 deletions
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);
}
/** \} */