Merge pull request #71 from limbonaut/blackboard-fixes

Blackboard-related improvements and fixes
This commit is contained in:
Serhii Snitsaruk 2024-03-12 22:31:07 +01:00 committed by GitHub
commit 088ef008a6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 140 additions and 104 deletions

View File

@ -36,7 +36,7 @@ Variant Blackboard::get_var(const StringName &p_name, const Variant &p_default,
if (data.has(p_name)) {
return data.get(p_name).get_value();
} else if (parent.is_valid()) {
return parent->get_var(p_name, p_default);
return parent->get_var(p_name, p_default, p_complain);
} else {
if (p_complain) {
ERR_PRINT(vformat("Blackboard: Variable \"%s\" not found.", p_name));
@ -74,11 +74,17 @@ void Blackboard::unbind_var(const StringName &p_name) {
data[p_name].unbind();
}
void Blackboard::add_var(const StringName &p_name, const BBVariable &p_var) {
ERR_FAIL_COND(data.has(p_name));
void Blackboard::assign_var(const StringName &p_name, const BBVariable &p_var) {
data.insert(p_name, p_var);
}
void Blackboard::link_var(const StringName &p_name, const Ref<Blackboard> &p_target_blackboard, const StringName &p_target_var) {
ERR_FAIL_COND_MSG(!data.has(p_name), "Blackboard: Can't link variable that doesn't exist (var: " + p_name + ").");
ERR_FAIL_COND_MSG(p_target_blackboard.is_null(), "Blackboard: Can't link variable to target blackboard that is null (var: " + p_name + ").");
ERR_FAIL_COND_MSG(!p_target_blackboard->data.has(p_target_var), "Blackboard: Can't link variable to non-existent target (var: " + p_name + ", target: " + p_target_var + ").");
data[p_name] = p_target_blackboard->data[p_target_var];
}
void Blackboard::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_var", "var_name", "default", "complain"), &Blackboard::get_var, Variant(), true);
ClassDB::bind_method(D_METHOD("set_var", "var_name", "value"), &Blackboard::set_var);
@ -89,4 +95,5 @@ void Blackboard::_bind_methods() {
ClassDB::bind_method(D_METHOD("top"), &Blackboard::top);
ClassDB::bind_method(D_METHOD("bind_var_to_property", "var_name", "object", "property"), &Blackboard::bind_var_to_property);
ClassDB::bind_method(D_METHOD("unbind_var", "var_name"), &Blackboard::unbind_var);
ClassDB::bind_method(D_METHOD("link_var", "var_name", "target_blackboard", "target_var"), &Blackboard::link_var);
}

View File

@ -54,7 +54,9 @@ public:
void bind_var_to_property(const StringName &p_name, Object *p_object, const StringName &p_property);
void unbind_var(const StringName &p_name);
void add_var(const StringName &p_name, const BBVariable &p_var);
void assign_var(const StringName &p_name, const BBVariable &p_var);
void link_var(const StringName &p_name, const Ref<Blackboard> &p_target_blackboard, const StringName &p_target_var);
// TODO: Add serialization API.
};

View File

@ -88,7 +88,7 @@ void BlackboardPlan::_get_property_list(List<PropertyInfo> *p_list) const {
BBVariable var = p.second;
// * Editor
if (!is_derived() || !var_name.begins_with("_")) {
if (var.get_type() != Variant::NIL && (!is_derived() || !var_name.begins_with("_"))) {
p_list->push_back(PropertyInfo(var.get_type(), var_name, var.get_hint(), var.get_hint_string(), PROPERTY_USAGE_EDITOR));
}
@ -270,6 +270,25 @@ void BlackboardPlan::sync_with_base_plan() {
}
}
// Sync order of variables.
// Glossary: E - element of current plan, B - element of base plan, F - element of current plan (used for forward search).
ERR_FAIL_COND(base->var_list.size() != var_list.size());
List<Pair<StringName, BBVariable>>::Element *B = base->var_list.front();
for (List<Pair<StringName, BBVariable>>::Element *E = var_list.front(); E; E = E->next()) {
if (E->get().first != B->get().first) {
List<Pair<StringName, BBVariable>>::Element *F = E->next();
while (F) {
if (F->get().first == B->get().first) {
var_list.move_before(F, E);
E = F;
break;
}
F = F->next();
}
}
B = B->next();
}
if (changed) {
notify_property_list_changed();
emit_changed();
@ -284,11 +303,16 @@ inline void bb_add_var_dup_with_prefetch(const Ref<Blackboard> &p_blackboard, co
if (n != nullptr) {
var.set_value(n);
} else {
if (p_blackboard->has_var(p_name)) {
// Not adding: Assuming variable was initialized by the user or in the parent scope.
return;
}
ERR_PRINT(vformat("BlackboardPlan: Prefetch failed for variable $%s with value: %s", p_name, p_var.get_value()));
var.set_value(Variant());
}
p_blackboard->add_var(p_name, var);
p_blackboard->assign_var(p_name, var);
} else {
p_blackboard->add_var(p_name, p_var.duplicate());
p_blackboard->assign_var(p_name, p_var.duplicate());
}
}

View File

@ -193,6 +193,8 @@ void BTPlayer::_notification(int p_notification) {
#ifdef DEBUG_ENABLED
_set_monitor_performance(monitor_performance);
#endif
} else {
_update_blackboard_plan();
}
} break;
case NOTIFICATION_ENTER_TREE: {

View File

@ -17,9 +17,6 @@
#ifdef LIMBOAI_MODULE
#include "core/debugger/engine_debugger.h"
#include "core/error/error_macros.h"
#include "core/object/class_db.h"
#include "core/variant/variant.h"
#endif // LIMBOAI_MODULE
#ifdef LIMBOAI_GDEXTENSION

View File

@ -26,14 +26,13 @@ private:
StringName success_event;
StringName failure_event;
void _update_blackboard_plan();
protected:
static void _bind_methods();
void _notification(int p_notification);
virtual bool _should_use_new_scope() const override { return true; }
virtual void _update_blackboard_plan() override;
virtual void _setup() override;
virtual void _exit() override;

View File

@ -33,25 +33,27 @@ Methods
.. table::
:widths: auto
+-------------------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------+
| void | :ref:`bind_var_to_property<class_Blackboard_method_bind_var_to_property>` **(** StringName var_name, Object object, StringName property **)** |
+-------------------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------+
| void | :ref:`erase_var<class_Blackboard_method_erase_var>` **(** StringName var_name **)** |
+-------------------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------+
| :ref:`Blackboard<class_Blackboard>` | :ref:`get_parent<class_Blackboard_method_get_parent>` **(** **)** |const| |
+-------------------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------+
| Variant | :ref:`get_var<class_Blackboard_method_get_var>` **(** StringName var_name, Variant default=null, bool complain=true **)** |const| |
+-------------------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------+
| bool | :ref:`has_var<class_Blackboard_method_has_var>` **(** StringName var_name **)** |const| |
+-------------------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------+
| void | :ref:`set_parent<class_Blackboard_method_set_parent>` **(** :ref:`Blackboard<class_Blackboard>` blackboard **)** |
+-------------------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------+
| void | :ref:`set_var<class_Blackboard_method_set_var>` **(** StringName var_name, Variant value **)** |
+-------------------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------+
| :ref:`Blackboard<class_Blackboard>` | :ref:`top<class_Blackboard_method_top>` **(** **)** |const| |
+-------------------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------+
| void | :ref:`unbind_var<class_Blackboard_method_unbind_var>` **(** StringName var_name **)** |
+-------------------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------+
+-------------------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------+
| void | :ref:`bind_var_to_property<class_Blackboard_method_bind_var_to_property>` **(** StringName var_name, Object object, StringName property **)** |
+-------------------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------+
| void | :ref:`erase_var<class_Blackboard_method_erase_var>` **(** StringName var_name **)** |
+-------------------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------+
| :ref:`Blackboard<class_Blackboard>` | :ref:`get_parent<class_Blackboard_method_get_parent>` **(** **)** |const| |
+-------------------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Variant | :ref:`get_var<class_Blackboard_method_get_var>` **(** StringName var_name, Variant default=null, bool complain=true **)** |const| |
+-------------------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------+
| bool | :ref:`has_var<class_Blackboard_method_has_var>` **(** StringName var_name **)** |const| |
+-------------------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------+
| void | :ref:`link_var<class_Blackboard_method_link_var>` **(** StringName var_name, :ref:`Blackboard<class_Blackboard>` target_blackboard, StringName target_var **)** |
+-------------------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------+
| void | :ref:`set_parent<class_Blackboard_method_set_parent>` **(** :ref:`Blackboard<class_Blackboard>` blackboard **)** |
+-------------------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------+
| void | :ref:`set_var<class_Blackboard_method_set_var>` **(** StringName var_name, Variant value **)** |
+-------------------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------+
| :ref:`Blackboard<class_Blackboard>` | :ref:`top<class_Blackboard_method_top>` **(** **)** |const| |
+-------------------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------+
| void | :ref:`unbind_var<class_Blackboard_method_unbind_var>` **(** StringName var_name **)** |
+-------------------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------+
.. rst-class:: classref-section-separator
@ -122,6 +124,22 @@ Returns ``true`` if the Blackboard contains the ``var_name`` variable, including
----
.. _class_Blackboard_method_link_var:
.. rst-class:: classref-method
void **link_var** **(** StringName var_name, :ref:`Blackboard<class_Blackboard>` target_blackboard, StringName target_var **)**
Links a variable to another Blackboard variable. If a variable is linked to another variable, their state will always be identical, and any change to one will be reflected in the other. You can use this method to link a variable in the current scope to a variable in another scope, or in another Blackboard instance.
A variable can only be linked to one other variable. Calling this method again will overwrite the previous link. However, it is possible to link to the same variable from multiple different variables.
.. rst-class:: classref-item-separator
----
.. _class_Blackboard_method_set_parent:
.. rst-class:: classref-method

View File

@ -49,6 +49,17 @@
Returns [code]true[/code] if the Blackboard contains the [param var_name] variable, including the parent scopes.
</description>
</method>
<method name="link_var">
<return type="void" />
<param index="0" name="var_name" type="StringName" />
<param index="1" name="target_blackboard" type="Blackboard" />
<param index="2" name="target_var" type="StringName" />
<description>
Links a variable to another Blackboard variable. If a variable is linked to another variable, their state will always be identical, and any change to one will be reflected in the other. You can use this method to link a variable in the current scope to a variable in another scope, or in another Blackboard instance.
A variable can only be linked to one other variable. Calling this method again will overwrite the previous link. However, it is possible to link to the same variable from multiple different variables.
</description>
</method>
<method name="set_parent">
<return type="void" />
<param index="0" name="blackboard" type="Blackboard" />

View File

@ -1,7 +1,7 @@
/**
* editor_property_bb_param.cpp
* =============================================================================
* Copyright 2021-2023 Serhii Snitsaruk
* Copyright 2021-2024 Serhii Snitsaruk
*
* Use of this source code is governed by an MIT-style
* license that can be found in the LICENSE file or at
@ -17,9 +17,9 @@
#include "../blackboard/bb_param/bb_param.h"
#include "../blackboard/bb_param/bb_variant.h"
#include "../util/limbo_string_names.h"
#include "editor_property_variable_name.h"
#include "mode_switch_button.h"
#include "../util/limbo_string_names.h"
#include "core/error/error_macros.h"
#include "core/io/marshalls.h"
@ -253,15 +253,18 @@ void EditorPropertyBBParam::_value_edited(const String &p_property, Variant p_va
_get_edited_param()->set_saved_value(p_value);
}
void EditorPropertyBBParam::_mode_changed() {
_get_edited_param()->set_value_source(mode_button->get_mode() == Mode::SPECIFY_VALUE ? BBParam::SAVED_VALUE : BBParam::BLACKBOARD_VAR);
update_property();
}
void EditorPropertyBBParam::_type_selected(int p_index) {
Ref<BBVariant> param = _get_edited_param();
Ref<BBParam> param = _get_edited_param();
ERR_FAIL_COND(param.is_null());
param->set_type(Variant::Type(p_index));
if (p_index == ID_BIND_VAR) {
param->set_value_source(BBParam::BLACKBOARD_VAR);
} else {
param->set_value_source(BBParam::SAVED_VALUE);
Ref<BBVariant> variant_param = param;
if (variant_param.is_valid()) {
variant_param->set_type(Variant::Type(p_index));
}
}
update_property();
}
@ -270,36 +273,27 @@ void EditorPropertyBBParam::_variable_edited(const String &p_property, Variant p
}
void EditorPropertyBBParam::update_property() {
if (mode_button->get_mode() == -1) {
if (!initialized) {
// Initialize UI -- needed after https://github.com/godotengine/godot/commit/db7175458a0532f1efe733f303ad2b55a02a52a5
_notification(NOTIFICATION_THEME_CHANGED);
}
Ref<BBParam> param = _get_edited_param();
bool is_variant_param = param->is_class_ptr(BBVariant::get_class_ptr_static());
if (param->get_value_source() == BBParam::BLACKBOARD_VAR) {
_remove_value_editor();
variable_editor->set_object_and_property(param.ptr(), SNAME("variable"));
variable_editor->update_property();
variable_editor->show();
mode_button->call_deferred(SNAME("set_mode"), Mode::BIND_VAR, true);
type_choice->hide();
bottom_container->hide();
type_choice->set_icon(get_editor_theme_icon(SNAME("BTSetVar")));
} else {
_create_value_editor(param->get_type());
variable_editor->hide();
value_editor->show();
value_editor->set_object_and_property(param.ptr(), SNAME("saved_value"));
value_editor->update_property();
mode_button->call_deferred(SNAME("set_mode"), Mode::SPECIFY_VALUE, true);
type_choice->set_visible(is_variant_param);
}
if (is_variant_param) {
Variant::Type t = Variant::Type(param->get_type());
String type_name = Variant::get_type_name(t);
type_choice->set_icon(get_editor_theme_icon(type_name));
type_choice->set_icon(get_editor_theme_icon(Variant::get_type_name(param->get_type())));
}
}
@ -324,17 +318,14 @@ void EditorPropertyBBParam::_notification(int p_what) {
type_choice->set_icon(get_editor_theme_icon(type));
}
// Initialize mode button.
mode_button->clear();
mode_button->add_mode(Mode::SPECIFY_VALUE, get_editor_theme_icon(SNAME("LimboSpecifyValue")), TTR("Mode: Set a specific value.\nClick to switch modes."));
mode_button->add_mode(Mode::BIND_VAR, get_editor_theme_icon(SNAME("BTSetVar")), TTR("Mode: Use a blackboard variable.\nClick to switch modes."));
mode_button->set_mode(_get_edited_param()->get_value_source() == BBParam::BLACKBOARD_VAR ? Mode::BIND_VAR : Mode::SPECIFY_VALUE, true);
bool is_variant_param = _get_edited_param()->is_class_ptr(BBVariant::get_class_ptr_static());
// Initialize type choice.
PopupMenu *type_menu = type_choice->get_popup();
type_menu->clear();
type_menu->add_icon_item(get_editor_theme_icon(SNAME("BTSetVar")), TTR("Blackboard Variable"), ID_BIND_VAR);
type_menu->add_separator();
Ref<BBParam> param = _get_edited_param();
bool is_variant_param = param->is_class_ptr(BBVariant::get_class_ptr_static());
if (is_variant_param) {
// Initialize type choice.
PopupMenu *type_menu = type_choice->get_popup();
type_menu->clear();
for (int i = 0; i < Variant::VARIANT_MAX; i++) {
if (i == Variant::RID || i == Variant::CALLABLE || i == Variant::SIGNAL) {
continue;
@ -343,8 +334,11 @@ void EditorPropertyBBParam::_notification(int p_what) {
type_menu->add_icon_item(get_editor_theme_icon(type), type, i);
}
} else { // Not a variant param.
type_choice->hide();
String type = Variant::get_type_name(param->get_type());
type_menu->add_icon_item(get_editor_theme_icon(type), type, param->get_type());
}
initialized = true;
} break;
}
}
@ -358,11 +352,6 @@ EditorPropertyBBParam::EditorPropertyBBParam() {
bottom_container->set_theme_type_variation("MarginContainer4px");
add_child(bottom_container);
mode_button = memnew(ModeSwitchButton);
hbox->add_child(mode_button);
mode_button->set_focus_mode(FOCUS_NONE);
mode_button->connect(LW_NAME(mode_changed), callable_mp(this, &EditorPropertyBBParam::_mode_changed));
type_choice = memnew(MenuButton);
hbox->add_child(type_choice);
type_choice->get_popup()->connect(LW_NAME(id_pressed), callable_mp(this, &EditorPropertyBBParam::_type_selected));

View File

@ -1,7 +1,7 @@
/**
* editor_property_bb_param.h
* =============================================================================
* Copyright 2021-2023 Serhii Snitsaruk
* Copyright 2021-2024 Serhii Snitsaruk
*
* Use of this source code is governed by an MIT-style
* license that can be found in the LICENSE file or at
@ -32,19 +32,16 @@ class EditorPropertyBBParam : public EditorProperty {
GDCLASS(EditorPropertyBBParam, EditorProperty);
private:
enum Mode {
SPECIFY_VALUE,
BIND_VAR,
};
const int ID_BIND_VAR = 1000;
bool initialized = false;
StringName param_type;
PropertyHint property_hint = PROPERTY_HINT_NONE;
Mode mode = Mode::SPECIFY_VALUE;
HBoxContainer *hbox = nullptr;
MarginContainer *bottom_container = nullptr;
HBoxContainer *editor_hbox = nullptr;
ModeSwitchButton *mode_button = nullptr;
EditorProperty *value_editor = nullptr;
EditorPropertyVariableName *variable_editor = nullptr;
MenuButton *type_choice = nullptr;
@ -56,7 +53,6 @@ private:
void _value_edited(const String &p_property, Variant p_value, const String &p_name = "", bool p_changing = false);
void _variable_edited(const String &p_property, Variant p_value, const String &p_name = "", bool p_changing = false);
void _mode_changed();
void _type_selected(int p_index);
protected:

View File

@ -215,7 +215,9 @@ void LimboAIEditor::_load_bt(String p_path) {
ERR_FAIL_COND_MSG(p_path.is_empty(), "Empty p_path");
Ref<BehaviorTree> bt = RESOURCE_LOAD(p_path, "BehaviorTree");
ERR_FAIL_COND(!bt.is_valid());
if (bt->get_blackboard_plan().is_null()) {
bt->set_blackboard_plan(memnew(BlackboardPlan));
}
if (history.find(bt) != -1) {
history.erase(bt);
history.push_back(bt);

View File

@ -82,7 +82,6 @@ LimboExtractSubtree = "res://addons/limboai/icons/LimboExtractSubtree.svg"
LimboHSM = "res://addons/limboai/icons/LimboHSM.svg"
LimboPercent = "res://addons/limboai/icons/LimboPercent.svg"
LimboSelectAll = "res://addons/limboai/icons/LimboSelectAll.svg"
LimboSpecifyValue = "res://addons/limboai/icons/LimboSpecifyValue.svg"
LimboState = "res://addons/limboai/icons/LimboState.svg"
LimboVarAdd = "res://addons/limboai/icons/LimboVarAdd.svg"
LimboVarExists = "res://addons/limboai/icons/LimboVarExists.svg"

View File

@ -11,16 +11,6 @@
#include "limbo_hsm.h"
#ifdef LIMBOAI_MODULE
#include "core/config/engine.h"
#include "core/error/error_macros.h"
#include "core/object/class_db.h"
#include "core/object/object.h"
#include "core/typedefs.h"
#include "core/variant/callable.h"
#include "core/variant/variant.h"
#endif // LIMBOAI_MODULE
VARIANT_ENUM_CAST(LimboHSM::UpdateMode);
void LimboHSM::set_active(bool p_active) {

View File

@ -14,18 +14,20 @@
#include "../util/limbo_compat.h"
#ifdef LIMBOAI_MODULE
#include "core/error/error_macros.h"
#include "core/object/class_db.h"
#include "core/object/object.h"
#include "core/typedefs.h"
#include "core/variant/array.h"
#include "core/variant/callable.h"
#include "core/variant/variant.h"
#endif // LIMBOAI_MODULE
#ifdef LIMBOAI_GDEXTENSION
#include <godot_cpp/classes/engine.hpp>
#endif
void LimboState::set_blackboard_plan(const Ref<BlackboardPlan> &p_plan) {
blackboard_plan = p_plan;
_update_blackboard_plan();
}
void LimboState::_update_blackboard_plan() {
}
LimboState *LimboState::get_root() const {
const LimboState *state = this;
while (state->get_parent() && IS_CLASS(state->get_parent(), LimboState)) {
@ -159,6 +161,11 @@ void LimboState::clear_guard() {
void LimboState::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_READY: {
if (Engine::get_singleton()->is_editor_hint()) {
_update_blackboard_plan();
}
} break;
case NOTIFICATION_EXIT_TREE: {
if (active) {
_exit();

View File

@ -19,13 +19,6 @@
#include "../util/limbo_string_names.h"
#ifdef LIMBOAI_MODULE
#include "core/object/gdvirtual.gen.inc"
#include "core/object/object.h"
#include "core/string/string_name.h"
#include "core/string/ustring.h"
#include "core/templates/hash_map.h"
#include "core/variant/callable.h"
#include "core/variant/variant.h"
#include "scene/main/node.h"
#endif // LIMBOAI_MODULE
@ -58,6 +51,7 @@ protected:
virtual bool _dispatch(const StringName &p_event, const Variant &p_cargo = Variant());
virtual bool _should_use_new_scope() const { return blackboard_plan.is_valid() || is_root(); }
virtual void _update_blackboard_plan();
virtual void _setup();
virtual void _enter();
@ -72,7 +66,7 @@ protected:
#endif // LIMBOAI_MODULE
public:
void set_blackboard_plan(const Ref<BlackboardPlan> &p_plan) { blackboard_plan = p_plan; }
void set_blackboard_plan(const Ref<BlackboardPlan> &p_plan);
Ref<BlackboardPlan> get_blackboard_plan() const { return blackboard_plan; }
Ref<Blackboard> get_blackboard() const { return blackboard; }

View File

@ -1 +0,0 @@
<svg clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="2" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><clipPath id="a"><path clip-rule="evenodd" d="m0 0h16v16h-16z"/></clipPath><path d="m0 0h16v16h-16z" fill="none"/><g clip-path="url(#a)"><path d="m0 0h16v16h-16z" fill="none"/><g fill="#e0e0e0"><path d="m15.504 3.051v9.899c0 1.408-1.143 2.55-2.551 2.55h-9.899c-1.408 0-2.551-1.142-2.551-2.55v-9.899c0-1.408 1.143-2.551 2.551-2.551h9.899c1.408 0 2.551 1.143 2.551 2.551zm-1.001 0c0-.857-.695-1.551-1.55-1.551h-9.899c-.857 0-1.55.694-1.55 1.551v9.899c0 .855.693 1.55 1.55 1.55h9.899c.855 0 1.55-.695 1.55-1.55z"/><path d="m5.002 7.272c-.344 0-.652-.085-.924-.255s-.486-.417-.642-.741-.234-.716-.234-1.176.078-.852.234-1.176.37-.571.642-.741.58-.255.924-.255c.348 0 .657.085.927.255s.483.417.639.741.234.716.234 1.176-.078.852-.234 1.176-.369.571-.639.741-.579.255-.927.255zm0-.822c.164 0 .307-.046.429-.138s.218-.238.288-.438.105-.458.105-.774-.035-.574-.105-.774-.166-.346-.288-.438-.265-.138-.429-.138c-.16 0-.301.046-.423.138s-.218.238-.288.438-.105.458-.105.774.035.574.105.774.166.346.288.438.263.138.423.138z" fill-rule="nonzero"/><path d="m10.485 3.78h-.84v-.78h1.812v4.2h-.972z"/><path d="m4.936 9.58h-.84v-.78h1.812v4.2h-.972z"/><path d="m10.998 13.072c-.344 0-.652-.085-.924-.255s-.486-.417-.642-.741-.234-.716-.234-1.176.078-.852.234-1.176.37-.571.642-.741.58-.255.924-.255c.348 0 .657.085.927.255s.483.417.639.741.234.716.234 1.176-.078.852-.234 1.176-.369.571-.639.741-.579.255-.927.255zm0-.822c.164 0 .307-.046.429-.138s.218-.238.288-.438.105-.458.105-.774-.035-.574-.105-.774-.166-.346-.288-.438-.265-.138-.429-.138c-.16 0-.301.046-.423.138s-.218.238-.288.438-.105.458-.105.774.035.574.105.774.166.346.288.438.263.138.423.138z" fill-rule="nonzero"/></g></g></svg>

Before

Width:  |  Height:  |  Size: 1.8 KiB