Files
test/source/blender/blenkernel/intern/grease_pencil_convert_legacy.cc
Bastien Montagne f6a9f082e9 Core: Add data-level support for new 'system IDprops' storage for Blender 4.5 forward compatibility with 5.0 blendfiles
This mainly adds DNA level IDProp storage for system properties, their
handling in data management code, and the forward-versioning code
copying back content from system properties into 'all-in-one' single
IDProperties storage, for data types that will support both in Blender
5.0.

There is no user-facing changes expected here.

Part of #123232.

Pull Request: https://projects.blender.org/blender/blender/pulls/139257
2025-06-06 11:49:54 +02:00

3266 lines
138 KiB
C++

/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bke
*/
#include <optional>
#include <fmt/format.h>
#include "BKE_action.hh"
#include "BKE_anim_data.hh"
#include "BKE_attribute.hh"
#include "BKE_blendfile_link_append.hh"
#include "BKE_colortools.hh"
#include "BKE_curves.hh"
#include "BKE_deform.hh"
#include "BKE_fcurve.hh"
#include "BKE_gpencil_modifier_legacy.h"
#include "BKE_grease_pencil.hh"
#include "BKE_grease_pencil_legacy_convert.hh"
#include "BKE_idprop.hh"
#include "BKE_lib_id.hh"
#include "BKE_lib_remap.hh"
#include "BKE_main.hh"
#include "BKE_material.hh"
#include "BKE_modifier.hh"
#include "BKE_node.hh"
#include "BKE_node_tree_update.hh"
#include "BKE_object.hh"
#include "BKE_screen.hh"
#include "BLO_readfile.hh"
#include "BLI_color.hh"
#include "BLI_function_ref.hh"
#include "BLI_listbase.h"
#include "BLI_map.hh"
#include "BLI_math_matrix.h"
#include "BLI_math_matrix.hh"
#include "BLI_math_vector_types.hh"
#include "BLI_string.h"
#include "BLI_string_utf8.h"
#include "BLI_vector.hh"
#include "BLT_translation.hh"
#include "DNA_anim_types.h"
#include "DNA_brush_types.h"
#include "DNA_gpencil_legacy_types.h"
#include "DNA_gpencil_modifier_types.h"
#include "DNA_grease_pencil_types.h"
#include "DNA_meshdata_types.h"
#include "DNA_modifier_types.h"
#include "DNA_screen_types.h"
#include "DNA_space_types.h"
#include "DEG_depsgraph.hh"
#include "DEG_depsgraph_build.hh"
#include "ANIM_action.hh"
#include "ANIM_action_iterators.hh"
namespace blender::bke::greasepencil::convert {
/**
* Data shared across most of GP conversion code.
*/
struct ConversionData {
Main &bmain;
BlendfileLinkAppendContext *lapp_context;
/** A mapping between a library and a generated 'offset radius' node group. */
Map<Library *, bNodeTree *> offset_radius_ntree_by_library = {};
/** A mapping between a legacy GPv2 ID and its converted GPv3 ID. */
Map<bGPdata *, GreasePencil *> legacy_to_greasepencil_data = {};
ConversionData(Main &bmain, BlendfileLinkAppendContext *lapp_context)
: bmain(bmain), lapp_context(lapp_context)
{
}
};
/* -------------------------------------------------------------------- */
/** \name Animation conversion helpers.
*
* These utilities will call given callback over all relevant F-curves
* (also includes drivers, and actions linked through the NLA).
* \{ */
using FCurveConvertCB = void(FCurve &fcurve);
/**
* Conversion data for the FCurve of a specific RNA property.
*
* Used as sub-data of #AnimDataConvertor when precise control over which and how FCurves are
* converted.
*/
struct AnimDataFCurveConvertor {
/**
* Source and destination RNA paths
* (relative to the relevant root paths stored in the owner #AnimDataConvertor data).
*/
const char *relative_rna_path_src;
const char *relative_rna_path_dst;
/** Optional callback to perform additional conversion work on a specific FCurve. */
blender::FunctionRef<FCurveConvertCB> convert_cb;
AnimDataFCurveConvertor(const char *relative_rna_path_src,
const char *relative_rna_path_dst,
blender::FunctionRef<FCurveConvertCB> convert_cb = {nullptr})
: relative_rna_path_src(relative_rna_path_src),
relative_rna_path_dst(relative_rna_path_dst),
convert_cb(convert_cb)
{
}
};
/**
* This class contains data and logic to handle conversion of animation data (FCurves).
*
* It can be used to either:
* - Convert FCurves within a same animation data (essentially updating the RNA paths).
* - Convert FCurves and move them from the source to the destination IDs animation data.
* The constructor used defines which of these two 'modes' will be the used by a given convertor.
*
* RNA paths to convert can be specified in two ways:
* - Complete paths, with a list of source to destination pairs of paths (relative to the relevant
* root paths).
* - Only by the source and destination root paths (in which case all FCurves starting by these
* paths will be converted).
*
* \note In case of transfer of animation data between IDs, NLA animation in source data is not
* converted. This would require (partially) re-creating a copy of the potential source NLA into
* the destination NLA, which is too complex for the few potential use cases.
*/
class AnimDataConvertor {
ConversionData &conversion_data;
ID &id_src;
ID &id_dst;
AnimData *animdata_src;
AnimData *animdata_dst;
/** Whether the source and destination IDs are different or not. */
const bool is_transfer_between_ids;
/**
* Whether to skip NLA animation processing or not.
* \note Currently NLA is skipped if this convertor transfers animation data between different
* source and destination IDs.
*/
const bool skip_nla;
/**
* Source (old) RNA property path in source ID to destination (new) matching property RNA path in
* destination ID.
*
* \note All paths here are relative the their respective (source or destination) root path.
* \note If this array is empty, all FCurves starting with `root_path_source` will be "rebased"
* on `root_path_dst`.
*/
const Array<AnimDataFCurveConvertor> fcurve_convertors;
public:
/**
* Source and destination RNA root path. These can be modified by user code at any time (e.g.
* when processing animation data for different modifiers...).
*/
std::string root_path_src;
std::string root_path_dst;
private:
/**
* Store all FCurves that need to be moved from source animation to destination animation,
* respectively for the main action, the temp action, and the drivers.
*
* Currently only used when moving animation from one source ID to a different destination ID.
*/
blender::Vector<FCurve *> fcurves_from_src_main_action = {};
blender::Vector<FCurve *> fcurves_from_src_tmp_action = {};
blender::Vector<FCurve *> fcurves_from_src_drivers = {};
/**
* Generic 'has done something' flag, used to decide whether depsgraph tagging for updates is
* needed.
*/
bool has_changes = false;
public:
/** Constructor to use when only processing FCurves within a same ID animation data. */
AnimDataConvertor(ConversionData &conversion_data,
ID &id_src,
const Array<AnimDataFCurveConvertor> fcurve_convertors = {})
: conversion_data(conversion_data),
id_src(id_src),
id_dst(id_src),
animdata_src(BKE_animdata_from_id(&id_src)),
animdata_dst(BKE_animdata_from_id(&id_dst)),
is_transfer_between_ids(false),
skip_nla(false),
fcurve_convertors(fcurve_convertors)
{
}
/** Constructor to use when moving FCurves from one ID to another. */
AnimDataConvertor(ConversionData &conversion_data,
ID &id_dst,
ID &id_src,
const Array<AnimDataFCurveConvertor> fcurve_convertors = {})
: conversion_data(conversion_data),
id_src(id_src),
id_dst(id_dst),
animdata_src(BKE_animdata_from_id(&id_src)),
animdata_dst(BKE_animdata_from_id(&id_dst)),
is_transfer_between_ids(true),
skip_nla(true),
fcurve_convertors(fcurve_convertors)
{
}
AnimDataConvertor() = delete;
private:
using FCurveCallback = bool(bAction *owner_action, FCurve &fcurve);
using ActionCallback = bool(bAction &action);
/** \return True if this AnimDataConvertor is valid, i.e. can be used to process animation data
* from source ID. */
bool is_valid() const
{
return this->animdata_src != nullptr;
}
/* Basic common check to decide whether a legacy fcurve should be processed or not. */
bool legacy_fcurves_is_valid_for_root_path(FCurve &fcurve, StringRefNull legacy_root_path) const
{
if (!fcurve.rna_path) {
return false;
}
StringRefNull rna_path = fcurve.rna_path;
if (!rna_path.startswith(legacy_root_path)) {
return false;
}
return true;
}
/**
* Common filtering of FCurve RNA path to decide whether they can/need to be processed here or
* not.
*/
bool animation_fcurve_is_valid(bAction *owner_action, FCurve &fcurve) const
{
if (!this->is_valid()) {
return false;
}
/* Only take into account drivers (nullptr `action_owner`), and Actions directly assigned
* to the animdata, not the NLA ones. */
if (owner_action &&
!ELEM(owner_action, this->animdata_src->action, this->animdata_src->tmpact))
{
return false;
}
if (!legacy_fcurves_is_valid_for_root_path(fcurve, this->root_path_src)) {
return false;
}
return true;
}
/* Iterator over all FCurves in a given animation data. */
bool fcurve_foreach_in_action(bAction *owner_action,
blender::FunctionRef<FCurveCallback> callback) const
{
bool is_changed = false;
animrig::foreach_fcurve_in_action(owner_action->wrap(), [&](FCurve &fcurve) {
const bool local_is_changed = callback(owner_action, fcurve);
is_changed = is_changed || local_is_changed;
});
return is_changed;
}
bool fcurve_foreach_in_listbase(ListBase &fcurves,
blender::FunctionRef<FCurveCallback> callback) const
{
bool is_changed = false;
LISTBASE_FOREACH (FCurve *, fcurve, &fcurves) {
const bool local_is_changed = callback(nullptr, *fcurve);
is_changed = is_changed || local_is_changed;
}
return is_changed;
}
bool nla_strip_fcurve_foreach(NlaStrip &nla_strip,
blender::FunctionRef<FCurveCallback> callback) const
{
bool is_changed = false;
if (nla_strip.act) {
if (this->fcurve_foreach_in_action(nla_strip.act, callback)) {
DEG_id_tag_update(&nla_strip.act->id, ID_RECALC_ANIMATION);
is_changed = true;
}
}
LISTBASE_FOREACH (NlaStrip *, nla_strip_children, &nla_strip.strips) {
const bool local_is_changed = this->nla_strip_fcurve_foreach(*nla_strip_children, callback);
is_changed = is_changed || local_is_changed;
}
return is_changed;
}
bool animdata_fcurve_foreach(AnimData &anim_data,
blender::FunctionRef<FCurveCallback> callback) const
{
bool is_changed = false;
if (anim_data.action) {
if (this->fcurve_foreach_in_action(anim_data.action, callback)) {
DEG_id_tag_update(&anim_data.action->id, ID_RECALC_ANIMATION);
is_changed = true;
}
}
if (anim_data.tmpact) {
if (this->fcurve_foreach_in_action(anim_data.tmpact, callback)) {
DEG_id_tag_update(&anim_data.tmpact->id, ID_RECALC_ANIMATION);
is_changed = true;
}
}
{
const bool local_is_changed = this->fcurve_foreach_in_listbase(anim_data.drivers, callback);
is_changed = is_changed || local_is_changed;
}
/* NOTE: New layered actions system can be ignored here, it did not exist together with GPv2.
*/
if (this->skip_nla) {
return is_changed;
}
LISTBASE_FOREACH (NlaTrack *, nla_track, &anim_data.nla_tracks) {
LISTBASE_FOREACH (NlaStrip *, nla_strip, &nla_track->strips) {
const bool local_is_changed = this->nla_strip_fcurve_foreach(*nla_strip, callback);
is_changed = is_changed || local_is_changed;
}
}
return is_changed;
}
bool action_process(bAction &action, blender::FunctionRef<ActionCallback> callback) const
{
if (callback(action)) {
DEG_id_tag_update(&action.id, ID_RECALC_ANIMATION);
return true;
}
return false;
}
bool nla_strip_action_foreach(NlaStrip &nla_strip,
blender::FunctionRef<ActionCallback> callback) const
{
bool is_changed = false;
if (nla_strip.act) {
is_changed = action_process(*nla_strip.act, callback);
}
LISTBASE_FOREACH (NlaStrip *, nla_strip_children, &nla_strip.strips) {
is_changed = is_changed || this->nla_strip_action_foreach(*nla_strip_children, callback);
}
return is_changed;
}
bool animdata_action_foreach(AnimData &anim_data,
blender::FunctionRef<ActionCallback> callback) const
{
bool is_changed = false;
if (anim_data.action) {
is_changed = is_changed || action_process(*anim_data.action, callback);
}
if (anim_data.tmpact) {
is_changed = is_changed || action_process(*anim_data.tmpact, callback);
}
/* NOTE: New layered actions system can be ignored here, it did not exist together with GPv2.
*/
if (this->skip_nla) {
return is_changed;
}
LISTBASE_FOREACH (NlaTrack *, nla_track, &anim_data.nla_tracks) {
LISTBASE_FOREACH (NlaStrip *, nla_strip, &nla_track->strips) {
is_changed = is_changed || this->nla_strip_action_foreach(*nla_strip, callback);
}
}
return is_changed;
}
public:
/**
* Check whether the source animation data contains FCurves that need to be converted/moved to
* the destination animation data.
*/
bool source_has_animation_to_convert() const
{
if (!this->is_valid()) {
return false;
}
if (GS(id_src.name) != GS(id_dst.name)) {
return true;
}
bool has_animation = false;
auto animation_detection_cb = [&](bAction *owner_action, FCurve &fcurve) -> bool {
/* Early out if we already know that the target data is animated. */
if (has_animation) {
return false;
}
if (!this->animation_fcurve_is_valid(owner_action, fcurve)) {
return false;
}
if (this->fcurve_convertors.is_empty()) {
has_animation = true;
return false;
}
StringRefNull rna_path = fcurve.rna_path;
for (const AnimDataFCurveConvertor &fcurve_convertor : this->fcurve_convertors) {
const std::string rna_path_src = fmt::format(
"{}{}", this->root_path_src, fcurve_convertor.relative_rna_path_src);
if (rna_path == rna_path_src) {
has_animation = true;
return false;
}
}
return false;
};
this->animdata_fcurve_foreach(*this->animdata_src, animation_detection_cb);
return has_animation;
}
/**
* Convert relevant FCurves, i.e. modify their RNA path to match destination data.
*
* \note Edited FCurves will remain in the source animation data after this process. Once all
* source animation data has been processed, #fcurves_convert_finalize has to be called.
*/
void fcurves_convert()
{
if (!this->is_valid()) {
return;
}
auto fcurve_convert_cb = [&](const AnimDataFCurveConvertor *fcurve_convertor,
bAction *owner_action,
FCurve &fcurve,
const std::string &rna_path_dst) {
MEM_freeN(fcurve.rna_path);
fcurve.rna_path = BLI_strdupn(rna_path_dst.c_str(), rna_path_dst.size());
if (fcurve_convertor && fcurve_convertor->convert_cb) {
fcurve_convertor->convert_cb(fcurve);
}
this->has_changes = true;
if (!this->is_transfer_between_ids) {
return;
}
if (owner_action) {
if (owner_action == this->animdata_src->action) {
this->fcurves_from_src_main_action.append(&fcurve);
}
else if (owner_action == this->animdata_src->tmpact) {
this->fcurves_from_src_tmp_action.append(&fcurve);
}
}
else { /* Driver */
this->fcurves_from_src_drivers.append(&fcurve);
}
};
/* Update all FCurves which RNA path starts with the #root_path_src. */
if (this->fcurve_convertors.is_empty()) {
auto fcurve_root_path_convert_cb = [&](bAction *owner_action, FCurve &fcurve) -> bool {
if (!legacy_fcurves_is_valid_for_root_path(fcurve, this->root_path_src)) {
return false;
}
StringRefNull rna_path = fcurve.rna_path;
const std::string rna_path_dst = fmt::format(
"{}{}", this->root_path_dst, rna_path.substr(int64_t(this->root_path_src.size())));
fcurve_convert_cb(nullptr, owner_action, fcurve, rna_path_dst);
return true;
};
this->animdata_fcurve_foreach(*(this->animdata_src), fcurve_root_path_convert_cb);
return;
}
/* Update all FCurves which RNA path starts with the #root_path_src, and remains of the path
* matches one of the entries in #fcurve_convertors. */
auto fcurve_full_path_convert_cb = [&](bAction *owner_action, FCurve &fcurve) -> bool {
if (!animation_fcurve_is_valid(owner_action, fcurve)) {
return false;
}
StringRefNull rna_path = fcurve.rna_path;
for (const AnimDataFCurveConvertor &fcurve_convertor : this->fcurve_convertors) {
const std::string rna_path_src = fmt::format(
"{}{}", this->root_path_src, fcurve_convertor.relative_rna_path_src);
if (rna_path == rna_path_src) {
const std::string rna_path_dst = fmt::format(
"{}{}", this->root_path_dst, fcurve_convertor.relative_rna_path_dst);
fcurve_convert_cb(&fcurve_convertor, owner_action, fcurve, rna_path_dst);
return true;
}
}
return false;
};
this->animdata_fcurve_foreach(*(this->animdata_src), fcurve_full_path_convert_cb);
}
/**
* Finalize FCurves conversion. Typically, this #AnimDataConvertor should not be used after this
* call.
*
* Currently, this call merely ensure depsgraph update in case of conversion of animation data
* within a same ID.
*
* When transferring animation between source and destination IDs, this call actually moves the
* processed fcurves accumulated by previous call(s) to #fcurves_convert.
*/
void fcurves_convert_finalize()
{
if (!this->is_valid()) {
return;
}
/* Ensure existing actions moved to a different ID type keep a 'valid' `idroot` value. Not
* essential, but 'nice to have'. */
if (GS(this->id_src.name) != GS(this->id_dst.name)) {
if (!this->animdata_dst) {
this->animdata_dst = BKE_animdata_ensure_id(&this->id_dst);
}
auto actions_idroot_ensure = [&](bAction &action) -> bool {
BKE_animdata_action_ensure_idroot(&this->id_dst, &action);
return true;
};
this->animdata_action_foreach(*this->animdata_dst, actions_idroot_ensure);
}
if (&id_src == &id_dst) {
if (this->has_changes) {
DEG_id_tag_update(&this->id_src, ID_RECALC_ANIMATION);
DEG_relations_tag_update(&this->conversion_data.bmain);
}
return;
}
if (this->fcurves_from_src_main_action.is_empty() &&
this->fcurves_from_src_tmp_action.is_empty() && this->fcurves_from_src_drivers.is_empty())
{
return;
}
if (!this->animdata_dst) {
this->animdata_dst = BKE_animdata_ensure_id(&this->id_dst);
}
auto fcurves_move = [&](bAction *action_dst,
const animrig::slot_handle_t slot_handle_dst,
bAction *action_src,
const Span<FCurve *> fcurves) {
for (FCurve *fcurve : fcurves) {
animrig::action_fcurve_move(
action_dst->wrap(), slot_handle_dst, action_src->wrap(), *fcurve);
}
};
auto fcurves_move_between_listbases =
[&](ListBase &fcurves_dst, ListBase &fcurves_src, const Span<FCurve *> fcurves) {
for (FCurve *fcurve : fcurves) {
BLI_assert(BLI_findindex(&fcurves_src, fcurve) >= 0);
BLI_remlink(&fcurves_src, fcurve);
BLI_addtail(&fcurves_dst, fcurve);
}
};
if (!this->fcurves_from_src_main_action.is_empty()) {
if (!this->animdata_dst->action) {
/* Create a new action. */
animrig::Action &action = animrig::action_add(
this->conversion_data.bmain,
this->animdata_src->action ? this->animdata_src->action->id.name + 2 : nullptr);
action.slot_add_for_id(this->id_dst);
const bool ok = animrig::assign_action(&action, {this->id_dst, *this->animdata_dst});
BLI_assert_msg(ok, "Expecting action assignment to work when converting Grease Pencil");
UNUSED_VARS_NDEBUG(ok);
}
fcurves_move(this->animdata_dst->action,
this->animdata_dst->slot_handle,
this->animdata_src->action,
this->fcurves_from_src_main_action);
this->fcurves_from_src_main_action.clear();
}
if (!this->fcurves_from_src_tmp_action.is_empty()) {
if (!this->animdata_dst->tmpact) {
/* Create a new tmpact. */
animrig::Action &tmpact = animrig::action_add(
this->conversion_data.bmain,
this->animdata_src->tmpact ? this->animdata_src->tmpact->id.name + 2 : nullptr);
tmpact.slot_add_for_id(this->id_dst);
const bool ok = animrig::assign_tmpaction(&tmpact, {this->id_dst, *this->animdata_dst});
BLI_assert_msg(ok, "Expecting tmpact assignment to work when converting Grease Pencil");
UNUSED_VARS_NDEBUG(ok);
}
fcurves_move(this->animdata_dst->tmpact,
this->animdata_dst->tmp_slot_handle,
this->animdata_src->tmpact,
this->fcurves_from_src_tmp_action);
this->fcurves_from_src_tmp_action.clear();
}
if (!this->fcurves_from_src_drivers.is_empty()) {
fcurves_move_between_listbases(this->animdata_dst->drivers,
this->animdata_src->drivers,
this->fcurves_from_src_drivers);
this->fcurves_from_src_drivers.clear();
}
DEG_id_tag_update(&this->id_dst, ID_RECALC_ANIMATION);
DEG_id_tag_update(&this->id_src, ID_RECALC_ANIMATION);
DEG_relations_tag_update(&this->conversion_data.bmain);
}
};
/* \} */
/**
* Find vertex groups that have assigned vertices in this drawing.
* Returns:
* - ListBase with used vertex group names (bDeformGroup)
* - Array of indices in the new vertex group list for remapping
*/
static void find_used_vertex_groups(const bGPDframe &gpf,
const ListBase &vertex_group_names,
const int num_vertex_groups,
ListBase &r_vertex_group_names,
Array<int> &r_indices)
{
Array<int> is_group_used(num_vertex_groups, false);
LISTBASE_FOREACH (bGPDstroke *, gps, &gpf.strokes) {
if (!gps->dvert) {
continue;
}
Span<MDeformVert> dverts = {gps->dvert, gps->totpoints};
for (const MDeformVert &dvert : dverts) {
for (const MDeformWeight &weight : Span<MDeformWeight>{dvert.dw, dvert.totweight}) {
if (weight.def_nr >= num_vertex_groups) {
/* Ignore invalid deform weight group indices. */
continue;
}
is_group_used[weight.def_nr] = true;
}
}
}
BLI_listbase_clear(&r_vertex_group_names);
r_indices.reinitialize(num_vertex_groups);
int new_group_i = 0;
int old_group_i;
LISTBASE_FOREACH_INDEX (const bDeformGroup *, def_group, &vertex_group_names, old_group_i) {
if (!is_group_used[old_group_i]) {
r_indices[old_group_i] = -1;
continue;
}
r_indices[old_group_i] = new_group_i++;
bDeformGroup *def_group_copy = static_cast<bDeformGroup *>(MEM_dupallocN(def_group));
BLI_addtail(&r_vertex_group_names, def_group_copy);
}
}
/*
* This takes the legacy UV transforms and returns the stroke-space to texture-space matrix.
*/
static float3x2 get_legacy_stroke_to_texture_matrix(const float2 uv_translation,
const float uv_rotation,
const float2 uv_scale)
{
using namespace blender;
/* Bounding box data. */
const float2 minv = float2(-1.0f, -1.0f);
const float2 maxv = float2(1.0f, 1.0f);
/* Center of rotation. */
const float2 center = float2(0.5f, 0.5f);
const float2 uv_scale_inv = math::safe_rcp(uv_scale);
const float2 diagonal = maxv - minv;
const float sin_rotation = sin(uv_rotation);
const float cos_rotation = cos(uv_rotation);
const float2x2 rotation = float2x2(float2(cos_rotation, sin_rotation),
float2(-sin_rotation, cos_rotation));
float3x2 texture_matrix = float3x2::identity();
/* Apply bounding box re-scaling. */
texture_matrix[2] -= minv;
texture_matrix = math::from_scale<float2x2>(1.0f / diagonal) * texture_matrix;
/* Apply translation. */
texture_matrix[2] += uv_translation;
/* Apply rotation. */
texture_matrix[2] -= center;
texture_matrix = rotation * texture_matrix;
texture_matrix[2] += center;
/* Apply scale. */
texture_matrix = math::from_scale<float2x2>(uv_scale_inv) * texture_matrix;
return texture_matrix;
}
/*
* This gets the legacy layer-space to stroke-space matrix.
*/
static blender::float4x2 get_legacy_layer_to_stroke_matrix(bGPDstroke *gps)
{
using namespace blender;
using namespace blender::math;
const bGPDspoint *points = gps->points;
const int totpoints = gps->totpoints;
if (totpoints < 2) {
return float4x2::identity();
}
const bGPDspoint *point0 = &points[0];
const bGPDspoint *point1 = &points[1];
const bGPDspoint *point3 = &points[int(totpoints * 0.75f)];
const float3 pt0 = float3(point0->x, point0->y, point0->z);
const float3 pt1 = float3(point1->x, point1->y, point1->z);
const float3 pt3 = float3(point3->x, point3->y, point3->z);
/* Local X axis (p0 -> p1) */
const float3 local_x = normalize(pt1 - pt0);
/* Point vector at 3/4 */
const float3 local_3 = (totpoints == 2) ? (pt3 * 0.001f) - pt0 : pt3 - pt0;
/* Vector orthogonal to polygon plane. */
const float3 normal = cross(local_x, local_3);
/* Local Y axis (cross to normal/x axis). */
const float3 local_y = normalize(cross(normal, local_x));
/* Get local space using first point as origin. */
const float4x2 mat = transpose(
float2x4(float4(local_x, -dot(pt0, local_x)), float4(local_y, -dot(pt0, local_y))));
return mat;
}
static blender::float4x2 get_legacy_texture_matrix(bGPDstroke *gps)
{
const float3x2 texture_matrix = get_legacy_stroke_to_texture_matrix(
float2(gps->uv_translation), gps->uv_rotation, float2(gps->uv_scale));
const float4x2 strokemat = get_legacy_layer_to_stroke_matrix(gps);
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 texture_matrix * strokemat4x3;
}
static Drawing legacy_gpencil_frame_to_grease_pencil_drawing(const bGPDframe &gpf,
const ListBase &vertex_group_names)
{
/* Create a new empty drawing. */
Drawing drawing;
/* Get the number of points, number of strokes and the offsets for each stroke. */
Vector<int> offsets;
Vector<int8_t> curve_types;
offsets.append(0);
int num_strokes = 0;
int num_points = 0;
bool has_bezier_stroke = false;
LISTBASE_FOREACH (bGPDstroke *, gps, &gpf.strokes) {
/* Check for a valid edit curve. This is only the case when the `editcurve` exists and wasn't
* tagged for a stroke update. This tag indicates that the stroke points have changed,
* invalidating the edit curve. */
if (gps->editcurve != nullptr && (gps->editcurve->flag & GP_CURVE_NEEDS_STROKE_UPDATE) == 0) {
if (gps->editcurve->tot_curve_points == 0) {
continue;
}
has_bezier_stroke = true;
num_points += gps->editcurve->tot_curve_points;
curve_types.append(CURVE_TYPE_BEZIER);
}
else {
if (gps->totpoints == 0) {
continue;
}
num_points += gps->totpoints;
curve_types.append(CURVE_TYPE_POLY);
}
num_strokes++;
offsets.append(num_points);
}
/* Return if the legacy frame contains no strokes (or zero points). */
if (num_strokes == 0) {
return drawing;
}
/* Resize the CurvesGeometry. */
CurvesGeometry &curves = drawing.strokes_for_write();
curves.resize(num_points, num_strokes);
curves.offsets_for_write().copy_from(offsets);
OffsetIndices<int> points_by_curve = curves.points_by_curve();
MutableAttributeAccessor attributes = curves.attributes_for_write();
if (!has_bezier_stroke) {
/* All strokes are poly curves. */
curves.fill_curve_types(CURVE_TYPE_POLY);
}
else {
curves.curve_types_for_write().copy_from(curve_types);
curves.update_curve_types();
}
/* Find used vertex groups in this drawing. */
ListBase stroke_vertex_group_names;
Array<int> stroke_def_nr_map;
const int num_vertex_groups = BLI_listbase_count(&vertex_group_names);
find_used_vertex_groups(
gpf, vertex_group_names, num_vertex_groups, stroke_vertex_group_names, stroke_def_nr_map);
BLI_assert(BLI_listbase_is_empty(&curves.vertex_group_names));
curves.vertex_group_names = stroke_vertex_group_names;
const bool use_dverts = !BLI_listbase_is_empty(&curves.vertex_group_names);
/* Copy vertex weights and map the vertex group indices. */
auto copy_dvert = [&](const MDeformVert &src_dvert, MDeformVert &dst_dvert) {
dst_dvert = src_dvert;
dst_dvert.dw = static_cast<MDeformWeight *>(MEM_dupallocN(src_dvert.dw));
const MutableSpan<MDeformWeight> vertex_weights = {dst_dvert.dw, dst_dvert.totweight};
for (MDeformWeight &weight : vertex_weights) {
if (weight.def_nr >= num_vertex_groups) {
/* Ignore invalid deform weight group indices. */
continue;
}
/* Map def_nr to the reduced vertex group list. */
weight.def_nr = stroke_def_nr_map[weight.def_nr];
}
};
/* Point Attributes. */
MutableSpan<float3> positions = curves.positions_for_write();
MutableSpan<float3> handle_positions_left = has_bezier_stroke ?
curves.handle_positions_left_for_write() :
MutableSpan<float3>();
MutableSpan<float3> handle_positions_right = has_bezier_stroke ?
curves.handle_positions_right_for_write() :
MutableSpan<float3>();
MutableSpan<float> radii = drawing.radii_for_write();
MutableSpan<float> opacities = drawing.opacities_for_write();
/* Note: Since we *know* the drawing are created from scratch, we assume that the following
* `lookup_or_add_for_write_span` calls always return valid writers. */
SpanAttributeWriter<float> delta_times = attributes.lookup_or_add_for_write_span<float>(
"delta_time", AttrDomain::Point);
SpanAttributeWriter<float> rotations = attributes.lookup_or_add_for_write_span<float>(
"rotation", AttrDomain::Point);
MutableSpan<ColorGeometry4f> vertex_colors = drawing.vertex_colors_for_write();
SpanAttributeWriter<bool> selection = attributes.lookup_or_add_for_write_span<bool>(
".selection", AttrDomain::Point);
MutableSpan<MDeformVert> dverts = use_dverts ? curves.wrap().deform_verts_for_write() :
MutableSpan<MDeformVert>();
/* Curve Attributes. */
SpanAttributeWriter<bool> stroke_cyclic = attributes.lookup_or_add_for_write_span<bool>(
"cyclic", AttrDomain::Curve);
SpanAttributeWriter<float> stroke_init_times = attributes.lookup_or_add_for_write_span<float>(
"init_time", AttrDomain::Curve);
SpanAttributeWriter<int8_t> stroke_start_caps = attributes.lookup_or_add_for_write_span<int8_t>(
"start_cap", AttrDomain::Curve);
SpanAttributeWriter<int8_t> stroke_end_caps = attributes.lookup_or_add_for_write_span<int8_t>(
"end_cap", AttrDomain::Curve);
SpanAttributeWriter<float> stroke_softness = attributes.lookup_or_add_for_write_span<float>(
"softness", AttrDomain::Curve);
SpanAttributeWriter<float> stroke_point_aspect_ratios =
attributes.lookup_or_add_for_write_span<float>("aspect_ratio", AttrDomain::Curve);
MutableSpan<ColorGeometry4f> stroke_fill_colors = drawing.fill_colors_for_write();
SpanAttributeWriter<int> stroke_materials = attributes.lookup_or_add_for_write_span<int>(
"material_index", AttrDomain::Curve);
Array<float4x2> legacy_texture_matrices(num_strokes);
int stroke_i = 0;
LISTBASE_FOREACH (bGPDstroke *, gps, &gpf.strokes) {
/* In GPv2 strokes with 0 points could technically be represented. In `CurvesGeometry` this is
* not the case and would be a bug. So we explicitly make sure to skip over strokes with no
* points. */
if (gps->totpoints == 0 ||
(gps->editcurve != nullptr && gps->editcurve->tot_curve_points == 0))
{
continue;
}
stroke_cyclic.span[stroke_i] = (gps->flag & GP_STROKE_CYCLIC) != 0;
/* Truncating time in ms to uint32 then we don't lose precision in lower bits. */
const uint32_t clamped_init_time = uint32_t(
std::clamp(gps->inittime * 1e3, 0.0, double(std::numeric_limits<uint32_t>::max())));
stroke_init_times.span[stroke_i] = float(clamped_init_time) / float(1e3);
stroke_start_caps.span[stroke_i] = int8_t(gps->caps[0]);
stroke_end_caps.span[stroke_i] = int8_t(gps->caps[1]);
stroke_softness.span[stroke_i] = 1.0f - gps->hardness;
stroke_point_aspect_ratios.span[stroke_i] = gps->aspect_ratio[0] /
max_ff(gps->aspect_ratio[1], 1e-8);
stroke_fill_colors[stroke_i] = ColorGeometry4f(gps->vert_color_fill);
stroke_materials.span[stroke_i] = gps->mat_nr;
const IndexRange points = points_by_curve[stroke_i];
const float stroke_thickness = float(gps->thickness) * LEGACY_RADIUS_CONVERSION_FACTOR;
MutableSpan<float3> dst_positions = positions.slice(points);
MutableSpan<float3> dst_handle_positions_left = has_bezier_stroke ?
handle_positions_left.slice(points) :
MutableSpan<float3>();
MutableSpan<float3> dst_handle_positions_right = has_bezier_stroke ?
handle_positions_right.slice(points) :
MutableSpan<float3>();
MutableSpan<float> dst_radii = radii.slice(points);
MutableSpan<float> dst_opacities = opacities.slice(points);
MutableSpan<float> dst_deltatimes = delta_times.span.slice(points);
MutableSpan<float> dst_rotations = rotations.span.slice(points);
MutableSpan<ColorGeometry4f> dst_vertex_colors = vertex_colors.slice(points);
MutableSpan<bool> dst_selection = selection.span.slice(points);
MutableSpan<MDeformVert> dst_dverts = use_dverts ? dverts.slice(points) :
MutableSpan<MDeformVert>();
if (curve_types[stroke_i] == CURVE_TYPE_POLY) {
BLI_assert(points.size() == gps->totpoints);
const Span<bGPDspoint> src_points{gps->points, gps->totpoints};
threading::parallel_for(src_points.index_range(), 4096, [&](const IndexRange range) {
for (const int point_i : range) {
const bGPDspoint &pt = src_points[point_i];
dst_positions[point_i] = float3(pt.x, pt.y, pt.z);
dst_radii[point_i] = stroke_thickness * pt.pressure;
dst_opacities[point_i] = pt.strength;
dst_deltatimes[point_i] = pt.time;
dst_rotations[point_i] = pt.uv_rot;
dst_vertex_colors[point_i] = ColorGeometry4f(pt.vert_color);
dst_selection[point_i] = (pt.flag & GP_SPOINT_SELECT) != 0;
if (use_dverts && gps->dvert) {
copy_dvert(gps->dvert[point_i], dst_dverts[point_i]);
}
}
});
}
else if (curve_types[stroke_i] == CURVE_TYPE_BEZIER) {
BLI_assert(gps->editcurve != nullptr);
BLI_assert(points.size() == gps->editcurve->tot_curve_points);
Span<bGPDcurve_point> src_curve_points{gps->editcurve->curve_points,
gps->editcurve->tot_curve_points};
threading::parallel_for(src_curve_points.index_range(), 4096, [&](const IndexRange range) {
for (const int point_i : range) {
const bGPDcurve_point &cpt = src_curve_points[point_i];
dst_positions[point_i] = float3(cpt.bezt.vec[1]);
dst_handle_positions_left[point_i] = float3(cpt.bezt.vec[0]);
dst_handle_positions_right[point_i] = float3(cpt.bezt.vec[2]);
dst_radii[point_i] = stroke_thickness * cpt.pressure;
dst_opacities[point_i] = cpt.strength;
dst_rotations[point_i] = cpt.uv_rot;
dst_vertex_colors[point_i] = ColorGeometry4f(cpt.vert_color);
dst_selection[point_i] = (cpt.flag & GP_CURVE_POINT_SELECT) != 0;
if (use_dverts && gps->dvert) {
copy_dvert(gps->dvert[point_i], dst_dverts[point_i]);
}
}
});
}
else {
/* Unknown curve type. */
BLI_assert_unreachable();
}
const float4x2 legacy_texture_matrix = get_legacy_texture_matrix(gps);
legacy_texture_matrices[stroke_i] = legacy_texture_matrix;
stroke_i++;
}
/* Ensure that the normals are up to date. */
curves.tag_normals_changed();
drawing.set_texture_matrices(legacy_texture_matrices.as_span(), curves.curves_range());
delta_times.finish();
rotations.finish();
selection.finish();
stroke_cyclic.finish();
stroke_init_times.finish();
stroke_start_caps.finish();
stroke_end_caps.finish();
stroke_softness.finish();
stroke_point_aspect_ratios.finish();
stroke_materials.finish();
return drawing;
}
static void legacy_gpencil_to_grease_pencil(ConversionData &conversion_data,
GreasePencil &grease_pencil,
bGPdata &gpd)
{
using namespace blender::bke::greasepencil;
if (gpd.flag & ID_FLAG_FAKEUSER) {
id_fake_user_set(&grease_pencil.id);
}
BLI_assert(!grease_pencil.id.properties);
if (gpd.id.properties) {
grease_pencil.id.properties = IDP_CopyProperty(gpd.id.properties);
grease_pencil.id.system_properties = IDP_CopyProperty(gpd.id.properties);
}
/** Convert Grease Pencil data flag. */
SET_FLAG_FROM_TEST(
grease_pencil.flag, (gpd.flag & GP_DATA_EXPAND) != 0, GREASE_PENCIL_ANIM_CHANNEL_EXPANDED);
SET_FLAG_FROM_TEST(grease_pencil.flag,
(gpd.flag & GP_DATA_AUTOLOCK_LAYERS) != 0,
GREASE_PENCIL_AUTOLOCK_LAYERS);
SET_FLAG_FROM_TEST(
grease_pencil.flag, (gpd.draw_mode == GP_DRAWMODE_3D), GREASE_PENCIL_STROKE_ORDER_3D);
int layer_idx = 0;
LISTBASE_FOREACH_INDEX (bGPDlayer *, gpl, &gpd.layers, layer_idx) {
/* Create a new layer. */
Layer &new_layer = grease_pencil.add_layer(StringRefNull(gpl->info, STRNLEN(gpl->info)));
/* Flags. */
new_layer.set_visible((gpl->flag & GP_LAYER_HIDE) == 0);
new_layer.set_locked((gpl->flag & GP_LAYER_LOCKED) != 0);
new_layer.set_selected((gpl->flag & GP_LAYER_SELECT) != 0);
SET_FLAG_FROM_TEST(
new_layer.base.flag, (gpl->flag & GP_LAYER_FRAMELOCK) != 0, GP_LAYER_TREE_NODE_MUTE);
SET_FLAG_FROM_TEST(new_layer.base.flag,
(gpl->flag & GP_LAYER_USE_LIGHTS) != 0,
GP_LAYER_TREE_NODE_USE_LIGHTS);
SET_FLAG_FROM_TEST(new_layer.base.flag,
(gpl->onion_flag & GP_LAYER_ONIONSKIN) == 0,
GP_LAYER_TREE_NODE_HIDE_ONION_SKINNING);
SET_FLAG_FROM_TEST(
new_layer.base.flag, (gpl->flag & GP_LAYER_USE_MASK) == 0, GP_LAYER_TREE_NODE_HIDE_MASKS);
/* Copy Dope-sheet channel color. */
copy_v3_v3(new_layer.base.color, gpl->color);
new_layer.blend_mode = int8_t(gpl->blend_mode);
new_layer.parent = gpl->parent;
new_layer.set_parent_bone_name(gpl->parsubstr);
/* GPv2 parent inverse matrix is only valid when parent is set. */
if (gpl->parent) {
copy_m4_m4(new_layer.parentinv, gpl->inverse);
}
copy_v3_v3(new_layer.translation, gpl->location);
copy_v3_v3(new_layer.rotation, gpl->rotation);
copy_v3_v3(new_layer.scale, gpl->scale);
new_layer.set_view_layer_name(gpl->viewlayername);
SET_FLAG_FROM_TEST(new_layer.base.flag,
(gpl->flag & GP_LAYER_DISABLE_MASKS_IN_VIEWLAYER) != 0,
GP_LAYER_TREE_NODE_DISABLE_MASKS_IN_VIEWLAYER);
/* Convert the layer masks. */
LISTBASE_FOREACH (bGPDlayer_Mask *, mask, &gpl->mask_layers) {
LayerMask *new_mask = MEM_new<LayerMask>(__func__, mask->name);
new_mask->flag = mask->flag;
BLI_addtail(&new_layer.masks, new_mask);
}
new_layer.opacity = gpl->opacity;
LISTBASE_FOREACH (bGPDframe *, gpf, &gpl->frames) {
Drawing *dst_drawing = grease_pencil.insert_frame(
new_layer, gpf->framenum, 0, eBezTriple_KeyframeType(gpf->key_type));
if (dst_drawing == nullptr) {
/* Might fail because GPv2 technically allowed overlapping keyframes on the same frame
* (very unlikely to occur in real world files). In GPv3, keyframes always have to be on
* different frames. In this case we can't create a keyframe and have to skip it. */
continue;
}
/* Convert the frame to a drawing. */
*dst_drawing = legacy_gpencil_frame_to_grease_pencil_drawing(*gpf, gpd.vertex_group_names);
/* This frame was just inserted above, so it should always exist. */
GreasePencilFrame &new_frame = *new_layer.frame_at(gpf->framenum);
SET_FLAG_FROM_TEST(new_frame.flag, (gpf->flag & GP_FRAME_SELECT), GP_FRAME_SELECTED);
}
if ((gpl->flag & GP_LAYER_ACTIVE) != 0) {
grease_pencil.set_active_layer(&new_layer);
}
}
/* Second loop, to write to layer attributes after all layers were created. */
MutableAttributeAccessor layer_attributes = grease_pencil.attributes_for_write();
/* NOTE: Layer Adjustments like the tint and the radius offsets are deliberately ignored here!
* These are converted to modifiers at the bottom of the stack to keep visual compatibility with
* GPv2. */
SpanAttributeWriter<int> layer_passes = layer_attributes.lookup_or_add_for_write_span<int>(
"pass_index", bke::AttrDomain::Layer);
layer_idx = 0;
LISTBASE_FOREACH_INDEX (bGPDlayer *, gpl, &gpd.layers, layer_idx) {
layer_passes.span[layer_idx] = int(gpl->pass_index);
}
layer_passes.finish();
/* Copy vertex group names and settings. */
BKE_defgroup_copy_list(&grease_pencil.vertex_group_names, &gpd.vertex_group_names);
grease_pencil.vertex_group_active_index = gpd.vertex_group_active_index;
/* Convert the onion skinning settings. */
GreasePencilOnionSkinningSettings &settings = grease_pencil.onion_skinning_settings;
settings.opacity = gpd.onion_factor;
settings.mode = gpd.onion_mode;
SET_FLAG_FROM_TEST(settings.flag,
((gpd.onion_flag & GP_ONION_GHOST_PREVCOL) != 0 &&
(gpd.onion_flag & GP_ONION_GHOST_NEXTCOL) != 0),
GP_ONION_SKINNING_USE_CUSTOM_COLORS);
SET_FLAG_FROM_TEST(
settings.flag, (gpd.onion_flag & GP_ONION_FADE) != 0, GP_ONION_SKINNING_USE_FADE);
SET_FLAG_FROM_TEST(
settings.flag, (gpd.onion_flag & GP_ONION_LOOP) != 0, GP_ONION_SKINNING_SHOW_LOOP);
/* Convert keytype filter to a bit flag. */
if (gpd.onion_keytype == -1) {
settings.filter = GREASE_PENCIL_ONION_SKINNING_FILTER_ALL;
}
else {
settings.filter = (1 << gpd.onion_keytype);
}
settings.num_frames_before = gpd.gstep;
settings.num_frames_after = gpd.gstep_next;
copy_v3_v3(settings.color_before, gpd.gcolor_prev);
copy_v3_v3(settings.color_after, gpd.gcolor_next);
BKE_id_materials_copy(&conversion_data.bmain, &gpd.id, &grease_pencil.id);
/* Copy animation data from legacy GP data.
*
* Note that currently, Actions IDs are not duplicated. They may be needed ultimately, but for
* the time being, assuming invalid fcurves/drivers are fine here. */
if (AnimData *gpd_animdata = BKE_animdata_from_id(&gpd.id)) {
grease_pencil.adt = BKE_animdata_copy_in_lib(
&conversion_data.bmain, gpd.id.lib, gpd_animdata, LIB_ID_COPY_DEFAULT);
/* Some property was renamed between legacy GP layers and new GreasePencil ones. */
AnimDataConvertor animdata_gpdata_transfer(
conversion_data, grease_pencil.id, gpd.id, {{".location", ".translation"}});
for (const Layer *layer_iter : grease_pencil.layers()) {
/* Data comes from versioned GPv2 layers, which have a fixed max length. */
char layer_name_esc[sizeof((bGPDlayer{}).info) * 2];
BLI_str_escape(layer_name_esc, layer_iter->name().c_str(), sizeof(layer_name_esc));
std::string layer_root_path = fmt::format("layers[\"{}\"]", layer_name_esc);
animdata_gpdata_transfer.root_path_dst = layer_root_path;
animdata_gpdata_transfer.root_path_src = layer_root_path;
animdata_gpdata_transfer.fcurves_convert();
}
animdata_gpdata_transfer.fcurves_convert_finalize();
}
}
constexpr const char *OFFSET_RADIUS_NODETREE_NAME = "Offset Radius GPv3 Conversion";
static bNodeTree *offset_radius_node_tree_add(ConversionData &conversion_data, Library *library)
{
using namespace blender;
/* NOTE: DO NOT translate this ID name, it is used to find a potentially already existing
* node-tree. */
bNodeTree *group = bke::node_tree_add_in_lib(
&conversion_data.bmain, library, OFFSET_RADIUS_NODETREE_NAME, "GeometryNodeTree");
if (!group->geometry_node_asset_traits) {
group->geometry_node_asset_traits = MEM_callocN<GeometryNodeAssetTraits>(__func__);
}
group->geometry_node_asset_traits->flag |= GEO_NODE_ASSET_MODIFIER;
group->tree_interface.add_socket(
DATA_("Geometry"), "", "NodeSocketGeometry", NODE_INTERFACE_SOCKET_INPUT, nullptr);
group->tree_interface.add_socket(
DATA_("Geometry"), "", "NodeSocketGeometry", NODE_INTERFACE_SOCKET_OUTPUT, nullptr);
bNodeTreeInterfaceSocket *radius_offset = group->tree_interface.add_socket(
DATA_("Offset"), "", "NodeSocketFloat", NODE_INTERFACE_SOCKET_INPUT, nullptr);
auto &radius_offset_data = *static_cast<bNodeSocketValueFloat *>(radius_offset->socket_data);
radius_offset_data.subtype = PROP_DISTANCE;
radius_offset_data.min = -FLT_MAX;
radius_offset_data.max = FLT_MAX;
group->tree_interface.add_socket(
DATA_("Layer"), "", "NodeSocketString", NODE_INTERFACE_SOCKET_INPUT, nullptr);
bNode *group_output = bke::node_add_node(nullptr, *group, "NodeGroupOutput");
group_output->location[0] = 800;
group_output->location[1] = 160;
bNode *group_input = bke::node_add_node(nullptr, *group, "NodeGroupInput");
group_input->location[0] = 0;
group_input->location[1] = 160;
bNode *set_curve_radius = bke::node_add_node(nullptr, *group, "GeometryNodeSetCurveRadius");
set_curve_radius->location[0] = 600;
set_curve_radius->location[1] = 160;
bNode *named_layer_selection = bke::node_add_node(
nullptr, *group, "GeometryNodeInputNamedLayerSelection");
named_layer_selection->location[0] = 200;
named_layer_selection->location[1] = 100;
bNode *input_radius = bke::node_add_node(nullptr, *group, "GeometryNodeInputRadius");
input_radius->location[0] = 0;
input_radius->location[1] = 0;
bNode *add = bke::node_add_node(nullptr, *group, "ShaderNodeMath");
add->custom1 = NODE_MATH_ADD;
add->location[0] = 200;
add->location[1] = 0;
bNode *clamp_radius = bke::node_add_node(nullptr, *group, "ShaderNodeClamp");
clamp_radius->location[0] = 400;
clamp_radius->location[1] = 0;
bNodeSocket *sock_max = bke::node_find_socket(*clamp_radius, SOCK_IN, "Max");
static_cast<bNodeSocketValueFloat *>(sock_max->default_value)->value = FLT_MAX;
bke::node_add_link(*group,
*group_input,
*bke::node_find_socket(*group_input, SOCK_OUT, "Socket_0"),
*set_curve_radius,
*bke::node_find_socket(*set_curve_radius, SOCK_IN, "Curve"));
bke::node_add_link(*group,
*set_curve_radius,
*bke::node_find_socket(*set_curve_radius, SOCK_OUT, "Curve"),
*group_output,
*bke::node_find_socket(*group_output, SOCK_IN, "Socket_1"));
bke::node_add_link(*group,
*group_input,
*bke::node_find_socket(*group_input, SOCK_OUT, "Socket_3"),
*named_layer_selection,
*bke::node_find_socket(*named_layer_selection, SOCK_IN, "Name"));
bke::node_add_link(*group,
*named_layer_selection,
*bke::node_find_socket(*named_layer_selection, SOCK_OUT, "Selection"),
*set_curve_radius,
*bke::node_find_socket(*set_curve_radius, SOCK_IN, "Selection"));
bke::node_add_link(*group,
*group_input,
*bke::node_find_socket(*group_input, SOCK_OUT, "Socket_2"),
*add,
*bke::node_find_socket(*add, SOCK_IN, "Value"));
bke::node_add_link(*group,
*input_radius,
*bke::node_find_socket(*input_radius, SOCK_OUT, "Radius"),
*add,
*bke::node_find_socket(*add, SOCK_IN, "Value_001"));
bke::node_add_link(*group,
*add,
*bke::node_find_socket(*add, SOCK_OUT, "Value"),
*clamp_radius,
*bke::node_find_socket(*clamp_radius, SOCK_IN, "Value"));
bke::node_add_link(*group,
*clamp_radius,
*bke::node_find_socket(*clamp_radius, SOCK_OUT, "Result"),
*set_curve_radius,
*bke::node_find_socket(*set_curve_radius, SOCK_IN, "Radius"));
LISTBASE_FOREACH (bNode *, node, &group->nodes) {
bke::node_set_selected(*node, false);
}
return group;
}
static void thickness_factor_to_modifier(ConversionData &conversion_data,
bGPdata &src_object_data,
Object &dst_object)
{
AnimDataConvertor animdata_thickness_transfer(
conversion_data, dst_object.id, src_object_data.id, {{"pixel_factor", ".thickness_factor"}});
animdata_thickness_transfer.root_path_src = "";
const float thickness_factor = src_object_data.pixfactor;
const bool has_thickness_factor_animation =
animdata_thickness_transfer.source_has_animation_to_convert();
const bool has_thickness_factor = thickness_factor != 1.0f || has_thickness_factor_animation;
if (!has_thickness_factor) {
return;
}
ModifierData *md = BKE_modifier_new(eModifierType_GreasePencilThickness);
GreasePencilThickModifierData *tmd = reinterpret_cast<GreasePencilThickModifierData *>(md);
tmd->thickness_fac = thickness_factor;
STRNCPY(md->name, DATA_("Thickness"));
BKE_modifier_unique_name(&dst_object.modifiers, md);
BLI_addtail(&dst_object.modifiers, md);
BKE_modifiers_persistent_uid_init(dst_object, *md);
if (has_thickness_factor_animation) {
char modifier_name_esc[MAX_NAME * 2];
BLI_str_escape(modifier_name_esc, md->name, sizeof(modifier_name_esc));
animdata_thickness_transfer.root_path_dst = fmt::format("modifiers[\"{}\"]",
modifier_name_esc);
animdata_thickness_transfer.fcurves_convert();
}
animdata_thickness_transfer.fcurves_convert_finalize();
}
static void fcurve_convert_thickness_cb(FCurve &fcurve)
{
if (fcurve.bezt) {
for (uint i = 0; i < fcurve.totvert; i++) {
BezTriple &bezier_triple = fcurve.bezt[i];
bezier_triple.vec[0][1] *= LEGACY_RADIUS_CONVERSION_FACTOR;
bezier_triple.vec[1][1] *= LEGACY_RADIUS_CONVERSION_FACTOR;
bezier_triple.vec[2][1] *= LEGACY_RADIUS_CONVERSION_FACTOR;
}
}
if (fcurve.fpt) {
for (uint i = 0; i < fcurve.totvert; i++) {
FPoint &fpoint = fcurve.fpt[i];
fpoint.vec[1] *= LEGACY_RADIUS_CONVERSION_FACTOR;
}
}
fcurve.flag &= ~FCURVE_INT_VALUES;
BKE_fcurve_handles_recalc(&fcurve);
}
static void legacy_object_thickness_modifier_thickness_anim(ConversionData &conversion_data,
Object &object)
{
if (BKE_animdata_from_id(&object.id) == nullptr) {
return;
}
/* NOTE: At this point, the animation was already transferred to the destination object. Now we
* just need to convert the fcurve data to be in the right space. */
AnimDataConvertor animdata_convert_thickness(
conversion_data,
object.id,
object.id,
{{".thickness", ".thickness", fcurve_convert_thickness_cb}});
LISTBASE_FOREACH (ModifierData *, tmd, &object.modifiers) {
if (ModifierType(tmd->type) != eModifierType_GreasePencilThickness) {
continue;
}
char modifier_name[MAX_NAME * 2];
BLI_str_escape(modifier_name, tmd->name, sizeof(modifier_name));
animdata_convert_thickness.root_path_src = fmt::format("modifiers[\"{}\"]", modifier_name);
animdata_convert_thickness.root_path_dst = fmt::format("modifiers[\"{}\"]", modifier_name);
if (!animdata_convert_thickness.source_has_animation_to_convert()) {
continue;
}
animdata_convert_thickness.fcurves_convert();
}
animdata_convert_thickness.fcurves_convert_finalize();
DEG_relations_tag_update(&conversion_data.bmain);
}
static void layer_adjustments_to_modifiers(ConversionData &conversion_data,
bGPdata &src_object_data,
Object &dst_object)
{
/* Handling of animation here is a bit complex, since paths needs to be updated, but also
* FCurves need to be transferred from legacy GPData animation to Object animation. */
AnimDataConvertor animdata_tint_transfer(
conversion_data,
dst_object.id,
src_object_data.id,
{{".tint_color", ".color"}, {".tint_factor", ".factor"}});
AnimDataConvertor animdata_thickness_transfer(
conversion_data,
dst_object.id,
src_object_data.id,
{{".line_change", "[\"Socket_2\"]", fcurve_convert_thickness_cb}});
/* Replace layer adjustments with modifiers. */
LISTBASE_FOREACH (bGPDlayer *, gpl, &src_object_data.layers) {
const float3 tint_color = float3(gpl->tintcolor);
const float tint_factor = gpl->tintcolor[3];
const int thickness_px = gpl->line_change;
char layer_name_esc[sizeof(gpl->info) * 2];
BLI_str_escape(layer_name_esc, gpl->info, sizeof(layer_name_esc));
animdata_tint_transfer.root_path_src = fmt::format("layers[\"{}\"]", layer_name_esc);
animdata_thickness_transfer.root_path_src = fmt::format("layers[\"{}\"]", layer_name_esc);
const bool has_tint_adjustment_animation =
animdata_tint_transfer.source_has_animation_to_convert();
const bool has_thickness_adjustment_animation =
animdata_thickness_transfer.source_has_animation_to_convert();
/* If tint or thickness are animated, relevant modifiers also need to be created. */
const bool has_tint_adjustment = tint_factor > 0.0f || has_tint_adjustment_animation;
const bool has_thickness_adjustment = thickness_px != 0 || has_thickness_adjustment_animation;
/* Tint adjustment. */
if (has_tint_adjustment) {
ModifierData *md = BKE_modifier_new(eModifierType_GreasePencilTint);
GreasePencilTintModifierData *tmd = reinterpret_cast<GreasePencilTintModifierData *>(md);
copy_v3_v3(tmd->color, tint_color);
tmd->factor = tint_factor;
STRNCPY(tmd->influence.layer_name, gpl->info);
char modifier_name[MAX_NAME];
SNPRINTF(modifier_name, "Tint %s", gpl->info);
STRNCPY(md->name, modifier_name);
BKE_modifier_unique_name(&dst_object.modifiers, md);
BLI_addtail(&dst_object.modifiers, md);
BKE_modifiers_persistent_uid_init(dst_object, *md);
if (has_tint_adjustment_animation) {
char modifier_name_esc[MAX_NAME * 2];
BLI_str_escape(modifier_name_esc, md->name, sizeof(modifier_name_esc));
animdata_tint_transfer.root_path_dst = fmt::format("modifiers[\"{}\"]", modifier_name_esc);
animdata_tint_transfer.fcurves_convert();
}
}
/* Thickness adjustment. */
if (has_thickness_adjustment) {
/* Convert the "pixel" offset value into a radius value.
* GPv2 used a conversion of 1 "px" = 0.001. */
/* NOTE: this offset may be negative. */
const float uniform_object_scale = math::average(float3(dst_object.scale));
const float radius_offset = math::safe_divide(
float(thickness_px) * LEGACY_RADIUS_CONVERSION_FACTOR, uniform_object_scale);
const auto offset_radius_ntree_ensure = [&](Library *owner_library) {
if (bNodeTree **ntree = conversion_data.offset_radius_ntree_by_library.lookup_ptr(
owner_library))
{
/* Node tree has already been found/created for this versioning call. */
return *ntree;
}
/* Try to find an existing group added by previous versioning to avoid adding duplicates.
*/
LISTBASE_FOREACH (bNodeTree *, ntree_iter, &conversion_data.bmain.nodetrees) {
if (ntree_iter->id.lib != owner_library) {
continue;
}
if (STREQ(ntree_iter->id.name + 2, OFFSET_RADIUS_NODETREE_NAME)) {
conversion_data.offset_radius_ntree_by_library.add_new(owner_library, ntree_iter);
return ntree_iter;
}
}
bNodeTree *new_ntree = offset_radius_node_tree_add(conversion_data, owner_library);
/* Remove the default user. The count is tracked manually when assigning to modifiers. */
id_us_min(&new_ntree->id);
conversion_data.offset_radius_ntree_by_library.add_new(owner_library, new_ntree);
BKE_ntree_update_after_single_tree_change(conversion_data.bmain, *new_ntree);
return new_ntree;
};
bNodeTree *offset_radius_node_tree = offset_radius_ntree_ensure(dst_object.id.lib);
auto *md = reinterpret_cast<NodesModifierData *>(BKE_modifier_new(eModifierType_Nodes));
char modifier_name[MAX_NAME];
SNPRINTF(modifier_name, "Thickness %s", gpl->info);
STRNCPY(md->modifier.name, modifier_name);
BKE_modifier_unique_name(&dst_object.modifiers, &md->modifier);
md->node_group = offset_radius_node_tree;
BLI_addtail(&dst_object.modifiers, md);
BKE_modifiers_persistent_uid_init(dst_object, md->modifier);
md->settings.properties = bke::idprop::create_group("Nodes Modifier Settings").release();
IDProperty *radius_offset_prop =
bke::idprop::create(DATA_("Socket_2"), radius_offset).release();
auto *ui_data = reinterpret_cast<IDPropertyUIDataFloat *>(
IDP_ui_data_ensure(radius_offset_prop));
ui_data->soft_min = 0.0;
ui_data->base.rna_subtype = PROP_TRANSLATION;
IDP_AddToGroup(md->settings.properties, radius_offset_prop);
IDP_AddToGroup(md->settings.properties,
bke::idprop::create(DATA_("Socket_3"), gpl->info).release());
if (has_thickness_adjustment_animation) {
char modifier_name_esc[MAX_NAME * 2];
BLI_str_escape(modifier_name_esc, md->modifier.name, sizeof(modifier_name_esc));
animdata_thickness_transfer.root_path_dst = fmt::format("modifiers[\"{}\"]",
modifier_name_esc);
animdata_thickness_transfer.fcurves_convert();
}
}
}
animdata_tint_transfer.fcurves_convert_finalize();
animdata_thickness_transfer.fcurves_convert_finalize();
DEG_relations_tag_update(&conversion_data.bmain);
}
static ModifierData &legacy_object_modifier_common(ConversionData &conversion_data,
Object &object,
const ModifierType type,
GpencilModifierData &legacy_md)
{
/* TODO: Copy of most of #ed::object::modifier_add, this should be a BKE_modifiers function
* actually. */
const ModifierTypeInfo *mti = BKE_modifier_get_info(type);
ModifierData &new_md = *BKE_modifier_new(type);
if (mti->flags & eModifierTypeFlag_RequiresOriginalData) {
ModifierData *md;
for (md = static_cast<ModifierData *>(object.modifiers.first);
md && BKE_modifier_get_info(ModifierType(md->type))->type == ModifierTypeType::OnlyDeform;
md = md->next)
{
;
}
BLI_insertlinkbefore(&object.modifiers, md, &new_md);
}
else {
BLI_addtail(&object.modifiers, &new_md);
}
/* Generate new persistent UID and best possible unique name. */
BKE_modifiers_persistent_uid_init(object, new_md);
if (legacy_md.name[0]) {
STRNCPY_UTF8(new_md.name, legacy_md.name);
}
BKE_modifier_unique_name(&object.modifiers, &new_md);
/* Handle common modifier data. */
new_md.mode = legacy_md.mode;
new_md.flag |= legacy_md.flag & (eModifierFlag_OverrideLibrary_Local | eModifierFlag_Active);
/* Attempt to copy UI state (panels) as best as possible. */
new_md.ui_expand_flag = legacy_md.ui_expand_flag;
/* Convert animation data if needed. */
if (BKE_animdata_from_id(&object.id)) {
AnimDataConvertor anim_convertor(conversion_data, object.id);
char legacy_name_esc[MAX_NAME * 2];
BLI_str_escape(legacy_name_esc, legacy_md.name, sizeof(legacy_name_esc));
anim_convertor.root_path_src = fmt::format("grease_pencil_modifiers[\"{}\"]", legacy_name_esc);
char new_name_esc[MAX_NAME * 2];
BLI_str_escape(new_name_esc, new_md.name, sizeof(new_name_esc));
anim_convertor.root_path_dst = fmt::format("modifiers[\"{}\"]", new_name_esc);
anim_convertor.fcurves_convert();
anim_convertor.fcurves_convert_finalize();
}
return new_md;
}
static void legacy_object_modifier_influence(GreasePencilModifierInfluenceData &influence,
StringRef layername,
const int layer_pass,
const bool invert_layer,
const bool invert_layer_pass,
Material **material,
const int material_pass,
const bool invert_material,
const bool invert_material_pass,
StringRef vertex_group_name,
const bool invert_vertex_group,
CurveMapping **custom_curve,
const bool use_custom_curve)
{
influence.flag = 0;
layername.copy_utf8_truncated(influence.layer_name);
if (invert_layer) {
influence.flag |= GREASE_PENCIL_INFLUENCE_INVERT_LAYER_FILTER;
}
influence.layer_pass = layer_pass;
if (layer_pass > 0) {
influence.flag |= GREASE_PENCIL_INFLUENCE_USE_LAYER_PASS_FILTER;
}
if (invert_layer_pass) {
influence.flag |= GREASE_PENCIL_INFLUENCE_INVERT_LAYER_PASS_FILTER;
}
if (material) {
influence.material = *material;
*material = nullptr;
}
if (invert_material) {
influence.flag |= GREASE_PENCIL_INFLUENCE_INVERT_MATERIAL_FILTER;
}
influence.material_pass = material_pass;
if (material_pass > 0) {
influence.flag |= GREASE_PENCIL_INFLUENCE_USE_MATERIAL_PASS_FILTER;
}
if (invert_material_pass) {
influence.flag |= GREASE_PENCIL_INFLUENCE_INVERT_MATERIAL_PASS_FILTER;
}
vertex_group_name.copy_utf8_truncated(influence.vertex_group_name);
if (invert_vertex_group) {
influence.flag |= GREASE_PENCIL_INFLUENCE_INVERT_VERTEX_GROUP;
}
if (custom_curve) {
if (influence.custom_curve) {
BKE_curvemapping_free(influence.custom_curve);
}
influence.custom_curve = *custom_curve;
*custom_curve = nullptr;
}
if (use_custom_curve) {
influence.flag |= GREASE_PENCIL_INFLUENCE_USE_CUSTOM_CURVE;
}
}
static void legacy_object_modifier_armature(ConversionData &conversion_data,
Object &object,
GpencilModifierData &legacy_md)
{
ModifierData &md = legacy_object_modifier_common(
conversion_data, object, eModifierType_GreasePencilArmature, legacy_md);
auto &md_armature = reinterpret_cast<GreasePencilArmatureModifierData &>(md);
auto &legacy_md_armature = reinterpret_cast<ArmatureGpencilModifierData &>(legacy_md);
md_armature.object = legacy_md_armature.object;
legacy_md_armature.object = nullptr;
md_armature.deformflag = legacy_md_armature.deformflag;
legacy_object_modifier_influence(md_armature.influence,
"",
0,
false,
false,
nullptr,
0,
false,
false,
legacy_md_armature.vgname,
legacy_md_armature.deformflag & ARM_DEF_INVERT_VGROUP,
nullptr,
false);
}
static void legacy_object_modifier_array(ConversionData &conversion_data,
Object &object,
GpencilModifierData &legacy_md)
{
ModifierData &md = legacy_object_modifier_common(
conversion_data, object, eModifierType_GreasePencilArray, legacy_md);
auto &md_array = reinterpret_cast<GreasePencilArrayModifierData &>(md);
auto &legacy_md_array = reinterpret_cast<ArrayGpencilModifierData &>(legacy_md);
md_array.object = legacy_md_array.object;
legacy_md_array.object = nullptr;
md_array.count = legacy_md_array.count;
md_array.flag = 0;
if (legacy_md_array.flag & GP_ARRAY_UNIFORM_RANDOM_SCALE) {
md_array.flag |= MOD_GREASE_PENCIL_ARRAY_UNIFORM_RANDOM_SCALE;
}
if (legacy_md_array.flag & GP_ARRAY_USE_OB_OFFSET) {
md_array.flag |= MOD_GREASE_PENCIL_ARRAY_USE_OB_OFFSET;
}
if (legacy_md_array.flag & GP_ARRAY_USE_OFFSET) {
md_array.flag |= MOD_GREASE_PENCIL_ARRAY_USE_OFFSET;
}
if (legacy_md_array.flag & GP_ARRAY_USE_RELATIVE) {
md_array.flag |= MOD_GREASE_PENCIL_ARRAY_USE_RELATIVE;
}
copy_v3_v3(md_array.offset, legacy_md_array.offset);
copy_v3_v3(md_array.shift, legacy_md_array.shift);
copy_v3_v3(md_array.rnd_offset, legacy_md_array.rnd_offset);
copy_v3_v3(md_array.rnd_rot, legacy_md_array.rnd_rot);
copy_v3_v3(md_array.rnd_scale, legacy_md_array.rnd_scale);
md_array.seed = legacy_md_array.seed;
md_array.mat_rpl = legacy_md_array.mat_rpl;
legacy_object_modifier_influence(md_array.influence,
legacy_md_array.layername,
legacy_md_array.layer_pass,
legacy_md_array.flag & GP_ARRAY_INVERT_LAYER,
legacy_md_array.flag & GP_ARRAY_INVERT_LAYERPASS,
&legacy_md_array.material,
legacy_md_array.pass_index,
legacy_md_array.flag & GP_ARRAY_INVERT_MATERIAL,
legacy_md_array.flag & GP_ARRAY_INVERT_PASS,
"",
false,
nullptr,
false);
}
static void legacy_object_modifier_color(ConversionData &conversion_data,
Object &object,
GpencilModifierData &legacy_md)
{
ModifierData &md = legacy_object_modifier_common(
conversion_data, object, eModifierType_GreasePencilColor, legacy_md);
auto &md_color = reinterpret_cast<GreasePencilColorModifierData &>(md);
auto &legacy_md_color = reinterpret_cast<ColorGpencilModifierData &>(legacy_md);
switch (eModifyColorGpencil_Flag(legacy_md_color.modify_color)) {
case GP_MODIFY_COLOR_BOTH:
md_color.color_mode = MOD_GREASE_PENCIL_COLOR_BOTH;
break;
case GP_MODIFY_COLOR_STROKE:
md_color.color_mode = MOD_GREASE_PENCIL_COLOR_STROKE;
break;
case GP_MODIFY_COLOR_FILL:
md_color.color_mode = MOD_GREASE_PENCIL_COLOR_FILL;
break;
case GP_MODIFY_COLOR_HARDNESS:
md_color.color_mode = MOD_GREASE_PENCIL_COLOR_HARDNESS;
break;
}
copy_v3_v3(md_color.hsv, legacy_md_color.hsv);
legacy_object_modifier_influence(md_color.influence,
legacy_md_color.layername,
legacy_md_color.layer_pass,
legacy_md_color.flag & GP_COLOR_INVERT_LAYER,
legacy_md_color.flag & GP_COLOR_INVERT_LAYERPASS,
&legacy_md_color.material,
legacy_md_color.pass_index,
legacy_md_color.flag & GP_COLOR_INVERT_MATERIAL,
legacy_md_color.flag & GP_COLOR_INVERT_PASS,
"",
false,
&legacy_md_color.curve_intensity,
legacy_md_color.flag & GP_COLOR_CUSTOM_CURVE);
}
static void legacy_object_modifier_dash(ConversionData &conversion_data,
Object &object,
GpencilModifierData &legacy_md)
{
ModifierData &md = legacy_object_modifier_common(
conversion_data, object, eModifierType_GreasePencilDash, legacy_md);
auto &md_dash = reinterpret_cast<GreasePencilDashModifierData &>(md);
auto &legacy_md_dash = reinterpret_cast<DashGpencilModifierData &>(legacy_md);
md_dash.dash_offset = legacy_md_dash.dash_offset;
md_dash.segment_active_index = legacy_md_dash.segment_active_index;
md_dash.segments_num = legacy_md_dash.segments_len;
MEM_SAFE_FREE(md_dash.segments_array);
md_dash.segments_array = MEM_calloc_arrayN<GreasePencilDashModifierSegment>(
legacy_md_dash.segments_len, __func__);
for (const int i : IndexRange(md_dash.segments_num)) {
GreasePencilDashModifierSegment &dst_segment = md_dash.segments_array[i];
const DashGpencilModifierSegment &src_segment = legacy_md_dash.segments[i];
STRNCPY(dst_segment.name, src_segment.name);
dst_segment.flag = 0;
if (src_segment.flag & GP_DASH_USE_CYCLIC) {
dst_segment.flag |= MOD_GREASE_PENCIL_DASH_USE_CYCLIC;
}
dst_segment.dash = src_segment.dash;
dst_segment.gap = src_segment.gap;
dst_segment.opacity = src_segment.opacity;
dst_segment.radius = src_segment.radius;
dst_segment.mat_nr = src_segment.mat_nr;
}
legacy_object_modifier_influence(md_dash.influence,
legacy_md_dash.layername,
legacy_md_dash.layer_pass,
legacy_md_dash.flag & GP_DASH_INVERT_LAYER,
legacy_md_dash.flag & GP_DASH_INVERT_LAYERPASS,
&legacy_md_dash.material,
legacy_md_dash.pass_index,
legacy_md_dash.flag & GP_DASH_INVERT_MATERIAL,
legacy_md_dash.flag & GP_DASH_INVERT_PASS,
"",
false,
nullptr,
false);
}
static void legacy_object_modifier_envelope(ConversionData &conversion_data,
Object &object,
GpencilModifierData &legacy_md)
{
ModifierData &md = legacy_object_modifier_common(
conversion_data, object, eModifierType_GreasePencilEnvelope, legacy_md);
auto &md_envelope = reinterpret_cast<GreasePencilEnvelopeModifierData &>(md);
auto &legacy_md_envelope = reinterpret_cast<EnvelopeGpencilModifierData &>(legacy_md);
switch (eEnvelopeGpencil_Mode(legacy_md_envelope.mode)) {
case GP_ENVELOPE_DEFORM:
md_envelope.mode = MOD_GREASE_PENCIL_ENVELOPE_DEFORM;
break;
case GP_ENVELOPE_SEGMENTS:
md_envelope.mode = MOD_GREASE_PENCIL_ENVELOPE_SEGMENTS;
break;
case GP_ENVELOPE_FILLS:
md_envelope.mode = MOD_GREASE_PENCIL_ENVELOPE_FILLS;
break;
}
md_envelope.mat_nr = legacy_md_envelope.mat_nr;
md_envelope.thickness = legacy_md_envelope.thickness;
md_envelope.strength = legacy_md_envelope.strength;
md_envelope.skip = legacy_md_envelope.skip;
md_envelope.spread = legacy_md_envelope.spread;
legacy_object_modifier_influence(md_envelope.influence,
legacy_md_envelope.layername,
legacy_md_envelope.layer_pass,
legacy_md_envelope.flag & GP_ENVELOPE_INVERT_LAYER,
legacy_md_envelope.flag & GP_ENVELOPE_INVERT_LAYERPASS,
&legacy_md_envelope.material,
legacy_md_envelope.pass_index,
legacy_md_envelope.flag & GP_ENVELOPE_INVERT_MATERIAL,
legacy_md_envelope.flag & GP_ENVELOPE_INVERT_PASS,
legacy_md_envelope.vgname,
legacy_md_envelope.flag & GP_ENVELOPE_INVERT_VGROUP,
nullptr,
false);
}
static void legacy_object_modifier_hook(ConversionData &conversion_data,
Object &object,
GpencilModifierData &legacy_md)
{
ModifierData &md = legacy_object_modifier_common(
conversion_data, object, eModifierType_GreasePencilHook, legacy_md);
auto &md_hook = reinterpret_cast<GreasePencilHookModifierData &>(md);
auto &legacy_md_hook = reinterpret_cast<HookGpencilModifierData &>(legacy_md);
md_hook.flag = 0;
if (legacy_md_hook.flag & GP_HOOK_UNIFORM_SPACE) {
md_hook.flag |= MOD_GREASE_PENCIL_HOOK_UNIFORM_SPACE;
}
switch (eHookGpencil_Falloff(legacy_md_hook.falloff_type)) {
case eGPHook_Falloff_None:
md_hook.falloff_type = MOD_GREASE_PENCIL_HOOK_Falloff_None;
break;
case eGPHook_Falloff_Curve:
md_hook.falloff_type = MOD_GREASE_PENCIL_HOOK_Falloff_Curve;
break;
case eGPHook_Falloff_Sharp:
md_hook.falloff_type = MOD_GREASE_PENCIL_HOOK_Falloff_Sharp;
break;
case eGPHook_Falloff_Smooth:
md_hook.falloff_type = MOD_GREASE_PENCIL_HOOK_Falloff_Smooth;
break;
case eGPHook_Falloff_Root:
md_hook.falloff_type = MOD_GREASE_PENCIL_HOOK_Falloff_Root;
break;
case eGPHook_Falloff_Linear:
md_hook.falloff_type = MOD_GREASE_PENCIL_HOOK_Falloff_Linear;
break;
case eGPHook_Falloff_Const:
md_hook.falloff_type = MOD_GREASE_PENCIL_HOOK_Falloff_Const;
break;
case eGPHook_Falloff_Sphere:
md_hook.falloff_type = MOD_GREASE_PENCIL_HOOK_Falloff_Sphere;
break;
case eGPHook_Falloff_InvSquare:
md_hook.falloff_type = MOD_GREASE_PENCIL_HOOK_Falloff_InvSquare;
break;
}
md_hook.object = legacy_md_hook.object;
legacy_md_hook.object = nullptr;
STRNCPY(md_hook.subtarget, legacy_md_hook.subtarget);
copy_m4_m4(md_hook.parentinv, legacy_md_hook.parentinv);
copy_v3_v3(md_hook.cent, legacy_md_hook.cent);
md_hook.falloff = legacy_md_hook.falloff;
md_hook.force = legacy_md_hook.force;
legacy_object_modifier_influence(md_hook.influence,
legacy_md_hook.layername,
legacy_md_hook.layer_pass,
legacy_md_hook.flag & GP_HOOK_INVERT_LAYER,
legacy_md_hook.flag & GP_HOOK_INVERT_LAYERPASS,
&legacy_md_hook.material,
legacy_md_hook.pass_index,
legacy_md_hook.flag & GP_HOOK_INVERT_MATERIAL,
legacy_md_hook.flag & GP_HOOK_INVERT_PASS,
legacy_md_hook.vgname,
legacy_md_hook.flag & GP_HOOK_INVERT_VGROUP,
&legacy_md_hook.curfalloff,
true);
}
static void legacy_object_modifier_lattice(ConversionData &conversion_data,
Object &object,
GpencilModifierData &legacy_md)
{
ModifierData &md = legacy_object_modifier_common(
conversion_data, object, eModifierType_GreasePencilLattice, legacy_md);
auto &md_lattice = reinterpret_cast<GreasePencilLatticeModifierData &>(md);
auto &legacy_md_lattice = reinterpret_cast<LatticeGpencilModifierData &>(legacy_md);
md_lattice.object = legacy_md_lattice.object;
legacy_md_lattice.object = nullptr;
md_lattice.strength = legacy_md_lattice.strength;
legacy_object_modifier_influence(md_lattice.influence,
legacy_md_lattice.layername,
legacy_md_lattice.layer_pass,
legacy_md_lattice.flag & GP_LATTICE_INVERT_LAYER,
legacy_md_lattice.flag & GP_LATTICE_INVERT_LAYERPASS,
&legacy_md_lattice.material,
legacy_md_lattice.pass_index,
legacy_md_lattice.flag & GP_LATTICE_INVERT_MATERIAL,
legacy_md_lattice.flag & GP_LATTICE_INVERT_PASS,
legacy_md_lattice.vgname,
legacy_md_lattice.flag & GP_LATTICE_INVERT_VGROUP,
nullptr,
false);
}
static void legacy_object_modifier_length(ConversionData &conversion_data,
Object &object,
GpencilModifierData &legacy_md)
{
ModifierData &md = legacy_object_modifier_common(
conversion_data, object, eModifierType_GreasePencilLength, legacy_md);
auto &md_length = reinterpret_cast<GreasePencilLengthModifierData &>(md);
auto &legacy_md_length = reinterpret_cast<LengthGpencilModifierData &>(legacy_md);
md_length.flag = legacy_md_length.flag;
md_length.start_fac = legacy_md_length.start_fac;
md_length.end_fac = legacy_md_length.end_fac;
md_length.rand_start_fac = legacy_md_length.rand_start_fac;
md_length.rand_end_fac = legacy_md_length.rand_end_fac;
md_length.rand_offset = legacy_md_length.rand_offset;
md_length.overshoot_fac = legacy_md_length.overshoot_fac;
md_length.seed = legacy_md_length.seed;
md_length.step = legacy_md_length.step;
md_length.mode = legacy_md_length.mode;
md_length.point_density = legacy_md_length.point_density;
md_length.segment_influence = legacy_md_length.segment_influence;
md_length.max_angle = legacy_md_length.max_angle;
legacy_object_modifier_influence(md_length.influence,
legacy_md_length.layername,
legacy_md_length.layer_pass,
legacy_md_length.flag & GP_LENGTH_INVERT_LAYER,
legacy_md_length.flag & GP_LENGTH_INVERT_LAYERPASS,
&legacy_md_length.material,
legacy_md_length.pass_index,
legacy_md_length.flag & GP_LENGTH_INVERT_MATERIAL,
legacy_md_length.flag & GP_LENGTH_INVERT_PASS,
"",
false,
nullptr,
false);
}
static void legacy_object_modifier_mirror(ConversionData &conversion_data,
Object &object,
GpencilModifierData &legacy_md)
{
ModifierData &md = legacy_object_modifier_common(
conversion_data, object, eModifierType_GreasePencilMirror, legacy_md);
auto &md_mirror = reinterpret_cast<GreasePencilMirrorModifierData &>(md);
auto &legacy_md_mirror = reinterpret_cast<MirrorGpencilModifierData &>(legacy_md);
md_mirror.object = legacy_md_mirror.object;
legacy_md_mirror.object = nullptr;
md_mirror.flag = 0;
if (legacy_md_mirror.flag & GP_MIRROR_AXIS_X) {
md_mirror.flag |= MOD_GREASE_PENCIL_MIRROR_AXIS_X;
}
if (legacy_md_mirror.flag & GP_MIRROR_AXIS_Y) {
md_mirror.flag |= MOD_GREASE_PENCIL_MIRROR_AXIS_Y;
}
if (legacy_md_mirror.flag & GP_MIRROR_AXIS_Z) {
md_mirror.flag |= MOD_GREASE_PENCIL_MIRROR_AXIS_Z;
}
legacy_object_modifier_influence(md_mirror.influence,
legacy_md_mirror.layername,
legacy_md_mirror.layer_pass,
legacy_md_mirror.flag & GP_MIRROR_INVERT_LAYER,
legacy_md_mirror.flag & GP_MIRROR_INVERT_LAYERPASS,
&legacy_md_mirror.material,
legacy_md_mirror.pass_index,
legacy_md_mirror.flag & GP_MIRROR_INVERT_MATERIAL,
legacy_md_mirror.flag & GP_MIRROR_INVERT_PASS,
"",
false,
nullptr,
false);
}
static void legacy_object_modifier_multiply(ConversionData &conversion_data,
Object &object,
GpencilModifierData &legacy_md)
{
ModifierData &md = legacy_object_modifier_common(
conversion_data, object, eModifierType_GreasePencilMultiply, legacy_md);
auto &md_multiply = reinterpret_cast<GreasePencilMultiModifierData &>(md);
auto &legacy_md_multiply = reinterpret_cast<MultiplyGpencilModifierData &>(legacy_md);
md_multiply.flag = 0;
if (legacy_md_multiply.flags & GP_MULTIPLY_ENABLE_FADING) {
md_multiply.flag |= MOD_GREASE_PENCIL_MULTIPLY_ENABLE_FADING;
}
md_multiply.duplications = legacy_md_multiply.duplications;
md_multiply.distance = legacy_md_multiply.distance;
md_multiply.offset = legacy_md_multiply.offset;
md_multiply.fading_center = legacy_md_multiply.fading_center;
md_multiply.fading_thickness = legacy_md_multiply.fading_thickness;
md_multiply.fading_opacity = legacy_md_multiply.fading_opacity;
/* NOTE: This looks wrong, but GPv2 version uses Mirror modifier flags in its `flag` property
* and own flags in its `flags` property. */
legacy_object_modifier_influence(md_multiply.influence,
legacy_md_multiply.layername,
legacy_md_multiply.layer_pass,
legacy_md_multiply.flag & GP_MIRROR_INVERT_LAYER,
legacy_md_multiply.flag & GP_MIRROR_INVERT_LAYERPASS,
&legacy_md_multiply.material,
legacy_md_multiply.pass_index,
legacy_md_multiply.flag & GP_MIRROR_INVERT_MATERIAL,
legacy_md_multiply.flag & GP_MIRROR_INVERT_PASS,
"",
false,
nullptr,
false);
}
static void legacy_object_modifier_noise(ConversionData &conversion_data,
Object &object,
GpencilModifierData &legacy_md)
{
ModifierData &md = legacy_object_modifier_common(
conversion_data, object, eModifierType_GreasePencilNoise, legacy_md);
auto &md_noise = reinterpret_cast<GreasePencilNoiseModifierData &>(md);
auto &legacy_md_noise = reinterpret_cast<NoiseGpencilModifierData &>(legacy_md);
md_noise.flag = legacy_md_noise.flag;
md_noise.factor = legacy_md_noise.factor;
md_noise.factor_strength = legacy_md_noise.factor_strength;
md_noise.factor_thickness = legacy_md_noise.factor_thickness;
md_noise.factor_uvs = legacy_md_noise.factor_uvs;
md_noise.noise_scale = legacy_md_noise.noise_scale;
md_noise.noise_offset = legacy_md_noise.noise_offset;
md_noise.noise_mode = legacy_md_noise.noise_mode;
md_noise.step = legacy_md_noise.step;
md_noise.seed = legacy_md_noise.seed;
legacy_object_modifier_influence(md_noise.influence,
legacy_md_noise.layername,
legacy_md_noise.layer_pass,
legacy_md_noise.flag & GP_NOISE_INVERT_LAYER,
legacy_md_noise.flag & GP_NOISE_INVERT_LAYERPASS,
&legacy_md_noise.material,
legacy_md_noise.pass_index,
legacy_md_noise.flag & GP_NOISE_INVERT_MATERIAL,
legacy_md_noise.flag & GP_NOISE_INVERT_PASS,
legacy_md_noise.vgname,
legacy_md_noise.flag & GP_NOISE_INVERT_VGROUP,
&legacy_md_noise.curve_intensity,
legacy_md_noise.flag & GP_NOISE_CUSTOM_CURVE);
}
static void legacy_object_modifier_offset(ConversionData &conversion_data,
Object &object,
GpencilModifierData &legacy_md)
{
ModifierData &md = legacy_object_modifier_common(
conversion_data, object, eModifierType_GreasePencilOffset, legacy_md);
auto &md_offset = reinterpret_cast<GreasePencilOffsetModifierData &>(md);
auto &legacy_md_offset = reinterpret_cast<OffsetGpencilModifierData &>(legacy_md);
md_offset.flag = 0;
if (legacy_md_offset.flag & GP_OFFSET_UNIFORM_RANDOM_SCALE) {
md_offset.flag |= MOD_GREASE_PENCIL_OFFSET_UNIFORM_RANDOM_SCALE;
}
switch (eOffsetGpencil_Mode(legacy_md_offset.mode)) {
case GP_OFFSET_RANDOM:
md_offset.offset_mode = MOD_GREASE_PENCIL_OFFSET_RANDOM;
break;
case GP_OFFSET_LAYER:
md_offset.offset_mode = MOD_GREASE_PENCIL_OFFSET_LAYER;
break;
case GP_OFFSET_MATERIAL:
md_offset.offset_mode = MOD_GREASE_PENCIL_OFFSET_MATERIAL;
break;
case GP_OFFSET_STROKE:
md_offset.offset_mode = MOD_GREASE_PENCIL_OFFSET_STROKE;
break;
}
copy_v3_v3(md_offset.loc, legacy_md_offset.loc);
copy_v3_v3(md_offset.rot, legacy_md_offset.rot);
copy_v3_v3(md_offset.scale, legacy_md_offset.scale);
copy_v3_v3(md_offset.stroke_loc, legacy_md_offset.rnd_offset);
copy_v3_v3(md_offset.stroke_rot, legacy_md_offset.rnd_rot);
copy_v3_v3(md_offset.stroke_scale, legacy_md_offset.rnd_scale);
md_offset.seed = legacy_md_offset.seed;
md_offset.stroke_step = legacy_md_offset.stroke_step;
md_offset.stroke_start_offset = legacy_md_offset.stroke_start_offset;
legacy_object_modifier_influence(md_offset.influence,
legacy_md_offset.layername,
legacy_md_offset.layer_pass,
legacy_md_offset.flag & GP_OFFSET_INVERT_LAYER,
legacy_md_offset.flag & GP_OFFSET_INVERT_LAYERPASS,
&legacy_md_offset.material,
legacy_md_offset.pass_index,
legacy_md_offset.flag & GP_OFFSET_INVERT_MATERIAL,
legacy_md_offset.flag & GP_OFFSET_INVERT_PASS,
legacy_md_offset.vgname,
legacy_md_offset.flag & GP_OFFSET_INVERT_VGROUP,
nullptr,
false);
}
static void legacy_object_modifier_opacity(ConversionData &conversion_data,
Object &object,
GpencilModifierData &legacy_md)
{
ModifierData &md = legacy_object_modifier_common(
conversion_data, object, eModifierType_GreasePencilOpacity, legacy_md);
auto &md_opacity = reinterpret_cast<GreasePencilOpacityModifierData &>(md);
auto &legacy_md_opacity = reinterpret_cast<OpacityGpencilModifierData &>(legacy_md);
md_opacity.flag = 0;
if (legacy_md_opacity.flag & GP_OPACITY_NORMALIZE) {
md_opacity.flag |= MOD_GREASE_PENCIL_OPACITY_USE_UNIFORM_OPACITY;
}
if (legacy_md_opacity.flag & GP_OPACITY_WEIGHT_FACTOR) {
md_opacity.flag |= MOD_GREASE_PENCIL_OPACITY_USE_WEIGHT_AS_FACTOR;
}
switch (eModifyColorGpencil_Flag(legacy_md_opacity.modify_color)) {
case GP_MODIFY_COLOR_BOTH:
md_opacity.color_mode = MOD_GREASE_PENCIL_COLOR_BOTH;
break;
case GP_MODIFY_COLOR_STROKE:
md_opacity.color_mode = MOD_GREASE_PENCIL_COLOR_STROKE;
break;
case GP_MODIFY_COLOR_FILL:
md_opacity.color_mode = MOD_GREASE_PENCIL_COLOR_FILL;
break;
case GP_MODIFY_COLOR_HARDNESS:
md_opacity.color_mode = MOD_GREASE_PENCIL_COLOR_HARDNESS;
break;
}
md_opacity.color_factor = legacy_md_opacity.factor;
md_opacity.hardness_factor = legacy_md_opacity.hardness;
/* Account for animation on renamed properties. */
char modifier_name[MAX_NAME * 2];
BLI_str_escape(modifier_name, md.name, sizeof(modifier_name));
AnimDataConvertor anim_convertor_factor(
conversion_data, object.id, object.id, {{".factor", ".color_factor"}});
anim_convertor_factor.root_path_src = fmt::format("modifiers[\"{}\"]", modifier_name);
anim_convertor_factor.root_path_dst = fmt::format("modifiers[\"{}\"]", modifier_name);
anim_convertor_factor.fcurves_convert();
anim_convertor_factor.fcurves_convert_finalize();
AnimDataConvertor anim_convertor_hardness(
conversion_data, object.id, object.id, {{".hardness", ".hardness_factor"}});
anim_convertor_hardness.root_path_src = fmt::format("modifiers[\"{}\"]", modifier_name);
anim_convertor_hardness.root_path_dst = fmt::format("modifiers[\"{}\"]", modifier_name);
anim_convertor_hardness.fcurves_convert();
anim_convertor_hardness.fcurves_convert_finalize();
DEG_relations_tag_update(&conversion_data.bmain);
legacy_object_modifier_influence(md_opacity.influence,
legacy_md_opacity.layername,
legacy_md_opacity.layer_pass,
legacy_md_opacity.flag & GP_OPACITY_INVERT_LAYER,
legacy_md_opacity.flag & GP_OPACITY_INVERT_LAYERPASS,
&legacy_md_opacity.material,
legacy_md_opacity.pass_index,
legacy_md_opacity.flag & GP_OPACITY_INVERT_MATERIAL,
legacy_md_opacity.flag & GP_OPACITY_INVERT_PASS,
legacy_md_opacity.vgname,
legacy_md_opacity.flag & GP_OPACITY_INVERT_VGROUP,
&legacy_md_opacity.curve_intensity,
legacy_md_opacity.flag & GP_OPACITY_CUSTOM_CURVE);
}
static void legacy_object_modifier_outline(ConversionData &conversion_data,
Object &object,
GpencilModifierData &legacy_md)
{
ModifierData &md = legacy_object_modifier_common(
conversion_data, object, eModifierType_GreasePencilOutline, legacy_md);
auto &md_outline = reinterpret_cast<GreasePencilOutlineModifierData &>(md);
auto &legacy_md_outline = reinterpret_cast<OutlineGpencilModifierData &>(legacy_md);
md_outline.flag = 0;
if (legacy_md_outline.flag & GP_OUTLINE_KEEP_SHAPE) {
md_outline.flag |= MOD_GREASE_PENCIL_OUTLINE_KEEP_SHAPE;
}
md_outline.object = legacy_md_outline.object;
legacy_md_outline.object = nullptr;
md_outline.outline_material = legacy_md_outline.outline_material;
legacy_md_outline.outline_material = nullptr;
md_outline.sample_length = legacy_md_outline.sample_length;
md_outline.subdiv = legacy_md_outline.subdiv;
md_outline.thickness = legacy_md_outline.thickness;
legacy_object_modifier_influence(md_outline.influence,
legacy_md_outline.layername,
legacy_md_outline.layer_pass,
legacy_md_outline.flag & GP_OUTLINE_INVERT_LAYER,
legacy_md_outline.flag & GP_OUTLINE_INVERT_LAYERPASS,
&legacy_md_outline.material,
legacy_md_outline.pass_index,
legacy_md_outline.flag & GP_OUTLINE_INVERT_MATERIAL,
legacy_md_outline.flag & GP_OUTLINE_INVERT_PASS,
"",
false,
nullptr,
false);
}
static void legacy_object_modifier_shrinkwrap(ConversionData &conversion_data,
Object &object,
GpencilModifierData &legacy_md)
{
ModifierData &md = legacy_object_modifier_common(
conversion_data, object, eModifierType_GreasePencilShrinkwrap, legacy_md);
auto &md_shrinkwrap = reinterpret_cast<GreasePencilShrinkwrapModifierData &>(md);
auto &legacy_md_shrinkwrap = reinterpret_cast<ShrinkwrapGpencilModifierData &>(legacy_md);
/* Shrinkwrap enums and flags do not have named types. */
/* MOD_SHRINKWRAP_NEAREST_SURFACE etc. */
md_shrinkwrap.shrink_type = legacy_md_shrinkwrap.shrink_type;
/* MOD_SHRINKWRAP_PROJECT_ALLOW_POS_DIR etc. */
md_shrinkwrap.shrink_opts = legacy_md_shrinkwrap.shrink_opts;
/* MOD_SHRINKWRAP_ON_SURFACE etc. */
md_shrinkwrap.shrink_mode = legacy_md_shrinkwrap.shrink_mode;
/* MOD_SHRINKWRAP_PROJECT_OVER_NORMAL etc. */
md_shrinkwrap.proj_axis = legacy_md_shrinkwrap.proj_axis;
md_shrinkwrap.target = legacy_md_shrinkwrap.target;
legacy_md_shrinkwrap.target = nullptr;
md_shrinkwrap.aux_target = legacy_md_shrinkwrap.aux_target;
legacy_md_shrinkwrap.aux_target = nullptr;
md_shrinkwrap.keep_dist = legacy_md_shrinkwrap.keep_dist;
md_shrinkwrap.proj_limit = legacy_md_shrinkwrap.proj_limit;
md_shrinkwrap.subsurf_levels = legacy_md_shrinkwrap.subsurf_levels;
md_shrinkwrap.smooth_factor = legacy_md_shrinkwrap.smooth_factor;
md_shrinkwrap.smooth_step = legacy_md_shrinkwrap.smooth_step;
legacy_object_modifier_influence(md_shrinkwrap.influence,
legacy_md_shrinkwrap.layername,
legacy_md_shrinkwrap.layer_pass,
legacy_md_shrinkwrap.flag & GP_SHRINKWRAP_INVERT_LAYER,
legacy_md_shrinkwrap.flag & GP_SHRINKWRAP_INVERT_LAYERPASS,
&legacy_md_shrinkwrap.material,
legacy_md_shrinkwrap.pass_index,
legacy_md_shrinkwrap.flag & GP_SHRINKWRAP_INVERT_MATERIAL,
legacy_md_shrinkwrap.flag & GP_SHRINKWRAP_INVERT_PASS,
legacy_md_shrinkwrap.vgname,
legacy_md_shrinkwrap.flag & GP_SHRINKWRAP_INVERT_VGROUP,
nullptr,
false);
}
static void legacy_object_modifier_smooth(ConversionData &conversion_data,
Object &object,
GpencilModifierData &legacy_md)
{
ModifierData &md = legacy_object_modifier_common(
conversion_data, object, eModifierType_GreasePencilSmooth, legacy_md);
auto &md_smooth = reinterpret_cast<GreasePencilSmoothModifierData &>(md);
auto &legacy_md_smooth = reinterpret_cast<SmoothGpencilModifierData &>(legacy_md);
md_smooth.flag = 0;
if (legacy_md_smooth.flag & GP_SMOOTH_MOD_LOCATION) {
md_smooth.flag |= MOD_GREASE_PENCIL_SMOOTH_MOD_LOCATION;
}
if (legacy_md_smooth.flag & GP_SMOOTH_MOD_STRENGTH) {
md_smooth.flag |= MOD_GREASE_PENCIL_SMOOTH_MOD_STRENGTH;
}
if (legacy_md_smooth.flag & GP_SMOOTH_MOD_THICKNESS) {
md_smooth.flag |= MOD_GREASE_PENCIL_SMOOTH_MOD_THICKNESS;
}
if (legacy_md_smooth.flag & GP_SMOOTH_MOD_UV) {
md_smooth.flag |= MOD_GREASE_PENCIL_SMOOTH_MOD_UV;
}
if (legacy_md_smooth.flag & GP_SMOOTH_KEEP_SHAPE) {
md_smooth.flag |= MOD_GREASE_PENCIL_SMOOTH_KEEP_SHAPE;
}
md_smooth.factor = legacy_md_smooth.factor;
md_smooth.step = legacy_md_smooth.step;
legacy_object_modifier_influence(md_smooth.influence,
legacy_md_smooth.layername,
legacy_md_smooth.layer_pass,
legacy_md_smooth.flag & GP_SMOOTH_INVERT_LAYER,
legacy_md_smooth.flag & GP_SMOOTH_INVERT_LAYERPASS,
&legacy_md_smooth.material,
legacy_md_smooth.pass_index,
legacy_md_smooth.flag & GP_SMOOTH_INVERT_MATERIAL,
legacy_md_smooth.flag & GP_SMOOTH_INVERT_PASS,
legacy_md_smooth.vgname,
legacy_md_smooth.flag & GP_SMOOTH_INVERT_VGROUP,
&legacy_md_smooth.curve_intensity,
legacy_md_smooth.flag & GP_SMOOTH_CUSTOM_CURVE);
}
static void legacy_object_modifier_subdiv(ConversionData &conversion_data,
Object &object,
GpencilModifierData &legacy_md)
{
ModifierData &md = legacy_object_modifier_common(
conversion_data, object, eModifierType_GreasePencilSubdiv, legacy_md);
auto &md_subdiv = reinterpret_cast<GreasePencilSubdivModifierData &>(md);
auto &legacy_md_subdiv = reinterpret_cast<SubdivGpencilModifierData &>(legacy_md);
switch (eSubdivGpencil_Type(legacy_md_subdiv.type)) {
case GP_SUBDIV_CATMULL:
md_subdiv.type = MOD_GREASE_PENCIL_SUBDIV_CATMULL;
break;
case GP_SUBDIV_SIMPLE:
md_subdiv.type = MOD_GREASE_PENCIL_SUBDIV_SIMPLE;
break;
}
md_subdiv.level = legacy_md_subdiv.level;
legacy_object_modifier_influence(md_subdiv.influence,
legacy_md_subdiv.layername,
legacy_md_subdiv.layer_pass,
legacy_md_subdiv.flag & GP_SUBDIV_INVERT_LAYER,
legacy_md_subdiv.flag & GP_SUBDIV_INVERT_LAYERPASS,
&legacy_md_subdiv.material,
legacy_md_subdiv.pass_index,
legacy_md_subdiv.flag & GP_SUBDIV_INVERT_MATERIAL,
legacy_md_subdiv.flag & GP_SUBDIV_INVERT_PASS,
"",
false,
nullptr,
false);
}
static void legacy_object_modifier_texture(ConversionData &conversion_data,
Object &object,
GpencilModifierData &legacy_md)
{
ModifierData &md = legacy_object_modifier_common(
conversion_data, object, eModifierType_GreasePencilTexture, legacy_md);
auto &md_texture = reinterpret_cast<GreasePencilTextureModifierData &>(md);
auto &legacy_md_texture = reinterpret_cast<TextureGpencilModifierData &>(legacy_md);
switch (eTextureGpencil_Mode(legacy_md_texture.mode)) {
case STROKE:
md_texture.mode = MOD_GREASE_PENCIL_TEXTURE_STROKE;
break;
case FILL:
md_texture.mode = MOD_GREASE_PENCIL_TEXTURE_FILL;
break;
case STROKE_AND_FILL:
md_texture.mode = MOD_GREASE_PENCIL_TEXTURE_STROKE_AND_FILL;
break;
}
switch (eTextureGpencil_Fit(legacy_md_texture.fit_method)) {
case GP_TEX_FIT_STROKE:
md_texture.fit_method = MOD_GREASE_PENCIL_TEXTURE_FIT_STROKE;
break;
case GP_TEX_CONSTANT_LENGTH:
md_texture.fit_method = MOD_GREASE_PENCIL_TEXTURE_CONSTANT_LENGTH;
break;
}
md_texture.uv_offset = legacy_md_texture.uv_offset;
md_texture.uv_scale = legacy_md_texture.uv_scale;
md_texture.fill_rotation = legacy_md_texture.fill_rotation;
copy_v2_v2(md_texture.fill_offset, legacy_md_texture.fill_offset);
md_texture.fill_scale = legacy_md_texture.fill_scale;
md_texture.layer_pass = legacy_md_texture.layer_pass;
md_texture.alignment_rotation = legacy_md_texture.alignment_rotation;
legacy_object_modifier_influence(md_texture.influence,
legacy_md_texture.layername,
legacy_md_texture.layer_pass,
legacy_md_texture.flag & GP_TEX_INVERT_LAYER,
legacy_md_texture.flag & GP_TEX_INVERT_LAYERPASS,
&legacy_md_texture.material,
legacy_md_texture.pass_index,
legacy_md_texture.flag & GP_TEX_INVERT_MATERIAL,
legacy_md_texture.flag & GP_TEX_INVERT_PASS,
legacy_md_texture.vgname,
legacy_md_texture.flag & GP_TEX_INVERT_VGROUP,
nullptr,
false);
}
static void legacy_object_modifier_thickness(ConversionData &conversion_data,
Object &object,
GpencilModifierData &legacy_md)
{
ModifierData &md = legacy_object_modifier_common(
conversion_data, object, eModifierType_GreasePencilThickness, legacy_md);
auto &md_thickness = reinterpret_cast<GreasePencilThickModifierData &>(md);
auto &legacy_md_thickness = reinterpret_cast<ThickGpencilModifierData &>(legacy_md);
md_thickness.flag = 0;
if (legacy_md_thickness.flag & GP_THICK_NORMALIZE) {
md_thickness.flag |= MOD_GREASE_PENCIL_THICK_NORMALIZE;
}
if (legacy_md_thickness.flag & GP_THICK_WEIGHT_FACTOR) {
md_thickness.flag |= MOD_GREASE_PENCIL_THICK_WEIGHT_FACTOR;
}
md_thickness.thickness_fac = legacy_md_thickness.thickness_fac;
md_thickness.thickness = legacy_md_thickness.thickness * LEGACY_RADIUS_CONVERSION_FACTOR;
legacy_object_modifier_influence(md_thickness.influence,
legacy_md_thickness.layername,
legacy_md_thickness.layer_pass,
legacy_md_thickness.flag & GP_THICK_INVERT_LAYER,
legacy_md_thickness.flag & GP_THICK_INVERT_LAYERPASS,
&legacy_md_thickness.material,
legacy_md_thickness.pass_index,
legacy_md_thickness.flag & GP_THICK_INVERT_MATERIAL,
legacy_md_thickness.flag & GP_THICK_INVERT_PASS,
legacy_md_thickness.vgname,
legacy_md_thickness.flag & GP_THICK_INVERT_VGROUP,
&legacy_md_thickness.curve_thickness,
legacy_md_thickness.flag & GP_THICK_CUSTOM_CURVE);
}
static void legacy_object_modifier_time(ConversionData &conversion_data,
Object &object,
GpencilModifierData &legacy_md)
{
ModifierData &md = legacy_object_modifier_common(
conversion_data, object, eModifierType_GreasePencilTime, legacy_md);
auto &md_time = reinterpret_cast<GreasePencilTimeModifierData &>(md);
auto &legacy_md_time = reinterpret_cast<TimeGpencilModifierData &>(legacy_md);
md_time.flag = 0;
if (legacy_md_time.flag & GP_TIME_CUSTOM_RANGE) {
md_time.flag |= MOD_GREASE_PENCIL_TIME_CUSTOM_RANGE;
}
if (legacy_md_time.flag & GP_TIME_KEEP_LOOP) {
md_time.flag |= MOD_GREASE_PENCIL_TIME_KEEP_LOOP;
}
switch (eTimeGpencil_Mode(legacy_md_time.mode)) {
case GP_TIME_MODE_NORMAL:
md_time.mode = MOD_GREASE_PENCIL_TIME_MODE_NORMAL;
break;
case GP_TIME_MODE_REVERSE:
md_time.mode = MOD_GREASE_PENCIL_TIME_MODE_REVERSE;
break;
case GP_TIME_MODE_FIX:
md_time.mode = MOD_GREASE_PENCIL_TIME_MODE_FIX;
break;
case GP_TIME_MODE_PINGPONG:
md_time.mode = MOD_GREASE_PENCIL_TIME_MODE_PINGPONG;
break;
case GP_TIME_MODE_CHAIN:
md_time.mode = MOD_GREASE_PENCIL_TIME_MODE_CHAIN;
break;
}
md_time.offset = legacy_md_time.offset;
md_time.frame_scale = legacy_md_time.frame_scale;
md_time.sfra = legacy_md_time.sfra;
md_time.efra = legacy_md_time.efra;
md_time.segment_active_index = legacy_md_time.segment_active_index;
md_time.segments_num = legacy_md_time.segments_len;
MEM_SAFE_FREE(md_time.segments_array);
md_time.segments_array = MEM_calloc_arrayN<GreasePencilTimeModifierSegment>(
legacy_md_time.segments_len, __func__);
for (const int i : IndexRange(md_time.segments_num)) {
GreasePencilTimeModifierSegment &dst_segment = md_time.segments_array[i];
const TimeGpencilModifierSegment &src_segment = legacy_md_time.segments[i];
STRNCPY(dst_segment.name, src_segment.name);
switch (eTimeGpencil_Seg_Mode(src_segment.seg_mode)) {
case GP_TIME_SEG_MODE_NORMAL:
dst_segment.segment_mode = MOD_GREASE_PENCIL_TIME_SEG_MODE_NORMAL;
break;
case GP_TIME_SEG_MODE_REVERSE:
dst_segment.segment_mode = MOD_GREASE_PENCIL_TIME_SEG_MODE_REVERSE;
break;
case GP_TIME_SEG_MODE_PINGPONG:
dst_segment.segment_mode = MOD_GREASE_PENCIL_TIME_SEG_MODE_PINGPONG;
break;
}
dst_segment.segment_start = src_segment.seg_start;
dst_segment.segment_end = src_segment.seg_end;
dst_segment.segment_repeat = src_segment.seg_repeat;
}
/* NOTE: GPv2 time modifier has a material pointer but it is unused. */
legacy_object_modifier_influence(md_time.influence,
legacy_md_time.layername,
legacy_md_time.layer_pass,
legacy_md_time.flag & GP_TIME_INVERT_LAYER,
legacy_md_time.flag & GP_TIME_INVERT_LAYERPASS,
nullptr,
0,
false,
false,
"",
false,
nullptr,
false);
}
static void legacy_object_modifier_tint(ConversionData &conversion_data,
Object &object,
GpencilModifierData &legacy_md)
{
ModifierData &md = legacy_object_modifier_common(
conversion_data, object, eModifierType_GreasePencilTint, legacy_md);
auto &md_tint = reinterpret_cast<GreasePencilTintModifierData &>(md);
auto &legacy_md_tint = reinterpret_cast<TintGpencilModifierData &>(legacy_md);
md_tint.flag = 0;
if (legacy_md_tint.flag & GP_TINT_WEIGHT_FACTOR) {
md_tint.flag |= MOD_GREASE_PENCIL_TINT_USE_WEIGHT_AS_FACTOR;
}
switch (eGp_Vertex_Mode(legacy_md_tint.mode)) {
case GPPAINT_MODE_BOTH:
md_tint.color_mode = MOD_GREASE_PENCIL_COLOR_BOTH;
break;
case GPPAINT_MODE_STROKE:
md_tint.color_mode = MOD_GREASE_PENCIL_COLOR_STROKE;
break;
case GPPAINT_MODE_FILL:
md_tint.color_mode = MOD_GREASE_PENCIL_COLOR_FILL;
break;
}
switch (eTintGpencil_Type(legacy_md_tint.type)) {
case GP_TINT_UNIFORM:
md_tint.tint_mode = MOD_GREASE_PENCIL_TINT_UNIFORM;
break;
case GP_TINT_GRADIENT:
md_tint.tint_mode = MOD_GREASE_PENCIL_TINT_GRADIENT;
break;
}
md_tint.factor = legacy_md_tint.factor;
md_tint.radius = legacy_md_tint.radius;
copy_v3_v3(md_tint.color, legacy_md_tint.rgb);
md_tint.object = legacy_md_tint.object;
legacy_md_tint.object = nullptr;
MEM_SAFE_FREE(md_tint.color_ramp);
md_tint.color_ramp = legacy_md_tint.colorband;
legacy_md_tint.colorband = nullptr;
legacy_object_modifier_influence(md_tint.influence,
legacy_md_tint.layername,
legacy_md_tint.layer_pass,
legacy_md_tint.flag & GP_TINT_INVERT_LAYER,
legacy_md_tint.flag & GP_TINT_INVERT_LAYERPASS,
&legacy_md_tint.material,
legacy_md_tint.pass_index,
legacy_md_tint.flag & GP_TINT_INVERT_MATERIAL,
legacy_md_tint.flag & GP_TINT_INVERT_PASS,
legacy_md_tint.vgname,
legacy_md_tint.flag & GP_TINT_INVERT_VGROUP,
&legacy_md_tint.curve_intensity,
legacy_md_tint.flag & GP_TINT_CUSTOM_CURVE);
}
static void legacy_object_modifier_weight_angle(ConversionData &conversion_data,
Object &object,
GpencilModifierData &legacy_md)
{
ModifierData &md = legacy_object_modifier_common(
conversion_data, object, eModifierType_GreasePencilWeightAngle, legacy_md);
auto &md_weight_angle = reinterpret_cast<GreasePencilWeightAngleModifierData &>(md);
auto &legacy_md_weight_angle = reinterpret_cast<WeightAngleGpencilModifierData &>(legacy_md);
md_weight_angle.flag = 0;
if (legacy_md_weight_angle.flag & GP_WEIGHT_MULTIPLY_DATA) {
md_weight_angle.flag |= MOD_GREASE_PENCIL_WEIGHT_ANGLE_MULTIPLY_DATA;
}
if (legacy_md_weight_angle.flag & GP_WEIGHT_INVERT_OUTPUT) {
md_weight_angle.flag |= MOD_GREASE_PENCIL_WEIGHT_ANGLE_INVERT_OUTPUT;
}
switch (eGpencilModifierSpace(legacy_md_weight_angle.space)) {
case GP_SPACE_LOCAL:
md_weight_angle.space = MOD_GREASE_PENCIL_WEIGHT_ANGLE_SPACE_LOCAL;
break;
case GP_SPACE_WORLD:
md_weight_angle.space = MOD_GREASE_PENCIL_WEIGHT_ANGLE_SPACE_WORLD;
break;
}
md_weight_angle.axis = legacy_md_weight_angle.axis;
STRNCPY(md_weight_angle.target_vgname, legacy_md_weight_angle.target_vgname);
md_weight_angle.min_weight = legacy_md_weight_angle.min_weight;
md_weight_angle.angle = legacy_md_weight_angle.angle;
legacy_object_modifier_influence(md_weight_angle.influence,
legacy_md_weight_angle.layername,
legacy_md_weight_angle.layer_pass,
legacy_md_weight_angle.flag & GP_WEIGHT_INVERT_LAYER,
legacy_md_weight_angle.flag & GP_WEIGHT_INVERT_LAYERPASS,
&legacy_md_weight_angle.material,
legacy_md_weight_angle.pass_index,
legacy_md_weight_angle.flag & GP_WEIGHT_INVERT_MATERIAL,
legacy_md_weight_angle.flag & GP_WEIGHT_INVERT_PASS,
legacy_md_weight_angle.vgname,
legacy_md_weight_angle.flag & GP_WEIGHT_INVERT_VGROUP,
nullptr,
false);
}
static void legacy_object_modifier_weight_proximity(ConversionData &conversion_data,
Object &object,
GpencilModifierData &legacy_md)
{
ModifierData &md = legacy_object_modifier_common(
conversion_data, object, eModifierType_GreasePencilWeightProximity, legacy_md);
auto &md_weight_prox = reinterpret_cast<GreasePencilWeightProximityModifierData &>(md);
auto &legacy_md_weight_prox = reinterpret_cast<WeightProxGpencilModifierData &>(legacy_md);
md_weight_prox.flag = 0;
if (legacy_md_weight_prox.flag & GP_WEIGHT_MULTIPLY_DATA) {
md_weight_prox.flag |= MOD_GREASE_PENCIL_WEIGHT_PROXIMITY_MULTIPLY_DATA;
}
if (legacy_md_weight_prox.flag & GP_WEIGHT_INVERT_OUTPUT) {
md_weight_prox.flag |= MOD_GREASE_PENCIL_WEIGHT_PROXIMITY_INVERT_OUTPUT;
}
STRNCPY(md_weight_prox.target_vgname, legacy_md_weight_prox.target_vgname);
md_weight_prox.min_weight = legacy_md_weight_prox.min_weight;
md_weight_prox.dist_start = legacy_md_weight_prox.dist_start;
md_weight_prox.dist_end = legacy_md_weight_prox.dist_end;
md_weight_prox.object = legacy_md_weight_prox.object;
legacy_md_weight_prox.object = nullptr;
legacy_object_modifier_influence(md_weight_prox.influence,
legacy_md_weight_prox.layername,
legacy_md_weight_prox.layer_pass,
legacy_md_weight_prox.flag & GP_WEIGHT_INVERT_LAYER,
legacy_md_weight_prox.flag & GP_WEIGHT_INVERT_LAYERPASS,
&legacy_md_weight_prox.material,
legacy_md_weight_prox.pass_index,
legacy_md_weight_prox.flag & GP_WEIGHT_INVERT_MATERIAL,
legacy_md_weight_prox.flag & GP_WEIGHT_INVERT_PASS,
legacy_md_weight_prox.vgname,
legacy_md_weight_prox.flag & GP_WEIGHT_INVERT_VGROUP,
nullptr,
false);
}
static void legacy_object_modifier_lineart(ConversionData &conversion_data,
Object &object,
GpencilModifierData &legacy_md)
{
ModifierData &md = legacy_object_modifier_common(
conversion_data, object, eModifierType_GreasePencilLineart, legacy_md);
auto &md_lineart = reinterpret_cast<GreasePencilLineartModifierData &>(md);
auto &legacy_md_lineart = reinterpret_cast<LineartGpencilModifierData &>(legacy_md);
greasepencil::convert::lineart_wrap_v3(&legacy_md_lineart, &md_lineart);
}
static void legacy_object_modifier_build(ConversionData &conversion_data,
Object &object,
GpencilModifierData &legacy_md)
{
ModifierData &md = legacy_object_modifier_common(
conversion_data, object, eModifierType_GreasePencilBuild, legacy_md);
auto &md_build = reinterpret_cast<GreasePencilBuildModifierData &>(md);
auto &legacy_md_build = reinterpret_cast<BuildGpencilModifierData &>(legacy_md);
md_build.flag = 0;
if (legacy_md_build.flag & GP_BUILD_RESTRICT_TIME) {
md_build.flag |= MOD_GREASE_PENCIL_BUILD_RESTRICT_TIME;
}
if (legacy_md_build.flag & GP_BUILD_USE_FADING) {
md_build.flag |= MOD_GREASE_PENCIL_BUILD_USE_FADING;
}
switch (legacy_md_build.mode) {
case GP_BUILD_MODE_ADDITIVE:
md_build.mode = MOD_GREASE_PENCIL_BUILD_MODE_ADDITIVE;
break;
case GP_BUILD_MODE_CONCURRENT:
md_build.mode = MOD_GREASE_PENCIL_BUILD_MODE_CONCURRENT;
break;
case GP_BUILD_MODE_SEQUENTIAL:
default:
md_build.mode = MOD_GREASE_PENCIL_BUILD_MODE_SEQUENTIAL;
break;
}
switch (legacy_md_build.time_alignment) {
default:
case GP_BUILD_TIMEALIGN_START:
md_build.time_alignment = MOD_GREASE_PENCIL_BUILD_TIMEALIGN_START;
break;
case GP_BUILD_TIMEALIGN_END:
md_build.time_alignment = MOD_GREASE_PENCIL_BUILD_TIMEALIGN_END;
break;
}
switch (legacy_md_build.time_mode) {
default:
case GP_BUILD_TIMEMODE_FRAMES:
md_build.time_mode = MOD_GREASE_PENCIL_BUILD_TIMEMODE_FRAMES;
break;
case GP_BUILD_TIMEMODE_PERCENTAGE:
md_build.time_mode = MOD_GREASE_PENCIL_BUILD_TIMEMODE_PERCENTAGE;
break;
case GP_BUILD_TIMEMODE_DRAWSPEED:
md_build.time_mode = MOD_GREASE_PENCIL_BUILD_TIMEMODE_DRAWSPEED;
break;
}
switch (legacy_md_build.transition) {
default:
case GP_BUILD_TRANSITION_GROW:
md_build.transition = MOD_GREASE_PENCIL_BUILD_TRANSITION_GROW;
break;
case GP_BUILD_TRANSITION_SHRINK:
md_build.transition = MOD_GREASE_PENCIL_BUILD_TRANSITION_SHRINK;
break;
case GP_BUILD_TRANSITION_VANISH:
md_build.transition = MOD_GREASE_PENCIL_BUILD_TRANSITION_VANISH;
break;
}
md_build.start_frame = legacy_md_build.start_frame;
md_build.end_frame = legacy_md_build.end_frame;
md_build.start_delay = legacy_md_build.start_delay;
md_build.length = legacy_md_build.length;
md_build.fade_fac = legacy_md_build.fade_fac;
md_build.fade_opacity_strength = legacy_md_build.fade_opacity_strength;
md_build.fade_thickness_strength = legacy_md_build.fade_thickness_strength;
md_build.percentage_fac = legacy_md_build.percentage_fac;
md_build.speed_fac = legacy_md_build.speed_fac;
md_build.speed_maxgap = legacy_md_build.speed_maxgap;
md_build.object = legacy_md_build.object;
STRNCPY(md_build.target_vgname, legacy_md_build.target_vgname);
legacy_object_modifier_influence(md_build.influence,
legacy_md_build.layername,
legacy_md_build.layer_pass,
legacy_md_build.flag & GP_WEIGHT_INVERT_LAYER,
legacy_md_build.flag & GP_WEIGHT_INVERT_LAYERPASS,
&legacy_md_build.material,
legacy_md_build.pass_index,
legacy_md_build.flag & GP_WEIGHT_INVERT_MATERIAL,
legacy_md_build.flag & GP_WEIGHT_INVERT_PASS,
legacy_md_build.target_vgname,
legacy_md_build.flag & GP_WEIGHT_INVERT_VGROUP,
nullptr,
false);
}
static void legacy_object_modifier_simplify(ConversionData &conversion_data,
Object &object,
GpencilModifierData &legacy_md)
{
ModifierData &md = legacy_object_modifier_common(
conversion_data, object, eModifierType_GreasePencilSimplify, legacy_md);
auto &md_simplify = reinterpret_cast<GreasePencilSimplifyModifierData &>(md);
auto &legacy_md_simplify = reinterpret_cast<SimplifyGpencilModifierData &>(legacy_md);
switch (legacy_md_simplify.mode) {
case GP_SIMPLIFY_FIXED:
md_simplify.mode = MOD_GREASE_PENCIL_SIMPLIFY_FIXED;
break;
case GP_SIMPLIFY_ADAPTIVE:
md_simplify.mode = MOD_GREASE_PENCIL_SIMPLIFY_ADAPTIVE;
break;
case GP_SIMPLIFY_SAMPLE:
md_simplify.mode = MOD_GREASE_PENCIL_SIMPLIFY_SAMPLE;
break;
case GP_SIMPLIFY_MERGE:
md_simplify.mode = MOD_GREASE_PENCIL_SIMPLIFY_MERGE;
break;
}
md_simplify.step = legacy_md_simplify.step;
md_simplify.factor = legacy_md_simplify.factor;
md_simplify.length = legacy_md_simplify.length;
md_simplify.sharp_threshold = legacy_md_simplify.sharp_threshold;
md_simplify.distance = legacy_md_simplify.distance;
legacy_object_modifier_influence(md_simplify.influence,
legacy_md_simplify.layername,
legacy_md_simplify.layer_pass,
legacy_md_simplify.flag & GP_SIMPLIFY_INVERT_LAYER,
legacy_md_simplify.flag & GP_SIMPLIFY_INVERT_LAYERPASS,
&legacy_md_simplify.material,
legacy_md_simplify.pass_index,
legacy_md_simplify.flag & GP_SIMPLIFY_INVERT_MATERIAL,
legacy_md_simplify.flag & GP_SIMPLIFY_INVERT_PASS,
"",
false,
nullptr,
false);
}
static void legacy_object_modifiers(ConversionData &conversion_data, Object &object)
{
BLI_assert(BLI_listbase_is_empty(&object.modifiers));
while (GpencilModifierData *gpd_md = static_cast<GpencilModifierData *>(
BLI_pophead(&object.greasepencil_modifiers)))
{
switch (gpd_md->type) {
case eGpencilModifierType_None:
/* Unknown type, just ignore. */
break;
case eGpencilModifierType_Armature:
legacy_object_modifier_armature(conversion_data, object, *gpd_md);
break;
case eGpencilModifierType_Array:
legacy_object_modifier_array(conversion_data, object, *gpd_md);
break;
case eGpencilModifierType_Color:
legacy_object_modifier_color(conversion_data, object, *gpd_md);
break;
case eGpencilModifierType_Dash:
legacy_object_modifier_dash(conversion_data, object, *gpd_md);
break;
case eGpencilModifierType_Envelope:
legacy_object_modifier_envelope(conversion_data, object, *gpd_md);
break;
case eGpencilModifierType_Hook:
legacy_object_modifier_hook(conversion_data, object, *gpd_md);
break;
case eGpencilModifierType_Lattice:
legacy_object_modifier_lattice(conversion_data, object, *gpd_md);
break;
case eGpencilModifierType_Length:
legacy_object_modifier_length(conversion_data, object, *gpd_md);
break;
case eGpencilModifierType_Mirror:
legacy_object_modifier_mirror(conversion_data, object, *gpd_md);
break;
case eGpencilModifierType_Multiply:
legacy_object_modifier_multiply(conversion_data, object, *gpd_md);
break;
case eGpencilModifierType_Noise:
legacy_object_modifier_noise(conversion_data, object, *gpd_md);
break;
case eGpencilModifierType_Offset:
legacy_object_modifier_offset(conversion_data, object, *gpd_md);
break;
case eGpencilModifierType_Opacity:
legacy_object_modifier_opacity(conversion_data, object, *gpd_md);
break;
case eGpencilModifierType_Outline:
legacy_object_modifier_outline(conversion_data, object, *gpd_md);
break;
case eGpencilModifierType_Shrinkwrap:
legacy_object_modifier_shrinkwrap(conversion_data, object, *gpd_md);
break;
case eGpencilModifierType_Smooth:
legacy_object_modifier_smooth(conversion_data, object, *gpd_md);
break;
case eGpencilModifierType_Subdiv:
legacy_object_modifier_subdiv(conversion_data, object, *gpd_md);
break;
case eGpencilModifierType_Texture:
legacy_object_modifier_texture(conversion_data, object, *gpd_md);
break;
case eGpencilModifierType_Thick:
legacy_object_modifier_thickness(conversion_data, object, *gpd_md);
break;
case eGpencilModifierType_Time:
legacy_object_modifier_time(conversion_data, object, *gpd_md);
break;
case eGpencilModifierType_Tint:
legacy_object_modifier_tint(conversion_data, object, *gpd_md);
break;
case eGpencilModifierType_WeightAngle:
legacy_object_modifier_weight_angle(conversion_data, object, *gpd_md);
break;
case eGpencilModifierType_WeightProximity:
legacy_object_modifier_weight_proximity(conversion_data, object, *gpd_md);
break;
case eGpencilModifierType_Lineart:
legacy_object_modifier_lineart(conversion_data, object, *gpd_md);
break;
case eGpencilModifierType_Build:
legacy_object_modifier_build(conversion_data, object, *gpd_md);
break;
case eGpencilModifierType_Simplify:
legacy_object_modifier_simplify(conversion_data, object, *gpd_md);
break;
}
BKE_gpencil_modifier_free_ex(gpd_md, 0);
}
}
/**
* Ensure that both use-cases of legacy grease pencil data (as object, and as annotations) are
* fully separated and valid (have proper tag). Annotation IDs are left as-is currently, while GPv2
* data used by objects need to be converted to GPv3 data.
*
* \note GPv2 IDs not used by object nor annotations are just left in their current state - the
* ones tagged as annotations will be left untouched, the others will be converted to GPv3 data.
*
* \note De-duplication needs to assign the new, duplicated GPv2 ID to annotations, and keep the
* original one assigned to objects. That way, when the object data are converted, other potential
* references to the old GPv2 ID (drivers, custom properties, etc.) can be safely remapped to the
* new GPv3 ID.
*
* \warning: This makes the assumption that annotation data is not typically referenced by anything
* else than annotation pointers. If this is not true, then some data might be lost during
* conversion process.
*/
static void legacy_gpencil_sanitize_annotations(Main &bmain)
{
/* Annotations mapping, keys are existing GPv2 IDs actually used as annotations, values are
* either:
* - nullptr: this annotation is never used by any object, there was no need to duplicate it.
* - new GPv2 ID: this annotation was also used by objects, this is its new duplicated ID. */
Map<bGPdata *, bGPdata *> annotations_gpv2;
/* All legacy GPv2 data used by objects. */
Set<bGPdata *> object_gpv2;
/* Check all GP objects. */
LISTBASE_FOREACH (Object *, object, &bmain.objects) {
if (object->type != OB_GPENCIL_LEGACY) {
continue;
}
bGPdata *legacy_gpd = static_cast<bGPdata *>(object->data);
if (!legacy_gpd) {
continue;
}
if ((legacy_gpd->flag & GP_DATA_ANNOTATIONS) != 0) {
legacy_gpd->flag &= ~GP_DATA_ANNOTATIONS;
}
object_gpv2.add(legacy_gpd);
}
/* Detect all annotations currently used as such, and de-duplicate them if they are also used by
* objects. */
auto sanitize_gpv2_annotation = [&](bGPdata **legacy_gpd_p) {
bGPdata *legacy_gpd = *legacy_gpd_p;
if (!legacy_gpd) {
return;
}
bGPdata *new_annotation_gpd = annotations_gpv2.lookup_or_add(legacy_gpd, nullptr);
if (!object_gpv2.contains(legacy_gpd)) {
/* Legacy annotation GPv2 data not used by any object, just ensure that it is properly
* tagged. */
BLI_assert(!new_annotation_gpd);
if ((legacy_gpd->flag & GP_DATA_ANNOTATIONS) == 0) {
legacy_gpd->flag |= GP_DATA_ANNOTATIONS;
}
return;
}
/* Legacy GP data also used by objects. Create the duplicate of legacy GPv2 data for
* annotations, if not yet done. */
if (!new_annotation_gpd) {
new_annotation_gpd = reinterpret_cast<bGPdata *>(BKE_id_copy_in_lib(&bmain,
legacy_gpd->id.lib,
&legacy_gpd->id,
std::nullopt,
nullptr,
LIB_ID_COPY_DEFAULT));
new_annotation_gpd->flag |= GP_DATA_ANNOTATIONS;
id_us_min(&new_annotation_gpd->id);
annotations_gpv2.add_overwrite(legacy_gpd, new_annotation_gpd);
}
/* Assign the annotation duplicate ID to the annotation pointer. */
BLI_assert(new_annotation_gpd->flag & GP_DATA_ANNOTATIONS);
id_us_min(&legacy_gpd->id);
*legacy_gpd_p = new_annotation_gpd;
id_us_plus_no_lib(&new_annotation_gpd->id);
};
LISTBASE_FOREACH (Scene *, scene, &bmain.scenes) {
sanitize_gpv2_annotation(&scene->gpd);
}
ID *id_iter;
FOREACH_MAIN_ID_BEGIN (&bmain, id_iter) {
if (bNodeTree *node_tree = bke::node_tree_from_id(id_iter)) {
sanitize_gpv2_annotation(&node_tree->gpd);
}
}
FOREACH_MAIN_ID_END;
LISTBASE_FOREACH (bNodeTree *, node_tree, &bmain.nodetrees) {
sanitize_gpv2_annotation(&node_tree->gpd);
}
LISTBASE_FOREACH (MovieClip *, movie_clip, &bmain.movieclips) {
sanitize_gpv2_annotation(&movie_clip->gpd);
LISTBASE_FOREACH (MovieTrackingObject *, mvc_tracking_object, &movie_clip->tracking.objects) {
LISTBASE_FOREACH (MovieTrackingTrack *, mvc_track, &mvc_tracking_object->tracks) {
sanitize_gpv2_annotation(&mvc_track->gpd);
}
LISTBASE_FOREACH (
MovieTrackingPlaneTrack *, mvc_plane_track, &mvc_tracking_object->plane_tracks)
{
for (int i = 0; i < mvc_plane_track->point_tracksnr; i++) {
sanitize_gpv2_annotation(&mvc_plane_track->point_tracks[i]->gpd);
}
}
}
}
LISTBASE_FOREACH (bScreen *, screen, &bmain.screens) {
LISTBASE_FOREACH (ScrArea *, area, &screen->areabase) {
LISTBASE_FOREACH (SpaceLink *, space_link, &area->spacedata) {
switch (eSpace_Type(space_link->spacetype)) {
case SPACE_SEQ: {
SpaceSeq *space_sequencer = reinterpret_cast<SpaceSeq *>(space_link);
sanitize_gpv2_annotation(&space_sequencer->gpd);
break;
}
case SPACE_IMAGE: {
SpaceImage *space_image = reinterpret_cast<SpaceImage *>(space_link);
sanitize_gpv2_annotation(&space_image->gpd);
break;
}
case SPACE_NODE: {
SpaceNode *space_node = reinterpret_cast<SpaceNode *>(space_link);
sanitize_gpv2_annotation(&space_node->gpd);
break;
}
case SPACE_EMPTY:
case SPACE_VIEW3D:
/* #View3D.gpd is deprecated and can be ignored here. */
case SPACE_GRAPH:
case SPACE_OUTLINER:
case SPACE_PROPERTIES:
case SPACE_FILE:
case SPACE_INFO:
case SPACE_TEXT:
case SPACE_ACTION:
case SPACE_NLA:
case SPACE_SCRIPT:
case SPACE_CONSOLE:
case SPACE_USERPREF:
case SPACE_CLIP:
case SPACE_TOPBAR:
case SPACE_STATUSBAR:
case SPACE_SPREADSHEET:
break;
}
}
}
}
}
static void legacy_gpencil_object(ConversionData &conversion_data, Object &object)
{
BLI_assert((GS(static_cast<ID *>(object.data)->name) == ID_GD_LEGACY));
bGPdata *gpd = static_cast<bGPdata *>(object.data);
GreasePencil *new_grease_pencil = conversion_data.legacy_to_greasepencil_data.lookup_default(
gpd, nullptr);
const bool do_gpencil_data_conversion = (new_grease_pencil == nullptr);
if (!new_grease_pencil) {
new_grease_pencil = static_cast<GreasePencil *>(
BKE_id_new_in_lib(&conversion_data.bmain, gpd->id.lib, ID_GP, gpd->id.name + 2));
id_us_min(&new_grease_pencil->id);
}
object.data = new_grease_pencil;
object.type = OB_GREASE_PENCIL;
/* NOTE: Could also use #BKE_id_free_us, to also free the legacy GP if not used anymore? */
id_us_min(&gpd->id);
id_us_plus(&new_grease_pencil->id);
if (do_gpencil_data_conversion) {
legacy_gpencil_to_grease_pencil(conversion_data, *new_grease_pencil, *gpd);
conversion_data.legacy_to_greasepencil_data.add(gpd, new_grease_pencil);
}
legacy_object_modifiers(conversion_data, object);
/* Convert the animation of the "uniform thickness" setting of the thickness modifier. */
legacy_object_thickness_modifier_thickness_anim(conversion_data, object);
/* Layer adjustments should be added after all other modifiers. */
layer_adjustments_to_modifiers(conversion_data, *gpd, object);
/* Thickness factor is applied after all other changes to the radii. */
thickness_factor_to_modifier(conversion_data, *gpd, object);
BKE_object_free_derived_caches(&object);
}
void legacy_main(Main &bmain,
BlendfileLinkAppendContext *lapp_context,
BlendFileReadReport & /*reports*/)
{
ConversionData conversion_data(bmain, lapp_context);
/* Ensure that annotations are fully separated from object usages of legacy GPv2 data. */
legacy_gpencil_sanitize_annotations(bmain);
LISTBASE_FOREACH (Object *, object, &bmain.objects) {
if (object->type != OB_GPENCIL_LEGACY) {
continue;
}
legacy_gpencil_object(conversion_data, *object);
}
/* Potential other usages of legacy bGPdata IDs also need to be remapped to their matching new
* GreasePencil counterparts. */
bke::id::IDRemapper gpd_remapper;
/* Allow remapping from legacy bGPdata IDs to new GreasePencil ones. */
gpd_remapper.allow_idtype_mismatch = true;
LISTBASE_FOREACH (bGPdata *, legacy_gpd, &bmain.gpencils) {
/* Annotations still use legacy `bGPdata`, these should not be converted. Call to
* #legacy_gpencil_sanitize_annotations above ensured to fully separate annotations from object
* legacy grease pencil. */
if ((legacy_gpd->flag & GP_DATA_ANNOTATIONS) != 0) {
continue;
}
GreasePencil *new_grease_pencil = conversion_data.legacy_to_greasepencil_data.lookup_default(
legacy_gpd, nullptr);
if (!new_grease_pencil) {
new_grease_pencil = static_cast<GreasePencil *>(
BKE_id_new_in_lib(&bmain, legacy_gpd->id.lib, ID_GP, legacy_gpd->id.name + 2));
id_us_min(&new_grease_pencil->id);
legacy_gpencil_to_grease_pencil(conversion_data, *new_grease_pencil, *legacy_gpd);
conversion_data.legacy_to_greasepencil_data.add(legacy_gpd, new_grease_pencil);
}
gpd_remapper.add(&legacy_gpd->id, &new_grease_pencil->id);
}
BKE_libblock_remap_multiple(&bmain, gpd_remapper, ID_REMAP_ALLOW_IDTYPE_MISMATCH);
if (conversion_data.lapp_context) {
BKE_blendfile_link_append_context_item_foreach(
conversion_data.lapp_context,
[&conversion_data](BlendfileLinkAppendContext *lapp_context,
BlendfileLinkAppendContextItem *item) -> bool {
ID *item_new_id = BKE_blendfile_link_append_context_item_newid_get(lapp_context, item);
if (!item_new_id || GS(item_new_id->name) != ID_GD_LEGACY) {
return true;
}
GreasePencil **item_grease_pencil =
conversion_data.legacy_to_greasepencil_data.lookup_ptr(
reinterpret_cast<bGPdata *>(item_new_id));
if (item_grease_pencil && *item_grease_pencil) {
BKE_blendfile_link_append_context_item_newid_set(
lapp_context, item, &(*item_grease_pencil)->id);
}
return true;
},
eBlendfileLinkAppendForeachItemFlag(
BKE_BLENDFILE_LINK_APPEND_FOREACH_ITEM_FLAG_DO_DIRECT |
BKE_BLENDFILE_LINK_APPEND_FOREACH_ITEM_FLAG_DO_INDIRECT));
}
}
void lineart_wrap_v3(const LineartGpencilModifierData *lmd_legacy,
GreasePencilLineartModifierData *lmd)
{
lmd->edge_types = lmd_legacy->edge_types;
lmd->source_type = lmd_legacy->source_type;
lmd->use_multiple_levels = lmd_legacy->use_multiple_levels;
lmd->level_start = lmd_legacy->level_start;
lmd->level_end = lmd_legacy->level_end;
lmd->source_camera = lmd_legacy->source_camera;
lmd->light_contour_object = lmd_legacy->light_contour_object;
lmd->source_object = lmd_legacy->source_object;
lmd->source_collection = lmd_legacy->source_collection;
lmd->target_material = lmd_legacy->target_material;
STRNCPY(lmd->target_layer, lmd_legacy->target_layer);
STRNCPY(lmd->source_vertex_group, lmd_legacy->source_vertex_group);
STRNCPY(lmd->vgname, lmd_legacy->vgname);
lmd->overscan = lmd_legacy->overscan;
lmd->shadow_camera_fov = lmd_legacy->shadow_camera_fov;
lmd->shadow_camera_size = lmd_legacy->shadow_camera_size;
lmd->shadow_camera_near = lmd_legacy->shadow_camera_near;
lmd->shadow_camera_far = lmd_legacy->shadow_camera_far;
lmd->opacity = lmd_legacy->opacity;
lmd->thickness = lmd_legacy->thickness;
lmd->mask_switches = lmd_legacy->mask_switches;
lmd->material_mask_bits = lmd_legacy->material_mask_bits;
lmd->intersection_mask = lmd_legacy->intersection_mask;
lmd->shadow_selection = lmd_legacy->shadow_selection;
lmd->silhouette_selection = lmd_legacy->silhouette_selection;
lmd->crease_threshold = lmd_legacy->crease_threshold;
lmd->angle_splitting_threshold = lmd_legacy->angle_splitting_threshold;
lmd->chain_smooth_tolerance = lmd_legacy->chain_smooth_tolerance;
lmd->chaining_image_threshold = lmd_legacy->chaining_image_threshold;
lmd->calculation_flags = lmd_legacy->calculation_flags;
lmd->flags = lmd_legacy->flags;
lmd->stroke_depth_offset = lmd_legacy->stroke_depth_offset;
lmd->level_start_override = lmd_legacy->level_start_override;
lmd->level_end_override = lmd_legacy->level_end_override;
lmd->edge_types_override = lmd_legacy->edge_types_override;
lmd->shadow_selection_override = lmd_legacy->shadow_selection_override;
lmd->shadow_use_silhouette_override = lmd_legacy->shadow_use_silhouette_override;
lmd->cache = lmd_legacy->cache;
lmd->la_data_ptr = lmd_legacy->la_data_ptr;
}
void lineart_unwrap_v3(LineartGpencilModifierData *lmd_legacy,
const GreasePencilLineartModifierData *lmd)
{
lmd_legacy->edge_types = lmd->edge_types;
lmd_legacy->source_type = lmd->source_type;
lmd_legacy->use_multiple_levels = lmd->use_multiple_levels;
lmd_legacy->level_start = lmd->level_start;
lmd_legacy->level_end = lmd->level_end;
lmd_legacy->source_camera = lmd->source_camera;
lmd_legacy->light_contour_object = lmd->light_contour_object;
lmd_legacy->source_object = lmd->source_object;
lmd_legacy->source_collection = lmd->source_collection;
lmd_legacy->target_material = lmd->target_material;
STRNCPY(lmd_legacy->source_vertex_group, lmd->source_vertex_group);
STRNCPY(lmd_legacy->vgname, lmd->vgname);
lmd_legacy->overscan = lmd->overscan;
lmd_legacy->shadow_camera_fov = lmd->shadow_camera_fov;
lmd_legacy->shadow_camera_size = lmd->shadow_camera_size;
lmd_legacy->shadow_camera_near = lmd->shadow_camera_near;
lmd_legacy->shadow_camera_far = lmd->shadow_camera_far;
lmd_legacy->opacity = lmd->opacity;
lmd_legacy->thickness = lmd->thickness;
lmd_legacy->mask_switches = lmd->mask_switches;
lmd_legacy->material_mask_bits = lmd->material_mask_bits;
lmd_legacy->intersection_mask = lmd->intersection_mask;
lmd_legacy->shadow_selection = lmd->shadow_selection;
lmd_legacy->silhouette_selection = lmd->silhouette_selection;
lmd_legacy->crease_threshold = lmd->crease_threshold;
lmd_legacy->angle_splitting_threshold = lmd->angle_splitting_threshold;
lmd_legacy->chain_smooth_tolerance = lmd->chain_smooth_tolerance;
lmd_legacy->chaining_image_threshold = lmd->chaining_image_threshold;
lmd_legacy->calculation_flags = lmd->calculation_flags;
lmd_legacy->flags = lmd->flags;
lmd_legacy->stroke_depth_offset = lmd->stroke_depth_offset;
lmd_legacy->level_start_override = lmd->level_start_override;
lmd_legacy->level_end_override = lmd->level_end_override;
lmd_legacy->edge_types_override = lmd->edge_types_override;
lmd_legacy->shadow_selection_override = lmd->shadow_selection_override;
lmd_legacy->shadow_use_silhouette_override = lmd->shadow_use_silhouette_override;
lmd_legacy->cache = lmd->cache;
lmd_legacy->la_data_ptr = lmd->la_data_ptr;
}
} // namespace blender::bke::greasepencil::convert