Paint: Migrate radial_symmetry from Scene to Mesh

This commit introduces the `radial_symmetry` property on the `Mesh`
datablock and simultaneously removes the `radial_symm` property in
Sculpt, Vertex Paint, and Weight Paint.

This allows users to have these symmetry values defined on a per-object
basis instead of needing to reconfigure it for each mesh.

Current values stored on the `ToolSettings` on a per-scene basis are not
copied to each mesh in a scene. This is done to avoid introducing
potentially inaccurate data to a large number of meshes at the cost of
some minor backwards incompatibility.

Part of #108107

Pull Request: https://projects.blender.org/blender/blender/pulls/141108
This commit is contained in:
Sean Kim
2025-06-30 23:58:05 +02:00
committed by Sean Kim
parent abe151c70c
commit d73b8dd4f3
12 changed files with 47 additions and 39 deletions

View File

@@ -95,6 +95,8 @@ class View3DPanel:
# Used by vertex & weight paint
def draw_vpaint_symmetry(layout, vpaint, obj):
mesh = obj.data
col = layout.column()
row = col.row(heading="Mirror", align=True)
row.prop(obj, "use_mesh_mirror_x", text="X", toggle=True)
@@ -103,7 +105,7 @@ def draw_vpaint_symmetry(layout, vpaint, obj):
col = layout.column()
col.active = not obj.data.use_mirror_vertex_groups
col.prop(vpaint, "radial_symmetry", text="Radial")
col.prop(mesh, "radial_symmetry", text="Radial")
# ********** default tools for object mode ****************
@@ -1147,7 +1149,7 @@ class VIEW3D_PT_sculpt_symmetry(Panel, View3DPaintPanel):
row.prop(sculpt, "tile_z", text="Z", toggle=True)
layout.prop(sculpt, "use_symmetry_feather", text="Feather")
layout.prop(sculpt, "radial_symmetry", text="Radial")
layout.prop(mesh, "radial_symmetry", text="Radial")
layout.prop(sculpt, "tile_offset", text="Tile Offset")
layout.separator()

View File

