UI: Add temperature units

Based on the original patch by Vaishnav S (@padthai), this adds
support for temperature units. Initially supported units are Celsius,
Kelvin, and Fahrenheit.

The units aren't used anywhere with this commit. Those changes should
happen in separate patches by adding PROP_TEMPERATURE to RNA property
definitions. But it should be ensured that the various solvers and
simulations actually properly use real units.

The complexity of some of the changes comes from the fact that these
units have offsets from each other as well as coefficients. This also
makes the implementation in the current unit system troublesome.
For example, entering 0C evaluates correctly to 273K, but 0C + 0C
doubles that result, because each unit value is evaluated separately.
This is quite hard to solve in the general case with Blender's current
unit system, though, so it is not handled in this commit.

Differential Revision: https://developer.blender.org/D4401
This commit is contained in:
Hans Goudey 2020-09-07 14:59:07 -05:00
parent d4cca7b7b0
commit 36aeb0ec1e
11 changed files with 131 additions and 14 deletions

View File

@ -92,6 +92,7 @@ class SCENE_PT_unit(SceneButtonsPanel, Panel):
subcol.prop(unit, "length_unit", text="Length")
subcol.prop(unit, "mass_unit", text="Mass")
subcol.prop(unit, "time_unit", text="Time")
subcol.prop(unit, "temperature_unit", text="Temperature")
class SceneKeyingSetsPanel:

View File

