From cb43179770013025b03afa9376f5c1ceeca68889 Mon Sep 17 00:00:00 2001 From: Sara Date: Fri, 12 Dec 2025 22:28:04 +0100 Subject: [PATCH] feat: selection tracking --- modules/terrain_editor/layer_editor.cpp | 51 ++++++++++++++++++ modules/terrain_editor/layer_editor.h | 24 +++++++++ .../terrain_editor/primitive_layer_list.cpp | 21 ++++++-- modules/terrain_editor/primitive_layer_list.h | 11 ++-- modules/terrain_editor/register_types.cpp | 2 + .../terrain_editor/terrain_mesh_editor.cpp | 15 +++++- modules/terrain_editor/terrain_mesh_editor.h | 8 ++- project/scenes/editor.tscn | 14 ++--- project/ui/editor_elements/float_editor.tscn | 21 ++++++++ .../point_primitive_inspector.tscn | 23 ++++++++ test-terrains/highland.terrain.res | Bin 0 -> 2197 bytes test-terrains/my_terrain.terrain.res | Bin 0 -> 2094 bytes test-terrains/my_terrain.terrain.tres | 47 ++++++++++++++++ test-terrains/test.terrain.res | Bin 0 -> 2827 bytes test-terrains/valley.terrain.res | Bin 0 -> 2462 bytes 15 files changed, 217 insertions(+), 20 deletions(-) create mode 100644 modules/terrain_editor/layer_editor.cpp create mode 100644 modules/terrain_editor/layer_editor.h create mode 100644 project/ui/editor_elements/float_editor.tscn create mode 100644 project/ui/primitive_inspectors/point_primitive_inspector.tscn create mode 100644 test-terrains/highland.terrain.res create mode 100644 test-terrains/my_terrain.terrain.res create mode 100644 test-terrains/my_terrain.terrain.tres create mode 100644 test-terrains/test.terrain.res create mode 100644 test-terrains/valley.terrain.res diff --git a/modules/terrain_editor/layer_editor.cpp b/modules/terrain_editor/layer_editor.cpp new file mode 100644 index 00000000..c151bc1e --- /dev/null +++ b/modules/terrain_editor/layer_editor.cpp @@ -0,0 +1,51 @@ +#include "layer_editor.h" +#include "scene/resources/packed_scene.h" + +void LayerEditor::_bind_methods() { + String hint_string{ vformat("StringName;%s/%s:PackedScene", Variant::OBJECT, PROPERTY_HINT_RESOURCE_TYPE) }; + BIND_HPROPERTY(Variant::DICTIONARY, inspectors, PROPERTY_HINT_DICTIONARY_TYPE, hint_string); + BIND_HPROPERTY(Variant::OBJECT, terrain, PROPERTY_HINT_NODE_TYPE, "TerrainMeshEditor"); +} + +void LayerEditor::deselect_current() { + if (this->current_inspector) { + this->current_inspector->queue_free(); + } + this->current_inspector = nullptr; +} + +void LayerEditor::select(Ref primitive) { + deselect_current(); + if (primitive.is_valid() && this->inspectors.has(primitive->get_class())) { + this->current_inspector = this->inspectors.get(primitive->get_class())->instantiate(); + add_child(this->current_inspector); + } +} + +void LayerEditor::set_inspectors(Dictionary dict) { + this->inspectors.clear(); + for (KeyValue kvp : dict) { + StringName name{ kvp.key }; + Ref value{ kvp.value }; + this->inspectors.insert(name, value); + } +} + +Dictionary LayerEditor::get_inspectors() const { + Dictionary r; + for (KeyValue> const &kvp : this->inspectors) { + r[kvp.key] = kvp.value; + } + return r; +} + +void LayerEditor::set_terrain(TerrainMeshEditor *editor) { + this->terrain = editor; + if (editor && !Engine::get_singleton()->is_editor_hint()) { + editor->connect(TerrainMeshEditor::sig_selection_changed, callable_mp(this, &self_type::select)); + } +} + +TerrainMeshEditor *LayerEditor::get_terrain() const { + return this->terrain; +} diff --git a/modules/terrain_editor/layer_editor.h b/modules/terrain_editor/layer_editor.h new file mode 100644 index 00000000..860e0649 --- /dev/null +++ b/modules/terrain_editor/layer_editor.h @@ -0,0 +1,24 @@ +#pragma once + +#include "scene/gui/tab_container.h" +#include "terrain_editor/terrain_mesh_editor.h" +#include "terrain_primitive.h" + +class LayerEditor : public TabContainer { + GDCLASS(LayerEditor, TabContainer); + static void _bind_methods(); + +protected: +public: + void deselect_current(); + void select(Ref primitive); + void set_inspectors(Dictionary dict); + Dictionary get_inspectors() const; + void set_terrain(TerrainMeshEditor *terrain); + TerrainMeshEditor *get_terrain() const; + +private: + HashMap> inspectors{}; + Node *current_inspector{ nullptr }; + TerrainMeshEditor *terrain{ nullptr }; +}; diff --git a/modules/terrain_editor/primitive_layer_list.cpp b/modules/terrain_editor/primitive_layer_list.cpp index 1d02d916..97ae5db7 100644 --- a/modules/terrain_editor/primitive_layer_list.cpp +++ b/modules/terrain_editor/primitive_layer_list.cpp @@ -9,7 +9,7 @@ #include void PrimitiveLayerList::_bind_methods() { - BIND_HPROPERTY(Variant::OBJECT, terrain, PROPERTY_HINT_NODE_TYPE, "TerrainMeshGenerator"); + BIND_HPROPERTY(Variant::OBJECT, terrain, PROPERTY_HINT_NODE_TYPE, "TerrainMeshEditor"); BIND_HPROPERTY(Variant::DICTIONARY, icons, PROPERTY_HINT_DICTIONARY_TYPE, "StringName;Texture2D"); } @@ -27,15 +27,15 @@ void PrimitiveLayerList::generate_subtree(size_t idx, Ref prim base->set_text(NAME_COLUMN, prim->get_name().is_empty() ? prim->get_class() : prim->get_name()); base->set_editable(NAME_COLUMN, true); base->set_expand_right(NAME_COLUMN, true); - this->subtrees.insert(prim, base); } void PrimitiveLayerList::regenerate_tree(Array array) { get_root()->clear_children(); - this->subtrees.clear(); + this->primitives.clear(); size_t i{ 0 }; for (Variant var : array) { Ref prim{ var }; + this->primitives.push_back(prim); if (prim.is_valid()) { generate_subtree(i, prim, get_root()); ++i; @@ -88,6 +88,16 @@ void PrimitiveLayerList::item_edited() { } } +void PrimitiveLayerList::item_selected() { + TreeItem *item{ get_selected() }; + int64_t const idx{ item->get_text(IDX_COLUMN).to_int() }; + if (idx < this->primitives.size() && idx >= 0) { + this->terrain->set_current_selected(this->primitives[idx]); + } else { + this->terrain->set_current_selected(nullptr); + } +} + void PrimitiveLayerList::_notification(int what) { if (Engine::get_singleton()->is_editor_hint()) { return; @@ -97,6 +107,7 @@ void PrimitiveLayerList::_notification(int what) { return; case NOTIFICATION_READY: connect("item_edited", callable_mp(this, &self_type::item_edited)); + connect("item_selected", callable_mp(this, &self_type::item_selected)); create_item(); set_columns(COLUMN_MAX); set_hide_root(true); @@ -126,10 +137,10 @@ Dictionary PrimitiveLayerList::get_icons() const { return dict; } -void PrimitiveLayerList::set_terrain(TerrainMeshGenerator *terrain) { +void PrimitiveLayerList::set_terrain(TerrainMeshEditor *terrain) { this->terrain = terrain; } -TerrainMeshGenerator *PrimitiveLayerList::get_terrain() const { +TerrainMeshEditor *PrimitiveLayerList::get_terrain() const { return this->terrain; } diff --git a/modules/terrain_editor/primitive_layer_list.h b/modules/terrain_editor/primitive_layer_list.h index 159c079e..7c0c756f 100644 --- a/modules/terrain_editor/primitive_layer_list.h +++ b/modules/terrain_editor/primitive_layer_list.h @@ -2,7 +2,7 @@ #include "core/templates/hash_map.h" #include "scene/gui/tree.h" -#include "terrain_editor/terrain_mesh_generator.h" +#include "terrain_editor/terrain_mesh_editor.h" #include "terrain_editor/terrain_primitive.h" class PrimitiveLayerList : public Tree { @@ -19,6 +19,7 @@ class PrimitiveLayerList : public Tree { void switch_index(size_t from, size_t to); void layer_renamed(TreeItem *item); void item_edited(); + void item_selected(); protected: void _notification(int what); @@ -26,12 +27,12 @@ protected: public: void set_icons(Dictionary dict); Dictionary get_icons() const; - void set_terrain(TerrainMeshGenerator *generator); - TerrainMeshGenerator *get_terrain() const; + void set_terrain(TerrainMeshEditor *generator); + TerrainMeshEditor *get_terrain() const; private: HashMap> icons{}; - TerrainMeshGenerator *terrain{}; + TerrainMeshEditor *terrain{}; - HashMap, TreeItem *> subtrees{}; + Vector> primitives{}; }; diff --git a/modules/terrain_editor/register_types.cpp b/modules/terrain_editor/register_types.cpp index e6f65dae..cc1f0e9b 100644 --- a/modules/terrain_editor/register_types.cpp +++ b/modules/terrain_editor/register_types.cpp @@ -4,6 +4,7 @@ #include "core/object/class_db.h" #include "terrain_editor/add_primitive_button.h" #include "terrain_editor/edit_history.h" +#include "terrain_editor/layer_editor.h" #include "terrain_editor/point_primitive_node.h" #include "terrain_editor/primitive_layer_list.h" #include "terrain_editor/terrain_chunk.h" @@ -29,6 +30,7 @@ void initialize_terrain_editor_module(ModuleInitializationLevel p_level) { Engine::get_singleton()->add_singleton(Engine::Singleton("EditHistory", (EditHistory::singleton_instance = memnew(EditHistory)), "EditHistory")); ClassDB::register_class(); ClassDB::register_class(); + ClassDB::register_class(); } void uninitialize_terrain_editor_module(ModuleInitializationLevel p_level) { diff --git a/modules/terrain_editor/terrain_mesh_editor.cpp b/modules/terrain_editor/terrain_mesh_editor.cpp index 9e06762a..cb743ac5 100644 --- a/modules/terrain_editor/terrain_mesh_editor.cpp +++ b/modules/terrain_editor/terrain_mesh_editor.cpp @@ -1,6 +1,5 @@ #include "terrain_mesh_editor.h" #include "core/input/input_event.h" -#include "core/io/file_access.h" #include "core/io/resource_loader.h" #include "core/io/resource_saver.h" #include "core/os/keyboard.h" @@ -36,9 +35,13 @@ Array SaveData::get_primitives() const { return this->primitives; } +String const TerrainMeshEditor::sig_selection_changed{ "selection_changed" }; + void TerrainMeshEditor::_bind_methods() { BIND_HPROPERTY(Variant::OBJECT, point_primitive_object, PROPERTY_HINT_RESOURCE_TYPE, "PackedScene"); ClassDB::bind_method(D_METHOD("save_data"), &self_type::save_data); + BIND_HPROPERTY(Variant::OBJECT, current_selected, PROPERTY_HINT_RESOURCE_TYPE, "TerrainPrimitive"); + ADD_SIGNAL(MethodInfo(sig_selection_changed, PropertyInfo(Variant::OBJECT, "new_selection", PROPERTY_HINT_RESOURCE_TYPE, "TerrainPrimitive"))); } void TerrainMeshEditor::ready() { @@ -54,7 +57,6 @@ void TerrainMeshEditor::ready() { } void TerrainMeshEditor::on_primitive_list_changed(Array primitives) { - this->out_of_date = true; for (Node3D *existing : this->primitive_nodes) { existing->queue_free(); } @@ -162,6 +164,15 @@ void TerrainMeshEditor::save_data() { } } +void TerrainMeshEditor::set_current_selected(Ref primitive) { + this->current_selected = primitive; + emit_signal(sig_selection_changed, primitive); +} + +Ref TerrainMeshEditor::get_current_selected() const { + return this->current_selected; +} + void TerrainMeshEditor::set_point_primitive_object(Ref scene) { this->point_primitive_object = scene; } diff --git a/modules/terrain_editor/terrain_mesh_editor.h b/modules/terrain_editor/terrain_mesh_editor.h index 34cb3e29..19f6b200 100644 --- a/modules/terrain_editor/terrain_mesh_editor.h +++ b/modules/terrain_editor/terrain_mesh_editor.h @@ -3,6 +3,7 @@ #include "core/io/resource.h" #include "scene/gui/file_dialog.h" #include "terrain_editor/terrain_mesh_generator.h" +#include "terrain_editor/terrain_primitive.h" class SaveData : public Resource { GDCLASS(SaveData, Resource); @@ -34,13 +35,18 @@ protected: public: void save_data(); + void set_current_selected(Ref); + Ref get_current_selected() const; void set_point_primitive_object(Ref scene); Ref get_point_primitive_object() const; private: Ref data{ memnew(SaveData) }; + Ref current_selected{}; FileDialog *file_dialog{}; - bool out_of_date{ false }; Vector primitive_nodes{}; Ref point_primitive_object{}; + +public: + static String const sig_selection_changed; }; diff --git a/project/scenes/editor.tscn b/project/scenes/editor.tscn index a6d218df..bf52863b 100644 --- a/project/scenes/editor.tscn +++ b/project/scenes/editor.tscn @@ -7,6 +7,7 @@ [ext_resource type="Texture2D" uid="uid://bb0mnjwx58nt3" path="res://assets/icons/plus.svg" id="4_q68jb"] [ext_resource type="Texture2D" uid="uid://bl3gn6qruuy8w" path="res://assets/icons/plane.svg" id="4_xg7d5"] [ext_resource type="Texture2D" uid="uid://d1te42w7wpkrx" path="res://assets/icons/noise.svg" id="5_eqbpn"] +[ext_resource type="PackedScene" uid="uid://bsvvhue5x4rb" path="res://ui/primitive_inspectors/point_primitive_inspector.tscn" id="8_5tm2q"] [sub_resource type="PointPrimitive" id="PointPrimitive_5tm2q"] @@ -78,7 +79,7 @@ script/source = "extends Camera3D var pan_speed = .001 var rotate_speed := .0015 -var zoom_speed := 1.0 +var zoom_speed := 2. var rotating := false var panning := false var distance := 200 @@ -223,13 +224,12 @@ icons = { &"PointPrimitive": ExtResource("4_5lcyj") } -[node name="Inspector" type="TabContainer" parent="LeftPanel/VBoxContainer" unique_id=240272030] +[node name="Inspector" type="LayerEditor" parent="LeftPanel/VBoxContainer" unique_id=833878161 node_paths=PackedStringArray("terrain")] layout_mode = 2 size_flags_vertical = 3 -current_tab = 0 - -[node name="Inspector" type="VBoxContainer" parent="LeftPanel/VBoxContainer/Inspector" unique_id=1225013744] -layout_mode = 2 -metadata/_tab_index = 0 +inspectors = { +&"PointPrimitive": ExtResource("8_5tm2q") +} +terrain = NodePath("../../../TerrainMeshEditor") [connection signal="primitives_changed" from="TerrainMeshEditor" to="TerrainMeshEditor" method="_on_primitives_changed"] diff --git a/project/ui/editor_elements/float_editor.tscn b/project/ui/editor_elements/float_editor.tscn new file mode 100644 index 00000000..462e77fc --- /dev/null +++ b/project/ui/editor_elements/float_editor.tscn @@ -0,0 +1,21 @@ +[gd_scene format=3 uid="uid://cwby0in0f2wi2"] + +[node name="FloatEditor" type="HBoxContainer" unique_id=1730998858] +offset_right = 318.0 +offset_bottom = 31.0 + +[node name="HSlider" type="HSlider" parent="." unique_id=163542019] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 1 +max_value = 300.0 +step = 0.01 +value = 200.0 + +[node name="SpinBox" type="SpinBox" parent="." unique_id=580598896] +layout_mode = 2 +min_value = -10000.0 +max_value = 100000.0 +step = 0.01 +value = 10.0 +allow_greater = true diff --git a/project/ui/primitive_inspectors/point_primitive_inspector.tscn b/project/ui/primitive_inspectors/point_primitive_inspector.tscn new file mode 100644 index 00000000..3136791c --- /dev/null +++ b/project/ui/primitive_inspectors/point_primitive_inspector.tscn @@ -0,0 +1,23 @@ +[gd_scene format=3 uid="uid://bsvvhue5x4rb"] + +[ext_resource type="PackedScene" uid="uid://cwby0in0f2wi2" path="res://ui/editor_elements/float_editor.tscn" id="1_qb00w"] + +[node name="PointPrimitive" type="MarginContainer" unique_id=905749607] +offset_right = 302.0 +offset_bottom = 230.0 +theme_override_constants/margin_left = 5 +theme_override_constants/margin_top = 5 +theme_override_constants/margin_right = 5 +theme_override_constants/margin_bottom = 5 +metadata/_tab_index = 0 + +[node name="VBoxContainer" type="VBoxContainer" parent="." unique_id=236606520] +layout_mode = 2 + +[node name="HeightLabel" type="Label" parent="VBoxContainer" unique_id=1478707845] +layout_mode = 2 +size_flags_horizontal = 3 +text = "Height" + +[node name="FloatEditor" parent="VBoxContainer" unique_id=1730998858 instance=ExtResource("1_qb00w")] +layout_mode = 2 diff --git a/test-terrains/highland.terrain.res b/test-terrains/highland.terrain.res new file mode 100644 index 0000000000000000000000000000000000000000..ba3c005a940f5e5b53ea8f87e1a5b1b1f86d9dfd GIT binary patch literal 2197 zcma)8J#5oJ6h0{Bw=E#r0xgsR69v&iRTNc~+6Kf_p(;_ddQ4`Vw+@EBn!W}T@!R7>zWT78Pn^~@JQ`b_y71~TNGLY2?>M?Q%Bt@4A z$MSrKk-qd*Qc-4PAH2g(#iRxtx_%G~D~y*bO6x;g@<3@%!sj8gR2__ZVQ0vgLk$9#ffQDv}~j zVh|LL?*S%joyx#sfAal&Z3o0`Wy$LAPnBe#qX~2YK{Cz46*PA<4qc>Tlgg`amB3QPR*GivsgJ;S%})EkxP+IMT!K8VS9(gE4C-d z>+TNY$i4Ut!|LEr2VP3-Iv}iF%TDxVU=;}G4{#vFB4B}YS-^zGN! z$YFRix0c``iG=6J)Z;I2E^L3!W3f>d6$Upjf8_1^9~LmsE}$-MUVVL+AK4m8-Ta=H zshe-#Z|~I2rC%R6N5>~pH&KzKZl138ZcxX3` zl)fIYESUbo&1eo!QoJ&dvca1NOq-(#ZX@K`0(Kg2i`_sd!_PI*EgCof21mHqHS~n5 z0p#Z#eyu1+kH*7z@^1Rm5!Co%78JHP11jxLXHb}e0d?`g@H`GhoYuX0ykZP*0HVGH zl*s{~h&u}kbI)#V9{scrrjTdBJc+nrwl&@PSgUhDV?gNJbOhLxM+`=e_J&7%hR41b S9s?O3gBc#nVn7t&56NFf#^B=s literal 0 HcmV?d00001 diff --git a/test-terrains/my_terrain.terrain.res b/test-terrains/my_terrain.terrain.res new file mode 100644 index 0000000000000000000000000000000000000000..385bd3331b1e2e0d6eccda9ae2683ac7c576b561 GIT binary patch literal 2094 zcmb7G%We}f6gB1jrlKI;6xbp{8-Wn2=u@z1QPh6G$eFoGT+P^1+tUzM{R2Lr8+M3~ zK$R*Xb;}PR^#fqX8t(OslbMvXV96cNeVlvlW1Oz8tuC@+oxta*VxPt58sCH^Zh4_* z>^Po&7ppJ)hr=VZxq(}QQJD@Qh*ikrz^cH6Ac0*f>m*NrokvMC1{np3ihy0{ziKYq zz)s^kdRowqu!&Wg3p*g%9qaTa)FQPGNh)C=ust+7iJk@^V(0MC(y)<%413N&wOR%& zyXX`=w8W5Hu9O4{#hYN*)sigcVJ5j2wpa0Ob0OKSl3mwvgD5aogKXOlyIsUUh+)m3f;WeJTDnxiJ0q07ocBlQOQju!@5)S5>zeE)C2zOE z5zCvGzYmh2XbD&z;cVA}RKuo#Zh)1kV{cN9HD)yaO>Q6-IG!|&sTPT_f_hJjrspnh zH^?t*wT2SXHoQ%czT1eNv3>fWa2;aU>5;bx~WzwSm z6r+ZbWN&+Has0>6U9uoqyuK6s<|d13GCr-i?R$9-_R6)RKV z?q8xuG(PNRGjvtaolLt-sX7ytdQBt@DRH6=`C~JhGDLg^JT8YvBY)GM>@!rEX$mzgL#Hq zvvkqZdH?;ytVbaI5I-{U<3ms@5Q!|w2~wJrF6qkzX?V(z)lqj*JtUdE(KwP(sWp@H v4^UhX-SJ43A888x5cfUY6wdb%kNq>n@N%N*&(uMGCUAeIuKHlB_@BZb%rDF! literal 0 HcmV?d00001 diff --git a/test-terrains/my_terrain.terrain.tres b/test-terrains/my_terrain.terrain.tres new file mode 100644 index 00000000..3b9b9aef --- /dev/null +++ b/test-terrains/my_terrain.terrain.tres @@ -0,0 +1,47 @@ +[gd_resource type="SaveData" load_steps=10 format=3] + +[sub_resource type="PointPrimitive" id="PointPrimitive_pxqd5"] +center = Vector2(5.4302, 139.65) +slope = -0.7 +height = 67.0 + +[sub_resource type="PointPrimitive" id="PointPrimitive_ba0ut"] +center = Vector2(-81.1489, 92.763) +slope = -0.7 +height = 35.0 + +[sub_resource type="FastNoiseLite" id="FastNoiseLite_pxqd5"] +frequency = 0.02 +fractal_octaves = 3 + +[sub_resource type="NoisePrimitive" id="NoisePrimitive_ba0ut"] +blend_range = 5.0 +noise = SubResource("FastNoiseLite_pxqd5") +noise_amplitude = 20.0 + +[sub_resource type="FastNoiseLite" id="FastNoiseLite_b1cmn"] +noise_type = 0 +frequency = 0.03 +fractal_type = 2 +metadata/_preview_in_3d_space_ = true + +[sub_resource type="NoisePrimitive" id="NoisePrimitive_pxqd5"] +blend_range = 10.0 +noise = SubResource("FastNoiseLite_b1cmn") +noise_amplitude = 5.0 + +[sub_resource type="FastNoiseLite" id="FastNoiseLite_ba0ut"] +fractal_type = 2 +fractal_gain = 1.0 +fractal_weighted_strength = 0.58 + +[sub_resource type="NoisePrimitive" id="NoisePrimitive_q68jb"] +blend_mode = 1 +noise = SubResource("FastNoiseLite_ba0ut") + +[sub_resource type="PlanePrimitive" id="PlanePrimitive_pxqd5"] +blend_range = 10.0 +baseline = -1.0 + +[resource] +primitives = [SubResource("PointPrimitive_pxqd5"), SubResource("PointPrimitive_ba0ut"), SubResource("NoisePrimitive_ba0ut"), SubResource("NoisePrimitive_pxqd5"), SubResource("NoisePrimitive_q68jb"), SubResource("PlanePrimitive_pxqd5")] diff --git a/test-terrains/test.terrain.res b/test-terrains/test.terrain.res new file mode 100644 index 0000000000000000000000000000000000000000..6ee7ceef2efe4b620bc90fe1770371f66da0d83a GIT binary patch literal 2827 zcma)8O^6&t6t2XmYt&seiLC42Mj?tqX0s-nHH0KH8{#FAb@yNpp{ePvnW@-a-P>I~ zyCWj;;=zmHN%7!b%t1svNRBSd9y~Y%%uPZLB6tu)2zV5IUv*bc&m=pu1vT@!-g{qt z@29$YWp!nVB#d)rIu4e2$lK#s_vA7H2MY5Tu8H*%LV^=tS=yKoD zp_8~m2{u{QDIN&+Fs;;mp*$xDJ;5F+9%HVWf;~vjy=-lqw2>qo z_6wSg#t6e?-d$+3Y49IyxG*0am&_S&xZ(P9Ep~0ZVT&gx75i(vVPkRL^V!QdbE7#% z8*APP7LGTp^SM;BUT%oyuz9?|6a6Mm>qV)BlL4NXFdGk~(b)9!odLd=8}1BPKjsFC z{WKmh+xZaMfW`N(_fGf!7m_BQz_mFc6i5$Gmbr zTvvX`y`-W=qL0#vkLg4orS~dZ$vvjXW3U?;-s_mKnEB#~rTtq!bkUkvd$oP*N3;Os zlRtk{LrwP390T*&?{{jK|J=K6Vg9`^zhz;1H@@2~%*C&+c8_dN6)<AfD tNMa#l54#R~D}%C?v5A#YSu3NztqcNI1_>)88&*bNS{Y@qq*v%O_pUeQy}Ts1EA+uB!oOZ*?kTHjJ zfKZZbA!5R=#tZyXDP)Qqhz-3tX-uC`6;BmcbJZYf92c;pr`mI-TdX zup6n}3O`OWc5PhbxKomHhtOh4@sjsi5V)Ji6m_7tb54VEY$o z$XpKXiOm>D?s3Jj_BgO(&%lfu`9>}`?Q@}~wT~4nEjU-n3N{;zCXOXfT&t4J){eFl zC`Gz3(B_C?k$V+#zqR9tzXY+pYVBC0Ly;oiQ%5YNj`|i2)jd30ce!F|&?9&@VVFV3 zKDF#5fL`jjHDK|I?FsUsHDHqNw)SM_CV+7ycT&G53>U6lpD&Wt|2R&&RItWSzNC(q zfk1A~_gF5ZlIEuTMdA9?33CMR`G=*`%)zjFl6pUf=xI(g^8J>C7Y zURL)f2lLYp^DIon)>Qy8laYhgqraAspO$eGEn{7@jGJm1H`X%VE?UNPX>v30&+IRe CHxcFl literal 0 HcmV?d00001