From ced21c67394d8b0b50b4746b8a33122f7247b138 Mon Sep 17 00:00:00 2001 From: Christoph Lendenfeld Date: Tue, 20 Feb 2024 13:28:47 +0100 Subject: [PATCH 1/8] the fix --- .../blender/blenkernel/intern/constraint.cc | 68 +++++++++++++------ .../blender/makesrna/intern/rna_constraint.cc | 12 ++-- 2 files changed, 53 insertions(+), 27 deletions(-) diff --git a/source/blender/blenkernel/intern/constraint.cc b/source/blender/blenkernel/intern/constraint.cc index 27669a8da92..68981870044 100644 --- a/source/blender/blenkernel/intern/constraint.cc +++ b/source/blender/blenkernel/intern/constraint.cc @@ -1658,6 +1658,50 @@ static bConstraintTypeInfo CTI_LOCLIMIT = { /* -------- Limit Rotation --------- */ +/** Clamps an angle between min and max. The values are expected to be in radians. In case `max` is + * smaller than `min` the whole clockwise range on the unit circle from `min` to `max` is + * considered a valid range.*/ +static float clamp_angle(const float angle, const float min, const float max) +{ + if (min == max) { + return min; + } + + float angle_unit_circle[2]; + angle_unit_circle[0] = cos(angle); + angle_unit_circle[1] = sin(angle); + + float min_unit_circle[2]; + min_unit_circle[0] = cos(min); + min_unit_circle[1] = sin(min); + + float max_unit_circle[2]; + max_unit_circle[0] = cos(max); + max_unit_circle[1] = sin(max); + + float max_to_angle[2]; + sub_v2_v2v2(max_to_angle, angle_unit_circle, max_unit_circle); + + float max_to_min[2]; + sub_v2_v2v2(max_to_min, min_unit_circle, max_unit_circle); + + /* By calculating the cross between those two we can determine if the angle_unit_circle point is + * left or right to the max_to_min vector, which tells us if it is in the legal range or not.*/ + const float cross = cross_v2v2(max_to_angle, max_to_min); + if (cross < 0) { + return angle; + } + + const float dot_min = dot_v2v2(angle_unit_circle, min_unit_circle); + const float dot_max = dot_v2v2(angle_unit_circle, max_unit_circle); + + /* Clamps the value to whichever constraint is closer. */ + if (dot_min > dot_max) { + return min; + } + return max; +} + static void rotlimit_evaluate(bConstraint *con, bConstraintOb *cob, ListBase * /*targets*/) { bRotLimitConstraint *data = static_cast(con->data); @@ -1692,31 +1736,13 @@ static void rotlimit_evaluate(bConstraint *con, bConstraintOb *cob, ListBase * / /* limiting of euler values... */ if (data->flag & LIMIT_XROT) { - if (eul[0] < data->xmin) { - eul[0] = data->xmin; - } - - if (eul[0] > data->xmax) { - eul[0] = data->xmax; - } + eul[0] = clamp_angle(eul[0], data->xmin, data->xmax); } if (data->flag & LIMIT_YROT) { - if (eul[1] < data->ymin) { - eul[1] = data->ymin; - } - - if (eul[1] > data->ymax) { - eul[1] = data->ymax; - } + eul[1] = clamp_angle(eul[1], data->ymin, data->ymax); } if (data->flag & LIMIT_ZROT) { - if (eul[2] < data->zmin) { - eul[2] = data->zmin; - } - - if (eul[2] > data->zmax) { - eul[2] = data->zmax; - } + eul[2] = clamp_angle(eul[2], data->zmin, data->zmax); } loc_eulO_size_to_mat4(cob->matrix, loc, eul, size, rot_order); diff --git a/source/blender/makesrna/intern/rna_constraint.cc b/source/blender/makesrna/intern/rna_constraint.cc index d729cc81f96..ca3462acca5 100644 --- a/source/blender/makesrna/intern/rna_constraint.cc +++ b/source/blender/makesrna/intern/rna_constraint.cc @@ -2638,37 +2638,37 @@ static void rna_def_constraint_rotation_limit(BlenderRNA *brna) prop = RNA_def_property(srna, "min_x", PROP_FLOAT, PROP_ANGLE); RNA_def_property_float_sdna(prop, nullptr, "xmin"); RNA_def_property_range(prop, -1000.0, 1000.0f); - RNA_def_property_ui_text(prop, "Minimum X", "Lowest X value to allow"); + RNA_def_property_ui_text(prop, "Minimum X", "Lower X angle bound"); RNA_def_property_update(prop, NC_OBJECT | ND_CONSTRAINT, "rna_Constraint_update"); prop = RNA_def_property(srna, "min_y", PROP_FLOAT, PROP_ANGLE); RNA_def_property_float_sdna(prop, nullptr, "ymin"); RNA_def_property_range(prop, -1000.0, 1000.0f); - RNA_def_property_ui_text(prop, "Minimum Y", "Lowest Y value to allow"); + RNA_def_property_ui_text(prop, "Minimum Y", "Lower Y angle bound"); RNA_def_property_update(prop, NC_OBJECT | ND_CONSTRAINT, "rna_Constraint_update"); prop = RNA_def_property(srna, "min_z", PROP_FLOAT, PROP_ANGLE); RNA_def_property_float_sdna(prop, nullptr, "zmin"); RNA_def_property_range(prop, -1000.0, 1000.0f); - RNA_def_property_ui_text(prop, "Minimum Z", "Lowest Z value to allow"); + RNA_def_property_ui_text(prop, "Minimum Z", "Lower Z angle bound"); RNA_def_property_update(prop, NC_OBJECT | ND_CONSTRAINT, "rna_Constraint_update"); prop = RNA_def_property(srna, "max_x", PROP_FLOAT, PROP_ANGLE); RNA_def_property_float_sdna(prop, nullptr, "xmax"); RNA_def_property_range(prop, -1000.0, 1000.0f); - RNA_def_property_ui_text(prop, "Maximum X", "Highest X value to allow"); + RNA_def_property_ui_text(prop, "Maximum X", "Higher X angle bound"); RNA_def_property_update(prop, NC_OBJECT | ND_CONSTRAINT, "rna_Constraint_update"); prop = RNA_def_property(srna, "max_y", PROP_FLOAT, PROP_ANGLE); RNA_def_property_float_sdna(prop, nullptr, "ymax"); RNA_def_property_range(prop, -1000.0, 1000.0f); - RNA_def_property_ui_text(prop, "Maximum Y", "Highest Y value to allow"); + RNA_def_property_ui_text(prop, "Maximum Y", "Higher Y angle bound"); RNA_def_property_update(prop, NC_OBJECT | ND_CONSTRAINT, "rna_Constraint_update"); prop = RNA_def_property(srna, "max_z", PROP_FLOAT, PROP_ANGLE); RNA_def_property_float_sdna(prop, nullptr, "zmax"); RNA_def_property_range(prop, -1000.0, 1000.0f); - RNA_def_property_ui_text(prop, "Maximum Z", "Highest Z value to allow"); + RNA_def_property_ui_text(prop, "Maximum Z", "Higher Z angle bound"); RNA_def_property_update(prop, NC_OBJECT | ND_CONSTRAINT, "rna_Constraint_update"); prop = RNA_def_property(srna, "euler_order", PROP_ENUM, PROP_NONE); -- 2.30.2 From 548d7a90f22d9e2ee5dfb5b2be5d13c016b6e56e Mon Sep 17 00:00:00 2001 From: Christoph Lendenfeld Date: Thu, 22 Feb 2024 14:13:50 +0100 Subject: [PATCH 2/8] don't constrain if max<=min or range>2pi --- source/blender/blenkernel/intern/constraint.cc | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/source/blender/blenkernel/intern/constraint.cc b/source/blender/blenkernel/intern/constraint.cc index 68981870044..8329dc0243f 100644 --- a/source/blender/blenkernel/intern/constraint.cc +++ b/source/blender/blenkernel/intern/constraint.cc @@ -1663,8 +1663,12 @@ static bConstraintTypeInfo CTI_LOCLIMIT = { * considered a valid range.*/ static float clamp_angle(const float angle, const float min, const float max) { - if (min == max) { - return min; + if ((max - min) >= (2 * M_PI)) { + return angle; + } + + if (max <= min) { + return angle; } float angle_unit_circle[2]; -- 2.30.2 From 8b7e07405859c893d0f0c789d63ceafc99b6a123 Mon Sep 17 00:00:00 2001 From: Christoph Lendenfeld Date: Fri, 23 Feb 2024 12:26:15 +0100 Subject: [PATCH 3/8] clamp to min --- source/blender/blenkernel/intern/constraint.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/blender/blenkernel/intern/constraint.cc b/source/blender/blenkernel/intern/constraint.cc index 8329dc0243f..da5302374a0 100644 --- a/source/blender/blenkernel/intern/constraint.cc +++ b/source/blender/blenkernel/intern/constraint.cc @@ -1668,7 +1668,7 @@ static float clamp_angle(const float angle, const float min, const float max) } if (max <= min) { - return angle; + return min; } float angle_unit_circle[2]; -- 2.30.2 From ecbadc0e22f11e1be9609fc9bb7ffcbe1eaf07fb Mon Sep 17 00:00:00 2001 From: Christoph Lendenfeld Date: Tue, 27 Feb 2024 16:48:50 +0100 Subject: [PATCH 4/8] use nathans code --- .../blender/blenkernel/intern/constraint.cc | 69 ++++++++++--------- 1 file changed, 37 insertions(+), 32 deletions(-) diff --git a/source/blender/blenkernel/intern/constraint.cc b/source/blender/blenkernel/intern/constraint.cc index da5302374a0..98be2de590b 100644 --- a/source/blender/blenkernel/intern/constraint.cc +++ b/source/blender/blenkernel/intern/constraint.cc @@ -25,6 +25,7 @@ #include "BLI_math_rotation.h" #include "BLI_math_vector.h" #include "BLI_string_utils.hh" +#include "BLI_timeit.hh" #include "BLI_utildefines.h" #include "BLT_translation.h" @@ -1658,9 +1659,22 @@ static bConstraintTypeInfo CTI_LOCLIMIT = { /* -------- Limit Rotation --------- */ -/** Clamps an angle between min and max. The values are expected to be in radians. In case `max` is - * smaller than `min` the whole clockwise range on the unit circle from `min` to `max` is - * considered a valid range.*/ +/** + * Wraps a number to be in [-PI, +PI]. + * + * Can also be implemented as `std::remainder(a, M_PI * 2.0)`. + * Not sure which is faster. + */ +static inline float pi_wrap(const float a) +{ + const float b = a * (0.5 / M_PI) + 0.5; + const float c = b - std::floor(b); + return (c - 0.5) * (2.0 * M_PI); +} + +/** + * Clamps an angle between min and max. ... + */ static float clamp_angle(const float angle, const float min, const float max) { if ((max - min) >= (2 * M_PI)) { @@ -1671,43 +1685,34 @@ static float clamp_angle(const float angle, const float min, const float max) return min; } - float angle_unit_circle[2]; - angle_unit_circle[0] = cos(angle); - angle_unit_circle[1] = sin(angle); + /* For the code below, see: https://www.desmos.com/calculator/awzjzund1u */ - float min_unit_circle[2]; - min_unit_circle[0] = cos(min); - min_unit_circle[1] = sin(min); + /* Move min and max into a space where `angle == 0.0`, and wrap them to + * [-PI, +PI] in that space. */ + const float tmin = pi_wrap(min - angle); + const float tmax = pi_wrap(max - angle); - float max_unit_circle[2]; - max_unit_circle[0] = cos(max); - max_unit_circle[1] = sin(max); - - float max_to_angle[2]; - sub_v2_v2v2(max_to_angle, angle_unit_circle, max_unit_circle); - - float max_to_min[2]; - sub_v2_v2v2(max_to_min, min_unit_circle, max_unit_circle); - - /* By calculating the cross between those two we can determine if the angle_unit_circle point is - * left or right to the max_to_min vector, which tells us if it is in the legal range or not.*/ - const float cross = cross_v2v2(max_to_angle, max_to_min); - if (cross < 0) { - return angle; + /* Some shenanigans to handle the various special cases of + * clamping in a cyclic space. */ + float tclamped = 0.0; + if (tmin < tmax) { + tclamped = std::min(std::max(0.0f, tmin), tmax); + } + else if (tmax < 0.0 && 0.0 < tmin) { + if (std::fabs(tmax) < std::fabs(tmin)) { + tclamped = tmax; + } + else { + tclamped = tmin; + } } - const float dot_min = dot_v2v2(angle_unit_circle, min_unit_circle); - const float dot_max = dot_v2v2(angle_unit_circle, max_unit_circle); - - /* Clamps the value to whichever constraint is closer. */ - if (dot_min > dot_max) { - return min; - } - return max; + return tclamped + angle; } static void rotlimit_evaluate(bConstraint *con, bConstraintOb *cob, ListBase * /*targets*/) { + SCOPED_TIMER_AVERAGED("constraint"); bRotLimitConstraint *data = static_cast(con->data); float loc[3]; float eul[3]; -- 2.30.2 From 976beda1ee316b7f693ea94767804c7b1cc85d57 Mon Sep 17 00:00:00 2001 From: Christoph Lendenfeld Date: Thu, 29 Feb 2024 12:13:22 +0100 Subject: [PATCH 5/8] comments and code simplification --- .../blender/blenkernel/intern/constraint.cc | 61 +++++++++++-------- 1 file changed, 34 insertions(+), 27 deletions(-) diff --git a/source/blender/blenkernel/intern/constraint.cc b/source/blender/blenkernel/intern/constraint.cc index 98be2de590b..731906ebd58 100644 --- a/source/blender/blenkernel/intern/constraint.cc +++ b/source/blender/blenkernel/intern/constraint.cc @@ -1661,53 +1661,60 @@ static bConstraintTypeInfo CTI_LOCLIMIT = { /** * Wraps a number to be in [-PI, +PI]. - * - * Can also be implemented as `std::remainder(a, M_PI * 2.0)`. - * Not sure which is faster. */ -static inline float pi_wrap(const float a) +static inline float wrap_rad_angle(const float angle) { - const float b = a * (0.5 / M_PI) + 0.5; - const float c = b - std::floor(b); - return (c - 0.5) * (2.0 * M_PI); + const float b = angle * (0.5 / M_PI) + 0.5; + return ((b - std::floor(b)) - 0.5) * (2.0 * M_PI); } /** - * Clamps an angle between min and max. ... + * Clamps an angle between min and max. + * + * All angles are in radians. + * + * This function treats angles as existing in a looping (cyclic) space, and is therefore + * specifically not equivalent to a simple `clamp(angle, min, max)`. `min` and `max` are treated as + * a directed range on the unit circle and `angle` is treated as a point on the unit circle. + * `angle` is then clamped to be within the directed range defined by `min` and `max`. */ static float clamp_angle(const float angle, const float min, const float max) { + /* If the allowed range exceeds 360 degrees no clamping can occur. */ if ((max - min) >= (2 * M_PI)) { return angle; } + /* Invalid case, just return min. */ if (max <= min) { return min; } - /* For the code below, see: https://www.desmos.com/calculator/awzjzund1u */ - /* Move min and max into a space where `angle == 0.0`, and wrap them to - * [-PI, +PI] in that space. */ - const float tmin = pi_wrap(min - angle); - const float tmax = pi_wrap(max - angle); + * [-PI, +PI] in that space. This simplifies the cases below, as we can + * just use 0.0 in place of `angle` and know that everything is in + * [-PI, +PI]. */ + const float min_wrapped = wrap_rad_angle(min - angle); + const float max_wrapped = wrap_rad_angle(max - angle); - /* Some shenanigans to handle the various special cases of - * clamping in a cyclic space. */ - float tclamped = 0.0; - if (tmin < tmax) { - tclamped = std::min(std::max(0.0f, tmin), tmax); - } - else if (tmax < 0.0 && 0.0 < tmin) { - if (std::fabs(tmax) < std::fabs(tmin)) { - tclamped = tmax; - } - else { - tclamped = tmin; - } + /* If the range defined by `min`/`max` doesn't contain the boundary at + * PI/-PI. This is the simple case, because it means we can do a simple + * clamp. */ + if (min_wrapped < max_wrapped) { + return angle + std::clamp(0.0f, min_wrapped, max_wrapped); } - return tclamped + angle; + /* At this point we know that `min_wrapped` >= `max_wrapped`, meaning the boundary is crossed. + * With that we know that no clamping is needed in the following case. */ + if (max_wrapped >= 0.0 || min_wrapped <= 0.0) { + return angle; + } + + /* If zero is outside of the range, we clamp to the closest of `min_wrapped` or `max_wrapped`. */ + if (std::fabs(max_wrapped) < std::fabs(min_wrapped)) { + return angle + max_wrapped; + } + return angle + min_wrapped; } static void rotlimit_evaluate(bConstraint *con, bConstraintOb *cob, ListBase * /*targets*/) -- 2.30.2 From 4ced0e146be3c4eb2ad1ec50537993314e1d26f6 Mon Sep 17 00:00:00 2001 From: Christoph Lendenfeld Date: Fri, 1 Mar 2024 12:22:24 +0100 Subject: [PATCH 6/8] remove timing code --- source/blender/blenkernel/intern/constraint.cc | 2 -- 1 file changed, 2 deletions(-) diff --git a/source/blender/blenkernel/intern/constraint.cc b/source/blender/blenkernel/intern/constraint.cc index 0baa77db8c4..17c3b26d142 100644 --- a/source/blender/blenkernel/intern/constraint.cc +++ b/source/blender/blenkernel/intern/constraint.cc @@ -25,7 +25,6 @@ #include "BLI_math_rotation.h" #include "BLI_math_vector.h" #include "BLI_string_utils.hh" -#include "BLI_timeit.hh" #include "BLI_utildefines.h" #include "BLT_translation.hh" @@ -1720,7 +1719,6 @@ static float clamp_angle(const float angle, const float min, const float max) static void rotlimit_evaluate(bConstraint *con, bConstraintOb *cob, ListBase * /*targets*/) { - SCOPED_TIMER_AVERAGED("constraint"); bRotLimitConstraint *data = static_cast(con->data); float loc[3]; float eul[3]; -- 2.30.2 From 9e63b195c528e301cc623a73fe18478f89321b01 Mon Sep 17 00:00:00 2001 From: Christoph Lendenfeld Date: Fri, 1 Mar 2024 12:37:32 +0100 Subject: [PATCH 7/8] set soft limits --- source/blender/makesrna/intern/rna_constraint.cc | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/source/blender/makesrna/intern/rna_constraint.cc b/source/blender/makesrna/intern/rna_constraint.cc index cdb7dc27992..f05db486871 100644 --- a/source/blender/makesrna/intern/rna_constraint.cc +++ b/source/blender/makesrna/intern/rna_constraint.cc @@ -2638,36 +2638,42 @@ static void rna_def_constraint_rotation_limit(BlenderRNA *brna) prop = RNA_def_property(srna, "min_x", PROP_FLOAT, PROP_ANGLE); RNA_def_property_float_sdna(prop, nullptr, "xmin"); RNA_def_property_range(prop, -1000.0, 1000.0f); + RNA_def_property_ui_range(prop, -2 * M_PI, 2 * M_PI, 10.0f, 1.0f); RNA_def_property_ui_text(prop, "Minimum X", "Lower X angle bound"); RNA_def_property_update(prop, NC_OBJECT | ND_CONSTRAINT, "rna_Constraint_update"); prop = RNA_def_property(srna, "min_y", PROP_FLOAT, PROP_ANGLE); RNA_def_property_float_sdna(prop, nullptr, "ymin"); RNA_def_property_range(prop, -1000.0, 1000.0f); + RNA_def_property_ui_range(prop, -2 * M_PI, 2 * M_PI, 10.0f, 1.0f); RNA_def_property_ui_text(prop, "Minimum Y", "Lower Y angle bound"); RNA_def_property_update(prop, NC_OBJECT | ND_CONSTRAINT, "rna_Constraint_update"); prop = RNA_def_property(srna, "min_z", PROP_FLOAT, PROP_ANGLE); RNA_def_property_float_sdna(prop, nullptr, "zmin"); RNA_def_property_range(prop, -1000.0, 1000.0f); + RNA_def_property_ui_range(prop, -2 * M_PI, 2 * M_PI, 10.0f, 1.0f); RNA_def_property_ui_text(prop, "Minimum Z", "Lower Z angle bound"); RNA_def_property_update(prop, NC_OBJECT | ND_CONSTRAINT, "rna_Constraint_update"); prop = RNA_def_property(srna, "max_x", PROP_FLOAT, PROP_ANGLE); RNA_def_property_float_sdna(prop, nullptr, "xmax"); RNA_def_property_range(prop, -1000.0, 1000.0f); + RNA_def_property_ui_range(prop, -2 * M_PI, 2 * M_PI, 10.0f, 1.0f); RNA_def_property_ui_text(prop, "Maximum X", "Higher X angle bound"); RNA_def_property_update(prop, NC_OBJECT | ND_CONSTRAINT, "rna_Constraint_update"); prop = RNA_def_property(srna, "max_y", PROP_FLOAT, PROP_ANGLE); RNA_def_property_float_sdna(prop, nullptr, "ymax"); RNA_def_property_range(prop, -1000.0, 1000.0f); + RNA_def_property_ui_range(prop, -2 * M_PI, 2 * M_PI, 10.0f, 1.0f); RNA_def_property_ui_text(prop, "Maximum Y", "Higher Y angle bound"); RNA_def_property_update(prop, NC_OBJECT | ND_CONSTRAINT, "rna_Constraint_update"); prop = RNA_def_property(srna, "max_z", PROP_FLOAT, PROP_ANGLE); RNA_def_property_float_sdna(prop, nullptr, "zmax"); RNA_def_property_range(prop, -1000.0, 1000.0f); + RNA_def_property_ui_range(prop, -2 * M_PI, 2 * M_PI, 10.0f, 1.0f); RNA_def_property_ui_text(prop, "Maximum Z", "Higher Z angle bound"); RNA_def_property_update(prop, NC_OBJECT | ND_CONSTRAINT, "rna_Constraint_update"); -- 2.30.2 From 3ce86973f8842802399e373cb85bf389b8089efb Mon Sep 17 00:00:00 2001 From: Christoph Lendenfeld Date: Thu, 4 Apr 2024 15:33:23 +0200 Subject: [PATCH 8/8] rename Higher to Upper --- source/blender/makesrna/intern/rna_constraint.cc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/source/blender/makesrna/intern/rna_constraint.cc b/source/blender/makesrna/intern/rna_constraint.cc index 7c924aa2ee8..18b54ef216a 100644 --- a/source/blender/makesrna/intern/rna_constraint.cc +++ b/source/blender/makesrna/intern/rna_constraint.cc @@ -2661,21 +2661,21 @@ static void rna_def_constraint_rotation_limit(BlenderRNA *brna) RNA_def_property_float_sdna(prop, nullptr, "xmax"); RNA_def_property_range(prop, -1000.0, 1000.0f); RNA_def_property_ui_range(prop, -2 * M_PI, 2 * M_PI, 10.0f, 1.0f); - RNA_def_property_ui_text(prop, "Maximum X", "Higher X angle bound"); + RNA_def_property_ui_text(prop, "Maximum X", "Upper X angle bound"); RNA_def_property_update(prop, NC_OBJECT | ND_CONSTRAINT, "rna_Constraint_update"); prop = RNA_def_property(srna, "max_y", PROP_FLOAT, PROP_ANGLE); RNA_def_property_float_sdna(prop, nullptr, "ymax"); RNA_def_property_range(prop, -1000.0, 1000.0f); RNA_def_property_ui_range(prop, -2 * M_PI, 2 * M_PI, 10.0f, 1.0f); - RNA_def_property_ui_text(prop, "Maximum Y", "Higher Y angle bound"); + RNA_def_property_ui_text(prop, "Maximum Y", "Upper Y angle bound"); RNA_def_property_update(prop, NC_OBJECT | ND_CONSTRAINT, "rna_Constraint_update"); prop = RNA_def_property(srna, "max_z", PROP_FLOAT, PROP_ANGLE); RNA_def_property_float_sdna(prop, nullptr, "zmax"); RNA_def_property_range(prop, -1000.0, 1000.0f); RNA_def_property_ui_range(prop, -2 * M_PI, 2 * M_PI, 10.0f, 1.0f); - RNA_def_property_ui_text(prop, "Maximum Z", "Higher Z angle bound"); + RNA_def_property_ui_text(prop, "Maximum Z", "Upper Z angle bound"); RNA_def_property_update(prop, NC_OBJECT | ND_CONSTRAINT, "rna_Constraint_update"); prop = RNA_def_property(srna, "euler_order", PROP_ENUM, PROP_NONE); -- 2.30.2