4558 lines
157 KiB
C++
4558 lines
157 KiB
C++
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
|
*
|
|
* SPDX-License-Identifier: GPL-2.0-or-later */
|
|
|
|
/** \file
|
|
* \ingroup bke
|
|
*/
|
|
|
|
#include <iostream>
|
|
#include <optional>
|
|
|
|
#include "BKE_action.hh"
|
|
#include "BKE_anim_data.hh"
|
|
#include "BKE_animsys.h"
|
|
#include "BKE_asset_edit.hh"
|
|
#include "BKE_attribute_legacy_convert.hh"
|
|
#include "BKE_attribute_storage.hh"
|
|
#include "BKE_attribute_storage_blend_write.hh"
|
|
#include "BKE_bake_data_block_id.hh"
|
|
#include "BKE_curves.hh"
|
|
#include "BKE_customdata.hh"
|
|
#include "BKE_deform.hh"
|
|
#include "BKE_fcurve.hh"
|
|
#include "BKE_geometry_set.hh"
|
|
#include "BKE_grease_pencil.h"
|
|
#include "BKE_grease_pencil.hh"
|
|
#include "BKE_idtype.hh"
|
|
#include "BKE_lib_id.hh"
|
|
#include "BKE_lib_query.hh"
|
|
#include "BKE_main.hh"
|
|
#include "BKE_material.hh"
|
|
#include "BKE_modifier.hh"
|
|
#include "BKE_object.hh"
|
|
#include "BKE_object_types.hh"
|
|
|
|
#include "BLI_array_utils.hh"
|
|
#include "BLI_bounds.hh"
|
|
#include "BLI_color_types.hh"
|
|
#include "BLI_enumerable_thread_specific.hh"
|
|
#include "BLI_listbase.h"
|
|
#include "BLI_map.hh"
|
|
#include "BLI_math_euler_types.hh"
|
|
#include "BLI_math_geom.h"
|
|
#include "BLI_math_matrix.h"
|
|
#include "BLI_math_matrix.hh"
|
|
#include "BLI_math_matrix_types.hh"
|
|
#include "BLI_math_vector_types.hh"
|
|
#include "BLI_memarena.h"
|
|
#include "BLI_memory_utils.hh"
|
|
#include "BLI_polyfill_2d.h"
|
|
#include "BLI_resource_scope.hh"
|
|
#include "BLI_span.hh"
|
|
#include "BLI_stack.hh"
|
|
#include "BLI_string.h"
|
|
#include "BLI_string_ref.hh"
|
|
#include "BLI_string_utf8.h"
|
|
#include "BLI_string_utils.hh"
|
|
#include "BLI_utildefines.h"
|
|
#include "BLI_vector_set.hh"
|
|
#include "BLI_virtual_array.hh"
|
|
|
|
#include "BLO_read_write.hh"
|
|
|
|
#include "BLT_translation.hh"
|
|
|
|
#include "DNA_ID.h"
|
|
#include "DNA_ID_enums.h"
|
|
#include "DNA_brush_types.h"
|
|
#include "DNA_defaults.h"
|
|
#include "DNA_grease_pencil_types.h"
|
|
#include "DNA_material_types.h"
|
|
#include "DNA_modifier_types.h"
|
|
|
|
#include "DEG_depsgraph.hh"
|
|
#include "DEG_depsgraph_query.hh"
|
|
|
|
#include "RNA_access.hh"
|
|
#include "RNA_path.hh"
|
|
#include "RNA_prototypes.hh"
|
|
|
|
#include "MEM_guardedalloc.h"
|
|
|
|
#include "attribute_storage_access.hh"
|
|
|
|
using blender::float3;
|
|
using blender::int3;
|
|
using blender::Span;
|
|
using blender::VectorSet;
|
|
|
|
static const char *ATTR_POSITION = "position";
|
|
|
|
/* Forward declarations. */
|
|
static void read_drawing_array(GreasePencil &grease_pencil, BlendDataReader *reader);
|
|
static void write_drawing_array(GreasePencil &grease_pencil,
|
|
blender::ResourceScope &scope,
|
|
BlendWriter *writer);
|
|
static void free_drawing_array(GreasePencil &grease_pencil);
|
|
|
|
static void read_layer_tree(GreasePencil &grease_pencil, BlendDataReader *reader);
|
|
static void write_layer_tree(GreasePencil &grease_pencil, BlendWriter *writer);
|
|
|
|
static void grease_pencil_init_data(ID *id)
|
|
{
|
|
using namespace blender::bke;
|
|
|
|
GreasePencil *grease_pencil = reinterpret_cast<GreasePencil *>(id);
|
|
BLI_assert(MEMCMP_STRUCT_AFTER_IS_ZERO(grease_pencil, id));
|
|
|
|
MEMCPY_STRUCT_AFTER(grease_pencil, DNA_struct_default_get(GreasePencil), id);
|
|
|
|
grease_pencil->root_group_ptr = MEM_new<greasepencil::LayerGroup>(__func__);
|
|
grease_pencil->set_active_node(nullptr);
|
|
|
|
CustomData_reset(&grease_pencil->layers_data_legacy);
|
|
new (&grease_pencil->attribute_storage.wrap()) blender::bke::AttributeStorage();
|
|
|
|
grease_pencil->runtime = MEM_new<GreasePencilRuntime>(__func__);
|
|
}
|
|
|
|
/* See if the layer visibility is animated. This is determined whenever a copy is made, so that
|
|
* this happens in the "create evaluation copy" node of the depsgraph. */
|
|
static void grease_pencil_set_runtime_visibilities(ID &id_dst, GreasePencil &grease_pencil)
|
|
{
|
|
using namespace blender::bke;
|
|
|
|
if (!DEG_is_evaluated(&id_dst) || !grease_pencil.adt) {
|
|
return;
|
|
}
|
|
|
|
PropertyRNA *layer_hide_prop = RNA_struct_type_find_property(&RNA_GreasePencilLayer, "hide");
|
|
BLI_assert_msg(layer_hide_prop,
|
|
"RNA struct GreasePencilLayer is expected to have a 'hide' property.");
|
|
PropertyRNA *group_hide_prop = RNA_struct_type_find_property(&RNA_GreasePencilLayerGroup,
|
|
"hide");
|
|
BLI_assert_msg(group_hide_prop,
|
|
"RNA struct GreasePencilLayerGroup is expected to have a 'hide' property.");
|
|
|
|
for (greasepencil::LayerGroup *layer_group : grease_pencil.layer_groups_for_write()) {
|
|
PointerRNA layer_ptr = RNA_pointer_create_discrete(
|
|
&id_dst, &RNA_GreasePencilLayerGroup, layer_group);
|
|
std::optional<std::string> rna_path = RNA_path_from_ID_to_property(&layer_ptr,
|
|
group_hide_prop);
|
|
BLI_assert_msg(
|
|
rna_path,
|
|
"It should be possible to construct the RNA path of a grease pencil layer group.");
|
|
|
|
layer_group->runtime->is_visibility_animated_ = animdata::prop_is_animated(
|
|
grease_pencil.adt, rna_path->c_str(), 0);
|
|
}
|
|
|
|
std::function<bool(greasepencil::LayerGroup &)> parent_group_visibility_animated =
|
|
[&](greasepencil::LayerGroup &parent) {
|
|
if (parent.runtime->is_visibility_animated_) {
|
|
return true;
|
|
}
|
|
greasepencil::LayerGroup *parent_group = parent.as_node().parent_group();
|
|
if (parent_group) {
|
|
return parent_group_visibility_animated(*parent_group);
|
|
}
|
|
return false;
|
|
};
|
|
|
|
for (greasepencil::Layer *layer : grease_pencil.layers_for_write()) {
|
|
if (parent_group_visibility_animated(layer->parent_group())) {
|
|
layer->runtime->is_visibility_animated_ = true;
|
|
continue;
|
|
}
|
|
PointerRNA layer_ptr = RNA_pointer_create_discrete(&id_dst, &RNA_GreasePencilLayer, layer);
|
|
std::optional<std::string> rna_path = RNA_path_from_ID_to_property(&layer_ptr,
|
|
layer_hide_prop);
|
|
BLI_assert_msg(rna_path,
|
|
"It should be possible to construct the RNA path of a grease pencil layer.");
|
|
layer->runtime->is_visibility_animated_ = animdata::prop_is_animated(
|
|
grease_pencil.adt, rna_path.value(), 0);
|
|
}
|
|
}
|
|
|
|
static void grease_pencil_initialize_drawing_user_counts_after_read(GreasePencil &grease_pencil)
|
|
{
|
|
using namespace blender;
|
|
using namespace blender::bke::greasepencil;
|
|
const Array<int> user_counts = grease_pencil.count_frame_users_for_drawings();
|
|
BLI_assert(user_counts.size() == grease_pencil.drawings().size());
|
|
for (const int drawing_i : grease_pencil.drawings().index_range()) {
|
|
GreasePencilDrawingBase *drawing_base = grease_pencil.drawing(drawing_i);
|
|
if (drawing_base->type != GP_DRAWING_REFERENCE) {
|
|
Drawing &drawing = reinterpret_cast<GreasePencilDrawing *>(drawing_base)->wrap();
|
|
drawing.runtime->user_count.store(user_counts[drawing_i]);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void grease_pencil_copy_data(Main * /*bmain*/,
|
|
std::optional<Library *> /*owner_library*/,
|
|
ID *id_dst,
|
|
const ID *id_src,
|
|
const int /*flag*/)
|
|
{
|
|
using namespace blender;
|
|
|
|
GreasePencil *grease_pencil_dst = reinterpret_cast<GreasePencil *>(id_dst);
|
|
const GreasePencil *grease_pencil_src = reinterpret_cast<const GreasePencil *>(id_src);
|
|
|
|
/* Duplicate material array. */
|
|
grease_pencil_dst->material_array = static_cast<Material **>(
|
|
MEM_dupallocN(grease_pencil_src->material_array));
|
|
|
|
BKE_grease_pencil_duplicate_drawing_array(grease_pencil_src, grease_pencil_dst);
|
|
|
|
/* Duplicate layer tree. */
|
|
grease_pencil_dst->root_group_ptr = MEM_new<bke::greasepencil::LayerGroup>(
|
|
__func__, grease_pencil_src->root_group());
|
|
|
|
/* Set active node. */
|
|
if (grease_pencil_src->get_active_node()) {
|
|
bke::greasepencil::TreeNode *active_node = grease_pencil_dst->find_node_by_name(
|
|
grease_pencil_src->get_active_node()->name());
|
|
BLI_assert(active_node);
|
|
grease_pencil_dst->set_active_node(active_node);
|
|
}
|
|
|
|
new (&grease_pencil_dst->attribute_storage.wrap())
|
|
blender::bke::AttributeStorage(grease_pencil_src->attribute_storage.wrap());
|
|
|
|
BKE_defgroup_copy_list(&grease_pencil_dst->vertex_group_names,
|
|
&grease_pencil_src->vertex_group_names);
|
|
|
|
/* Make sure the runtime pointer exists. */
|
|
grease_pencil_dst->runtime = MEM_new<bke::GreasePencilRuntime>(__func__);
|
|
|
|
if (grease_pencil_src->runtime->bake_materials) {
|
|
grease_pencil_dst->runtime->bake_materials = std::make_unique<bke::bake::BakeMaterialsList>(
|
|
*grease_pencil_src->runtime->bake_materials);
|
|
}
|
|
|
|
grease_pencil_set_runtime_visibilities(*id_dst, *grease_pencil_dst);
|
|
}
|
|
|
|
static void grease_pencil_free_data(ID *id)
|
|
{
|
|
GreasePencil *grease_pencil = reinterpret_cast<GreasePencil *>(id);
|
|
BKE_animdata_free(&grease_pencil->id, false);
|
|
|
|
MEM_SAFE_FREE(grease_pencil->material_array);
|
|
|
|
grease_pencil->attribute_storage.wrap().~AttributeStorage();
|
|
|
|
free_drawing_array(*grease_pencil);
|
|
MEM_delete(&grease_pencil->root_group());
|
|
|
|
BLI_freelistN(&grease_pencil->vertex_group_names);
|
|
|
|
BKE_grease_pencil_batch_cache_free(grease_pencil);
|
|
|
|
MEM_delete(grease_pencil->runtime);
|
|
grease_pencil->runtime = nullptr;
|
|
}
|
|
|
|
static void grease_pencil_foreach_id(ID *id, LibraryForeachIDData *data)
|
|
{
|
|
GreasePencil *grease_pencil = reinterpret_cast<GreasePencil *>(id);
|
|
for (int i = 0; i < grease_pencil->material_array_num; i++) {
|
|
BKE_LIB_FOREACHID_PROCESS_IDSUPER(data, grease_pencil->material_array[i], IDWALK_CB_USER);
|
|
}
|
|
for (GreasePencilDrawingBase *drawing_base : grease_pencil->drawings()) {
|
|
if (drawing_base->type == GP_DRAWING_REFERENCE) {
|
|
GreasePencilDrawingReference *drawing_reference =
|
|
reinterpret_cast<GreasePencilDrawingReference *>(drawing_base);
|
|
BKE_LIB_FOREACHID_PROCESS_IDSUPER(data, drawing_reference->id_reference, IDWALK_CB_USER);
|
|
}
|
|
}
|
|
for (const blender::bke::greasepencil::Layer *layer : grease_pencil->layers()) {
|
|
if (layer->parent) {
|
|
BKE_LIB_FOREACHID_PROCESS_IDSUPER(data, layer->parent, IDWALK_CB_USER);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void grease_pencil_foreach_working_space_color(ID *id,
|
|
const IDTypeForeachColorFunctionCallback &fn)
|
|
{
|
|
GreasePencil *grease_pencil = reinterpret_cast<GreasePencil *>(id);
|
|
|
|
fn.single(grease_pencil->onion_skinning_settings.color_after);
|
|
fn.single(grease_pencil->onion_skinning_settings.color_before);
|
|
|
|
for (blender::bke::greasepencil::TreeNode *node : grease_pencil->nodes_for_write()) {
|
|
fn.single(node->color);
|
|
}
|
|
|
|
grease_pencil->attribute_storage.wrap().foreach_working_space_color(fn);
|
|
}
|
|
|
|
static void grease_pencil_blend_write(BlendWriter *writer, ID *id, const void *id_address)
|
|
{
|
|
using namespace blender;
|
|
using namespace blender::bke;
|
|
GreasePencil *grease_pencil = reinterpret_cast<GreasePencil *>(id);
|
|
|
|
blender::ResourceScope scope;
|
|
|
|
blender::Vector<CustomDataLayer, 16> layers_data_layers;
|
|
blender::bke::AttributeStorage::BlendWriteData attribute_data{scope};
|
|
attribute_storage_blend_write_prepare(grease_pencil->attribute_storage.wrap(), attribute_data);
|
|
grease_pencil->attribute_storage.dna_attributes = attribute_data.attributes.data();
|
|
grease_pencil->attribute_storage.dna_attributes_num = attribute_data.attributes.size();
|
|
|
|
CustomData_reset(&grease_pencil->layers_data_legacy);
|
|
|
|
/* Write LibData */
|
|
BLO_write_id_struct(writer, GreasePencil, id_address, &grease_pencil->id);
|
|
BKE_id_blend_write(writer, &grease_pencil->id);
|
|
|
|
grease_pencil->attribute_storage.wrap().blend_write(*writer, attribute_data);
|
|
|
|
/* Write drawings. */
|
|
write_drawing_array(*grease_pencil, scope, writer);
|
|
/* Write layer tree. */
|
|
write_layer_tree(*grease_pencil, writer);
|
|
|
|
/* Write materials. */
|
|
BLO_write_pointer_array(
|
|
writer, grease_pencil->material_array_num, grease_pencil->material_array);
|
|
/* Write vertex group names. */
|
|
BKE_defbase_blend_write(writer, &grease_pencil->vertex_group_names);
|
|
}
|
|
|
|
static void grease_pencil_blend_read_data(BlendDataReader *reader, ID *id)
|
|
{
|
|
using namespace blender::bke::greasepencil;
|
|
GreasePencil *grease_pencil = reinterpret_cast<GreasePencil *>(id);
|
|
|
|
/* Read drawings. */
|
|
read_drawing_array(*grease_pencil, reader);
|
|
/* Read layer tree. */
|
|
read_layer_tree(*grease_pencil, reader);
|
|
/* Initialize drawing user counts */
|
|
grease_pencil_initialize_drawing_user_counts_after_read(*grease_pencil);
|
|
|
|
CustomData_blend_read(
|
|
reader, &grease_pencil->layers_data_legacy, grease_pencil->layers().size());
|
|
grease_pencil->attribute_storage.wrap().blend_read(*reader);
|
|
|
|
/* Read materials. */
|
|
BLO_read_pointer_array(reader,
|
|
grease_pencil->material_array_num,
|
|
reinterpret_cast<void **>(&grease_pencil->material_array));
|
|
/* Read vertex group names. */
|
|
BLO_read_struct_list(reader, bDeformGroup, &grease_pencil->vertex_group_names);
|
|
|
|
grease_pencil->runtime = MEM_new<blender::bke::GreasePencilRuntime>(__func__);
|
|
}
|
|
|
|
IDTypeInfo IDType_ID_GP = {
|
|
/*id_code*/ GreasePencil::id_type,
|
|
/*id_filter*/ FILTER_ID_GP,
|
|
/*dependencies_id_types*/ FILTER_ID_GP | FILTER_ID_MA | FILTER_ID_OB,
|
|
/*main_listbase_index*/ INDEX_ID_GP,
|
|
/*struct_size*/ sizeof(GreasePencil),
|
|
/*name*/ "GreasePencil",
|
|
/*name_plural*/ N_("grease_pencils"),
|
|
/*translation_context*/ BLT_I18NCONTEXT_ID_GPENCIL,
|
|
/*flags*/ IDTYPE_FLAGS_APPEND_IS_REUSABLE,
|
|
/*asset_type_info*/ nullptr,
|
|
|
|
/*init_data*/ grease_pencil_init_data,
|
|
/*copy_data*/ grease_pencil_copy_data,
|
|
/*free_data*/ grease_pencil_free_data,
|
|
/*make_local*/ nullptr,
|
|
/*foreach_id*/ grease_pencil_foreach_id,
|
|
/*foreach_cache*/ nullptr,
|
|
/*foreach_path*/ nullptr,
|
|
/*foreach_working_space_color*/ grease_pencil_foreach_working_space_color,
|
|
/*owner_pointer_get*/ nullptr,
|
|
|
|
/*blend_write*/ grease_pencil_blend_write,
|
|
/*blend_read_data*/ grease_pencil_blend_read_data,
|
|
/*blend_read_after_liblink*/ nullptr,
|
|
|
|
/*blend_read_undo_preserve*/ nullptr,
|
|
|
|
/*lib_override_apply_post*/ nullptr,
|
|
};
|
|
|
|
namespace blender::bke::greasepencil {
|
|
constexpr StringRef ATTR_RADIUS = "radius";
|
|
constexpr StringRef ATTR_OPACITY = "opacity";
|
|
constexpr StringRef ATTR_VERTEX_COLOR = "vertex_color";
|
|
constexpr StringRef ATTR_FILL_COLOR = "fill_color";
|
|
|
|
Drawing::Drawing()
|
|
{
|
|
this->base.type = GP_DRAWING;
|
|
this->base.flag = 0;
|
|
|
|
new (&this->geometry) bke::CurvesGeometry();
|
|
/* Initialize runtime data. */
|
|
this->runtime = MEM_new<bke::greasepencil::DrawingRuntime>(__func__);
|
|
}
|
|
|
|
Drawing::Drawing(const Drawing &other)
|
|
{
|
|
this->base.type = GP_DRAWING;
|
|
this->base.flag = other.base.flag;
|
|
|
|
new (&this->geometry) bke::CurvesGeometry(other.strokes());
|
|
/* Initialize runtime data. */
|
|
this->runtime = MEM_new<bke::greasepencil::DrawingRuntime>(__func__);
|
|
|
|
this->runtime->triangle_offsets_cache = other.runtime->triangle_offsets_cache;
|
|
this->runtime->triangles_cache = other.runtime->triangles_cache;
|
|
this->runtime->curve_plane_normals_cache = other.runtime->curve_plane_normals_cache;
|
|
this->runtime->curve_texture_matrices = other.runtime->curve_texture_matrices;
|
|
}
|
|
|
|
Drawing::Drawing(Drawing &&other)
|
|
{
|
|
this->base.type = GP_DRAWING;
|
|
other.base.type = GP_DRAWING;
|
|
this->base.flag = other.base.flag;
|
|
other.base.flag = 0;
|
|
|
|
new (&this->geometry) bke::CurvesGeometry(std::move(other.geometry.wrap()));
|
|
|
|
this->runtime = other.runtime;
|
|
other.runtime = nullptr;
|
|
}
|
|
|
|
Drawing &Drawing::operator=(const Drawing &other)
|
|
{
|
|
if (this == &other) {
|
|
return *this;
|
|
}
|
|
std::destroy_at(this);
|
|
new (this) Drawing(other);
|
|
return *this;
|
|
}
|
|
|
|
Drawing &Drawing::operator=(Drawing &&other)
|
|
{
|
|
if (this == &other) {
|
|
return *this;
|
|
}
|
|
std::destroy_at(this);
|
|
new (this) Drawing(std::move(other));
|
|
return *this;
|
|
}
|
|
|
|
Drawing::~Drawing()
|
|
{
|
|
this->strokes().~CurvesGeometry();
|
|
MEM_delete(this->runtime);
|
|
this->runtime = nullptr;
|
|
}
|
|
|
|
OffsetIndices<int> Drawing::triangle_offsets() const
|
|
{
|
|
this->runtime->triangle_offsets_cache.ensure([&](Vector<int> &r_offsets) {
|
|
const CurvesGeometry &curves = this->strokes();
|
|
const OffsetIndices<int> points_by_curve = curves.evaluated_points_by_curve();
|
|
|
|
int offset = 0;
|
|
r_offsets.reinitialize(curves.curves_num() + 1);
|
|
for (const int curve_i : points_by_curve.index_range()) {
|
|
const IndexRange points = points_by_curve[curve_i];
|
|
r_offsets[curve_i] = offset;
|
|
offset += std::max(int(points.size() - 2), 0);
|
|
}
|
|
r_offsets.last() = offset;
|
|
});
|
|
return this->runtime->triangle_offsets_cache.data().as_span();
|
|
}
|
|
|
|
static void update_triangle_cache(const Span<float3> positions,
|
|
const Span<float3> normals,
|
|
const OffsetIndices<int> points_by_curve,
|
|
const OffsetIndices<int> triangle_offsets,
|
|
const IndexMask &curve_mask,
|
|
MutableSpan<int3> triangles)
|
|
{
|
|
struct LocalMemArena {
|
|
MemArena *pf_arena = nullptr;
|
|
LocalMemArena() : pf_arena(BLI_memarena_new(BLI_MEMARENA_STD_BUFSIZE, "Drawing::triangles")) {}
|
|
|
|
~LocalMemArena()
|
|
{
|
|
if (pf_arena != nullptr) {
|
|
BLI_memarena_free(pf_arena);
|
|
}
|
|
}
|
|
};
|
|
threading::EnumerableThreadSpecific<LocalMemArena> all_local_mem_arenas;
|
|
curve_mask.foreach_segment(GrainSize(32), [&](const IndexMaskSegment mask_segment) {
|
|
MemArena *pf_arena = all_local_mem_arenas.local().pf_arena;
|
|
for (const int curve_i : mask_segment) {
|
|
const IndexRange points = points_by_curve[curve_i];
|
|
if (points.size() < 3) {
|
|
continue;
|
|
}
|
|
MutableSpan<int3> r_tris = triangles.slice(triangle_offsets[curve_i]);
|
|
|
|
float (*projverts)[2] = static_cast<float (*)[2]>(
|
|
BLI_memarena_alloc(pf_arena, sizeof(*projverts) * size_t(points.size())));
|
|
|
|
float3x3 axis_mat;
|
|
axis_dominant_v3_to_m3(axis_mat.ptr(), normals[curve_i]);
|
|
|
|
for (const int i : IndexRange(points.size())) {
|
|
mul_v2_m3v3(projverts[i], axis_mat.ptr(), positions[points[i]]);
|
|
}
|
|
|
|
BLI_polyfill_calc_arena(
|
|
projverts, points.size(), 0, reinterpret_cast<uint32_t (*)[3]>(r_tris.data()), pf_arena);
|
|
BLI_memarena_clear(pf_arena);
|
|
}
|
|
});
|
|
}
|
|
|
|
Span<int3> Drawing::triangles() const
|
|
{
|
|
const CurvesGeometry &curves = this->strokes();
|
|
const OffsetIndices<int> triangle_offsets = this->triangle_offsets();
|
|
this->runtime->triangles_cache.ensure([&](Vector<int3> &r_data) {
|
|
const int total_triangles = triangle_offsets.total_size();
|
|
r_data.resize(total_triangles);
|
|
|
|
update_triangle_cache(curves.evaluated_positions(),
|
|
this->curve_plane_normals(),
|
|
curves.evaluated_points_by_curve(),
|
|
triangle_offsets,
|
|
curves.curves_range(),
|
|
r_data.as_mutable_span());
|
|
});
|
|
|
|
return this->runtime->triangles_cache.data().as_span();
|
|
}
|
|
|
|
static void update_curve_plane_normal_cache(const Span<float3> positions,
|
|
const OffsetIndices<int> points_by_curve,
|
|
const IndexMask &curve_mask,
|
|
MutableSpan<float3> normals)
|
|
{
|
|
curve_mask.foreach_index(GrainSize(512), [&](const int curve_i) {
|
|
const IndexRange points = points_by_curve[curve_i];
|
|
if (points.size() < 2) {
|
|
normals[curve_i] = float3(1.0f, 0.0f, 0.0f);
|
|
return;
|
|
}
|
|
|
|
/* Calculate normal using Newell's method. */
|
|
float3 normal(0.0f);
|
|
float3 prev_point = positions[points.last()];
|
|
for (const int point_i : points) {
|
|
const float3 curr_point = positions[point_i];
|
|
add_newell_cross_v3_v3v3(normal, prev_point, curr_point);
|
|
prev_point = curr_point;
|
|
}
|
|
|
|
float length;
|
|
normal = math::normalize_and_get_length(normal, length);
|
|
/* Check for degenerate case where the points are on a line. */
|
|
if (math::is_zero(length)) {
|
|
for (const int point_i : points.drop_back(1)) {
|
|
float3 segment_vec = positions[point_i] - positions[point_i + 1];
|
|
if (math::length_squared(segment_vec) != 0.0f) {
|
|
normal = math::normalize(float3(segment_vec.y, -segment_vec.x, 0.0f));
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
normals[curve_i] = normal;
|
|
});
|
|
}
|
|
|
|
Span<float3> Drawing::curve_plane_normals() const
|
|
{
|
|
this->runtime->curve_plane_normals_cache.ensure([&](Vector<float3> &r_data) {
|
|
const CurvesGeometry &curves = this->strokes();
|
|
r_data.reinitialize(curves.curves_num());
|
|
update_curve_plane_normal_cache(curves.positions(),
|
|
curves.points_by_curve(),
|
|
curves.curves_range(),
|
|
r_data.as_mutable_span());
|
|
});
|
|
return this->runtime->curve_plane_normals_cache.data().as_span();
|
|
}
|
|
|
|
/*
|
|
* Returns the matrix that transforms from a 3D point in layer-space to a 2D point in
|
|
* stroke-space for the stroke `curve_i`
|
|
*/
|
|
static float4x2 get_local_to_stroke_matrix(const Span<float3> positions, const float3 normal)
|
|
{
|
|
using namespace blender::math;
|
|
|
|
if (positions.size() <= 2) {
|
|
return float4x2::identity();
|
|
}
|
|
|
|
const float3 point_0 = positions[0];
|
|
const float3 point_1 = positions[1];
|
|
|
|
/* Local X axis (p0 -> p1) */
|
|
const float3 local_x = normalize(point_1 - point_0);
|
|
/* Local Y axis (cross to normal/x axis). */
|
|
const float3 local_y = normalize(cross(normal, local_x));
|
|
|
|
if (length_squared(local_x) == 0.0f || length_squared(local_y) == 0.0f) {
|
|
return float4x2::identity();
|
|
}
|
|
|
|
/* Get local space using first point as origin. */
|
|
const float4x2 mat = transpose(
|
|
float2x4(float4(local_x, -dot(point_0, local_x)), float4(local_y, -dot(point_0, local_y))));
|
|
|
|
return mat;
|
|
}
|
|
|
|
/*
|
|
* Returns the matrix that transforms from a 2D point in stroke-space to a 2D point in
|
|
* texture-space for a stroke `curve_i`
|
|
*/
|
|
static float3x2 get_stroke_to_texture_matrix(const float uv_rotation,
|
|
const float2 uv_translation,
|
|
const float2 uv_scale)
|
|
{
|
|
using namespace blender::math;
|
|
|
|
const float2 uv_scale_inv = safe_rcp(uv_scale);
|
|
const float s = sin(uv_rotation);
|
|
const float c = cos(uv_rotation);
|
|
const float2x2 rot = float2x2(float2(c, s), float2(-s, c));
|
|
|
|
float3x2 texture_matrix = float3x2::identity();
|
|
/*
|
|
* The order in which the three transforms are applied has been carefully chosen to be easy to
|
|
* invert.
|
|
*
|
|
* The translation is applied last so that the origin goes to `uv_translation`
|
|
* The rotation is applied after the scale so that the `u` direction's angle is `uv_rotation`
|
|
* Scale is the only transform that changes the length of the basis vectors and if it is applied
|
|
* first it's independent of the other transforms.
|
|
*
|
|
* These properties are not true with a different order.
|
|
*/
|
|
|
|
/* Apply scale. */
|
|
texture_matrix = from_scale<float2x2>(uv_scale_inv) * texture_matrix;
|
|
|
|
/* Apply rotation. */
|
|
texture_matrix = rot * texture_matrix;
|
|
|
|
/* Apply translation. */
|
|
texture_matrix[2] += uv_translation;
|
|
|
|
return texture_matrix;
|
|
}
|
|
|
|
static float4x3 expand_4x2_mat(const float4x2 &strokemat)
|
|
{
|
|
float4x3 strokemat4x3 = float4x3(strokemat);
|
|
|
|
/*
|
|
* We need the diagonal of ones to start from the bottom right instead top left to properly
|
|
* apply the two matrices.
|
|
*
|
|
* i.e.
|
|
* # # # # # # # #
|
|
* We need # # # # Instead of # # # #
|
|
* 0 0 0 1 0 0 1 0
|
|
*
|
|
*/
|
|
strokemat4x3[2][2] = 0.0f;
|
|
strokemat4x3[3][2] = 1.0f;
|
|
|
|
return strokemat4x3;
|
|
}
|
|
|
|
Span<float4x2> Drawing::texture_matrices() const
|
|
{
|
|
this->runtime->curve_texture_matrices.ensure([&](Vector<float4x2> &r_data) {
|
|
const CurvesGeometry &curves = this->strokes();
|
|
const AttributeAccessor attributes = curves.attributes();
|
|
|
|
const VArray<float> uv_rotations = *attributes.lookup_or_default<float>(
|
|
"uv_rotation", AttrDomain::Curve, 0.0f);
|
|
const VArray<float2> uv_translations = *attributes.lookup_or_default<float2>(
|
|
"uv_translation", AttrDomain::Curve, float2(0.0f, 0.0f));
|
|
const VArray<float2> uv_scales = *attributes.lookup_or_default<float2>(
|
|
"uv_scale", AttrDomain::Curve, float2(1.0f, 1.0f));
|
|
|
|
const OffsetIndices<int> points_by_curve = curves.points_by_curve();
|
|
const Span<float3> positions = curves.positions();
|
|
const Span<float3> normals = this->curve_plane_normals();
|
|
|
|
r_data.reinitialize(curves.curves_num());
|
|
threading::parallel_for(curves.curves_range(), 512, [&](const IndexRange range) {
|
|
for (const int curve_i : range) {
|
|
const IndexRange points = points_by_curve[curve_i];
|
|
const float3 normal = normals[curve_i];
|
|
const float4x2 strokemat = get_local_to_stroke_matrix(positions.slice(points), normal);
|
|
const float3x2 texture_matrix = get_stroke_to_texture_matrix(
|
|
uv_rotations[curve_i], uv_translations[curve_i], uv_scales[curve_i]);
|
|
|
|
const float4x2 texspace = texture_matrix * expand_4x2_mat(strokemat);
|
|
|
|
r_data[curve_i] = texspace;
|
|
}
|
|
});
|
|
});
|
|
return this->runtime->curve_texture_matrices.data().as_span();
|
|
}
|
|
|
|
void Drawing::set_texture_matrices(Span<float4x2> matrices, const IndexMask &selection)
|
|
{
|
|
CurvesGeometry &curves = this->strokes_for_write();
|
|
MutableAttributeAccessor attributes = curves.attributes_for_write();
|
|
SpanAttributeWriter<float> uv_rotations = attributes.lookup_or_add_for_write_span<float>(
|
|
"uv_rotation", AttrDomain::Curve);
|
|
SpanAttributeWriter<float2> uv_translations = attributes.lookup_or_add_for_write_span<float2>(
|
|
"uv_translation", AttrDomain::Curve);
|
|
SpanAttributeWriter<float2> uv_scales = attributes.lookup_or_add_for_write_span<float2>(
|
|
"uv_scale",
|
|
AttrDomain::Curve,
|
|
AttributeInitVArray(VArray<float2>::from_single(float2(1.0f, 1.0f), curves.curves_num())));
|
|
|
|
if (!uv_rotations || !uv_translations || !uv_scales) {
|
|
/* FIXME: It might be better to ensure the attributes exist and are on the right domain. */
|
|
return;
|
|
}
|
|
|
|
const OffsetIndices<int> points_by_curve = curves.points_by_curve();
|
|
const Span<float3> positions = curves.positions();
|
|
const Span<float3> normals = this->curve_plane_normals();
|
|
|
|
selection.foreach_index(GrainSize(256), [&](const int64_t curve_i, const int64_t pos) {
|
|
const IndexRange points = points_by_curve[curve_i];
|
|
const float3 normal = normals[curve_i];
|
|
const float4x2 strokemat = get_local_to_stroke_matrix(positions.slice(points), normal);
|
|
const float4x2 texspace = matrices[pos];
|
|
|
|
/* We do the computation using doubles to avoid numerical precision errors. */
|
|
const double4x3 strokemat4x3 = double4x3(expand_4x2_mat(strokemat));
|
|
|
|
/*
|
|
* We want to solve for `texture_matrix` in the equation:
|
|
* `texspace = texture_matrix * strokemat4x3`
|
|
* Because these matrices are not square we can not use a standard inverse.
|
|
*
|
|
* Our problem has the form of: `X = A * Y`
|
|
* We can solve for `A` using: `A = X * B`
|
|
*
|
|
* Where `B` is the Right-sided inverse or Moore-Penrose pseudo inverse.
|
|
* Calculated as:
|
|
*
|
|
* |--------------------------|
|
|
* | B = T(Y) * (Y * T(Y))^-1 |
|
|
* |--------------------------|
|
|
*
|
|
* And `T()` is transpose and `()^-1` is the inverse.
|
|
*/
|
|
|
|
const double3x4 transpose_strokemat = math::transpose(strokemat4x3);
|
|
const double3x4 right_inverse = transpose_strokemat *
|
|
math::invert(strokemat4x3 * transpose_strokemat);
|
|
|
|
const float3x2 texture_matrix = float3x2(double4x2(texspace) * right_inverse);
|
|
|
|
/* Solve for translation, the translation is simply the origin. */
|
|
const float2 uv_translation = texture_matrix[2];
|
|
|
|
/* Solve rotation, the angle of the `u` basis is the rotation. */
|
|
const float uv_rotation = math::atan2(texture_matrix[0][1], texture_matrix[0][0]);
|
|
|
|
/* Calculate the determinant to check if the `v` scale is negative. */
|
|
const float det = math::determinant(float2x2(texture_matrix));
|
|
|
|
/* Solve scale, scaling is the only transformation that changes the length, so scale factor
|
|
* is simply the length. And flip the sign of `v` if the determinant is negative. */
|
|
const float2 uv_scale = math::safe_rcp(float2(
|
|
math::length(texture_matrix[0]), math::sign(det) * math::length(texture_matrix[1])));
|
|
|
|
uv_rotations.span[curve_i] = uv_rotation;
|
|
uv_translations.span[curve_i] = uv_translation;
|
|
uv_scales.span[curve_i] = uv_scale;
|
|
});
|
|
uv_rotations.finish();
|
|
uv_translations.finish();
|
|
uv_scales.finish();
|
|
|
|
this->tag_texture_matrices_changed();
|
|
}
|
|
|
|
const bke::CurvesGeometry &Drawing::strokes() const
|
|
{
|
|
return this->geometry.wrap();
|
|
}
|
|
|
|
bke::CurvesGeometry &Drawing::strokes_for_write()
|
|
{
|
|
return this->geometry.wrap();
|
|
}
|
|
|
|
VArray<float> Drawing::radii() const
|
|
{
|
|
return *this->strokes().attributes().lookup_or_default<float>(
|
|
ATTR_RADIUS, AttrDomain::Point, 0.01f);
|
|
}
|
|
|
|
MutableSpan<float> Drawing::radii_for_write()
|
|
{
|
|
return blender::bke::get_mutable_attribute<float>(
|
|
this->strokes_for_write().attribute_storage.wrap(),
|
|
AttrDomain::Point,
|
|
ATTR_RADIUS,
|
|
this->strokes().points_num(),
|
|
0.01f);
|
|
}
|
|
|
|
VArray<float> Drawing::opacities() const
|
|
{
|
|
return *this->strokes().attributes().lookup_or_default<float>(
|
|
ATTR_OPACITY, AttrDomain::Point, 1.0f);
|
|
}
|
|
|
|
MutableSpan<float> Drawing::opacities_for_write()
|
|
{
|
|
return blender::bke::get_mutable_attribute<float>(
|
|
this->strokes_for_write().attribute_storage.wrap(),
|
|
AttrDomain::Point,
|
|
ATTR_OPACITY,
|
|
this->strokes().points_num(),
|
|
1.0f);
|
|
}
|
|
|
|
VArray<ColorGeometry4f> Drawing::vertex_colors() const
|
|
{
|
|
return *this->strokes().attributes().lookup_or_default<ColorGeometry4f>(
|
|
ATTR_VERTEX_COLOR, AttrDomain::Point, ColorGeometry4f(0.0f, 0.0f, 0.0f, 0.0f));
|
|
}
|
|
|
|
MutableSpan<ColorGeometry4f> Drawing::vertex_colors_for_write()
|
|
{
|
|
return blender::bke::get_mutable_attribute<ColorGeometry4f>(
|
|
this->strokes_for_write().attribute_storage.wrap(),
|
|
AttrDomain::Point,
|
|
ATTR_VERTEX_COLOR,
|
|
this->strokes().points_num(),
|
|
ColorGeometry4f(0.0f, 0.0f, 0.0f, 0.0f));
|
|
}
|
|
|
|
VArray<ColorGeometry4f> Drawing::fill_colors() const
|
|
{
|
|
return *this->strokes().attributes().lookup_or_default<ColorGeometry4f>(
|
|
ATTR_FILL_COLOR, AttrDomain::Curve, ColorGeometry4f(0.0f, 0.0f, 0.0f, 0.0f));
|
|
}
|
|
|
|
MutableSpan<ColorGeometry4f> Drawing::fill_colors_for_write()
|
|
{
|
|
return blender::bke::get_mutable_attribute<ColorGeometry4f>(
|
|
this->strokes_for_write().attribute_storage.wrap(),
|
|
AttrDomain::Curve,
|
|
ATTR_FILL_COLOR,
|
|
this->strokes().curves_num(),
|
|
ColorGeometry4f(0.0f, 0.0f, 0.0f, 0.0f));
|
|
}
|
|
|
|
void Drawing::tag_texture_matrices_changed()
|
|
{
|
|
this->runtime->curve_texture_matrices.tag_dirty();
|
|
}
|
|
|
|
void Drawing::tag_positions_changed()
|
|
{
|
|
this->strokes_for_write().tag_positions_changed();
|
|
this->runtime->curve_plane_normals_cache.tag_dirty();
|
|
this->runtime->triangles_cache.tag_dirty();
|
|
this->tag_texture_matrices_changed();
|
|
}
|
|
|
|
void Drawing::tag_positions_changed(const IndexMask &changed_curves)
|
|
{
|
|
if (changed_curves.is_empty()) {
|
|
return;
|
|
}
|
|
/* If more than half of the curves have changed, update the entire cache instead.
|
|
* The assumption here is that it's better to lazily compute the caches if more than half of the
|
|
* curves need to be updated.
|
|
* TODO: This could probably be a bit more rigorous once this function gets used in more places.
|
|
*/
|
|
if (changed_curves.size() > this->strokes().curves_num() / 2) {
|
|
this->tag_positions_changed();
|
|
return;
|
|
}
|
|
if (!this->runtime->triangles_cache.is_cached() ||
|
|
!this->runtime->curve_plane_normals_cache.is_cached())
|
|
{
|
|
this->tag_positions_changed();
|
|
return;
|
|
}
|
|
/* Positions needs to be tagged first, because the triangle cache updates just after need the
|
|
* positions to be up-to-date. */
|
|
this->strokes_for_write().tag_positions_changed();
|
|
this->runtime->curve_plane_normals_cache.update([&](Vector<float3> &normals) {
|
|
const CurvesGeometry &curves = this->strokes();
|
|
update_curve_plane_normal_cache(
|
|
curves.positions(), curves.points_by_curve(), changed_curves, normals);
|
|
});
|
|
this->runtime->triangles_cache.update([&](Vector<int3> &triangles) {
|
|
const CurvesGeometry &curves = this->strokes();
|
|
update_triangle_cache(curves.evaluated_positions(),
|
|
this->curve_plane_normals(),
|
|
curves.evaluated_points_by_curve(),
|
|
this->triangle_offsets(),
|
|
curves.curves_range(),
|
|
triangles);
|
|
});
|
|
this->tag_texture_matrices_changed();
|
|
}
|
|
|
|
void Drawing::tag_topology_changed()
|
|
{
|
|
this->runtime->triangle_offsets_cache.tag_dirty();
|
|
this->tag_positions_changed();
|
|
this->strokes_for_write().tag_topology_changed();
|
|
}
|
|
|
|
void Drawing::tag_topology_changed(const IndexMask &changed_curves)
|
|
{
|
|
if (changed_curves.is_empty()) {
|
|
return;
|
|
}
|
|
/* If more than half of the curves have changed, update the entire cache instead.
|
|
* The assumption here is that it's better to lazily compute the caches if more than half of the
|
|
* curves need to be updated.
|
|
* TODO: This could probably be a bit more rigorous once this function gets used in more places.
|
|
*/
|
|
if (changed_curves.size() > this->strokes().curves_num() / 2) {
|
|
this->tag_topology_changed();
|
|
return;
|
|
}
|
|
if (!this->runtime->triangles_cache.is_cached() ||
|
|
!this->runtime->curve_plane_normals_cache.is_cached())
|
|
{
|
|
this->tag_topology_changed();
|
|
return;
|
|
}
|
|
/* Positions needs to be tagged first, because the triangle cache updates just after need the
|
|
* positions to be up-to-date. */
|
|
this->strokes_for_write().tag_positions_changed();
|
|
this->runtime->curve_plane_normals_cache.update([&](Vector<float3> &normals) {
|
|
const CurvesGeometry &curves = this->strokes();
|
|
update_curve_plane_normal_cache(
|
|
curves.positions(), curves.points_by_curve(), changed_curves, normals);
|
|
});
|
|
/* Copy the current triangle offsets. These are used to copy over the triangle data for curves
|
|
* that don't need to be updated. */
|
|
const Array<int> src_triangle_offset_data(this->triangle_offsets().data());
|
|
const OffsetIndices<int> src_triangle_offsets = src_triangle_offset_data.as_span();
|
|
/* Tag the `triangle_offsets_cache` so that the `triangles_cache` update can use the up-to-date
|
|
* triangle offsets. */
|
|
this->runtime->triangle_offsets_cache.tag_dirty();
|
|
|
|
this->runtime->triangles_cache.update([&](Vector<int3> &triangles) {
|
|
const CurvesGeometry &curves = this->strokes();
|
|
const OffsetIndices<int> dst_triangle_offsets = this->triangle_offsets();
|
|
|
|
IndexMaskMemory memory;
|
|
const IndexMask curves_to_copy = changed_curves.complement(curves.curves_range(), memory);
|
|
|
|
const Vector<int3> src_triangles(triangles);
|
|
triangles.reinitialize(dst_triangle_offsets.total_size());
|
|
array_utils::copy_group_to_group(src_triangle_offsets,
|
|
dst_triangle_offsets,
|
|
curves_to_copy,
|
|
src_triangles.as_span(),
|
|
triangles.as_mutable_span());
|
|
|
|
update_triangle_cache(curves.evaluated_positions(),
|
|
this->curve_plane_normals(),
|
|
curves.evaluated_points_by_curve(),
|
|
dst_triangle_offsets,
|
|
changed_curves,
|
|
triangles);
|
|
});
|
|
this->tag_texture_matrices_changed();
|
|
}
|
|
|
|
DrawingReference::DrawingReference()
|
|
{
|
|
this->base.type = GP_DRAWING_REFERENCE;
|
|
this->base.flag = 0;
|
|
|
|
this->id_reference = nullptr;
|
|
}
|
|
|
|
DrawingReference::DrawingReference(const DrawingReference &other)
|
|
{
|
|
this->base.type = GP_DRAWING_REFERENCE;
|
|
this->base.flag = other.base.flag;
|
|
|
|
this->id_reference = other.id_reference;
|
|
}
|
|
|
|
DrawingReference::~DrawingReference() = default;
|
|
|
|
void copy_drawing_array(Span<const GreasePencilDrawingBase *> src_drawings,
|
|
MutableSpan<GreasePencilDrawingBase *> dst_drawings)
|
|
{
|
|
BLI_assert(src_drawings.size() == dst_drawings.size());
|
|
for (const int i : src_drawings.index_range()) {
|
|
const GreasePencilDrawingBase *src_drawing_base = src_drawings[i];
|
|
switch (src_drawing_base->type) {
|
|
case GP_DRAWING: {
|
|
const GreasePencilDrawing *src_drawing = reinterpret_cast<const GreasePencilDrawing *>(
|
|
src_drawing_base);
|
|
dst_drawings[i] = reinterpret_cast<GreasePencilDrawingBase *>(
|
|
MEM_new<bke::greasepencil::Drawing>(__func__, src_drawing->wrap()));
|
|
break;
|
|
}
|
|
case GP_DRAWING_REFERENCE: {
|
|
const GreasePencilDrawingReference *src_drawing_reference =
|
|
reinterpret_cast<const GreasePencilDrawingReference *>(src_drawing_base);
|
|
dst_drawings[i] = reinterpret_cast<GreasePencilDrawingBase *>(
|
|
MEM_new<bke::greasepencil::DrawingReference>(__func__, src_drawing_reference->wrap()));
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
TreeNode::TreeNode()
|
|
{
|
|
this->next = this->prev = nullptr;
|
|
this->parent = nullptr;
|
|
|
|
this->GreasePencilLayerTreeNode::name = nullptr;
|
|
this->flag = 0;
|
|
this->color[0] = this->color[1] = this->color[2] = 0;
|
|
}
|
|
|
|
TreeNode::TreeNode(const GreasePencilLayerTreeNodeType type) : TreeNode()
|
|
{
|
|
this->type = type;
|
|
}
|
|
|
|
TreeNode::TreeNode(const GreasePencilLayerTreeNodeType type, const StringRef name) : TreeNode()
|
|
{
|
|
this->type = type;
|
|
this->GreasePencilLayerTreeNode::name = BLI_strdupn(name.data(), name.size());
|
|
}
|
|
|
|
TreeNode::TreeNode(const TreeNode &other) : TreeNode(GreasePencilLayerTreeNodeType(other.type))
|
|
{
|
|
this->GreasePencilLayerTreeNode::name = BLI_strdup_null(other.GreasePencilLayerTreeNode::name);
|
|
this->flag = other.flag;
|
|
copy_v3_v3(this->color, other.color);
|
|
}
|
|
|
|
TreeNode::~TreeNode()
|
|
{
|
|
MEM_SAFE_FREE(this->GreasePencilLayerTreeNode::name);
|
|
}
|
|
|
|
void TreeNode::set_name(const StringRef name)
|
|
{
|
|
MEM_SAFE_FREE(this->GreasePencilLayerTreeNode::name);
|
|
this->GreasePencilLayerTreeNode::name = BLI_strdupn(name.data(), name.size());
|
|
}
|
|
|
|
const LayerGroup &TreeNode::as_group() const
|
|
{
|
|
return *reinterpret_cast<const LayerGroup *>(this);
|
|
}
|
|
|
|
const Layer &TreeNode::as_layer() const
|
|
{
|
|
return *reinterpret_cast<const Layer *>(this);
|
|
}
|
|
|
|
LayerGroup &TreeNode::as_group()
|
|
{
|
|
return *reinterpret_cast<LayerGroup *>(this);
|
|
}
|
|
|
|
Layer &TreeNode::as_layer()
|
|
{
|
|
return *reinterpret_cast<Layer *>(this);
|
|
}
|
|
|
|
const LayerGroup *TreeNode::parent_group() const
|
|
{
|
|
return (this->parent) ? &this->parent->wrap() : nullptr;
|
|
}
|
|
LayerGroup *TreeNode::parent_group()
|
|
{
|
|
return (this->parent) ? &this->parent->wrap() : nullptr;
|
|
}
|
|
const TreeNode *TreeNode::parent_node() const
|
|
{
|
|
return this->parent_group() ? &this->parent->wrap().as_node() : nullptr;
|
|
}
|
|
TreeNode *TreeNode::parent_node()
|
|
{
|
|
return this->parent_group() ? &this->parent->wrap().as_node() : nullptr;
|
|
}
|
|
|
|
int64_t TreeNode::depth() const
|
|
{
|
|
const LayerGroup *parent = this->parent_group();
|
|
if (parent == nullptr) {
|
|
/* The root group has a depth of 0. */
|
|
return 0;
|
|
}
|
|
return 1 + parent->as_node().depth();
|
|
}
|
|
|
|
LayerMask::LayerMask()
|
|
{
|
|
this->layer_name = nullptr;
|
|
this->flag = 0;
|
|
}
|
|
|
|
LayerMask::LayerMask(const StringRef name) : LayerMask()
|
|
{
|
|
this->layer_name = BLI_strdupn(name.data(), name.size());
|
|
}
|
|
|
|
LayerMask::LayerMask(const LayerMask &other) : LayerMask()
|
|
{
|
|
this->layer_name = BLI_strdup_null(other.layer_name);
|
|
this->flag = other.flag;
|
|
}
|
|
|
|
LayerMask::~LayerMask()
|
|
{
|
|
if (this->layer_name) {
|
|
MEM_freeN(this->layer_name);
|
|
}
|
|
}
|
|
|
|
void LayerRuntime::clear()
|
|
{
|
|
frames_.clear();
|
|
sorted_keys_cache_.tag_dirty();
|
|
masks_.clear_and_shrink();
|
|
trans_data_ = {};
|
|
}
|
|
|
|
Layer::Layer()
|
|
{
|
|
new (&this->base) TreeNode(GP_LAYER_TREE_LEAF);
|
|
|
|
this->frames_storage.num = 0;
|
|
this->frames_storage.keys = nullptr;
|
|
this->frames_storage.values = nullptr;
|
|
this->frames_storage.flag = 0;
|
|
|
|
this->blend_mode = GP_LAYER_BLEND_NONE;
|
|
this->opacity = 1.0f;
|
|
|
|
this->parent = nullptr;
|
|
this->parsubstr = nullptr;
|
|
unit_m4(this->parentinv);
|
|
|
|
zero_v3(this->translation);
|
|
zero_v3(this->rotation);
|
|
copy_v3_fl(this->scale, 1.0f);
|
|
|
|
this->viewlayername = nullptr;
|
|
|
|
BLI_listbase_clear(&this->masks);
|
|
this->active_mask_index = 0;
|
|
|
|
this->runtime = MEM_new<LayerRuntime>(__func__);
|
|
}
|
|
|
|
Layer::Layer(const StringRef name) : Layer()
|
|
{
|
|
new (&this->base) TreeNode(GP_LAYER_TREE_LEAF, name);
|
|
}
|
|
|
|
Layer::Layer(const Layer &other) : Layer()
|
|
{
|
|
new (&this->base) TreeNode(other.base.wrap());
|
|
|
|
LISTBASE_FOREACH (GreasePencilLayerMask *, other_mask, &other.masks) {
|
|
LayerMask *new_mask = MEM_new<LayerMask>(__func__, *reinterpret_cast<LayerMask *>(other_mask));
|
|
BLI_addtail(&this->masks, reinterpret_cast<GreasePencilLayerMask *>(new_mask));
|
|
}
|
|
this->active_mask_index = other.active_mask_index;
|
|
|
|
this->blend_mode = other.blend_mode;
|
|
this->opacity = other.opacity;
|
|
|
|
this->parent = other.parent;
|
|
this->set_parent_bone_name(other.parsubstr);
|
|
copy_m4_m4(this->parentinv, other.parentinv);
|
|
|
|
copy_v3_v3(this->translation, other.translation);
|
|
copy_v3_v3(this->rotation, other.rotation);
|
|
copy_v3_v3(this->scale, other.scale);
|
|
|
|
this->set_view_layer_name(other.viewlayername);
|
|
|
|
/* NOTE: We do not duplicate the frame storage since it is only needed for writing to file. */
|
|
this->runtime->frames_ = other.runtime->frames_;
|
|
this->runtime->sorted_keys_cache_ = other.runtime->sorted_keys_cache_;
|
|
/* Tag the frames map, so the frame storage is recreated once the DNA is saved. */
|
|
this->tag_frames_map_changed();
|
|
|
|
/* TODO: what about masks cache? */
|
|
}
|
|
|
|
Layer::~Layer()
|
|
{
|
|
this->base.wrap().~TreeNode();
|
|
|
|
MEM_SAFE_FREE(this->frames_storage.keys);
|
|
MEM_SAFE_FREE(this->frames_storage.values);
|
|
|
|
LISTBASE_FOREACH_MUTABLE (GreasePencilLayerMask *, mask, &this->masks) {
|
|
MEM_delete(reinterpret_cast<LayerMask *>(mask));
|
|
}
|
|
BLI_listbase_clear(&this->masks);
|
|
|
|
MEM_SAFE_FREE(this->parsubstr);
|
|
MEM_SAFE_FREE(this->viewlayername);
|
|
|
|
MEM_delete(this->runtime);
|
|
this->runtime = nullptr;
|
|
}
|
|
|
|
const Map<int, GreasePencilFrame> &Layer::frames() const
|
|
{
|
|
return this->runtime->frames_;
|
|
}
|
|
|
|
Map<int, GreasePencilFrame> &Layer::frames_for_write()
|
|
{
|
|
return this->runtime->frames_;
|
|
}
|
|
|
|
Layer::SortedKeysIterator Layer::remove_leading_end_frames_in_range(
|
|
Layer::SortedKeysIterator begin, Layer::SortedKeysIterator end)
|
|
{
|
|
Layer::SortedKeysIterator next_it = begin;
|
|
while (next_it != end && this->frames().lookup(*next_it).is_end()) {
|
|
this->frames_for_write().remove_contained(*next_it);
|
|
this->tag_frames_map_keys_changed();
|
|
next_it = std::next(next_it);
|
|
}
|
|
return next_it;
|
|
}
|
|
|
|
GreasePencilFrame *Layer::add_frame_internal(const FramesMapKeyT frame_number)
|
|
{
|
|
if (!this->frames().contains(frame_number)) {
|
|
GreasePencilFrame frame{};
|
|
this->frames_for_write().add_new(frame_number, frame);
|
|
this->tag_frames_map_keys_changed();
|
|
return this->frames_for_write().lookup_ptr(frame_number);
|
|
}
|
|
/* Overwrite end-frames. */
|
|
if (this->frames().lookup(frame_number).is_end()) {
|
|
GreasePencilFrame frame{};
|
|
this->frames_for_write().add_overwrite(frame_number, frame);
|
|
this->tag_frames_map_changed();
|
|
return this->frames_for_write().lookup_ptr(frame_number);
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
GreasePencilFrame *Layer::add_frame(const FramesMapKeyT key, const int duration)
|
|
{
|
|
BLI_assert(duration >= 0);
|
|
GreasePencilFrame *frame = this->add_frame_internal(key);
|
|
if (frame == nullptr) {
|
|
return nullptr;
|
|
}
|
|
Span<FramesMapKeyT> sorted_keys = this->sorted_keys();
|
|
const FramesMapKeyT end_key = key + duration;
|
|
/* Finds the next greater key that is stored in the map. */
|
|
SortedKeysIterator next_key_it = std::upper_bound(sorted_keys.begin(), sorted_keys.end(), key);
|
|
/* If the next frame we found is at the end of the frame we're inserting, then we are done. */
|
|
if (next_key_it != sorted_keys.end() && *next_key_it == end_key) {
|
|
return frame;
|
|
}
|
|
next_key_it = this->remove_leading_end_frames_in_range(next_key_it, sorted_keys.end());
|
|
/* If the duration is set to 0, the frame is marked as an implicit hold. */
|
|
if (duration == 0) {
|
|
frame->flag |= GP_FRAME_IMPLICIT_HOLD;
|
|
return frame;
|
|
}
|
|
/* If the next frame comes after the end of the frame we're inserting (or if there are no more
|
|
* frames), add an end-frame. */
|
|
if (next_key_it == sorted_keys.end() || *next_key_it > end_key) {
|
|
this->frames_for_write().add_new(end_key, GreasePencilFrame::end());
|
|
this->tag_frames_map_keys_changed();
|
|
}
|
|
return frame;
|
|
}
|
|
|
|
bool Layer::remove_frame(const FramesMapKeyT key)
|
|
{
|
|
/* If the frame number is not in the frames map, do nothing. */
|
|
if (!this->frames().contains(key)) {
|
|
return false;
|
|
}
|
|
if (this->frames().size() == 1) {
|
|
this->frames_for_write().remove_contained(key);
|
|
this->tag_frames_map_keys_changed();
|
|
return true;
|
|
}
|
|
Span<FramesMapKeyT> sorted_keys = this->sorted_keys();
|
|
/* Find the index of the frame to remove in the `sorted_keys` array. */
|
|
SortedKeysIterator remove_key_it = std::lower_bound(sorted_keys.begin(), sorted_keys.end(), key);
|
|
/* If there is a next frame: */
|
|
if (std::next(remove_key_it) != sorted_keys.end()) {
|
|
SortedKeysIterator next_key_it = std::next(remove_key_it);
|
|
this->remove_leading_end_frames_in_range(next_key_it, sorted_keys.end());
|
|
}
|
|
/* If there is a previous frame: */
|
|
if (remove_key_it != sorted_keys.begin()) {
|
|
SortedKeysIterator prev_key_it = std::prev(remove_key_it);
|
|
const GreasePencilFrame &prev_frame = this->frames().lookup(*prev_key_it);
|
|
/* If the previous frame is not an implicit hold (e.g. it has a fixed duration) and it's not an
|
|
* end frame, we cannot just delete the frame. We need to replace it with an end frame. */
|
|
if (!prev_frame.is_implicit_hold() && !prev_frame.is_end()) {
|
|
this->frames_for_write().lookup(key) = GreasePencilFrame::end();
|
|
this->tag_frames_map_changed();
|
|
/* Since the original frame was replaced with an end frame, we consider the frame to be
|
|
* successfully removed here. */
|
|
return true;
|
|
}
|
|
}
|
|
/* Finally, remove the actual frame. */
|
|
this->frames_for_write().remove_contained(key);
|
|
this->tag_frames_map_keys_changed();
|
|
return true;
|
|
}
|
|
|
|
Span<FramesMapKeyT> Layer::sorted_keys() const
|
|
{
|
|
this->runtime->sorted_keys_cache_.ensure([&](Vector<FramesMapKeyT> &r_data) {
|
|
r_data.reinitialize(this->frames().size());
|
|
int i = 0;
|
|
for (const FramesMapKeyT key : this->frames().keys()) {
|
|
r_data[i++] = key;
|
|
}
|
|
std::sort(r_data.begin(), r_data.end());
|
|
});
|
|
return this->runtime->sorted_keys_cache_.data();
|
|
}
|
|
|
|
Layer::SortedKeysIterator Layer::sorted_keys_iterator_at(const int frame_number) const
|
|
{
|
|
Span<int> sorted_keys = this->sorted_keys();
|
|
/* No keyframes, return nullptr. */
|
|
if (sorted_keys.is_empty()) {
|
|
return nullptr;
|
|
}
|
|
/* Before the first frame, return nullptr. */
|
|
if (frame_number < sorted_keys.first()) {
|
|
return nullptr;
|
|
}
|
|
/* After or at the last frame, return iterator to last. */
|
|
if (frame_number >= sorted_keys.last()) {
|
|
return std::prev(sorted_keys.end());
|
|
}
|
|
/* Search for the frame. std::upper_bound will get the frame just after. */
|
|
SortedKeysIterator it = std::upper_bound(sorted_keys.begin(), sorted_keys.end(), frame_number);
|
|
if (it == sorted_keys.end()) {
|
|
return nullptr;
|
|
}
|
|
return std::prev(it);
|
|
}
|
|
|
|
std::optional<FramesMapKeyT> Layer::frame_key_at(const int frame_number) const
|
|
{
|
|
SortedKeysIterator it = this->sorted_keys_iterator_at(frame_number);
|
|
if (it == nullptr) {
|
|
return {};
|
|
}
|
|
return *it;
|
|
}
|
|
|
|
std::optional<int> Layer::start_frame_at(int frame_number) const
|
|
{
|
|
const std::optional<FramesMapKeyT> frame_key = this->frame_key_at(frame_number);
|
|
/* Return the frame number only if the frame key exists and if it's not an end frame. */
|
|
if (frame_key && !this->frames().lookup_ptr(*frame_key)->is_end()) {
|
|
return frame_key;
|
|
}
|
|
return {};
|
|
}
|
|
|
|
int Layer::sorted_keys_index_at(const int frame_number) const
|
|
{
|
|
SortedKeysIterator it = this->sorted_keys_iterator_at(frame_number);
|
|
if (it == nullptr) {
|
|
return -1;
|
|
}
|
|
return std::distance(this->sorted_keys().begin(), it);
|
|
}
|
|
|
|
const GreasePencilFrame *Layer::frame_at(const int frame_number) const
|
|
{
|
|
const GreasePencilFrame *frame_ptr = [&]() -> const GreasePencilFrame * {
|
|
if (const GreasePencilFrame *frame = this->frames().lookup_ptr(frame_number)) {
|
|
return frame;
|
|
}
|
|
/* Look for a keyframe that starts before `frame_number` and ends after `frame_number`. */
|
|
const std::optional<FramesMapKeyT> frame_key = this->frame_key_at(frame_number);
|
|
if (!frame_key) {
|
|
return nullptr;
|
|
}
|
|
return this->frames().lookup_ptr(*frame_key);
|
|
}();
|
|
if (frame_ptr == nullptr || frame_ptr->is_end()) {
|
|
/* Not a valid frame. */
|
|
return nullptr;
|
|
}
|
|
return frame_ptr;
|
|
}
|
|
|
|
GreasePencilFrame *Layer::frame_at(const int frame_number)
|
|
{
|
|
GreasePencilFrame *frame_ptr = [&]() -> GreasePencilFrame * {
|
|
if (GreasePencilFrame *frame = this->frames_for_write().lookup_ptr(frame_number)) {
|
|
return frame;
|
|
}
|
|
/* Look for a keyframe that starts before `frame_number`. */
|
|
const std::optional<FramesMapKeyT> frame_key = this->frame_key_at(frame_number);
|
|
if (!frame_key) {
|
|
return nullptr;
|
|
}
|
|
return this->frames_for_write().lookup_ptr(*frame_key);
|
|
}();
|
|
if (frame_ptr == nullptr || frame_ptr->is_end()) {
|
|
/* Not a valid frame. */
|
|
return nullptr;
|
|
}
|
|
return frame_ptr;
|
|
}
|
|
|
|
int Layer::drawing_index_at(const int frame_number) const
|
|
{
|
|
const GreasePencilFrame *frame = frame_at(frame_number);
|
|
return (frame != nullptr) ? frame->drawing_index : -1;
|
|
}
|
|
|
|
bool Layer::has_drawing_at(const int frame_number) const
|
|
{
|
|
return frame_at(frame_number) != nullptr;
|
|
}
|
|
|
|
int Layer::get_frame_duration_at(const int frame_number) const
|
|
{
|
|
SortedKeysIterator it = this->sorted_keys_iterator_at(frame_number);
|
|
if (it == nullptr) {
|
|
return -1;
|
|
}
|
|
const FramesMapKeyT key = *it;
|
|
const GreasePencilFrame *frame = this->frames().lookup_ptr(key);
|
|
/* For frames that are implicitly held, we return a duration of 0. */
|
|
if (frame->is_implicit_hold()) {
|
|
return 0;
|
|
}
|
|
/* Frame is an end frame, so there is no keyframe at `frame_number`. */
|
|
if (frame->is_end()) {
|
|
return -1;
|
|
}
|
|
/* Compute the distance in frames between this key and the next key. */
|
|
const int next_frame_number = *(std::next(it));
|
|
return math::distance(key, next_frame_number);
|
|
}
|
|
|
|
void Layer::tag_frames_map_changed()
|
|
{
|
|
this->frames_storage.flag |= GP_LAYER_FRAMES_STORAGE_DIRTY;
|
|
}
|
|
|
|
void Layer::tag_frames_map_keys_changed()
|
|
{
|
|
this->tag_frames_map_changed();
|
|
this->runtime->sorted_keys_cache_.tag_dirty();
|
|
}
|
|
|
|
void Layer::prepare_for_dna_write()
|
|
{
|
|
/* Re-create the frames storage only if it was tagged dirty. */
|
|
if ((frames_storage.flag & GP_LAYER_FRAMES_STORAGE_DIRTY) == 0) {
|
|
return;
|
|
}
|
|
|
|
MEM_SAFE_FREE(frames_storage.keys);
|
|
MEM_SAFE_FREE(frames_storage.values);
|
|
|
|
const size_t frames_num = size_t(frames().size());
|
|
frames_storage.num = int(frames_num);
|
|
frames_storage.keys = MEM_calloc_arrayN<int>(frames_num, __func__);
|
|
frames_storage.values = MEM_calloc_arrayN<GreasePencilFrame>(frames_num, __func__);
|
|
const Span<int> sorted_keys_data = sorted_keys();
|
|
for (const int64_t i : sorted_keys_data.index_range()) {
|
|
frames_storage.keys[i] = sorted_keys_data[i];
|
|
frames_storage.values[i] = frames().lookup(sorted_keys_data[i]);
|
|
}
|
|
|
|
/* Reset the flag. */
|
|
frames_storage.flag &= ~GP_LAYER_FRAMES_STORAGE_DIRTY;
|
|
}
|
|
|
|
void Layer::update_from_dna_read()
|
|
{
|
|
/* Re-create frames data in runtime map. */
|
|
/* NOTE: Avoid re-allocating runtime data to reduce 'false positive' change detection from
|
|
* MEMFILE undo. */
|
|
if (runtime) {
|
|
runtime->clear();
|
|
}
|
|
else {
|
|
runtime = MEM_new<blender::bke::greasepencil::LayerRuntime>(__func__);
|
|
}
|
|
Map<int, GreasePencilFrame> &frames = frames_for_write();
|
|
for (int i = 0; i < frames_storage.num; i++) {
|
|
frames.add_new(frames_storage.keys[i], frames_storage.values[i]);
|
|
}
|
|
}
|
|
|
|
float4x4 Layer::to_world_space(const Object &object) const
|
|
{
|
|
if (this->parent == nullptr) {
|
|
return object.object_to_world() * this->local_transform();
|
|
}
|
|
const Object &parent = *this->parent;
|
|
return this->parent_to_world(parent) * this->parent_inverse() * this->local_transform();
|
|
}
|
|
|
|
float4x4 Layer::to_object_space(const Object &object) const
|
|
{
|
|
if (this->parent == nullptr) {
|
|
return this->local_transform();
|
|
}
|
|
const Object &parent = *this->parent;
|
|
return object.world_to_object() * this->parent_to_world(parent) * this->parent_inverse() *
|
|
this->local_transform();
|
|
}
|
|
|
|
StringRefNull Layer::parent_bone_name() const
|
|
{
|
|
return (this->parsubstr != nullptr) ? StringRefNull(this->parsubstr) : StringRefNull();
|
|
}
|
|
|
|
void Layer::set_parent_bone_name(const StringRef new_name)
|
|
{
|
|
if (this->parsubstr != nullptr) {
|
|
MEM_freeN(this->parsubstr);
|
|
this->parsubstr = nullptr;
|
|
}
|
|
if (!new_name.is_empty()) {
|
|
this->parsubstr = BLI_strdupn(new_name.data(), new_name.size());
|
|
}
|
|
}
|
|
|
|
float4x4 Layer::parent_to_world(const Object &parent) const
|
|
{
|
|
const float4x4 &parent_object_to_world = parent.object_to_world();
|
|
if (parent.type == OB_ARMATURE && !this->parent_bone_name().is_empty()) {
|
|
if (bPoseChannel *channel = BKE_pose_channel_find_name(parent.pose,
|
|
this->parent_bone_name().c_str()))
|
|
{
|
|
return parent_object_to_world * float4x4_view(channel->pose_mat);
|
|
}
|
|
}
|
|
return parent_object_to_world;
|
|
}
|
|
|
|
float4x4 Layer::parent_inverse() const
|
|
{
|
|
return float4x4_view(this->parentinv);
|
|
}
|
|
|
|
float4x4 Layer::local_transform() const
|
|
{
|
|
return math::from_loc_rot_scale<float4x4, math::EulerXYZ>(
|
|
float3(this->translation), float3(this->rotation), float3(this->scale));
|
|
}
|
|
|
|
void Layer::set_local_transform(const float4x4 &transform)
|
|
{
|
|
math::to_loc_rot_scale_safe<true>(transform,
|
|
*reinterpret_cast<float3 *>(this->translation),
|
|
*reinterpret_cast<math::EulerXYZ *>(this->rotation),
|
|
*reinterpret_cast<float3 *>(this->scale));
|
|
}
|
|
|
|
StringRefNull Layer::view_layer_name() const
|
|
{
|
|
return (this->viewlayername != nullptr) ? StringRefNull(this->viewlayername) : StringRefNull();
|
|
}
|
|
|
|
void Layer::set_view_layer_name(const StringRef new_name)
|
|
{
|
|
if (this->viewlayername != nullptr) {
|
|
MEM_freeN(this->viewlayername);
|
|
this->viewlayername = nullptr;
|
|
}
|
|
if (!new_name.is_empty()) {
|
|
this->viewlayername = BLI_strdupn(new_name.data(), new_name.size());
|
|
}
|
|
}
|
|
|
|
LayerGroup::LayerGroup()
|
|
{
|
|
new (&this->base) TreeNode(GP_LAYER_TREE_GROUP);
|
|
|
|
BLI_listbase_clear(&this->children);
|
|
this->color_tag = LAYERGROUP_COLOR_NONE;
|
|
|
|
this->runtime = MEM_new<LayerGroupRuntime>(__func__);
|
|
}
|
|
|
|
LayerGroup::LayerGroup(StringRef name) : LayerGroup()
|
|
{
|
|
new (&this->base) TreeNode(GP_LAYER_TREE_GROUP, name);
|
|
}
|
|
|
|
LayerGroup::LayerGroup(const LayerGroup &other) : LayerGroup()
|
|
{
|
|
new (&this->base) TreeNode(other.base.wrap());
|
|
|
|
LISTBASE_FOREACH (GreasePencilLayerTreeNode *, child, &other.children) {
|
|
switch (child->type) {
|
|
case GP_LAYER_TREE_LEAF: {
|
|
GreasePencilLayer *layer = reinterpret_cast<GreasePencilLayer *>(child);
|
|
Layer *dup_layer = MEM_new<Layer>(__func__, layer->wrap());
|
|
this->add_node(dup_layer->as_node());
|
|
break;
|
|
}
|
|
case GP_LAYER_TREE_GROUP: {
|
|
GreasePencilLayerTreeGroup *group = reinterpret_cast<GreasePencilLayerTreeGroup *>(child);
|
|
LayerGroup *dup_group = MEM_new<LayerGroup>(__func__, group->wrap());
|
|
this->add_node(dup_group->as_node());
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
this->color_tag = other.color_tag;
|
|
}
|
|
|
|
LayerGroup::~LayerGroup()
|
|
{
|
|
this->base.wrap().~TreeNode();
|
|
|
|
LISTBASE_FOREACH_MUTABLE (GreasePencilLayerTreeNode *, child, &this->children) {
|
|
switch (child->type) {
|
|
case GP_LAYER_TREE_LEAF: {
|
|
GreasePencilLayer *layer = reinterpret_cast<GreasePencilLayer *>(child);
|
|
MEM_delete(&layer->wrap());
|
|
break;
|
|
}
|
|
case GP_LAYER_TREE_GROUP: {
|
|
GreasePencilLayerTreeGroup *group = reinterpret_cast<GreasePencilLayerTreeGroup *>(child);
|
|
MEM_delete(&group->wrap());
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
MEM_delete(this->runtime);
|
|
this->runtime = nullptr;
|
|
}
|
|
|
|
LayerGroup &LayerGroup::operator=(const LayerGroup &other)
|
|
{
|
|
if (this == &other) {
|
|
return *this;
|
|
}
|
|
|
|
this->~LayerGroup();
|
|
new (this) LayerGroup(other);
|
|
|
|
return *this;
|
|
}
|
|
|
|
bool LayerGroup::is_empty() const
|
|
{
|
|
return BLI_listbase_is_empty(&this->children);
|
|
}
|
|
|
|
TreeNode &LayerGroup::add_node(TreeNode &node)
|
|
{
|
|
BLI_addtail(&this->children, &node);
|
|
node.parent = reinterpret_cast<GreasePencilLayerTreeGroup *>(this);
|
|
this->tag_nodes_cache_dirty();
|
|
return node;
|
|
}
|
|
void LayerGroup::add_node_before(TreeNode &node, TreeNode &link)
|
|
{
|
|
BLI_assert(BLI_findindex(&this->children, &link) != -1);
|
|
BLI_insertlinkbefore(&this->children, &link, &node);
|
|
node.parent = reinterpret_cast<GreasePencilLayerTreeGroup *>(this);
|
|
this->tag_nodes_cache_dirty();
|
|
}
|
|
void LayerGroup::add_node_after(TreeNode &node, TreeNode &link)
|
|
{
|
|
BLI_assert(BLI_findindex(&this->children, &link) != -1);
|
|
BLI_insertlinkafter(&this->children, &link, &node);
|
|
node.parent = reinterpret_cast<GreasePencilLayerTreeGroup *>(this);
|
|
this->tag_nodes_cache_dirty();
|
|
}
|
|
|
|
void LayerGroup::move_node_up(TreeNode &node, const int step)
|
|
{
|
|
BLI_listbase_link_move(&this->children, &node, step);
|
|
this->tag_nodes_cache_dirty();
|
|
}
|
|
void LayerGroup::move_node_down(TreeNode &node, const int step)
|
|
{
|
|
BLI_listbase_link_move(&this->children, &node, -step);
|
|
this->tag_nodes_cache_dirty();
|
|
}
|
|
void LayerGroup::move_node_top(TreeNode &node)
|
|
{
|
|
BLI_remlink(&this->children, &node);
|
|
BLI_insertlinkafter(&this->children, this->children.last, &node);
|
|
this->tag_nodes_cache_dirty();
|
|
}
|
|
void LayerGroup::move_node_bottom(TreeNode &node)
|
|
{
|
|
BLI_remlink(&this->children, &node);
|
|
BLI_insertlinkbefore(&this->children, this->children.first, &node);
|
|
this->tag_nodes_cache_dirty();
|
|
}
|
|
|
|
int64_t LayerGroup::num_direct_nodes() const
|
|
{
|
|
return BLI_listbase_count(&this->children);
|
|
}
|
|
|
|
int64_t LayerGroup::num_nodes_total() const
|
|
{
|
|
this->ensure_nodes_cache();
|
|
return this->runtime->nodes_cache_.size();
|
|
}
|
|
|
|
bool LayerGroup::unlink_node(TreeNode &link, const bool keep_children)
|
|
{
|
|
if (link.is_group() && !link.as_group().is_empty() && keep_children) {
|
|
if (BLI_findindex(&this->children, &link) == -1) {
|
|
return false;
|
|
}
|
|
|
|
/* Take ownership of the children of `link` by replacing the node with the listbase of its
|
|
* children. */
|
|
ListBase link_children = link.as_group().children;
|
|
GreasePencilLayerTreeNode *first = static_cast<GreasePencilLayerTreeNode *>(
|
|
link_children.first);
|
|
GreasePencilLayerTreeNode *last = static_cast<GreasePencilLayerTreeNode *>(link_children.last);
|
|
|
|
/* Rewrite the parent pointers. */
|
|
LISTBASE_FOREACH (GreasePencilLayerTreeNode *, child, &link_children) {
|
|
child->parent = this;
|
|
}
|
|
|
|
/* Update previous and/or next link(s). */
|
|
if (link.next != nullptr) {
|
|
link.next->prev = last;
|
|
last->next = link.next;
|
|
}
|
|
if (link.prev != nullptr) {
|
|
link.prev->next = first;
|
|
first->prev = link.prev;
|
|
}
|
|
|
|
/* Update first and/or last link(s). */
|
|
if (this->children.last == &link) {
|
|
this->children.last = last;
|
|
}
|
|
if (this->children.first == &link) {
|
|
this->children.first = first;
|
|
}
|
|
|
|
/* Listbase has been inserted in `this->children` we can clear the pointers in `link`. */
|
|
BLI_listbase_clear(&link.as_group().children);
|
|
link.parent = nullptr;
|
|
|
|
this->tag_nodes_cache_dirty();
|
|
return true;
|
|
}
|
|
if (BLI_remlink_safe(&this->children, &link)) {
|
|
link.parent = nullptr;
|
|
this->tag_nodes_cache_dirty();
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
Span<const TreeNode *> LayerGroup::nodes() const
|
|
{
|
|
this->ensure_nodes_cache();
|
|
return this->runtime->nodes_cache_.as_span();
|
|
}
|
|
|
|
Span<TreeNode *> LayerGroup::nodes_for_write()
|
|
{
|
|
this->ensure_nodes_cache();
|
|
return this->runtime->nodes_cache_.as_span();
|
|
}
|
|
|
|
Span<const Layer *> LayerGroup::layers() const
|
|
{
|
|
this->ensure_nodes_cache();
|
|
return this->runtime->layer_cache_.as_span();
|
|
}
|
|
|
|
Span<Layer *> LayerGroup::layers_for_write()
|
|
{
|
|
this->ensure_nodes_cache();
|
|
return this->runtime->layer_cache_.as_span();
|
|
}
|
|
|
|
Span<const LayerGroup *> LayerGroup::groups() const
|
|
{
|
|
this->ensure_nodes_cache();
|
|
return this->runtime->layer_group_cache_.as_span();
|
|
}
|
|
|
|
Span<LayerGroup *> LayerGroup::groups_for_write()
|
|
{
|
|
this->ensure_nodes_cache();
|
|
return this->runtime->layer_group_cache_.as_span();
|
|
}
|
|
|
|
const TreeNode *LayerGroup::find_node_by_name(const StringRef name) const
|
|
{
|
|
for (const TreeNode *node : this->nodes()) {
|
|
if (node->name() == name) {
|
|
return node;
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
TreeNode *LayerGroup::find_node_by_name(const StringRef name)
|
|
{
|
|
for (TreeNode *node : this->nodes_for_write()) {
|
|
if (node->name() == name) {
|
|
return node;
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
bool LayerGroup::is_expanded() const
|
|
{
|
|
return (this->base.flag & GP_LAYER_TREE_NODE_EXPANDED) != 0;
|
|
}
|
|
|
|
void LayerGroup::set_expanded(const bool expanded)
|
|
{
|
|
SET_FLAG_FROM_TEST(this->base.flag, expanded, GP_LAYER_TREE_NODE_EXPANDED);
|
|
}
|
|
|
|
void LayerGroup::print_nodes(const StringRef header) const
|
|
{
|
|
std::cout << header << std::endl;
|
|
Stack<std::pair<int, TreeNode *>> next_node;
|
|
LISTBASE_FOREACH_BACKWARD (GreasePencilLayerTreeNode *, child_, &this->children) {
|
|
TreeNode *child = reinterpret_cast<TreeNode *>(child_);
|
|
next_node.push(std::make_pair(1, child));
|
|
}
|
|
while (!next_node.is_empty()) {
|
|
auto [indent, node] = next_node.pop();
|
|
for (int i = 0; i < indent; i++) {
|
|
std::cout << " ";
|
|
}
|
|
if (node->is_layer()) {
|
|
std::cout << node->name();
|
|
}
|
|
else if (node->is_group()) {
|
|
std::cout << node->name() << ": ";
|
|
LISTBASE_FOREACH_BACKWARD (GreasePencilLayerTreeNode *, child_, &node->as_group().children) {
|
|
TreeNode *child = reinterpret_cast<TreeNode *>(child_);
|
|
next_node.push(std::make_pair(indent + 1, child));
|
|
}
|
|
}
|
|
std::cout << std::endl;
|
|
}
|
|
std::cout << std::endl;
|
|
}
|
|
|
|
void LayerGroup::ensure_nodes_cache() const
|
|
{
|
|
this->runtime->nodes_cache_mutex_.ensure([&]() {
|
|
this->runtime->nodes_cache_.clear_and_shrink();
|
|
this->runtime->layer_cache_.clear_and_shrink();
|
|
this->runtime->layer_group_cache_.clear_and_shrink();
|
|
|
|
LISTBASE_FOREACH (GreasePencilLayerTreeNode *, child_, &this->children) {
|
|
TreeNode *node = reinterpret_cast<TreeNode *>(child_);
|
|
this->runtime->nodes_cache_.append(node);
|
|
switch (node->type) {
|
|
case GP_LAYER_TREE_LEAF: {
|
|
this->runtime->layer_cache_.append(&node->as_layer());
|
|
break;
|
|
}
|
|
case GP_LAYER_TREE_GROUP: {
|
|
this->runtime->layer_group_cache_.append(&node->as_group());
|
|
for (TreeNode *child : node->as_group().nodes_for_write()) {
|
|
this->runtime->nodes_cache_.append(child);
|
|
if (child->is_layer()) {
|
|
this->runtime->layer_cache_.append(&child->as_layer());
|
|
}
|
|
else if (child->is_group()) {
|
|
this->runtime->layer_group_cache_.append(&child->as_group());
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
void LayerGroup::tag_nodes_cache_dirty() const
|
|
{
|
|
this->runtime->nodes_cache_mutex_.tag_dirty();
|
|
if (this->base.parent) {
|
|
this->base.parent->wrap().tag_nodes_cache_dirty();
|
|
}
|
|
}
|
|
|
|
void LayerGroup::prepare_for_dna_write()
|
|
{
|
|
LISTBASE_FOREACH (TreeNode *, child, &children) {
|
|
switch (child->type) {
|
|
case GP_LAYER_TREE_LEAF: {
|
|
child->as_layer().prepare_for_dna_write();
|
|
break;
|
|
}
|
|
case GP_LAYER_TREE_GROUP: {
|
|
child->as_group().prepare_for_dna_write();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void LayerGroup::update_from_dna_read()
|
|
{
|
|
LISTBASE_FOREACH (TreeNode *, child, &children) {
|
|
switch (child->type) {
|
|
case GP_LAYER_TREE_LEAF: {
|
|
child->as_layer().update_from_dna_read();
|
|
break;
|
|
}
|
|
case GP_LAYER_TREE_GROUP: {
|
|
child->as_group().update_from_dna_read();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void ensure_non_empty_layer_names(Main &bmain, GreasePencil &grease_pencil)
|
|
{
|
|
for (bke::greasepencil::Layer *layer : grease_pencil.layers_for_write()) {
|
|
if (layer->name().is_empty()) {
|
|
grease_pencil.rename_node(bmain, layer->as_node(), DATA_("Layer"));
|
|
}
|
|
}
|
|
}
|
|
|
|
} // namespace blender::bke::greasepencil
|
|
|
|
namespace blender::bke {
|
|
|
|
GreasePencilRuntime::GreasePencilRuntime() = default;
|
|
GreasePencilRuntime::~GreasePencilRuntime() = default;
|
|
|
|
std::optional<Span<float3>> GreasePencilDrawingEditHints::positions() const
|
|
{
|
|
if (!this->positions_data.has_value()) {
|
|
return std::nullopt;
|
|
}
|
|
const int points_num = this->drawing_orig->geometry.wrap().points_num();
|
|
return Span(static_cast<const float3 *>(this->positions_data.data), points_num);
|
|
}
|
|
|
|
std::optional<MutableSpan<float3>> GreasePencilDrawingEditHints::positions_for_write()
|
|
{
|
|
if (!this->positions_data.has_value()) {
|
|
return std::nullopt;
|
|
}
|
|
|
|
const int points_num = this->drawing_orig->geometry.wrap().points_num();
|
|
ImplicitSharingPtrAndData &data = this->positions_data;
|
|
if (data.sharing_info->is_mutable()) {
|
|
/* If the referenced component is already mutable, return it directly. */
|
|
data.sharing_info->tag_ensured_mutable();
|
|
}
|
|
else {
|
|
auto *new_sharing_info = new ImplicitSharedValue<Array<float3>>(*this->positions());
|
|
data.sharing_info = ImplicitSharingPtr<>(new_sharing_info);
|
|
data.data = new_sharing_info->data.data();
|
|
}
|
|
|
|
return MutableSpan(const_cast<float3 *>(static_cast<const float3 *>(data.data)), points_num);
|
|
}
|
|
|
|
} // namespace blender::bke
|
|
|
|
/* ------------------------------------------------------------------- */
|
|
/** \name Grease Pencil kernel functions
|
|
* \{ */
|
|
|
|
bool BKE_grease_pencil_drawing_attribute_required(const GreasePencilDrawing * /*drawing*/,
|
|
const blender::StringRef name)
|
|
{
|
|
return name == ATTR_POSITION;
|
|
}
|
|
|
|
GreasePencil *BKE_grease_pencil_add(Main *bmain, const char *name)
|
|
{
|
|
GreasePencil *grease_pencil = BKE_id_new<GreasePencil>(bmain, name);
|
|
|
|
return grease_pencil;
|
|
}
|
|
|
|
GreasePencil *BKE_grease_pencil_new_nomain()
|
|
{
|
|
GreasePencil *grease_pencil = BKE_id_new_nomain<GreasePencil>(nullptr);
|
|
return grease_pencil;
|
|
}
|
|
|
|
GreasePencil *BKE_grease_pencil_copy_for_eval(const GreasePencil *grease_pencil_src)
|
|
{
|
|
GreasePencil *grease_pencil = reinterpret_cast<GreasePencil *>(
|
|
BKE_id_copy_ex(nullptr, &grease_pencil_src->id, nullptr, LIB_ID_COPY_LOCALIZE));
|
|
grease_pencil->runtime->eval_frame = grease_pencil_src->runtime->eval_frame;
|
|
return grease_pencil;
|
|
}
|
|
|
|
void BKE_grease_pencil_copy_parameters(const GreasePencil &src, GreasePencil &dst)
|
|
{
|
|
dst.material_array_num = src.material_array_num;
|
|
dst.material_array = static_cast<Material **>(MEM_dupallocN(src.material_array));
|
|
dst.attributes_active_index = src.attributes_active_index;
|
|
dst.flag = src.flag;
|
|
BLI_duplicatelist(&dst.vertex_group_names, &src.vertex_group_names);
|
|
dst.vertex_group_active_index = src.vertex_group_active_index;
|
|
dst.onion_skinning_settings = src.onion_skinning_settings;
|
|
}
|
|
|
|
void BKE_grease_pencil_copy_layer_parameters(const blender::bke::greasepencil::Layer &src,
|
|
blender::bke::greasepencil::Layer &dst)
|
|
{
|
|
using namespace blender::bke::greasepencil;
|
|
dst.as_node().flag = src.as_node().flag;
|
|
copy_v3_v3(dst.as_node().color, src.as_node().color);
|
|
|
|
dst.blend_mode = src.blend_mode;
|
|
dst.opacity = src.opacity;
|
|
|
|
LISTBASE_FOREACH (GreasePencilLayerMask *, src_mask, &src.masks) {
|
|
LayerMask *new_mask = MEM_new<LayerMask>(__func__, *reinterpret_cast<LayerMask *>(src_mask));
|
|
BLI_addtail(&dst.masks, reinterpret_cast<GreasePencilLayerMask *>(new_mask));
|
|
}
|
|
dst.active_mask_index = src.active_mask_index;
|
|
|
|
dst.parent = src.parent;
|
|
dst.set_parent_bone_name(src.parsubstr);
|
|
copy_m4_m4(dst.parentinv, src.parentinv);
|
|
|
|
copy_v3_v3(dst.translation, src.translation);
|
|
copy_v3_v3(dst.rotation, src.rotation);
|
|
copy_v3_v3(dst.scale, src.scale);
|
|
|
|
dst.set_view_layer_name(src.viewlayername);
|
|
}
|
|
|
|
void BKE_grease_pencil_copy_layer_group_parameters(
|
|
const blender::bke::greasepencil::LayerGroup &src, blender::bke::greasepencil::LayerGroup &dst)
|
|
{
|
|
using namespace blender::bke::greasepencil;
|
|
dst.as_node().flag = src.as_node().flag;
|
|
copy_v3_v3(dst.as_node().color, src.as_node().color);
|
|
dst.color_tag = src.color_tag;
|
|
}
|
|
|
|
void BKE_grease_pencil_nomain_to_grease_pencil(GreasePencil *grease_pencil_src,
|
|
GreasePencil *grease_pencil_dst)
|
|
{
|
|
using namespace blender;
|
|
using bke::greasepencil::Drawing;
|
|
using bke::greasepencil::DrawingReference;
|
|
|
|
/* Drawings. */
|
|
free_drawing_array(*grease_pencil_dst);
|
|
grease_pencil_dst->resize_drawings(grease_pencil_src->drawing_array_num);
|
|
for (const int i : IndexRange(grease_pencil_dst->drawing_array_num)) {
|
|
switch (grease_pencil_src->drawing_array[i]->type) {
|
|
case GP_DRAWING: {
|
|
const Drawing &src_drawing =
|
|
reinterpret_cast<GreasePencilDrawing *>(grease_pencil_src->drawing_array[i])->wrap();
|
|
grease_pencil_dst->drawing_array[i] = &MEM_new<Drawing>(__func__, src_drawing)->base;
|
|
break;
|
|
}
|
|
case GP_DRAWING_REFERENCE:
|
|
const DrawingReference &src_drawing_ref = reinterpret_cast<GreasePencilDrawingReference *>(
|
|
grease_pencil_src->drawing_array[i])
|
|
->wrap();
|
|
grease_pencil_dst->drawing_array[i] =
|
|
&MEM_new<DrawingReference>(__func__, src_drawing_ref)->base;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Layers. */
|
|
if (grease_pencil_dst->root_group_ptr) {
|
|
MEM_delete(&grease_pencil_dst->root_group());
|
|
}
|
|
|
|
grease_pencil_dst->root_group_ptr = MEM_new<bke::greasepencil::LayerGroup>(
|
|
__func__, grease_pencil_src->root_group_ptr->wrap());
|
|
BLI_assert(grease_pencil_src->layers().size() == grease_pencil_dst->layers().size());
|
|
|
|
/* Reset the active node. */
|
|
grease_pencil_dst->active_node = nullptr;
|
|
|
|
grease_pencil_dst->attribute_storage.wrap() = std::move(
|
|
grease_pencil_src->attribute_storage.wrap());
|
|
|
|
DEG_id_tag_update(&grease_pencil_dst->id, ID_RECALC_GEOMETRY);
|
|
|
|
BKE_id_free(nullptr, grease_pencil_src);
|
|
}
|
|
|
|
void BKE_grease_pencil_vgroup_name_update(Object *ob, const char *old_name, const char *new_name)
|
|
{
|
|
using namespace blender::bke::greasepencil;
|
|
GreasePencil &grease_pencil = *static_cast<GreasePencil *>(ob->data);
|
|
for (GreasePencilDrawingBase *base : grease_pencil.drawings()) {
|
|
Drawing &drawing = reinterpret_cast<GreasePencilDrawing *>(base)->wrap();
|
|
CurvesGeometry &curves = drawing.strokes_for_write();
|
|
LISTBASE_FOREACH (bDeformGroup *, vgroup, &curves.vertex_group_names) {
|
|
if (STREQ(vgroup->name, old_name)) {
|
|
STRNCPY_UTF8(vgroup->name, new_name);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void grease_pencil_evaluate_modifiers(Depsgraph *depsgraph,
|
|
Scene *scene,
|
|
Object *object,
|
|
blender::bke::GeometrySet &geometry_set)
|
|
{
|
|
/* Modifier evaluation modes. */
|
|
const bool use_render = DEG_get_mode(depsgraph) == DAG_EVAL_RENDER;
|
|
ModifierMode required_mode = use_render ? eModifierMode_Render : eModifierMode_Realtime;
|
|
if (BKE_object_is_in_editmode(object)) {
|
|
required_mode |= eModifierMode_Editmode;
|
|
}
|
|
ModifierApplyFlag apply_flag = use_render ? MOD_APPLY_RENDER : MOD_APPLY_USECACHE;
|
|
const ModifierEvalContext mectx = {depsgraph, object, apply_flag};
|
|
|
|
BKE_modifiers_clear_errors(object);
|
|
|
|
/* Get effective list of modifiers to execute. Some effects like shape keys
|
|
* are added as virtual modifiers before the user created modifiers. */
|
|
VirtualModifierData virtualModifierData;
|
|
ModifierData *md = BKE_modifiers_get_virtual_modifierlist(object, &virtualModifierData);
|
|
|
|
/* Evaluate time modifiers.
|
|
* The time offset modifier can change what drawings are shown on the current frame. But it
|
|
* doesn't affect the drawings data. Modifiers that modify the drawings data are only evaluated
|
|
* for the current frame, so we run the time offset modifiers before all the other ones. */
|
|
ModifierData *tmd = md;
|
|
for (; tmd; tmd = tmd->next) {
|
|
const ModifierTypeInfo *mti = BKE_modifier_get_info(ModifierType(tmd->type));
|
|
|
|
if (!BKE_modifier_is_enabled(scene, tmd, required_mode) ||
|
|
ModifierType(tmd->type) != eModifierType_GreasePencilTime)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
blender::bke::ScopedModifierTimer modifier_timer{*md};
|
|
|
|
if (mti->modify_geometry_set != nullptr) {
|
|
mti->modify_geometry_set(tmd, &mectx, &geometry_set);
|
|
}
|
|
}
|
|
|
|
/* Evaluate drawing modifiers. */
|
|
for (; md; md = md->next) {
|
|
const ModifierTypeInfo *mti = BKE_modifier_get_info(ModifierType(md->type));
|
|
|
|
if (!BKE_modifier_is_enabled(scene, md, required_mode) ||
|
|
ModifierType(md->type) == eModifierType_GreasePencilTime)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
blender::bke::ScopedModifierTimer modifier_timer{*md};
|
|
|
|
if (mti->modify_geometry_set != nullptr) {
|
|
mti->modify_geometry_set(md, &mectx, &geometry_set);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void grease_pencil_do_layer_adjustments(GreasePencil &grease_pencil)
|
|
{
|
|
using namespace blender;
|
|
using namespace bke::greasepencil;
|
|
|
|
const bke::AttributeAccessor layer_attributes = grease_pencil.attributes();
|
|
|
|
struct LayerDrawingInfo {
|
|
Drawing *drawing;
|
|
const int layer_index;
|
|
};
|
|
|
|
Set<Drawing *> all_drawings;
|
|
Vector<LayerDrawingInfo> drawing_infos;
|
|
for (const int layer_i : grease_pencil.layers().index_range()) {
|
|
const Layer &layer = grease_pencil.layer(layer_i);
|
|
/* Set of owned drawings, ignore drawing references to other data blocks. */
|
|
if (Drawing *drawing = grease_pencil.get_eval_drawing(layer)) {
|
|
if (all_drawings.add(drawing)) {
|
|
drawing_infos.append({drawing, layer_i});
|
|
}
|
|
}
|
|
}
|
|
|
|
if (layer_attributes.contains("radius_offset")) {
|
|
const VArray<float> radius_offsets = *layer_attributes.lookup_or_default<float>(
|
|
"radius_offset", bke::AttrDomain::Layer, 0.0f);
|
|
threading::parallel_for_each(drawing_infos, [&](LayerDrawingInfo &info) {
|
|
if (radius_offsets[info.layer_index] == 0.0f) {
|
|
return;
|
|
}
|
|
MutableSpan<float> radii = info.drawing->radii_for_write();
|
|
threading::parallel_for(radii.index_range(), 4096, [&](const IndexRange range) {
|
|
for (const int i : range) {
|
|
radii[i] += radius_offsets[info.layer_index];
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
if (layer_attributes.contains("tint_color")) {
|
|
auto mix_tint = [](const float4 base, const float4 tint) -> float4 {
|
|
return base * (1.0 - tint.w) + tint * tint.w;
|
|
};
|
|
const VArray<ColorGeometry4f> tint_colors =
|
|
*layer_attributes.lookup_or_default<ColorGeometry4f>(
|
|
"tint_color", bke::AttrDomain::Layer, ColorGeometry4f(0.0f, 0.0f, 0.0f, 0.0f));
|
|
threading::parallel_for_each(drawing_infos, [&](LayerDrawingInfo &info) {
|
|
if (tint_colors[info.layer_index].a == 0.0f) {
|
|
return;
|
|
}
|
|
MutableSpan<ColorGeometry4f> vertex_colors = info.drawing->vertex_colors_for_write();
|
|
threading::parallel_for(vertex_colors.index_range(), 4096, [&](const IndexRange range) {
|
|
for (const int i : range) {
|
|
vertex_colors[i] = ColorGeometry4f(
|
|
mix_tint(float4(vertex_colors[i]), float4(tint_colors[info.layer_index])));
|
|
}
|
|
});
|
|
MutableSpan<ColorGeometry4f> fill_colors = info.drawing->fill_colors_for_write();
|
|
threading::parallel_for(fill_colors.index_range(), 4096, [&](const IndexRange range) {
|
|
for (const int i : range) {
|
|
fill_colors[i] = ColorGeometry4f(
|
|
mix_tint(float4(fill_colors[i]), float4(tint_colors[info.layer_index])));
|
|
}
|
|
});
|
|
});
|
|
}
|
|
}
|
|
|
|
static void grease_pencil_evaluate_layers(GreasePencil &grease_pencil)
|
|
{
|
|
using namespace blender;
|
|
using namespace blender::bke::greasepencil;
|
|
|
|
/* Copy the layer cache into an array here, because removing a layer will invalidate the layer
|
|
* cache. This will only copy the pointers to the layers, not the layers themselves. */
|
|
Array<Layer *> layers = grease_pencil.layers_for_write();
|
|
|
|
for (const int layer_i : layers.index_range()) {
|
|
Layer *layer = layers[layer_i];
|
|
/* Store the original index of the layer. */
|
|
layer->runtime->orig_layer_index_ = layer_i;
|
|
/* When the visibility is animated, the layer should be retained even when it is invisible.
|
|
* Changing the visibility through the animation system does NOT create another evaluated copy,
|
|
* and thus the layer has to be kept for this future use. */
|
|
if (layer->is_visible() || layer->runtime->is_visibility_animated_) {
|
|
continue;
|
|
}
|
|
|
|
/* Remove layer from evaluated data. */
|
|
grease_pencil.remove_layer(*layer);
|
|
}
|
|
}
|
|
|
|
void BKE_grease_pencil_eval_geometry(Depsgraph *depsgraph, GreasePencil *grease_pencil)
|
|
{
|
|
using namespace blender;
|
|
/* Store the frame that this grease pencil is evaluated on. */
|
|
grease_pencil->runtime->eval_frame = int(DEG_get_ctime(depsgraph));
|
|
/* This will remove layers that aren't visible. */
|
|
grease_pencil_evaluate_layers(*grease_pencil);
|
|
}
|
|
|
|
void BKE_object_eval_grease_pencil(Depsgraph *depsgraph, Scene *scene, Object *object)
|
|
{
|
|
using namespace blender;
|
|
using namespace blender::bke;
|
|
/* Free any evaluated data and restore original data. */
|
|
BKE_object_free_derived_caches(object);
|
|
|
|
GreasePencil *grease_pencil = static_cast<GreasePencil *>(object->data);
|
|
GeometrySet geometry_set = GeometrySet::from_grease_pencil(grease_pencil,
|
|
GeometryOwnershipType::ReadOnly);
|
|
/* The layer adjustments for tinting and radii offsets are applied before modifier evaluation.
|
|
* This ensures that the evaluated geometry contains the modifications. In the future, it would
|
|
* be better to move these into modifiers. For now, these are hardcoded. */
|
|
const bke::AttributeAccessor layer_attributes = grease_pencil->attributes();
|
|
if (layer_attributes.contains("tint_color") || layer_attributes.contains("radius_offset")) {
|
|
grease_pencil_do_layer_adjustments(*geometry_set.get_grease_pencil_for_write());
|
|
}
|
|
/* Only add the edit hint component in modes where users can potentially interact with deformed
|
|
* drawings. */
|
|
if (ELEM(object->mode,
|
|
OB_MODE_EDIT,
|
|
OB_MODE_SCULPT_GREASE_PENCIL,
|
|
OB_MODE_VERTEX_GREASE_PENCIL,
|
|
OB_MODE_WEIGHT_GREASE_PENCIL))
|
|
{
|
|
GeometryComponentEditData &edit_component =
|
|
geometry_set.get_component_for_write<GeometryComponentEditData>();
|
|
edit_component.grease_pencil_edit_hints_ = std::make_unique<GreasePencilEditHints>(
|
|
*static_cast<const GreasePencil *>(DEG_get_original(object)->data));
|
|
}
|
|
grease_pencil_evaluate_modifiers(depsgraph, scene, object, geometry_set);
|
|
|
|
if (geometry_set.has_grease_pencil()) {
|
|
/* Output geometry set may be different from the input,
|
|
* set the frame again to ensure a correct value. */
|
|
geometry_set.get_grease_pencil()->runtime->eval_frame = int(DEG_get_ctime(depsgraph));
|
|
}
|
|
else {
|
|
GreasePencil *empty_grease_pencil = BKE_grease_pencil_new_nomain();
|
|
empty_grease_pencil->runtime->eval_frame = int(DEG_get_ctime(depsgraph));
|
|
geometry_set.replace_grease_pencil(empty_grease_pencil);
|
|
}
|
|
|
|
/* For now the evaluated data is not const. We could use #get_grease_pencil_for_write, but that
|
|
* would result in a copy when it's shared. So for now, we use a const_cast here. */
|
|
GreasePencil *grease_pencil_eval = const_cast<GreasePencil *>(geometry_set.get_grease_pencil());
|
|
|
|
/* Assign evaluated object. */
|
|
BKE_object_eval_assign_data(object, &grease_pencil_eval->id, false);
|
|
object->runtime->geometry_set_eval = new GeometrySet(std::move(geometry_set));
|
|
}
|
|
|
|
void BKE_grease_pencil_duplicate_drawing_array(const GreasePencil *grease_pencil_src,
|
|
GreasePencil *grease_pencil_dst)
|
|
{
|
|
using namespace blender;
|
|
grease_pencil_dst->drawing_array_num = grease_pencil_src->drawing_array_num;
|
|
if (grease_pencil_dst->drawing_array_num > 0) {
|
|
grease_pencil_dst->drawing_array = MEM_calloc_arrayN<GreasePencilDrawingBase *>(
|
|
grease_pencil_src->drawing_array_num, __func__);
|
|
bke::greasepencil::copy_drawing_array(grease_pencil_src->drawings(),
|
|
grease_pencil_dst->drawings());
|
|
}
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* ------------------------------------------------------------------- */
|
|
/** \name Grease Pencil origin functions
|
|
* \note Used for "move only origins" in object_data_transform.cc.
|
|
* \{ */
|
|
|
|
bool BKE_grease_pencil_has_curve_with_type(const GreasePencil &grease_pencil, const CurveType type)
|
|
{
|
|
using namespace blender;
|
|
|
|
for (const GreasePencilDrawingBase *base : grease_pencil.drawings()) {
|
|
if (base->type != GP_DRAWING) {
|
|
continue;
|
|
}
|
|
const bke::greasepencil::Drawing &drawing =
|
|
reinterpret_cast<const GreasePencilDrawing *>(base)->wrap();
|
|
const bke::CurvesGeometry &curves = drawing.strokes();
|
|
if (curves.has_curve_with_type(type)) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
int BKE_grease_pencil_stroke_point_count(const GreasePencil &grease_pencil)
|
|
{
|
|
using namespace blender;
|
|
|
|
int total_points = 0;
|
|
|
|
for (const int layer_i : grease_pencil.layers().index_range()) {
|
|
const bke::greasepencil::Layer &layer = grease_pencil.layer(layer_i);
|
|
const Map<bke::greasepencil::FramesMapKeyT, GreasePencilFrame> frames = layer.frames();
|
|
frames.foreach_item(
|
|
[&](const bke::greasepencil::FramesMapKeyT /*key*/, const GreasePencilFrame frame) {
|
|
const GreasePencilDrawingBase *base = grease_pencil.drawing(frame.drawing_index);
|
|
if (base->type != GP_DRAWING) {
|
|
return;
|
|
}
|
|
const bke::greasepencil::Drawing &drawing =
|
|
reinterpret_cast<const GreasePencilDrawing *>(base)->wrap();
|
|
const bke::CurvesGeometry &curves = drawing.strokes();
|
|
total_points += curves.points_num();
|
|
});
|
|
}
|
|
|
|
return total_points;
|
|
}
|
|
|
|
void BKE_grease_pencil_point_coords_get(const GreasePencil &grease_pencil,
|
|
blender::MutableSpan<blender::float3> all_positions,
|
|
blender::MutableSpan<float> all_radii)
|
|
{
|
|
using namespace blender;
|
|
int64_t index = 0;
|
|
for (const int layer_i : grease_pencil.layers().index_range()) {
|
|
const bke::greasepencil::Layer &layer = grease_pencil.layer(layer_i);
|
|
const float4x4 layer_to_object = layer.local_transform();
|
|
const Map<bke::greasepencil::FramesMapKeyT, GreasePencilFrame> frames = layer.frames();
|
|
frames.foreach_item([&](const bke::greasepencil::FramesMapKeyT /*key*/,
|
|
const GreasePencilFrame frame) {
|
|
const GreasePencilDrawingBase *base = grease_pencil.drawing(frame.drawing_index);
|
|
if (base->type != GP_DRAWING) {
|
|
return;
|
|
}
|
|
const bke::greasepencil::Drawing &drawing =
|
|
reinterpret_cast<const GreasePencilDrawing *>(base)->wrap();
|
|
const bke::CurvesGeometry &curves = drawing.strokes();
|
|
const Span<float3> positions = curves.positions();
|
|
const VArray<float> radii = drawing.radii();
|
|
|
|
if (!curves.has_curve_with_type(CURVE_TYPE_BEZIER)) {
|
|
for (const int i : curves.points_range()) {
|
|
all_positions[index] = math::transform_point(layer_to_object, positions[i]);
|
|
all_radii[index] = radii[i];
|
|
index++;
|
|
}
|
|
}
|
|
else {
|
|
const std::optional<Span<float3>> handle_positions_left = curves.handle_positions_left();
|
|
const std::optional<Span<float3>> handle_positions_right = curves.handle_positions_right();
|
|
for (const int i : curves.points_range()) {
|
|
const int index_pos = index * 3;
|
|
all_positions[index_pos] = math::transform_point(layer_to_object,
|
|
(*handle_positions_left)[i]);
|
|
all_positions[index_pos + 1] = math::transform_point(layer_to_object, positions[i]);
|
|
all_positions[index_pos + 2] = math::transform_point(layer_to_object,
|
|
(*handle_positions_right)[i]);
|
|
all_radii[index] = radii[i];
|
|
index++;
|
|
}
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
void BKE_grease_pencil_point_coords_apply(GreasePencil &grease_pencil,
|
|
blender::Span<blender::float3> all_positions,
|
|
blender::Span<float> all_radii)
|
|
{
|
|
using namespace blender;
|
|
int64_t index = 0;
|
|
for (const int layer_i : grease_pencil.layers().index_range()) {
|
|
bke::greasepencil::Layer &layer = grease_pencil.layer(layer_i);
|
|
const float4x4 layer_to_object = layer.local_transform();
|
|
const float4x4 object_to_layer = math::invert(layer_to_object);
|
|
const Map<bke::greasepencil::FramesMapKeyT, GreasePencilFrame> frames = layer.frames();
|
|
frames.foreach_item([&](bke::greasepencil::FramesMapKeyT /*key*/, GreasePencilFrame frame) {
|
|
GreasePencilDrawingBase *base = grease_pencil.drawing(frame.drawing_index);
|
|
if (base->type != GP_DRAWING) {
|
|
return;
|
|
}
|
|
bke::greasepencil::Drawing &drawing = reinterpret_cast<GreasePencilDrawing *>(base)->wrap();
|
|
bke::CurvesGeometry &curves = drawing.strokes_for_write();
|
|
|
|
MutableSpan<float3> positions = curves.positions_for_write();
|
|
MutableSpan<float> radii = drawing.radii_for_write();
|
|
|
|
if (!curves.has_curve_with_type(CURVE_TYPE_BEZIER)) {
|
|
for (const int i : curves.points_range()) {
|
|
positions[i] = math::transform_point(object_to_layer, all_positions[index]);
|
|
radii[i] = all_radii[index];
|
|
index++;
|
|
}
|
|
}
|
|
else {
|
|
MutableSpan<float3> handle_positions_left = curves.handle_positions_left_for_write();
|
|
MutableSpan<float3> handle_positions_right = curves.handle_positions_right_for_write();
|
|
for (const int i : curves.points_range()) {
|
|
const int index_pos = index * 3;
|
|
handle_positions_left[i] = math::transform_point(object_to_layer,
|
|
all_positions[index_pos]);
|
|
positions[i] = math::transform_point(object_to_layer, all_positions[index_pos + 1]);
|
|
handle_positions_right[i] = math::transform_point(object_to_layer,
|
|
all_positions[index_pos + 2]);
|
|
radii[i] = all_radii[index];
|
|
index++;
|
|
}
|
|
}
|
|
|
|
curves.tag_radii_changed();
|
|
drawing.tag_positions_changed();
|
|
});
|
|
}
|
|
}
|
|
|
|
void BKE_grease_pencil_point_coords_apply_with_mat4(GreasePencil &grease_pencil,
|
|
blender::Span<blender::float3> all_positions,
|
|
blender::Span<float> all_radii,
|
|
const blender::float4x4 &mat)
|
|
{
|
|
using namespace blender;
|
|
const float scalef = mat4_to_scale(mat.ptr());
|
|
int64_t index = 0;
|
|
for (const int layer_i : grease_pencil.layers().index_range()) {
|
|
bke::greasepencil::Layer &layer = grease_pencil.layer(layer_i);
|
|
const float4x4 layer_to_object = layer.local_transform();
|
|
const float4x4 object_to_layer = math::invert(layer_to_object);
|
|
const Map<bke::greasepencil::FramesMapKeyT, GreasePencilFrame> frames = layer.frames();
|
|
frames.foreach_item([&](bke::greasepencil::FramesMapKeyT /*key*/, GreasePencilFrame frame) {
|
|
GreasePencilDrawingBase *base = grease_pencil.drawing(frame.drawing_index);
|
|
if (base->type != GP_DRAWING) {
|
|
return;
|
|
}
|
|
bke::greasepencil::Drawing &drawing = reinterpret_cast<GreasePencilDrawing *>(base)->wrap();
|
|
bke::CurvesGeometry &curves = drawing.strokes_for_write();
|
|
|
|
MutableSpan<float3> positions = curves.positions_for_write();
|
|
MutableSpan<float> radii = drawing.radii_for_write();
|
|
|
|
if (!curves.has_curve_with_type(CURVE_TYPE_BEZIER)) {
|
|
for (const int i : curves.points_range()) {
|
|
positions[i] = math::transform_point(object_to_layer * mat, all_positions[index]);
|
|
radii[i] = all_radii[index] * scalef;
|
|
index++;
|
|
}
|
|
}
|
|
else {
|
|
MutableSpan<float3> handle_positions_left = curves.handle_positions_left_for_write();
|
|
MutableSpan<float3> handle_positions_right = curves.handle_positions_right_for_write();
|
|
for (const int i : curves.points_range()) {
|
|
const int index_pos = index * 3;
|
|
handle_positions_left[i] = math::transform_point(object_to_layer * mat,
|
|
all_positions[index_pos]);
|
|
positions[i] = math::transform_point(object_to_layer * mat,
|
|
all_positions[index_pos + 1]);
|
|
handle_positions_right[i] = math::transform_point(object_to_layer * mat,
|
|
all_positions[index_pos + 2]);
|
|
radii[i] = all_radii[index] * scalef;
|
|
index++;
|
|
}
|
|
}
|
|
|
|
curves.tag_radii_changed();
|
|
drawing.tag_positions_changed();
|
|
});
|
|
}
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* ------------------------------------------------------------------- */
|
|
/** \name Grease Pencil material functions
|
|
* \{ */
|
|
|
|
int BKE_grease_pencil_object_material_index_get_by_name(Object *ob, const char *name)
|
|
{
|
|
short *totcol = BKE_object_material_len_p(ob);
|
|
Material *read_ma = nullptr;
|
|
for (short i = 0; i < *totcol; i++) {
|
|
read_ma = BKE_object_material_get(ob, i + 1);
|
|
if (STREQ(name, read_ma->id.name + 2)) {
|
|
return i;
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
Material *BKE_grease_pencil_object_material_new(Main *bmain,
|
|
Object *ob,
|
|
const char *name,
|
|
int *r_index)
|
|
{
|
|
Material *ma = BKE_gpencil_material_add(bmain, name);
|
|
id_us_min(&ma->id); /* no users yet */
|
|
|
|
BKE_object_material_slot_add(bmain, ob);
|
|
BKE_object_material_assign(bmain, ob, ma, ob->totcol, BKE_MAT_ASSIGN_USERPREF);
|
|
|
|
if (r_index) {
|
|
*r_index = ob->actcol - 1;
|
|
}
|
|
return ma;
|
|
}
|
|
|
|
Material *BKE_grease_pencil_object_material_from_brush_get(Object *ob, Brush *brush)
|
|
{
|
|
if (brush && brush->gpencil_settings &&
|
|
(brush->gpencil_settings->flag & GP_BRUSH_MATERIAL_PINNED))
|
|
{
|
|
return brush->gpencil_settings->material;
|
|
}
|
|
|
|
return BKE_object_material_get(ob, ob->actcol);
|
|
}
|
|
|
|
Material *BKE_grease_pencil_object_material_ensure_by_name(Main *bmain,
|
|
Object *ob,
|
|
const char *name,
|
|
int *r_index)
|
|
{
|
|
int index = BKE_grease_pencil_object_material_index_get_by_name(ob, name);
|
|
if (index != -1) {
|
|
*r_index = index;
|
|
return BKE_object_material_get(ob, index + 1);
|
|
}
|
|
return BKE_grease_pencil_object_material_new(bmain, ob, name, r_index);
|
|
}
|
|
|
|
static Material *grease_pencil_object_material_ensure_from_brush_pinned(Main *bmain,
|
|
Object *ob,
|
|
Brush *brush)
|
|
{
|
|
Material *ma = (brush->gpencil_settings) ? brush->gpencil_settings->material : nullptr;
|
|
|
|
if (ma) {
|
|
/* Ensure we assign a local datablock if this is an editable asset. */
|
|
ma = reinterpret_cast<Material *>(blender::bke::asset_edit_id_ensure_local(*bmain, ma->id));
|
|
}
|
|
|
|
/* check if the material is already on object material slots and add it if missing */
|
|
if (ma && BKE_object_material_index_get(ob, ma) < 0) {
|
|
/* The object's active material is what's used for the unpinned material. Do not touch it
|
|
* while using a pinned material. */
|
|
const bool change_active_material = false;
|
|
|
|
BKE_object_material_slot_add(bmain, ob, change_active_material);
|
|
BKE_object_material_assign(bmain, ob, ma, ob->totcol, BKE_MAT_ASSIGN_USERPREF);
|
|
}
|
|
|
|
return ma;
|
|
}
|
|
|
|
Material *BKE_grease_pencil_object_material_ensure_from_brush(Main *bmain,
|
|
Object *ob,
|
|
Brush *brush)
|
|
{
|
|
/* Use pinned material. */
|
|
if (brush && brush->gpencil_settings &&
|
|
(brush->gpencil_settings->flag & GP_BRUSH_MATERIAL_PINNED))
|
|
{
|
|
if (Material *ma = grease_pencil_object_material_ensure_from_brush_pinned(bmain, ob, brush)) {
|
|
return ma;
|
|
}
|
|
|
|
/* It is easier to just unpin a null material, instead of setting a new one. */
|
|
brush->gpencil_settings->flag &= ~GP_BRUSH_MATERIAL_PINNED;
|
|
}
|
|
|
|
/* Use the active material. */
|
|
if (Material *ma = BKE_object_material_get(ob, ob->actcol)) {
|
|
return ma;
|
|
}
|
|
|
|
/* Fall back to default material. */
|
|
/* XXX FIXME This is critical abuse of the 'default material' feature, these IDs should never be
|
|
* used/returned as 'regular' data. */
|
|
return BKE_material_default_gpencil();
|
|
}
|
|
|
|
Material *BKE_grease_pencil_object_material_alt_ensure_from_brush(Main *bmain,
|
|
Object *ob,
|
|
Brush *brush)
|
|
{
|
|
Material *material_alt = (brush->gpencil_settings) ? brush->gpencil_settings->material_alt :
|
|
nullptr;
|
|
if (material_alt) {
|
|
material_alt = reinterpret_cast<Material *>(
|
|
blender::bke::asset_edit_id_find_local(*bmain, material_alt->id));
|
|
if (material_alt && BKE_object_material_slot_find_index(ob, material_alt) != -1) {
|
|
return material_alt;
|
|
}
|
|
}
|
|
|
|
return BKE_grease_pencil_object_material_ensure_from_brush(bmain, ob, brush);
|
|
}
|
|
|
|
void BKE_grease_pencil_material_remap(GreasePencil *grease_pencil, const uint *remap, int totcol)
|
|
{
|
|
using namespace blender::bke;
|
|
|
|
for (GreasePencilDrawingBase *base : grease_pencil->drawings()) {
|
|
if (base->type != GP_DRAWING) {
|
|
continue;
|
|
}
|
|
greasepencil::Drawing &drawing = reinterpret_cast<GreasePencilDrawing *>(base)->wrap();
|
|
MutableAttributeAccessor attributes = drawing.strokes_for_write().attributes_for_write();
|
|
SpanAttributeWriter<int> material_indices = attributes.lookup_for_write_span<int>(
|
|
"material_index");
|
|
if (!material_indices) {
|
|
continue;
|
|
}
|
|
BLI_assert(material_indices.domain == AttrDomain::Curve);
|
|
for (const int i : material_indices.span.index_range()) {
|
|
BLI_assert(blender::IndexRange(totcol).contains(remap[material_indices.span[i]]));
|
|
UNUSED_VARS_NDEBUG(totcol);
|
|
material_indices.span[i] = remap[material_indices.span[i]];
|
|
}
|
|
material_indices.finish();
|
|
}
|
|
}
|
|
|
|
void BKE_grease_pencil_material_index_remove(GreasePencil *grease_pencil, const int index)
|
|
{
|
|
using namespace blender;
|
|
using namespace blender::bke;
|
|
|
|
for (GreasePencilDrawingBase *base : grease_pencil->drawings()) {
|
|
if (base->type != GP_DRAWING) {
|
|
continue;
|
|
}
|
|
greasepencil::Drawing &drawing = reinterpret_cast<GreasePencilDrawing *>(base)->wrap();
|
|
MutableAttributeAccessor attributes = drawing.strokes_for_write().attributes_for_write();
|
|
SpanAttributeWriter<int> material_indices = attributes.lookup_for_write_span<int>(
|
|
"material_index");
|
|
if (!material_indices) {
|
|
continue;
|
|
}
|
|
BLI_assert(material_indices.domain == AttrDomain::Curve);
|
|
for (const int i : material_indices.span.index_range()) {
|
|
if (material_indices.span[i] > 0 && material_indices.span[i] >= index) {
|
|
material_indices.span[i]--;
|
|
}
|
|
}
|
|
material_indices.finish();
|
|
}
|
|
}
|
|
|
|
bool BKE_grease_pencil_material_index_used(GreasePencil *grease_pencil, int index)
|
|
{
|
|
using namespace blender;
|
|
using namespace blender::bke;
|
|
|
|
for (GreasePencilDrawingBase *base : grease_pencil->drawings()) {
|
|
if (base->type != GP_DRAWING) {
|
|
continue;
|
|
}
|
|
greasepencil::Drawing &drawing = reinterpret_cast<GreasePencilDrawing *>(base)->wrap();
|
|
AttributeAccessor attributes = drawing.strokes().attributes();
|
|
const VArraySpan<int> material_indices = *attributes.lookup_or_default<int>(
|
|
"material_index", AttrDomain::Curve, 0);
|
|
|
|
if (material_indices.contains(index)) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* ------------------------------------------------------------------- */
|
|
/** \name Grease Pencil reference functions
|
|
* \{ */
|
|
|
|
static bool grease_pencil_references_cyclic_check_internal(const GreasePencil *id_reference,
|
|
const GreasePencil *grease_pencil)
|
|
{
|
|
for (const GreasePencilDrawingBase *base : grease_pencil->drawings()) {
|
|
if (base->type == GP_DRAWING_REFERENCE) {
|
|
const auto *reference = reinterpret_cast<const GreasePencilDrawingReference *>(base);
|
|
if (id_reference == reference->id_reference) {
|
|
return true;
|
|
}
|
|
|
|
if (grease_pencil_references_cyclic_check_internal(id_reference, reference->id_reference)) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool BKE_grease_pencil_references_cyclic_check(const GreasePencil *id_reference,
|
|
const GreasePencil *grease_pencil)
|
|
{
|
|
return grease_pencil_references_cyclic_check_internal(id_reference, grease_pencil);
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* ------------------------------------------------------------------- */
|
|
/** \name Draw Cache
|
|
* \{ */
|
|
|
|
void (*BKE_grease_pencil_batch_cache_dirty_tag_cb)(GreasePencil *grease_pencil,
|
|
int mode) = nullptr;
|
|
void (*BKE_grease_pencil_batch_cache_free_cb)(GreasePencil *grease_pencil) = nullptr;
|
|
|
|
void BKE_grease_pencil_batch_cache_dirty_tag(GreasePencil *grease_pencil, int mode)
|
|
{
|
|
if (grease_pencil->runtime && grease_pencil->runtime->batch_cache) {
|
|
BKE_grease_pencil_batch_cache_dirty_tag_cb(grease_pencil, mode);
|
|
}
|
|
}
|
|
|
|
void BKE_grease_pencil_batch_cache_free(GreasePencil *grease_pencil)
|
|
{
|
|
if (grease_pencil->runtime && grease_pencil->runtime->batch_cache) {
|
|
BKE_grease_pencil_batch_cache_free_cb(grease_pencil);
|
|
}
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* ------------------------------------------------------------------- */
|
|
/** \name Grease Pencil data-block API
|
|
* \{ */
|
|
|
|
template<typename T> static void grow_array(T **array, int *num, const int add_num)
|
|
{
|
|
BLI_assert(add_num > 0);
|
|
const int new_array_num = *num + add_num;
|
|
T *new_array = MEM_calloc_arrayN<T>(new_array_num, __func__);
|
|
|
|
blender::uninitialized_relocate_n(*array, *num, new_array);
|
|
if (*array != nullptr) {
|
|
MEM_freeN(*array);
|
|
}
|
|
|
|
*array = new_array;
|
|
*num = new_array_num;
|
|
}
|
|
template<typename T> static void shrink_array(T **array, int *num, const int shrink_num)
|
|
{
|
|
BLI_assert(shrink_num > 0);
|
|
const int new_array_num = *num - shrink_num;
|
|
if (new_array_num == 0) {
|
|
MEM_freeN(*array);
|
|
*array = nullptr;
|
|
*num = 0;
|
|
return;
|
|
}
|
|
|
|
T *new_array = MEM_calloc_arrayN<T>(new_array_num, __func__);
|
|
|
|
blender::uninitialized_move_n(*array, new_array_num, new_array);
|
|
MEM_freeN(*array);
|
|
|
|
*array = new_array;
|
|
*num = new_array_num;
|
|
}
|
|
|
|
blender::Span<const GreasePencilDrawingBase *> GreasePencil::drawings() const
|
|
{
|
|
return blender::Span<GreasePencilDrawingBase *>{this->drawing_array, this->drawing_array_num};
|
|
}
|
|
|
|
blender::MutableSpan<GreasePencilDrawingBase *> GreasePencil::drawings()
|
|
{
|
|
return blender::MutableSpan<GreasePencilDrawingBase *>{this->drawing_array,
|
|
this->drawing_array_num};
|
|
}
|
|
|
|
static void delete_drawing(GreasePencilDrawingBase *drawing_base)
|
|
{
|
|
switch (GreasePencilDrawingType(drawing_base->type)) {
|
|
case GP_DRAWING: {
|
|
GreasePencilDrawing *drawing = reinterpret_cast<GreasePencilDrawing *>(drawing_base);
|
|
MEM_delete(&drawing->wrap());
|
|
break;
|
|
}
|
|
case GP_DRAWING_REFERENCE: {
|
|
GreasePencilDrawingReference *drawing_reference =
|
|
reinterpret_cast<GreasePencilDrawingReference *>(drawing_base);
|
|
MEM_delete(&drawing_reference->wrap());
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void GreasePencil::resize_drawings(const int new_num)
|
|
{
|
|
using namespace blender;
|
|
BLI_assert(new_num >= 0);
|
|
|
|
const int prev_num = int(this->drawings().size());
|
|
if (new_num == prev_num) {
|
|
return;
|
|
}
|
|
if (new_num > prev_num) {
|
|
const int add_num = new_num - prev_num;
|
|
grow_array<GreasePencilDrawingBase *>(&this->drawing_array, &this->drawing_array_num, add_num);
|
|
}
|
|
else { /* if (new_num < prev_num) */
|
|
const int shrink_num = prev_num - new_num;
|
|
MutableSpan<GreasePencilDrawingBase *> old_drawings = this->drawings().drop_front(new_num);
|
|
for (const int64_t i : old_drawings.index_range()) {
|
|
if (GreasePencilDrawingBase *drawing_base = old_drawings[i]) {
|
|
delete_drawing(drawing_base);
|
|
}
|
|
}
|
|
shrink_array<GreasePencilDrawingBase *>(
|
|
&this->drawing_array, &this->drawing_array_num, shrink_num);
|
|
}
|
|
}
|
|
|
|
void GreasePencil::add_empty_drawings(const int add_num)
|
|
{
|
|
using namespace blender;
|
|
BLI_assert(add_num > 0);
|
|
const int prev_num = this->drawings().size();
|
|
grow_array<GreasePencilDrawingBase *>(&this->drawing_array, &this->drawing_array_num, add_num);
|
|
MutableSpan<GreasePencilDrawingBase *> new_drawings = this->drawings().drop_front(prev_num);
|
|
for (const int i : new_drawings.index_range()) {
|
|
new_drawings[i] = reinterpret_cast<GreasePencilDrawingBase *>(
|
|
MEM_new<blender::bke::greasepencil::Drawing>(__func__));
|
|
}
|
|
}
|
|
|
|
void GreasePencil::add_duplicate_drawings(const int duplicate_num,
|
|
const blender::bke::greasepencil::Drawing &drawing)
|
|
{
|
|
using namespace blender;
|
|
BLI_assert(duplicate_num > 0);
|
|
const int prev_num = this->drawings().size();
|
|
grow_array<GreasePencilDrawingBase *>(
|
|
&this->drawing_array, &this->drawing_array_num, duplicate_num);
|
|
MutableSpan<GreasePencilDrawingBase *> new_drawings = this->drawings().drop_front(prev_num);
|
|
for (const int i : new_drawings.index_range()) {
|
|
new_drawings[i] = reinterpret_cast<GreasePencilDrawingBase *>(
|
|
MEM_new<bke::greasepencil::Drawing>(__func__, drawing));
|
|
}
|
|
}
|
|
|
|
blender::bke::greasepencil::Drawing *GreasePencil::insert_frame(
|
|
blender::bke::greasepencil::Layer &layer,
|
|
const int frame_number,
|
|
const int duration,
|
|
const eBezTriple_KeyframeType keytype)
|
|
{
|
|
using namespace blender;
|
|
GreasePencilFrame *frame = layer.add_frame(frame_number, duration);
|
|
if (frame == nullptr) {
|
|
return nullptr;
|
|
}
|
|
this->add_empty_drawings(1);
|
|
frame->drawing_index = this->drawings().index_range().last();
|
|
frame->type = int8_t(keytype);
|
|
|
|
GreasePencilDrawingBase *drawing_base = this->drawings().last();
|
|
BLI_assert(drawing_base->type == GP_DRAWING);
|
|
GreasePencilDrawing *drawing = reinterpret_cast<GreasePencilDrawing *>(drawing_base);
|
|
return &drawing->wrap();
|
|
}
|
|
|
|
void GreasePencil::insert_frames(Span<blender::bke::greasepencil::Layer *> layers,
|
|
const int frame_number,
|
|
const int duration,
|
|
const eBezTriple_KeyframeType keytype)
|
|
{
|
|
using namespace blender;
|
|
if (layers.is_empty()) {
|
|
return;
|
|
}
|
|
Vector<GreasePencilFrame *> frames;
|
|
frames.reserve(layers.size());
|
|
for (bke::greasepencil::Layer *layer : layers) {
|
|
BLI_assert(layer != nullptr);
|
|
GreasePencilFrame *frame = layer->add_frame(frame_number, duration);
|
|
if (frame != nullptr) {
|
|
frames.append(frame);
|
|
}
|
|
}
|
|
|
|
if (frames.is_empty()) {
|
|
return;
|
|
}
|
|
|
|
this->add_empty_drawings(frames.size());
|
|
const IndexRange new_drawings = this->drawings().index_range().take_back(frames.size());
|
|
for (const int frame_i : frames.index_range()) {
|
|
GreasePencilFrame *frame = frames[frame_i];
|
|
frame->drawing_index = new_drawings[frame_i];
|
|
frame->type = int8_t(keytype);
|
|
}
|
|
}
|
|
|
|
bool GreasePencil::insert_duplicate_frame(blender::bke::greasepencil::Layer &layer,
|
|
const int src_frame_number,
|
|
const int dst_frame_number,
|
|
const bool do_instance)
|
|
{
|
|
using namespace blender::bke::greasepencil;
|
|
|
|
if (!layer.frames().contains(src_frame_number)) {
|
|
return false;
|
|
}
|
|
|
|
if (layer.is_locked()) {
|
|
return false;
|
|
}
|
|
const GreasePencilFrame src_frame = layer.frames().lookup(src_frame_number);
|
|
|
|
/* Create the new frame structure, with the same duration.
|
|
* If we want to make an instance of the source frame, the drawing index gets copied from the
|
|
* source frame. Otherwise, we set the drawing index to the size of the drawings array, since we
|
|
* are going to add a new drawing copied from the source drawing. */
|
|
const int duration = layer.get_frame_duration_at(src_frame_number);
|
|
GreasePencilFrame *dst_frame = layer.add_frame(dst_frame_number, duration);
|
|
if (dst_frame == nullptr) {
|
|
return false;
|
|
}
|
|
dst_frame->drawing_index = do_instance ? src_frame.drawing_index : int(this->drawings().size());
|
|
dst_frame->type = src_frame.type;
|
|
|
|
const GreasePencilDrawingBase *src_drawing_base = this->drawing(src_frame.drawing_index);
|
|
switch (src_drawing_base->type) {
|
|
case GP_DRAWING: {
|
|
const Drawing &src_drawing =
|
|
reinterpret_cast<const GreasePencilDrawing *>(src_drawing_base)->wrap();
|
|
if (do_instance) {
|
|
/* Adds the duplicate frame as a new instance of the same drawing. We thus increase the
|
|
* user count of the corresponding drawing. */
|
|
src_drawing.add_user();
|
|
}
|
|
else {
|
|
/* Create a copy of the drawing, and add it at the end of the drawings array.
|
|
* Note that the frame already points to this new drawing, as the drawing index was set to
|
|
* `int(this->drawings().size())`. */
|
|
this->add_duplicate_drawings(1, src_drawing);
|
|
}
|
|
break;
|
|
}
|
|
case GP_DRAWING_REFERENCE:
|
|
/* TODO: Duplicate drawing references is not yet implemented.
|
|
* For now, just remove the frame that we inserted. */
|
|
layer.remove_frame(dst_frame_number);
|
|
return false;
|
|
}
|
|
#ifndef NDEBUG
|
|
this->validate_drawing_user_counts();
|
|
#endif
|
|
return true;
|
|
}
|
|
|
|
bool GreasePencil::remove_frames(blender::bke::greasepencil::Layer &layer,
|
|
blender::Span<int> frame_numbers)
|
|
{
|
|
using namespace blender::bke::greasepencil;
|
|
bool removed_any_drawing_user = false;
|
|
for (const int frame_number : frame_numbers) {
|
|
if (!layer.frames().contains(frame_number)) {
|
|
continue;
|
|
}
|
|
const GreasePencilFrame frame_to_remove = layer.frames().lookup(frame_number);
|
|
const int64_t drawing_index_to_remove = frame_to_remove.drawing_index;
|
|
if (!layer.remove_frame(frame_number)) {
|
|
/* If removing the frame was not successful, continue. */
|
|
continue;
|
|
}
|
|
if (frame_to_remove.is_end()) {
|
|
/* End frames don't reference a drawing, continue. */
|
|
continue;
|
|
}
|
|
GreasePencilDrawingBase *drawing_base = this->drawing(drawing_index_to_remove);
|
|
if (drawing_base->type != GP_DRAWING) {
|
|
/* If the drawing is referenced from another object, we don't track it's users because we
|
|
* cannot delete drawings from another object. */
|
|
continue;
|
|
}
|
|
Drawing &drawing = reinterpret_cast<GreasePencilDrawing *>(drawing_base)->wrap();
|
|
drawing.remove_user();
|
|
removed_any_drawing_user = true;
|
|
}
|
|
if (removed_any_drawing_user) {
|
|
this->remove_drawings_with_no_users();
|
|
return true;
|
|
}
|
|
#ifndef NDEBUG
|
|
else {
|
|
this->validate_drawing_user_counts();
|
|
}
|
|
#endif
|
|
return false;
|
|
}
|
|
|
|
void GreasePencil::copy_frames_from_layer(blender::bke::greasepencil::Layer &dst_layer,
|
|
const GreasePencil &src_grease_pencil,
|
|
const blender::bke::greasepencil::Layer &src_layer,
|
|
const std::optional<int> frame_select)
|
|
{
|
|
using namespace blender;
|
|
|
|
const Span<const GreasePencilDrawingBase *> src_drawings = src_grease_pencil.drawings();
|
|
Array<int> drawing_index_map(src_grease_pencil.drawing_array_num, -1);
|
|
|
|
for (auto [frame_number, src_frame] : src_layer.frames().items()) {
|
|
if (frame_select && *frame_select != frame_number) {
|
|
continue;
|
|
}
|
|
|
|
const int src_drawing_index = src_frame.drawing_index;
|
|
int dst_drawing_index = drawing_index_map[src_drawing_index];
|
|
if (dst_drawing_index < 0) {
|
|
switch (src_drawings[src_drawing_index]->type) {
|
|
case GP_DRAWING: {
|
|
const bke::greasepencil::Drawing &src_drawing =
|
|
reinterpret_cast<const GreasePencilDrawing *>(src_drawings[src_drawing_index])
|
|
->wrap();
|
|
this->add_duplicate_drawings(1, src_drawing);
|
|
break;
|
|
}
|
|
case GP_DRAWING_REFERENCE:
|
|
/* Dummy drawing to keep frame reference valid. */
|
|
this->add_empty_drawings(1);
|
|
break;
|
|
}
|
|
dst_drawing_index = this->drawings().size() - 1;
|
|
drawing_index_map[src_drawing_index] = dst_drawing_index;
|
|
}
|
|
BLI_assert(this->drawings().index_range().contains(dst_drawing_index));
|
|
|
|
GreasePencilFrame *dst_frame = dst_layer.add_frame(frame_number);
|
|
dst_frame->flag = src_frame.flag;
|
|
dst_frame->drawing_index = dst_drawing_index;
|
|
}
|
|
}
|
|
|
|
void GreasePencil::add_layers_with_empty_drawings_for_eval(const int num)
|
|
{
|
|
using namespace blender;
|
|
using namespace blender::bke::greasepencil;
|
|
const int old_drawings_num = this->drawing_array_num;
|
|
const int old_layers_num = this->layers().size();
|
|
this->add_empty_drawings(num);
|
|
this->add_layers_for_eval(num);
|
|
threading::parallel_for(IndexRange(num), 256, [&](const IndexRange range) {
|
|
for (const int i : range) {
|
|
const int new_drawing_i = old_drawings_num + i;
|
|
const int new_layer_i = old_layers_num + i;
|
|
Layer &layer = this->layer(new_layer_i);
|
|
GreasePencilFrame *frame = layer.add_frame(this->runtime->eval_frame);
|
|
BLI_assert(frame);
|
|
frame->drawing_index = new_drawing_i;
|
|
}
|
|
});
|
|
}
|
|
|
|
void GreasePencil::remove_drawings_with_no_users()
|
|
{
|
|
using namespace blender;
|
|
using namespace blender::bke::greasepencil;
|
|
|
|
/* Compress the drawings array by finding unused drawings.
|
|
* In every step two indices are found:
|
|
* - The next unused drawing from the start
|
|
* - The last used drawing from the end
|
|
* These two drawings are then swapped. Rinse and repeat until both iterators meet somewhere in
|
|
* the middle. At this point the drawings array is fully compressed.
|
|
* Then the drawing indices in frame data are remapped. */
|
|
|
|
const MutableSpan<GreasePencilDrawingBase *> drawings = this->drawings();
|
|
if (drawings.is_empty()) {
|
|
return;
|
|
}
|
|
|
|
auto is_drawing_used = [&](const int drawing_index) {
|
|
GreasePencilDrawingBase *drawing_base = drawings[drawing_index];
|
|
/* NOTE: GreasePencilDrawingReference does not have a user count currently, but should
|
|
* eventually be counted like GreasePencilDrawing. */
|
|
if (drawing_base->type != GP_DRAWING) {
|
|
return false;
|
|
}
|
|
GreasePencilDrawing *drawing = reinterpret_cast<GreasePencilDrawing *>(drawing_base);
|
|
return drawing->wrap().has_users() || drawing->runtime->fake_user;
|
|
};
|
|
|
|
/* Index map to remap drawing indices in frame data.
|
|
* Index -1 indicates that the drawing has not been moved. */
|
|
constexpr const int unchanged_index = -1;
|
|
Array<int> drawing_index_map(drawings.size(), unchanged_index);
|
|
|
|
int first_unused_drawing = -1;
|
|
int last_used_drawing = drawings.size() - 1;
|
|
/* Advance head and tail iterators to the next unused/used drawing respectively.
|
|
* Returns true if an index pair was found that needs to be swapped. */
|
|
auto find_next_swap_index = [&]() -> bool {
|
|
do {
|
|
++first_unused_drawing;
|
|
} while (first_unused_drawing <= last_used_drawing && is_drawing_used(first_unused_drawing));
|
|
while (last_used_drawing >= 0 && !is_drawing_used(last_used_drawing)) {
|
|
--last_used_drawing;
|
|
}
|
|
|
|
return first_unused_drawing < last_used_drawing;
|
|
};
|
|
|
|
while (find_next_swap_index()) {
|
|
/* Found two valid iterators, now swap drawings. */
|
|
std::swap(drawings[first_unused_drawing], drawings[last_used_drawing]);
|
|
drawing_index_map[last_used_drawing] = first_unused_drawing;
|
|
}
|
|
|
|
/* `last_used_drawing` is expected to be exactly the item before the first unused drawing, once
|
|
* the loop above is fully done and all unused drawings are supposed to be at the end of the
|
|
* array. */
|
|
BLI_assert(last_used_drawing == first_unused_drawing - 1);
|
|
#ifndef NDEBUG
|
|
for (const int i : drawings.index_range()) {
|
|
if (i < first_unused_drawing) {
|
|
BLI_assert(is_drawing_used(i));
|
|
}
|
|
else {
|
|
BLI_assert(!is_drawing_used(i));
|
|
}
|
|
}
|
|
#endif
|
|
|
|
/* Tail range of unused drawings that can be removed. */
|
|
const IndexRange drawings_to_remove = (first_unused_drawing > 0) ?
|
|
drawings.index_range().drop_front(
|
|
first_unused_drawing) :
|
|
drawings.index_range();
|
|
if (drawings_to_remove.is_empty()) {
|
|
return;
|
|
}
|
|
|
|
/* Free the unused drawings. */
|
|
for (const int i : drawings_to_remove) {
|
|
GreasePencilDrawingBase *unused_drawing_base = drawings[i];
|
|
switch (unused_drawing_base->type) {
|
|
case GP_DRAWING: {
|
|
auto *unused_drawing = reinterpret_cast<GreasePencilDrawing *>(unused_drawing_base);
|
|
MEM_delete(&unused_drawing->wrap());
|
|
break;
|
|
}
|
|
case GP_DRAWING_REFERENCE: {
|
|
auto *unused_drawing_ref = reinterpret_cast<GreasePencilDrawingReference *>(
|
|
unused_drawing_base);
|
|
MEM_delete(&unused_drawing_ref->wrap());
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
shrink_array<GreasePencilDrawingBase *>(
|
|
&this->drawing_array, &this->drawing_array_num, drawings_to_remove.size());
|
|
|
|
/* Remap drawing indices in frame data. */
|
|
for (Layer *layer : this->layers_for_write()) {
|
|
for (auto [key, value] : layer->frames_for_write().items()) {
|
|
const int new_drawing_index = drawing_index_map[value.drawing_index];
|
|
if (new_drawing_index != unchanged_index) {
|
|
value.drawing_index = new_drawing_index;
|
|
layer->tag_frames_map_changed();
|
|
}
|
|
}
|
|
}
|
|
|
|
#ifndef NDEBUG
|
|
this->validate_drawing_user_counts();
|
|
#endif
|
|
}
|
|
|
|
void GreasePencil::update_drawing_users_for_layer(const blender::bke::greasepencil::Layer &layer)
|
|
{
|
|
using namespace blender;
|
|
for (const auto &[key, value] : layer.frames().items()) {
|
|
BLI_assert(this->drawings().index_range().contains(value.drawing_index));
|
|
GreasePencilDrawingBase *drawing_base = this->drawing(value.drawing_index);
|
|
if (drawing_base->type != GP_DRAWING) {
|
|
continue;
|
|
}
|
|
bke::greasepencil::Drawing &drawing =
|
|
reinterpret_cast<GreasePencilDrawing *>(drawing_base)->wrap();
|
|
if (!drawing.has_users()) {
|
|
drawing.add_user();
|
|
}
|
|
}
|
|
|
|
#ifndef NDEBUG
|
|
this->validate_drawing_user_counts();
|
|
#endif
|
|
}
|
|
|
|
void GreasePencil::move_frames(blender::bke::greasepencil::Layer &layer,
|
|
const blender::Map<int, int> &frame_number_destinations)
|
|
{
|
|
this->move_duplicate_frames(
|
|
layer, frame_number_destinations, blender::Map<int, GreasePencilFrame>());
|
|
}
|
|
|
|
void GreasePencil::move_duplicate_frames(
|
|
blender::bke::greasepencil::Layer &layer,
|
|
const blender::Map<int, int> &frame_number_destinations,
|
|
const blender::Map<int, GreasePencilFrame> &duplicate_frames)
|
|
{
|
|
using namespace blender;
|
|
Map<int, GreasePencilFrame> layer_frames_copy = layer.frames();
|
|
|
|
/* Copy frames durations. */
|
|
Map<int, int> src_layer_frames_durations;
|
|
for (const auto [frame_number, frame] : layer.frames().items()) {
|
|
src_layer_frames_durations.add(frame_number, layer.get_frame_duration_at(frame_number));
|
|
}
|
|
|
|
/* Remove original frames for duplicates before inserting any frames.
|
|
* This has to be done early to avoid removing frames that may be inserted
|
|
* in place of the source frames. */
|
|
for (const auto src_frame_number : frame_number_destinations.keys()) {
|
|
if (!duplicate_frames.contains(src_frame_number)) {
|
|
/* User count not decremented here, the same frame is inserted again later. */
|
|
layer.remove_frame(src_frame_number);
|
|
}
|
|
}
|
|
|
|
auto get_source_frame = [&](const int frame_number) -> const GreasePencilFrame * {
|
|
if (const GreasePencilFrame *ptr = duplicate_frames.lookup_ptr(frame_number)) {
|
|
return ptr;
|
|
}
|
|
return layer_frames_copy.lookup_ptr(frame_number);
|
|
};
|
|
|
|
for (const auto [src_frame_number, dst_frame_number] : frame_number_destinations.items()) {
|
|
const GreasePencilFrame *src_frame = get_source_frame(src_frame_number);
|
|
if (!src_frame) {
|
|
continue;
|
|
}
|
|
const int duration = src_layer_frames_durations.lookup_default(src_frame_number, 0);
|
|
|
|
/* Add and overwrite the frame at the destination number. */
|
|
if (layer.frames().contains(dst_frame_number)) {
|
|
GreasePencilFrame frame_to_overwrite = layer.frames().lookup(dst_frame_number);
|
|
GreasePencilDrawingBase *drawing_base = this->drawing(frame_to_overwrite.drawing_index);
|
|
if (drawing_base->type == GP_DRAWING) {
|
|
reinterpret_cast<GreasePencilDrawing *>(drawing_base)->wrap().remove_user();
|
|
}
|
|
layer.remove_frame(dst_frame_number);
|
|
}
|
|
GreasePencilFrame *frame = layer.add_frame(dst_frame_number, duration);
|
|
*frame = *src_frame;
|
|
}
|
|
|
|
/* Remove drawings if they no longer have users. */
|
|
this->remove_drawings_with_no_users();
|
|
}
|
|
|
|
const blender::bke::greasepencil::Drawing *GreasePencil::get_drawing_at(
|
|
const blender::bke::greasepencil::Layer &layer, const int frame_number) const
|
|
{
|
|
if (this->drawings().is_empty()) {
|
|
return nullptr;
|
|
}
|
|
const int drawing_index = layer.drawing_index_at(frame_number);
|
|
if (drawing_index == -1) {
|
|
/* No drawing found. */
|
|
return nullptr;
|
|
}
|
|
const GreasePencilDrawingBase *drawing_base = this->drawing(drawing_index);
|
|
if (drawing_base->type != GP_DRAWING) {
|
|
/* TODO: Get reference drawing. */
|
|
return nullptr;
|
|
}
|
|
const GreasePencilDrawing *drawing = reinterpret_cast<const GreasePencilDrawing *>(drawing_base);
|
|
return &drawing->wrap();
|
|
}
|
|
|
|
blender::bke::greasepencil::Drawing *GreasePencil::get_drawing_at(
|
|
const blender::bke::greasepencil::Layer &layer, const int frame_number)
|
|
{
|
|
if (this->drawings().is_empty()) {
|
|
return nullptr;
|
|
}
|
|
const int drawing_index = layer.drawing_index_at(frame_number);
|
|
if (drawing_index == -1) {
|
|
/* No drawing found. */
|
|
return nullptr;
|
|
}
|
|
GreasePencilDrawingBase *drawing_base = this->drawing(drawing_index);
|
|
if (drawing_base->type != GP_DRAWING) {
|
|
/* TODO: Get reference drawing. */
|
|
return nullptr;
|
|
}
|
|
GreasePencilDrawing *drawing = reinterpret_cast<GreasePencilDrawing *>(drawing_base);
|
|
return &drawing->wrap();
|
|
}
|
|
|
|
blender::bke::greasepencil::Drawing *GreasePencil::get_editable_drawing_at(
|
|
const blender::bke::greasepencil::Layer &layer, const int frame_number)
|
|
{
|
|
if (!layer.is_editable()) {
|
|
return nullptr;
|
|
}
|
|
if (this->drawings().is_empty()) {
|
|
return nullptr;
|
|
}
|
|
const int drawing_index = layer.drawing_index_at(frame_number);
|
|
if (drawing_index == -1) {
|
|
/* No drawing found. */
|
|
return nullptr;
|
|
}
|
|
GreasePencilDrawingBase *drawing_base = this->drawing(drawing_index);
|
|
if (drawing_base->type != GP_DRAWING) {
|
|
/* Drawing references are not editable. */
|
|
return nullptr;
|
|
}
|
|
GreasePencilDrawing *drawing = reinterpret_cast<GreasePencilDrawing *>(drawing_base);
|
|
return &drawing->wrap();
|
|
}
|
|
|
|
const blender::bke::greasepencil::Drawing *GreasePencil::get_eval_drawing(
|
|
const blender::bke::greasepencil::Layer &layer) const
|
|
{
|
|
return this->get_drawing_at(layer, this->runtime->eval_frame);
|
|
}
|
|
|
|
blender::bke::greasepencil::Drawing *GreasePencil::get_eval_drawing(
|
|
const blender::bke::greasepencil::Layer &layer)
|
|
{
|
|
return this->get_drawing_at(layer, this->runtime->eval_frame);
|
|
}
|
|
|
|
std::optional<blender::Bounds<blender::float3>> GreasePencil::bounds_min_max(
|
|
const int frame, const bool use_radius) const
|
|
{
|
|
using namespace blender;
|
|
std::optional<Bounds<float3>> bounds;
|
|
const Span<const bke::greasepencil::Layer *> layers = this->layers();
|
|
for (const int layer_i : layers.index_range()) {
|
|
const bke::greasepencil::Layer &layer = *layers[layer_i];
|
|
const float4x4 layer_to_object = layer.local_transform();
|
|
if (!layer.is_visible()) {
|
|
continue;
|
|
}
|
|
const bke::greasepencil::Drawing *drawing = this->get_drawing_at(layer, frame);
|
|
if (!drawing) {
|
|
continue;
|
|
}
|
|
const bke::CurvesGeometry &curves = drawing->strokes();
|
|
if (curves.is_empty()) {
|
|
continue;
|
|
}
|
|
if (layer_to_object == float4x4::identity()) {
|
|
bounds = bounds::merge(bounds, curves.bounds_min_max(use_radius));
|
|
continue;
|
|
}
|
|
const VArray<float> radius = curves.radius();
|
|
Array<float3> positions_world(curves.evaluated_points_num());
|
|
math::transform_points(curves.evaluated_positions(), layer_to_object, positions_world);
|
|
if (!use_radius) {
|
|
const Bounds<float3> drawing_bounds = *bounds::min_max(positions_world.as_span());
|
|
bounds = bounds::merge(bounds, drawing_bounds);
|
|
continue;
|
|
}
|
|
if (const std::optional radius_single = radius.get_if_single()) {
|
|
Bounds<float3> drawing_bounds = *curves.bounds_min_max(false);
|
|
drawing_bounds.pad(*radius_single);
|
|
bounds = bounds::merge(bounds, drawing_bounds);
|
|
continue;
|
|
}
|
|
const Span radius_span = radius.get_internal_span();
|
|
if (curves.is_single_type(CURVE_TYPE_POLY)) {
|
|
const Bounds<float3> drawing_bounds = *bounds::min_max_with_radii(positions_world.as_span(),
|
|
radius_span);
|
|
bounds = bounds::merge(bounds, drawing_bounds);
|
|
continue;
|
|
}
|
|
curves.ensure_can_interpolate_to_evaluated();
|
|
Array<float> radii_eval(curves.evaluated_points_num());
|
|
curves.interpolate_to_evaluated(radius_span, radii_eval.as_mutable_span());
|
|
const Bounds<float3> drawing_bounds = *bounds::min_max_with_radii(positions_world.as_span(),
|
|
radii_eval.as_span());
|
|
bounds = bounds::merge(bounds, drawing_bounds);
|
|
}
|
|
return bounds;
|
|
}
|
|
|
|
std::optional<blender::Bounds<blender::float3>> GreasePencil::bounds_min_max_eval(
|
|
const bool use_radius) const
|
|
{
|
|
return this->bounds_min_max(this->runtime->eval_frame, use_radius);
|
|
}
|
|
|
|
void GreasePencil::count_memory(blender::MemoryCounter &memory) const
|
|
{
|
|
using namespace blender::bke;
|
|
for (const GreasePencilDrawingBase *base : this->drawings()) {
|
|
if (base->type != GP_DRAWING) {
|
|
continue;
|
|
}
|
|
const greasepencil::Drawing &drawing =
|
|
reinterpret_cast<const GreasePencilDrawing *>(base)->wrap();
|
|
drawing.strokes().count_memory(memory);
|
|
}
|
|
}
|
|
|
|
std::optional<int> GreasePencil::material_index_max_eval() const
|
|
{
|
|
using namespace blender;
|
|
using namespace blender::bke;
|
|
std::optional<int> max_index;
|
|
for (const greasepencil::Layer *layer : this->layers()) {
|
|
if (const greasepencil::Drawing *drawing = this->get_eval_drawing(*layer)) {
|
|
const bke::CurvesGeometry &curves = drawing->strokes();
|
|
const std::optional<int> max_index_on_layer = curves.material_index_max();
|
|
if (max_index) {
|
|
if (max_index_on_layer) {
|
|
max_index = std::max(*max_index, *max_index_on_layer);
|
|
}
|
|
}
|
|
else {
|
|
max_index = max_index_on_layer;
|
|
}
|
|
}
|
|
}
|
|
return max_index;
|
|
}
|
|
|
|
blender::Span<const blender::bke::greasepencil::Layer *> GreasePencil::layers() const
|
|
{
|
|
BLI_assert(this->runtime != nullptr);
|
|
return this->root_group().layers();
|
|
}
|
|
|
|
blender::Span<blender::bke::greasepencil::Layer *> GreasePencil::layers_for_write()
|
|
{
|
|
BLI_assert(this->runtime != nullptr);
|
|
return this->root_group().layers_for_write();
|
|
}
|
|
|
|
blender::Span<const blender::bke::greasepencil::LayerGroup *> GreasePencil::layer_groups() const
|
|
{
|
|
BLI_assert(this->runtime != nullptr);
|
|
return this->root_group().groups();
|
|
}
|
|
|
|
blender::Span<blender::bke::greasepencil::LayerGroup *> GreasePencil::layer_groups_for_write()
|
|
{
|
|
BLI_assert(this->runtime != nullptr);
|
|
return this->root_group().groups_for_write();
|
|
}
|
|
|
|
blender::Span<const blender::bke::greasepencil::TreeNode *> GreasePencil::nodes() const
|
|
{
|
|
BLI_assert(this->runtime != nullptr);
|
|
return this->root_group().nodes();
|
|
}
|
|
|
|
blender::Span<blender::bke::greasepencil::TreeNode *> GreasePencil::nodes_for_write()
|
|
{
|
|
BLI_assert(this->runtime != nullptr);
|
|
return this->root_group().nodes_for_write();
|
|
}
|
|
|
|
std::optional<int> GreasePencil::get_layer_index(
|
|
const blender::bke::greasepencil::Layer &layer) const
|
|
{
|
|
const int index = int(this->layers().first_index_try(&layer));
|
|
if (index == -1) {
|
|
return {};
|
|
}
|
|
return index;
|
|
}
|
|
|
|
const blender::bke::greasepencil::Layer *GreasePencil::get_active_layer() const
|
|
{
|
|
if (this->active_node == nullptr) {
|
|
return nullptr;
|
|
}
|
|
const blender::bke::greasepencil::TreeNode &active_node = *this->get_active_node();
|
|
if (!active_node.is_layer()) {
|
|
return nullptr;
|
|
}
|
|
return &active_node.as_layer();
|
|
}
|
|
|
|
blender::bke::greasepencil::Layer *GreasePencil::get_active_layer()
|
|
{
|
|
if (this->active_node == nullptr) {
|
|
return nullptr;
|
|
}
|
|
blender::bke::greasepencil::TreeNode &active_node = *this->get_active_node();
|
|
if (!active_node.is_layer()) {
|
|
return nullptr;
|
|
}
|
|
return &active_node.as_layer();
|
|
}
|
|
|
|
void GreasePencil::set_active_layer(blender::bke::greasepencil::Layer *layer)
|
|
{
|
|
this->active_node = reinterpret_cast<GreasePencilLayerTreeNode *>(&layer->as_node());
|
|
|
|
if (this->flag & GREASE_PENCIL_AUTOLOCK_LAYERS) {
|
|
this->autolock_inactive_layers();
|
|
}
|
|
}
|
|
|
|
bool GreasePencil::is_layer_active(const blender::bke::greasepencil::Layer *layer) const
|
|
{
|
|
if (layer == nullptr) {
|
|
return false;
|
|
}
|
|
return this->get_active_layer() == layer;
|
|
}
|
|
|
|
void GreasePencil::autolock_inactive_layers()
|
|
{
|
|
using namespace blender::bke::greasepencil;
|
|
|
|
for (Layer *layer : this->layers_for_write()) {
|
|
if (this->is_layer_active(layer)) {
|
|
layer->set_locked(false);
|
|
continue;
|
|
}
|
|
layer->set_locked(true);
|
|
}
|
|
}
|
|
|
|
const blender::bke::greasepencil::LayerGroup *GreasePencil::get_active_group() const
|
|
{
|
|
if (this->active_node == nullptr) {
|
|
return nullptr;
|
|
}
|
|
const blender::bke::greasepencil::TreeNode &active_node = *this->get_active_node();
|
|
if (!active_node.is_group()) {
|
|
return nullptr;
|
|
}
|
|
return &active_node.as_group();
|
|
}
|
|
|
|
blender::bke::greasepencil::LayerGroup *GreasePencil::get_active_group()
|
|
{
|
|
if (this->active_node == nullptr) {
|
|
return nullptr;
|
|
}
|
|
blender::bke::greasepencil::TreeNode &active_node = *this->get_active_node();
|
|
if (!active_node.is_group()) {
|
|
return nullptr;
|
|
}
|
|
return &active_node.as_group();
|
|
}
|
|
|
|
const blender::bke::greasepencil::TreeNode *GreasePencil::get_active_node() const
|
|
{
|
|
if (this->active_node == nullptr) {
|
|
return nullptr;
|
|
}
|
|
return &this->active_node->wrap();
|
|
}
|
|
|
|
blender::bke::greasepencil::TreeNode *GreasePencil::get_active_node()
|
|
{
|
|
if (this->active_node == nullptr) {
|
|
return nullptr;
|
|
}
|
|
return &this->active_node->wrap();
|
|
}
|
|
|
|
void GreasePencil::set_active_node(blender::bke::greasepencil::TreeNode *node)
|
|
{
|
|
this->active_node = reinterpret_cast<GreasePencilLayerTreeNode *>(node);
|
|
}
|
|
|
|
static blender::VectorSet<blender::StringRef> get_node_names(const GreasePencil &grease_pencil)
|
|
{
|
|
using namespace blender;
|
|
VectorSet<StringRef> names;
|
|
for (const blender::bke::greasepencil::TreeNode *node : grease_pencil.nodes()) {
|
|
names.add(node->name());
|
|
}
|
|
return names;
|
|
}
|
|
|
|
static std::string unique_node_name(const GreasePencil &grease_pencil,
|
|
const blender::StringRef name)
|
|
{
|
|
using namespace blender;
|
|
BLI_assert(!name.is_empty());
|
|
const VectorSet<StringRef> names = get_node_names(grease_pencil);
|
|
return BLI_uniquename_cb(
|
|
[&](const StringRef check_name) { return names.contains(check_name); }, '.', name);
|
|
}
|
|
|
|
std::string GreasePencil::unique_layer_name(blender::StringRef name)
|
|
{
|
|
if (name.is_empty()) {
|
|
/* Default name is "Layer". */
|
|
name = DATA_("Layer");
|
|
}
|
|
return unique_node_name(*this, name);
|
|
}
|
|
|
|
static std::string unique_layer_group_name(const GreasePencil &grease_pencil,
|
|
blender::StringRef name)
|
|
{
|
|
if (name.is_empty()) {
|
|
/* Default name is "Group". */
|
|
name = DATA_("Group");
|
|
}
|
|
return unique_node_name(grease_pencil, name);
|
|
}
|
|
|
|
blender::bke::greasepencil::Layer &GreasePencil::add_layer(const blender::StringRef name,
|
|
const bool check_name_is_unique)
|
|
{
|
|
using namespace blender;
|
|
std::string unique_name = check_name_is_unique ? unique_layer_name(name) : std::string(name);
|
|
const int numLayers = layers().size();
|
|
this->attribute_storage.wrap().resize(bke::AttrDomain::Layer, numLayers + 1);
|
|
bke::greasepencil::Layer *new_layer = MEM_new<bke::greasepencil::Layer>(__func__, unique_name);
|
|
/* Enable Lights by default. */
|
|
new_layer->base.flag |= GP_LAYER_TREE_NODE_USE_LIGHTS;
|
|
/* Hide masks by default. */
|
|
new_layer->base.flag |= GP_LAYER_TREE_NODE_HIDE_MASKS;
|
|
bke::greasepencil::Layer &layer = root_group().add_node(new_layer->as_node()).as_layer();
|
|
|
|
/* Initialize the attributes with default values. */
|
|
bke::MutableAttributeAccessor attributes = this->attributes_for_write();
|
|
bke::fill_attribute_range_default(attributes,
|
|
bke::AttrDomain::Layer,
|
|
bke::attribute_filter_from_skip_ref({"name"}),
|
|
IndexRange::from_single(numLayers));
|
|
|
|
return layer;
|
|
}
|
|
|
|
blender::bke::greasepencil::Layer &GreasePencil::add_layer(
|
|
blender::bke::greasepencil::LayerGroup &parent_group,
|
|
const blender::StringRef name,
|
|
const bool check_name_is_unique)
|
|
{
|
|
using namespace blender;
|
|
blender::bke::greasepencil::Layer &new_layer = this->add_layer(name, check_name_is_unique);
|
|
move_node_into(new_layer.as_node(), parent_group);
|
|
return new_layer;
|
|
}
|
|
|
|
void GreasePencil::add_layers_for_eval(const int num_new_layers)
|
|
{
|
|
using namespace blender;
|
|
const int num_layers = this->layers().size();
|
|
this->attribute_storage.wrap().resize(bke::AttrDomain::Layer, num_layers + num_new_layers);
|
|
for ([[maybe_unused]] const int i : IndexRange(num_new_layers)) {
|
|
bke::greasepencil::Layer *new_layer = MEM_new<bke::greasepencil::Layer>(__func__);
|
|
/* Hide masks by default. */
|
|
new_layer->base.flag |= GP_LAYER_TREE_NODE_HIDE_MASKS;
|
|
this->root_group().add_node(new_layer->as_node());
|
|
}
|
|
}
|
|
|
|
blender::bke::greasepencil::Layer &GreasePencil::duplicate_layer(
|
|
const blender::bke::greasepencil::Layer &duplicate_layer,
|
|
const bool duplicate_frames,
|
|
const bool duplicate_drawings)
|
|
{
|
|
using namespace blender;
|
|
std::string unique_name = unique_layer_name(duplicate_layer.name());
|
|
std::optional<int> duplicate_layer_idx = get_layer_index(duplicate_layer);
|
|
BLI_assert(duplicate_layer_idx.has_value());
|
|
const int numLayers = layers().size();
|
|
bke::greasepencil::Layer *new_layer = MEM_new<bke::greasepencil::Layer>(__func__,
|
|
duplicate_layer);
|
|
root_group().add_node(new_layer->as_node());
|
|
|
|
this->attribute_storage.wrap().resize(bke::AttrDomain::Layer, numLayers + 1);
|
|
bke::MutableAttributeAccessor attributes = this->attributes_for_write();
|
|
attributes.foreach_attribute([&](const bke::AttributeIter &iter) {
|
|
bke::GSpanAttributeWriter attr = attributes.lookup_for_write_span(iter.name);
|
|
GMutableSpan span = attr.span;
|
|
span.type().copy_assign(span[*duplicate_layer_idx], span[numLayers]);
|
|
attr.finish();
|
|
});
|
|
|
|
/* When a layer is duplicated, the frames are shared by default. Clear the frames, to ensure a
|
|
* valid state. */
|
|
new_layer->frames_for_write().clear();
|
|
if (duplicate_frames) {
|
|
for (auto [frame_number, frame] : duplicate_layer.frames().items()) {
|
|
const int duration = duplicate_layer.get_frame_duration_at(frame_number);
|
|
bke::greasepencil::Drawing *dst_drawing = this->insert_frame(
|
|
*new_layer, frame_number, duration, eBezTriple_KeyframeType(frame.type));
|
|
if (duplicate_drawings) {
|
|
BLI_assert(dst_drawing != nullptr);
|
|
/* TODO: This can fail (return `nullptr`) if the drawing is a drawing reference! */
|
|
const bke::greasepencil::Drawing &src_drawing = *this->get_drawing_at(duplicate_layer,
|
|
frame_number);
|
|
/* Duplicate the drawing. */
|
|
*dst_drawing = src_drawing;
|
|
}
|
|
}
|
|
}
|
|
|
|
this->update_drawing_users_for_layer(*new_layer);
|
|
new_layer->set_name(unique_name);
|
|
return *new_layer;
|
|
}
|
|
|
|
blender::bke::greasepencil::Layer &GreasePencil::duplicate_layer(
|
|
blender::bke::greasepencil::LayerGroup &parent_group,
|
|
const blender::bke::greasepencil::Layer &duplicate_layer,
|
|
const bool duplicate_frames,
|
|
const bool duplicate_drawings)
|
|
{
|
|
using namespace blender;
|
|
bke::greasepencil::Layer &new_layer = this->duplicate_layer(
|
|
duplicate_layer, duplicate_frames, duplicate_drawings);
|
|
move_node_into(new_layer.as_node(), parent_group);
|
|
return new_layer;
|
|
}
|
|
|
|
blender::bke::greasepencil::LayerGroup &GreasePencil::add_layer_group(
|
|
const blender::StringRef name, const bool check_name_is_unique)
|
|
{
|
|
using namespace blender;
|
|
std::string unique_name = check_name_is_unique ? unique_layer_group_name(*this, name) :
|
|
std::string(name);
|
|
bke::greasepencil::LayerGroup *new_group = MEM_new<bke::greasepencil::LayerGroup>(__func__,
|
|
unique_name);
|
|
return root_group().add_node(new_group->as_node()).as_group();
|
|
}
|
|
|
|
blender::bke::greasepencil::LayerGroup &GreasePencil::add_layer_group(
|
|
blender::bke::greasepencil::LayerGroup &parent_group,
|
|
const blender::StringRef name,
|
|
const bool check_name_is_unique)
|
|
{
|
|
using namespace blender;
|
|
bke::greasepencil::LayerGroup &new_group = this->add_layer_group(name, check_name_is_unique);
|
|
move_node_into(new_group.as_node(), parent_group);
|
|
return new_group;
|
|
}
|
|
|
|
static void reorder_attribute_domain(blender::bke::AttributeStorage &data,
|
|
const blender::bke::AttrDomain domain,
|
|
const Span<int> new_by_old_map)
|
|
{
|
|
using namespace blender;
|
|
data.foreach([&](bke::Attribute &attr) {
|
|
if (attr.domain() != domain) {
|
|
return;
|
|
}
|
|
const CPPType &type = bke::attribute_type_to_cpp_type(attr.data_type());
|
|
switch (attr.storage_type()) {
|
|
case bke::AttrStorageType::Array: {
|
|
const auto &data = std::get<bke::Attribute::ArrayData>(attr.data());
|
|
auto new_data = bke::Attribute::ArrayData::from_constructed(type, new_by_old_map.size());
|
|
bke::attribute_math::gather(GSpan(type, data.data, data.size),
|
|
new_by_old_map,
|
|
GMutableSpan(type, new_data.data, new_data.size));
|
|
attr.assign_data(std::move(new_data));
|
|
}
|
|
case bke::AttrStorageType::Single: {
|
|
return;
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
static void reorder_layer_data(GreasePencil &grease_pencil,
|
|
const blender::FunctionRef<void()> do_layer_order_changes)
|
|
{
|
|
using namespace blender;
|
|
Span<const bke::greasepencil::Layer *> layers = grease_pencil.layers();
|
|
|
|
/* Stash the initial layer order that we can refer back to later */
|
|
Map<const bke::greasepencil::Layer *, int> old_layer_index_by_layer;
|
|
old_layer_index_by_layer.reserve(layers.size());
|
|
for (const int i : layers.index_range()) {
|
|
old_layer_index_by_layer.add_new(layers[i], i);
|
|
}
|
|
|
|
/* Execute the callback that changes the order of the layers. */
|
|
do_layer_order_changes();
|
|
layers = grease_pencil.layers();
|
|
BLI_assert(layers.size() == old_layer_index_by_layer.size());
|
|
|
|
/* Compose the mapping from old layer indices to new layer indices */
|
|
Array<int> new_by_old_map(layers.size());
|
|
for (const int layer_i_new : layers.index_range()) {
|
|
const bke::greasepencil::Layer *layer = layers[layer_i_new];
|
|
BLI_assert(old_layer_index_by_layer.contains(layer));
|
|
const int layer_i_old = old_layer_index_by_layer.pop(layer);
|
|
new_by_old_map[layer_i_new] = layer_i_old;
|
|
}
|
|
BLI_assert(old_layer_index_by_layer.is_empty());
|
|
|
|
/* Use the mapping to re-order the custom data */
|
|
reorder_attribute_domain(
|
|
grease_pencil.attribute_storage.wrap(), bke::AttrDomain::Layer, new_by_old_map);
|
|
}
|
|
|
|
void GreasePencil::move_node_up(blender::bke::greasepencil::TreeNode &node, const int step)
|
|
{
|
|
using namespace blender;
|
|
if (!node.parent_group()) {
|
|
return;
|
|
}
|
|
reorder_layer_data(*this, [&]() { node.parent_group()->move_node_up(node, step); });
|
|
}
|
|
void GreasePencil::move_node_down(blender::bke::greasepencil::TreeNode &node, const int step)
|
|
{
|
|
using namespace blender;
|
|
if (!node.parent_group()) {
|
|
return;
|
|
}
|
|
reorder_layer_data(*this, [&]() { node.parent_group()->move_node_down(node, step); });
|
|
}
|
|
void GreasePencil::move_node_top(blender::bke::greasepencil::TreeNode &node)
|
|
{
|
|
using namespace blender;
|
|
if (!node.parent_group()) {
|
|
return;
|
|
}
|
|
reorder_layer_data(*this, [&]() { node.parent_group()->move_node_top(node); });
|
|
}
|
|
void GreasePencil::move_node_bottom(blender::bke::greasepencil::TreeNode &node)
|
|
{
|
|
using namespace blender;
|
|
if (!node.parent_group()) {
|
|
return;
|
|
}
|
|
reorder_layer_data(*this, [&]() { node.parent_group()->move_node_bottom(node); });
|
|
}
|
|
|
|
void GreasePencil::move_node_after(blender::bke::greasepencil::TreeNode &node,
|
|
blender::bke::greasepencil::TreeNode &target_node)
|
|
{
|
|
using namespace blender;
|
|
if (!target_node.parent_group() || !node.parent_group()) {
|
|
return;
|
|
}
|
|
reorder_layer_data(*this, [&]() {
|
|
node.parent_group()->unlink_node(node);
|
|
target_node.parent_group()->add_node_after(node, target_node);
|
|
});
|
|
}
|
|
|
|
void GreasePencil::move_node_before(blender::bke::greasepencil::TreeNode &node,
|
|
blender::bke::greasepencil::TreeNode &target_node)
|
|
{
|
|
using namespace blender;
|
|
if (!target_node.parent_group() || !node.parent_group()) {
|
|
return;
|
|
}
|
|
reorder_layer_data(*this, [&]() {
|
|
node.parent_group()->unlink_node(node);
|
|
target_node.parent_group()->add_node_before(node, target_node);
|
|
});
|
|
}
|
|
|
|
void GreasePencil::move_node_into(blender::bke::greasepencil::TreeNode &node,
|
|
blender::bke::greasepencil::LayerGroup &parent_group)
|
|
{
|
|
using namespace blender;
|
|
if (!node.parent_group()) {
|
|
return;
|
|
}
|
|
reorder_layer_data(*this, [&]() {
|
|
node.parent_group()->unlink_node(node);
|
|
parent_group.add_node(node);
|
|
});
|
|
}
|
|
|
|
const blender::bke::greasepencil::TreeNode *GreasePencil::find_node_by_name(
|
|
const blender::StringRef name) const
|
|
{
|
|
return this->root_group().find_node_by_name(name);
|
|
}
|
|
|
|
blender::bke::greasepencil::TreeNode *GreasePencil::find_node_by_name(
|
|
const blender::StringRef name)
|
|
{
|
|
return this->root_group().find_node_by_name(name);
|
|
}
|
|
|
|
blender::IndexMask GreasePencil::layer_selection_by_name(const blender::StringRef name,
|
|
blender::IndexMaskMemory &memory) const
|
|
{
|
|
using namespace blender::bke::greasepencil;
|
|
const TreeNode *node = this->find_node_by_name(name);
|
|
if (!node) {
|
|
return {};
|
|
}
|
|
|
|
if (node->is_layer()) {
|
|
const int index = *this->get_layer_index(node->as_layer());
|
|
return blender::IndexMask::from_indices(blender::Span<int>{index}, memory);
|
|
}
|
|
if (node->is_group()) {
|
|
blender::Vector<int64_t> layer_indices;
|
|
for (const int64_t layer_index : this->layers().index_range()) {
|
|
const Layer &layer = *this->layers()[layer_index];
|
|
if (layer.is_child_of(node->as_group())) {
|
|
layer_indices.append(layer_index);
|
|
}
|
|
}
|
|
return blender::IndexMask::from_indices(layer_indices.as_span(), memory);
|
|
}
|
|
return {};
|
|
}
|
|
|
|
static GreasePencilModifierInfluenceData *influence_data_from_modifier(ModifierData *md)
|
|
{
|
|
switch (md->type) {
|
|
case eModifierType_GreasePencilArmature: {
|
|
auto *amd = reinterpret_cast<GreasePencilArmatureModifierData *>(md);
|
|
return &amd->influence;
|
|
}
|
|
case eModifierType_GreasePencilArray: {
|
|
auto *mmd = reinterpret_cast<GreasePencilArrayModifierData *>(md);
|
|
return &mmd->influence;
|
|
}
|
|
case eModifierType_GreasePencilBuild: {
|
|
auto *bmd = reinterpret_cast<GreasePencilBuildModifierData *>(md);
|
|
return &bmd->influence;
|
|
}
|
|
case eModifierType_GreasePencilColor: {
|
|
auto *cmd = reinterpret_cast<GreasePencilColorModifierData *>(md);
|
|
return &cmd->influence;
|
|
}
|
|
case eModifierType_GreasePencilDash: {
|
|
auto *dmd = reinterpret_cast<GreasePencilDashModifierData *>(md);
|
|
return &dmd->influence;
|
|
}
|
|
case eModifierType_GreasePencilEnvelope: {
|
|
auto *emd = reinterpret_cast<GreasePencilEnvelopeModifierData *>(md);
|
|
return &emd->influence;
|
|
}
|
|
case eModifierType_GreasePencilHook: {
|
|
auto *hmd = reinterpret_cast<GreasePencilHookModifierData *>(md);
|
|
return &hmd->influence;
|
|
}
|
|
case eModifierType_GreasePencilLattice: {
|
|
auto *lmd = reinterpret_cast<GreasePencilLatticeModifierData *>(md);
|
|
return &lmd->influence;
|
|
}
|
|
case eModifierType_GreasePencilLength: {
|
|
auto *lmd = reinterpret_cast<GreasePencilLengthModifierData *>(md);
|
|
return &lmd->influence;
|
|
}
|
|
case eModifierType_GreasePencilMirror: {
|
|
auto *mmd = reinterpret_cast<GreasePencilMirrorModifierData *>(md);
|
|
return &mmd->influence;
|
|
}
|
|
case eModifierType_GreasePencilMultiply: {
|
|
auto *mmd = reinterpret_cast<GreasePencilMultiModifierData *>(md);
|
|
return &mmd->influence;
|
|
}
|
|
case eModifierType_GreasePencilNoise: {
|
|
auto *nmd = reinterpret_cast<GreasePencilNoiseModifierData *>(md);
|
|
return &nmd->influence;
|
|
}
|
|
case eModifierType_GreasePencilOffset: {
|
|
auto *omd = reinterpret_cast<GreasePencilOffsetModifierData *>(md);
|
|
return &omd->influence;
|
|
}
|
|
case eModifierType_GreasePencilOpacity: {
|
|
auto *omd = reinterpret_cast<GreasePencilOpacityModifierData *>(md);
|
|
return &omd->influence;
|
|
}
|
|
case eModifierType_GreasePencilOutline: {
|
|
auto *omd = reinterpret_cast<GreasePencilOutlineModifierData *>(md);
|
|
return &omd->influence;
|
|
}
|
|
case eModifierType_GreasePencilShrinkwrap: {
|
|
auto *smd = reinterpret_cast<GreasePencilShrinkwrapModifierData *>(md);
|
|
return &smd->influence;
|
|
}
|
|
case eModifierType_GreasePencilSimplify: {
|
|
auto *smd = reinterpret_cast<GreasePencilSimplifyModifierData *>(md);
|
|
return &smd->influence;
|
|
}
|
|
case eModifierType_GreasePencilSmooth: {
|
|
auto *smd = reinterpret_cast<GreasePencilSmoothModifierData *>(md);
|
|
return &smd->influence;
|
|
}
|
|
case eModifierType_GreasePencilSubdiv: {
|
|
auto *smd = reinterpret_cast<GreasePencilSubdivModifierData *>(md);
|
|
return &smd->influence;
|
|
}
|
|
case eModifierType_GreasePencilTexture: {
|
|
auto *tmd = reinterpret_cast<GreasePencilTextureModifierData *>(md);
|
|
return &tmd->influence;
|
|
}
|
|
case eModifierType_GreasePencilThickness: {
|
|
auto *tmd = reinterpret_cast<GreasePencilThickModifierData *>(md);
|
|
return &tmd->influence;
|
|
}
|
|
case eModifierType_GreasePencilTime: {
|
|
auto *tmd = reinterpret_cast<GreasePencilTimeModifierData *>(md);
|
|
return &tmd->influence;
|
|
}
|
|
case eModifierType_GreasePencilTint: {
|
|
auto *tmd = reinterpret_cast<GreasePencilTintModifierData *>(md);
|
|
return &tmd->influence;
|
|
}
|
|
case eModifierType_GreasePencilWeightAngle: {
|
|
auto *wmd = reinterpret_cast<GreasePencilWeightAngleModifierData *>(md);
|
|
return &wmd->influence;
|
|
}
|
|
case eModifierType_GreasePencilWeightProximity: {
|
|
auto *wmd = reinterpret_cast<GreasePencilWeightProximityModifierData *>(md);
|
|
return &wmd->influence;
|
|
}
|
|
case eModifierType_GreasePencilLineart:
|
|
ATTR_FALLTHROUGH;
|
|
default:
|
|
return nullptr;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
void GreasePencil::rename_node(Main &bmain,
|
|
blender::bke::greasepencil::TreeNode &node,
|
|
const blender::StringRef new_name)
|
|
{
|
|
using namespace blender;
|
|
if (node.name() == new_name) {
|
|
return;
|
|
}
|
|
|
|
/* Rename the node. */
|
|
std::string old_name = node.name();
|
|
if (node.is_layer()) {
|
|
node.set_name(unique_layer_name(new_name));
|
|
}
|
|
else if (node.is_group()) {
|
|
node.set_name(unique_layer_group_name(*this, new_name));
|
|
}
|
|
|
|
/* Update layer name dependencies. */
|
|
if (node.is_layer()) {
|
|
BKE_animdata_fix_paths_rename_all(&this->id, "layers", old_name.c_str(), node.name().c_str());
|
|
/* Update names in layer masks. */
|
|
for (bke::greasepencil::Layer *layer : this->layers_for_write()) {
|
|
LISTBASE_FOREACH (GreasePencilLayerMask *, mask, &layer->masks) {
|
|
if (STREQ(mask->layer_name, old_name.c_str())) {
|
|
mask->layer_name = BLI_strdup(node.name().c_str());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Update name dependencies outside of the ID. */
|
|
LISTBASE_FOREACH (Object *, object, &bmain.objects) {
|
|
if (object->data != this) {
|
|
continue;
|
|
}
|
|
|
|
/* Update the layer name of the influence data of the modifiers. */
|
|
LISTBASE_FOREACH (ModifierData *, md, &object->modifiers) {
|
|
char *dst_layer_name = nullptr;
|
|
size_t dst_layer_name_maxncpy = 0;
|
|
/* LineArt doesn't use the `GreasePencilModifierInfluenceData` struct. */
|
|
if (md->type == eModifierType_GreasePencilLineart) {
|
|
auto *lmd = reinterpret_cast<GreasePencilLineartModifierData *>(md);
|
|
dst_layer_name = lmd->target_layer;
|
|
dst_layer_name_maxncpy = sizeof(lmd->target_layer);
|
|
}
|
|
else if (GreasePencilModifierInfluenceData *influence_data = influence_data_from_modifier(
|
|
md))
|
|
{
|
|
dst_layer_name = influence_data->layer_name;
|
|
dst_layer_name_maxncpy = sizeof(influence_data->layer_name);
|
|
}
|
|
if (dst_layer_name && STREQ(dst_layer_name, old_name.c_str())) {
|
|
BLI_strncpy(dst_layer_name, node.name().c_str(), dst_layer_name_maxncpy);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void shrink_attribute_storage(blender::bke::AttributeStorage &storage,
|
|
const int index_to_remove,
|
|
const int size)
|
|
{
|
|
using namespace blender;
|
|
const IndexRange range_before(index_to_remove);
|
|
const IndexRange range_after(index_to_remove + 1, size - index_to_remove - 1);
|
|
|
|
storage.foreach([&](bke::Attribute &attr) {
|
|
const CPPType &type = bke::attribute_type_to_cpp_type(attr.data_type());
|
|
switch (attr.storage_type()) {
|
|
case bke::AttrStorageType::Array: {
|
|
const auto &data = std::get<bke::Attribute::ArrayData>(attr.data());
|
|
|
|
auto new_data = bke::Attribute::ArrayData::from_uninitialized(type, size - 1);
|
|
type.copy_construct_n(data.data, new_data.data, range_before.size());
|
|
type.copy_construct_n(POINTER_OFFSET(data.data, type.size * range_after.start()),
|
|
POINTER_OFFSET(new_data.data, type.size * index_to_remove),
|
|
range_after.size());
|
|
|
|
attr.assign_data(std::move(new_data));
|
|
}
|
|
case bke::AttrStorageType::Single: {
|
|
return;
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
static void update_active_node_from_node_to_remove(
|
|
GreasePencil &grease_pencil, const blender::bke::greasepencil::TreeNode &node)
|
|
{
|
|
using namespace blender::bke::greasepencil;
|
|
/* 1. Try setting the node below (within the same group) to be active. */
|
|
if (node.prev != nullptr) {
|
|
grease_pencil.set_active_node(reinterpret_cast<TreeNode *>(node.prev));
|
|
}
|
|
/* 2. If there is no node below, try setting the node above (within the same group) to be the
|
|
* active one. */
|
|
else if (node.next != nullptr) {
|
|
grease_pencil.set_active_node(reinterpret_cast<TreeNode *>(node.next));
|
|
}
|
|
/* 3. If this is the only node within its parent group and the parent group is not the root
|
|
* group, try setting the parent to be active. */
|
|
else if (node.parent != grease_pencil.root_group_ptr) {
|
|
grease_pencil.set_active_node(&node.parent->wrap().as_node());
|
|
}
|
|
/* 4. Otherwise, clear the active node. */
|
|
else {
|
|
grease_pencil.set_active_node(nullptr);
|
|
}
|
|
}
|
|
|
|
void GreasePencil::remove_layer(blender::bke::greasepencil::Layer &layer)
|
|
{
|
|
using namespace blender::bke::greasepencil;
|
|
/* If the layer is active, update the active layer. */
|
|
if (&layer.as_node() == this->get_active_node()) {
|
|
update_active_node_from_node_to_remove(*this, layer.as_node());
|
|
}
|
|
|
|
/* Remove all the layer attributes and shrink the `CustomData`. */
|
|
const int layer_index = *this->get_layer_index(layer);
|
|
shrink_attribute_storage(this->attribute_storage.wrap(), layer_index, this->layers().size());
|
|
|
|
/* Unlink the layer from the parent group. */
|
|
layer.parent_group().unlink_node(layer.as_node());
|
|
|
|
/* Remove drawings. */
|
|
for (const GreasePencilFrame frame : layer.frames().values()) {
|
|
GreasePencilDrawingBase *drawing_base = this->drawing(frame.drawing_index);
|
|
if (drawing_base->type != GP_DRAWING) {
|
|
/* TODO: Remove drawing reference. */
|
|
continue;
|
|
}
|
|
GreasePencilDrawing *drawing = reinterpret_cast<GreasePencilDrawing *>(drawing_base);
|
|
drawing->wrap().remove_user();
|
|
}
|
|
this->remove_drawings_with_no_users();
|
|
|
|
/* Delete the layer. */
|
|
MEM_delete(&layer);
|
|
}
|
|
|
|
void GreasePencil::remove_group(blender::bke::greasepencil::LayerGroup &group,
|
|
const bool keep_children)
|
|
{
|
|
using namespace blender::bke::greasepencil;
|
|
/* If the group is active, update the active layer. */
|
|
if (&group.as_node() == this->get_active_node()) {
|
|
/* If we keep the children and there is at least one child, make it the active node. */
|
|
if (keep_children && !group.is_empty()) {
|
|
this->set_active_node(reinterpret_cast<TreeNode *>(group.children.last));
|
|
}
|
|
else {
|
|
update_active_node_from_node_to_remove(*this, group.as_node());
|
|
}
|
|
}
|
|
|
|
if (!keep_children) {
|
|
/* Recursively remove groups and layers. */
|
|
LISTBASE_FOREACH_MUTABLE (GreasePencilLayerTreeNode *, child, &group.children) {
|
|
switch (child->type) {
|
|
case GP_LAYER_TREE_LEAF: {
|
|
this->remove_layer(reinterpret_cast<GreasePencilLayer *>(child)->wrap());
|
|
break;
|
|
}
|
|
case GP_LAYER_TREE_GROUP: {
|
|
this->remove_group(reinterpret_cast<GreasePencilLayerTreeGroup *>(child)->wrap(), false);
|
|
break;
|
|
}
|
|
default:
|
|
BLI_assert_unreachable();
|
|
}
|
|
}
|
|
BLI_assert(BLI_listbase_is_empty(&group.children));
|
|
}
|
|
|
|
/* Unlink then delete active group node. */
|
|
group.as_node().parent_group()->unlink_node(group.as_node(), true);
|
|
MEM_delete(&group);
|
|
}
|
|
|
|
void GreasePencil::print_layer_tree()
|
|
{
|
|
using namespace blender::bke::greasepencil;
|
|
this->root_group().print_nodes("Layer Tree:");
|
|
}
|
|
|
|
blender::Array<int> GreasePencil::count_frame_users_for_drawings() const
|
|
{
|
|
using namespace blender;
|
|
using namespace blender::bke::greasepencil;
|
|
Array<int> user_counts(this->drawings().size(), 0);
|
|
for (const Layer *layer : this->layers()) {
|
|
for (const auto &[frame, value] : layer->frames().items()) {
|
|
BLI_assert(this->drawings().index_range().contains(value.drawing_index));
|
|
user_counts[value.drawing_index]++;
|
|
}
|
|
}
|
|
return user_counts;
|
|
}
|
|
|
|
void GreasePencil::validate_drawing_user_counts()
|
|
{
|
|
#ifndef NDEBUG
|
|
using namespace blender::bke::greasepencil;
|
|
blender::Array<int> actual_user_counts = this->count_frame_users_for_drawings();
|
|
for (const int drawing_i : this->drawings().index_range()) {
|
|
const GreasePencilDrawingBase *drawing_base = this->drawing(drawing_i);
|
|
if (drawing_base->type != GP_DRAWING_REFERENCE) {
|
|
const Drawing &drawing = reinterpret_cast<const GreasePencilDrawing *>(drawing_base)->wrap();
|
|
/* Ignore `fake_user` flag. */
|
|
BLI_assert(drawing.user_count() == actual_user_counts[drawing_i]);
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
blender::bke::AttributeAccessor GreasePencil::attributes() const
|
|
{
|
|
return blender::bke::AttributeAccessor(
|
|
this, blender::bke::greasepencil::get_attribute_accessor_functions());
|
|
}
|
|
|
|
blender::bke::MutableAttributeAccessor GreasePencil::attributes_for_write()
|
|
{
|
|
return blender::bke::MutableAttributeAccessor(
|
|
this, blender::bke::greasepencil::get_attribute_accessor_functions());
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* ------------------------------------------------------------------- */
|
|
/** \name Drawing array read/write functions
|
|
* \{ */
|
|
|
|
static void read_drawing_array(GreasePencil &grease_pencil, BlendDataReader *reader)
|
|
{
|
|
BLO_read_pointer_array(reader,
|
|
grease_pencil.drawing_array_num,
|
|
reinterpret_cast<void **>(&grease_pencil.drawing_array));
|
|
for (int i = 0; i < grease_pencil.drawing_array_num; i++) {
|
|
BLO_read_struct(reader, GreasePencilDrawingBase, &grease_pencil.drawing_array[i]);
|
|
GreasePencilDrawingBase *drawing_base = grease_pencil.drawing_array[i];
|
|
switch (GreasePencilDrawingType(drawing_base->type)) {
|
|
case GP_DRAWING: {
|
|
GreasePencilDrawing *drawing = reinterpret_cast<GreasePencilDrawing *>(drawing_base);
|
|
drawing->wrap().strokes_for_write().blend_read(*reader);
|
|
/* Initialize runtime data. */
|
|
drawing->runtime = MEM_new<blender::bke::greasepencil::DrawingRuntime>(__func__);
|
|
break;
|
|
}
|
|
case GP_DRAWING_REFERENCE: {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void write_drawing_array(GreasePencil &grease_pencil,
|
|
blender::ResourceScope &scope,
|
|
BlendWriter *writer)
|
|
{
|
|
using namespace blender;
|
|
BLO_write_pointer_array(writer, grease_pencil.drawing_array_num, grease_pencil.drawing_array);
|
|
for (int i = 0; i < grease_pencil.drawing_array_num; i++) {
|
|
GreasePencilDrawingBase *drawing_base = grease_pencil.drawing_array[i];
|
|
switch (GreasePencilDrawingType(drawing_base->type)) {
|
|
case GP_DRAWING: {
|
|
GreasePencilDrawing &drawing_copy = scope.construct<GreasePencilDrawing>();
|
|
drawing_copy = *reinterpret_cast<GreasePencilDrawing *>(drawing_base);
|
|
bke::CurvesGeometry &curves = drawing_copy.geometry.wrap();
|
|
|
|
bke::CurvesGeometry::BlendWriteData write_data(scope);
|
|
curves.blend_write_prepare(write_data);
|
|
drawing_copy.runtime = nullptr;
|
|
|
|
BLO_write_struct_at_address(writer, GreasePencilDrawing, drawing_base, &drawing_copy);
|
|
curves.blend_write(*writer, grease_pencil.id, write_data);
|
|
break;
|
|
}
|
|
case GP_DRAWING_REFERENCE: {
|
|
GreasePencilDrawingReference *drawing_reference =
|
|
reinterpret_cast<GreasePencilDrawingReference *>(drawing_base);
|
|
BLO_write_struct(writer, GreasePencilDrawingReference, drawing_reference);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void free_drawing_array(GreasePencil &grease_pencil)
|
|
{
|
|
grease_pencil.resize_drawings(0);
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* ------------------------------------------------------------------- */
|
|
/** \name Layer tree read/write functions
|
|
* \{ */
|
|
|
|
static void read_layer(BlendDataReader *reader,
|
|
GreasePencilLayer *node,
|
|
GreasePencilLayerTreeGroup *parent)
|
|
{
|
|
BLO_read_string(reader, &node->base.name);
|
|
node->base.parent = parent;
|
|
BLO_read_string(reader, &node->parsubstr);
|
|
BLO_read_string(reader, &node->viewlayername);
|
|
|
|
/* Read frames storage. */
|
|
BLO_read_int32_array(reader, node->frames_storage.num, &node->frames_storage.keys);
|
|
BLO_read_struct_array(
|
|
reader, GreasePencilFrame, node->frames_storage.num, &node->frames_storage.values);
|
|
|
|
/* Read layer masks. */
|
|
BLO_read_struct_list(reader, GreasePencilLayerMask, &node->masks);
|
|
LISTBASE_FOREACH (GreasePencilLayerMask *, mask, &node->masks) {
|
|
BLO_read_string(reader, &mask->layer_name);
|
|
}
|
|
|
|
/* NOTE: Ideally this should be cleared on write, to reduce false 'changes' detection in memfile
|
|
* undo system. This is not easily doable currently though, since modifying to actual data during
|
|
* write is not an option (a shallow copy of the #Layer data would be needed then). */
|
|
node->runtime = nullptr;
|
|
node->wrap().update_from_dna_read();
|
|
}
|
|
|
|
static void read_layer_tree_group(BlendDataReader *reader,
|
|
GreasePencilLayerTreeGroup *node,
|
|
GreasePencilLayerTreeGroup *parent)
|
|
{
|
|
BLO_read_string(reader, &node->base.name);
|
|
node->base.parent = parent;
|
|
/* Read list of children. */
|
|
BLO_read_struct_list(reader, GreasePencilLayerTreeNode, &node->children);
|
|
LISTBASE_FOREACH (GreasePencilLayerTreeNode *, child, &node->children) {
|
|
switch (child->type) {
|
|
case GP_LAYER_TREE_LEAF: {
|
|
GreasePencilLayer *layer = reinterpret_cast<GreasePencilLayer *>(child);
|
|
read_layer(reader, layer, node);
|
|
break;
|
|
}
|
|
case GP_LAYER_TREE_GROUP: {
|
|
GreasePencilLayerTreeGroup *group = reinterpret_cast<GreasePencilLayerTreeGroup *>(child);
|
|
read_layer_tree_group(reader, group, node);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
node->wrap().runtime = MEM_new<blender::bke::greasepencil::LayerGroupRuntime>(__func__);
|
|
}
|
|
|
|
static void read_layer_tree(GreasePencil &grease_pencil, BlendDataReader *reader)
|
|
{
|
|
/* Read root group. */
|
|
BLO_read_struct(reader, GreasePencilLayerTreeGroup, &grease_pencil.root_group_ptr);
|
|
/* This shouldn't normally happen, but for files that were created before the root group became a
|
|
* pointer, this address will not exist. In this case, we clear the pointer to the active layer
|
|
* and create an empty root group to avoid crashes. */
|
|
if (grease_pencil.root_group_ptr == nullptr) {
|
|
grease_pencil.root_group_ptr = MEM_new<blender::bke::greasepencil::LayerGroup>(__func__);
|
|
grease_pencil.set_active_node(nullptr);
|
|
return;
|
|
}
|
|
/* Read active layer. */
|
|
BLO_read_struct(reader, GreasePencilLayerTreeNode, &grease_pencil.active_node);
|
|
read_layer_tree_group(reader, grease_pencil.root_group_ptr, nullptr);
|
|
|
|
grease_pencil.root_group_ptr->wrap().update_from_dna_read();
|
|
}
|
|
|
|
static void write_layer(BlendWriter *writer, GreasePencilLayer *node)
|
|
{
|
|
BLO_write_struct(writer, GreasePencilLayer, node);
|
|
BLO_write_string(writer, node->base.name);
|
|
BLO_write_string(writer, node->parsubstr);
|
|
BLO_write_string(writer, node->viewlayername);
|
|
|
|
BLO_write_int32_array(writer, node->frames_storage.num, node->frames_storage.keys);
|
|
BLO_write_struct_array(
|
|
writer, GreasePencilFrame, node->frames_storage.num, node->frames_storage.values);
|
|
|
|
BLO_write_struct_list(writer, GreasePencilLayerMask, &node->masks);
|
|
LISTBASE_FOREACH (GreasePencilLayerMask *, mask, &node->masks) {
|
|
BLO_write_string(writer, mask->layer_name);
|
|
}
|
|
}
|
|
|
|
static void write_layer_tree_group(BlendWriter *writer, GreasePencilLayerTreeGroup *node)
|
|
{
|
|
BLO_write_struct(writer, GreasePencilLayerTreeGroup, node);
|
|
BLO_write_string(writer, node->base.name);
|
|
LISTBASE_FOREACH (GreasePencilLayerTreeNode *, child, &node->children) {
|
|
switch (child->type) {
|
|
case GP_LAYER_TREE_LEAF: {
|
|
GreasePencilLayer *layer = reinterpret_cast<GreasePencilLayer *>(child);
|
|
write_layer(writer, layer);
|
|
break;
|
|
}
|
|
case GP_LAYER_TREE_GROUP: {
|
|
GreasePencilLayerTreeGroup *group = reinterpret_cast<GreasePencilLayerTreeGroup *>(child);
|
|
write_layer_tree_group(writer, group);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void write_layer_tree(GreasePencil &grease_pencil, BlendWriter *writer)
|
|
{
|
|
grease_pencil.root_group_ptr->wrap().prepare_for_dna_write();
|
|
write_layer_tree_group(writer, grease_pencil.root_group_ptr);
|
|
}
|