33 b.use_custom_socket_order();
34 b.allow_any_socket_order();
37 "Format string using a Python and path template compatible syntax. For example, \"Count: "
38 "{}\" would replace the {} with the first input value.");
42 const bNode *node =
b.node_or_null();
43 if (!ntree || !node) {
53 b.add_input(socket_type,
name, identifier)
70 dst_node->
storage = dst_storage;
98 C, panel,
tree, node);
101 panel->use_property_split_set(
true);
102 panel->use_property_decorate_set(
false);
103 panel->prop(item_ptr,
"socket_type",
UI_ITEM_NONE, std::nullopt, ICON_NONE);
122 for (
const char &c :
format.substr(1)) {
129 if (braces_depth == 0) {
148 const char next_c =
format[
i + 1];
183 pattern +=
"([^{}]?[<>^])?";
185 if (type.
is<
float>() || type.
is<
int>()) {
187 pattern +=
"[+\\- ]?";
193 const std::string integer_or_identifier =
"(\\d+|(\\{.*\\}))";
195 pattern += integer_or_identifier;
198 const int width_group = groups_num;
200 std::optional<int> precision_group;
201 if (type.
is<
float>() || type.
is<std::string>()) {
204 pattern += integer_or_identifier;
207 precision_group = groups_num;
211 if (type.
is<std::string>()) {
212 pattern +=
"[s\\?]?";
214 else if (type.
is<
int>()) {
215 pattern +=
"[bBcdoxX]?";
217 else if (type.
is<
float>()) {
218 pattern +=
"[aAeEfFgG]?";
222 return {pattern, std::regex{pattern}, width_group, precision_group};
227 if (type.
is<
float>()) {
231 if (type.
is<
int>()) {
235 if (type.
is<std::string>()) {
251 bool non_auto_index_used_ =
false;
255 : inputs_(
inputs), input_names_(input_names)
262 if (!input_index.has_value()) {
265 return &inputs_[*input_index];
269 std::optional<std::string> &r_error)
272 if (non_auto_index_used_) {
277 "Empty identifier cannot be used when explicit identifier was used before. For "
278 "example, \"{} {x}\" is ok but \"{x} {}\" is not.");
282 if (next_auto_index_ == inputs_.size()) {
285 r_error =
TIP_(
"Format uses more inputs than provided.");
289 return next_auto_index_++;
291 non_auto_index_used_ =
true;
292 if (std::isdigit(identifier[0])) {
294 std::from_chars_result res = std::from_chars(identifier.
begin(), identifier.
end(), index);
295 if (res.ec != std::errc()) {
297 r_error = fmt::format(fmt::runtime(
TIP_(
"Invalid identifier: \"{}\"")), identifier);
301 if (res.ptr < identifier.
end()) {
304 r_error = fmt::format(
305 fmt::runtime(
TIP_(
"An input name cannot start with a digit: \"{}\"")), identifier);
309 if (index >= inputs_.size()) {
311 if (inputs_.is_empty()) {
312 r_error = fmt::format(fmt::runtime(
TIP_(
"There are no inputs.")), identifier);
315 r_error = fmt::format(
316 fmt::runtime(
TIP_(
"Input with index {} does not exist. Currently, the maximum "
317 "possible index is {}. Did you mean to use {{:{}}}?")),
327 const int index = input_names_.index_of_try_as(identifier);
330 r_error = fmt::format(fmt::runtime(
TIP_(
"Input does not exist: \"{}\"")), identifier);
352 for (
const char c :
format) {
353 if (pattern.
pattern_str.find(c) == std::string::npos && std::isprint(c) && !std::isdigit(c)) {
355 fmt::runtime(
TIP_(
"Format contains unsupported \"{}\" character: \"{}\"")),
360 return fmt::format(fmt::runtime(
TIP_(
"Invalid format: \"{}\"")), format_outer);
368 std::optional<std::string> &r_error)
371 if (!allowed_pattern) {
375 r_error = fmt::format(fmt::runtime(
TIP_(
"Type \"{}\" cannot be formatted")), type.
name());
385 format, format_outer, *allowed_pattern);
396 const std::string width_outer = m.str(allowed_pattern->
width_group);
397 if (!width_outer.empty()) {
403 if (!
result.widths->type().is<
int>()) {
405 r_error = fmt::format(
406 fmt::runtime(
TIP_(
"Only integer inputs can be used as dynamic width: \"{}\"")),
411 formats_to_replace.
append(width_outer);
416 const std::string precision_outer = m.str(*allowed_pattern->
precision_group);
417 if (!precision_outer.empty()) {
423 if (!
result.precisions->type().is<
int>()) {
425 r_error = fmt::format(
426 fmt::runtime(
TIP_(
"Only integer inputs can be used as dynamic precision: \"{}\"")),
431 formats_to_replace.
append(precision_outer);
435 result.fmt_format_str =
"{:";
437 result.fmt_format_str +=
'}';
440 for (
const std::string &old : formats_to_replace) {
442 if (old_start != std::string::npos) {
443 result.fmt_format_str.replace(old_start, old.size(),
"{}");
457 const auto append_single_formatted_string = [&](
const auto &varray) {
459 std::string &
output = r_formatted_strings[
i];
460 auto output_inserter = std::back_inserter(
output);
463 const int precision = std::max(0, precisions->
get<
int>(
i));
465 const int width = std::max(0, widths->
get<
int>(
i));
466 fmt::format_to(output_inserter,
format, varray[
i], width, precision);
469 fmt::format_to(output_inserter,
format, varray[
i], precision);
474 const int width = std::max(0, widths->
get<
int>(
i));
475 fmt::format_to(output_inserter,
format, varray[
i], width);
478 fmt::format_to(output_inserter,
format, varray[
i]);
482 catch (
const fmt::format_error & ) {
490 if (type.
is<
float>()) {
491 append_single_formatted_string(
input.typed<
float>());
493 else if (type.
is<
int>()) {
494 append_single_formatted_string(
input.typed<
int>());
496 else if (type.
is<std::string>()) {
497 append_single_formatted_string(
input.typed<std::string>());
511 std::optional<std::string> &r_error)
515 std::optional<ProcessedPythonCompatibleFormat> processed_format =
517 format_pattern, format_outer, type, inputs_lookup, r_error);
518 if (!processed_format.has_value()) {
524 processed_format->widths,
525 processed_format->precisions,
527 r_formatted_strings);
534 std::optional<std::string> &r_error)
537 if (type.
is<
float>()) {
539 std::string &
output = r_formatted_strings[
i];
540 const float value =
input.get<
float>(
i);
542 format_pattern, value))
544 output.append(*value_str);
547 r_error = fmt::format(fmt::runtime(
TIP_(
"Invalid format specifier: \"{}\"")),
552 else if (type.
is<
int>()) {
554 std::string &
output = r_formatted_strings[
i];
559 output.append(*value_str);
562 r_error = fmt::format(fmt::runtime(
TIP_(
"Invalid format specifier: \"{}\"")),
567 else if (type.
is<std::string>()) {
569 r_error = fmt::format(fmt::runtime(
TIP_(
"Invalid format specifier for string: \"{}\"")),
574 r_error = fmt::format(fmt::runtime(
TIP_(
"Type \"{}\" cannot be formatted")), type.
name());
581 std::optional<std::string> &r_error)
584 if (type.
is<
float>()) {
586 const float value =
input.get<
float>(
i);
587 std::string &
output = r_formatted_strings[
i];
588 std::string value_str = fmt::format(
"{}", value);
591 value_str.append(
".0");
596 else if (type.
is<
int>()) {
599 std::string &
output = r_formatted_strings[
i];
600 output += fmt::format(
"{}", value);
603 else if (type.
is<std::string>()) {
605 const std::string value =
input.get<std::string>(
i);
606 std::string &
output = r_formatted_strings[
i];
611 r_error = fmt::format(fmt::runtime(
TIP_(
"Type \"{}\" cannot be formatted")), type.
name());
620 std::optional<std::string> &r_error)
627 while (current_index <
format.size()) {
629 std::string copy_str;
631 format, current_index, copy_str);
634 if (!copy_str.empty()) {
636 std::string &
output = r_formatted_strings[
i];
642 if (next_format_start_or_end ==
format.size()) {
645 current_index = next_format_start_or_end;
649 format.substr(current_index));
650 if (!format_outer.has_value()) {
652 r_error = fmt::format(fmt::runtime(
TIP_(
"Format specifier is not closed: \"{}\"")),
653 format.substr(current_index));
657 const StringRef format_inner = format_outer->
substr(1, format_outer->size() - 2);
662 const int64_t colon_index = format_inner.
find(
':');
664 identifier = format_inner;
667 identifier = format_inner.
substr(0, colon_index);
668 format_pattern = format_inner.
substr(colon_index + 1);
696 current_index += format_outer->
size();
705 mf::Signature signature_;
712 mf::SignatureBuilder builder{
"Format String", signature_};
713 builder.single_input<std::string>(
"Format");
718 builder.single_input(item.
name, type);
722 builder.single_output<std::string>(
"String");
740 std::optional<std::string> error_message;
742 if (
const std::optional<std::string> single_format = formats.
get_if_single()) {
758 if (error_message.has_value()) {
774 ntype.
ui_name =
"Format String";
776 "Insert values into a string using a Python and path template compatible formatting syntax";
816 const char first_c = src_name[0];
817 if (first_c >=
'a' && first_c <=
'z') {
820 else if (first_c >=
'A' && first_c <=
'Z') {
821 initial = first_c -
'A' +
'a';
825 char c = initial +
i;
828 c = c -
'z' +
'a' - 1;
830 const std::string potential_name = std::string(1, c);
831 const bool name_exists = std::any_of(
833 storage.items + storage.items_num,
836 return potential_name;
847 if (
name.is_empty()) {
850 const char first_char =
name[0];
851 if (!std::isalpha(first_char) && first_char !=
'_') {
854 for (
const char c :
name) {
855 if (std::isalnum(c) || c ==
'_') {
858 if (
ELEM(c,
'-',
'.',
' ',
'\t')) {
#define NODE_CLASS_CONVERTER
#define NODE_STORAGE_FUNCS(StorageT)
Functions and classes for evaluating template expressions in filepaths.
std::optional< std::string > BKE_path_template_format_int(blender::StringRef format_specifier, int64_t value)
std::optional< std::string > BKE_path_template_format_float(blender::StringRef format_specifier, double value)
#define BLI_assert_unreachable()
void BLO_read_string(BlendDataReader *reader, char **ptr_p)
void BLO_write_string(BlendWriter *writer, const char *data_ptr)
#define NOD_REGISTER_NODE(REGISTER_FUNC)
BMesh const char void * data
static const CPPType & get()
void value_initialize_indices(void *ptr, const IndexMask &mask) const
StringRefNull name() const
void get(int64_t index, void *r_value) const
static constexpr IndexRange from_single(const int64_t index)
constexpr T * data() const
static constexpr int64_t not_found
constexpr int64_t find(char c, int64_t pos=0) const
constexpr const char * begin() const
constexpr const char * end() const
constexpr bool is_empty() const
constexpr StringRef substr(int64_t start, int64_t size) const
constexpr int64_t size() const
constexpr StringRef drop_prefix(int64_t n) const
constexpr StringRef drop_suffix(int64_t n) const
std::optional< T > get_if_single() const
void append(const T &value)
void set_signature(const Signature *signature)
void construct_and_set_matching_fn(Args &&...args)
float length(VecOp< float, D >) RET
void * MEM_callocN(size_t len, const char *str)
void * MEM_dupallocN(const void *vmemh)
void MEM_freeN(void *vmemh)
ccl_device_inline float2 mask(const MaskType mask, const float2 a)
void node_register_type(bNodeType &ntype)
const CPPType * socket_type_to_geo_nodes_base_cpp_type(eNodeSocketDatatype type)
void node_type_storage(bNodeType &ntype, std::optional< StringRefNull > storagename, void(*freefunc)(bNode *node), void(*copyfunc)(bNodeTree *dest_ntree, bNode *dest_node, const bNode *src_node))
void make_common_operators()
static void draw_items_list_with_operators(const bContext *C, uiLayout *layout, const bNodeTree &tree, const bNode &node)
static void draw_active_item_props(const bNodeTree &tree, const bNode &node, const FunctionRef< void(PointerRNA *item_ptr)> draw_item)
void blend_write(BlendWriter *writer, const bNode &node)
void destruct_array(bNode &node)
void blend_read_data(BlendDataReader *reader, bNode &node)
void copy_array(const bNode &src_node, bNode &dst_node)
bool try_add_item_via_any_extend_socket(bNodeTree &ntree, bNode &extend_node, bNode &storage_node, bNodeLink &link, const std::optional< StringRef > socket_identifier=std::nullopt, typename Accessor::ItemT **r_new_item=nullptr)
void report_from_multi_function(const mf::Context &context, NodeWarningType type, std::string message)
void fn_node_type_base(blender::bke::bNodeType *ntype, std::string idname, const std::optional< int16_t > legacy_type)
static blender::bke::bNodeSocketTemplate outputs[]
static blender::bke::bNodeSocketTemplate inputs[]
NodeBlendWriteFunction blend_write_storage_content
std::string ui_description
NodeBlendDataReadFunction blend_data_read_storage_content
void(* initfunc)(bNodeTree *ntree, bNode *node)
void(* draw_buttons_ex)(uiLayout *, bContext *C, PointerRNA *ptr)
NodeMultiFunctionBuildFunction build_multi_function
bool(* insert_link)(NodeInsertLinkParams ¶ms)
NodeDeclareFunction declare
void(* register_operators)()
PanelLayout panel(const bContext *C, blender::StringRef idname, bool default_closed)