mirror of
https://projects.blender.org/blender/blender.git
synced 2025-01-22 07:22:12 -05:00
Grease Pencil: Convert from mesh to Grease Pencil
Convert mesh objects to grease pencil, optionally preserve mesh faces as filled shapes, and convert crease edges into strokes. Following options are available: - Whether to convert faces to filled strokes. - Stroke thickness. - Stroke offset (Using normal direction to lift strokes out of mesh surfaces). Note that "Crease Angle" option from legacy grease pencil is not ported to this implementation. This option is deemed more suitable for using geometry nodes to achieve. Resolves #126480 Pull Request: https://projects.blender.org/blender/blender/pulls/131854
This commit is contained in:
parent
d675e886d4
commit
9d509de52a
2 changed files with 149 additions and 20 deletions
|
@ -35,6 +35,7 @@
|
|||
#include "DNA_scene_types.h"
|
||||
#include "DNA_vfont_types.h"
|
||||
|
||||
#include "BLI_array_utils.hh"
|
||||
#include "BLI_ghash.h"
|
||||
#include "BLI_listbase.h"
|
||||
#include "BLI_math_matrix.h"
|
||||
|
@ -2874,6 +2875,7 @@ struct ObjectConversionInfo {
|
|||
Object *obact;
|
||||
bool keep_original;
|
||||
bool do_merge_customdata;
|
||||
PointerRNA *op_props;
|
||||
ReportList *reports;
|
||||
};
|
||||
|
||||
|
@ -3086,6 +3088,145 @@ static Object *convert_mesh_to_mesh(Base &base, ObjectConversionInfo &info, Base
|
|||
return newob;
|
||||
}
|
||||
|
||||
static int mesh_to_grease_pencil_add_material(Main &bmain,
|
||||
Object &ob_grease_pencil,
|
||||
const StringRefNull name,
|
||||
const std::optional<float4> &stroke_color,
|
||||
const std::optional<float4> &fill_color)
|
||||
{
|
||||
int index;
|
||||
Material *ma = BKE_grease_pencil_object_material_ensure_by_name(
|
||||
&bmain, &ob_grease_pencil, DATA_(name.c_str()), &index);
|
||||
|
||||
if (stroke_color.has_value()) {
|
||||
copy_v4_v4(ma->gp_style->stroke_rgba, stroke_color.value());
|
||||
srgb_to_linearrgb_v4(ma->gp_style->stroke_rgba, ma->gp_style->stroke_rgba);
|
||||
}
|
||||
|
||||
if (fill_color.has_value()) {
|
||||
copy_v4_v4(ma->gp_style->fill_rgba, fill_color.value());
|
||||
srgb_to_linearrgb_v4(ma->gp_style->fill_rgba, ma->gp_style->fill_rgba);
|
||||
}
|
||||
|
||||
SET_FLAG_FROM_TEST(ma->gp_style->flag, stroke_color.has_value(), GP_MATERIAL_STROKE_SHOW);
|
||||
SET_FLAG_FROM_TEST(ma->gp_style->flag, fill_color.has_value(), GP_MATERIAL_FILL_SHOW);
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
static void mesh_data_to_grease_pencil(const Mesh &mesh_eval,
|
||||
GreasePencil &grease_pencil,
|
||||
const int current_frame,
|
||||
const bool generate_faces,
|
||||
const float stroke_radius,
|
||||
const float offset)
|
||||
{
|
||||
grease_pencil.flag |= GREASE_PENCIL_STROKE_ORDER_3D;
|
||||
|
||||
if (mesh_eval.edges_num <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
bke::greasepencil::Layer &layer_line = grease_pencil.add_layer(DATA_("Lines"));
|
||||
bke::greasepencil::Drawing *drawing_line = grease_pencil.insert_frame(layer_line, current_frame);
|
||||
|
||||
constexpr int face_mat_index = 1;
|
||||
const Span<float3> mesh_positions = mesh_eval.vert_positions();
|
||||
const Span<float3> vert_normals = mesh_eval.vert_normals();
|
||||
const Span<int2> edges = mesh_eval.edges();
|
||||
const OffsetIndices<int> faces = mesh_eval.faces();
|
||||
Span<int> faces_span = faces.data();
|
||||
const Span<int> corner_verts = mesh_eval.corner_verts();
|
||||
|
||||
if (generate_faces && !faces.is_empty()) {
|
||||
bke::greasepencil::Layer &layer_fill = grease_pencil.add_layer(DATA_("Fills"));
|
||||
bke::greasepencil::Drawing *drawing_fill = grease_pencil.insert_frame(layer_fill,
|
||||
current_frame);
|
||||
const int fills_num = faces.size();
|
||||
const int fills_points_num = corner_verts.size();
|
||||
|
||||
drawing_fill->strokes_for_write().resize(fills_points_num, fills_num);
|
||||
bke::CurvesGeometry &curves_fill = drawing_fill->strokes_for_write();
|
||||
MutableSpan<float3> positions_fill = curves_fill.positions_for_write();
|
||||
MutableSpan<int> offsets_fill = curves_fill.offsets_for_write();
|
||||
MutableSpan<bool> cyclic_fill = curves_fill.cyclic_for_write();
|
||||
bke::SpanAttributeWriter<int> stroke_materials_fill =
|
||||
curves_fill.attributes_for_write().lookup_or_add_for_write_span<int>(
|
||||
"material_index", bke::AttrDomain::Curve);
|
||||
curves_fill.fill_curve_types(CURVE_TYPE_POLY);
|
||||
|
||||
array_utils::gather(mesh_positions, corner_verts, positions_fill);
|
||||
array_utils::copy(faces_span, offsets_fill);
|
||||
cyclic_fill.fill(true);
|
||||
stroke_materials_fill.span.fill(face_mat_index);
|
||||
stroke_materials_fill.finish();
|
||||
}
|
||||
|
||||
const int edges_num = edges.size();
|
||||
const int points_num = edges_num * 2;
|
||||
|
||||
bke::CurvesGeometry &curves = drawing_line->strokes_for_write();
|
||||
curves.resize(points_num, edges_num);
|
||||
MutableSpan<float3> positions = curves.positions_for_write();
|
||||
MutableSpan<int> offsets = curves.offsets_for_write();
|
||||
MutableSpan<float> radii = curves.radius_for_write();
|
||||
curves.fill_curve_types(CURVE_TYPE_POLY);
|
||||
|
||||
for (const int edge_i : edges.index_range()) {
|
||||
const int2 edge = edges[edge_i];
|
||||
const int point_i = edge_i * 2;
|
||||
positions[point_i] = mesh_positions[edge[0]] + offset * vert_normals[edge[0]];
|
||||
positions[point_i + 1] = mesh_positions[edge[1]] + offset * vert_normals[edge[1]];
|
||||
radii[point_i] = radii[point_i + 1] = stroke_radius;
|
||||
}
|
||||
radii.fill(stroke_radius);
|
||||
|
||||
offset_indices::fill_constant_group_size(2, 0, offsets);
|
||||
}
|
||||
|
||||
static Object *convert_mesh_to_grease_pencil(Base &base,
|
||||
ObjectConversionInfo &info,
|
||||
Base **r_new_base)
|
||||
{
|
||||
Object *ob = base.object;
|
||||
ob->flag |= OB_DONE;
|
||||
Object *newob = get_object_for_conversion(base, info, r_new_base);
|
||||
|
||||
const bool generate_faces = RNA_boolean_get(info.op_props, "faces");
|
||||
const int thickness = RNA_int_get(info.op_props, "thickness");
|
||||
const float offset = RNA_float_get(info.op_props, "offset");
|
||||
|
||||
/* To be compatible with the thickness value prior to Grease Pencil v3. */
|
||||
const float stroke_radius = float(thickness) / 1000.0f;
|
||||
|
||||
const Object *ob_eval = DEG_get_evaluated_object(info.depsgraph, ob);
|
||||
const Mesh *mesh_eval = BKE_object_get_evaluated_mesh(ob_eval);
|
||||
|
||||
BKE_object_free_derived_caches(newob);
|
||||
BKE_object_free_modifiers(newob, 0);
|
||||
|
||||
GreasePencil *grease_pencil = BKE_grease_pencil_add(info.bmain, BKE_id_name(mesh_eval->id));
|
||||
newob->data = grease_pencil;
|
||||
newob->type = OB_GREASE_PENCIL;
|
||||
|
||||
/* Reset `ob->totcol` since currently the generic / grease pencil material functions still
|
||||
* depend on this value being coherent (The same value as `GreasePencil::material_array_num`).
|
||||
*/
|
||||
short *totcol = BKE_object_material_len_p(newob);
|
||||
newob->totcol = *totcol;
|
||||
|
||||
mesh_to_grease_pencil_add_material(
|
||||
*info.bmain, *newob, DATA_("Stroke"), float4(0.0f, 0.0f, 0.0f, 1.0f), {});
|
||||
if (generate_faces) {
|
||||
mesh_to_grease_pencil_add_material(*info.bmain, *newob, DATA_("Fill"), {}, float4(1.0f));
|
||||
}
|
||||
|
||||
mesh_data_to_grease_pencil(
|
||||
*mesh_eval, *grease_pencil, info.scene->r.cfra, generate_faces, stroke_radius, offset);
|
||||
|
||||
return newob;
|
||||
}
|
||||
|
||||
static Object *convert_mesh(Base &base,
|
||||
const ObjectType target,
|
||||
ObjectConversionInfo &info,
|
||||
|
@ -3100,6 +3241,8 @@ static Object *convert_mesh(Base &base,
|
|||
return convert_mesh_to_point_cloud(base, info, r_new_base);
|
||||
case OB_MESH:
|
||||
return convert_mesh_to_mesh(base, info, r_new_base);
|
||||
case OB_GREASE_PENCIL:
|
||||
return convert_mesh_to_grease_pencil(base, info, r_new_base);
|
||||
default:
|
||||
/* Current logic does convert mesh to mesh for any other target types. This would change
|
||||
* after other types of conversion are designed and implemented. */
|
||||
|
@ -3728,6 +3871,7 @@ static int object_convert_exec(bContext *C, wmOperator *op)
|
|||
info.obact = obact;
|
||||
info.keep_original = keep_original;
|
||||
info.do_merge_customdata = do_merge_customdata;
|
||||
info.op_props = op->ptr;
|
||||
info.reports = op->reports;
|
||||
|
||||
Base *act_base = nullptr;
|
||||
|
@ -3913,11 +4057,9 @@ static void object_convert_ui(bContext * /*C*/, wmOperator *op)
|
|||
if (target == OB_MESH) {
|
||||
uiItemR(layout, op->ptr, "merge_customdata", UI_ITEM_NONE, std::nullopt, ICON_NONE);
|
||||
}
|
||||
else if (target == OB_GPENCIL_LEGACY) {
|
||||
else if (target == OB_GREASE_PENCIL) {
|
||||
uiItemR(layout, op->ptr, "thickness", UI_ITEM_NONE, std::nullopt, ICON_NONE);
|
||||
uiItemR(layout, op->ptr, "angle", UI_ITEM_NONE, std::nullopt, ICON_NONE);
|
||||
uiItemR(layout, op->ptr, "offset", UI_ITEM_NONE, std::nullopt, ICON_NONE);
|
||||
uiItemR(layout, op->ptr, "seams", UI_ITEM_NONE, std::nullopt, ICON_NONE);
|
||||
uiItemR(layout, op->ptr, "faces", UI_ITEM_NONE, std::nullopt, ICON_NONE);
|
||||
}
|
||||
}
|
||||
|
@ -3959,20 +4101,7 @@ void OBJECT_OT_convert(wmOperatorType *ot)
|
|||
"Merge UVs",
|
||||
"Merge UV coordinates that share a vertex to account for imprecision in some modifiers");
|
||||
|
||||
prop = RNA_def_float_rotation(ot->srna,
|
||||
"angle",
|
||||
0,
|
||||
nullptr,
|
||||
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", false, "Only Seam Edges", "Convert only seam edges");
|
||||
RNA_def_boolean(ot->srna, "faces", true, "Export Faces", "Export faces as filled strokes");
|
||||
RNA_def_float_distance(ot->srna,
|
||||
"offset",
|
||||
|
|
|
@ -136,10 +136,10 @@ def main():
|
|||
ConversionPair('Curves 6', 'BezierCircle', 'CURVES', 'CURVES'),
|
||||
ConversionPair('Curves 7', 'BezierCurve', 'CURVES', 'CURVES'),
|
||||
ConversionPair('Curves 8', 'Text', 'CURVES', 'CURVES'),
|
||||
ConversionPair('GreasePencil 1', 'Cube', 'GREASEPENCIL', 'MESH'),
|
||||
ConversionPair('GreasePencil 2', 'CubeWithEdges', 'GREASEPENCIL', 'MESH'),
|
||||
ConversionPair('GreasePencil 3', 'Plane', 'GREASEPENCIL', 'MESH'),
|
||||
ConversionPair('GreasePencil 4', 'HollowPlane', 'GREASEPENCIL', 'MESH'),
|
||||
ConversionPair('GreasePencil 1', 'Cube', 'GREASEPENCIL', 'GREASEPENCIL'),
|
||||
ConversionPair('GreasePencil 2', 'CubeWithEdges', 'GREASEPENCIL', 'GREASEPENCIL'),
|
||||
ConversionPair('GreasePencil 3', 'Plane', 'GREASEPENCIL', 'GREASEPENCIL'),
|
||||
ConversionPair('GreasePencil 4', 'HollowPlane', 'GREASEPENCIL', 'GREASEPENCIL'),
|
||||
ConversionPair('GreasePencil 5', 'Suzanne', 'GREASEPENCIL', 'GREASEPENCIL'),
|
||||
ConversionPair('GreasePencil 6', 'BezierCircle', 'GREASEPENCIL', 'GREASEPENCIL'),
|
||||
ConversionPair('GreasePencil 7', 'BezierCurve', 'GREASEPENCIL', 'GREASEPENCIL'),
|
||||
|
|
Loading…
Reference in a new issue