From 9d509de52a99a24d129f41beddaedc056d456fd5 Mon Sep 17 00:00:00 2001 From: YimingWu Date: Tue, 14 Jan 2025 08:28:52 +0100 Subject: [PATCH] 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 --- source/blender/editors/object/object_add.cc | 161 ++++++++++++++++++-- tests/python/object_conversion.py | 8 +- 2 files changed, 149 insertions(+), 20 deletions(-) diff --git a/source/blender/editors/object/object_add.cc b/source/blender/editors/object/object_add.cc index d3245b9ca52..0e47cf1b00c 100644 --- a/source/blender/editors/object/object_add.cc +++ b/source/blender/editors/object/object_add.cc @@ -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 &stroke_color, + const std::optional &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 mesh_positions = mesh_eval.vert_positions(); + const Span vert_normals = mesh_eval.vert_normals(); + const Span edges = mesh_eval.edges(); + const OffsetIndices faces = mesh_eval.faces(); + Span faces_span = faces.data(); + const Span 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 positions_fill = curves_fill.positions_for_write(); + MutableSpan offsets_fill = curves_fill.offsets_for_write(); + MutableSpan cyclic_fill = curves_fill.cyclic_for_write(); + bke::SpanAttributeWriter stroke_materials_fill = + curves_fill.attributes_for_write().lookup_or_add_for_write_span( + "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 positions = curves.positions_for_write(); + MutableSpan offsets = curves.offsets_for_write(); + MutableSpan 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", diff --git a/tests/python/object_conversion.py b/tests/python/object_conversion.py index 089a9dddd01..0046d217743 100644 --- a/tests/python/object_conversion.py +++ b/tests/python/object_conversion.py @@ -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'),