933 lines
33 KiB
C++
933 lines
33 KiB
C++
/* SPDX-FileCopyrightText: 2005 Blender Authors
|
|
*
|
|
* SPDX-License-Identifier: GPL-2.0-or-later */
|
|
|
|
/** \file
|
|
* \ingroup modifiers
|
|
*/
|
|
|
|
#include "BLT_translation.hh"
|
|
|
|
#include "BLO_read_write.hh"
|
|
|
|
#include "DNA_collection_types.h"
|
|
#include "DNA_defaults.h"
|
|
#include "DNA_gpencil_modifier_types.h"
|
|
#include "DNA_scene_types.h"
|
|
|
|
#include "BKE_collection.hh"
|
|
#include "BKE_geometry_set.hh"
|
|
#include "BKE_grease_pencil.hh"
|
|
#include "BKE_lib_query.hh"
|
|
#include "BKE_material.hh"
|
|
#include "BKE_modifier.hh"
|
|
|
|
#include "UI_interface.hh"
|
|
#include "UI_resources.hh"
|
|
|
|
#include "MOD_grease_pencil_util.hh"
|
|
#include "MOD_lineart.hh"
|
|
#include "MOD_modifiertypes.hh"
|
|
#include "MOD_ui_common.hh"
|
|
|
|
#include "RNA_access.hh"
|
|
#include "RNA_prototypes.hh"
|
|
|
|
#include "DEG_depsgraph_query.hh"
|
|
|
|
#include "ED_grease_pencil.hh"
|
|
|
|
namespace blender {
|
|
|
|
static bool is_first_lineart(const GreasePencilLineartModifierData &md)
|
|
{
|
|
if (md.modifier.type != eModifierType_GreasePencilLineart) {
|
|
return false;
|
|
}
|
|
ModifierData *imd = md.modifier.prev;
|
|
while (imd != nullptr) {
|
|
if (imd->type == eModifierType_GreasePencilLineart) {
|
|
return false;
|
|
}
|
|
imd = imd->prev;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static bool is_last_line_art(const GreasePencilLineartModifierData &md, const bool use_render)
|
|
{
|
|
if (md.modifier.type != eModifierType_GreasePencilLineart) {
|
|
return false;
|
|
}
|
|
ModifierData *imd = md.modifier.next;
|
|
while (imd != nullptr) {
|
|
if (imd->type == eModifierType_GreasePencilLineart) {
|
|
if (use_render && (imd->mode & eModifierMode_Render)) {
|
|
return false;
|
|
}
|
|
if ((!use_render) && (imd->mode & eModifierMode_Realtime)) {
|
|
return false;
|
|
}
|
|
}
|
|
imd = imd->next;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static void init_data(ModifierData *md)
|
|
{
|
|
GreasePencilLineartModifierData *gpmd = (GreasePencilLineartModifierData *)md;
|
|
|
|
BLI_assert(MEMCMP_STRUCT_AFTER_IS_ZERO(gpmd, modifier));
|
|
|
|
MEMCPY_STRUCT_AFTER(gpmd, DNA_struct_default_get(GreasePencilLineartModifierData), modifier);
|
|
}
|
|
|
|
static void copy_data(const ModifierData *md, ModifierData *target, const int flag)
|
|
{
|
|
BKE_modifier_copydata_generic(md, target, flag);
|
|
|
|
const GreasePencilLineartModifierData *source_lmd =
|
|
reinterpret_cast<const GreasePencilLineartModifierData *>(md);
|
|
const LineartModifierRuntime *source_runtime = source_lmd->runtime;
|
|
|
|
GreasePencilLineartModifierData *target_lmd =
|
|
reinterpret_cast<GreasePencilLineartModifierData *>(target);
|
|
|
|
target_lmd->runtime = MEM_new<LineartModifierRuntime>(__func__, *source_runtime);
|
|
}
|
|
|
|
static void free_data(ModifierData *md)
|
|
{
|
|
GreasePencilLineartModifierData *lmd = reinterpret_cast<GreasePencilLineartModifierData *>(md);
|
|
if (LineartModifierRuntime *runtime = lmd->runtime) {
|
|
MEM_delete(runtime);
|
|
lmd->runtime = nullptr;
|
|
}
|
|
}
|
|
|
|
static bool is_disabled(const Scene * /*scene*/, ModifierData *md, bool /*use_render_params*/)
|
|
{
|
|
GreasePencilLineartModifierData *lmd = (GreasePencilLineartModifierData *)md;
|
|
|
|
if (lmd->target_layer[0] == '\0' || !lmd->target_material) {
|
|
return true;
|
|
}
|
|
if (lmd->source_type == LINEART_SOURCE_OBJECT && !lmd->source_object) {
|
|
return true;
|
|
}
|
|
if (lmd->source_type == LINEART_SOURCE_COLLECTION && !lmd->source_collection) {
|
|
return true;
|
|
}
|
|
/* Preventing calculation in depsgraph when baking frames. */
|
|
if (lmd->flags & MOD_LINEART_IS_BAKED) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static void add_this_collection(Collection &collection,
|
|
const ModifierUpdateDepsgraphContext *ctx,
|
|
const int mode,
|
|
Set<const Object *> &object_dependencies)
|
|
{
|
|
bool default_add = true;
|
|
/* Do not do nested collection usage check, this is consistent with lineart calculation, because
|
|
* collection usage doesn't have a INHERIT mode. This might initially be derived from the fact
|
|
* that an object can be inside multiple collections, but might be irrelevant now with the way
|
|
* objects are iterated. Keep this logic for now. */
|
|
if (collection.lineart_usage & COLLECTION_LRT_EXCLUDE) {
|
|
default_add = false;
|
|
}
|
|
FOREACH_COLLECTION_VISIBLE_OBJECT_RECURSIVE_BEGIN (&collection, ob, mode) {
|
|
if (ELEM(ob->type, OB_MESH, OB_MBALL, OB_CURVES_LEGACY, OB_SURF, OB_FONT)) {
|
|
if ((ob->lineart.usage == OBJECT_LRT_INHERIT && default_add) ||
|
|
ob->lineart.usage != OBJECT_LRT_EXCLUDE)
|
|
{
|
|
DEG_add_object_relation(ctx->node, ob, DEG_OB_COMP_GEOMETRY, "Line Art Modifier");
|
|
DEG_add_object_relation(ctx->node, ob, DEG_OB_COMP_TRANSFORM, "Line Art Modifier");
|
|
object_dependencies.add(ob);
|
|
}
|
|
}
|
|
if (ob->type == OB_EMPTY && (ob->transflag & OB_DUPLICOLLECTION)) {
|
|
if (!ob->instance_collection) {
|
|
continue;
|
|
}
|
|
add_this_collection(*ob->instance_collection, ctx, mode, object_dependencies);
|
|
object_dependencies.add(ob);
|
|
}
|
|
}
|
|
FOREACH_COLLECTION_VISIBLE_OBJECT_RECURSIVE_END;
|
|
}
|
|
|
|
static void update_depsgraph(ModifierData *md, const ModifierUpdateDepsgraphContext *ctx)
|
|
{
|
|
DEG_add_object_relation(ctx->node, ctx->object, DEG_OB_COMP_TRANSFORM, "Line Art Modifier");
|
|
|
|
GreasePencilLineartModifierData *lmd = (GreasePencilLineartModifierData *)md;
|
|
|
|
/* Always add whole master collection because line art will need the whole scene for
|
|
* visibility computation. Line art exclusion is handled inside #add_this_collection. */
|
|
|
|
/* Do we need to distinguish DAG_EVAL_VIEWPORT or DAG_EVAL_RENDER here? */
|
|
|
|
LineartModifierRuntime *runtime = reinterpret_cast<LineartModifierRuntime *>(lmd->runtime);
|
|
if (!runtime) {
|
|
runtime = MEM_new<LineartModifierRuntime>(__func__);
|
|
lmd->runtime = runtime;
|
|
}
|
|
Set<const Object *> &object_dependencies = runtime->object_dependencies;
|
|
object_dependencies.clear();
|
|
|
|
add_this_collection(*ctx->scene->master_collection, ctx, DAG_EVAL_VIEWPORT, object_dependencies);
|
|
|
|
/* No need to add any non-geometry objects into `lmd->object_dependencies` because we won't be
|
|
* loading... */
|
|
if (lmd->calculation_flags & MOD_LINEART_USE_CUSTOM_CAMERA && lmd->source_camera) {
|
|
DEG_add_object_relation(
|
|
ctx->node, lmd->source_camera, DEG_OB_COMP_TRANSFORM, "Line Art Modifier");
|
|
DEG_add_object_relation(
|
|
ctx->node, lmd->source_camera, DEG_OB_COMP_PARAMETERS, "Line Art Modifier");
|
|
}
|
|
else if (ctx->scene->camera) {
|
|
DEG_add_object_relation(
|
|
ctx->node, ctx->scene->camera, DEG_OB_COMP_TRANSFORM, "Line Art Modifier");
|
|
DEG_add_object_relation(
|
|
ctx->node, ctx->scene->camera, DEG_OB_COMP_PARAMETERS, "Line Art Modifier");
|
|
DEG_add_scene_relation(ctx->node, ctx->scene, DEG_SCENE_COMP_PARAMETERS, "Line Art Modifier");
|
|
}
|
|
if (lmd->light_contour_object) {
|
|
DEG_add_object_relation(
|
|
ctx->node, lmd->light_contour_object, DEG_OB_COMP_TRANSFORM, "Line Art Modifier");
|
|
}
|
|
}
|
|
|
|
static void foreach_ID_link(ModifierData *md, Object *ob, IDWalkFunc walk, void *user_data)
|
|
{
|
|
GreasePencilLineartModifierData *lmd = (GreasePencilLineartModifierData *)md;
|
|
|
|
walk(user_data, ob, (ID **)&lmd->target_material, IDWALK_CB_USER);
|
|
walk(user_data, ob, (ID **)&lmd->source_collection, IDWALK_CB_NOP);
|
|
|
|
walk(user_data, ob, (ID **)&lmd->source_object, IDWALK_CB_NOP);
|
|
walk(user_data, ob, (ID **)&lmd->source_camera, IDWALK_CB_NOP);
|
|
walk(user_data, ob, (ID **)&lmd->light_contour_object, IDWALK_CB_NOP);
|
|
}
|
|
|
|
static void panel_draw(const bContext * /*C*/, Panel *panel)
|
|
{
|
|
uiLayout *layout = panel->layout;
|
|
|
|
PointerRNA ob_ptr;
|
|
PointerRNA *ptr = modifier_panel_get_property_pointers(panel, &ob_ptr);
|
|
|
|
PointerRNA obj_data_ptr = RNA_pointer_get(&ob_ptr, "data");
|
|
|
|
const int source_type = RNA_enum_get(ptr, "source_type");
|
|
const bool is_baked = RNA_boolean_get(ptr, "is_baked");
|
|
|
|
uiLayoutSetPropSep(layout, true);
|
|
uiLayoutSetEnabled(layout, !is_baked);
|
|
|
|
if (!is_first_lineart(*static_cast<const GreasePencilLineartModifierData *>(ptr->data))) {
|
|
uiItemR(layout, ptr, "use_cache", UI_ITEM_NONE, std::nullopt, ICON_NONE);
|
|
}
|
|
|
|
uiItemR(layout, ptr, "source_type", UI_ITEM_NONE, std::nullopt, ICON_NONE);
|
|
|
|
if (source_type == LINEART_SOURCE_OBJECT) {
|
|
uiItemR(layout, ptr, "source_object", UI_ITEM_NONE, std::nullopt, ICON_OBJECT_DATA);
|
|
}
|
|
else if (source_type == LINEART_SOURCE_COLLECTION) {
|
|
uiLayout *sub = uiLayoutRow(layout, true);
|
|
uiItemR(sub, ptr, "source_collection", UI_ITEM_NONE, std::nullopt, ICON_OUTLINER_COLLECTION);
|
|
uiItemR(sub, ptr, "use_invert_collection", UI_ITEM_NONE, "", ICON_ARROW_LEFTRIGHT);
|
|
}
|
|
else {
|
|
/* Source is Scene. */
|
|
}
|
|
|
|
uiLayout *col = uiLayoutColumn(layout, false);
|
|
uiItemPointerR(col,
|
|
ptr,
|
|
"target_layer",
|
|
&obj_data_ptr,
|
|
"layers",
|
|
std::nullopt,
|
|
ICON_OUTLINER_DATA_GP_LAYER);
|
|
uiItemPointerR(
|
|
col, ptr, "target_material", &obj_data_ptr, "materials", std::nullopt, ICON_MATERIAL);
|
|
|
|
col = uiLayoutColumn(layout, false);
|
|
uiItemR(col, ptr, "thickness", UI_ITEM_R_SLIDER, IFACE_("Line Thickness"), ICON_NONE);
|
|
uiItemR(col, ptr, "opacity", UI_ITEM_R_SLIDER, std::nullopt, ICON_NONE);
|
|
|
|
modifier_panel_end(layout, ptr);
|
|
}
|
|
|
|
static void edge_types_panel_draw(const bContext * /*C*/, Panel *panel)
|
|
{
|
|
uiLayout *layout = panel->layout;
|
|
PointerRNA ob_ptr;
|
|
PointerRNA *ptr = modifier_panel_get_property_pointers(panel, &ob_ptr);
|
|
|
|
const bool is_baked = RNA_boolean_get(ptr, "is_baked");
|
|
const bool use_cache = RNA_boolean_get(ptr, "use_cache");
|
|
const bool is_first = is_first_lineart(
|
|
*static_cast<const GreasePencilLineartModifierData *>(ptr->data));
|
|
const bool has_light = RNA_pointer_get(ptr, "light_contour_object").data != nullptr;
|
|
|
|
uiLayoutSetEnabled(layout, !is_baked);
|
|
|
|
uiLayoutSetPropSep(layout, true);
|
|
|
|
uiLayout *sub = uiLayoutRow(layout, false);
|
|
uiLayoutSetActive(sub, has_light);
|
|
uiItemR(sub,
|
|
ptr,
|
|
"shadow_region_filtering",
|
|
UI_ITEM_NONE,
|
|
IFACE_("Illumination Filtering"),
|
|
ICON_NONE);
|
|
|
|
uiLayout *col = uiLayoutColumn(layout, true);
|
|
|
|
sub = uiLayoutRowWithHeading(col, false, IFACE_("Create"));
|
|
uiItemR(sub, ptr, "use_contour", UI_ITEM_NONE, "", ICON_NONE);
|
|
|
|
uiLayout *entry = uiLayoutRow(sub, true);
|
|
uiLayoutSetActive(entry, RNA_boolean_get(ptr, "use_contour"));
|
|
uiItemR(entry, ptr, "silhouette_filtering", UI_ITEM_NONE, "", ICON_NONE);
|
|
|
|
const int silhouette_filtering = RNA_enum_get(ptr, "silhouette_filtering");
|
|
if (silhouette_filtering != LINEART_SILHOUETTE_FILTER_NONE) {
|
|
uiItemR(entry, ptr, "use_invert_silhouette", UI_ITEM_NONE, "", ICON_ARROW_LEFTRIGHT);
|
|
}
|
|
|
|
sub = uiLayoutRow(col, false);
|
|
if (use_cache && !is_first) {
|
|
uiItemR(sub, ptr, "use_crease", UI_ITEM_NONE, IFACE_("Crease (Angle Cached)"), ICON_NONE);
|
|
}
|
|
else {
|
|
uiItemR(sub, ptr, "use_crease", UI_ITEM_NONE, "", ICON_NONE);
|
|
uiItemR(sub,
|
|
ptr,
|
|
"crease_threshold",
|
|
UI_ITEM_R_SLIDER | UI_ITEM_R_FORCE_BLANK_DECORATE,
|
|
std::nullopt,
|
|
ICON_NONE);
|
|
}
|
|
|
|
uiItemR(col, ptr, "use_intersection", UI_ITEM_NONE, IFACE_("Intersections"), ICON_NONE);
|
|
uiItemR(col, ptr, "use_material", UI_ITEM_NONE, IFACE_("Material Borders"), ICON_NONE);
|
|
uiItemR(col, ptr, "use_edge_mark", UI_ITEM_NONE, IFACE_("Edge Marks"), ICON_NONE);
|
|
uiItemR(col, ptr, "use_loose", UI_ITEM_NONE, IFACE_("Loose"), ICON_NONE);
|
|
|
|
entry = uiLayoutColumn(col, false);
|
|
uiLayoutSetActive(entry, has_light);
|
|
|
|
sub = uiLayoutRow(entry, false);
|
|
uiItemR(sub, ptr, "use_light_contour", UI_ITEM_NONE, IFACE_("Light Contour"), ICON_NONE);
|
|
|
|
uiItemR(entry,
|
|
ptr,
|
|
"use_shadow",
|
|
UI_ITEM_NONE,
|
|
CTX_IFACE_(BLT_I18NCONTEXT_ID_GPENCIL, "Cast Shadow"),
|
|
ICON_NONE);
|
|
|
|
uiItemL(layout, IFACE_("Options"), ICON_NONE);
|
|
|
|
sub = uiLayoutColumn(layout, false);
|
|
if (use_cache && !is_first) {
|
|
uiItemL(sub, IFACE_("Type overlapping cached"), ICON_INFO);
|
|
}
|
|
else {
|
|
uiItemR(sub,
|
|
ptr,
|
|
"use_overlap_edge_type_support",
|
|
UI_ITEM_NONE,
|
|
IFACE_("Allow Overlapping Types"),
|
|
ICON_NONE);
|
|
}
|
|
}
|
|
|
|
static void options_light_reference_draw(const bContext * /*C*/, Panel *panel)
|
|
{
|
|
uiLayout *layout = panel->layout;
|
|
PointerRNA ob_ptr;
|
|
PointerRNA *ptr = modifier_panel_get_property_pointers(panel, &ob_ptr);
|
|
|
|
const bool is_baked = RNA_boolean_get(ptr, "is_baked");
|
|
const bool use_cache = RNA_boolean_get(ptr, "use_cache");
|
|
const bool has_light = RNA_pointer_get(ptr, "light_contour_object").data != nullptr;
|
|
const bool is_first = is_first_lineart(
|
|
*static_cast<const GreasePencilLineartModifierData *>(ptr->data));
|
|
|
|
uiLayoutSetPropSep(layout, true);
|
|
uiLayoutSetEnabled(layout, !is_baked);
|
|
|
|
if (use_cache && !is_first) {
|
|
uiItemL(layout, RPT_("Cached from the first Line Art modifier."), ICON_INFO);
|
|
return;
|
|
}
|
|
|
|
uiItemR(layout, ptr, "light_contour_object", UI_ITEM_NONE, std::nullopt, ICON_NONE);
|
|
|
|
uiLayout *remaining = uiLayoutColumn(layout, false);
|
|
uiLayoutSetActive(remaining, has_light);
|
|
|
|
uiItemR(remaining, ptr, "shadow_camera_size", UI_ITEM_NONE, std::nullopt, ICON_NONE);
|
|
|
|
uiLayout *col = uiLayoutColumn(remaining, true);
|
|
uiItemR(col, ptr, "shadow_camera_near", UI_ITEM_NONE, IFACE_("Near"), ICON_NONE);
|
|
uiItemR(col, ptr, "shadow_camera_far", UI_ITEM_NONE, IFACE_("Far"), ICON_NONE);
|
|
}
|
|
|
|
static void options_panel_draw(const bContext * /*C*/, Panel *panel)
|
|
{
|
|
uiLayout *layout = panel->layout;
|
|
PointerRNA ob_ptr;
|
|
PointerRNA *ptr = modifier_panel_get_property_pointers(panel, &ob_ptr);
|
|
|
|
const bool is_baked = RNA_boolean_get(ptr, "is_baked");
|
|
const bool use_cache = RNA_boolean_get(ptr, "use_cache");
|
|
const bool is_first = is_first_lineart(
|
|
*static_cast<const GreasePencilLineartModifierData *>(ptr->data));
|
|
|
|
uiLayoutSetPropSep(layout, true);
|
|
uiLayoutSetEnabled(layout, !is_baked);
|
|
|
|
if (use_cache && !is_first) {
|
|
uiItemL(layout, TIP_("Cached from the first Line Art modifier"), ICON_INFO);
|
|
return;
|
|
}
|
|
|
|
uiLayout *row = uiLayoutRowWithHeading(layout, false, IFACE_("Custom Camera"));
|
|
uiItemR(row, ptr, "use_custom_camera", UI_ITEM_NONE, "", ICON_NONE);
|
|
uiLayout *subrow = uiLayoutRow(row, true);
|
|
uiLayoutSetActive(subrow, RNA_boolean_get(ptr, "use_custom_camera"));
|
|
uiLayoutSetPropSep(subrow, true);
|
|
uiItemR(subrow, ptr, "source_camera", UI_ITEM_NONE, "", ICON_OBJECT_DATA);
|
|
|
|
uiLayout *col = uiLayoutColumn(layout, true);
|
|
|
|
uiItemR(col,
|
|
ptr,
|
|
"use_edge_overlap",
|
|
UI_ITEM_NONE,
|
|
IFACE_("Overlapping Edges As Contour"),
|
|
ICON_NONE);
|
|
uiItemR(col, ptr, "use_object_instances", UI_ITEM_NONE, std::nullopt, ICON_NONE);
|
|
uiItemR(col, ptr, "use_clip_plane_boundaries", UI_ITEM_NONE, std::nullopt, ICON_NONE);
|
|
uiItemR(col, ptr, "use_crease_on_smooth", UI_ITEM_NONE, IFACE_("Crease On Smooth"), ICON_NONE);
|
|
uiItemR(col, ptr, "use_crease_on_sharp", UI_ITEM_NONE, IFACE_("Crease On Sharp"), ICON_NONE);
|
|
uiItemR(col,
|
|
ptr,
|
|
"use_back_face_culling",
|
|
UI_ITEM_NONE,
|
|
IFACE_("Force Backface Culling"),
|
|
ICON_NONE);
|
|
}
|
|
|
|
static void occlusion_panel_draw(const bContext * /*C*/, Panel *panel)
|
|
{
|
|
uiLayout *layout = panel->layout;
|
|
PointerRNA ob_ptr;
|
|
PointerRNA *ptr = modifier_panel_get_property_pointers(panel, &ob_ptr);
|
|
|
|
const bool is_baked = RNA_boolean_get(ptr, "is_baked");
|
|
|
|
const bool use_multiple_levels = RNA_boolean_get(ptr, "use_multiple_levels");
|
|
const bool show_in_front = RNA_boolean_get(&ob_ptr, "show_in_front");
|
|
|
|
uiLayoutSetPropSep(layout, true);
|
|
uiLayoutSetEnabled(layout, !is_baked);
|
|
|
|
if (!show_in_front) {
|
|
uiItemL(layout, TIP_("Object is not in front"), ICON_INFO);
|
|
}
|
|
|
|
layout = uiLayoutColumn(layout, false);
|
|
uiLayoutSetActive(layout, show_in_front);
|
|
|
|
uiItemR(layout, ptr, "use_multiple_levels", UI_ITEM_NONE, IFACE_("Range"), ICON_NONE);
|
|
|
|
if (use_multiple_levels) {
|
|
uiLayout *col = uiLayoutColumn(layout, true);
|
|
uiItemR(col, ptr, "level_start", UI_ITEM_NONE, std::nullopt, ICON_NONE);
|
|
uiItemR(col, ptr, "level_end", UI_ITEM_NONE, IFACE_("End"), ICON_NONE);
|
|
}
|
|
else {
|
|
uiItemR(layout, ptr, "level_start", UI_ITEM_NONE, IFACE_("Level"), ICON_NONE);
|
|
}
|
|
}
|
|
|
|
static bool anything_showing_through(PointerRNA *ptr)
|
|
{
|
|
const bool use_multiple_levels = RNA_boolean_get(ptr, "use_multiple_levels");
|
|
const int level_start = RNA_int_get(ptr, "level_start");
|
|
const int level_end = RNA_int_get(ptr, "level_end");
|
|
if (use_multiple_levels) {
|
|
return std::max(level_start, level_end) > 0;
|
|
}
|
|
return level_start > 0;
|
|
}
|
|
|
|
static void material_mask_panel_draw_header(const bContext * /*C*/, Panel *panel)
|
|
{
|
|
uiLayout *layout = panel->layout;
|
|
PointerRNA ob_ptr;
|
|
PointerRNA *ptr = modifier_panel_get_property_pointers(panel, &ob_ptr);
|
|
|
|
const bool is_baked = RNA_boolean_get(ptr, "is_baked");
|
|
const bool show_in_front = RNA_boolean_get(&ob_ptr, "show_in_front");
|
|
|
|
uiLayoutSetEnabled(layout, !is_baked);
|
|
uiLayoutSetActive(layout, show_in_front && anything_showing_through(ptr));
|
|
|
|
uiItemR(layout, ptr, "use_material_mask", UI_ITEM_NONE, IFACE_("Material Mask"), ICON_NONE);
|
|
}
|
|
|
|
static void material_mask_panel_draw(const bContext * /*C*/, Panel *panel)
|
|
{
|
|
uiLayout *layout = panel->layout;
|
|
PointerRNA *ptr = modifier_panel_get_property_pointers(panel, nullptr);
|
|
|
|
const bool is_baked = RNA_boolean_get(ptr, "is_baked");
|
|
uiLayoutSetEnabled(layout, !is_baked);
|
|
uiLayoutSetActive(layout, anything_showing_through(ptr));
|
|
|
|
uiLayoutSetPropSep(layout, true);
|
|
|
|
uiLayoutSetEnabled(layout, RNA_boolean_get(ptr, "use_material_mask"));
|
|
|
|
uiLayout *col = uiLayoutColumn(layout, true);
|
|
uiLayout *sub = uiLayoutRowWithHeading(col, true, IFACE_("Masks"));
|
|
|
|
PropertyRNA *prop = RNA_struct_find_property(ptr, "use_material_mask_bits");
|
|
for (int i = 0; i < 8; i++) {
|
|
uiItemFullR(sub, ptr, prop, i, 0, UI_ITEM_R_TOGGLE, " ", ICON_NONE);
|
|
if (i == 3) {
|
|
sub = uiLayoutRow(col, true);
|
|
}
|
|
}
|
|
|
|
uiItemR(layout, ptr, "use_material_mask_match", UI_ITEM_NONE, IFACE_("Exact Match"), ICON_NONE);
|
|
}
|
|
|
|
static void intersection_panel_draw(const bContext * /*C*/, Panel *panel)
|
|
{
|
|
uiLayout *layout = panel->layout;
|
|
PointerRNA *ptr = modifier_panel_get_property_pointers(panel, nullptr);
|
|
|
|
const bool is_baked = RNA_boolean_get(ptr, "is_baked");
|
|
uiLayoutSetEnabled(layout, !is_baked);
|
|
|
|
uiLayoutSetPropSep(layout, true);
|
|
|
|
uiLayoutSetActive(layout, RNA_boolean_get(ptr, "use_intersection"));
|
|
|
|
uiLayout *col = uiLayoutColumn(layout, true);
|
|
uiLayout *sub = uiLayoutRowWithHeading(col, true, IFACE_("Collection Masks"));
|
|
|
|
PropertyRNA *prop = RNA_struct_find_property(ptr, "use_intersection_mask");
|
|
for (int i = 0; i < 8; i++) {
|
|
uiItemFullR(sub, ptr, prop, i, 0, UI_ITEM_R_TOGGLE, " ", ICON_NONE);
|
|
if (i == 3) {
|
|
sub = uiLayoutRow(col, true);
|
|
}
|
|
}
|
|
|
|
uiItemR(layout, ptr, "use_intersection_match", UI_ITEM_NONE, IFACE_("Exact Match"), ICON_NONE);
|
|
}
|
|
|
|
static void face_mark_panel_draw_header(const bContext * /*C*/, Panel *panel)
|
|
{
|
|
uiLayout *layout = panel->layout;
|
|
PointerRNA ob_ptr;
|
|
PointerRNA *ptr = modifier_panel_get_property_pointers(panel, &ob_ptr);
|
|
|
|
const bool is_baked = RNA_boolean_get(ptr, "is_baked");
|
|
const bool use_cache = RNA_boolean_get(ptr, "use_cache");
|
|
const bool is_first = is_first_lineart(
|
|
*static_cast<const GreasePencilLineartModifierData *>(ptr->data));
|
|
|
|
if (!use_cache || is_first) {
|
|
uiLayoutSetEnabled(layout, !is_baked);
|
|
uiItemR(layout, ptr, "use_face_mark", UI_ITEM_NONE, IFACE_("Face Mark Filtering"), ICON_NONE);
|
|
}
|
|
else {
|
|
uiItemL(layout, IFACE_("Face Mark Filtering"), ICON_NONE);
|
|
}
|
|
}
|
|
|
|
static void face_mark_panel_draw(const bContext * /*C*/, Panel *panel)
|
|
{
|
|
uiLayout *layout = panel->layout;
|
|
PointerRNA ob_ptr;
|
|
PointerRNA *ptr = modifier_panel_get_property_pointers(panel, &ob_ptr);
|
|
|
|
const bool is_baked = RNA_boolean_get(ptr, "is_baked");
|
|
const bool use_mark = RNA_boolean_get(ptr, "use_face_mark");
|
|
const bool use_cache = RNA_boolean_get(ptr, "use_cache");
|
|
const bool is_first = is_first_lineart(
|
|
*static_cast<const GreasePencilLineartModifierData *>(ptr->data));
|
|
|
|
uiLayoutSetEnabled(layout, !is_baked);
|
|
|
|
if (use_cache && !is_first) {
|
|
uiItemL(layout, TIP_("Cached from the first Line Art modifier"), ICON_INFO);
|
|
return;
|
|
}
|
|
|
|
uiLayoutSetPropSep(layout, true);
|
|
|
|
uiLayoutSetActive(layout, use_mark);
|
|
|
|
uiItemR(layout, ptr, "use_face_mark_invert", UI_ITEM_NONE, std::nullopt, ICON_NONE);
|
|
uiItemR(layout, ptr, "use_face_mark_boundaries", UI_ITEM_NONE, std::nullopt, ICON_NONE);
|
|
uiItemR(layout, ptr, "use_face_mark_keep_contour", UI_ITEM_NONE, std::nullopt, ICON_NONE);
|
|
}
|
|
|
|
static void chaining_panel_draw(const bContext * /*C*/, Panel *panel)
|
|
{
|
|
PointerRNA ob_ptr;
|
|
PointerRNA *ptr = modifier_panel_get_property_pointers(panel, &ob_ptr);
|
|
|
|
uiLayout *layout = panel->layout;
|
|
|
|
const bool is_baked = RNA_boolean_get(ptr, "is_baked");
|
|
const bool use_cache = RNA_boolean_get(ptr, "use_cache");
|
|
const bool is_first = is_first_lineart(
|
|
*static_cast<const GreasePencilLineartModifierData *>(ptr->data));
|
|
const bool is_geom = RNA_boolean_get(ptr, "use_geometry_space_chain");
|
|
|
|
uiLayoutSetPropSep(layout, true);
|
|
uiLayoutSetEnabled(layout, !is_baked);
|
|
|
|
if (use_cache && !is_first) {
|
|
uiItemL(layout, TIP_("Cached from the first Line Art modifier"), ICON_INFO);
|
|
return;
|
|
}
|
|
|
|
uiLayout *col = uiLayoutColumnWithHeading(layout, true, IFACE_("Chain"));
|
|
uiItemR(col, ptr, "use_fuzzy_intersections", UI_ITEM_NONE, std::nullopt, ICON_NONE);
|
|
uiItemR(col, ptr, "use_fuzzy_all", UI_ITEM_NONE, std::nullopt, ICON_NONE);
|
|
uiItemR(col, ptr, "use_loose_edge_chain", UI_ITEM_NONE, IFACE_("Loose Edges"), ICON_NONE);
|
|
uiItemR(
|
|
col, ptr, "use_loose_as_contour", UI_ITEM_NONE, IFACE_("Loose Edges As Contour"), ICON_NONE);
|
|
uiItemR(col, ptr, "use_detail_preserve", UI_ITEM_NONE, std::nullopt, ICON_NONE);
|
|
uiItemR(col, ptr, "use_geometry_space_chain", UI_ITEM_NONE, IFACE_("Geometry Space"), ICON_NONE);
|
|
|
|
uiItemR(layout,
|
|
ptr,
|
|
"chaining_image_threshold",
|
|
UI_ITEM_NONE,
|
|
is_geom ? std::make_optional<StringRefNull>(IFACE_("Geometry Threshold")) : std::nullopt,
|
|
ICON_NONE);
|
|
|
|
uiItemR(layout, ptr, "smooth_tolerance", UI_ITEM_R_SLIDER, std::nullopt, ICON_NONE);
|
|
uiItemR(layout, ptr, "split_angle", UI_ITEM_R_SLIDER, std::nullopt, ICON_NONE);
|
|
}
|
|
|
|
static void vgroup_panel_draw(const bContext * /*C*/, Panel *panel)
|
|
{
|
|
PointerRNA ob_ptr;
|
|
PointerRNA *ptr = modifier_panel_get_property_pointers(panel, &ob_ptr);
|
|
|
|
uiLayout *layout = panel->layout;
|
|
|
|
const bool is_baked = RNA_boolean_get(ptr, "is_baked");
|
|
const bool use_cache = RNA_boolean_get(ptr, "use_cache");
|
|
const bool is_first = is_first_lineart(
|
|
*static_cast<const GreasePencilLineartModifierData *>(ptr->data));
|
|
|
|
uiLayoutSetPropSep(layout, true);
|
|
uiLayoutSetEnabled(layout, !is_baked);
|
|
|
|
if (use_cache && !is_first) {
|
|
uiItemL(layout, TIP_("Cached from the first Line Art modifier"), ICON_INFO);
|
|
return;
|
|
}
|
|
|
|
uiLayout *col = uiLayoutColumn(layout, true);
|
|
|
|
uiLayout *row = uiLayoutRow(col, true);
|
|
|
|
uiItemR(
|
|
row, ptr, "source_vertex_group", UI_ITEM_NONE, IFACE_("Filter Source"), ICON_GROUP_VERTEX);
|
|
uiItemR(row, ptr, "invert_source_vertex_group", UI_ITEM_R_TOGGLE, "", ICON_ARROW_LEFTRIGHT);
|
|
|
|
uiItemR(
|
|
col, ptr, "use_output_vertex_group_match_by_name", UI_ITEM_NONE, std::nullopt, ICON_NONE);
|
|
|
|
uiItemPointerR(col, ptr, "vertex_group", &ob_ptr, "vertex_groups", IFACE_("Target"), ICON_NONE);
|
|
}
|
|
|
|
static void bake_panel_draw(const bContext * /*C*/, Panel *panel)
|
|
{
|
|
uiLayout *layout = panel->layout;
|
|
PointerRNA ob_ptr;
|
|
PointerRNA *ptr = modifier_panel_get_property_pointers(panel, &ob_ptr);
|
|
|
|
const bool is_baked = RNA_boolean_get(ptr, "is_baked");
|
|
|
|
uiLayoutSetPropSep(layout, true);
|
|
|
|
if (is_baked) {
|
|
uiLayout *col = uiLayoutColumn(layout, false);
|
|
uiLayoutSetPropSep(col, false);
|
|
uiItemL(col, TIP_("Modifier has baked data"), ICON_NONE);
|
|
uiItemR(
|
|
col, ptr, "is_baked", UI_ITEM_R_TOGGLE, IFACE_("Continue Without Clearing"), ICON_NONE);
|
|
}
|
|
|
|
uiLayout *col = uiLayoutColumn(layout, false);
|
|
uiLayoutSetEnabled(col, !is_baked);
|
|
uiItemO(col, std::nullopt, ICON_NONE, "OBJECT_OT_lineart_bake_strokes");
|
|
uiItemBooleanO(
|
|
col, IFACE_("Bake All"), ICON_NONE, "OBJECT_OT_lineart_bake_strokes", "bake_all", true);
|
|
|
|
col = uiLayoutColumn(layout, false);
|
|
uiItemO(col, std::nullopt, ICON_NONE, "OBJECT_OT_lineart_clear");
|
|
uiItemBooleanO(
|
|
col, IFACE_("Clear All"), ICON_NONE, "OBJECT_OT_lineart_clear", "clear_all", true);
|
|
}
|
|
|
|
static void composition_panel_draw(const bContext * /*C*/, Panel *panel)
|
|
{
|
|
PointerRNA ob_ptr;
|
|
PointerRNA *ptr = modifier_panel_get_property_pointers(panel, &ob_ptr);
|
|
|
|
uiLayout *layout = panel->layout;
|
|
|
|
const bool show_in_front = RNA_boolean_get(&ob_ptr, "show_in_front");
|
|
|
|
uiLayoutSetPropSep(layout, true);
|
|
|
|
uiItemR(layout, ptr, "overscan", UI_ITEM_NONE, std::nullopt, ICON_NONE);
|
|
uiItemR(layout, ptr, "use_image_boundary_trimming", UI_ITEM_NONE, std::nullopt, ICON_NONE);
|
|
|
|
if (show_in_front) {
|
|
uiItemL(layout, TIP_("Object is shown in front"), ICON_ERROR);
|
|
}
|
|
|
|
uiLayout *col = uiLayoutColumn(layout, false);
|
|
uiLayoutSetActive(col, !show_in_front);
|
|
|
|
uiItemR(col, ptr, "stroke_depth_offset", UI_ITEM_R_SLIDER, IFACE_("Depth Offset"), ICON_NONE);
|
|
uiItemR(col,
|
|
ptr,
|
|
"use_offset_towards_custom_camera",
|
|
UI_ITEM_NONE,
|
|
IFACE_("Towards Custom Camera"),
|
|
ICON_NONE);
|
|
}
|
|
|
|
static void panel_register(ARegionType *region_type)
|
|
{
|
|
PanelType *panel_type = modifier_panel_register(
|
|
region_type, eModifierType_GreasePencilLineart, panel_draw);
|
|
|
|
modifier_subpanel_register(
|
|
region_type, "edge_types", "Edge Types", nullptr, edge_types_panel_draw, panel_type);
|
|
modifier_subpanel_register(region_type,
|
|
"light_reference",
|
|
"Light Reference",
|
|
nullptr,
|
|
options_light_reference_draw,
|
|
panel_type);
|
|
modifier_subpanel_register(
|
|
region_type, "geometry", "Geometry Processing", nullptr, options_panel_draw, panel_type);
|
|
PanelType *occlusion_panel = modifier_subpanel_register(
|
|
region_type, "occlusion", "Occlusion", nullptr, occlusion_panel_draw, panel_type);
|
|
modifier_subpanel_register(region_type,
|
|
"material_mask",
|
|
"",
|
|
material_mask_panel_draw_header,
|
|
material_mask_panel_draw,
|
|
occlusion_panel);
|
|
modifier_subpanel_register(
|
|
region_type, "intersection", "Intersection", nullptr, intersection_panel_draw, panel_type);
|
|
modifier_subpanel_register(
|
|
region_type, "face_mark", "", face_mark_panel_draw_header, face_mark_panel_draw, panel_type);
|
|
modifier_subpanel_register(
|
|
region_type, "chaining", "Chaining", nullptr, chaining_panel_draw, panel_type);
|
|
modifier_subpanel_register(
|
|
region_type, "vgroup", "Vertex Weight Transfer", nullptr, vgroup_panel_draw, panel_type);
|
|
modifier_subpanel_register(
|
|
region_type, "composition", "Composition", nullptr, composition_panel_draw, panel_type);
|
|
modifier_subpanel_register(region_type, "bake", "Bake", nullptr, bake_panel_draw, panel_type);
|
|
}
|
|
|
|
static void generate_strokes(ModifierData &md,
|
|
const ModifierEvalContext &ctx,
|
|
GreasePencil &grease_pencil,
|
|
GreasePencilLineartModifierData &first_lineart,
|
|
const bool force_compute)
|
|
{
|
|
using namespace bke::greasepencil;
|
|
auto &lmd = reinterpret_cast<GreasePencilLineartModifierData &>(md);
|
|
|
|
TreeNode *node = grease_pencil.find_node_by_name(lmd.target_layer);
|
|
if (!node || !node->is_layer()) {
|
|
return;
|
|
}
|
|
|
|
const bool is_first_lineart = (&first_lineart == &lmd);
|
|
const bool use_cache = (lmd.flags & MOD_LINEART_USE_CACHE);
|
|
LineartCache *local_lc = (is_first_lineart || use_cache) ? first_lineart.shared_cache : nullptr;
|
|
|
|
/* Only calculate strokes in these three conditions:
|
|
* 1. It's the very first line art modifier in the stack.
|
|
* 2. This line art modifier doesn't want to use globally cached data.
|
|
* 3. This modifier is not the first line art in stack, but it's the first that's visible (so we
|
|
* need to do a `force_compute`). */
|
|
if (is_first_lineart || (!use_cache) || force_compute) {
|
|
MOD_lineart_compute_feature_lines_v3(
|
|
ctx.depsgraph, lmd, &local_lc, !(ctx.object->dtx & OB_DRAW_IN_FRONT));
|
|
MOD_lineart_destroy_render_data_v3(&lmd);
|
|
}
|
|
MOD_lineart_chain_clear_picked_flag(local_lc);
|
|
lmd.cache = local_lc;
|
|
|
|
const int current_frame = grease_pencil.runtime->eval_frame;
|
|
|
|
/* Ensure we have a frame in the selected layer to put line art result in. */
|
|
Layer &layer = node->as_layer();
|
|
|
|
const float4x4 &mat = ctx.object->world_to_object();
|
|
|
|
/* `drawing` can be nullptr if current frame is before any of the key frames, in which case no
|
|
* strokes are generated. We still allow cache operations to run at the end of this function
|
|
* because there might be other line art modifiers in the same stack. */
|
|
Drawing *drawing = [&]() -> Drawing * {
|
|
if (Drawing *drawing = grease_pencil.get_drawing_at(layer, current_frame)) {
|
|
return drawing;
|
|
}
|
|
return grease_pencil.insert_frame(layer, current_frame);
|
|
}();
|
|
|
|
if (drawing) {
|
|
MOD_lineart_gpencil_generate_v3(
|
|
lmd.cache,
|
|
mat,
|
|
ctx.depsgraph,
|
|
*drawing,
|
|
lmd.source_type,
|
|
lmd.source_object,
|
|
lmd.source_collection,
|
|
lmd.level_start,
|
|
lmd.use_multiple_levels ? lmd.level_end : lmd.level_start,
|
|
lmd.target_material ? BKE_object_material_index_get(ctx.object, lmd.target_material) : 0,
|
|
lmd.edge_types,
|
|
lmd.mask_switches,
|
|
lmd.material_mask_bits,
|
|
lmd.intersection_mask,
|
|
float(lmd.thickness) / 1000.0f,
|
|
lmd.opacity,
|
|
lmd.shadow_selection,
|
|
lmd.silhouette_selection,
|
|
lmd.source_vertex_group,
|
|
lmd.vgname,
|
|
lmd.flags,
|
|
lmd.calculation_flags);
|
|
}
|
|
|
|
if ((!is_first_lineart) && (!use_cache)) {
|
|
/* We only clear local cache, not global cache from the first line art modifier. */
|
|
BLI_assert(local_lc != first_lineart.shared_cache);
|
|
MOD_lineart_clear_cache(&local_lc);
|
|
/* Restore the original cache pointer so the modifiers below still have access to the "global"
|
|
* cache. */
|
|
lmd.cache = first_lineart.shared_cache;
|
|
}
|
|
}
|
|
|
|
static void modify_geometry_set(ModifierData *md,
|
|
const ModifierEvalContext *ctx,
|
|
bke::GeometrySet *geometry_set)
|
|
{
|
|
if (!geometry_set->has_grease_pencil()) {
|
|
return;
|
|
}
|
|
GreasePencil &grease_pencil = *geometry_set->get_grease_pencil_for_write();
|
|
auto *mmd = reinterpret_cast<GreasePencilLineartModifierData *>(md);
|
|
|
|
GreasePencilLineartModifierData *first_lineart =
|
|
blender::ed::greasepencil::get_first_lineart_modifier(*ctx->object);
|
|
BLI_assert(first_lineart);
|
|
|
|
/* Since settings for line art cached data are always in the first line art modifier, we need to
|
|
* get and set overall calculation limits on the first modifier regardless of its visibility
|
|
* state. If line art cache doesn't exist, it means line art hasn't done any calculation. */
|
|
const bool cache_ready = (first_lineart->shared_cache != nullptr);
|
|
if (!cache_ready) {
|
|
first_lineart->shared_cache = MOD_lineart_init_cache();
|
|
ed::greasepencil::get_lineart_modifier_limits(*ctx->object,
|
|
first_lineart->shared_cache->LimitInfo);
|
|
}
|
|
ed::greasepencil::set_lineart_modifier_limits(
|
|
*mmd, first_lineart->shared_cache->LimitInfo, cache_ready);
|
|
|
|
generate_strokes(*md, *ctx, grease_pencil, *first_lineart, (!cache_ready));
|
|
|
|
const bool use_render_params = (ctx->flag & MOD_APPLY_RENDER);
|
|
if (is_last_line_art(*mmd, use_render_params)) {
|
|
MOD_lineart_clear_cache(&first_lineart->shared_cache);
|
|
}
|
|
|
|
DEG_id_tag_update(&grease_pencil.id, ID_RECALC_GEOMETRY);
|
|
}
|
|
|
|
static void blend_write(BlendWriter *writer, const ID * /*id_owner*/, const ModifierData *md)
|
|
{
|
|
const auto *lmd = reinterpret_cast<const GreasePencilLineartModifierData *>(md);
|
|
|
|
BLO_write_struct(writer, GreasePencilLineartModifierData, lmd);
|
|
}
|
|
|
|
static void blend_read(BlendDataReader * /*reader*/, ModifierData *md)
|
|
{
|
|
GreasePencilLineartModifierData *lmd = reinterpret_cast<GreasePencilLineartModifierData *>(md);
|
|
lmd->runtime = MEM_new<LineartModifierRuntime>(__func__);
|
|
}
|
|
|
|
} // namespace blender
|
|
|
|
ModifierTypeInfo modifierType_GreasePencilLineart = {
|
|
/*idname*/ "Lineart Modifier",
|
|
/*name*/ N_("Lineart"),
|
|
/*struct_name*/ "GreasePencilLineartModifierData",
|
|
/*struct_size*/ sizeof(GreasePencilLineartModifierData),
|
|
/*srna*/ &RNA_GreasePencilLineartModifier,
|
|
/*type*/ ModifierTypeType::Constructive,
|
|
/*flags*/ eModifierTypeFlag_AcceptsGreasePencil,
|
|
/*icon*/ ICON_MOD_LINEART,
|
|
|
|
/*copy_data*/ blender::copy_data,
|
|
|
|
/*deform_verts*/ nullptr,
|
|
/*deform_matrices*/ nullptr,
|
|
/*deform_verts_EM*/ nullptr,
|
|
/*deform_matrices_EM*/ nullptr,
|
|
/*modify_mesh*/ nullptr,
|
|
/*modify_geometry_set*/ blender::modify_geometry_set,
|
|
|
|
/*init_data*/ blender::init_data,
|
|
/*required_data_mask*/ nullptr,
|
|
/*free_data*/ blender::free_data,
|
|
/*is_disabled*/ blender::is_disabled,
|
|
/*update_depsgraph*/ blender::update_depsgraph,
|
|
/*depends_on_time*/ nullptr,
|
|
/*depends_on_normals*/ nullptr,
|
|
/*foreach_ID_link*/ blender::foreach_ID_link,
|
|
/*foreach_tex_link*/ nullptr,
|
|
/*free_runtime_data*/ nullptr,
|
|
/*panel_register*/ blender::panel_register,
|
|
/*blend_write*/ blender::blend_write,
|
|
/*blend_read*/ blender::blend_read,
|
|
};
|