GPv3: Outline modifier
Port of GPv2 outline modifier. Pull Request: https://projects.blender.org/blender/blender/pulls/118911
This commit is contained in:
@@ -160,6 +160,7 @@ class OBJECT_MT_modifier_add_generate(ModifierAddMenu, Menu):
|
||||
self.operator_modifier_add(layout, 'GREASE_PENCIL_LENGTH')
|
||||
self.operator_modifier_add(layout, 'GREASE_PENCIL_MIRROR')
|
||||
self.operator_modifier_add(layout, 'GREASE_PENCIL_MULTIPLY')
|
||||
self.operator_modifier_add(layout, 'GREASE_PENCIL_OUTLINE')
|
||||
self.operator_modifier_add(layout, 'GREASE_PENCIL_SUBDIV')
|
||||
self.operator_modifier_add(layout, 'LINEART')
|
||||
layout.template_modifier_asset_menu_items(catalog_path=self.bl_label)
|
||||
|
||||
@@ -1324,6 +1324,40 @@ static void legacy_object_modifier_opacity(Object &object, GpencilModifierData &
|
||||
legacy_md_opacity.flag & GP_OPACITY_CUSTOM_CURVE);
|
||||
}
|
||||
|
||||
static void legacy_object_modifier_outline(Object &object, GpencilModifierData &legacy_md)
|
||||
{
|
||||
ModifierData &md = legacy_object_modifier_common(
|
||||
object, eModifierType_GreasePencilOutline, legacy_md);
|
||||
auto &md_outline = reinterpret_cast<GreasePencilOutlineModifierData &>(md);
|
||||
auto &legacy_md_outline = reinterpret_cast<OutlineGpencilModifierData &>(legacy_md);
|
||||
|
||||
md_outline.flag = 0;
|
||||
if (legacy_md_outline.flag & GP_OUTLINE_KEEP_SHAPE) {
|
||||
md_outline.flag |= MOD_GREASE_PENCIL_OUTLINE_KEEP_SHAPE;
|
||||
}
|
||||
md_outline.object = legacy_md_outline.object;
|
||||
legacy_md_outline.object = nullptr;
|
||||
md_outline.outline_material = legacy_md_outline.outline_material;
|
||||
legacy_md_outline.outline_material = nullptr;
|
||||
md_outline.sample_length = legacy_md_outline.sample_length;
|
||||
md_outline.subdiv = legacy_md_outline.subdiv;
|
||||
md_outline.thickness = legacy_md_outline.thickness;
|
||||
|
||||
legacy_object_modifier_influence(md_outline.influence,
|
||||
legacy_md_outline.layername,
|
||||
legacy_md_outline.layer_pass,
|
||||
legacy_md_outline.flag & GP_OUTLINE_INVERT_LAYER,
|
||||
legacy_md_outline.flag & GP_OUTLINE_INVERT_LAYERPASS,
|
||||
&legacy_md_outline.material,
|
||||
legacy_md_outline.pass_index,
|
||||
legacy_md_outline.flag & GP_OUTLINE_INVERT_MATERIAL,
|
||||
legacy_md_outline.flag & GP_OUTLINE_INVERT_PASS,
|
||||
"",
|
||||
false,
|
||||
nullptr,
|
||||
false);
|
||||
}
|
||||
|
||||
static void legacy_object_modifier_smooth(Object &object, GpencilModifierData &legacy_md)
|
||||
{
|
||||
ModifierData &md = legacy_object_modifier_common(
|
||||
@@ -1697,6 +1731,9 @@ static void legacy_object_modifiers(Main & /*bmain*/, Object &object)
|
||||
case eGpencilModifierType_Opacity:
|
||||
legacy_object_modifier_opacity(object, *gpd_md);
|
||||
break;
|
||||
case eGpencilModifierType_Outline:
|
||||
legacy_object_modifier_outline(object, *gpd_md);
|
||||
break;
|
||||
case eGpencilModifierType_Smooth:
|
||||
legacy_object_modifier_smooth(object, *gpd_md);
|
||||
break;
|
||||
@@ -1725,7 +1762,6 @@ static void legacy_object_modifiers(Main & /*bmain*/, Object &object)
|
||||
case eGpencilModifierType_Simplify:
|
||||
case eGpencilModifierType_Texture:
|
||||
case eGpencilModifierType_Shrinkwrap:
|
||||
case eGpencilModifierType_Outline:
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
@@ -1021,4 +1021,13 @@
|
||||
.skip = 0, \
|
||||
}
|
||||
|
||||
#define _DNA_DEFAULT_GreasePencilOutlineModifierData \
|
||||
{ \
|
||||
.flag = MOD_GREASE_PENCIL_OUTLINE_KEEP_SHAPE, \
|
||||
.thickness = 1, \
|
||||
.sample_length = 0.0f, \
|
||||
.subdiv = 3, \
|
||||
.outline_material = NULL, \
|
||||
}
|
||||
|
||||
/* clang-format off */
|
||||
|
||||
@@ -116,6 +116,7 @@ typedef enum ModifierType {
|
||||
eModifierType_GreasePencilArmature = 79,
|
||||
eModifierType_GreasePencilTime = 80,
|
||||
eModifierType_GreasePencilEnvelope = 81,
|
||||
eModifierType_GreasePencilOutline = 82,
|
||||
NUM_MODIFIER_TYPES,
|
||||
} ModifierType;
|
||||
|
||||
@@ -3250,3 +3251,25 @@ typedef enum GreasePencilEnvelopeModifierMode {
|
||||
MOD_GREASE_PENCIL_ENVELOPE_SEGMENTS = 1,
|
||||
MOD_GREASE_PENCIL_ENVELOPE_FILLS = 2,
|
||||
} GreasePencilEnvelopeModifierMode;
|
||||
|
||||
typedef struct GreasePencilOutlineModifierData {
|
||||
ModifierData modifier;
|
||||
GreasePencilModifierInfluenceData influence;
|
||||
|
||||
/** Target stroke origin. */
|
||||
struct Object *object;
|
||||
/** #GreasePencilOutlineModifierFlag. */
|
||||
int flag;
|
||||
/** Thickness. */
|
||||
int thickness;
|
||||
/** Sample Length. */
|
||||
float sample_length;
|
||||
/** Subdivisions. */
|
||||
int subdiv;
|
||||
/** Material for outline. */
|
||||
struct Material *outline_material;
|
||||
} GreasePencilOutlineModifierData;
|
||||
|
||||
typedef enum GreasePencilOutlineModifierFlag {
|
||||
MOD_GREASE_PENCIL_OUTLINE_KEEP_SHAPE = (1 << 0),
|
||||
} GreasePencilOutlineModifierFlag;
|
||||
|
||||
@@ -350,6 +350,7 @@ SDNA_DEFAULT_DECL_STRUCT(GreasePencilArmatureModifierData);
|
||||
SDNA_DEFAULT_DECL_STRUCT(GreasePencilTimeModifierSegment);
|
||||
SDNA_DEFAULT_DECL_STRUCT(GreasePencilTimeModifierData);
|
||||
SDNA_DEFAULT_DECL_STRUCT(GreasePencilEnvelopeModifierData);
|
||||
SDNA_DEFAULT_DECL_STRUCT(GreasePencilOutlineModifierData);
|
||||
|
||||
#undef SDNA_DEFAULT_DECL_STRUCT
|
||||
|
||||
@@ -619,6 +620,7 @@ const void *DNA_default_table[SDNA_TYPE_MAX] = {
|
||||
SDNA_DEFAULT_DECL(GreasePencilTimeModifierSegment),
|
||||
SDNA_DEFAULT_DECL(GreasePencilTimeModifierData),
|
||||
SDNA_DEFAULT_DECL(GreasePencilEnvelopeModifierData),
|
||||
SDNA_DEFAULT_DECL(GreasePencilOutlineModifierData),
|
||||
};
|
||||
#undef SDNA_DEFAULT_DECL
|
||||
#undef SDNA_DEFAULT_DECL_EX
|
||||
|
||||
@@ -264,6 +264,11 @@ const EnumPropertyItem rna_enum_object_modifier_type_items[] = {
|
||||
ICON_MOD_ENVELOPE,
|
||||
"Envelope",
|
||||
"Create an envelope shape"},
|
||||
{eModifierType_GreasePencilOutline,
|
||||
"GREASE_PENCIL_OUTLINE",
|
||||
ICON_MOD_OUTLINE,
|
||||
"Outline",
|
||||
"Convert stroke to perimeter"},
|
||||
|
||||
RNA_ENUM_ITEM_HEADING(N_("Deform"), nullptr),
|
||||
{eModifierType_Armature,
|
||||
@@ -1016,6 +1021,7 @@ RNA_MOD_OBJECT_SET(GreasePencilLattice, object, OB_LATTICE);
|
||||
RNA_MOD_OBJECT_SET(GreasePencilWeightProximity, object, OB_EMPTY);
|
||||
RNA_MOD_OBJECT_SET(GreasePencilHook, object, OB_EMPTY);
|
||||
RNA_MOD_OBJECT_SET(GreasePencilArmature, object, OB_ARMATURE);
|
||||
RNA_MOD_OBJECT_SET(GreasePencilOutline, object, OB_EMPTY);
|
||||
|
||||
static void rna_HookModifier_object_set(PointerRNA *ptr,
|
||||
PointerRNA value,
|
||||
@@ -1954,6 +1960,7 @@ RNA_MOD_GREASE_PENCIL_MATERIAL_FILTER_SET(GreasePencilArray);
|
||||
RNA_MOD_GREASE_PENCIL_MATERIAL_FILTER_SET(GreasePencilWeightProximity);
|
||||
RNA_MOD_GREASE_PENCIL_MATERIAL_FILTER_SET(GreasePencilHook);
|
||||
RNA_MOD_GREASE_PENCIL_MATERIAL_FILTER_SET(GreasePencilEnvelope);
|
||||
RNA_MOD_GREASE_PENCIL_MATERIAL_FILTER_SET(GreasePencilOutline);
|
||||
|
||||
RNA_MOD_GREASE_PENCIL_VERTEX_GROUP_SET(GreasePencilOffset);
|
||||
RNA_MOD_GREASE_PENCIL_VERTEX_GROUP_SET(GreasePencilOpacity);
|
||||
@@ -2169,6 +2176,14 @@ static void rna_GreasePencilTimeModifier_end_frame_set(PointerRNA *ptr, int valu
|
||||
}
|
||||
}
|
||||
|
||||
static void rna_GreasePencilOutlineModifier_outline_material_set(PointerRNA *ptr,
|
||||
PointerRNA value,
|
||||
ReportList *reports)
|
||||
{
|
||||
GreasePencilOutlineModifierData *omd = static_cast<GreasePencilOutlineModifierData *>(ptr->data);
|
||||
rna_GreasePencilModifier_material_set(ptr, value, reports, &omd->outline_material);
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
static void rna_def_modifier_panel_open_prop(StructRNA *srna, const char *identifier, const int id)
|
||||
@@ -10195,6 +10210,67 @@ static void rna_def_modifier_grease_pencil_envelope(BlenderRNA *brna)
|
||||
RNA_define_lib_overridable(false);
|
||||
}
|
||||
|
||||
static void rna_def_modifier_grease_pencil_outline(BlenderRNA *brna)
|
||||
{
|
||||
StructRNA *srna;
|
||||
PropertyRNA *prop;
|
||||
|
||||
srna = RNA_def_struct(brna, "GreasePencilOutlineModifier", "Modifier");
|
||||
RNA_def_struct_ui_text(srna, "Outline Modifier", "Outline of Strokes modifier from camera view");
|
||||
RNA_def_struct_sdna(srna, "GreasePencilOutlineModifierData");
|
||||
RNA_def_struct_ui_icon(srna, ICON_MOD_OUTLINE);
|
||||
|
||||
rna_def_modifier_grease_pencil_layer_filter(srna);
|
||||
rna_def_modifier_grease_pencil_material_filter(
|
||||
srna, "rna_GreasePencilOutlineModifier_material_filter_set");
|
||||
|
||||
rna_def_modifier_panel_open_prop(srna, "open_influence_panel", 0);
|
||||
|
||||
RNA_define_lib_overridable(true);
|
||||
|
||||
prop = RNA_def_property(srna, "thickness", PROP_INT, PROP_NONE);
|
||||
RNA_def_property_int_sdna(prop, nullptr, "thickness");
|
||||
RNA_def_property_range(prop, 1, 1000);
|
||||
RNA_def_property_ui_text(prop, "Thickness", "Thickness of the perimeter stroke");
|
||||
RNA_def_property_update(prop, 0, "rna_Modifier_update");
|
||||
|
||||
prop = RNA_def_property(srna, "sample_length", PROP_FLOAT, PROP_NONE);
|
||||
RNA_def_property_float_sdna(prop, nullptr, "sample_length");
|
||||
RNA_def_property_ui_range(prop, 0.0f, 100.0f, 0.1f, 2);
|
||||
RNA_def_property_ui_text(prop, "Sample Length", "");
|
||||
RNA_def_property_update(prop, 0, "rna_Modifier_update");
|
||||
|
||||
prop = RNA_def_property(srna, "subdivision", PROP_INT, PROP_NONE);
|
||||
RNA_def_property_int_sdna(prop, nullptr, "subdiv");
|
||||
RNA_def_property_range(prop, 0, 10);
|
||||
RNA_def_property_ui_text(prop, "Subdivisions", "Number of subdivisions");
|
||||
RNA_def_property_update(prop, 0, "rna_Modifier_update");
|
||||
|
||||
prop = RNA_def_property(srna, "use_keep_shape", PROP_BOOLEAN, PROP_NONE);
|
||||
RNA_def_property_boolean_sdna(prop, nullptr, "flag", MOD_GREASE_PENCIL_OUTLINE_KEEP_SHAPE);
|
||||
RNA_def_property_ui_text(prop, "Keep Shape", "Try to keep global shape");
|
||||
RNA_def_property_update(prop, 0, "rna_Modifier_update");
|
||||
|
||||
prop = RNA_def_property(srna, "outline_material", PROP_POINTER, PROP_NONE);
|
||||
RNA_def_property_flag(prop, PROP_EDITABLE);
|
||||
RNA_def_property_pointer_funcs(prop,
|
||||
nullptr,
|
||||
"rna_GreasePencilOutlineModifier_outline_material_set",
|
||||
nullptr,
|
||||
"rna_GreasePencilModifier_material_poll");
|
||||
RNA_def_property_ui_text(prop, "Outline Material", "Material used for outline strokes");
|
||||
RNA_def_property_update(prop, 0, "rna_Modifier_update");
|
||||
|
||||
prop = RNA_def_property(srna, "object", PROP_POINTER, PROP_NONE);
|
||||
RNA_def_property_ui_text(prop, "Target Object", "Target object to define stroke start");
|
||||
RNA_def_property_flag(prop, PROP_EDITABLE | PROP_ID_SELF_CHECK);
|
||||
RNA_def_property_pointer_funcs(
|
||||
prop, nullptr, "rna_GreasePencilOutlineModifier_object_set", nullptr, nullptr);
|
||||
RNA_def_property_update(prop, 0, "rna_Modifier_dependency_update");
|
||||
|
||||
RNA_define_lib_overridable(false);
|
||||
}
|
||||
|
||||
void RNA_def_modifier(BlenderRNA *brna)
|
||||
{
|
||||
StructRNA *srna;
|
||||
@@ -10385,6 +10461,7 @@ void RNA_def_modifier(BlenderRNA *brna)
|
||||
rna_def_modifier_grease_pencil_time_segment(brna);
|
||||
rna_def_modifier_grease_pencil_time(brna);
|
||||
rna_def_modifier_grease_pencil_envelope(brna);
|
||||
rna_def_modifier_grease_pencil_outline(brna);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -62,6 +62,7 @@ set(SRC
|
||||
intern/MOD_grease_pencil_noise.cc
|
||||
intern/MOD_grease_pencil_offset.cc
|
||||
intern/MOD_grease_pencil_opacity.cc
|
||||
intern/MOD_grease_pencil_outline.cc
|
||||
intern/MOD_grease_pencil_smooth.cc
|
||||
intern/MOD_grease_pencil_subdiv.cc
|
||||
intern/MOD_grease_pencil_thickness.cc
|
||||
|
||||
@@ -94,6 +94,7 @@ extern ModifierTypeInfo modifierType_GreasePencilLineart;
|
||||
extern ModifierTypeInfo modifierType_GreasePencilArmature;
|
||||
extern ModifierTypeInfo modifierType_GreasePencilTime;
|
||||
extern ModifierTypeInfo modifierType_GreasePencilEnvelope;
|
||||
extern ModifierTypeInfo modifierType_GreasePencilOutline;
|
||||
|
||||
/* MOD_util.cc */
|
||||
|
||||
|
||||
726
source/blender/modifiers/intern/MOD_grease_pencil_outline.cc
Normal file
726
source/blender/modifiers/intern/MOD_grease_pencil_outline.cc
Normal file
@@ -0,0 +1,726 @@
|
||||
/* SPDX-FileCopyrightText: 2024 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup modifiers
|
||||
*/
|
||||
|
||||
#include "BKE_anonymous_attribute_id.hh"
|
||||
#include "BKE_attribute.hh"
|
||||
#include "BKE_material.h"
|
||||
#include "BLI_array_utils.hh"
|
||||
#include "BLI_enumerable_thread_specific.hh"
|
||||
#include "BLI_index_range.hh"
|
||||
#include "BLI_math_matrix.hh"
|
||||
#include "BLI_math_vector.hh"
|
||||
#include "BLI_offset_indices.hh"
|
||||
#include "BLI_span.hh"
|
||||
#include "BLI_string.h"
|
||||
#include "BLI_string_utf8.h"
|
||||
|
||||
#include "BLI_virtual_array.hh"
|
||||
#include "DNA_defaults.h"
|
||||
#include "DNA_modifier_types.h"
|
||||
#include "DNA_scene_types.h"
|
||||
|
||||
#include "BKE_curves.hh"
|
||||
#include "BKE_geometry_set.hh"
|
||||
#include "BKE_grease_pencil.hh"
|
||||
#include "BKE_instances.hh"
|
||||
#include "BKE_lib_query.hh"
|
||||
#include "BKE_modifier.hh"
|
||||
#include "BKE_screen.hh"
|
||||
|
||||
#include "BLO_read_write.hh"
|
||||
|
||||
#include "DEG_depsgraph_query.hh"
|
||||
|
||||
#include "GEO_resample_curves.hh"
|
||||
|
||||
#include "UI_interface.hh"
|
||||
#include "UI_resources.hh"
|
||||
|
||||
#include "BLT_translation.hh"
|
||||
|
||||
#include "WM_api.hh"
|
||||
#include "WM_types.hh"
|
||||
|
||||
#include "RNA_access.hh"
|
||||
#include "RNA_prototypes.h"
|
||||
|
||||
#include "MOD_grease_pencil_util.hh"
|
||||
#include "MOD_ui_common.hh"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
namespace blender {
|
||||
|
||||
static void init_data(ModifierData *md)
|
||||
{
|
||||
auto *omd = reinterpret_cast<GreasePencilOutlineModifierData *>(md);
|
||||
|
||||
BLI_assert(MEMCMP_STRUCT_AFTER_IS_ZERO(omd, modifier));
|
||||
|
||||
MEMCPY_STRUCT_AFTER(omd, DNA_struct_default_get(GreasePencilOutlineModifierData), modifier);
|
||||
modifier::greasepencil::init_influence_data(&omd->influence, false);
|
||||
}
|
||||
|
||||
static void copy_data(const ModifierData *md, ModifierData *target, const int flag)
|
||||
{
|
||||
const auto *omd = reinterpret_cast<const GreasePencilOutlineModifierData *>(md);
|
||||
auto *tmmd = reinterpret_cast<GreasePencilOutlineModifierData *>(target);
|
||||
|
||||
modifier::greasepencil::free_influence_data(&tmmd->influence);
|
||||
|
||||
BKE_modifier_copydata_generic(md, target, flag);
|
||||
modifier::greasepencil::copy_influence_data(&omd->influence, &tmmd->influence, flag);
|
||||
}
|
||||
|
||||
static void free_data(ModifierData *md)
|
||||
{
|
||||
auto *omd = reinterpret_cast<GreasePencilOutlineModifierData *>(md);
|
||||
modifier::greasepencil::free_influence_data(&omd->influence);
|
||||
}
|
||||
|
||||
static void foreach_ID_link(ModifierData *md, Object *ob, IDWalkFunc walk, void *user_data)
|
||||
{
|
||||
auto *omd = reinterpret_cast<GreasePencilOutlineModifierData *>(md);
|
||||
modifier::greasepencil::foreach_influence_ID_link(&omd->influence, ob, walk, user_data);
|
||||
walk(user_data, ob, (ID **)&omd->outline_material, IDWALK_CB_USER);
|
||||
walk(user_data, ob, (ID **)&omd->object, IDWALK_CB_NOP);
|
||||
}
|
||||
|
||||
static void update_depsgraph(ModifierData *md, const ModifierUpdateDepsgraphContext *ctx)
|
||||
{
|
||||
auto *omd = reinterpret_cast<GreasePencilOutlineModifierData *>(md);
|
||||
if (ctx->scene->camera) {
|
||||
DEG_add_object_relation(
|
||||
ctx->node, ctx->scene->camera, DEG_OB_COMP_TRANSFORM, "Grease Pencil Outline Modifier");
|
||||
DEG_add_object_relation(
|
||||
ctx->node, ctx->scene->camera, DEG_OB_COMP_PARAMETERS, "Grease Pencil Outline Modifier");
|
||||
}
|
||||
if (omd->object != nullptr) {
|
||||
DEG_add_object_relation(
|
||||
ctx->node, omd->object, DEG_OB_COMP_TRANSFORM, "Grease Pencil Outline Modifier");
|
||||
}
|
||||
DEG_add_object_relation(
|
||||
ctx->node, ctx->object, DEG_OB_COMP_TRANSFORM, "Grease Pencil Outline Modifier");
|
||||
}
|
||||
|
||||
/**
|
||||
* Rearrange curve buffers by moving points from the start to the back of each stroke.
|
||||
* \note This is an optional feature. The offset is determine by the closest point to an object.
|
||||
* \param curve_offsets Offset of each curve, indicating the point that becomes the new start.
|
||||
*/
|
||||
static bke::CurvesGeometry reorder_cyclic_curve_points(const bke::CurvesGeometry &src_curves,
|
||||
const IndexMask &curve_selection,
|
||||
const Span<int> curve_offsets)
|
||||
{
|
||||
BLI_assert(curve_offsets.size() == src_curves.curves_num());
|
||||
|
||||
OffsetIndices<int> src_offsets = src_curves.points_by_curve();
|
||||
bke::AttributeAccessor src_attributes = src_curves.attributes();
|
||||
|
||||
Array<int> indices(src_curves.points_num());
|
||||
curve_selection.foreach_index(GrainSize(512), [&](const int64_t curve_i) {
|
||||
const IndexRange points = src_offsets[curve_i];
|
||||
const int point_num = points.size();
|
||||
const int point_start = points.start();
|
||||
MutableSpan<int> point_indices = indices.as_mutable_span().slice(points);
|
||||
if (points.size() < 2) {
|
||||
array_utils::fill_index_range(point_indices, point_start);
|
||||
return;
|
||||
}
|
||||
/* Offset can be negative or larger than the buffer. Use modulo to get an
|
||||
* equivalent offset within buffer size to simplify copying. */
|
||||
const int offset_raw = curve_offsets[curve_i];
|
||||
const int offset = offset_raw >= 0 ? offset_raw % points.size() :
|
||||
points.size() - ((-offset_raw) % points.size());
|
||||
BLI_assert(0 <= offset && offset < points.size());
|
||||
if (offset == 0) {
|
||||
array_utils::fill_index_range(point_indices, point_start);
|
||||
return;
|
||||
}
|
||||
|
||||
const int point_middle = point_start + offset;
|
||||
array_utils::fill_index_range(point_indices.take_front(point_num - offset), point_middle);
|
||||
array_utils::fill_index_range(point_indices.take_back(offset), point_start);
|
||||
});
|
||||
|
||||
/* Have to make a copy of the input geometry, gather_attributes does not work in-place when the
|
||||
* source indices are not ordered. */
|
||||
bke::CurvesGeometry dst_curves(src_curves);
|
||||
bke::MutableAttributeAccessor dst_attributes = dst_curves.attributes_for_write();
|
||||
bke::gather_attributes(src_attributes, bke::AttrDomain::Point, {}, {}, indices, dst_attributes);
|
||||
|
||||
return dst_curves;
|
||||
}
|
||||
|
||||
static int find_closest_point(const Span<float3> positions, const float3 &target)
|
||||
{
|
||||
if (positions.is_empty()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int closest_i = 0;
|
||||
float min_dist_squared = math::distance_squared(positions.first(), target);
|
||||
for (const int i : positions.index_range().drop_front(1)) {
|
||||
const float dist_squared = math::distance_squared(positions[i], target);
|
||||
if (dist_squared < min_dist_squared) {
|
||||
closest_i = i;
|
||||
min_dist_squared = dist_squared;
|
||||
}
|
||||
}
|
||||
return closest_i;
|
||||
}
|
||||
|
||||
/* Generate points in an arc between two directions. */
|
||||
static void generate_arc_from_point_to_point(const float3 &from,
|
||||
const float3 &to,
|
||||
const float3 ¢er_pt,
|
||||
const int subdivisions,
|
||||
const int src_point_index,
|
||||
Vector<float3> &r_perimeter,
|
||||
Vector<int> &r_src_indices)
|
||||
{
|
||||
const float3 vec_from = from - center_pt;
|
||||
const float3 vec_to = to - center_pt;
|
||||
if (math::is_zero(vec_from) || math::is_zero(vec_to)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const float dot = math::dot(vec_from.xy(), vec_to.xy());
|
||||
const float det = vec_from.x * vec_to.y - vec_from.y * vec_to.x;
|
||||
const float angle = math::atan2(-det, -dot) + M_PI;
|
||||
|
||||
/* Number of points is 2^(n+1) + 1 on half a circle (n=subdivisions)
|
||||
* so we multiply by (angle / pi) to get the right amount of
|
||||
* points to insert. */
|
||||
const int num_points = std::max(int(((1 << (subdivisions + 1)) + 1) * (math::abs(angle) / M_PI)),
|
||||
2);
|
||||
const float delta_angle = angle / float(num_points - 1);
|
||||
const float delta_cos = math::cos(delta_angle);
|
||||
const float delta_sin = math::sin(delta_angle);
|
||||
|
||||
float3 vec = vec_from;
|
||||
for ([[maybe_unused]] const int i : IndexRange(num_points)) {
|
||||
r_perimeter.append(center_pt + vec);
|
||||
r_src_indices.append(src_point_index);
|
||||
|
||||
const float x = delta_cos * vec.x - delta_sin * vec.y;
|
||||
const float y = delta_sin * vec.x + delta_cos * vec.y;
|
||||
vec = float3(x, y, 0.0f);
|
||||
}
|
||||
}
|
||||
|
||||
/* Generate a semi-circle around a point, opposite the direction. */
|
||||
static void generate_cap(const float3 &point,
|
||||
const float3 &tangent,
|
||||
const float radius,
|
||||
const int subdivisions,
|
||||
const eGPDstroke_Caps cap_type,
|
||||
const int src_point_index,
|
||||
Vector<float3> &r_perimeter,
|
||||
Vector<int> &r_src_indices)
|
||||
{
|
||||
const float3 normal = {tangent.y, -tangent.x, 0.0f};
|
||||
switch (cap_type) {
|
||||
case GP_STROKE_CAP_ROUND:
|
||||
generate_arc_from_point_to_point(point - normal * radius,
|
||||
point + normal * radius,
|
||||
point,
|
||||
subdivisions,
|
||||
src_point_index,
|
||||
r_perimeter,
|
||||
r_src_indices);
|
||||
break;
|
||||
case GP_STROKE_CAP_FLAT:
|
||||
r_perimeter.append(point + normal * radius);
|
||||
r_src_indices.append(src_point_index);
|
||||
break;
|
||||
case GP_STROKE_CAP_MAX:
|
||||
BLI_assert_unreachable();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Generate a corner between two segments, with a rounded outer perimeter.
|
||||
* Note: The perimeter is considered to be to the right hand side of the stroke. The left side
|
||||
* perimeter can be generated by reversing the order of points. */
|
||||
static void generate_corner(const float3 &pt_a,
|
||||
const float3 &pt_b,
|
||||
const float3 &pt_c,
|
||||
const float radius,
|
||||
const int subdivisions,
|
||||
const int src_point_index,
|
||||
Vector<float3> &r_perimeter,
|
||||
Vector<int> &r_src_indices)
|
||||
{
|
||||
const float length = math::length(pt_c - pt_b);
|
||||
const float length_prev = math::length(pt_b - pt_a);
|
||||
const float3 tangent = math::normalize(pt_c - pt_b);
|
||||
const float3 tangent_prev = math::normalize(pt_b - pt_a);
|
||||
const float3 normal = {tangent.y, -tangent.x, 0.0f};
|
||||
const float3 normal_prev = {tangent_prev.y, -tangent_prev.x, 0.0f};
|
||||
|
||||
const float sin_angle = tangent_prev.x * tangent.y - tangent_prev.y * tangent.x;
|
||||
/* Whether the corner is an inside or outside corner.
|
||||
* This determines whether an arc is added or a single miter point. */
|
||||
const bool is_outside_corner = (sin_angle >= 0.0f);
|
||||
if (is_outside_corner) {
|
||||
generate_arc_from_point_to_point(pt_b + normal_prev * radius,
|
||||
pt_b + normal * radius,
|
||||
pt_b,
|
||||
subdivisions,
|
||||
src_point_index,
|
||||
r_perimeter,
|
||||
r_src_indices);
|
||||
}
|
||||
else {
|
||||
const float3 avg_tangent = math::normalize(tangent_prev + tangent);
|
||||
const float3 miter = {avg_tangent.y, -avg_tangent.x, 0.0f};
|
||||
const float miter_invscale = math::dot(normal, miter);
|
||||
|
||||
/* Avoid division by tiny values for steep angles. */
|
||||
const float3 miter_point = (radius < length * miter_invscale &&
|
||||
radius < length_prev * miter_invscale) ?
|
||||
pt_b + miter * radius / miter_invscale :
|
||||
pt_b + miter * radius;
|
||||
|
||||
r_perimeter.append(miter_point);
|
||||
r_src_indices.append(src_point_index);
|
||||
}
|
||||
}
|
||||
|
||||
static void generate_stroke_perimeter(const Span<float3> all_positions,
|
||||
const VArray<float> all_radii,
|
||||
const IndexRange points,
|
||||
const int subdivisions,
|
||||
const bool is_cyclic,
|
||||
const bool use_caps,
|
||||
const eGPDstroke_Caps start_cap_type,
|
||||
const eGPDstroke_Caps end_cap_type,
|
||||
const float radius_offset,
|
||||
Vector<float3> &r_perimeter,
|
||||
Vector<int> &r_point_counts,
|
||||
Vector<int> &r_point_indices)
|
||||
{
|
||||
const Span<float3> positions = all_positions.slice(points);
|
||||
const int point_num = points.size();
|
||||
if (point_num < 2) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto add_corner = [&](const int a, const int b, const int c) {
|
||||
const int point = points[b];
|
||||
const float3 pt_a = positions[a];
|
||||
const float3 pt_b = positions[b];
|
||||
const float3 pt_c = positions[c];
|
||||
const float radius = all_radii[point] + radius_offset;
|
||||
generate_corner(pt_a, pt_b, pt_c, radius, subdivisions, point, r_perimeter, r_point_indices);
|
||||
};
|
||||
auto add_cap = [&](const int center_i, const int next_i, const eGPDstroke_Caps cap_type) {
|
||||
const int point = points[center_i];
|
||||
const float3 ¢er = positions[center_i];
|
||||
const float3 dir = math::normalize(positions[next_i] - center);
|
||||
const float radius = all_radii[point] + radius_offset;
|
||||
generate_cap(center, dir, radius, subdivisions, cap_type, point, r_perimeter, r_point_indices);
|
||||
};
|
||||
|
||||
/* Creates a single cyclic curve with end caps. */
|
||||
if (use_caps) {
|
||||
/* Open curves generate a start and end cap and a connecting stroke on either side. */
|
||||
const int perimeter_start = r_perimeter.size();
|
||||
|
||||
/* Start cap. */
|
||||
add_cap(0, 1, start_cap_type);
|
||||
|
||||
/* Left perimeter half. */
|
||||
for (const int i : points.index_range().drop_front(1).drop_back(1)) {
|
||||
add_corner(i - 1, i, i + 1);
|
||||
}
|
||||
if (is_cyclic) {
|
||||
add_corner(point_num - 2, point_num - 1, 0);
|
||||
}
|
||||
|
||||
/* End cap. */
|
||||
if (is_cyclic) {
|
||||
/* End point is same as start point. */
|
||||
add_cap(0, point_num - 1, end_cap_type);
|
||||
}
|
||||
else {
|
||||
/* End point is last point of the curve. */
|
||||
add_cap(point_num - 1, point_num - 2, end_cap_type);
|
||||
}
|
||||
|
||||
/* Right perimeter half. */
|
||||
if (is_cyclic) {
|
||||
add_corner(0, point_num - 1, point_num - 2);
|
||||
}
|
||||
for (const int i : points.index_range().drop_front(1).drop_back(1)) {
|
||||
add_corner(point_num - i, point_num - i - 1, point_num - i - 2);
|
||||
}
|
||||
|
||||
const int perimeter_count = r_perimeter.size() - perimeter_start;
|
||||
if (perimeter_count > 0) {
|
||||
r_point_counts.append(perimeter_count);
|
||||
}
|
||||
}
|
||||
else {
|
||||
/* Generate separate "inside" and an "outside" perimeter curves.
|
||||
* The distinction is arbitrary, called left/right here. */
|
||||
|
||||
/* Left side perimeter. */
|
||||
const int left_perimeter_start = r_perimeter.size();
|
||||
add_corner(point_num - 1, 0, 1);
|
||||
for (const int i : points.index_range().drop_front(1).drop_back(1)) {
|
||||
add_corner(i - 1, i, i + 1);
|
||||
}
|
||||
add_corner(point_num - 2, point_num - 1, 0);
|
||||
const int left_perimeter_count = r_perimeter.size() - left_perimeter_start;
|
||||
if (left_perimeter_count > 0) {
|
||||
r_point_counts.append(left_perimeter_count);
|
||||
}
|
||||
|
||||
/* Right side perimeter. */
|
||||
const int right_perimeter_start = r_perimeter.size();
|
||||
add_corner(0, point_num - 1, point_num - 2);
|
||||
for (const int i : points.index_range().drop_front(1).drop_back(1)) {
|
||||
add_corner(point_num - i, point_num - i - 1, point_num - i - 2);
|
||||
}
|
||||
add_corner(1, 0, point_num - 1);
|
||||
const int right_perimeter_count = r_perimeter.size() - right_perimeter_start;
|
||||
if (right_perimeter_count > 0) {
|
||||
r_point_counts.append(right_perimeter_count);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct PerimeterData {
|
||||
/* New points per curve count. */
|
||||
Vector<int> point_counts;
|
||||
/* New point coordinates. */
|
||||
Vector<float3> positions;
|
||||
/* Source curve index. */
|
||||
Vector<int> curve_indices;
|
||||
/* Source point index. */
|
||||
Vector<int> point_indices;
|
||||
};
|
||||
|
||||
static bke::CurvesGeometry create_curves_outline(const bke::greasepencil::Drawing &drawing,
|
||||
const float4x4 &viewmat,
|
||||
const IndexMask &curves_mask,
|
||||
const int subdivisions,
|
||||
const float stroke_radius,
|
||||
int stroke_mat_nr,
|
||||
const bool keep_shape)
|
||||
{
|
||||
const bke::CurvesGeometry &src_curves = drawing.strokes();
|
||||
Span<float3> src_positions = src_curves.positions();
|
||||
bke::AttributeAccessor src_attributes = src_curves.attributes();
|
||||
const VArray<float> src_radii = drawing.radii();
|
||||
const VArray<bool> src_cyclic = *src_attributes.lookup_or_default(
|
||||
"cyclic", bke::AttrDomain::Curve, false);
|
||||
const VArray<int8_t> src_start_caps = *src_attributes.lookup_or_default<int8_t>(
|
||||
"start_cap", bke::AttrDomain::Curve, GP_STROKE_CAP_ROUND);
|
||||
const VArray<int8_t> src_end_caps = *src_attributes.lookup_or_default<int8_t>(
|
||||
"end_cap", bke::AttrDomain::Curve, GP_STROKE_CAP_ROUND);
|
||||
const VArray<int> src_material_index = *src_attributes.lookup_or_default(
|
||||
"material_index", bke::AttrDomain::Curve, -1);
|
||||
|
||||
/* Transform positions into view space. */
|
||||
Array<float3> view_positions(src_positions.size());
|
||||
threading::parallel_for(view_positions.index_range(), 4096, [&](const IndexRange range) {
|
||||
for (const int i : range) {
|
||||
view_positions[i] = math::transform_point(viewmat, src_positions[i]);
|
||||
}
|
||||
});
|
||||
|
||||
const float4x4 viewinv = math::invert(viewmat);
|
||||
threading::EnumerableThreadSpecific<PerimeterData> thread_data;
|
||||
curves_mask.foreach_index([&](const int64_t curve_i) {
|
||||
PerimeterData &data = thread_data.local();
|
||||
|
||||
const bool is_cyclic_curve = src_cyclic[curve_i];
|
||||
/* Note: Cyclic curves would better be represented by a cyclic perimeter without end caps, but
|
||||
* we always generate caps for compatibility with GPv2. Fill materials cannot create holes, so
|
||||
* a cyclic outline does not work well. */
|
||||
const bool use_caps = true /*!is_cyclic_curve*/;
|
||||
|
||||
const int prev_point_num = data.positions.size();
|
||||
const int prev_curve_num = data.point_counts.size();
|
||||
const IndexRange points = src_curves.points_by_curve()[curve_i];
|
||||
/* Offset the strokes by the radius so the outside aligns with the input stroke. */
|
||||
const float radius_offset = keep_shape ? -stroke_radius : 0.0f;
|
||||
generate_stroke_perimeter(view_positions,
|
||||
src_radii,
|
||||
points,
|
||||
subdivisions,
|
||||
is_cyclic_curve,
|
||||
use_caps,
|
||||
eGPDstroke_Caps(src_start_caps[curve_i]),
|
||||
eGPDstroke_Caps(src_end_caps[curve_i]),
|
||||
radius_offset,
|
||||
data.positions,
|
||||
data.point_counts,
|
||||
data.point_indices);
|
||||
|
||||
/* Transform perimeter positions back into object space. */
|
||||
for (float3 &pos : data.positions.as_mutable_span().drop_front(prev_point_num)) {
|
||||
pos = math::transform_point(viewinv, pos);
|
||||
}
|
||||
|
||||
data.curve_indices.append_n_times(curve_i, data.point_counts.size() - prev_curve_num);
|
||||
});
|
||||
|
||||
int dst_curve_num = 0;
|
||||
int dst_point_num = 0;
|
||||
for (const PerimeterData &data : thread_data) {
|
||||
BLI_assert(data.point_counts.size() == data.curve_indices.size());
|
||||
BLI_assert(data.positions.size() == data.point_indices.size());
|
||||
dst_curve_num += data.point_counts.size();
|
||||
dst_point_num += data.positions.size();
|
||||
}
|
||||
|
||||
bke::CurvesGeometry dst_curves(dst_point_num, dst_curve_num);
|
||||
if (dst_point_num == 0 || dst_curve_num == 0) {
|
||||
return dst_curves;
|
||||
}
|
||||
|
||||
bke::MutableAttributeAccessor dst_attributes = dst_curves.attributes_for_write();
|
||||
bke::SpanAttributeWriter<bool> dst_cyclic = dst_attributes.lookup_or_add_for_write_span<bool>(
|
||||
"cyclic", bke::AttrDomain::Curve);
|
||||
bke::SpanAttributeWriter<int> dst_material = dst_attributes.lookup_or_add_for_write_span<int>(
|
||||
"material_index", bke::AttrDomain::Curve);
|
||||
bke::SpanAttributeWriter<float> dst_radius = dst_attributes.lookup_or_add_for_write_span<float>(
|
||||
"radius", bke::AttrDomain::Point);
|
||||
const MutableSpan<int> dst_offsets = dst_curves.offsets_for_write();
|
||||
const MutableSpan<float3> dst_positions = dst_curves.positions_for_write();
|
||||
/* Source indices for attribute mapping. */
|
||||
Array<int> dst_curve_map(dst_curve_num);
|
||||
Array<int> dst_point_map(dst_point_num);
|
||||
|
||||
IndexRange curves;
|
||||
IndexRange points;
|
||||
for (const PerimeterData &data : thread_data) {
|
||||
curves = curves.after(data.point_counts.size());
|
||||
points = points.after(data.positions.size());
|
||||
|
||||
/* Append curve data. */
|
||||
dst_curve_map.as_mutable_span().slice(curves).copy_from(data.curve_indices);
|
||||
/* Curve offsets are accumulated below. */
|
||||
dst_offsets.slice(curves).copy_from(data.point_counts);
|
||||
dst_cyclic.span.slice(curves).fill(true);
|
||||
if (stroke_mat_nr >= 0) {
|
||||
dst_material.span.slice(curves).fill(stroke_mat_nr);
|
||||
}
|
||||
else {
|
||||
for (const int i : curves.index_range()) {
|
||||
dst_material.span[curves[i]] = src_material_index[data.curve_indices[i]];
|
||||
}
|
||||
}
|
||||
|
||||
/* Append point data. */
|
||||
dst_positions.slice(points).copy_from(data.positions);
|
||||
dst_point_map.as_mutable_span().slice(points).copy_from(data.point_indices);
|
||||
dst_radius.span.slice(points).fill(stroke_radius);
|
||||
}
|
||||
offset_indices::accumulate_counts_to_offsets(dst_curves.offsets_for_write());
|
||||
|
||||
bke::gather_attributes(src_attributes,
|
||||
bke::AttrDomain::Point,
|
||||
{},
|
||||
{"position", "radius"},
|
||||
dst_point_map,
|
||||
dst_attributes);
|
||||
bke::gather_attributes(src_attributes,
|
||||
bke::AttrDomain::Curve,
|
||||
{},
|
||||
{"cyclic", "material_index"},
|
||||
dst_curve_map,
|
||||
dst_attributes);
|
||||
|
||||
dst_cyclic.finish();
|
||||
dst_material.finish();
|
||||
dst_radius.finish();
|
||||
dst_curves.update_curve_types();
|
||||
|
||||
return dst_curves;
|
||||
}
|
||||
|
||||
static void modify_drawing(const GreasePencilOutlineModifierData &omd,
|
||||
const ModifierEvalContext &ctx,
|
||||
bke::greasepencil::Drawing &drawing,
|
||||
const float4x4 &viewmat)
|
||||
{
|
||||
if (drawing.strokes().curve_num == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Selected source curves. */
|
||||
IndexMaskMemory curve_mask_memory;
|
||||
const IndexMask curves_mask = modifier::greasepencil::get_filtered_stroke_mask(
|
||||
ctx.object, drawing.strokes(), omd.influence, curve_mask_memory);
|
||||
|
||||
/* Unit object scale is applied to the stroke radius. */
|
||||
const float object_scale = math::length(
|
||||
math::transform_direction(ctx.object->object_to_world(), float3(M_SQRT1_3)));
|
||||
/* Legacy thickness setting is diameter in pixels, divide by 2000 to get radius. */
|
||||
const float radius = math::max(omd.thickness * object_scale, 1.0f) * 0.0005f;
|
||||
const bool keep_shape = omd.flag & MOD_GREASE_PENCIL_OUTLINE_KEEP_SHAPE;
|
||||
const int mat_nr = (omd.outline_material ?
|
||||
BKE_object_material_index_get(ctx.object, omd.outline_material) :
|
||||
-1);
|
||||
|
||||
bke::CurvesGeometry curves = create_curves_outline(
|
||||
drawing, viewmat, curves_mask, omd.subdiv, radius, mat_nr, keep_shape);
|
||||
|
||||
/* Cyclic curve reordering feature. */
|
||||
if (omd.object) {
|
||||
const OffsetIndices points_by_curve = curves.points_by_curve();
|
||||
|
||||
/* Computes the offset of the closest point to the object from the curve start. */
|
||||
Array<int> offset_by_curve(curves.curves_num());
|
||||
for (const int i : curves.curves_range()) {
|
||||
const IndexRange points = points_by_curve[i];
|
||||
/* Closest point index is already relative to the point range and can be used as offset. */
|
||||
offset_by_curve[i] = find_closest_point(curves.positions().slice(points), omd.object->loc);
|
||||
}
|
||||
|
||||
curves = reorder_cyclic_curve_points(curves, curves.curves_range(), offset_by_curve);
|
||||
}
|
||||
|
||||
/* Resampling feature. */
|
||||
if (omd.sample_length > 0.0f) {
|
||||
VArray<float> sample_lengths = VArray<float>::ForSingle(omd.sample_length,
|
||||
curves.curves_num());
|
||||
curves = geometry::resample_to_length(curves, curves.curves_range(), sample_lengths);
|
||||
}
|
||||
|
||||
drawing.strokes_for_write() = std::move(curves);
|
||||
drawing.tag_topology_changed();
|
||||
}
|
||||
|
||||
static void modify_geometry_set(ModifierData *md,
|
||||
const ModifierEvalContext *ctx,
|
||||
bke::GeometrySet *geometry_set)
|
||||
{
|
||||
using bke::greasepencil::Drawing;
|
||||
using bke::greasepencil::Layer;
|
||||
using modifier::greasepencil::LayerDrawingInfo;
|
||||
|
||||
const auto &omd = *reinterpret_cast<const GreasePencilOutlineModifierData *>(md);
|
||||
|
||||
const Scene *scene = DEG_get_evaluated_scene(ctx->depsgraph);
|
||||
if (!scene->camera) {
|
||||
return;
|
||||
}
|
||||
const float4x4 viewinv = scene->camera->world_to_object();
|
||||
|
||||
if (!geometry_set->has_grease_pencil()) {
|
||||
return;
|
||||
}
|
||||
GreasePencil &grease_pencil = *geometry_set->get_grease_pencil_for_write();
|
||||
const int frame = grease_pencil.runtime->eval_frame;
|
||||
|
||||
IndexMaskMemory mask_memory;
|
||||
const IndexMask layer_mask = modifier::greasepencil::get_filtered_layer_mask(
|
||||
grease_pencil, omd.influence, mask_memory);
|
||||
|
||||
const Vector<LayerDrawingInfo> drawings = modifier::greasepencil::get_drawing_infos_by_layer(
|
||||
grease_pencil, layer_mask, frame);
|
||||
threading::parallel_for_each(drawings, [&](const LayerDrawingInfo &info) {
|
||||
const Layer &layer = *grease_pencil.layers()[info.layer_index];
|
||||
const float4x4 viewmat = viewinv * layer.to_world_space(*ctx->object);
|
||||
modify_drawing(omd, *ctx, *info.drawing, viewmat);
|
||||
});
|
||||
}
|
||||
|
||||
static void panel_draw(const bContext *C, Panel *panel)
|
||||
{
|
||||
uiLayout *layout = panel->layout;
|
||||
|
||||
PointerRNA ob_ptr;
|
||||
PointerRNA *ptr = modifier_panel_get_property_pointers(panel, &ob_ptr);
|
||||
|
||||
uiLayoutSetPropSep(layout, true);
|
||||
|
||||
uiItemR(layout, ptr, "thickness", UI_ITEM_NONE, nullptr, ICON_NONE);
|
||||
uiItemR(layout, ptr, "use_keep_shape", UI_ITEM_NONE, nullptr, ICON_NONE);
|
||||
uiItemR(layout, ptr, "subdivision", UI_ITEM_NONE, nullptr, ICON_NONE);
|
||||
uiItemR(layout, ptr, "sample_length", UI_ITEM_NONE, nullptr, ICON_NONE);
|
||||
uiItemR(layout, ptr, "outline_material", UI_ITEM_NONE, nullptr, ICON_NONE);
|
||||
uiItemR(layout, ptr, "object", UI_ITEM_NONE, nullptr, ICON_NONE);
|
||||
|
||||
Scene *scene = CTX_data_scene(C);
|
||||
if (scene->camera == nullptr) {
|
||||
uiItemL(layout, RPT_("Outline requires an active camera"), ICON_ERROR);
|
||||
}
|
||||
|
||||
if (uiLayout *influence_panel = uiLayoutPanelProp(
|
||||
C, layout, ptr, "open_influence_panel", "Influence"))
|
||||
{
|
||||
modifier::greasepencil::draw_layer_filter_settings(C, influence_panel, ptr);
|
||||
modifier::greasepencil::draw_material_filter_settings(C, influence_panel, ptr);
|
||||
}
|
||||
|
||||
modifier_panel_end(layout, ptr);
|
||||
}
|
||||
|
||||
static void panel_register(ARegionType *region_type)
|
||||
{
|
||||
modifier_panel_register(region_type, eModifierType_GreasePencilOutline, panel_draw);
|
||||
}
|
||||
|
||||
static void blend_write(BlendWriter *writer, const ID * /*id_owner*/, const ModifierData *md)
|
||||
{
|
||||
const auto *omd = reinterpret_cast<const GreasePencilOutlineModifierData *>(md);
|
||||
|
||||
BLO_write_struct(writer, GreasePencilOutlineModifierData, omd);
|
||||
modifier::greasepencil::write_influence_data(writer, &omd->influence);
|
||||
}
|
||||
|
||||
static void blend_read(BlendDataReader *reader, ModifierData *md)
|
||||
{
|
||||
auto *omd = reinterpret_cast<GreasePencilOutlineModifierData *>(md);
|
||||
|
||||
modifier::greasepencil::read_influence_data(reader, &omd->influence);
|
||||
}
|
||||
|
||||
} // namespace blender
|
||||
|
||||
ModifierTypeInfo modifierType_GreasePencilOutline = {
|
||||
/*idname*/ "GreasePencilOutline",
|
||||
/*name*/ N_("Outline"),
|
||||
/*struct_name*/ "GreasePencilOutlineModifierData",
|
||||
/*struct_size*/ sizeof(GreasePencilOutlineModifierData),
|
||||
/*srna*/ &RNA_GreasePencilOutlineModifier,
|
||||
/*type*/ ModifierTypeType::Nonconstructive,
|
||||
/*flags*/ eModifierTypeFlag_AcceptsGreasePencil | eModifierTypeFlag_SupportsEditmode |
|
||||
eModifierTypeFlag_EnableInEditmode | eModifierTypeFlag_SupportsMapping,
|
||||
/*icon*/ ICON_MOD_OUTLINE,
|
||||
|
||||
/*copy_data*/ blender::copy_data,
|
||||
|
||||
/*deform_verts*/ nullptr,
|
||||
/*deform_matrices*/ nullptr,
|
||||
/*deform_verts_EM*/ nullptr,
|
||||
/*deform_matrices_EM*/ nullptr,
|
||||
/*modify_mesh*/ nullptr,
|
||||
/*modify_geometry_set*/ blender::modify_geometry_set,
|
||||
|
||||
/*init_data*/ blender::init_data,
|
||||
/*required_data_mask*/ nullptr,
|
||||
/*free_data*/ blender::free_data,
|
||||
/*is_disabled*/ nullptr,
|
||||
/*update_depsgraph*/ blender::update_depsgraph,
|
||||
/*depends_on_time*/ nullptr,
|
||||
/*depends_on_normals*/ nullptr,
|
||||
/*foreach_ID_link*/ blender::foreach_ID_link,
|
||||
/*foreach_tex_link*/ nullptr,
|
||||
/*free_runtime_data*/ nullptr,
|
||||
/*panel_register*/ blender::panel_register,
|
||||
/*blend_write*/ blender::blend_write,
|
||||
/*blend_read*/ blender::blend_read,
|
||||
};
|
||||
@@ -285,5 +285,6 @@ void modifier_type_init(ModifierTypeInfo *types[])
|
||||
INIT_TYPE(GreasePencilArmature);
|
||||
INIT_TYPE(GreasePencilTime);
|
||||
INIT_TYPE(GreasePencilEnvelope);
|
||||
INIT_TYPE(GreasePencilOutline);
|
||||
#undef INIT_TYPE
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user