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
This commit is contained in:
Antonio Vazquez 2020-06-16 15:28:46 +02:00
parent e54058b121
commit bc7a4b126a
Notes: blender-bot 2023-02-14 01:35:49 +01:00
Referenced by issue #82597, Crash running with --debug-wm and pressing F3
Referenced by issue #77629, GPencil: Convert Mesh to Grease Pencil stroke
Referenced by issue #77630, GPencil: Bake Mesh animation to Grease Pencil
11 changed files with 1089 additions and 12 deletions

View File

@ -49,6 +49,7 @@ _modules = [
"uvcalc_smart_project",
"vertexpaint_dirt",
"view3d",
"gpencil_mesh_bake",
"wm",
]

View File

@ -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 #####
# <pep8-80 compliant>
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,
)

View File

@ -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):

View File

@ -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

View File

@ -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])
{

View File

@ -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

View File

@ -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"

View File

@ -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 {

View File

@ -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", "");
}

View File

@ -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);

View File

@ -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);
}
/** \} */