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:
parent
e54058b121
commit
bc7a4b126a
@ -49,6 +49,7 @@ _modules = [
|
||||
"uvcalc_smart_project",
|
||||
"vertexpaint_dirt",
|
||||
"view3d",
|
||||
"gpencil_mesh_bake",
|
||||
"wm",
|
||||
]
|
||||
|
||||
|
162
release/scripts/startup/bl_operators/gpencil_mesh_bake.py
Normal file
162
release/scripts/startup/bl_operators/gpencil_mesh_bake.py
Normal 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,
|
||||
)
|
@ -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):
|
||||
|
@ -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
|
||||
|
@ -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])
|
||||
{
|
||||
|
@ -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
|
||||
|
@ -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"
|
||||
|
@ -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 {
|
||||
|
404
source/blender/editors/gpencil/gpencil_mesh.c
Normal file
404
source/blender/editors/gpencil/gpencil_mesh.c
Normal 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", "");
|
||||
}
|
@ -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);
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
Loading…
Reference in New Issue
Block a user