Blender  V2.93
bpy_utils_units.c
Go to the documentation of this file.
1 /*
2  * This program is free software; you can redistribute it and/or
3  * modify it under the terms of the GNU General Public License
4  * as published by the Free Software Foundation; either version 2
5  * of the License, or (at your option) any later version.
6  *
7  * This program is distributed in the hope that it will be useful,
8  * but WITHOUT ANY WARRANTY; without even the implied warranty of
9  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10  * GNU General Public License for more details.
11  *
12  * You should have received a copy of the GNU General Public License
13  * along with this program; if not, write to the Free Software Foundation,
14  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
15  */
16 
24 /* Future-proof, See https://docs.python.org/3/c-api/arg.html#strings-and-buffers */
25 #define PY_SSIZE_T_CLEAN
26 
27 #include <Python.h>
28 #include <structmember.h>
29 
30 #include "BLI_string.h"
31 #include "BLI_utildefines.h"
32 
33 #include "bpy_utils_units.h"
34 
35 #include "../generic/py_capi_utils.h"
36 
37 #include "BKE_unit.h"
38 
39 /***** C-defined systems and types *****/
40 
41 static PyTypeObject BPyUnitsSystemsType;
42 static PyTypeObject BPyUnitsCategoriesType;
43 
44 /* XXX Maybe better as externs of BKE_unit.h ? */
45 static const char *bpyunits_usystem_items[] = {
46  "NONE",
47  "METRIC",
48  "IMPERIAL",
49  NULL,
50 };
51 
52 static const char *bpyunits_ucategorie_items[] = {
53  "NONE",
54  "LENGTH",
55  "AREA",
56  "VOLUME",
57  "MASS",
58  "ROTATION",
59  "TIME",
60  "VELOCITY",
61  "ACCELERATION",
62  "CAMERA",
63  "POWER",
64  NULL,
65 };
66 
74 
75 static PyStructSequence_Desc bpyunits_systems_desc = {
76  "bpy.utils.units.systems", /* name */
77  "This named tuple contains all predefined unit systems", /* doc */
78  bpyunits_systems_fields, /* fields */
80 };
81 static PyStructSequence_Desc bpyunits_categories_desc = {
82  "bpy.utils.units.categories", /* name */
83  "This named tuple contains all predefined unit names", /* doc */
84  bpyunits_categories_fields, /* fields */
86 };
87 
91 static PyObject *py_structseq_from_strings(PyTypeObject *py_type,
92  PyStructSequence_Desc *py_sseq_desc,
93  const char **str_items)
94 {
95  PyObject *py_struct_seq;
96  int pos = 0;
97 
98  const char **str_iter;
99  PyStructSequence_Field *desc;
100 
101  /* initialize array */
102  /* We really populate the contexts' fields here! */
103  for (str_iter = str_items, desc = py_sseq_desc->fields; *str_iter; str_iter++, desc++) {
104  desc->name = (char *)*str_iter;
105  desc->doc = NULL;
106  }
107  /* end sentinel */
108  desc->name = desc->doc = NULL;
109 
110  PyStructSequence_InitType(py_type, py_sseq_desc);
111 
112  /* initialize pytype */
113  py_struct_seq = PyStructSequence_New(py_type);
114  BLI_assert(py_struct_seq != NULL);
115 
116  for (str_iter = str_items; *str_iter; str_iter++) {
117  PyStructSequence_SET_ITEM(py_struct_seq, pos++, PyUnicode_FromString((*str_iter)));
118  }
119 
120  return py_struct_seq;
121 }
122 
123 static bool bpyunits_validate(const char *usys_str, const char *ucat_str, int *r_usys, int *r_ucat)
124 {
125  *r_usys = BLI_str_index_in_array(usys_str, bpyunits_usystem_items);
126  if (*r_usys < 0) {
127  PyErr_Format(PyExc_ValueError, "Unknown unit system specified: %.200s.", usys_str);
128  return false;
129  }
130 
132  if (*r_ucat < 0) {
133  PyErr_Format(PyExc_ValueError, "Unknown unit category specified: %.200s.", ucat_str);
134  return false;
135  }
136 
137  if (!BKE_unit_is_valid(*r_usys, *r_ucat)) {
138  PyErr_Format(PyExc_ValueError,
139  "%.200s / %.200s unit system/category combination is not valid.",
140  usys_str,
141  ucat_str);
142  return false;
143  }
144 
145  return true;
146 }
147 
149  bpyunits_to_value_doc,
150  ".. method:: to_value(unit_system, unit_category, str_input, str_ref_unit=None)\n"
151  "\n"
152  " Convert a given input string into a float value.\n"
153  "\n"
154  " :arg unit_system: The unit system, from :attr:`bpy.utils.units.systems`.\n"
155  " :type unit_system: string\n"
156  " :arg unit_category: The category of data we are converting (length, area, rotation, "
157  "etc.),\n"
158  " from :attr:`bpy.utils.units.categories`.\n"
159  " :type unit_category: string\n"
160  " :arg str_input: The string to convert to a float value.\n"
161  " :type str_input: string\n"
162  " :arg str_ref_unit: A reference string from which to extract a default unit, if none is "
163  "found in ``str_input``.\n"
164  " :type str_ref_unit: string or None\n"
165  " :return: The converted/interpreted value.\n"
166  " :rtype: float\n"
167  " :raises ValueError: if conversion fails to generate a valid python float value.\n");
168 static PyObject *bpyunits_to_value(PyObject *UNUSED(self), PyObject *args, PyObject *kw)
169 {
170  char *usys_str = NULL, *ucat_str = NULL, *inpt = NULL, *uref = NULL;
171  const float scale = 1.0f;
172 
173  char *str;
174  Py_ssize_t str_len;
175  double result;
176  int usys, ucat;
177  PyObject *ret;
178 
179  static const char *_keywords[] = {
180  "unit_system",
181  "unit_category",
182  "str_input",
183  "str_ref_unit",
184  NULL,
185  };
186  static _PyArg_Parser _parser = {"sss#|z:to_value", _keywords, 0};
187  if (!_PyArg_ParseTupleAndKeywordsFast(
188  args, kw, &_parser, &usys_str, &ucat_str, &inpt, &str_len, &uref)) {
189  return NULL;
190  }
191 
192  if (!bpyunits_validate(usys_str, ucat_str, &usys, &ucat)) {
193  return NULL;
194  }
195 
196  str_len = str_len * 2 + 64;
197  str = PyMem_MALLOC(sizeof(*str) * (size_t)str_len);
198  BLI_strncpy(str, inpt, (size_t)str_len);
199 
200  BKE_unit_replace_string(str, (int)str_len, uref, scale, usys, ucat);
201 
202  if (!PyC_RunString_AsNumber(NULL, str, "<bpy_units_api>", &result)) {
203  if (PyErr_Occurred()) {
204  PyErr_Print();
205  PyErr_Clear();
206  }
207 
208  PyErr_Format(
209  PyExc_ValueError, "'%.200s' (converted as '%s') could not be evaluated.", inpt, str);
210  ret = NULL;
211  }
212  else {
213  ret = PyFloat_FromDouble(result);
214  }
215 
216  PyMem_FREE(str);
217  return ret;
218 }
219 
220 PyDoc_STRVAR(bpyunits_to_string_doc,
221  ".. method:: to_string(unit_system, unit_category, value, precision=3, "
222  "split_unit=False, compatible_unit=False)\n"
223  "\n"
224  " Convert a given input float value into a string with units.\n"
225  "\n"
226  " :arg unit_system: The unit system, from :attr:`bpy.utils.units.systems`.\n"
227  " :type unit_system: string\n"
228  " :arg unit_category: The category of data we are converting (length, area, "
229  "rotation, etc.),\n"
230  " from :attr:`bpy.utils.units.categories`.\n"
231  " :type unit_category: string\n"
232  " :arg value: The value to convert to a string.\n"
233  " :type value: float\n"
234  " :arg precision: Number of digits after the comma.\n"
235  " :type precision: int\n"
236  " :arg split_unit: Whether to use several units if needed (1m1cm), or always only "
237  "one (1.01m).\n"
238  " :type split_unit: bool\n"
239  " :arg compatible_unit: Whether to use keyboard-friendly units (1m2) or nicer "
240  "utf-8 ones (1m²).\n"
241  " :type compatible_unit: bool\n"
242  " :return: The converted string.\n"
243  " :rtype: str\n"
244  " :raises ValueError: if conversion fails to generate a valid python string.\n");
245 static PyObject *bpyunits_to_string(PyObject *UNUSED(self), PyObject *args, PyObject *kw)
246 {
247  char *usys_str = NULL, *ucat_str = NULL;
248  double value = 0.0;
249  int precision = 3;
250  bool split_unit = false, compatible_unit = false;
251 
252  int usys, ucat;
253 
254  static const char *_keywords[] = {
255  "unit_system",
256  "unit_category",
257  "value",
258  "precision",
259  "split_unit",
260  "compatible_unit",
261  NULL,
262  };
263  static _PyArg_Parser _parser = {"ssd|iO&O&:to_string", _keywords, 0};
264  if (!_PyArg_ParseTupleAndKeywordsFast(args,
265  kw,
266  &_parser,
267  &usys_str,
268  &ucat_str,
269  &value,
270  &precision,
272  &split_unit,
274  &compatible_unit)) {
275  return NULL;
276  }
277 
278  if (!bpyunits_validate(usys_str, ucat_str, &usys, &ucat)) {
279  return NULL;
280  }
281 
282  {
283  /* Maximum expected length of string result:
284  * - Number itself: precision + decimal dot + up to four 'above dot' digits.
285  * - Unit: up to ten chars
286  * (six currently, let's be conservative, also because we use some utf8 chars).
287  * This can be repeated twice (e.g. 1m20cm), and we add ten more spare chars
288  * (spaces, trailing '\0'...).
289  * So in practice, 64 should be more than enough.
290  */
291  char buf1[64], buf2[64], *str;
292  PyObject *result;
293 
295  buf1, sizeof(buf1), value, precision, usys, ucat, (bool)split_unit, false);
296 
297  if (compatible_unit) {
298  BKE_unit_name_to_alt(buf2, sizeof(buf2), buf1, usys, ucat);
299  str = buf2;
300  }
301  else {
302  str = buf1;
303  }
304 
305  result = PyUnicode_FromString(str);
306 
307  return result;
308  }
309 }
310 
311 static PyMethodDef bpyunits_methods[] = {
312  {"to_value",
313  (PyCFunction)bpyunits_to_value,
314  METH_VARARGS | METH_KEYWORDS,
315  bpyunits_to_value_doc},
316  {"to_string",
317  (PyCFunction)bpyunits_to_string,
318  METH_VARARGS | METH_KEYWORDS,
319  bpyunits_to_string_doc},
320  {NULL, NULL, 0, NULL},
321 };
322 
323 PyDoc_STRVAR(bpyunits_doc, "This module contains some data/methods regarding units handling.");
324 
325 static struct PyModuleDef bpyunits_module = {
326  PyModuleDef_HEAD_INIT,
327  "bpy.utils.units",
328  bpyunits_doc,
329  -1, /* multiple "initialization" just copies the module dict. */
331  NULL,
332  NULL,
333  NULL,
334  NULL,
335 };
336 
337 PyObject *BPY_utils_units(void)
338 {
339  PyObject *submodule, *item;
340 
341  submodule = PyModule_Create(&bpyunits_module);
342  PyDict_SetItemString(PyImport_GetModuleDict(), bpyunits_module.m_name, submodule);
343 
344  /* Finalize our unit systems and types structseq definitions! */
345 
346  /* bpy.utils.units.system */
349  PyModule_AddObject(submodule, "systems", item); /* steals ref */
350 
351  /* bpy.utils.units.categories */
354  PyModule_AddObject(submodule, "categories", item); /* steals ref */
355 
356  return submodule;
357 }
bool BKE_unit_is_valid(int system, int type)
Definition: unit.c:1262
size_t BKE_unit_value_as_string_adaptive(char *str, int len_max, double value, int prec, int system, int type, bool split, bool pad)
Definition: unit.c:665
void BKE_unit_name_to_alt(char *str, int len_max, const char *orig_str, int system, int type)
Definition: unit.c:1196
bool BKE_unit_replace_string(char *str, int len_max, const char *str_prev, double scale_pref, int system, int type)
Definition: unit.c:1106
#define BLI_assert(a)
Definition: BLI_assert.h:58
int BLI_str_index_in_array(const char *__restrict str, const char **__restrict str_array) ATTR_NONNULL()
Definition: string.c:986
char * BLI_strncpy(char *__restrict dst, const char *__restrict src, const size_t maxncpy) ATTR_NONNULL()
Definition: string.c:108
#define ARRAY_SIZE(arr)
#define UNUSED(x)
static PyObject * py_structseq_from_strings(PyTypeObject *py_type, PyStructSequence_Desc *py_sseq_desc, const char **str_items)
static PyStructSequence_Field bpyunits_systems_fields[ARRAY_SIZE(bpyunits_usystem_items)]
static PyObject * bpyunits_to_string(PyObject *UNUSED(self), PyObject *args, PyObject *kw)
PyObject * BPY_utils_units(void)
static const char * bpyunits_usystem_items[]
static const char * bpyunits_ucategorie_items[]
static PyStructSequence_Desc bpyunits_categories_desc
static PyMethodDef bpyunits_methods[]
static PyObject * bpyunits_to_value(PyObject *UNUSED(self), PyObject *args, PyObject *kw)
static PyStructSequence_Field bpyunits_categories_fields[ARRAY_SIZE(bpyunits_ucategorie_items)]
static PyTypeObject BPyUnitsCategoriesType
static bool bpyunits_validate(const char *usys_str, const char *ucat_str, int *r_usys, int *r_ucat)
static PyStructSequence_Desc bpyunits_systems_desc
PyDoc_STRVAR(bpyunits_to_value_doc, ".. method:: to_value(unit_system, unit_category, str_input, str_ref_unit=None)\n" "\n" " Convert a given input string into a float value.\n" "\n" " :arg unit_system: The unit system, from :attr:`bpy.utils.units.systems`.\n" " :type unit_system: string\n" " :arg unit_category: The category of data we are converting (length, area, rotation, " "etc.),\n" " from :attr:`bpy.utils.units.categories`.\n" " :type unit_category: string\n" " :arg str_input: The string to convert to a float value.\n" " :type str_input: string\n" " :arg str_ref_unit: A reference string from which to extract a default unit, if none is " "found in ``str_input``.\n" " :type str_ref_unit: string or None\n" " :return: The converted/interpreted value.\n" " :rtype: float\n" " :raises ValueError: if conversion fails to generate a valid python float value.\n")
static struct PyModuleDef bpyunits_module
static PyTypeObject BPyUnitsSystemsType
#define str(s)
uint pos
bool PyC_RunString_AsNumber(const char *imports[], const char *expr, const char *filename, double *r_value)
int PyC_ParseBool(PyObject *o, void *p)
return ret