@@ -27,7 +27,7 @@
/* Blender file format version. */
#define BLENDER_FILE_VERSION BLENDER_VERSION
#define BLENDER_FILE_SUBVERSION 31
#define BLENDER_FILE_SUBVERSION 32
/* Minimum Blender version that supports reading file written with the current
* version. Older Blender versions will test this and cancel loading the file, showing a warning to

View File

@@ -1253,6 +1253,14 @@ void blo_do_versions_500(FileData * /*fd*/, Library * /*lib*/, Main *bmain)
}
}
if (!MAIN_VERSION_FILE_ATLEAST(bmain, 500, 32)) {
LISTBASE_FOREACH (Mesh *, mesh, &bmain->meshes) {
mesh->radial_symmetry[0] = 1;
mesh->radial_symmetry[1] = 1;
mesh->radial_symmetry[2] = 1;
}
}
/**
* Always bump subversion in BKE_blender_version.h when adding versioning
* code here, and wrap it inside a MAIN_VERSION_FILE_ATLEAST check.

View File

@@ -1150,6 +1150,7 @@ static void cursor_draw_point_with_symmetry(const uint gpuattr,
const Object &ob,
const float radius)
{
const Mesh *mesh = static_cast<const Mesh *>(ob.data);
const char symm = SCULPT_mesh_symmetry_xyz_get(ob);
float3 location;
float symm_rot_mat[4][4];
@@ -1166,8 +1167,8 @@ static void cursor_draw_point_with_symmetry(const uint gpuattr,
/* Radial Symmetry. */
for (char raxis = 0; raxis < 3; raxis++) {
for (int r = 1; r < sd.radial_symm[raxis]; r++) {
float angle = 2 * M_PI * r / sd.radial_symm[int(raxis)];
for (int r = 1; r < mesh->radial_symmetry[raxis]; r++) {
float angle = 2 * M_PI * r / mesh->radial_symmetry[int(raxis)];
location = symmetry_flip(true_location, ePaintSymmetryFlags(i));
unit_m4(symm_rot_mat);
rotate_m4(symm_rot_mat, raxis + 'X', angle);

View File

@@ -1977,8 +1977,8 @@ static void vpaint_do_radial_symmetry(bContext *C,
const ePaintSymmetryFlags symm,
const int axis)
{
for (int i = 1; i < vp.radial_symm[axis - 'X']; i++) {
const float angle = (2.0 * M_PI) * i / vp.radial_symm[axis - 'X'];
for (int i = 1; i < mesh.radial_symmetry[axis - 'X']; i++) {
const float angle = (2.0 * M_PI) * i / mesh.radial_symmetry[axis - 'X'];
vpaint_do_paint(C, vp, vpd, ob, mesh, brush, symm, axis, i, angle);
}
}

View File

@@ -1719,8 +1719,8 @@ static void wpaint_do_radial_symmetry(bContext *C,
const ePaintSymmetryFlags symm,
const int axis)
{
for (int i = 1; i < wp.radial_symm[axis - 'X']; i++) {
const float angle = (2.0 * M_PI) * i / wp.radial_symm[axis - 'X'];
for (int i = 1; i < mesh.radial_symmetry[axis - 'X']; i++) {
const float angle = (2.0 * M_PI) * i / mesh.radial_symmetry[axis - 'X'];
wpaint_do_paint(C, ob, wp, wpd, wpi, mesh, brush, symm, axis, i, angle);
}
}

View File

@@ -1212,15 +1212,15 @@ static float calc_overlap(const blender::ed::sculpt_paint::StrokeCache &cache,
return 0.0f;
}
static float calc_radial_symmetry_feather(const Sculpt &sd,
static float calc_radial_symmetry_feather(const Mesh &mesh,
const blender::ed::sculpt_paint::StrokeCache &cache,
const ePaintSymmetryFlags symm,
const char axis)
{
float overlap = 0.0f;
for (int i = 1; i < sd.radial_symm[axis - 'X']; i++) {
const float angle = 2.0f * M_PI * i / sd.radial_symm[axis - 'X'];
for (int i = 1; i < mesh.radial_symmetry[axis - 'X']; i++) {
const float angle = 2.0f * M_PI * i / mesh.radial_symmetry[axis - 'X'];
overlap += calc_overlap(cache, symm, axis, angle);
}
@@ -1228,6 +1228,7 @@ static float calc_radial_symmetry_feather(const Sculpt &sd,
}
static float calc_symmetry_feather(const Sculpt &sd,
const Mesh &mesh,
const blender::ed::sculpt_paint::StrokeCache &cache)
{
if (!(sd.paint.symmetry_flags & PAINT_SYMMETRY_FEATHER)) {
@@ -1244,9 +1245,9 @@ static float calc_symmetry_feather(const Sculpt &sd,
overlap += calc_overlap(cache, ePaintSymmetryFlags(i), 0, 0);
overlap += calc_radial_symmetry_feather(sd, cache, ePaintSymmetryFlags(i), 'X');
overlap += calc_radial_symmetry_feather(sd, cache, ePaintSymmetryFlags(i), 'Y');
overlap += calc_radial_symmetry_feather(sd, cache, ePaintSymmetryFlags(i), 'Z');
overlap += calc_radial_symmetry_feather(mesh, cache, ePaintSymmetryFlags(i), 'X');
overlap += calc_radial_symmetry_feather(mesh, cache, ePaintSymmetryFlags(i), 'Y');
overlap += calc_radial_symmetry_feather(mesh, cache, ePaintSymmetryFlags(i), 'Z');
}
return 1.0f / overlap;
}
@@ -3576,9 +3577,10 @@ static void do_radial_symmetry(const Depsgraph &depsgraph,
const float /*feather*/)
{
SculptSession &ss = *ob.sculpt;
const Mesh &mesh = *static_cast<Mesh *>(ob.data);
for (int i = 1; i < sd.radial_symm[axis - 'X']; i++) {
const float angle = 2.0f * M_PI * i / sd.radial_symm[axis - 'X'];
for (int i = 1; i < mesh.radial_symmetry[axis - 'X']; i++) {
const float angle = 2.0f * M_PI * i / mesh.radial_symmetry[axis - 'X'];
ss.cache->radial_symmetry_pass = i;
SCULPT_cache_calc_brushdata_symm(*ss.cache, symm, axis, angle);
do_tiled(depsgraph, scene, sd, ob, brush, ups, paint_mode_settings, action);
@@ -3609,11 +3611,12 @@ static void do_symmetrical_brush_actions(const Depsgraph &depsgraph,
PaintModeSettings &paint_mode_settings)
{
const Brush &brush = *BKE_paint_brush_for_read(&sd.paint);
const Mesh &mesh = *static_cast<Mesh *>(ob.data);
SculptSession &ss = *ob.sculpt;
StrokeCache &cache = *ss.cache;
const char symm = SCULPT_mesh_symmetry_xyz_get(ob);
float feather = calc_symmetry_feather(sd, *ss.cache);
float feather = calc_symmetry_feather(sd, mesh, *ss.cache);
cache.bstrength = brush_strength(sd, cache, feather, ups, paint_mode_settings);
cache.symmetry = symm;

View File

@@ -23,7 +23,8 @@
.face_sets_color_seed = 0, \
.face_sets_color_default = 1, \
.flag = ME_REMESH_REPROJECT_VOLUME | ME_REMESH_REPROJECT_ATTRIBUTES, \
.editflag = ME_EDIT_MIRROR_VERTEX_GROUPS \
.editflag = ME_EDIT_MIRROR_VERTEX_GROUPS, \
.radial_symmetry = {1, 1, 1} \
}
/** \} */

View File

@@ -241,7 +241,8 @@ typedef struct Mesh {
/* Deprecated size of #fdata. */
int totface_legacy;
char _pad1[4];
char _pad1;
int8_t radial_symmetry[3];
/**
* Data that isn't saved in files, including caches of derived data, temporary data to improve

View File

@@ -1340,7 +1340,7 @@ typedef struct Sculpt {
// /* Control tablet input. */
// char tablet_size, tablet_strength; XXX not used?
int radial_symm[3];
int radial_symm[3] DNA_DEPRECATED;
/** Maximum edge length for dynamic topology sculpting (in pixels). */
float detail_size;
@@ -1428,7 +1428,7 @@ typedef struct VPaint {
char flag;
char _pad[3];
/** For mirrored painting. */
int radial_symm[3];
int radial_symm[3] DNA_DEPRECATED;
} VPaint;
/** #VPaint::flag */

View File

@@ -3302,6 +3302,15 @@ static void rna_def_mesh(BlenderRNA *brna)
"Mirror the left/right vertex groups when painting. The symmetry axis "
"is determined by the symmetry settings.");
RNA_def_property_update(prop, 0, "rna_Mesh_update_draw");
prop = RNA_def_property(srna, "radial_symmetry", PROP_INT, PROP_XYZ);
RNA_def_property_int_sdna(prop, nullptr, "radial_symmetry");
RNA_def_property_clear_flag(prop, PROP_ANIMATABLE);
RNA_def_property_int_default(prop, 1);
RNA_def_property_range(prop, 1, 64);
RNA_def_property_ui_range(prop, 1, 32, 1, 1);
RNA_def_property_ui_text(
prop, "Radial Symmetry Count", "Number of mirrored regions around a central axis");
/* End Symmetry */
RNA_define_verify_sdna(false);

View File

@@ -1006,14 +1006,6 @@ static void rna_def_sculpt(BlenderRNA *brna)
RNA_def_struct_ui_text(srna, "Sculpt", "");
RNA_def_struct_clear_flag(srna, STRUCT_UNDO);
prop = RNA_def_property(srna, "radial_symmetry", PROP_INT, PROP_XYZ);
RNA_def_property_int_sdna(prop, nullptr, "radial_symm");
RNA_def_property_int_default(prop, 1);
RNA_def_property_range(prop, 1, 64);
RNA_def_property_ui_range(prop, 0, 32, 1, 1);
RNA_def_property_ui_text(
prop, "Radial Symmetry Count X Axis", "Number of times to copy strokes across the surface");
prop = RNA_def_property(srna, "lock_x", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, nullptr, "flags", SCULPT_LOCK_X);
RNA_def_property_ui_text(prop, "Lock X", "Disallow changes to the X axis of vertices");
@@ -1312,15 +1304,6 @@ static void rna_def_vertex_paint(BlenderRNA *brna)
RNA_def_property_boolean_sdna(prop, nullptr, "flag", VP_FLAG_VGROUP_RESTRICT);
RNA_def_property_ui_text(prop, "Restrict", "Restrict painting to vertices in the group");
RNA_def_property_update(prop, NC_SCENE | ND_TOOLSETTINGS, nullptr);
/* Mirroring */
prop = RNA_def_property(srna, "radial_symmetry", PROP_INT, PROP_XYZ);
RNA_def_property_int_sdna(prop, nullptr, "radial_symm");
RNA_def_property_int_default(prop, 1);
RNA_def_property_range(prop, 1, 64);
RNA_def_property_ui_range(prop, 1, 32, 1, 1);
RNA_def_property_ui_text(
prop, "Radial Symmetry Count X Axis", "Number of times to copy strokes across the surface");
}
static void rna_def_paint_mode(BlenderRNA *brna)