@ -46,8 +46,8 @@ bool bUnit_ReplaceString(
/* return true if the string contains any valid unit for the given type */
bool bUnit_ContainsUnit(const char *str, int type);
/* if user does not specify a unit, multiply with this value */
double bUnit_PreferredInputUnitScalar(const struct UnitSettings *settings, int type);
/* If user does not specify a unit, this converts it to the unit from the settings. */
double bUnit_ApplyPreferredUnit(const struct UnitSettings *settings, int type, double value);
/* make string keyboard-friendly: 10µm --> 10um */
void bUnit_ToUnitAltName(char *str, int len_max, const char *orig_str, int system, int type);
@ -86,7 +86,8 @@ enum {
B_UNIT_ACCELERATION = 8,
B_UNIT_CAMERA = 9,
B_UNIT_POWER = 10,
B_UNIT_TYPE_TOT = 11,
B_UNIT_TEMPERATURE = 11,
B_UNIT_TYPE_TOT = 12,
};
#ifdef __cplusplus

View File

@ -157,6 +157,8 @@ static void scene_init_data(ID *id)
scene->unit.length_unit = (uchar)bUnit_GetBaseUnitOfType(USER_UNIT_METRIC, B_UNIT_LENGTH);
scene->unit.mass_unit = (uchar)bUnit_GetBaseUnitOfType(USER_UNIT_METRIC, B_UNIT_MASS);
scene->unit.time_unit = (uchar)bUnit_GetBaseUnitOfType(USER_UNIT_METRIC, B_UNIT_TIME);
scene->unit.temperature_unit = (uchar)bUnit_GetBaseUnitOfType(USER_UNIT_METRIC,
B_UNIT_TEMPERATURE);
/* Anti-Aliasing threshold. */
scene->grease_pencil_settings.smaa_threshold = 1.0f;

View File

@ -80,6 +80,8 @@
#define UN_SC_LB 0.45359237f
#define UN_SC_OZ 0.028349523125f
#define UN_SC_FAH 0.555555555555f
/* clang-format on */
/* Define a single unit. */
@ -101,7 +103,7 @@ typedef struct bUnitDef {
const char *identifier;
double scalar;
/** Not used yet, needed for converting temperature. */
/** Needed for converting temperatures. */
double bias;
int flag;
} bUnitDef;
@ -329,6 +331,22 @@ static struct bUnitDef buPowerDef[] = {
};
static struct bUnitCollection buPowerCollection = {buPowerDef, 3, 0, UNIT_COLLECTION_LENGTH(buPowerDef)};
/* Temperature */
static struct bUnitDef buMetricTempDef[] = {
{"kelvin", "kelvin", "K", NULL, "Kelvin", "KELVIN", 1.0f, 0.0, B_UNIT_DEF_NONE}, /* Base unit. */
{"celsius", "celsius", "°C", "C", "Celsius", "CELCIUS", 1.0f, 273.15, B_UNIT_DEF_NONE},
NULL_UNIT,
};
static struct bUnitCollection buMetricTempCollection = {buMetricTempDef, 0, 0, UNIT_COLLECTION_LENGTH(buMetricTempDef)};
static struct bUnitDef buImperialTempDef[] = {
{"kelvin", "kelvin", "K", NULL, "Kelvin", "KELVIN", 1.0f, 0.0, B_UNIT_DEF_NONE}, /* Base unit. */
{"fahrenheit", "fahrenheit", "°F", "F", "Fahrenheit", "FAHRENHEIT", UN_SC_FAH, 459.67, B_UNIT_DEF_NONE},
NULL_UNIT,
};
static struct bUnitCollection buImperialTempCollection = {
buImperialTempDef, 1, 0, UNIT_COLLECTION_LENGTH(buImperialTempDef)};
/* clang-format on */
#define UNIT_SYSTEM_TOT (((sizeof(bUnitSystems) / B_UNIT_TYPE_TOT) / sizeof(void *)) - 1)
@ -344,6 +362,7 @@ static const struct bUnitCollection *bUnitSystems[][B_UNIT_TYPE_TOT] = {
NULL,
NULL,
NULL,
NULL,
NULL},
/* Metric. */
{NULL,
@ -356,7 +375,8 @@ static const struct bUnitCollection *bUnitSystems[][B_UNIT_TYPE_TOT] = {
&buMetricVelCollection,
&buMetricAclCollection,
&buCameraLenCollection,
&buPowerCollection},
&buPowerCollection,
&buMetricTempCollection},
/* Imperial. */
{NULL,
&buImperialLenCollection,
@ -368,7 +388,8 @@ static const struct bUnitCollection *bUnitSystems[][B_UNIT_TYPE_TOT] = {
&buImperialVelCollection,
&buImperialAclCollection,
&buCameraLenCollection,
&buPowerCollection},
&buPowerCollection,
&buImperialTempCollection},
{NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL},
};
@ -449,7 +470,7 @@ static size_t unit_as_string(char *str,
}
}
double value_conv = value / unit->scalar;
double value_conv = (value / unit->scalar) - unit->bias;
/* Adjust precision to expected number of significant digits.
* Note that here, we shall not have to worry about very big/small numbers, units are expected
@ -512,6 +533,7 @@ typedef struct {
int length;
int mass;
int time;
int temperature;
} PreferredUnits;
static PreferredUnits preferred_units_from_UnitSettings(const UnitSettings *settings)
@ -522,6 +544,7 @@ static PreferredUnits preferred_units_from_UnitSettings(const UnitSettings *sett
units.length = settings->length_unit;
units.mass = settings->mass_unit;
units.time = settings->time_unit;
units.temperature = settings->temperature_unit;
return units;
}
@ -597,6 +620,11 @@ static const bUnitDef *get_preferred_display_unit_if_used(int type, PreferredUni
return usys->units + 3;
}
break;
case B_UNIT_TEMPERATURE:
if (units.temperature == USER_UNIT_ADAPTIVE) {
return NULL;
}
return usys->units + MIN2(units.temperature, max_offset);
default:
break;
}
@ -643,6 +671,7 @@ size_t bUnit_AsString(
units.length = USER_UNIT_ADAPTIVE;
units.mass = USER_UNIT_ADAPTIVE;
units.time = USER_UNIT_ADAPTIVE;
units.temperature = USER_UNIT_ADAPTIVE;
return unit_as_string_main(str, len_max, value, prec, type, split, pad, units);
}
@ -844,6 +873,35 @@ static bool unit_distribute_negatives(char *str, const int len_max)
return changed;
}
/**
* Helper for #unit_scale_str for the process of correctly applying the order of operations
* for the unit's bias term.
*/
static int find_previous_non_value_char(const char *str, const int start_ofs)
{
for (int i = start_ofs; i > 0; i--) {
if (ch_is_op(str[i - 1]) || strchr("( )", str[i - 1])) {
return i;
}
}
return 0;
}
/**
* Helper for #unit_scale_str for the process of correctly applying the order of operations
* for the unit's bias term.
*/
static int find_end_of_value_chars(const char *str, const int len_max, const int start_ofs)
{
int i;
for (i = start_ofs; i < len_max; i++) {
if (!strchr("0123456789eE.", str[i])) {
return i;
}
}
return i;
}
static int unit_scale_str(char *str,
int len_max,
char *str_tmp,
@ -867,6 +925,34 @@ static int unit_scale_str(char *str,
int len = strlen(str);
/* Deal with unit bias for temperature units. Order of operations is important, so we
* have to add parentheses, add the bias, then multiply by the scalar like usual.
*
* Note: If these changes don't fit in the buffer properly unit evaluation has failed,
* just try not to destroy anything while failing. */
if (unit->bias != 0.0) {
/* Add the open parenthesis. */
int prev_op_ofs = find_previous_non_value_char(str, found_ofs);
if (len + 1 < len_max) {
memmove(str + prev_op_ofs + 1, str + prev_op_ofs, len - prev_op_ofs + 1);
str[prev_op_ofs] = '(';
len++;
found_ofs++;
str_found++;
} /* If this doesn't fit, we have failed. */
/* Add the addition sign, the bias, and the close parenthesis after the value. */
int value_end_ofs = find_end_of_value_chars(str, len_max, prev_op_ofs + 2);
int len_bias_num = BLI_snprintf(str_tmp, TEMP_STR_SIZE, "+%.9g)", unit->bias);
if (value_end_ofs + len_bias_num < len_max) {
memmove(str + value_end_ofs + len_bias_num, str + value_end_ofs, len - value_end_ofs + 1);
memcpy(str + value_end_ofs, str_tmp, len_bias_num);
len += len_bias_num;
found_ofs += len_bias_num;
str_found += len_bias_num;
} /* If this doesn't fit, we have failed. */
}
int len_name = strlen(replace_str);
int len_move = (len - (found_ofs + len_name)) + 1; /* 1+ to copy the string terminator. */
@ -990,15 +1076,15 @@ bool bUnit_ContainsUnit(const char *str, int type)
return false;
}
double bUnit_PreferredInputUnitScalar(const struct UnitSettings *settings, int type)
double bUnit_ApplyPreferredUnit(const struct UnitSettings *settings, int type, double value)
{
PreferredUnits units = preferred_units_from_UnitSettings(settings);
const bUnitDef *unit = get_preferred_display_unit_if_used(type, units);
if (unit) {
return unit->scalar;
}
return bUnit_BaseScalar(units.system, type);
const double scalar = (unit == NULL) ? bUnit_BaseScalar(units.system, type) : unit->scalar;
const double bias = (unit == NULL) ? 0.0 : unit->bias; /* Base unit shouldn't have a bias. */
return value * scalar + bias;
}
/**

View File

@ -298,7 +298,7 @@ bool user_string_to_number(bContext *C,
}
int success = BPY_run_string_as_number(C, NULL, str, error_prefix, r_value);
*r_value *= bUnit_PreferredInputUnitScalar(unit, type);
*r_value = bUnit_ApplyPreferredUnit(unit, type, *r_value);
*r_value /= unit_scale;
return success;

View File

@ -1534,8 +1534,9 @@ typedef struct UnitSettings {
char length_unit;
char mass_unit;
char time_unit;
char temperature_unit;
char _pad[5];
char _pad[4];
} UnitSettings;
/* ------------------------------------------- */

View File

@ -91,6 +91,7 @@ typedef enum PropertyUnit {
PROP_UNIT_ACCELERATION = (8 << 16), /* m/(s^2) */
PROP_UNIT_CAMERA = (9 << 16), /* mm */
PROP_UNIT_POWER = (10 << 16), /* W */
PROP_UNIT_TEMPERATURE = (11 << 16), /* C */
} PropertyUnit;
#define RNA_SUBTYPE_UNIT(subtype) ((subtype)&0x00FF0000)
@ -156,6 +157,9 @@ typedef enum PropertySubType {
/** Light */
PROP_POWER = 42 | PROP_UNIT_POWER,
/* temperature */
PROP_TEMPERATURE = 43 | PROP_UNIT_TEMPERATURE,
} PropertySubType;
/* Make sure enums are updated with these */

View File

@ -3199,6 +3199,8 @@ static const char *rna_property_subtypename(PropertySubType type)
return "PROP_PASSWORD";
case PROP_POWER:
return "PROP_POWER";
case PROP_TEMPERATURE:
return "PROP_TEMPERATURE";
default: {
/* in case we don't have a type preset that includes the subtype */
if (RNA_SUBTYPE_UNIT(type)) {
@ -3236,6 +3238,8 @@ static const char *rna_property_subtype_unit(PropertySubType type)
return "PROP_UNIT_CAMERA";
case PROP_UNIT_POWER:
return "PROP_UNIT_POWER";
case PROP_UNIT_TEMPERATURE:
return "PROP_UNIT_TEMPERATURE";
default:
return "PROP_UNIT_UNKNOWN";
}

View File

@ -117,6 +117,7 @@ const EnumPropertyItem rna_enum_property_unit_items[] = {
{PROP_UNIT_MASS, "MASS", 0, "Mass", ""},
{PROP_UNIT_CAMERA, "CAMERA", 0, "Camera", ""},
{PROP_UNIT_POWER, "POWER", 0, "Power", ""},
{PROP_UNIT_TEMPERATURE, "TEMPERATURE", 0, "Temperature", ""},
{0, NULL, 0, NULL, NULL},
};

View File

@ -2568,6 +2568,15 @@ const EnumPropertyItem *rna_UnitSettings_time_unit_itemf(bContext *UNUSED(C),
return rna_UnitSettings_itemf_wrapper(units->system, B_UNIT_TIME, r_free);
}
const EnumPropertyItem *rna_UnitSettings_temperature_unit_itemf(bContext *UNUSED(C),
PointerRNA *ptr,
PropertyRNA *UNUSED(prop),
bool *r_free)
{
UnitSettings *units = ptr->data;
return rna_UnitSettings_itemf_wrapper(units->system, B_UNIT_TEMPERATURE, r_free);
}
static void rna_UnitSettings_system_update(Main *UNUSED(bmain),
Scene *scene,
PointerRNA *UNUSED(ptr))
@ -3906,6 +3915,13 @@ static void rna_def_unit_settings(BlenderRNA *brna)
RNA_def_property_enum_funcs(prop, NULL, NULL, "rna_UnitSettings_time_unit_itemf");
RNA_def_property_ui_text(prop, "Time Unit", "Unit that will be used to display time values");
RNA_def_property_update(prop, NC_WINDOW, NULL);
prop = RNA_def_property(srna, "temperature_unit", PROP_ENUM, PROP_NONE);
RNA_def_property_enum_items(prop, DummyRNA_DEFAULT_items);
RNA_def_property_enum_funcs(prop, NULL, NULL, "rna_UnitSettings_temperature_unit_itemf");
RNA_def_property_ui_text(
prop, "Temperature Unit", "Unit that will be used to display temperature values");
RNA_def_property_update(prop, NC_WINDOW, NULL);
}
static void rna_def_view_layer_eevee(BlenderRNA *brna)

View File

@ -172,6 +172,7 @@ static const EnumPropertyItem property_subtype_array_items[] = {
{PROP_LAYER, "LAYER", 0, "Layer", ""},
{PROP_LAYER_MEMBER, "LAYER_MEMBER", 0, "Layer Member", ""},
{PROP_POWER, "POWER", 0, "Power", ""},
{PROP_TEMPERATURE, "TEMPERATURE", 0, "Temperature", ""},
{PROP_NONE, "NONE", 0, "None", ""},
{0, NULL, 0, NULL, NULL},