From 645a54a7399538d8db90e46906d31cc60f5a7d6c Mon Sep 17 00:00:00 2001 From: Hirek193 Date: Fri, 9 Oct 2026 11:54:47 +0200 Subject: [PATCH] feat(editor): icons over the 3d view: the tools of the field of work along its left edge (the edit modes of the surroundings, the track tools, the road tools with the lanes, the terrain tools with the orthophoto), the modes of the gizmo at its top, the axes of the world as the camera sees them in its top right corner, a click on one looking along it, with the top view under them; the icons drawn by editor/icons/editor_icons.py into a png which CMake writes out as an array built into the executable; the gizmo modes and the top view left out of the toolbar; Polish translation --- CMakeLists.txt | 10 + application/editormode.cpp | 26 +- application/editormode.h | 8 + application/editormodeviewport.cpp | 398 +++++++++++++++++++++++++++++ editor/editorIcons.cpp | 176 +++++++++++++ editor/editorIcons.hpp | 61 +++++ editor/icons/editor_icons.png | Bin 0 -> 44886 bytes editor/icons/editor_icons.py | 364 ++++++++++++++++++++++++++ lang/editor_pl.po | 29 +++ 9 files changed, 1049 insertions(+), 23 deletions(-) create mode 100644 application/editormodeviewport.cpp create mode 100644 editor/editorIcons.cpp create mode 100644 editor/editorIcons.hpp create mode 100644 editor/icons/editor_icons.png create mode 100644 editor/icons/editor_icons.py diff --git a/CMakeLists.txt b/CMakeLists.txt index 300427ac..332dd832 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -185,6 +185,7 @@ set(SOURCES "editor/editorTerrainStreamer.cpp" "editor/editorOrthophoto.cpp" "editor/editorPreviews.cpp" +"editor/editorIcons.cpp" "application/editormode.cpp" "application/editormodetrack.cpp" "application/editormodetrackux.cpp" @@ -198,6 +199,7 @@ set(SOURCES "application/editormodelineside.cpp" "application/editormodevehicle.cpp" "application/editormodeselection.cpp" +"application/editormodeviewport.cpp" "application/editormodesignal.cpp" "application/editormodesweep.cpp" "application/editormodeturntable.cpp" @@ -381,6 +383,14 @@ endif() configure_file("${CMAKE_SOURCE_DIR}/version_info.h.in" "${CMAKE_BINARY_DIR}/geninc/version_info.h") +# the icons of the editor are built into the executable: the png is written out as the bytes of an array, see editor/editorIcons.cpp +set(EDITOR_ICONS "${CMAKE_SOURCE_DIR}/editor/icons/editor_icons.png") +set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS "${EDITOR_ICONS}") +file(READ "${EDITOR_ICONS}" EDITOR_ICONS_HEX HEX) +string(REGEX REPLACE "(................................)" "\\1\n" EDITOR_ICONS_HEX "${EDITOR_ICONS_HEX}") +string(REGEX REPLACE "([0-9a-f][0-9a-f])" "0x\\1," EDITOR_ICONS_HEX "${EDITOR_ICONS_HEX}") +file(CONFIGURE OUTPUT "${CMAKE_BINARY_DIR}/geninc/editor_icons_png.inc" CONTENT "${EDITOR_ICONS_HEX}\n") + set (PREFIX "") set (SUFFIX "") diff --git a/application/editormode.cpp b/application/editormode.cpp index 5f99a52a..0d2b6acc 100644 --- a/application/editormode.cpp +++ b/application/editormode.cpp @@ -1344,6 +1344,8 @@ bool editor_mode::update() // --- ImGuizmo: in-viewport transform gizmo for the selected node --- render_gizmo(); + // the tools, the gizmo modes and the axes over the 3d view + render_viewport_overlays(); render_track_inspector(); if (selected_track()) @@ -3897,29 +3899,7 @@ void editor_mode::render_toolbar() case work_area::roads: render_road_toolbar(); break; case work_area::terrain: render_terrain_toolbar(); break; } - ImGui::Separator(); - // the gizmo moves the models and the tracks; on the terrain the mouse is the brush's - if (m_gizmo_enabled && area != work_area::terrain) - { - std::pair const operations[] = { - {STR_C("Translate (Q)"), gizmo_operation::translate}, {STR_C("Rotate (W)"), gizmo_operation::rotate}, {STR_C("Scale (E)"), gizmo_operation::scale}}; - for (auto const &operation : operations) - { - if (ImGui::MenuItem(operation.first, nullptr, m_gizmo_op == operation.second)) - m_gizmo_op = operation.second; - if (ImGui::IsItemHovered()) - ImGui::SetTooltip("%s", STR_C("What the handles of the gizmo on the selection do when dragged; Ctrl held snaps the values")); - } - if (m_gizmo_op != gizmo_operation::scale && ImGui::MenuItem(STR_C("Local space (R)"), nullptr, m_gizmo_local)) - m_gizmo_local = !m_gizmo_local; - if (m_gizmo_op != gizmo_operation::scale && ImGui::IsItemHovered()) - ImGui::SetTooltip("%s", STR_C("The handles along the axes of the selected model instead of the axes of the world")); - ImGui::Separator(); - } - if (ImGui::MenuItem(STR_C("Top view, orthographic (O)"), nullptr, Global.EditorOrtho)) - toggle_ortho(); - if (ImGui::IsItemHovered()) - ImGui::SetTooltip("%s", STR_C("The view straight down without perspective, as on a map; again for the camera as it was")); + // the modes of the gizmo and the top view are over the 3d view, see render_viewport_overlays() } void editor_mode::render_edit_menu() diff --git a/application/editormode.h b/application/editormode.h index 25ee8451..edc45daa 100644 --- a/application/editormode.h +++ b/application/editormode.h @@ -385,6 +385,14 @@ class editor_mode : public application_mode, private editor_track::observer // time of day, day of the year, visibility, clouds and air temperature, as the scenery is seen in the editor void render_environment_window(); bool m_environment_open{false}; + // over the 3d view: the tools of the field of work along its left edge, the modes of the gizmo at its top, the axes of the + // world and the top view in its top right corner + void render_viewport_overlays(); + void render_viewport_tools(); + void render_viewport_gizmo(); + void render_view_axes(); + // the camera looking along an axis of the world (0: x, 1: y, 2: z) from its positive or negative side + void look_along_axis(int const Axis, bool const Positive); // origin of the scenery in PUWG 1992 the scenery file gives in its //$g line, if it does bool m_georeference_read{false}; bool m_georeference{false}; diff --git a/application/editormodeviewport.cpp b/application/editormodeviewport.cpp new file mode 100644 index 00000000..17434652 --- /dev/null +++ b/application/editormodeviewport.cpp @@ -0,0 +1,398 @@ +/* +This Source Code Form is subject to the +terms of the Mozilla Public License, v. +2.0. If a copy of the MPL was not +distributed with this file, You can +obtain one at +http://mozilla.org/MPL/2.0/. +*/ + +// over the 3d view: the tools of the field of work along its left edge, the modes of the gizmo at its top, the axes of the +// world and the top view in its top right corner + +#include "stdafx.h" +#include "application/editormode.h" +#include "application/editoruilayer.h" +#include "editor/editorIcons.hpp" +#include "rendering/renderer.h" +#include "utilities/Globals.h" +#include "utilities/translation.h" +#include "imgui/imgui.h" + +#include +#include +#include +#include +#include + +namespace +{ + +using editor_icons::icon; + +struct tool_button +{ + icon picture; + char const *label; + char const *key; // nullptr if there's none + char const *tooltip; // nullptr if the label says enough + bool chosen; + std::function action; + bool group{false}; // a gap before it, it starts another group + bool enabled{true}; +}; + +float scale() +{ + return std::max(1.0f, Global.ui_scale); +} + +// side of a button, the icon with some room around it +float button_side() +{ + return editor_icons::size() + 10.0f * scale(); +} + +// a window of no looks of its own, for the buttons over the 3d view; it takes the mouse only where it lies +bool begin_overlay(char const *Name, ImVec2 const &Position, ImVec2 const &Size) +{ + ImGui::SetNextWindowViewport(ImGui::GetMainViewport()->ID); + ImGui::SetNextWindowPos(Position); + ImGui::SetNextWindowSize(Size); + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f)); + ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f); + ImGui::PushStyleVar(ImGuiStyleVar_WindowMinSize, ImVec2(1.0f, 1.0f)); + auto const flags{ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoDocking | ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoNav | + ImGuiWindowFlags_NoBackground | ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoScrollWithMouse}; + auto const open{ImGui::Begin(Name, nullptr, flags)}; + ImGui::PopStyleVar(3); + return open; +} + +// the button with the icon at the place; the label stands in for the icon if the renderer can't show images +void icon_button(tool_button const &Button, ImVec2 const &Position, int const Id) +{ + auto const side{button_side()}; + ImGui::SetCursorScreenPos(Position); + ImGui::PushID(Id); + auto const clicked{ImGui::InvisibleButton("##tool", ImVec2(side, side))}; + ImGui::PopID(); + auto const hovered{ImGui::IsItemHovered()}; + auto *list{ImGui::GetWindowDrawList()}; + ImVec2 const end{Position.x + side, Position.y + side}; + auto const rounding{4.0f * scale()}; + auto const background{Button.chosen ? ImGui::GetColorU32(ImGuiCol_ButtonActive) : + hovered && Button.enabled ? ImGui::GetColorU32(ImGuiCol_ButtonHovered) : + IM_COL32(24, 26, 30, 200)}; + list->AddRectFilled(Position, end, background, rounding); + if (Button.chosen) + list->AddRect(Position, end, ImGui::GetColorU32(ImGuiCol_CheckMark), rounding, 0, 1.5f * scale()); + auto const color{Button.enabled ? (Button.chosen || hovered ? IM_COL32(255, 255, 255, 255) : IM_COL32(215, 220, 225, 235)) : IM_COL32(150, 150, 150, 110)}; + ImVec2 const center{Position.x + side * 0.5f, Position.y + side * 0.5f}; + if (false == editor_icons::draw(list, Button.picture, center, color)) + { + char const text[]{Button.label[0], (Button.label[0] != '\0' ? Button.label[1] : '\0'), '\0'}; + auto const size{ImGui::CalcTextSize(text)}; + list->AddText(ImVec2(center.x - size.x * 0.5f, center.y - size.y * 0.5f), color, text); + } + if (hovered) + { + std::string tip{Button.label}; + if (Button.key != nullptr) + tip += std::string{" ["} + Button.key + "]"; + if (Button.tooltip != nullptr) + tip += std::string{"\n"} + Button.tooltip; + ImGui::SetTooltip("%s", tip.c_str()); + } + if (clicked && Button.enabled && Button.action) + Button.action(); +} + +} // namespace + +void editor_mode::render_viewport_overlays() +{ + auto const width{editor_ui::view_max().x - editor_ui::view_min().x}; + auto const height{editor_ui::view_max().y - editor_ui::view_min().y}; + // a view squeezed between the docks has no room for them + if (width < 8.0f * button_side() || height < 6.0f * button_side()) + return; + render_viewport_tools(); + render_viewport_gizmo(); + render_view_axes(); +} + +void editor_mode::render_viewport_tools() +{ + // the tools of the field of work, as in the toolbar + std::vector buttons; + buttons.reserve(16); + auto const area{current_work_area()}; + switch (area) + { + case work_area::surroundings: + { + struct mode + { + icon picture; + char const *label; + char const *key; + nodebank_panel::edit_mode which; + }; + mode const modes[] = {{icon::select, STR_C("Select"), "1", nodebank_panel::MODIFY}, + {icon::insert, STR_C("Insert"), "2", nodebank_panel::ADD}, + {icon::brush, STR_C("Brush"), "3", nodebank_panel::BRUSH}, + {icon::area_fill, STR_C("Area fill"), "4", nodebank_panel::FILL}, + {icon::copy_to_bank, STR_C("Copy to bank"), "5", nodebank_panel::COPY}}; + for (auto const &entry : modes) + buttons.push_back({entry.picture, entry.label, entry.key, nullptr, ui()->mode() == entry.which, [this, which = entry.which]() { choose_edit_mode(which); }}); + break; + } + case work_area::tracks: + { + auto const vehicle{m_track_tab == track_tab::lineside && m_vehicle.open}; + auto const add = [&](track_tab const Tab, icon const Picture, bool const Group) { + auto const &modes{track_modes()}; + auto const found{std::find_if(modes.begin(), modes.end(), [&](track_mode const &Mode) { return Mode.tab == Tab; })}; + if (found == modes.end()) + return; + // a tool chosen again gives way to the select one, as in the toolbar + auto const chosen{m_track_tab == Tab && false == (Tab == track_tab::lineside && vehicle)}; + buttons.push_back({Picture, STR_C(found->label), found->key, STR_C(found->tooltip), chosen, [this, Tab, chosen]() { show_track_tab(chosen ? track_tab::path : Tab); }, Group}); + }; + add(track_tab::path, icon::select, false); + add(track_tab::lay, icon::lay_track, false); + add(track_tab::turnout, icon::turnout, false); + add(track_tab::straights, icon::straight, false); + add(track_tab::route, icon::curve, false); + add(track_tab::turntable, icon::turntable, false); + add(track_tab::signals, icon::signal, true); + add(track_tab::lineside, icon::objects, false); + buttons.push_back({icon::vehicle, STR_C("Vehicle"), nullptr, STR_C("A vehicle to drive, put on the selected track; it isn't written to the scenery files"), vehicle, [this, vehicle]() { + show_track_tab(vehicle ? track_tab::path : track_tab::lineside); + m_vehicle.expand = false == vehicle; + }}); + add(track_tab::profile, icon::profile, true); + add(track_tab::speed, icon::speed, false); + add(track_tab::joints, icon::joints, false); + add(track_tab::infra, icon::infra, false); + buttons.push_back({icon::gauge, STR_C("Structure gauge"), nullptr, STR_C("Checks which models enter the structure gauge of the tracks and the clearance over the roads (skrajnia budowli)"), m_gauge.open, + [this]() { m_gauge.open = !m_gauge.open; }}); + break; + } + case work_area::roads: + { + auto &tool{m_roadtool}; + buttons.push_back({icon::select, STR_C("Select"), nullptr, STR_C("LMB: a road piece, a junction, a point where pieces meet, a level crossing or a traffic point"), tool.tool == 0, [this]() { road_choose_tool(0); }}); + buttons.push_back({icon::road, STR_C("Build"), nullptr, STR_C("LMB: start a road, then each next point; on a loose end, the side of a road or a junction it's joined to them"), tool.tool == 1, [this]() { road_choose_tool(1); }}); + buttons.push_back({icon::place, STR_C("Place"), nullptr, STR_C("Level crossings, and the points where vehicles appear on the roads or are taken off them"), tool.tool == 2, [this]() { road_choose_tool(2); }}); + buttons.push_back({icon::lanes, STR_C("Show lanes"), nullptr, STR_C("The lanes of the roads drawn as the paths the vehicles take"), tool.lanes, [&tool]() { tool.lanes = !tool.lanes; }, true}); + break; + } + case work_area::terrain: + { + // what the left button does on the terrain, as in the toolbar + auto const choose = [this](bool const Sculpt, bool const Smooth, bool const Chunks) { + return [this, Sculpt, Smooth, Chunks]() { + m_terrain_sculpt = Sculpt; + m_terrain_brush_smooth = Smooth; + m_chunk_edit = Chunks; + }; + }; + buttons.push_back({icon::select, STR_C("Select"), nullptr, STR_C("LMB picks the models, as in the surroundings"), false == m_terrain_sculpt && false == m_chunk_edit, choose(false, m_terrain_brush_smooth, false)}); + buttons.push_back({icon::sculpt, STR_C("Sculpt"), nullptr, STR_C("LMB raises the terrain under the brush, Shift+LMB lowers it"), m_terrain_sculpt && false == m_terrain_brush_smooth, choose(true, false, false)}); + buttons.push_back({icon::smooth, STR_C("Smooth"), nullptr, STR_C("LMB evens the terrain out under the brush"), m_terrain_sculpt && m_terrain_brush_smooth, choose(true, true, false)}); + buttons.push_back({icon::chunks, STR_C("Chunks"), nullptr, STR_C("LMB adds a chunk next to the clicked one, Shift+LMB deletes it"), m_chunk_edit, choose(false, m_terrain_brush_smooth, true)}); + auto const orthophoto{m_orthophoto.enabled()}; + buttons.push_back({icon::orthophoto, STR_C("Orthophoto"), nullptr, STR_C("Aerial imagery of geoportal.gov.pl under the scenery, laid out by the origin of the scenery"), orthophoto, + [this, orthophoto]() { m_orthophoto.enabled(false == orthophoto); }, true}); + break; + } + } + if (buttons.empty()) + return; + + // down the left edge, in more columns if the view is too low for them; the bottom is left to the hints of the tools + auto const side{button_side()}; + auto const gap{2.0f * scale()}; + auto const groupgap{8.0f * scale()}; + auto const margin{8.0f * scale()}; + ImVec2 const origin{editor_ui::view_min().x + margin, editor_ui::view_min().y + margin}; + auto const bottom{std::max(origin.y + 4.0f * side, editor_ui::view_max().y - 130.0f * scale())}; + std::vector places; + places.reserve(buttons.size()); + ImVec2 place{origin}; + for (auto const &button : buttons) + { + if (button.group && place.y > origin.y) + place.y += groupgap - gap; + if (place.y > origin.y && place.y + side > bottom) + place = ImVec2(place.x + side + gap, origin.y); + places.push_back(place); + place.y += side + gap; + } + ImVec2 extent{0.0f, 0.0f}; + for (auto const &at : places) + extent = ImVec2(std::max(extent.x, at.x + side - origin.x), std::max(extent.y, at.y + side - origin.y)); + auto const pad{3.0f * scale()}; + if (begin_overlay("##viewporttools", ImVec2(origin.x - pad, origin.y - pad), ImVec2(extent.x + 2.0f * pad, extent.y + 2.0f * pad))) + { + ImGui::GetWindowDrawList()->AddRectFilled(ImVec2(origin.x - pad, origin.y - pad), ImVec2(origin.x + extent.x + pad, origin.y + extent.y + pad), IM_COL32(16, 18, 21, 140), 6.0f * scale()); + for (std::size_t index = 0; index < buttons.size(); ++index) + icon_button(buttons[index], places[index], static_cast(index)); + } + ImGui::End(); +} + +void editor_mode::render_viewport_gizmo() +{ + // the gizmo moves the models and the tracks; on the terrain the mouse is the brush's + if (false == m_gizmo_enabled || current_work_area() == work_area::terrain) + return; + auto const tooltip{STR_C("What the handles of the gizmo on the selection do when dragged; Ctrl held snaps the values")}; + std::array const buttons{{ + {icon::translate, STR_C("Translate"), "Q", tooltip, m_gizmo_op == gizmo_operation::translate, [this]() { m_gizmo_op = gizmo_operation::translate; }}, + {icon::rotate, STR_C("Rotate"), "W", tooltip, m_gizmo_op == gizmo_operation::rotate, [this]() { m_gizmo_op = gizmo_operation::rotate; }}, + {icon::scale, STR_C("Scale"), "E", tooltip, m_gizmo_op == gizmo_operation::scale, [this]() { m_gizmo_op = gizmo_operation::scale; }}, + // the gizmo always scales along the axes of the model + {icon::local_space, STR_C("Local space"), "R", STR_C("The handles along the axes of the selected model instead of the axes of the world"), m_gizmo_local || m_gizmo_op == gizmo_operation::scale, + [this]() { m_gizmo_local = !m_gizmo_local; }, true, m_gizmo_op != gizmo_operation::scale}, + }}; + // in a row at the top, in the middle + auto const side{button_side()}; + auto const gap{2.0f * scale()}; + auto const groupgap{8.0f * scale()}; + auto const margin{8.0f * scale()}; + auto const pad{3.0f * scale()}; + auto const width{4.0f * side + 2.0f * gap + groupgap}; + ImVec2 const origin{std::round((editor_ui::view_min().x + editor_ui::view_max().x - width) * 0.5f), editor_ui::view_min().y + margin}; + if (begin_overlay("##viewportgizmo", ImVec2(origin.x - pad, origin.y - pad), ImVec2(width + 2.0f * pad, side + 2.0f * pad))) + { + ImGui::GetWindowDrawList()->AddRectFilled(ImVec2(origin.x - pad, origin.y - pad), ImVec2(origin.x + width + pad, origin.y + side + pad), IM_COL32(16, 18, 21, 140), 6.0f * scale()); + auto x{origin.x}; + for (std::size_t index = 0; index < buttons.size(); ++index) + { + if (buttons[index].group) + x += groupgap - gap; + icon_button(buttons[index], ImVec2(x, origin.y), static_cast(index)); + x += side + gap; + } + } + ImGui::End(); +} + +void editor_mode::render_view_axes() +{ + // the axes of the world as the camera sees them, as in blender: a click on one looks along it + auto const s{scale()}; + auto const radius{40.0f * s}; + auto const margin{8.0f * s}; + auto const side{button_side()}; + auto const size{2.0f * radius}; + ImVec2 const origin{editor_ui::view_max().x - margin - size, editor_ui::view_min().y + margin}; + if (begin_overlay("##viewaxes", origin, ImVec2(size, size + 6.0f * s + side))) + { + auto *list{ImGui::GetWindowDrawList()}; + ImVec2 const center{origin.x + radius, origin.y + radius}; + ImGui::SetCursorScreenPos(origin); + ImGui::InvisibleButton("##axes", ImVec2(size, size)); + auto const hovered{ImGui::IsItemHovered()}; + auto const clicked{ImGui::IsItemClicked(ImGuiMouseButton_Left)}; + if (hovered) + list->AddCircleFilled(center, radius, IM_COL32(255, 255, 255, 28), 48); + // the view matrix is of the camera turned only, the axes go through it as they are + glm::mat3 const view{GfxRenderer->Camera_View_Matrix()}; + struct end + { + int axis; + bool positive; + glm::vec3 seen; + }; + std::array ends; + for (int axis = 0; axis < 3; ++axis) + { + glm::vec3 direction{0.0f}; + direction[axis] = 1.0f; + auto const seen{view * direction}; + ends[axis * 2] = {axis, true, seen}; + ends[axis * 2 + 1] = {axis, false, -seen}; + } + // the farther ones first, the nearer ones over them + std::sort(ends.begin(), ends.end(), [](end const &Left, end const &Right) { return Left.seen.z < Right.seen.z; }); + ImU32 const colors[]{IM_COL32(230, 70, 80, 255), IM_COL32(130, 200, 40, 255), IM_COL32(60, 135, 235, 255)}; + char const *const names[]{"X", "Y", "Z"}; + auto const reach{radius - 10.0f * s}; + auto const mouse{ImGui::GetIO().MousePos}; + end const *pointed{nullptr}; + auto const place = [&](end const &End) { return ImVec2(center.x + End.seen.x * reach, center.y - End.seen.y * reach); }; + for (auto const &entry : ends) + { + auto const at{place(entry)}; + auto const dot{(entry.positive ? 8.0f : 6.0f) * s}; + if (hovered && (mouse.x - at.x) * (mouse.x - at.x) + (mouse.y - at.y) * (mouse.y - at.y) <= (dot + 2.0f * s) * (dot + 2.0f * s)) + pointed = &entry; // the nearest one under the mouse, as it's drawn over the others + } + for (auto const &entry : ends) + { + auto const at{place(entry)}; + auto const color{colors[entry.axis]}; + if (entry.positive) + { + list->AddLine(center, at, color, 2.0f * s); + list->AddCircleFilled(at, 8.0f * s, color, 24); + auto const text{ImGui::CalcTextSize(names[entry.axis])}; + list->AddText(ImVec2(at.x - text.x * 0.5f, at.y - text.y * 0.5f), IM_COL32(20, 20, 20, 255), names[entry.axis]); + } + else + { + list->AddCircleFilled(at, 6.0f * s, (color & ~IM_COL32_A_MASK) | IM_COL32(0, 0, 0, 90), 24); + list->AddCircle(at, 6.0f * s, color, 24, 1.5f * s); + } + if (&entry == pointed) + list->AddCircle(at, (entry.positive ? 9.5f : 7.5f) * s, IM_COL32(255, 255, 255, 255), 24, 1.5f * s); + } + if (pointed != nullptr) + { + ImGui::SetTooltip(STR_C("Look along %s%s; from over the scenery it's the top view"), pointed->positive ? "-" : "+", names[pointed->axis]); + if (clicked) + look_along_axis(pointed->axis, pointed->positive); + } + // the top view, under the axes + tool_button const ortho{Global.EditorOrtho ? icon::ortho : icon::perspective, + STR_C("Top view, orthographic"), + "O", + STR_C("The view straight down without perspective, as on a map; again for the camera as it was"), + Global.EditorOrtho, + [this]() { toggle_ortho(); }}; + icon_button(ortho, ImVec2(std::round(center.x - side * 0.5f), origin.y + size + 6.0f * s), 0); + } + ImGui::End(); +} + +void editor_mode::look_along_axis(int const Axis, bool const Positive) +{ + m_focus_active = false; + if (Axis == 1) + { + if (Positive) + { + // from over the scenery: the top view + if (false == Global.EditorOrtho) + toggle_ortho(); + return; + } + if (Global.EditorOrtho) + toggle_ortho(); + Camera.Angle.x = M_PI_2; + Camera.Angle.z = 0.0; + return; + } + if (Global.EditorOrtho) + toggle_ortho(); + // from the side of the axis the camera's on, towards the other one; the yaw as the camera works it out of where it looks + glm::dvec3 where{0.0}; + where[Axis] = Positive ? -1.0 : 1.0; + Camera.Angle.y = std::atan2(-where.x, -where.z); + Camera.Angle.x = 0.0; + Camera.Angle.z = 0.0; +} diff --git a/editor/editorIcons.cpp b/editor/editorIcons.cpp new file mode 100644 index 00000000..6056763f --- /dev/null +++ b/editor/editorIcons.cpp @@ -0,0 +1,176 @@ +/* +This Source Code Form is subject to the +terms of the Mozilla Public License, v. +2.0. If a copy of the MPL was not +distributed with this file, You can +obtain one at +http://mozilla.org/MPL/2.0/. +*/ + +#include "stdafx.h" +#include "editor/editorIcons.hpp" + +#include "rendering/renderer.h" +#include "utilities/Globals.h" +#include "stb/stb_image.h" + +#include +#include +#include +#include +#include + +namespace editor_icons +{ + +namespace +{ + +// editor_icons.png, written out by CMake +unsigned char const png[] = { +#include "editor_icons_png.inc" +}; + +int const cell{64}; // pixels of an icon in the png +int const columns{8}; +int const rows{(static_cast(icon::count) + columns - 1) / columns}; + +// the coverage of the icons, a byte a pixel, as they're in the png. empty if it couldn't be read +std::vector const &coverage() +{ + static std::vector const pixels = []() { + std::vector result; + // read in a thread of its own: the texture loader turns the images upside down for the threads which don't say otherwise, + // and what a thread says stays with it + std::thread reader([&result]() { + stbi_set_flip_vertically_on_load_thread(0); + int width{0}, height{0}, components{0}; + auto *rgba{stbi_load_from_memory(png, static_cast(sizeof(png)), &width, &height, &components, 4)}; + if (rgba == nullptr) + return; + if (width == columns * cell && height >= rows * cell) + { + result.resize(static_cast(width) * height); + for (std::size_t index = 0; index < result.size(); ++index) + result[index] = rgba[index * 4 + 3]; + } + stbi_image_free(rgba); + }); + reader.join(); + return result; + }(); + return pixels; +} + +// the icons scaled down to the size they're drawn at, each with a pixel of empty space around it, so that the filtering +// doesn't pick up a neighbour +struct atlas +{ + std::uint64_t texture{0}; + int size{0}; // pixels of an icon + bool failed{false}; +}; +atlas g_atlas; + +int stride(int const Size) +{ + return Size + 2; +} + +// part of the source pixel Pixel the target pixel Target of the size Scale covers +float overlap(int const Target, float const Scale, int const Pixel) +{ + auto const start{std::max(Target * Scale, static_cast(Pixel))}; + auto const end{std::min((Target + 1) * Scale, static_cast(Pixel + 1))}; + return std::max(0.0f, end - start); +} + +std::vector scaled(int const Size) +{ + auto const &source{coverage()}; + auto const width{columns * stride(Size)}; + auto const height{rows * stride(Size)}; + std::vector rgba(static_cast(width) * height * 4, 255); + for (std::size_t index = 3; index < rgba.size(); index += 4) + rgba[index] = 0; + // each target pixel the average of the source pixels under it, the ones under its edges by the part they're under it + auto const scale{static_cast(cell) / Size}; + std::vector across(static_cast(Size) * cell); + for (int index = 0; index < static_cast(icon::count); ++index) + { + auto const sourcex{(index % columns) * cell}; + auto const sourcey{(index / columns) * cell}; + for (int y = 0; y < cell; ++y) + { + for (int x = 0; x < Size; ++x) + { + float sum{0.0f}; + for (int pixel = static_cast(x * scale); pixel < std::min(cell, static_cast(std::ceil((x + 1) * scale))); ++pixel) + sum += overlap(x, scale, pixel) * source[static_cast(sourcey + y) * columns * cell + sourcex + pixel]; + across[static_cast(y) * Size + x] = sum / scale; + } + } + auto const targetx{(index % columns) * stride(Size) + 1}; + auto const targety{(index / columns) * stride(Size) + 1}; + for (int y = 0; y < Size; ++y) + { + for (int x = 0; x < Size; ++x) + { + float sum{0.0f}; + for (int pixel = static_cast(y * scale); pixel < std::min(cell, static_cast(std::ceil((y + 1) * scale))); ++pixel) + sum += overlap(y, scale, pixel) * across[static_cast(pixel) * Size + x]; + rgba[(static_cast(targety + y) * width + targetx + x) * 4 + 3] = static_cast(std::clamp(sum / scale + 0.5f, 0.0f, 255.0f)); + } + } + } + return rgba; +} + +// the texture of the icons at the size, made again when the size changes +bool prepare(int const Size) +{ + if (g_atlas.failed) + return false; + if (g_atlas.texture != 0 && g_atlas.size == Size) + return true; + auto *renderer{GfxRenderer ? GfxRenderer->GetImguiRenderer() : nullptr}; + if (renderer == nullptr || coverage().empty()) + { + g_atlas.failed = true; + return false; + } + if (g_atlas.texture != 0) + renderer->Release_Image(g_atlas.texture); + auto const rgba{scaled(Size)}; + g_atlas.texture = renderer->Create_Image(rgba.data(), columns * stride(Size), rows * stride(Size)); + g_atlas.size = Size; + // a renderer which can't show them gets no more requests + g_atlas.failed = (g_atlas.texture == 0); + return false == g_atlas.failed; +} + +} // namespace + +float size() +{ + return std::round(20.0f * std::max(1.0f, Global.ui_scale)); +} + +bool draw(ImDrawList *List, icon const Icon, ImVec2 const &Center, ImU32 const Color) +{ + // all at the one size, the texture is made for it + auto const pixels{static_cast(size())}; + if (List == nullptr || Icon >= icon::count || false == prepare(pixels)) + return false; + auto const index{static_cast(Icon)}; + auto const width{static_cast(columns * stride(pixels))}; + auto const height{static_cast(rows * stride(pixels))}; + ImVec2 const uv0{((index % columns) * stride(pixels) + 1) / width, ((index / columns) * stride(pixels) + 1) / height}; + ImVec2 const uv1{uv0.x + pixels / width, uv0.y + pixels / height}; + // on whole pixels, the icon was scaled for them + ImVec2 const min{std::round(Center.x - pixels * 0.5f), std::round(Center.y - pixels * 0.5f)}; + List->AddImage(static_cast(g_atlas.texture), min, ImVec2(min.x + pixels, min.y + pixels), uv0, uv1, Color); + return true; +} + +} // namespace editor_icons diff --git a/editor/editorIcons.hpp b/editor/editorIcons.hpp new file mode 100644 index 00000000..3734d465 --- /dev/null +++ b/editor/editorIcons.hpp @@ -0,0 +1,61 @@ +/* +This Source Code Form is subject to the +terms of the Mozilla Public License, v. +2.0. If a copy of the MPL was not +distributed with this file, You can +obtain one at +http://mozilla.org/MPL/2.0/. +*/ + +#pragma once + +#include "imgui/imgui.h" + +// icons of the scenery editor. they're drawn by editor/icons/editor_icons.py into editor_icons.png, which CMake writes out +// as the bytes of an array built into the executable, so they're there without any file of the simulator +namespace editor_icons +{ + +// in the order of the cells of editor_icons.png +enum class icon +{ + select, + insert, + brush, + area_fill, + copy_to_bank, + translate, + rotate, + scale, + local_space, + ortho, + perspective, + sculpt, + smooth, + chunks, + orthophoto, + road, + place, + lanes, + lay_track, + turnout, + straight, + curve, + signal, + objects, + vehicle, + profile, + speed, + joints, + infra, + gauge, + turntable, + count +}; + +// pixels of the side of an icon, at the scale of the user interface +float size(); +// draws the icon in the colour given, centred on the point. returns: false if the renderer can't show images +bool draw(ImDrawList *List, icon const Icon, ImVec2 const &Center, ImU32 const Color); + +} // namespace editor_icons diff --git a/editor/icons/editor_icons.png b/editor/icons/editor_icons.png new file mode 100644 index 0000000000000000000000000000000000000000..80b7013b1d6941acdfa1e00a8bac910d91eb69ee GIT binary patch literal 44886 zcmeFZWmsEXw>BC=a47CxC@#TW3&o*@0!4}w+})kxQd&xJcWZGd1PYYm4lR~YiWdUK zCFJ9g{qA>vd++O9=f}CupC^CTnrn@5k1_8du-2SO{8Mce0$dtg002OsrmCb1008e# z(E!+(_lLo>2|ECQBGlSa@x>oUM;{lf`^yaXM;mKFcS`^Nzu`Flg0uAcZ$|kny}WEay&c?Kz3vqN?SCsM{i$H(=K1dmq<`4vb8xluw6$@t zb@k@+75YPm%39FNUiO@+q*S$$6L5g3ZcDt(cz@m5`d+`p!Nyt=;^ygM>Fwa)Vrgf~ z@9t{n=-}$*ZRu)ltES}7EK`^DQFF`vJn)p;)RHd3-AgHfQ5K11q1|ng#{%< zCBR@yu#k|gv!|`|J$m+Ss8rF zsG#a`4|A{b_$lXmp!a_`U2ONaaAYy$uQR7Vr|#x%>uPJ`cyHIz#n#)_)2qnoKiTk# zD7N^>1F?dX+_7lJd|iTPcH$6?@}cXg(^$; z$TD@65vS?`{0<{7O8@r5pWfQdh0oH}+uqa7-NBmA+S$^_#+J|B)6Lhx=D)h;&-h!a99(&=-CQB}qW9$6`M5f{y7{>}+1PqndpfxPL61M=@t=rH|AENlZSUa41aWY- zWwP|XAJ{T^+IslddU-RsL6|I=eD0Hn$EBqBJHCJor`{~)7 z-AC-}-w~Go-y{47m3%gq-j@Fgu=UVeo1)m!=4ZBz$lD>2nEM;ye+0P9+S1t$;$?3s zC?aa@ZfT?ShsM7?f5)V%#0@%A^chBd;xXWj3R9BuvYUH1ATLnDgs$CjSnRLl@(TUR@8duob=`vxKV_dMADoQI&b^S@Wv zpMBtupgOqxm8U?$|Jz*$F9$nUOK%@fTYs&8%d|gJi~s*sgZL{O{{{Cy7NxMLkfp7# zjVQ06gqRhtu(&9g*AfC2;}w?>0)uU=?<>_x)bsC6_|c*_R2O-_8w3i@dIi1o`)H!J`*33el;cma{1-0g{}b-NlI(9-XdeYb z&nXVZ6fL<)UQT_Xm<&Z%{0Vn%Y6)W!qTI{f`_E{a->12)r=_=>r@4bG#I5Ad+;LO* zk2+?0a$jIxcE0y5xU?(qkTWf>AuL$x9^WArJBJsbohvV<;`5zkkzXE!n z&qUaNq|Z#W5s;)9jg5-g2#5=g1Y&|C(UJ?%foMP=nfzuU?%`*Nsh;+zYfME989RMa z02mh&n=4jzLI#A64ptMO1e0T;>w|DfAN7#$$V0!#lg)=?A z{~K{%*$$S@UQD`fZr)6f6~GLX1cIVqaj=+xxUi6jsPX+!{C+4ZAo1rAtn|N|;smq* zWs2$FO#RiV{==f)eO=slW3UmJ6cg)D_kn1C&jTF|3tbTa$_m7F8huKIK{hYktym^u zqO|*Mb@Ahs`Y+0)k3iMSpZYBZB;Y^}T;i{$b!B2L55-9|rzm z;2#G5Vc;JI{$b!B2L55-9|rz^WZ?XTd}8gMfS{GkIjCzsb`E@6=TbG<)e~obY8gQ$D2B|H+hh=nWR zB~KhKg(HA_)l>huv7#zA>ts{c$<0RYNlsJ7_tp2S?_DN?%^Lz_h7ZRk#x8<-wu80# zF%;wlo_M}_h~lUCNHs{6{0x((*Q__C7egMSl2T7EsRuV52uRAEYN%{%7y^@9^CVkU zCp=3kqG$WXkCl(^TprYE@nw($ATO{aNPt2~;Nlvf=_vI$k>Qu!!>JQ70%*~~Fc;9y z(d#*D0fRdil@xkl5`1)9d|EVqG&5XhLg!jo9pDgHkJ*E@a!?Bt8l-vZJJ#|XJQFeN zWX3{;E?I}}X0T4^UJMMkA}eVGkd?$6WdTqC_uR;EUu%&gTli}4YwBYCH&&p>4ocC= zYKz~ampRZ|L#LKFOxp12v;clUlfAB5%=~hSi1(l+m56+kV~X&lv;tZ`Z8X3W%}f5L z5ut@9W3Nrz>1GMA>m)CL4OkSZ64;FaOQin%V9FPuV4t}6Rg~9fqJ?f`LrcBJ(V1C1 zEq4HaZ+yVadav@e+j0{lP`k(-9luFefNvXu3&0)7E+}sVTrxLef$Ne!=X9X3rSYY? zKl3tT$YGw_ha{8CW&nYK6}dk4|7knD0w2K1nju=pF7A zh;xu~G7QJ)F~hkPvs$$p;6ZD}qD1f7zscI)L$lz99}@>Rc#z*=q3d9{dYsbjNRFdT zxI0NYZ<9z=4A$dDnx!P%_*D(TxKg$Pc(D}ReVDAduvKKRBcBj~{4f+6^Yc*z$oj2` zrZ71oP+x|3$cVMD%DHSA6SnKD-^)5L{l$y?64*P;%cQa_qyRT|nmu_Hs?%I*sUogGp27}G@+Z9gWNS~m(j+>gCo(P=5J z@l2)JQ-p`nb8%9_1%!mwgTj&eCk1i#ijOQf8%U-Gr;pyv-DEa@73)Ynv`;ga z*D9Mg{mjctg5VS2n-=0FHFGDyoZ`#~%|)x?4I|C|K1Jt)nx)*Xyt`HDKE#&K;dv)k zelf~?(|^cuRcg{z*{~aeCLIgPaRJaBXd1_86pF=mG9U3F{Y1VbVK-*vUxed!wLhB| z2|zoN{=za}`LlQYVOdvpPDSV44T*(O0c>%sgAIF8e_b{P6cF;g4)z8}u>#AMMNs$R zy~T93Ye%O+$EeJBu=9;(lR5W*!AoW86pDU?i-lWKU;8p5gd9Zi0T5MU7ry@{#g4gt z4yM#&4*aCQc%IRF{oqaJjk~MS+uo8l+OoL6l5OJO)75j)*OexR@AwGGmS&HM_Ktdg z#3h{`jKxsy-0i!1l6x;pHp#$sT)p`R~PkmD5g!>oF%XZA!k+6Ei z0vh;{yP1v z!wKE(x$K8m8KjTw#yPez0Kdd&dZFlsoEw3q@{KOz8VXpaSB%0cvhS=ztsg!NS3eXA zW4v*ddz8uk#NRsmNRCDzBj|~vJe<8Z#}gty4HJE@Lio4aeDBKfs`JlXqw>U zqgZalyyBqDWVoBWpOhKKhtOJYId;THZ;COe6(G-C*$xA`%4;rZkX!J23>>wGo~PxX zV8P3=iM|1josvmTl3YJ$LUx@pe_*Y zsKAlhvH4qAD{4`vlgqNE2Nh(0s`&!c}wFnO@|S5f@L7}0{ZB>tjhW8S3j6k-s76c!e{*UrV@urSdc4`BW4x?3W3L7+fMjVUXIfa~qEn)gV5aBQ!49SlBC)HMYT59oN0OVna%e zhPb(|J0^BW65_n(^$Wa{4n(6u&)1XEAb|@p~x#dvv&Fvi%wl+beMN!vXcGhQEkN)~Ar5rEkmkbXgEe^5C zT|Qo|e4c)>imcn0I!O08CgWafvLpg+V<@=WMVE)5y<=JR*&ihH9I5_BtU&ase|ntj z0$*+_WCxGsEaR{wxHrKdFS{?lO(ew3h7jZWJD76(gU_-gb|kxLdiCztyNhO9*!PY} zsz^Vcp_Q|d|82rhf2>#cX|U)K(HjekST5ardaqS z!!A6wsQF6O<~)6%0-DtM1%YnPb|&M7!S}QdhGm}x{W4}JB65)jyrrnZ>gjdxiFh0) zB2x<L;wr7d&eX8=;yV2nrs!I#! z4s03(bP1N9t44R zRc!ltD-x|oc_I}jOkxahUT`L#x`bp1hK z*3_r`M%@YlcuP2H28wx9>l#U!t=IXb=BEAPHD=-9`%;3^g4A}7pm_7fZU5@u`snk^ zdp%)Tx(JqiDx77pS4=ToPcQo8Q?|Ev1-V#0km`T_5%?C=Y+xYSr5wgSePVc{^t?QV zZ)S4BT6j6om-~|O>GKN-48W^FQ)_t((w2uH`rCvsah}EEOQmmuI^nOEx9mxWvcqX{Afllb1g7yhKB!iq)_9qA2(Uu7MRAYd4 z&hY~o4iTOAiq?0dPn~%=>L9X|r5DN1?GtHLxB=j;JloZ1vv4+6Jo%b|qp}3xRD+Vg zCBH%MA~WaMlxg4+_Zy?gm&-vW68TMj4}w1B{kFu_Hdj(PJMo7`YFXOtje`);rXyNnuVXMJ#qk+(@^K_% z_xnjny8H!p?im7Am-Ci0RO*xbGR7pP2UFBh*VPOhkEJzu0kkY=84dH$O>8g3qJQL? z-sPu6r{^jniW_J@W)iZk>s%F|m(-o5LtV;%V)3nvZ3g=AE>&OkM^P>OEZ@O|mGe#y z#t&P((zDscY_NgO2;^9ux!msL&!5piWZm4};s^;m3e~e|Kc-G?IQ)UkUF>UhePTyR zajs=6=ta>xW6Vg{_O0P_CpRpRagpq7WquV9?iT$lfBx$h=Pn)Wt9wp#$r&=$YW(_+ zlnNC z&#%t!z2*0UEFR8m`+d#MF9sAY02?oi#fsiZ$Uf&)W1TuTnda*)qc&lssXl;}hxx?= z*4~vWO*#qIeG2kp$NRCTN_BLd85ypYeUsGOR=AmkG3BbM{E(t4 zA$x))p-u03SVi2OW8&9erSH+tqP>>lq$jSe1(&0R@t5Hnf zDha8{o)DVjMZy*G-f1IDd?)==of4hBPcoRWedCLEYnuR>-hEh|)C&)Spv=QSjtc)+ zo+h@B6TB@71q6=S2NNdn=KHs@7#n(ZKY$qyM7*NB;R zk@0Nij-rv_SHIa>qG=YX`BD4sO!kCtk~Hw3)Ba}Hqvcax#c4*Y9TnpZt8Q@Z>thCy z{3C3VmGije5|L7u>mnPy(6vQ0anef$WEDEVDO2h~Yi&f;6-r1V(%EHj0fkZ4khYUy zak(Q$L*H&aR3cKyKMGTwOb98LDpq3 z)?E-Y%`@`PcZA@*cEq^aN?3MtE4}!nT(BPgwaMo(DxyS)5Liw+^-@h_Gc4ULj~E@z zoINR3fM!Me$|e)g{ThvbSO3!REnyEpjU^1vlBoH(5-|v9kT|@$F~)7rd3TWq!uqcL zjm>r-!y;*p@))WEacin1^%*nI(n$7pCg8|d5)~VAK zE4;hKv5}nJn@>4%YN^hLKK?TQFyowYJh7k*zM-Y6#}-shgVbtkwz6nSJDn6`;;g0w zm_HOo8K^F1is1s8#+3Qnh@#n!CiiE%XthwO#y{C|2X|e1DTP)FkZE#i(a>R~nsv`4 zvl9vo#ncX%n&F7Qyh8YaI`tYIsP79p?$L*fY+BN}xwwF#nkXjm!qM z>viv0Rv1F)XW6q71-_tWj$>M0#+PbT1E=@@josDn`zjEDXiuq0K{G zx*lBV%sR2Hm?{zC?U)jgg0g-F-W&@1ximljl_wDK)dTUS8FW^a4j*j~A6s4qN!ck2 zd3cAWXV4pz2QAXscfXkxAjY|DYB~Mr9{Mxnb}85_;PGORRTi%Ei|iGKJ;b;EqCy4x z$}+0`aQ>4e`AD76+95PyERepgBaLj~j)UVJl$>)d^JV~uqv0R}Ww`v<6lI44_ zOyVfwyL#suS{@F&=zF!h%3qgKeV0es#Aoo`)8G~CtHw#H&_1n+c z9Q65#C<>MSJkkmm-_B6U9|>H_Tc!1r_GN>VyHce-gqJPSZyPzZ<~FSsz6xRE>6hj0 z7bcrj&bkH5ewG^SSGhyd}2;%2~ zTt1=*t5jR+PgK79VeNQ`=(hjZqaw9pCuQNfgjGSlsQEFH+O(=2D*PVU(EQLZD&1dS zMY}=w`TFkJlcVftC)X2E6|La1u4LDwHc4#-u0!#;w|P0|RNOmbv|42$0a z3d6KNxaMOP^L3R9(6>@RA4@)P-8k%q9=?4N`>CeZDN~;!`C$F=8%kf2yJU(_oQGY{ z%aUF_b8?>7_J6wT+$z5P@H=h=$H(knXxOF1x3+B^VZh)Q4L6Va>3?p+j+IJzH?Q_4 z^Syh%nC;&f;?$hIWidu|QeU>ZRY+9+27hjk(N5Y>VvR%8q&Q#@8#kae$VX-sJ7}`a zv7fc2>=kmp^548w0Tf+BimX3w#OJe1ZlE)#`n5eUi7#BDL?)+5(L=kCL2&_I3i<`36jbK z;R)?IS>rtK<2h5i0ooE6hL^vdO6~C=0)@E{qqm0PGFSwk%A};wC=k`+M_QaPSS$ZB zTAUFb2&hkf=d-pAJ7`*7L3VXb>crL&J*5FbY_(D$jR(==WWspE<8ma!^H5b0o)fq0 zACIsaAxZMdA|bb#t~)KsAB~C33Ww&ad2L?Wun^*<)GZsPvq_3F?n}DBoG5#G!n*Me z#w!Z7cCKyGxgf@KHS?^uxGew-oH111?<<%5a|K6&x!lcO#x3p@`H;|qAB4YdrPAI^ zv7|XCSOA2rC)){TXwEAfBk$fD2W&lQ{#J6@<+O6OtQ5+f`k}7A?=uyt7mZ6p{r;QN zoy}|Epi(mHeDg}gCl_3yOUd+;QPuck6OwFHNXXl9{UEVU;k$s$rUz^-Q>zr|luh)j zr`fpDwEns_{E&ceE1BMW=2=fqbOd8N$=r=KE>!GY`(7#{~*Rxm2LOPZUtM?EY&7 zB58PYLm(08=E*`uEh5+}q5DXQ2$a*>-d1!YRvGJ8@dW!F)(c2i?b2P{%U?GpXG@_n zH%-DpHUXL)h%eYqcWc$nrm$lpY7^xeoG|N@Ogz`-VGe(YqAUc4$U*1-Wf#A1Phr?# zaHyWG+uKhk!JwSt6I+qz&ho=cG_kkBClu=~%6|g)q%9vU;-a9ep-aI>B?dkQ3Z^+#XL5U890r(VXOjjx6J5;T; zut_cprHBzzbsGIc(L*JtVB%HFKm%`lM)g%MUPDgH?czYAlPdaZ{_-+?l4J(4jG`go0Hee zCsE#A(;31w@iQp7Yw_F>f^?-E`{q}2>hPo+JYcpvrMU}J{%GH7zww!)tNR#m6or2- z|9U$$a{neR`@BH^$eV8bJ3wRs|2fb8$(>t*os=<}e0SnLN(vJ|HEu{Bg2czUzDbYd zMCCh}h`h5p_sRx7CTCqjvE>=UK1}3WS7o2h+SCqo_k&8rK6=h7YoN8|rCKJSarlXI z9V)NI{0x|6>$*mC1JMxB)GyezzK9n-i%KE4(}92zi#!2{7tgp1Jr_{;d`iqxconn6ak}a=cYg`VKZS|pTnRm%j z&RSwg0p+ZV%YOTvS7$8>wF2J*uE+SN{IWc0Al-Otx{6${?}UC|O>hFraoP!YIyEJy zteQh;W(*$=cfPaA@8hw#*tEc*Y7rnQ3m;$cqj;_TT3CPhH?+oP_LkW#v3Hw1xk1C8 z;(|cwiPi?YRkidh@VACHPvKeLVYz@Fr+iFcd^99mTN(kbbiXv!XYB zT;c`x9t9ccJC?L65A{)IbFa?YT0W(K3wlZy^7gS4`@M4}5fN#VH0`XLK^UrDTtZiM z^0-eFB>m!Wl4i;8zcpa^P%EdrGs~?B_@XbXByMs`K; z=c?{iVRw&%{6i7y;A^PyqNg7mPgUyhQ(q`jNq7%)p~$&QBR9m%;7Cm;YDmFwdXmC(uIG}-%SM)^8}0#^Lz zF63V&cY6;ZacUtivzsuT`Oft<?lVq&P$;m+8hF2mAMN-rKqhLBg9 zy=giUSx0&6Q-maJ_p6o5_ZIk*^ zamcQStWzOf08G+yLB56tN0#EpQ16)|&bRb+@3Q&4N1J_dW$VFTgxqVkQXG&sPsc(- zG@&Ns9gwWpx=Dva7^d)~ZeOk;lwujtluiRav3Fg(k-$K+h4|iuE$#^8DHd3a`^L(O zY)-0Ld|n@@P{-({)!#Rgk>?Jl5B{3O);%8}6 zjX#dOd%blvmDbIV0!YgN&U5gvPZp$<4NwRqloCRCxV&~&pmVR(-t!K zl9;1ZlcY&b07r{Oqe(U7lpJ$o^h5rhY~*saUtT20IWI$q$o|o#PGN^^$9p&%5v2x0 zf4((kX{uk;pvzZpYu9^Ga+(rb4&&Wa{4RE^yX8JYTzy$lJntVhBO* z8f0RO%cgKW_3>ENSe~b>?l%Ye0TKHfXHzPF<*Tnv>B0lPH+bZ^lgYssGRWjlD-T!e z&5f?$hpHc_n>1x#_8sr1Gp%xUzugpNJQubsNbI*dAKO?2lI6fSe# zpNJ}_9ZcN`qFAFp%OwKVQP!L*BvOTE>RRH02v)#zQj5$lDf%?;p^j6K2q zV!a#Y(1a;!JN~g%_D9<<9*|#IaJd3@npFDpLC(^ExpoJX!R$I{pjJ+K2WW4I)3`N( zgcAWDmQvogtT$jBu;XD}&G_QS>*H(qmRg0RlWL6S*nqXaM6n2YGyU$q zFR_MQiIU%q?yQVg#|oc*Z^LkGC94eanZ8L5O&&HYM9C6=8xFC{zWWTKh~N>q>s$_R zbtT*hk;`Oh5`&-LzR9kK%4Yvw+z)(EtXSA##Go=ZpR>0evKuz+6V!NE(m`xjz@*paZIQ=E(LLj++Y ze2xj5q7&B0aN*J2tmEUQfO#?E^`P}0MxRpVLmm6o@B7#5+Nvz2JG`M`rFLE82l*1y zDQA13uS`boADX1j;tfUv371uTp^WVk(8mtdbJmQf;(_DE<^iW6Y!5FciYc#8Ja>p; zqbHxf$>>yAZhsPeb;VYJbZ_3!X~8x(zkfN8alc3B?T?qf_Y0(Ct1!{MKq^AXA(_XV z8y5-7SPlW^GtU^ALVR7&z^CWfGaI4g!$7d6l(DiG*Hei|B6QXV78<2!gG`Z9_;or} z@yL%9#sRvC^0^eWJEo=6mnSK5agL7&;4mC+SACLNkA7qxtI>%0l0Y18M9;%)v7s;q z|5wHmT&lVKn0uM=j#8<_z7(3(->;Sqr?LXr0gX|9C zewmR(o`CVPujwL9BiGcgEH9U^3Un2|s}_2{kox(|Ju%DZPLh@5NO6FkZZna*bY}kn zr`Gp18P8N;{Qj3I+c)!#0UvaGnOv_)tyk5kvo$&elJgh^Zp0pL9U9+1PDAV;{yLN4 zewn>5x235D`F+3VC^DxDd}eqfOzE2%pg?rnUzciLBA&6O9?~Eb$>eWK1oFxomK?R? zuG$~Yfrc@H`WUD?Xe6f(FCLcnk#~wN?$XFH2#M9qm99;}B={rZj~u&U%qgOomxV8`hFI~QqUP+z9Rtb6`V zBvsG1r%QRBD!iVThzgc^%Z;d$>*m-{MxNaaJPVj8!r8s_;Wyuyw@(6z(Tw%`# zvEJ|~^}Y3Rq=8~wnlLT^G4+_I>@UblAHvxru^8uq3d$y%M8{VzN2#h88Et<2Pb&}MOm!Z?N#D<#CklTaU_rK)g=YHt0 z9EC_)M+M_OQdY2++fdf5Y|!m<9{ZfL7ot%0uDSyi>|^;WVUFLNP|fyqTUJl&+OB%) zlIgYYk}Hql)8F>*l1A&_r_PbQfcL7M(!cW2$>z3y*)}<%T6gV)#rlGm!8(Z5+R-37 zv|B#-4p1QEwB~9}So)<8O%jWih(hr<0!kMDsGa(<=y%pUIy_1?bB8}Jr}N2q{Z8+2 zx{Q&K=+~Vq3w;XL_@hsCY51(>pk0yZ zrK#vL!MS&o_jd_1;1>vy2oPASx)G%hj9{EXg=?8JH=3W2uWH(M*7TV_bLz8BpCnLP zfr}z7Rabg>xk~*O_RZ@x&sbZseefca)_I-+8qJalX<95#j(|&Td57Nc*IRg4nY5lcCYSw}$BuXBe!F)3Y_xqcstu zQl?>|R?NZly2;bAMlKC1KiErM9%?Dc*_m9@!kALuG92^7}@>Vd@2Elfa)yyxB! zZ*Sd?^T+IF&wNis?Wrw3Er>G`6@ZDHopQ&FH2;xS@pbcux}{73*-?Pcw`AGl%Dj>NU--p6bEvBUOR&vg=iq zGt-i~M~*N3!H2T_K!Ccn>d`YMa73wyYxrddBOO|wT)mT!p1e|nQxB;^VNYZ#yzubk1@8AHTjBT8d`QPd`lJ~XE|LKX zsY2Ml0PnX9Qw?%FF@J?~JsPpibhh+L08me5EsIO}m(HnQRv`+Xr0^R@Gs=pL;*V{{ zRBEp_)lnb;<|tu}08HJMRK*gWCjtr6@$r9PQFVtb%j3S0R>24%_(oBMYu^XC$G^7^ zxYxB#iY?3Nrc~oRAs<=OOjkEZvLa)_Vc|3d403$=iW9akc*Xv?oGTMy@`MJ5w;Y-` z`-R@wRw9zQ-B94V#Nu7xx2SG2^A~{FZ~zzjryi;eH9f%4LrxpB8?ic6W#qA)VDQyG zPpW!SBVxNdVY(n>sjx%KlkAs~)0rpb;>A1=oS~Ia#!!EfY!)eNZf+0Q&`6wC_w+dH z^8+$)*%xkq5N)Ke++Q#gISv<*S|j*jHu%-A0JHajs>(R+b#Ta!bYX(V9MOsm!9e0q zCM+!|H@e=lK`NcP;*@6O%In6&Tsu0EviD+uf``Aj43fO@+d;YbbS0UYx=%ipJ}||( zZ3BwjtcI@C0HTnyw>m0IUOGE8bkWblPkGx=ln#gx+#@HfpBwdI0M|N9t*6)Zk|hs62Qs$kMLoDsL!wi+1fd1>aQ5rd zzz{OPC%Vf=%0=lOM4ANf0M%nEd-{IsUihP4!h{wb-kk-EJM3DRWqudEi0xh%cUZvF z0ImFsCA|o18e{wUug-4(UTuXHIhG%fvEhpB>>6BBI=cBixR?3pw%qrNf|)&lAT(&g z?YTJ_m8Knz^XqoNSn8=7>)z$E`qNpVXy#hNv0bzX8ol>&;rm5U!H0F$##5;l&EuF5xrgZ> zzoCEFgIQdkm>WcTq>D8ZEC!_|ua(IFW4q&77$W(EKxq?F@9E#7hV&N-UVh){z%@db6FA{x==X2#mT+LS2eti zGt&W>F>`~BPy6tmdGym98@@4P*q@cLOMvVr-b&XOHq#inSqF0G#uO%iGU?@_$GYYe zy=WJ15weNi{E%{y_5KX>-%V zV61q&_wL(0`0*HD$FB3|Fu(^?W4F}zTaB!F(0!SW9LJ^e3JAi5!gWe>Ynm5x%)kud?{j8jF zHcpv&_T2#%vuxG4P(G`F+3;ixl}x4^iC`}YS9uYJ63}xU_eKGS zWe@fbqEcvF&fTE}i&TeZBmDU|&Z(Dg4{oHwVZg;|&kvHn^9OhMQ%y$EB5{wrUgjv& zzB*3k9HQL-b?6kyjvc29u3~wzW3)r=3P!WHP~-xWQuC0o^KpWwTK?Lm-$x{0f3Z3Y zzh~%^J1$A?28xl@=F6&S!D-tG71lHzt3ydcWHs}R@m~pWk~>I z^5R|&ZDF>2*D~?({`Kn;hO=KqOz@bv)KBuigj8tjSl-r}kz5@*!*jH!7Kuq$D$Y;{ zOjjR}v#=NDkD4z1$zzHrmvDZiyZg;VFQqatqA5@NUuQe*f53599$IZMCN519vh*-BUG98%>q$fZ|W!fq}klP#pL>K zY$SBl#!;cpnqD{vhlS>&%|lF8Ycy=*U^^!GO>YxRwyMAvKPo_Qd?Y{!#gC=^h)2fJ zdIYD6u@VrMB1I11!KjZ9_9JjFb&cg5fclAB299f*Va>9NDhKb^V76l=;~*mD@V^h# z9bi-wBkKlR_F0}Sy8#}^>ryp5+wTjAd1O&ovJMK91BB!BmP9D0BhII+{V?LnlU}$x zbWfh;321Gy;UrGfJ{?bu8^$XQqF2pG+7veAY=%M?p!+1{B$>vA z>wyD6fy1%JImL{20*@SLbW>4y_oBR`f^#WK_6bIt?Zlj9t$yM5+*x)G@$8;1@-Q(j z%#UcShYDG8CP_m1@fGQ1-Q`kj+#r}LoWx>sa~#!r@##t~?)&II3liA#YA!Q=V}6am zqOZ|+V)VJ?-tcO=&F9v5=P})AwAEzL(XV7s!0NdFq^o_ywg@^GC(YsyPcVYm1`e<==8_g2`b9*OR zuuT|uxFNeY2EJ9+@cDdoOG!d+Ky=9CnMmRN$kwc>$u1(C2l!b56b0`^M_3#5ge2s9 z@pcnXRBqDf4m78_@S5mk*pe-}%^@a-pM^A&7PflyxBvrl(u?LvH$Obl2b$m50=Yw= zy&*k4j?8%}XRD@T3zMR%{w`vR{8P|pj7JZsoxb*LZpsn^r7L2LMhweM78Fk&$GoWN zr9gjcu8S9mQFzT0?IA>vbAH6As&(EzeyA6ZS$0LcA^8jyLir=7Zpx%LD;k&-{0x}Fd30T%61OHg9L(B+H(+xRHZ(gXg9{c@EJlVYdcEos{IP#W zPQvS|31=EOmDb*^IA0^}RZ07_KDl#yzrGu_;`KytQdh$QN1Re1%)kNGRZLElgf3n% z<&#f9r~ZPbOO=35r^b-rlFpsj>h9b_HX>A117jnk*1-{hwOYY@w{c}w;~HL9i{D{` z-i!`7hnuS;c-}6A_(`}Y1hp?R-F1A)A~JuBu^UU=lw<^$N%46oU(C3elU7M0diqt- zI-yvH<%_Qp;)Y~wud#~DB|5?-pPLNu?M7Oq<+CLxL9ViijnF{>NO~2mPmj7j*FR{H zlDIs=Z2dgFLHk=(q5k_V$20;JDX8+^V`)9a8D~Nu%Z8-$IKQ&)Xle>}|Ht3ygQ`Kl z1f3SDhJ#LxZ^DYvsZxF~nQ z3vCC8Vabl)+vV$+YzGSNsF(bD^^rF;uis7oIuGnRbZ;4%^9hlQ8GyVIBBEKl zh2o8~?Q$k7k`D^@f#N+<#%HqC-{wN7>^-^1#j=U;fJg z+2QG*9_nGERQblV=b=I3+!IJ)N{r^Z z_rJ!*mV{?qpN)!F4Y!9n6fW+dnnx5|l_RQg++4>Nsiq6g9$h@vrH1?;0P;W$zau8$ zPv!^!`swEkoKQOp3D7_V|9eyX&JilWf1q=dc64+7-UiHR4-P-9z;_p=`+IKB_U_!(hr$m)xHE5K`phRwUIY4G1vCaR|+M?oe_y z#I3wQ0YE2Sa=^cF_nVjVI|Y6G{eIkQPyv&@TExuCxWZ}HwoeAt zEi#MNzonG-%f$^)?c(JPjsFmMNYQv*H~cfmujOJxfBV$P=db8+-J^*4aIR_=uO{p& zmBbwoC)8>Q06;DB>`BXLc6)uf#fHdg9c)c^Ma=W&VyGiQq4Q%lxLvcaTZ<*c*Xp)& z@6rE1O|`{Vg);6`jqyp&67g~*IBZZI%j&B2@21amzCPy-3HO8te`m2~{7FOS{A9Iw z3_2CA6hn8f0G_YlWg@a~y#K!(5l;Sd%l+O=kAs!L-S3#;beH@qjnJv*G{?4%L4@}6 zyw@PCB=%mfRAuqFE-hkp)(iry50K`2HUC5VZh)=$OXhBXY7d*^Aq4?%)Ca2|!5TN` zFzH4BKSt^dZ0#69T)W~p4frOc#`rP{zhN&L?yDRzns4^94#a2L;Q}NJpxx#$SHVVl zZoLg1t`iX^uVOI4VP(e@PLI&$l}OFb;#5WQ0oiOmD8NflN}*Kz$6^tB+xrVJ1t;g-r= z`zaWWv3`s&zt@<+0^nT z--%dK^Ssx}KJN)r627RyzlIcsx+8@}Y#2ut=6~qi4KVJ$nY#hH1pp>Ue}M!9Zmln{ z5HZ7kiQc2JOYUVFD*P+JKU-+}al(H>iSAgM&6UOIj_L2?tS=^vz5qPfLa^CqZ$yIn z{)+g-OD!xnM$B)}&n^P~FJ|4mzx}N$WGz17P{d8tueJ7q`wbDnZF^vMrNe`iCZl=3 zB?qR?jC|JWeg`A2m23ONOug1W0lY_r|Mo4;Lk05>RN!An7}VE^CvBq||1O9-VaFB> z^i-tR!1s`}KE@bAodQzIF@cKV#7AF zivCa14X_=k%FYcC1VOh)RsWtE$XKO+f1sb2yZFsg5Cj!H{O3CcL2ybC1h?xw8bRyP z2!f!d|6ZimSM;~jJO@D#%nyQKsr&7v`WbD0&tLnXUiZPsoF%EGwM zIp}t+?b=KAH&^T58xvlCm7f3Q1m|w+o_)^*=YJydO!o#sFqv|_yFH9vt3eRls`szQ z(4@W387JYUz{KZ`#aK@i-hd|_b=TB^1<;@)Fc z5Cm@ug5cL~Z`Yh3uLXu z;e)-Z&Vq6Q@NH)mthnE^0NG9SydNQfgY_OPoaU4L0FrLD1j#plq=#dv{?0>c+1kLp ztKI89rT--v91q9btJDWxQBo_$m)shaT3Ezv-57H$7}x8R*j{C*S9QWwI7D)cW@6H--wd&*r`juHO$OveBxIObId|K5aL zNzMNoJH|SN1f*DR?p92FHO*yhYaI?xH?7M`s?(tW{=$d5H`BYbQt>h~IiI z2LfypTh4#d=Qf%?El1&R(c|kQu{Upb;M+&*KcwL0fAzEdkgA3DE+*WAgzshizyQPU zNah2o5FD;xvf`e@{HO;i2-uY{P&Ex57|(bwlFqpa5=cB(L2$RnT>YH|`utyVG-UUB zGE#wbUIxc{k$d$60GKQ4EW%8oatuvP>mEHr|6OTAe+{JI&qtiOdsNN;9wa5{2H1}8 zWYQa;oDmZ|KXT)P++07^1q<@530H&4E2kIZ4M1onqPM8zia7RUj zzjYx?%5;j+nJFLS{EDTB;BB-Zbm&|K(BH#X1BCc$fKi4s1$;O1`OC;Z^~r>e1^#u? zP<%0zE0JPNCK#hR|7n=*J_@+Av;vf!wzfACwDk%nlsy1A3n^9nzbd5f(RchXQXSA@ zak}**&sWXfz~k{q@bPZ???)ZDvS;2AiH$hQ@qkLgHMPP<$2)6eknu5%1cg3B=kWw* z8Cj;*f>y^P`p%DZIB%)$4ZGKFZjSO2{x#*@M>z0BN{d)@{f}1JaI#t;YYe98S2wMH zTkWl+-2mHJAu?$Z+$#XE8+{iE?lGaehiU;H$$Jxj1K3m-_c|m{$k!LZXAJ?fc4^1a7@Gr^cU!W$#b#-pe?$X3*Amn7-Cs~+DO_UXQz%d?c5j{sWaRUL>6m#T-|5d6Wlyv{!t@m1`;A2muH;K0fm=Hft zk2xgIvp09gWaKxqJWNomtN))MEOxVt(r@(G%Kcp*$(Zn;`(qpZdlR8+c5cqru(|^F zvqHt8? zNJ&^<7PhRW&%88$(@@TI0wS%Zv>W16|B%jYH%!f}FWVsvxq4-cHh(**R&s_j!`i-H zf%vPRgA{-=6T4|6HzF|+DA4(VKAZRP`?vr*s_uPigxw&m$G~|y-^MHKckTgRtH(V~ zuo#FrN?|Ccg|)oN`>TXHptX0??*m#<5` zzM{8I$NKJBXX|GhB)qnp>lf(XsJV5A9^4>C&<8niQ9|ne|BLuSOwkSgO#S-X0Y@2cbZ?8%pz&vUnlc+LrsA1>d{hRwEA90tEDdAtAWHb{94@- zbtn9*x-R?bUamPyNZ6ZH_c<$_#qYAyE@_T1A1}4 zW7faRRhjsj0~03rc12zZikO#a zyspaGGf7v>$Xmp|raW)8?yW@;tCI=J1CRjIUV=OaW&V%4LAqU=%-jHJCa_)cl6?u= z16N{D^brvgujLX?IL2`kQrzQ*s`>k83>=g2&vG`>Gx}Ejt*p%cfij|>(43b$4=H4l z6zN&X+v8OeN&6h#T)&>1w>nZNfKniJx{1tM1)2Xx~ZfPa(bHX>KPRxBgYto?or^_=MX7W)XNP z5-)KP!&`{Pzhe-2Pvc7sB(r8M#9bDxtI@B(XD^iBLA)2K_=;O1w_vl=nhHCxY9 z5ZCe5eOkw?qyOn~eKIQwlw`4txaJT>;s=q5I4Oq7mK?+PT0+&`_YP&yU!=J@SjD|) zt<8d5>z*O2CPXIq)>G4Lv>yBVUHs-c^j6@bo9jzXbKATNLE|^~TtYwY3e`U7KwP%o z7RYxG;yP#t8tfVi7F<__N4vJKaht)+h6e%P&%qSvEB^HwW093rB2WQ+4(VH@mK?w> zxxHlWwRaH)`X(vxW!8bKlo`DriIrGStpIy zYEmg9{`Aif`qkSt16mtNn_5TT^V)=j+U@mr-Qk4(v?@{?|I?0O(xS}6oN%`4kM}2d z7n>Uk^Y35YYxx+lo zU#XfhNM*fO5H|Qqvo~#i9#jj~$B|fo1Cd5g8%NH67^zwRDy9^{o1$Rh(DFUWZ=@@oCtD*(-;H$XMP_cbKw z=h>?LehCTYsyL)yPVlg0ebBR%DMZm}{2?SbV>>l7nn~?7NQ>P$v<^Np!d$7E=!1l5FQ&N+Ydz?BtQknN zAKN+%y560@mv^IB9rxQ3(41|AdkCum zJ~IK}Fhu?@)gV1XJSYROm_^`UKdb-^1?i`EVUo4^no`042^E%UEk~`ia!c!@cKBiM-!X)BteOOmSMv+*MH;Nxw|NQ)>iULi zC0XU@_hYmQa;nkU1yGBiwVJ!W6VY{ZeXEkk-7teF5!r91HFB3qOjorx#ocr?KrI*ma2>$=jc5Tjo6p^Z7{fainw@yJit!Y=6^`v4X~Yc zqH8xmwFB~r^Y1J~{DhuCOtg5gvNHa~YB?BX-`h+XzBc_XG@JjgNVzeSRsoD^@>UIS zFFIEPi~^k>>Bd}_u=d0Oz@>EO2FN<_uOC)`DpDNiur8i8Ya!guD$p;1S{sqQS5fQ8 z-bi&oW8`sT9i$^;-1s9Bn4O14JdG6Z*^!9?Q$4$K&g8CWY8OOgO!*^- zU)0cG1qo951rprz55i!-Lii8=FoC(N(t&TYdqoNX-c1$2C{3oR1{mE8KUb7)o4ASu zG(x&6Wy2o$*U55mF!{F&E=Kksz(AZY^=oaiA9v^ZvcY!V6J%Q<&sTQ67)ej=M`yuq zpYzK|fL#EYw7btb`oGt1fN{^+b#H)jN z?`tK3y>M?;HaJVo-vq~h5jF%H#=T&fBU>UCz-V-R8o-ig@t?wB$R|j6e{)ZKDPq;w#aT7V2^c_{R=>k+&DpmLD}VvE%9tEg z2G>Wv7Z4D4tm^Q6knz23tTzJt!1nEWF?+n8a_>O4wf~7IB;9rcY-g=V-5bC*Mh(fA zeupyPsODadu^3ps`8hf%e`R$1q`hp66$#&Qg_}PUo>M9AmG*jjrfwsieZbq+4h+;0 zcg}p$`OgCdh$z!qFJeLHgl3IkXCsy49!^PLcPUzblG5}f64-cZhu;~+z`st+<}}2o zoRomxGXH!2b`xR6&9vPB z|=cD#wu{BL=_U+KbxX8q1Vg7E$l!_3-U>*??k+Sgua^&7Jzi2n6R!0M_N zzcs3Xf1N=Hy_t-rT}y$M5*Ao^uehDXJG!~Po1zkHIrQj}^nxBYVwJE z>urRAf1@5eXX|(};%l64Apngzz8VPv9>?(%#LAgfcgQ@3cjX0eI@b5 zc*;Ep7W=JRkb+vD0G>Xg>-`S}{AZ_FbK3`bM*rtAI%NZ#9H+Qe^(Ehnv7jsClxi_t!NkmkV7cIVb2AUMslnZUam z$yZ-@#MZiwn8R0)E-=z~{XsE+>WU1XZz^M2>*NO59ch!@wD+rs@BXhXgh(ldK(h=s z83pZW4$bEDO3IDYOQVclt}nYaPNOM*0N4yE;**sS9tIHiIAYdpRdLHStS``A%jaPp zMhhQ9(vITVE1Qeu)4)c`^hY)OAx_XP0p6Mr)CG=FmUE&<-@L|RZRXI~#aMD$f2VGo zyAWEjx@oj#*DW65^-uHe#us)3o=0;g!2@Jt{*U$oh|8MlWuIGD)IFV?uBb)hhUk5& zG}q;BjFHxp1=CoL6h*40Tm(x;IZv9w}?*Jgv{Qfg^&8-)}7c*KPv)+F@^89-cR!9sA0FaIuc8k8- zbwA7ddXEazF7mi$a|AiA_b6#a#ima__8?pTcO5as#sy>c^IpYh&-*5T*C8=M7n4sb z>55txRu1a<3jmNmc(5S8L0n&qVicn{Mw%CcEJpx-zwT9x?&%@OX-)dEKC#bV0Dxi? zqZq|#55lt^t*kKpyjL+g3rt&&yt3T#1b&I0zW@NmC`K`gQH)~Dz)|!GFGewnQH){~ zqW}QKC`K`gQH){~qW}QKD8_Ko>yAiamj+U{7bNPnJm5W4>Erdi-nSUVC`Q3hi&2b` z85T4%2g#pp0G|bpLQ*n6?gK;gr0uFZX!gaiD{4j$#b|+7+V!07Fl})@Que z`LkZ-!}2`CKCCqBO-NG|OOregDcrFuVKbF}?41C~-Toa?+3g6zI_h~{Usd71dzbee z2c8vlc@&nC!Sl={Ym`T;<6^`uOpygJ&`nWHe)qts4jcxYuL5xrX=Jn=VQuvw$F&2P zg>+`~O=fmNx@oq{?d|q@d+&{b>j*nUHjs{Oo$fm>d!|hCQq|E?&@*Y@7=cTkv*O*W*rGB$~o>5!}fPZsZ z?-NMZt2YDZATa^cId9b2NZ)W@b9`PxMv?Y+L;`20rgB97YG{Af4{gHhUR0&=+Sh zNekL`1nwGB5D?X<=jmt5Vt`b?tThKJf7>bL^$#L_?azw*-k`_YNcr9@8>RVfyN?zY zfZ-grNb?=@U8q{zsuaXv=WR*YeQob91a?->GpyQPDFTM#g#T(43J<7Oc(Mx0MS4x1 zARx{n;j3?Db-g|Rvw(96OG8`y!#h#*87o7E>90OGR|Lxca3Gn@Q1-DBFNaGfuc(N)N>*PQ`CJU|v z{u{}1%3X;o06;pS)Ib8ztjkiZiv_atf2BlJrMqSjSv}7%GO}om>~_Nc5yZ536VmnS z9K`hBPY{sTFgL~)h)L87vYIl+^V(RBhQSA%HIuaVylFKsyJmU}d6Vy8_$Af4{n%5F z*CN)!n{xyKMw_Q90I0_R+i06%b5~Si*7^{{LiPg31Nu2^Z=DRB-iP}gfw;F`)Q`Pe z9x&04V*1BK3+*lKm`u11oiYWodY)m@VzyR|*LtV76aELP@Smj`@-a?u+WEB+1iKY| zhi!nbDHF6Th{u6_9k{8t5qRSk{a(s|=Zqr&C;^x2ewcO|w+Yv2#C7sLwW8#aHB!;N zwmHMhhr5kc^o9RbWmKx`<5Ew*9aX@bMe~fYIFAz~>yl zFGYlZ8Tct;wcAF){zOf00;K>YrltUXmd=amt<%1V|5 zz#bM}G<78Agm2B5eS^Gy9%+rGFu^45qd&TKME znfCLL;FN8Eb@jX&lEeIG;FrJ;kiahg8>2QlY38fjoEw)Z@Fdb_3XIY)hH;}Zfqt3en%Vdk)K3 z32cf)0U(G<*CmkG#MvZ-xAF+ zNLdIgv+C~M0*C~N>%o9bEdmhCLUkq*-0BNG8Bg6p>AC>2Lks}?0w0K^a%>w}=(ym& zhWlg!@or^JbMPFp64^jejF_3z+tQsagnT zMJy04RX$5CVXbTTGt=T61O|+AuIrOm#(pmJ9k8#9uVbAi#wGhKMh0 zLnY*25Ss2yqx}q$le`sDy)I50O`0h%PE0RaHZ$zKGozqRr>SkgmD!Ek0x{z z42$sJSP!4)eoR8)Uk46Af~6X6fBzndU>H;XHU$z zRm2^1AKAMT16o_<8YDN}Pqq23R*=c?0ss@vo`ILTxQ|`qb3wO2aw7*>5TesCE$odd z{88cliLxD#~B3scquEL`!-c1DiHw~MCg1XM$ktK?$jMfcqjSy$%q2+xrq7x zHsXWa%p$xFp{ux@;P;C$Bpb>Y>nX}`r|7*io6iN^1Icq8)@WuU>a~$*Rb#~ep;}!u z6{|c0iQ1Tfj}T5$j3yUn$+hx2#JF!QddU^02q-zA1T;iL0Cx|q9oan zlgjw_C#)`28N#^?kSvECoS<75Da(m_X4{;W;x`javw0TzndlNs44Ph^h^)QM$%BNo z{o6HgF$>6x5%-7}{#PIw6r)-NG5VgC8adYqwQj5{{o)%HnuC{32$y2OTLY=mcdJ^v zp609pCiJUrzuX6W3$Y+RSNGKRYAtw%uKN-PE>;3pBd*l*+jw4s=C%k>$(L_m?tdPJW^-xV!r;-6|oSfIacy#W61&KU$vl-%Ck9C79SLqD4fyb8(sT0pjq zc8ZZR05C@M4rQqADy{_)!hTdgThCq73jG;DOejj+cgW_lp_>15Bk$Ni0(qW`RKPMI zGm}#M%;FI(@-vMD=%p?469oZ2zds3>D657%Na4SW0>HJLkYA<3|K<*i+WY;_b>PPY z$mJ8Vq9uooePN`9x; zZ>06u-QS{G6-IGAWp7I0o8J z^SB9~EtD4K6PmM2NR9vBB%o>gopT)Mwu+l4=sb_Q>A#imw_wi2gc%P>T#1kAHF*oT zP7Qfpr)%_P!X{qc-(68!{u1zM;17{G37qwy6(4HVY-bq2pvjCmNQP9DM-;+;gaCkT z$mgp@+e&a7;Vn_^GI&e$s-8`he(Uu4?}C9iZ`1-D@ilD7CR5)RBDfgK6%z_I1@TXM*rQP}jIy z4Lz^*y&Q>EiZ;SAA-oOZ!t_GV^MK2Nk2t~aEs1H?G+?;hKlRAF6`KD5MmZy*e@N}c9#0?A+Ngi2pRg zzpC$^^ayg~0ZAJ2RGqMEIOU+Cyzj+GFyJ2ug9v>Gx^YqjDumqt%Ml=4j1d6<#%Ok= zb-Igf;7fp?5JvRJwX2*!BA`!dgVueS+;s+w983D#vj|iF$}Q$OKytFzjuD76LHPUV z{1XUMgJuyH`8k#_;IGr~H4=b{$srN`rnSEVsYGbcnYKNx{m%Q4fXcd?i#O0YaB!T2 zf7yY)q(@%Ec+e4aZpahuCRavkQS6EM`@P^Fw^Fy=bOS8LkOBZ(tT}|y)TK7(T}0;~ z6f+5b3vn03p?3SdbqJfClm^JKmLcgi+dE+sM3{rU+m^s9Vl=&RYUx|)ApBe9<(Cnk z_R)j^f4zcN+66NfDoewJgloHOZ1rMG~bBWk5B57V#MmOp|t?(Zf;b;JC zp7MZF0-7@+_W)uQJzllp%9sj%FZ|PX11!c6TL7$Aa;ruorV&31+?8N)4M{(S1o+Y7 ze7kR&1{isax&o2Y^#2pA8OY*Y93{fmzS}y@WMT|xX_$)ql#z(zc1dOvaBT{GF2foeB3|4=Ui9Nt*vKzGpebJ~m-^91;&Q$zUV&`@tv6G$*F_o@dEs<8k`)1Bj=tCtwaxi#S~mk4 zMd5Enel{SCLJvrumv1Gcqxt<-_$LXNxCzO3{(@e=*?{NOp7#vZ-0RL-@j)b~d%YCD z^EUT8#ysB++&{uhbJT^uDJ{!^zY=CKRoa|yk^a{criG5X*}Io+fWz|q{idmr0RXiQ zf?+Pwy7E!l=iu7;YZG=5EVU8-gJyUo6++iUK7W?`_gyjHmw>Z%PTDmC8Wk(jFctYZ z2Qe#drh*p+69-_7eH#_hzCq8;$_(cxcs3Jy8v&;iR*suMqW-_e=tljIgXZto`a4KW zO3*=RsUR+!hZsMTchCaxETgw>fP)ak(Rc9H&Af(Xxz+o%0ehJgGwMzP6eF z6ZkF3`kC*E)Ps&o1+@FF0PY+D!D%L}l6(HO-S=xdlUL2nNsZ3UfJR08AGRVtUmJpX zG1|S<(M1WV`S3bgX92XD-?JTHsHnzy5~CaSKQ6)_XOTGriQ?ZThIg5uxAnVOX}rSt zj8dET$*LP*Kj+H?|Dy`--bfh0n`ADc?e3Eb|9gR3`mwjONCOOjKhZjR(^6zkKnkE( zw13o3UUeJk)3h%r1n@5f0mIN4FdA*#h_O^eqmjp@8BnyykEJ?Yghah}Q{=~L{#%is z!VH2S;e<<48@3QS|+flkoRS$ugvExi{(i z!aNniK-CWdM={tKm8{1!YsR!hy8*Up5Z9?2pv6e67yyt5sX6mQ!hQ^$j#7-Q?Q~R; z*tgfKqL906%c2q>NLt4mXrBczPc^fbsqpp-+;9Tm3&5_lF9c))XBDIA`}EC#Hb?%@1^^^3!sY@aJ+w;M)g5Cjt=*=5B!g`VtcB@`n@} zbyn{N@UZUZJ&?Gsc66{7a6Vxd*op#(Uw2sNI+77F-7m{tuK>VaGq z01OCSr!xdOhGBrkMg4Wo{w*@f^))g-DTCM?ET_&1Os*!M-= ztEB69g$jS4%DJ@y!n2S@K}Qn$fJbrI`(CMQ7&Tv;iGPuT+9wkRw9Z9}DE$cOr4aR> z+y5^bu$9B)UAx07Kt|VbUBYz7cGvecrl*|I#8i+>&<1&y8ELv zMyVN4QRFAtSfQY0p?k->3H|l;gmYa&>M6X{G1d|i^}l??tVQE2{B2DSMw&DEE_{Dd zuv$^tZ$yNMrN$lY1fAa>qo~~2dwmZ0h619g7)yZf2KYR+2Gm;!{|N=pZ)UVzBtxs! zWwr31AYD7hk~4cGeGX&z=PJ_7v%yEdECEr5QfVWVh&Zfkgc%>J_pXcA}eafm)eRd4cV4UExCP`EJ_pzAo?C9!&x!&QZ<(L?l%dhAR$WmL3L@et`sfr3w6d0`Y?$7ZC!djhMA-oP|H` znO{Sg_iw`O3Z&3ar*$_viSCc{kf7)iqgN)}1oR_Fm)O%Bh%M9X>^e9Ac5J&t##rcow{)e&u=4D9FL%^Jwq*ig-7WrvP`obwzM zMSgm-?3_b$`R63s|6@o%*aAWm=(T#}1+EL>-`&1g8+fl z3e?@)jnW)>-hZlEt_}xYq{>94jk57KBrf2~3Zf{4|L_9!UyA_&R^(?Y1(@)r!eK6b zTS01Fe2;XQD$@$KthMwKosZMoSd9wdKN#)5u7G?y_YP$wwfEkXJjwzC&qo5{9z?Q8 zE(9*q{Wfalo&&cR{(xBRPSW{Wh@|`N;@(LYhj6tcQKfal-o%ovTw-Xw=+yWj7Cj14Ey58!XRJ6!}RHnySE8l#!)j+)I(j z|D6>KtgrL%3q~X5btVXZ+lyr+7&&nNe*%g5=!QqxTwSmKzXjM0u?iY^9>tyy_Fk41 zaEBgCBG!WIl|Fnq;i4m8ACPR;W}g~jrq4=HH=Mw`cMQxyCOkibbiVqYGE8fi`4>XJ zUtGE12_!21%06w9xFSFH9zHLBRs!bai~Jl;m=;xcVB*CLXOW*Y+J8;8H&W8-2C605 zUYgL~8HK3unIin{*|!9q8{vIFMPih?eLr*US*DBavv%1tqc9qd?^S7^CvTRMLimpu z0KjRdKSs>;y_)LHg77CK|9M<|L8i%{fW%BZsPDECsi!~-@MQ1)6{JB0Ia@=DB0uTr zzobC!_LTH$<+7jT=J;;livythQp#guh6sPpGY=)K>tLUqOYd$PD-nGGVQ-CbND(v| zw{+rGdGbY+{|EyBkA~kzy7Z@30HB1FRb9^Ll|D^6_ujmFt^$F$ca3V7Sw2fqeGtv#WluSgv5uy=w$~K zV}<|#L&W#eTf-7#AipF`rK*j|y!&QAC(>MI@&P1jHd+$Ww98+rb~%~xvlK;sve{2l zs=0R~2ucp#Xzk#DIfu}@3**B3GfUIhhM7v|! zo_dSURwLcD>SILgNtywjP3!K8=VuJw0$?ko z;mi$$-RwH$`CE~n2F<|JU^CgAUce$Qbm@{^S2EfeN%Jy^c%cYcKg5%voJ zn1J2`F+V>!!5WxlV!qRa$5r_IIeHPHo67d#4|`biBp{%MxEd9)c&ov;~@&CzXo>`rqp@jt1ik!2d(PDoAe^;OgG zsd`C^{LlgfOcDn4bOICSAW`s}0cSV{I4Z*!|6NGOvTlW*{mxGbO~H*m&}4wLF})1Q z;7R+;CHK7JDE$4}?Bo7l2L6dOXd;JqHIQMgq@SX^VS(Am;m{ zfaj2`Hh?jcM-ZRzIQjiO6NzgEWDKQ-)YHBwVo|vaSe5a*nJKqUQRF9!b-f{?Ep}re zT8(r@%R`{Fd;ZyKjcTN{z?kqaAwirk8AIW3aXPbEvsTB=h|4A~Ma8lM8fuxbB2ku7 zJE|#y#Tf7?_kpR@5i|a3k_9uFu=@aFPAxH>g&O3dBEdIkZn85#d?S$FGpU zB4b_`>3VfjUs(joFDXYsL2Btv#%HTKXu{x`7akUf^sodZ)`u!lN2hu=;@* zeV>3wIxq)A)yESSju>@DQtE=y+B;uD=pHyHM=hgF7^n$Fk)Lko@NvW++^sOMwY|1a z0wu5gJ}$!Fw0>*QH4o{Ci34aWk&Gw$q^!iH2%`UAk8De@7h||-v+j6olA{*oyVX7s zY?3|!<5ch8Gr(3|`&PHH2H2ea8Htd82JjQ|GYL0<9SGeGwjS>R?o&&IX&dbbGFs#( zaN55w@-tQJ-&mTdD2TXagaN=fF<6`bBZ1cgPe&@&c};s6;-2g0@NTy{l5YJa#Pza3 z{}v5&#-BD12A-Q{Frrzn^hN9NQ(0>m~)nxCYlVLSKlz(b60 z?8~SySdie@FClKLR_k>quwno-ZC1Jqk$MqJfwL5-oT#4{fq~;UOa{CfajSPTo`Se$ zEBy)@>C`7+-0J;5KiyT8*1n~hT#k$|c;5_YGd<_cZbQdy@_v}i zE&Xr>Vy0iD&s-GwNsi2yBAw3Yf`+|DIgax)Y8uuN!2Y`TCR;G#IHa$}X1^Aefbkh7KR%5VgW4@2_%!~q1bC~_PQTHi?-THH!f5d-VZHxRCsy-&pU`3f z?BDec=R}!JKO&5cflbjmzu%b3Oe*q|2Ax-WxEA0y9cZB#{ozqZ&^Z&stN{OsxC@pr z826!#6Szn)9~ z+-{|utsr^-{-_Oet)@Aehbm?B$ildF)pi9uQbf&pJYENd~5^8Cpmc<#9U3jJ}rc?(0i!+CMf;)PVu zMs}Fsn@Rl-DzvsNrCHNbbdL@K0Qf!up8=kWm_3JmQEk#JgP}BFMB1T=Qzp5pm-8|l@^-=ocb}PAg zS{rF%MF$}seU_E_T{{b*wGLm&@MnsZGW?y~cxQEhL|Rf!@H^j_1u0?*5YP{B_brdyb%lK$GOHJDDon;lCqpoe$fbSp{u66*x zQeAIW;5ICNc3ZoC^w#iQBrjIbbaw(C^o)oM`aGT8Ur$1c9lcK#kYSE%n9g9Ir_J~8 z^g9Eq1JH_TtfqCLoMF`tP69ST(r!*-Fgd`3cph`zY!yNAGr7o**O(is{cH~0o^Yc^ zuU|_ww%h5ihGW$_S0I5!=g^%=`wK`}-0}!pe9RU3X{06h7KBZ&yb0B!?*pWa_8`&# zlUQT&bh{aiu8qWSZKIz*xeHBsmlW5Wk7O6!F=QaX&&Q^2|8`>;%Oc;u)%gE)0IEYU zVZ)>ab=pu6=F6`|Sf;lzL!Ku|#lV>oZ5-8@t()H52yN$eeI9FDQqAgmW#Ww#&+(XA zfX2b1Q$oz=hXCJ2tZaT?l?g3fZ*`z&*yz6Ar}>&d8YIPOzU?drrIgQFa*FG`0qv8d z^RbBo_}y5>vdH&uHU57cfKm_?*+wZxG}xsvL!QTibDp7S-zo+k=FWMoqScuYq~r6& z7b#Qv0#b}62?*=;`hL(;w;Iqm?ek55j{q-2tZX2$8rLJG&2MDp1*OH&@!?9x6MeYn zFzk~g^Ko8}``W<6$AMkx&B|oJ_F)CB4;KJnd@L9Jex52~=30StA+rF%cOjGw&94E!L#tehJud<_l4) zLYBkIzZU?Y7{w??F^bW*VHT51fH&%A_ad%E>#A4?r9O?KQA9C{QH-?OSH)=(g$7)V zG{c1Z7VfikR2JpwIw(c~0E$tJV)Q8dt=EN(qQ-nNx*0xO;0`28zM!Q702HGb#Ta7Y zKL-i?oQq_G{E;Gdp%~rKlZ{F-9R&a=Mlp&pyv(|U^qT)6u%~|hcHjj_Es8>WYZhpE zn6isL8pS98KrxC@jJ^xB8d9@hPo#QhfV9B<2=F3CqmfrXMxIp=$JNqO`27n2P>eoo zu)<7Rj9$iiG?$pQs%Hglg+Qqeyb{=%Y{~=Mye@w6 zjB=g!VhnazlNrk+E&(?<&9^bb_nMW({%B73cs7zc*BArPRUs@jy(v;yY!yX3NdW+g zk;(9aexCmS5&f*#WX0%a#F=Uv%14HYmjv!_KR}5I|ASQc*Y&sh7o*U+N5iTp)PPfgQ}o}Xpgi1*w8~zU)BM`JyaPDH3ICgbf6v$|a)#c= z?(uctV20Zj&@Sd^JtycVks!x7e%3EHP5}UlF%|%zftUyn)QuNq)V7%(Pz3%+F>QU< zoEsegTmb-N)FR-h;&+Ja?rgoM0gyf1o(=}g zdum9~;t5Ec(LzTrt(21J`qh*Uj#kj~C0*~kX>P&X>3uzY?1cnn-bs-KRRDl79OmnP z7-_EYEW)BK(enb}G2r%`wOtcraSV99$hpf2)2E`Fc+^dI#&FhBX10xLx!wx0DR4e; zJ#g0uX)2GCjT>np;^RIN*wTGI4SX5+Ht+?+a`jlkd*(5RCj1Wq-l~Fl68Mt}yu92v z2DmQ?wfmbR&$_ejft`TaZtbf`1*=gN5Nlr z8*DRRcVIWwR^|g+N3Odc_?K!qzf}ft56ynWQ3e7A05(ESZqQsDOyOF%i{J+r2jxRgH=e#Zh!9i8Se?0#6w%|LzXYw>gr&{951u2L$G+<>xR30BZwR0<-t?*NwfO(|GRD&3d^CMZ3qSrU;pNvxG!FuZ(DT0i#zIHHaLo=(DWv*iafg z5O^D*&)3H9fwO?`B7v&5=JO)I-|3!t3A?zIztfOdn!Gh|v*@Z!wjorD+5ZA|N5Z6ys*w%)GADQ*S{O#tCUkSV} zLaY54mKAX#@LYYa)ks{x+mJYcy!VDr4L#ZcqDPQeh_A)0WzxOha8?BCJUJ0jc&bP& z$c0KHYXe_J;%m$*G6_5gd|3I$R3Y*^1At(J8P7}t0p9#}9uoBDg?|}1hBedtvNWL^ zli662{(<_rJqI)gAwI51d>PF_Kbt`T-=4=kCRQSm4}@hd2a&>vys?~)0KB@H3@_k}hV0Y^EZYbL^D6c|)#ZjMsVkalWF5Z4t70<2qH#XaK+;9E!(_pQVS z9&V3BF@u;`Z$pB6UJX3i0RemdZGmeQG@P3us3~V?8HxUG+Vd+pJcoDHWchABux}2M z_kE<&h9B3d#&((0#(Z$+FfwP`XU8Cs>Z{b6mRHJO(h-*$IHmLdF~rSR%8B<_e}Z$} zo~!5_Emm+el>u)#TJKSJGN~Nl@gD?!r1w2u-+ie9gXw6re;NWTQ-WCjX4r2a%a|~nXMv&zdI8~3ld#Bvx2KksPgB+}7kL&ww zj<{)#)PMW$zpg_6>X`Sej{tuG@kN_i`CdnJgM5CU8u&Z~%>j~eGfypTSzAW!olgL^ zA~gMs+DqJ5+?kQrnxMuZHDs{804jyR3aNR`o18+hI3T#-=_?sMspFMhGSxu_q_!8 z3!w{OxoV7GLQ=)vs}=!Y-7C88-OROV{W~h-yfb6%>B)$j$}^tWc}`Tuf5bX*KqGvK?3t$t6KR- zfDa*QH-BoOVcY-rBy=B469(~EuJ>wv-y>C+nklg2z|b8kh|&Tdd#BTZ_YnFrvsg24 zSt~1ps?eOG>7N56$n4pOnK|p{n5*~szB1D39)8=N_wdN)tJMGcyL>NYQDsK%VO7)l+fKkz=M;mps0MgrAT zpgCS8I`(tt=kXjh;3W#wF3IROrS66|5sQ!PJ zTFWl)QR7Z4{6T7jd`0<8O)Uc35mwPG3jK5m09q0J@`%RQO3RoN^SgOoAH8n6&mh1A zqYfNZ8}NN3Xt3<83Kr;SDUQ_`aA4&kuXj0MT}M%?V$$%zOCji`nTPN4v<0&&!M?%Q=ZpHuj{8Jzy#X9j(7uyQce3g z8u%;S6%6quey>uku;DcC-$yn`UTHsd!jvwbZukh2-~9-oxxS19_ZWT?=}0QTL}M(qX8>IbT)7Z$s_M8YO%NysVtV~Sc1d?w0pLHBEI@cPR}p_{GBj= zy4yWrYxEPsQoTl(wHV=1imX*yE7j(N!O3e!ezO?;0{}i?cbc%WUN?Lvt?+NSypX3!?Sk>=H*9YAb-e8!Bp|Yz)Y)!@ zzXv3%k#@)i0QOLT{TM~Zzf1vuX%6kun99TlH|2eOtLyRx0VbT+LxR{^wYs$D+fdK@ zNkr)RV7+J-L4cI_{Sje_Ut_jQkzC*3lW&0w+mvs20K}g4bA+is(=o#AHa((DB#yb% zBI|ZggfTQ6z$z)2I$j0-*CKOviUNQj1!J2+TnKrN**aH2hv)ceANGD$46@-sp>M|G z>2XH|Y4cP|dp43nHc97>Y~elmvx3nC87mZk+iJWsi5A4pP3X zk5%y8t6J!{bx*B`?8`hdVP@j6wOx;}%`Xnzr*S=IA^g1zuxFT7?SBWt7@#5zFhc+^ z%J1kwz}x99{mBzx6)~m$5s6T56&Y-wZ;a&2o~QueTSx@070QWb5j?CLuEClmes3WE zDr6>*U!e@xn|MF0&*z0!y@htzj~Py4iAo40ilSI;_xUEL=2vz?Sa zKZ3NIy%>q<@Zh?z2xPa`6A|l7x4gI);ZKWaC{uK}EdW3V!ut|oK6X8W-#kiDr_WIE zg00~+O9LJgh#BRF#3pxfJRSJglU-iO%y9g2Q5T zKfLf?g;W4MOa-#Jsw{rRdd2&uOGyq7rtLRMYFRi{_tpz^uRY+N=V?fu`mIQ*;#m}N z0o@FPxp&Q^R35Euej(NEX z`@3Sau`H?qHrlLLO#$T9NG*tc5Vyo@Rp>tr2^#%xBx~kvNT68Z8cN3xYP!#DY(e|3 zm3eCZHnw)px5`-)i*?Na0QmEs)1?6E_w}V5=PD^N|1rV@eKdOw2>`rah5Bn%v+q=2 zyi6DX)sNAL0IR@hU6y8J?{NWPFl3wnwfDLli5-|Gbm>Hydj&Bar|D^6F#*4f0Dx`P zda)t_vmcgjfTJ?3Fw;3mhDaQUs`rUTeWu6*NM^_tNH@xN>KE&T2Gp6x#Sy(%b0CBTdU04X+es|sCtU!P}Xux@Hqo6xLB zw55`J))o4DM-kS!uf%9sffDweZM300w7V+JdB0}Kn2tQ>jgAoqF~Yg7mV*5eQ~qSk zIXGtg&j?^jIv$KX|7;|nuoD0nrfz_vHY}2V7NMJF5SECzB1C^sYt^Tb_O(aq=l2v^ zVpnqv55CCCmACLW*XWi=eG~(uza%UqRsevE0YI-zYtYwsV|@tdu2ywY zW@Uu>dBlG?6=7f&EI14J1`=4+FY~{3kK2mo7QTz!IX=}YXtC+ca&QE#;{&2=d|Be!wMVKhJ@+*{bfM=4@f)dtB{hypG>Kg6eC>Q zTh+>9u^&e$0BH0{_}YEXhF-X+yH;}?XOX;8gIB@qrfUwVZ2}x;8t38!TR|(Ft zB+@ea+X&m~+W1Z6oVYPe-2hGSPvy9-GcEYd1A1?{Dn@to^GVgd4QQXDKw!1`-W#VudB3004;vRDux{{-p$w1LyCuSxm5!`2OO84|p>AyOPFOIN`rh+F4| z8M@C4&9HYMRY5X6zKXQsol*+)eF&#Bk6z)X=jDrYpt`uut0DvS!Rd>kM zkNHmcHgXXDQ2|v!0@i*JqY(xJoS*1(HoDlf-$C-V+W`P$7&jyF4r%8%&c)Da?uMls zV8v-y&qkuwvyMfWtM~an@Jg~NiCv7DX9XxBF*C0~0s$MqC4`p3JcPdm{>?^GiRY>I zUj;s(|K1fz9exP0np#kHKoJyJ0Dv|^=eZ8K`am^bkG!m!;)`<-{{C5_L4frkn4ey+ zFC&rIZ$NT~rwNO>`1iEU`YxI)BBo(Z^)9Y~!FL0+v7dXcJYo@Qz)MtPyQJ9u#pu!s z@JFOC$Jx5)CL-r#t@+!&{=8ZMRw+pSx>_UFRV#qC2Hph;B);0ox&{RhSOZnXEpVr! z9W!m@cw}+kxFR(t5~kakVDf_4`r99@np`DELNXy%9}{BredrGX{zcf@+1AAT<8|Oz zB$%h)0gv_!c^nh^H%T`@15GsrN?9M~|6W+Izhd+N>a894Z8WzvxBdK6rz~~LFRl4d z9jTde0a9ghwF>{gD8H~~WR{umgaUvc17AVv71X*^DrOV_DCtC76mq%6QEQ=Dl-gXA z#nH=stHgJUS`Voi->My~Spe2XQizJOvfXJ7WyJiwJCgFa#I4OrMe6^KQ~=BqV6_7( zwVaP6!vCN8{u4PJYauoDFDz&|S1=iO^y~V5$PMfMxE0MVGgDwLC+rq#T6`(R@0&Ye z8Zqr(i_~$jpua~{p;)TtKZ+EjdQf>adhKfv%k8O1CQ_rI`*2hvTH11?SWC49QJNX% zD4kf}pZG%; zh?`_XWv+FyjYTj^%$Jop7mP4ptJk2e?+3s! zWg(Ik^-d&y#mxSXAg+a5^!M!km#HH0W+X_^o^2kIsqkLiUtgmL5NrYfUYq`9oAW1i zk`Ia?)c~o#y$+Hda#zOBna6!S=((4$mF%!o;u}{XXV#jfGzUB8k)u9}LSqW?Y4(GO z7hpD@d7VobwB}ocub^`ZVz1ZtV;C^Kf#&QP6VN704o7;UTn3y^>#mT|u_NMceG8$h zYFdB$Uf}imTfQZ_@$LtZCSsSX*8dv){xOvk?^FO#(0&Wex6(rU1*x?3Jl6E3m_K(n z;8#e~ng{849zz3CnE!g)+J?=!z@qa=xeWq#d>i1M61$#Fq zhs@+Zp+fyQ#C;go$H8}0y&DO1y%6{q;zj_;fT6H{wFUrj{gtPYl8?_LzLvWQ>uIFp z+eeXLja~?~U-zvbX(i8LI4kjuI}j*F>v<{WXLyBuyYJRVVyU8IBH?d$bA4$5bZQo> zi{0m^Bf)p4BSpkK;M_rvBmmXw`_DpL2>*iw^ci2Sw|OqBuv$V~0yiRVfrpUHm!eu= zM8od1&`E2#Tj0ijDiPOvAk2Q<*EG*#kbKgA5C*3$M*`MfsHih2G^b)@#5kits-?*H z#Td?YgRo*`t6}vO8V=B<;T_#vKh@${`vHb5+r@&%ZvtMR$JK<{RenUXT!M&FRzh^t=#&GbA8w+VPGOoh_B7A-796PJ6xmdsFv(1~RKLN=`q7 zU?aji@6h8+1y1WIDDdy`N5ta#Xba%gi^)DH1b=q`pkKAzhvmM*Sc$I~qdzQ9`Z1*F z)VWApOLTkzDXda1=x!hYt=@tsfHa@E6iHt_2}wnZ8_U}8p2rA_e38-h_U~hmN|yF~ z%Z6Z1eOqSBYIs7AU$g-^c5b8C2mJ*AaXuD{=KL;F%e_-w^$HR+_YZpOeV)gClcbsj zteJi@4$Q3xd>QO8db|j!UGby{jXsX#H5X$r`y)V7&z`F5P>N{ItB_pswUA7s{USWc z&Y#1YR!dvsy^&fX?Xm#6xxUl$*>7*9zkkL6XulD8mvRffTa@eVC*cP{5GBA?V;$8_ z%-0mRfT+;k6Y=F_G4i-?yLTODUw;wDF!<3flVn)e$MUUDRUuPzpP#0m`<(1iQ-W$p zBdE(W6nJs#Sn1RCoyKQ?cTl7>7Z!jRgt!rsuj!+bEhX@Y812{)-20I{&n&V4 zvb=AA_{r8mf=k*l;JA4h)sf`Ai_u?gsN90C3T8YQPz+M$J4=u^$m=_eZ6ohqj2R37 z%-^y-QcP!k!eTnnjq(IxSEV@fcmwg3{db0%OcwX`=C!qu>QG;eXe~+SVH7p~=zAB) zfAAn++yv;T4l73UX$AeeF`i9hj4v($-dX_8Vi*80pZ|OndJuM1>SjD;K<|>~zMID| zFQ0mt#XJn-*t|gg#mIP!Lv*zT`@VaItzF>`EC9g&H<#Z4yb>wN*e-C-*Wr&VUXmA4 z^?F~6#9o3#TO0bvq2UrzH7dzGjM65LzITxlQ;c4PhV6@IjFDBNG6RI7DLRGlFUA^q zqTkYErZcyhJ`cqxMmquG(`BDDM(