This commit modifies the `OBJECT_OT_multires_base_apply` operator to take in a new parameter which determines whether the final heuristic of adjusting for a subdivision surface modifier applies. Resolves #124553 Pull Request: https://projects.blender.org/blender/blender/pulls/145055
557 lines
16 KiB
C++
557 lines
16 KiB
C++
/* SPDX-FileCopyrightText: 2024 Blender Authors
|
|
*
|
|
* SPDX-License-Identifier: GPL-2.0-or-later */
|
|
|
|
#include "object_intern.hh"
|
|
|
|
#include "DNA_mesh_types.h"
|
|
#include "DNA_modifier_types.h"
|
|
#include "DNA_object_types.h"
|
|
#include "DNA_space_types.h"
|
|
#include "DNA_windowmanager_types.h"
|
|
|
|
#include "BKE_context.hh"
|
|
#include "BKE_customdata.hh"
|
|
#include "BKE_main.hh"
|
|
#include "BKE_multires.hh"
|
|
#include "BKE_paint.hh"
|
|
#include "BKE_report.hh"
|
|
|
|
#include "BLI_path_utils.hh"
|
|
#include "BLI_string_utf8.h"
|
|
|
|
#include "DEG_depsgraph.hh"
|
|
|
|
#include "ED_object.hh"
|
|
#include "ED_sculpt.hh"
|
|
|
|
#include "RNA_access.hh"
|
|
#include "RNA_define.hh"
|
|
#include "RNA_prototypes.hh"
|
|
|
|
#include "WM_api.hh"
|
|
#include "WM_types.hh"
|
|
|
|
namespace blender::ed::object {
|
|
|
|
/* ------------------------------------------------------------------- */
|
|
/** \name Multires Delete Higher Levels Operator
|
|
* \{ */
|
|
|
|
static bool multires_poll(bContext *C)
|
|
{
|
|
return edit_modifier_poll_generic(C, &RNA_MultiresModifier, (1 << OB_MESH), true, false);
|
|
}
|
|
|
|
static wmOperatorStatus multires_higher_levels_delete_exec(bContext *C, wmOperator *op)
|
|
{
|
|
Scene *scene = CTX_data_scene(C);
|
|
Object *ob = context_active_object(C);
|
|
MultiresModifierData *mmd = (MultiresModifierData *)edit_modifier_property_get(
|
|
op, ob, eModifierType_Multires);
|
|
|
|
if (!mmd) {
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
|
|
multiresModifier_del_levels(mmd, scene, ob, 1);
|
|
|
|
iter_other(CTX_data_main(C), ob, true, multires_update_totlevels, &mmd->totlvl);
|
|
|
|
WM_event_add_notifier(C, NC_OBJECT | ND_MODIFIER, ob);
|
|
|
|
return OPERATOR_FINISHED;
|
|
}
|
|
|
|
static wmOperatorStatus multires_higher_levels_delete_invoke(bContext *C,
|
|
wmOperator *op,
|
|
const wmEvent * /*event*/)
|
|
{
|
|
if (edit_modifier_invoke_properties(C, op)) {
|
|
return multires_higher_levels_delete_exec(C, op);
|
|
}
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
|
|
void OBJECT_OT_multires_higher_levels_delete(wmOperatorType *ot)
|
|
{
|
|
ot->name = "Delete Higher Levels";
|
|
ot->description = "Deletes the higher resolution mesh, potential loss of detail";
|
|
ot->idname = "OBJECT_OT_multires_higher_levels_delete";
|
|
|
|
ot->poll = multires_poll;
|
|
ot->invoke = multires_higher_levels_delete_invoke;
|
|
ot->exec = multires_higher_levels_delete_exec;
|
|
|
|
/* flags */
|
|
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_INTERNAL;
|
|
edit_modifier_properties(ot);
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* ------------------------------------------------------------------- */
|
|
/** \name Multires Subdivide Operator
|
|
* \{ */
|
|
|
|
static EnumPropertyItem prop_multires_subdivide_mode_type[] = {
|
|
{int8_t(MultiresSubdivideModeType::CatmullClark),
|
|
"CATMULL_CLARK",
|
|
0,
|
|
"Catmull-Clark",
|
|
"Create a new level using Catmull-Clark subdivisions"},
|
|
{int8_t(MultiresSubdivideModeType::Simple),
|
|
"SIMPLE",
|
|
0,
|
|
"Simple",
|
|
"Create a new level using simple subdivisions"},
|
|
{int8_t(MultiresSubdivideModeType::Linear),
|
|
"LINEAR",
|
|
0,
|
|
"Linear",
|
|
"Create a new level using linear interpolation of the sculpted displacement"},
|
|
{0, nullptr, 0, nullptr, nullptr},
|
|
};
|
|
|
|
static wmOperatorStatus multires_subdivide_exec(bContext *C, wmOperator *op)
|
|
{
|
|
Object *object = context_active_object(C);
|
|
MultiresModifierData *mmd = (MultiresModifierData *)edit_modifier_property_get(
|
|
op, object, eModifierType_Multires);
|
|
|
|
if (!mmd) {
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
|
|
const MultiresSubdivideModeType subdivide_mode = (MultiresSubdivideModeType)RNA_enum_get(op->ptr,
|
|
"mode");
|
|
multiresModifier_subdivide(object, mmd, subdivide_mode);
|
|
|
|
iter_other(CTX_data_main(C), object, true, multires_update_totlevels, &mmd->totlvl);
|
|
|
|
DEG_id_tag_update(&object->id, ID_RECALC_GEOMETRY);
|
|
WM_event_add_notifier(C, NC_OBJECT | ND_MODIFIER, object);
|
|
|
|
if (object->mode & OB_MODE_SCULPT) {
|
|
/* ensure that grid paint mask layer is created */
|
|
BKE_sculpt_mask_layers_ensure(
|
|
CTX_data_ensure_evaluated_depsgraph(C), CTX_data_main(C), object, mmd);
|
|
}
|
|
|
|
return OPERATOR_FINISHED;
|
|
}
|
|
|
|
static wmOperatorStatus multires_subdivide_invoke(bContext *C,
|
|
wmOperator *op,
|
|
const wmEvent * /*event*/)
|
|
{
|
|
if (edit_modifier_invoke_properties(C, op)) {
|
|
return multires_subdivide_exec(C, op);
|
|
}
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
|
|
void OBJECT_OT_multires_subdivide(wmOperatorType *ot)
|
|
{
|
|
ot->name = "Multires Subdivide";
|
|
ot->description = "Add a new level of subdivision";
|
|
ot->idname = "OBJECT_OT_multires_subdivide";
|
|
|
|
ot->poll = multires_poll;
|
|
ot->invoke = multires_subdivide_invoke;
|
|
ot->exec = multires_subdivide_exec;
|
|
|
|
/* flags */
|
|
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_INTERNAL;
|
|
edit_modifier_properties(ot);
|
|
RNA_def_enum(ot->srna,
|
|
"mode",
|
|
prop_multires_subdivide_mode_type,
|
|
int8_t(MultiresSubdivideModeType::CatmullClark),
|
|
"Subdivision Mode",
|
|
"How the mesh is going to be subdivided to create a new level");
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* ------------------------------------------------------------------- */
|
|
/** \name Multires Reshape Operator
|
|
* \{ */
|
|
|
|
static wmOperatorStatus multires_reshape_exec(bContext *C, wmOperator *op)
|
|
{
|
|
Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
|
|
Object *ob = context_active_object(C), *secondob = nullptr;
|
|
MultiresModifierData *mmd = (MultiresModifierData *)edit_modifier_property_get(
|
|
op, ob, eModifierType_Multires);
|
|
|
|
if (!mmd) {
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
|
|
if (mmd->lvl == 0) {
|
|
BKE_report(op->reports, RPT_ERROR, "Reshape can work only with higher levels of subdivisions");
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
|
|
CTX_DATA_BEGIN (C, Object *, selob, selected_editable_objects) {
|
|
if (selob->type == OB_MESH && selob != ob) {
|
|
secondob = selob;
|
|
break;
|
|
}
|
|
}
|
|
CTX_DATA_END;
|
|
|
|
if (!secondob) {
|
|
BKE_report(op->reports, RPT_ERROR, "Second selected mesh object required to copy shape from");
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
|
|
if (!multiresModifier_reshapeFromObject(depsgraph, mmd, ob, secondob)) {
|
|
BKE_report(op->reports, RPT_ERROR, "Objects do not have the same number of vertices");
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
|
|
DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY);
|
|
WM_event_add_notifier(C, NC_OBJECT | ND_MODIFIER, ob);
|
|
|
|
return OPERATOR_FINISHED;
|
|
}
|
|
|
|
static wmOperatorStatus multires_reshape_invoke(bContext *C,
|
|
wmOperator *op,
|
|
const wmEvent * /*event*/)
|
|
{
|
|
if (edit_modifier_invoke_properties(C, op)) {
|
|
return multires_reshape_exec(C, op);
|
|
}
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
|
|
void OBJECT_OT_multires_reshape(wmOperatorType *ot)
|
|
{
|
|
ot->name = "Multires Reshape";
|
|
ot->description = "Copy vertex coordinates from other object";
|
|
ot->idname = "OBJECT_OT_multires_reshape";
|
|
|
|
ot->poll = multires_poll;
|
|
ot->invoke = multires_reshape_invoke;
|
|
ot->exec = multires_reshape_exec;
|
|
|
|
/* flags */
|
|
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_INTERNAL;
|
|
edit_modifier_properties(ot);
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* ------------------------------------------------------------------- */
|
|
/** \name Multires Save External Operator
|
|
* \{ */
|
|
|
|
static wmOperatorStatus multires_external_save_exec(bContext *C, wmOperator *op)
|
|
{
|
|
Main *bmain = CTX_data_main(C);
|
|
Object *ob = context_active_object(C);
|
|
Mesh *mesh = (ob) ? static_cast<Mesh *>(ob->data) : static_cast<Mesh *>(op->customdata);
|
|
char filepath[FILE_MAX];
|
|
const bool relative = RNA_boolean_get(op->ptr, "relative_path");
|
|
|
|
if (!mesh) {
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
|
|
if (CustomData_external_test(&mesh->corner_data, CD_MDISPS)) {
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
|
|
RNA_string_get(op->ptr, "filepath", filepath);
|
|
|
|
if (relative) {
|
|
BLI_path_rel(filepath, BKE_main_blendfile_path(bmain));
|
|
}
|
|
|
|
CustomData_external_add(&mesh->corner_data, &mesh->id, CD_MDISPS, mesh->corners_num, filepath);
|
|
CustomData_external_write(
|
|
&mesh->corner_data, &mesh->id, CD_MASK_MESH.lmask, mesh->corners_num, 0);
|
|
|
|
return OPERATOR_FINISHED;
|
|
}
|
|
|
|
static wmOperatorStatus multires_external_save_invoke(bContext *C,
|
|
wmOperator *op,
|
|
const wmEvent * /*event*/)
|
|
{
|
|
Object *ob = context_active_object(C);
|
|
Mesh *mesh = static_cast<Mesh *>(ob->data);
|
|
char filepath[FILE_MAX];
|
|
|
|
if (!edit_modifier_invoke_properties(C, op)) {
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
|
|
MultiresModifierData *mmd = (MultiresModifierData *)edit_modifier_property_get(
|
|
op, ob, eModifierType_Multires);
|
|
|
|
if (!mmd) {
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
|
|
if (CustomData_external_test(&mesh->corner_data, CD_MDISPS)) {
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
|
|
if (RNA_struct_property_is_set(op->ptr, "filepath")) {
|
|
return multires_external_save_exec(C, op);
|
|
}
|
|
|
|
op->customdata = mesh;
|
|
|
|
/* While a filename need not be UTF8, at this point the constructed name should be UTF8. */
|
|
SNPRINTF_UTF8(filepath, "//%s.btx", mesh->id.name + 2);
|
|
RNA_string_set(op->ptr, "filepath", filepath);
|
|
|
|
WM_event_add_fileselect(C, op);
|
|
|
|
return OPERATOR_RUNNING_MODAL;
|
|
}
|
|
|
|
void OBJECT_OT_multires_external_save(wmOperatorType *ot)
|
|
{
|
|
ot->name = "Multires Save External";
|
|
ot->description = "Save displacements to an external file";
|
|
ot->idname = "OBJECT_OT_multires_external_save";
|
|
|
|
/* XXX modifier no longer in context after file browser: `ot->poll = multires_poll;`. */
|
|
ot->exec = multires_external_save_exec;
|
|
ot->invoke = multires_external_save_invoke;
|
|
ot->poll = multires_poll;
|
|
|
|
/* flags */
|
|
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_INTERNAL;
|
|
|
|
WM_operator_properties_filesel(ot,
|
|
FILE_TYPE_FOLDER | FILE_TYPE_BTX,
|
|
FILE_SPECIAL,
|
|
FILE_SAVE,
|
|
WM_FILESEL_FILEPATH | WM_FILESEL_RELPATH,
|
|
FILE_DEFAULTDISPLAY,
|
|
FILE_SORT_DEFAULT);
|
|
edit_modifier_properties(ot);
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* ------------------------------------------------------------------- */
|
|
/** \name Multires Pack Operator
|
|
* \{ */
|
|
|
|
static wmOperatorStatus multires_external_pack_exec(bContext *C, wmOperator * /*op*/)
|
|
{
|
|
Object *ob = context_active_object(C);
|
|
Mesh *mesh = static_cast<Mesh *>(ob->data);
|
|
|
|
if (!CustomData_external_test(&mesh->corner_data, CD_MDISPS)) {
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
|
|
/* XXX don't remove. */
|
|
CustomData_external_remove(&mesh->corner_data, &mesh->id, CD_MDISPS, mesh->corners_num);
|
|
|
|
return OPERATOR_FINISHED;
|
|
}
|
|
|
|
void OBJECT_OT_multires_external_pack(wmOperatorType *ot)
|
|
{
|
|
ot->name = "Multires Pack External";
|
|
ot->description = "Pack displacements from an external file";
|
|
ot->idname = "OBJECT_OT_multires_external_pack";
|
|
|
|
ot->poll = multires_poll;
|
|
ot->exec = multires_external_pack_exec;
|
|
|
|
/* flags */
|
|
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* ------------------------------------------------------------------- */
|
|
/** \name Multires Apply Base
|
|
* \{ */
|
|
|
|
static wmOperatorStatus multires_base_apply_exec(bContext *C, wmOperator *op)
|
|
{
|
|
Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C);
|
|
Object *object = context_active_object(C);
|
|
MultiresModifierData *mmd = (MultiresModifierData *)edit_modifier_property_get(
|
|
op, object, eModifierType_Multires);
|
|
|
|
if (!mmd) {
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
|
|
const ApplyBaseMode mode = RNA_boolean_get(op->ptr, "apply_heuristic") ?
|
|
ApplyBaseMode::ForSubdivision :
|
|
ApplyBaseMode::Base;
|
|
|
|
ed::sculpt_paint::undo::push_multires_mesh_begin(C, op->type->name);
|
|
|
|
multiresModifier_base_apply(depsgraph, object, mmd, mode);
|
|
|
|
ed::sculpt_paint::undo::push_multires_mesh_end(C, op->type->name);
|
|
|
|
DEG_id_tag_update(&object->id, ID_RECALC_GEOMETRY);
|
|
WM_event_add_notifier(C, NC_OBJECT | ND_MODIFIER, object);
|
|
|
|
return OPERATOR_FINISHED;
|
|
}
|
|
|
|
static wmOperatorStatus multires_base_apply_invoke(bContext *C,
|
|
wmOperator *op,
|
|
const wmEvent * /*event*/)
|
|
{
|
|
if (edit_modifier_invoke_properties(C, op)) {
|
|
return multires_base_apply_exec(C, op);
|
|
}
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
|
|
void OBJECT_OT_multires_base_apply(wmOperatorType *ot)
|
|
{
|
|
ot->name = "Multires Apply Base";
|
|
ot->description = "Modify the base mesh to conform to the displaced mesh";
|
|
ot->idname = "OBJECT_OT_multires_base_apply";
|
|
|
|
ot->poll = multires_poll;
|
|
ot->invoke = multires_base_apply_invoke;
|
|
ot->exec = multires_base_apply_exec;
|
|
|
|
/* flags */
|
|
ot->flag = OPTYPE_REGISTER | OPTYPE_INTERNAL;
|
|
edit_modifier_properties(ot);
|
|
PropertyRNA *prop = RNA_def_boolean(
|
|
ot->srna,
|
|
"apply_heuristic",
|
|
true,
|
|
"Apply Subdivision Heuristic",
|
|
"Whether or not the final base mesh positions will be slightly altered to account for a new "
|
|
"subdivision modifier being added");
|
|
RNA_def_property_flag(prop, PROP_SKIP_SAVE | PROP_HIDDEN);
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* ------------------------------------------------------------------- */
|
|
/** \name Multires Unsubdivide
|
|
* \{ */
|
|
|
|
static wmOperatorStatus multires_unsubdivide_exec(bContext *C, wmOperator *op)
|
|
{
|
|
Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C);
|
|
Object *object = context_active_object(C);
|
|
MultiresModifierData *mmd = (MultiresModifierData *)edit_modifier_property_get(
|
|
op, object, eModifierType_Multires);
|
|
|
|
if (!mmd) {
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
|
|
int new_levels = multiresModifier_rebuild_subdiv(depsgraph, object, mmd, 1, true);
|
|
if (new_levels == 0) {
|
|
BKE_report(op->reports, RPT_ERROR, "No valid subdivisions found to rebuild a lower level");
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
|
|
DEG_id_tag_update(&object->id, ID_RECALC_GEOMETRY);
|
|
WM_event_add_notifier(C, NC_OBJECT | ND_MODIFIER, object);
|
|
|
|
return OPERATOR_FINISHED;
|
|
}
|
|
|
|
static wmOperatorStatus multires_unsubdivide_invoke(bContext *C,
|
|
wmOperator *op,
|
|
const wmEvent * /*event*/)
|
|
{
|
|
if (edit_modifier_invoke_properties(C, op)) {
|
|
return multires_unsubdivide_exec(C, op);
|
|
}
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
|
|
void OBJECT_OT_multires_unsubdivide(wmOperatorType *ot)
|
|
{
|
|
ot->name = "Unsubdivide";
|
|
ot->description = "Rebuild a lower subdivision level of the current base mesh";
|
|
ot->idname = "OBJECT_OT_multires_unsubdivide";
|
|
|
|
ot->poll = multires_poll;
|
|
ot->invoke = multires_unsubdivide_invoke;
|
|
ot->exec = multires_unsubdivide_exec;
|
|
|
|
/* flags */
|
|
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_INTERNAL;
|
|
edit_modifier_properties(ot);
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* ------------------------------------------------------------------- */
|
|
/** \name Multires Rebuild Subdivisions
|
|
* \{ */
|
|
|
|
static wmOperatorStatus multires_rebuild_subdiv_exec(bContext *C, wmOperator *op)
|
|
{
|
|
Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C);
|
|
Object *object = context_active_object(C);
|
|
MultiresModifierData *mmd = (MultiresModifierData *)edit_modifier_property_get(
|
|
op, object, eModifierType_Multires);
|
|
|
|
if (!mmd) {
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
|
|
int new_levels = multiresModifier_rebuild_subdiv(depsgraph, object, mmd, INT_MAX, false);
|
|
if (new_levels == 0) {
|
|
BKE_report(op->reports, RPT_ERROR, "No valid subdivisions found to rebuild lower levels");
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
|
|
BKE_reportf(op->reports, RPT_INFO, "%d new levels rebuilt", new_levels);
|
|
|
|
DEG_id_tag_update(&object->id, ID_RECALC_GEOMETRY);
|
|
WM_event_add_notifier(C, NC_OBJECT | ND_MODIFIER, object);
|
|
|
|
return OPERATOR_FINISHED;
|
|
}
|
|
|
|
static wmOperatorStatus multires_rebuild_subdiv_invoke(bContext *C,
|
|
wmOperator *op,
|
|
const wmEvent * /*event*/)
|
|
{
|
|
if (edit_modifier_invoke_properties(C, op)) {
|
|
return multires_rebuild_subdiv_exec(C, op);
|
|
}
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
|
|
void OBJECT_OT_multires_rebuild_subdiv(wmOperatorType *ot)
|
|
{
|
|
ot->name = "Rebuild Lower Subdivisions";
|
|
ot->description =
|
|
"Rebuilds all possible subdivisions levels to generate a lower resolution base mesh";
|
|
ot->idname = "OBJECT_OT_multires_rebuild_subdiv";
|
|
|
|
ot->poll = multires_poll;
|
|
ot->invoke = multires_rebuild_subdiv_invoke;
|
|
ot->exec = multires_rebuild_subdiv_exec;
|
|
|
|
/* flags */
|
|
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_INTERNAL;
|
|
edit_modifier_properties(ot);
|
|
}
|
|
|
|
/** \} */
|
|
|
|
} // namespace blender::ed::object
|