static PyObject *bpyunits_to_value(PyObject *UNUSED(self), PyObject *args, PyObject *kw) { static const char *kwlist[] = {"unit_system", "unit_category", "str_input", "str_ref_unit", NULL}; char *usys_str = NULL, *ucat_str = NULL, *inpt = NULL, *uref = NULL; const float scale = 1.0f; char *str; Py_ssize_t str_len; double result; int usys, ucat; PyObject *ret; if (!PyArg_ParseTupleAndKeywords(args, kw, "sss#|z:bpy.utils.units.to_value", (char **)kwlist, &usys_str, &ucat_str, &inpt, &str_len, &uref)) { return NULL; } if (!bpyunits_validate(usys_str, ucat_str, &usys, &ucat)) { return NULL; } str_len = str_len * 2 + 64; str = PyMem_MALLOC(sizeof(*str) * (size_t)str_len); BLI_strncpy(str, inpt, (size_t)str_len); bUnit_ReplaceString(str, (int)str_len, uref, scale, usys, ucat); if (!PyC_RunString_AsNumber(str, &result, "<bpy_units_api>")) { if (PyErr_Occurred()) { PyErr_Print(); PyErr_Clear(); } PyErr_Format(PyExc_ValueError, "'%.200s' (converted as '%s') could not be evaluated.", inpt, str); ret = NULL; } else { ret = PyFloat_FromDouble(result); } PyMem_FREE(str); return ret; }
bool handleNumInput(bContext *C, NumInput *n, const wmEvent *event) { const char *utf8_buf = NULL; char ascii[2] = {'\0', '\0'}; bool updated = false; short idx = n->idx, idx_max = n->idx_max; short dir = STRCUR_DIR_NEXT, mode = STRCUR_JUMP_NONE; int cur; double val; switch (event->type) { case EVT_MODAL_MAP: if (ELEM(event->val, NUM_MODAL_INCREMENT_UP, NUM_MODAL_INCREMENT_DOWN)) { n->val[idx] += (event->val == NUM_MODAL_INCREMENT_UP) ? n->val_inc[idx] : -n->val_inc[idx]; value_to_editstr(n, idx); n->val_flag[idx] |= NUM_EDITED; updated = true; } else { /* might be a char too... */ utf8_buf = event->utf8_buf; ascii[0] = event->ascii; } break; case BACKSPACEKEY: /* Part specific to backspace... */ if (!(n->val_flag[idx] & NUM_EDITED)) { copy_v3_v3(n->val, n->val_org); n->val_flag[0] &= ~NUM_EDITED; n->val_flag[1] &= ~NUM_EDITED; n->val_flag[2] &= ~NUM_EDITED; updated = true; break; } else if (event->shift || !n->str[0]) { n->val[idx] = n->val_org[idx]; n->val_flag[idx] &= ~NUM_EDITED; n->str[0] = '\0'; n->str_cur = 0; updated = true; break; } /* Else, common behavior with DELKEY, only difference is remove char(s) before/after the cursor. */ dir = STRCUR_DIR_PREV; /* fall-through */ case DELKEY: if ((n->val_flag[idx] & NUM_EDITED) && n->str[0]) { int t_cur = cur = n->str_cur; if (event->ctrl) { mode = STRCUR_JUMP_DELIM; } BLI_str_cursor_step_utf8(n->str, strlen(n->str), &t_cur, dir, mode, true); if (t_cur != cur) { if (t_cur < cur) { SWAP(int, t_cur, cur); n->str_cur = cur; } memmove(&n->str[cur], &n->str[t_cur], strlen(&n->str[t_cur]) + 1); /* +1 for trailing '\0'. */ updated = true; } } else { return false; } break; case LEFTARROWKEY: dir = STRCUR_DIR_PREV; /* fall-through */ case RIGHTARROWKEY: cur = n->str_cur; if (event->ctrl) { mode = STRCUR_JUMP_DELIM; } BLI_str_cursor_step_utf8(n->str, strlen(n->str), &cur, dir, mode, true); if (cur != n->str_cur) { n->str_cur = cur; return true; } return false; case HOMEKEY: if (n->str[0]) { n->str_cur = 0; return true; } return false; case ENDKEY: if (n->str[0]) { n->str_cur = strlen(n->str); return true; } return false; case TABKEY: n->val_org[idx] = n->val[idx]; n->val_flag[idx] &= ~(NUM_NEGATE | NUM_INVERSE); idx += event->ctrl ? -1 : 1; idx %= idx_max + 1; n->idx = idx; n->val[idx] = n->val_org[idx]; if (n->val_flag[idx] & NUM_EDITED) { value_to_editstr(n, idx); } else { n->str[0] = '\0'; n->str_cur = 0; } return true; case PADPERIOD: /* Force numdot, some OSs/countries generate a comma char in this case, sic... (T37992) */ ascii[0] = '.'; utf8_buf = ascii; break; case EQUALKEY: case PADASTERKEY: if (!(n->flag & NUM_EDIT_FULL)) { n->flag |= NUM_EDIT_FULL; n->val_flag[idx] |= NUM_EDITED; return true; } else if (event->ctrl) { n->flag &= ~NUM_EDIT_FULL; return true; } /* fall-through */ case PADMINUS: case MINUSKEY: if (event->ctrl || !(n->flag & NUM_EDIT_FULL)) { n->val_flag[idx] ^= NUM_NEGATE; updated = true; break; } /* fall-through */ case PADSLASHKEY: case SLASHKEY: if (event->ctrl || !(n->flag & NUM_EDIT_FULL)) { n->val_flag[idx] ^= NUM_INVERSE; updated = true; break; } /* fall-through */ case CKEY: if (event->ctrl) { /* Copy current str to the copypaste buffer. */ WM_clipboard_text_set(n->str, 0); updated = true; break; } /* fall-through */ case VKEY: if (event->ctrl) { /* extract the first line from the clipboard */ int pbuf_len; char *pbuf = WM_clipboard_text_get_firstline(false, &pbuf_len); if (pbuf) { bool success; success = editstr_insert_at_cursor(n, pbuf, pbuf_len); MEM_freeN(pbuf); if (!success) { return false; } n->val_flag[idx] |= NUM_EDITED; } updated = true; break; } /* fall-through */ default: utf8_buf = event->utf8_buf; ascii[0] = event->ascii; break; } if (utf8_buf && !utf8_buf[0] && ascii[0]) { /* Fallback to ascii. */ utf8_buf = ascii; } if (utf8_buf && utf8_buf[0]) { if (!(n->flag & NUM_EDIT_FULL)) { /* In simple edit mode, we only keep a few chars as valid! */ /* no need to decode unicode, ascii is first char only */ if (!editstr_is_simple_numinput(utf8_buf[0])) { return false; } } if (!editstr_insert_at_cursor(n, utf8_buf, BLI_str_utf8_size(utf8_buf))) { return false; } n->val_flag[idx] |= NUM_EDITED; } else if (!updated) { return false; } /* At this point, our value has changed, try to interpret it with python (if str is not empty!). */ if (n->str[0]) { #ifdef WITH_PYTHON char str_unit_convert[NUM_STR_REP_LEN * 6]; /* Should be more than enough! */ const char *default_unit = NULL; /* Make radian default unit when needed. */ if (n->unit_use_radians && n->unit_type[idx] == B_UNIT_ROTATION) default_unit = "r"; BLI_strncpy(str_unit_convert, n->str, sizeof(str_unit_convert)); bUnit_ReplaceString(str_unit_convert, sizeof(str_unit_convert), default_unit, 1.0, n->unit_sys, n->unit_type[idx]); /* Note: with angles, we always get values as radians here... */ if (BPY_button_exec(C, str_unit_convert, &val, false) != -1) { n->val[idx] = (float)val; n->val_flag[idx] &= ~NUM_INVALID; } else { n->val_flag[idx] |= NUM_INVALID; } #else /* Very unlikely, but does not harm... */ n->val[idx] = (float)atof(n->str); #endif /* WITH_PYTHON */ if (n->val_flag[idx] & NUM_NEGATE) { n->val[idx] = -n->val[idx]; } if (n->val_flag[idx] & NUM_INVERSE) { n->val[idx] = 1.0f / n->val[idx]; } } /* REDRAW SINCE NUMBERS HAVE CHANGED */ return true; }
/* make a copy of the string that replaces the units with numbers * this is used before parsing * This is only used when evaluating user input and can afford to be a bit slower * * This is to be used before python evaluation so.. * 10.1km -> 10.1*1000.0 * ...will be resolved by python. * * values will be split by a comma's * 5'2" -> 5'0.0254, 2*0.3048 * * str_prev is optional, when valid it is used to get a base unit when none is set. * * return true of a change was made. */ int bUnit_ReplaceString(char *str, int len_max, const char *str_prev, double scale_pref, int system, int type) { bUnitCollection *usys = unit_get_system(system, type); bUnitDef *unit; char str_tmp[TEMP_STR_SIZE]; int changed = 0; if (usys == NULL || usys->units[0].name == NULL) { return 0; } /* make lowercase */ BLI_ascii_strtolower(str, len_max); for (unit = usys->units; unit->name; unit++) { /* in case there are multiple instances */ while (unit_replace(str, len_max, str_tmp, scale_pref, unit)) changed = true; } unit = NULL; { /* try other unit systems now, so we can evaluate imperial when metric is set for eg. */ bUnitCollection *usys_iter; int system_iter; for (system_iter = 0; system_iter < UNIT_SYSTEM_TOT; system_iter++) { if (system_iter != system) { usys_iter = unit_get_system(system_iter, type); if (usys_iter) { for (unit = usys_iter->units; unit->name; unit++) { int ofs = 0; /* in case there are multiple instances */ while ((ofs = unit_replace(str + ofs, len_max - ofs, str_tmp, scale_pref, unit))) changed = true; } } } } } unit = NULL; if (changed == 0) { /* no units given so infer a unit from the previous string or default */ if (str_prev) { /* see which units the original value had */ for (unit = usys->units; unit->name; unit++) { if (unit_find(str_prev, unit)) break; } } if (unit == NULL || unit->name == NULL) unit = unit_default(usys); /* add the unit prefix and re-run, use brackets in case there was an expression given */ if (BLI_snprintf(str_tmp, sizeof(str_tmp), "(%s)%s", str, unit->name) < sizeof(str_tmp)) { strncpy(str, str_tmp, len_max); return bUnit_ReplaceString(str, len_max, NULL, scale_pref, system, type); } else { /* BLI_snprintf would not fit into str_tmp, cant do much in this case * check for this because otherwise bUnit_ReplaceString could call its self forever */ return 0; } } /* replace # with commas when there is no operator between it and the next number * * "1*1# 3*100# * 3" -> "1 *1, 3 *100 * 3" * * */ { char *str_found = str; char *ch = str; while ((str_found = strchr(str_found, SEP_CHR))) { int op_found = 0; /* any operators after this?*/ for (ch = str_found + 1; *ch != '\0'; ch++) { if (*ch == ' ' || *ch == '\t') { /* do nothing */ } else if (ch_is_op(*ch) || *ch == ',') { /* found an op, no need to insert a ',' */ op_found = 1; break; } else { /* found a non-op character */ op_found = 0; break; } } *str_found++ = op_found ? ' ' : ','; } } return changed; }
bool handleNumInput(bContext *C, NumInput *n, const wmEvent *event) { const char *utf8_buf = NULL; char ascii[2] = {'\0', '\0'}; bool updated = false; short idx = n->idx, idx_max = n->idx_max; short dir = STRCUR_DIR_NEXT, mode = STRCUR_JUMP_NONE; int cur; switch (event->type) { case EVT_MODAL_MAP: if (ELEM(event->val, NUM_MODAL_INCREMENT_UP, NUM_MODAL_INCREMENT_DOWN)) { n->val[idx] += (event->val == NUM_MODAL_INCREMENT_UP) ? n->val_inc[idx] : -n->val_inc[idx]; value_to_editstr(n, idx); n->val_flag[idx] |= NUM_EDITED; updated = true; } else { /* might be a char too... */ utf8_buf = event->utf8_buf; ascii[0] = event->ascii; } break; case BACKSPACEKEY: /* Part specific to backspace... */ if (!(n->val_flag[idx] & NUM_EDITED)) { copy_v3_v3(n->val, n->val_org); n->val_flag[0] &= ~NUM_EDITED; n->val_flag[1] &= ~NUM_EDITED; n->val_flag[2] &= ~NUM_EDITED; n->flag |= NUM_FAKE_EDITED; updated = true; break; } else if (event->shift || !n->str[0]) { n->val[idx] = n->val_org[idx]; n->val_flag[idx] &= ~NUM_EDITED; n->str[0] = '\0'; n->str_cur = 0; updated = true; break; } /* Else, common behavior with DELKEY, only difference is remove char(s) before/after the cursor. */ dir = STRCUR_DIR_PREV; ATTR_FALLTHROUGH; case DELKEY: if ((n->val_flag[idx] & NUM_EDITED) && n->str[0]) { int t_cur = cur = n->str_cur; if (event->ctrl) { mode = STRCUR_JUMP_DELIM; } BLI_str_cursor_step_utf8(n->str, strlen(n->str), &t_cur, dir, mode, true); if (t_cur != cur) { if (t_cur < cur) { SWAP(int, t_cur, cur); n->str_cur = cur; } memmove(&n->str[cur], &n->str[t_cur], strlen(&n->str[t_cur]) + 1); /* +1 for trailing '\0'. */ updated = true; } if (!n->str[0]) { n->val[idx] = n->val_org[idx]; } } else { return false; } break; case LEFTARROWKEY: dir = STRCUR_DIR_PREV; ATTR_FALLTHROUGH; case RIGHTARROWKEY: cur = n->str_cur; if (event->ctrl) { mode = STRCUR_JUMP_DELIM; } BLI_str_cursor_step_utf8(n->str, strlen(n->str), &cur, dir, mode, true); if (cur != n->str_cur) { n->str_cur = cur; return true; } return false; case HOMEKEY: if (n->str[0]) { n->str_cur = 0; return true; } return false; case ENDKEY: if (n->str[0]) { n->str_cur = strlen(n->str); return true; } return false; case TABKEY: n->val_flag[idx] &= ~(NUM_NEGATE | NUM_INVERSE); idx = (idx + idx_max + (event->ctrl ? 0 : 2)) % (idx_max + 1); n->idx = idx; if (n->val_flag[idx] & NUM_EDITED) { value_to_editstr(n, idx); } else { n->str[0] = '\0'; n->str_cur = 0; } return true; case PADPERIOD: case PERIODKEY: /* Force numdot, some OSs/countries generate a comma char in this case, sic... (T37992) */ ascii[0] = '.'; utf8_buf = ascii; break; #if 0 /* Those keys are not directly accessible in all layouts, preventing to generate matching events. * So we use a hack (ascii value) instead, see below. */ case EQUALKEY: case PADASTERKEY: if (!(n->flag & NUM_EDIT_FULL)) { n->flag |= NUM_EDIT_FULL; n->val_flag[idx] |= NUM_EDITED; return true; } else if (event->ctrl) { n->flag &= ~NUM_EDIT_FULL; return true; } break; #endif case PADMINUS: case MINUSKEY: if (event->ctrl || !(n->flag & NUM_EDIT_FULL)) { n->val_flag[idx] ^= NUM_NEGATE; updated = true; } break; case PADSLASHKEY: case SLASHKEY: if (event->ctrl || !(n->flag & NUM_EDIT_FULL)) { n->val_flag[idx] ^= NUM_INVERSE; updated = true; } break; case CKEY: if (event->ctrl) { /* Copy current str to the copypaste buffer. */ WM_clipboard_text_set(n->str, 0); updated = true; } break; case VKEY: if (event->ctrl) { /* extract the first line from the clipboard */ int pbuf_len; char *pbuf = WM_clipboard_text_get_firstline(false, &pbuf_len); if (pbuf) { const bool success = editstr_insert_at_cursor(n, pbuf, pbuf_len); MEM_freeN(pbuf); if (!success) { return false; } n->val_flag[idx] |= NUM_EDITED; } updated = true; } break; default: break; } if (!updated && !utf8_buf && (event->utf8_buf[0] || event->ascii)) { utf8_buf = event->utf8_buf; ascii[0] = event->ascii; } /* XXX Hack around keyboards without direct access to '=' nor '*'... */ if (ELEM(ascii[0], '=', '*')) { if (!(n->flag & NUM_EDIT_FULL)) { n->flag |= NUM_EDIT_FULL; n->val_flag[idx] |= NUM_EDITED; return true; } else if (event->ctrl) { n->flag &= ~NUM_EDIT_FULL; return true; } } /* Up to this point, if we have a ctrl modifier, skip. * This allows to still access most of modals' shortcuts even in numinput mode. */ if (!updated && event->ctrl) { return false; } if ((!utf8_buf || !utf8_buf[0]) && ascii[0]) { /* Fallback to ascii. */ utf8_buf = ascii; } if (utf8_buf && utf8_buf[0]) { if (!(n->flag & NUM_EDIT_FULL)) { /* In simple edit mode, we only keep a few chars as valid! */ /* no need to decode unicode, ascii is first char only */ if (!editstr_is_simple_numinput(utf8_buf[0])) { return false; } } if (!editstr_insert_at_cursor(n, utf8_buf, BLI_str_utf8_size(utf8_buf))) { return false; } n->val_flag[idx] |= NUM_EDITED; } else if (!updated) { return false; } /* At this point, our value has changed, try to interpret it with python (if str is not empty!). */ if (n->str[0]) { const float val_prev = n->val[idx]; #ifdef WITH_PYTHON Scene *sce = CTX_data_scene(C); double val; char str_unit_convert[NUM_STR_REP_LEN * 6]; /* Should be more than enough! */ const char *default_unit = NULL; /* Use scale_length if needed! */ const float fac = (float)BKE_scene_unit_scale(&sce->unit, n->unit_type[idx], 1.0); /* Make radian default unit when needed. */ if (n->unit_use_radians && n->unit_type[idx] == B_UNIT_ROTATION) default_unit = "r"; BLI_strncpy(str_unit_convert, n->str, sizeof(str_unit_convert)); bUnit_ReplaceString(str_unit_convert, sizeof(str_unit_convert), default_unit, fac, n->unit_sys, n->unit_type[idx]); /* Note: with angles, we always get values as radians here... */ if (BPY_execute_string_as_number(C, str_unit_convert, false, &val)) { n->val[idx] = (float)val; n->val_flag[idx] &= ~NUM_INVALID; } else { n->val_flag[idx] |= NUM_INVALID; } #else /* Very unlikely, but does not harm... */ n->val[idx] = (float)atof(n->str); (void)C; #endif /* WITH_PYTHON */ if (n->val_flag[idx] & NUM_NEGATE) { n->val[idx] = -n->val[idx]; } if (n->val_flag[idx] & NUM_INVERSE) { n->val[idx] = 1.0f / n->val[idx]; } if (UNLIKELY(!isfinite(n->val[idx]))) { n->val[idx] = val_prev; n->val_flag[idx] |= NUM_INVALID; } } /* REDRAW SINCE NUMBERS HAVE CHANGED */ return true; }