From 0f40166e27bf68c06f9ff8f65f6b88c1841b099b Mon Sep 17 00:00:00 2001 From: Artem30801 Date: Wed, 20 May 2020 21:18:38 +0300 Subject: [PATCH 01/49] Docs: Fixed styling by MD rules --- docs/ru/server.md | 79 +++++++++++++++++++++++------------------------ 1 file changed, 38 insertions(+), 41 deletions(-) diff --git a/docs/ru/server.md b/docs/ru/server.md index 191e164..70ad6a8 100644 --- a/docs/ru/server.md +++ b/docs/ru/server.md @@ -5,7 +5,7 @@ * [Установка и запуск](start-tutorial.md#установка-и-запуск-сервера) * [Интерфейс](#интерфейс-сервера) * [Настройка](#настройка-сервера) -* [Дополнительные операции](#дополнительные-операции) +* [Дополнительные операции](#дополнительные-операции-и-окна) ## Интерфейс сервера @@ -17,7 +17,7 @@ При первом подключении клиента к серверу в таблицу добавляется строка для отображения состояния клиента, содержащая только имя клиента (`copter ID`). Если на клиентах настроена автоматическая передача телеметрии, данные в таблице будут обновляться автоматически. Так же возможно запросить телеметрию выбранных клиентов с помощью кнопки [`Preflight check`](#управление). -Строки можно сортировать по возрастанию или убыванию значений любого из столбцов, кликнув по его заголовку. +Строки можно сортировать по возрастанию или убыванию значений любого из столбцов, кликнув по его заголовку. Столбцы можно менять местами и изменять их ширину: все изменения сохраняются в файле конфигурации сервера при штатном завершении работы сервера. При нажатии правой кнопкой мыши на шапку таблицы откроется контекстное меню с [встроенным конфигуратором](#column-preset-editor), в котором можно скрыть или отобразить столбцы, изменить их порядок, загрузить определенный набор настроек. При запуске сервера последние использованные настройки будут загружены и применены. @@ -34,7 +34,7 @@ #### Столбцы таблицы * `copter ID` - имя клиента. Может быть сконфигурирован на стороне клиента. Отображается сразу при подключении клиента. Рядом с каждым ID коптера расположен чекбокс - коптеры, чей ID отмечен чекбоксом положительно (галочка), считаются *выбранными*. Ячейки в этом столбце всегда проходят проверку. - * При двойном нажатии на это поле можно ввести новый `copter ID` клиента и переименовать его. В качестве имени допустимы сочетания латинских букв, цифр и тире (A-Z, a-z, 0-9, '-') длинной не более 63 символов. Тире не может являться первым символом. + * При двойном нажатии на это поле можно ввести новый `copter ID` клиента и переименовать его. В качестве имени допустимы сочетания латинских букв, цифр и тире (A-Z, a-z, 0-9, '-') длинной не более 63 символов. Тире не может являться первым символом. * `version` - хеш-код текущей git версии клиента. Ячейки в этом столбце проверяются при включенном (значение `true`) параметре [check_git_version](#раздел-checks), задаваемом в настройках сервера. Ячейка в данном столбце проходит проверку если хеш-код git версии данного клиента и сервера совпадают (если сервер не расположен в git-репозитории, то проверка проходится автоматически). * `configuration` - заданная пользователем версия конфигурации клиента. Ячейки в этом столбце всегда проходят проверку. * Ячейки этого столбца поддерживают *drag-and-drop*. При перетаскивании ячейки в любое стороннее приложение, поддерживающее файлы (к примеру, "Проводник"), файл конфигурации клиента будет скопирован в указанное место. При перетаскивании ячейки на другую ячейку файл конфигурации будет скопирован с одного на другой. При перетаскивании файла на ячейку он будет записан на клиент в качестве конфигурации (при условии валидации). При передаче конфигурации на клиент секция `PRIVATE` не будет отправляться. @@ -45,7 +45,7 @@ * `mode` - режим полётного контроллера. Ячейка в данном столбце не проходит проверку, если её значение `NO_FCU` или содержит `CMODE`. В остальных случаях, если ячейка не пустая, она проходит проверку. * `checks` - состояние самодиагностики коптера. Ячейка в данном столбце проходит проверку, если её значение `OK`. В остальных случаях, если ячейка не пустая, она не проходит проверку. * При двойном клике на ячейку при наличии ошибок будет показано диалоговое окно с полной детализацией всех ошибок. -* `current x y z yaw frame_id` - текущее положение коптера с указанием названия системы координат. Ячейка автоматически проходит проверку если у параметра [check_current_position ](#раздел-checks) установлено значение `false`. Иначе, ячейка в данном столбце не проходит проверку, если её значение `NO_POS` или содержит `nan`. В остальных случаях, если ячейка не пустая, она проходит проверку. +* `current x y z yaw frame_id` - текущее положение коптера с указанием названия системы координат. Ячейка автоматически проходит проверку если у параметра [check_current_position](#раздел-checks) установлено значение `false`. Иначе, ячейка в данном столбце не проходит проверку, если её значение `NO_POS` или содержит `nan`. В остальных случаях, если ячейка не пустая, она проходит проверку. * `start x y z` - стартовое положение коптера для воспроизведения анимации. Ячейка в данном столбце не проходит проверку, если её значение `NO_POS` или разница между текущим и стартовым положением коптера больше значения [start_pos_delta_max](#раздел-checks). В остальных случаях, если ячейка не пустая, она проходит проверку. * `dt` - разница между временем на сервере и клиенте в секундах, включая сетевую задержку. Ячейка в данном столбце проходит проверку, если её значение меньше значения [time_delta_max](#раздел-checks), задаваемого в настройках сервера. В остальных случаях, если ячейка не пустая, она не проходит проверку. При слишком больших значениях сигнализирует об отсутствии синхронизации времени между коптером и клиентом. @@ -180,7 +180,7 @@ ### Файл конфигурации -Конфигурация сервера создаётся согласно [спецификации](../../Server/config/spec/configspec_server.ini), в ней можно посмотреть значения по умолчанию для любого параметра после ключевого слова `default`. Все изменения сохраняются в файл конфигурации `server.ini` в папке `clever-show/Server/config`. +Конфигурация сервера создаётся согласно [спецификации](../../Server/config/spec/configspec_server.ini), в ней можно посмотреть значения по умолчанию для любого параметра после ключевого слова `default`. Все изменения сохраняются в файл конфигурации `server.ini` в папке `clever-show/Server/config`. Доступно редактирование конфигурации сервера через GUI модуль `Config editor` через меню `Server -> Edit server config`. @@ -227,7 +227,7 @@ Сервер может использовать UDP broadcast, чтобы передавать клиентам актуальную информацию о конфигурации сервера. Таким образом становится возможным автоматическое подключение клиентов к серверу без необходимости дополнительной ручной конфигурации. В данном разделе задаются параметры этого механизма: * `send` - будут ли использованы broadcast'ы для передачи данных (при значении `False` broadcast'ы НЕ будут отправляться). Используйте `False` в случае повышенных требований безопасности, перегруженности сети или невозможности передачи по широковещательному каналу (из-за конфигурации брандмауэра или сети) -* `listen` - будет ли сервер прослушивать порт бродкастов для автоматического выключения во избежание наличия нескольких серверов в одной сети. +* `listen` - будет ли сервер прослушивать порт бродкастов для автоматического выключения во избежание наличия нескольких серверов в одной сети. * `port` - UDP порт, по которому будет осуществляться отправка сообщений. *Рекомендуется изменить значение по умолчанию в целях безопасности.* **Внимание!** При изменении этого параметра клиенты НЕ смогут принимать сообщения автоконфигурации до изменения (вручную) соответствующего параметра в конфигурации клиента на равное значение. * `delay` - периодичность (в секундах, дробное значение), с которой будет происходить отправка broadcast сообщений. Увеличьте задержку для уменьшения нагрузки на сеть. Уменьшите задержку для уменьшения времени отклика и подключения при первом запуске клиентов. @@ -247,7 +247,7 @@ #### Интерфейс -![LED Visual Land](../assets/server-led-emergency-land.png) +![LED Visual Land](..\assets\server-led-emergency-land.png) При нажатии на кнопку `Visual land` все коптеры делятся на 2 равные группы по порядку расположения в таблице. Первая половина коптеров зажигает светодиодную ленту зелёным цветом, вторая - красным. При нажатии на зелёную или красную кнопку происходит выбор группы, соответствующей цвету нажатой кнопки. Коптеры выбранного цвета снова делятся на две половины и каждая половина зажигает светодиодную ленту зелёным и красным цветом соответственно. Остальные коптеры выключают светодиодную ленту. @@ -259,49 +259,47 @@ Встроенный редактор конфигураций позволяет открывать и интуитивно редактировать файлы конфигурации сервера и клиента, ровно как и сторонние файлы конфигурации формата `.ini`. Редактор может быть запущен с помощью контекстного меню в таблице коптеров в сервере, из меню сервера, или запущен как отдельное приложение. -Значение каждого столбца может быть отредактировано по двойному клику на него. +Значение каждого столбца может быть отредактировано по двойному клику на него. Значения столбца `Option` (названия секций и параметров) не могут повторяться в одном и том же уровне древа - названия секций и параметров будут автоматически изменятся с добавлением нумерации при повторяющихся наименованиях. Для значений столбца `Value` (фактические значения параметров) будет использован соответствующий встроенный редактор значений (для целочисленных, дробных и булевых значений). Значения массивов любых типов также отображается в виде элементов древа. Каждое из этих значений можно редактировать, перемещать, удалять и добавлять независимо. -При редактировании параметра без значения можно указать значение ` ` или в виде списка в виде `[1, 2, 3]` или `(1, 2, 3)` - это автоматически преобразует значение в список. +При редактировании параметра без значения можно указать значение `` или в виде списка в виде `[1, 2, 3]` или `(1, 2, 3)` - это автоматически преобразует значение в список. ![Config editor](..\assets\server-config-editor.png) Файл конфигурации отображается в редакторе в виде древа. Каждая "строка" в представлении - раздел или параметр конфигурации. Разделы конфигурации могут быть свёрнуты. Каждая строка может быть перемещена с помощью drag-n-drop для изменения порядка параметров и структуры разделов (возможно перетаскивание строки *на* раздел конфигурации для перемещения другого раздела\параметра в него). При зажатой клавише `alt` при перетаскивании параметр или раздел будут скопированы. -- Чекбокс `Color Indication` Включает или отключает цветовую индикацию состояния строк: - - *Синий* - Данного параметра *не было* в файле конфигурации при загрузке и его значение было взято из спецификации конфигурации. Изменение данного параметра приведет к добавлению его в текущий файл конфигурации. - - *Бирюзовый* - Данный параметр находится в файле конфигурации, но его значение совпадает со значением по умолчанию из спецификации конфигурации. - - *Жёлтый* - Значение данного параметра было изменено пользователем в текущей сессии редактирования. - - *Зелёный* - Данный параметр был добавлен в текущей сессии редактирования. - - *Красный* - Данный параметр был исключен из конфигурации пользователем текущей сессии редактирования. Он не будет сохранен в конфигурации. -- Чекбокс `Restart` - доступен только при редактировании конфигураций сервера или клиента. Автоматически перезапускает сервер или клиент после сохранения конфигурации. -- Кнопка `Save as` - Позволяет сохранить копию файла конфигурации на компьютер. В диалоговом окне выберите расположение и имя файла. **Внимание!** Конфигурация *не будет* проверена на соответствие спецификации! -- Кнопка `Save` - Сохраняет файл конфигурации. Перед сохранением будет проведена проверка файла на соответствие текущей конфигурации спецификации. При возникновении ошибок будет выведен детальный отчет по ним и будет предложено продолжить редактирование. При сохранении конфигурации сгенерированной на основе спецификации будет предложено выбрать новый путь для сохранения файла. -- Кнопка `Cancel` - Отменяет внесенные в конфигурацию изменения и закрывает диалоговое окно редактора конфигураций. - - +* Чекбокс `Color Indication` Включает или отключает цветовую индикацию состояния строк: + * *Синий* - Данного параметра *не было* в файле конфигурации при загрузке и его значение было взято из спецификации конфигурации. Изменение данного параметра приведет к добавлению его в текущий файл конфигурации. + * *Бирюзовый* - Данный параметр находится в файле конфигурации, но его значение совпадает со значением по умолчанию из спецификации конфигурации. + * *Жёлтый* - Значение данного параметра было изменено пользователем в текущей сессии редактирования. + * *Зелёный* - Данный параметр был добавлен в текущей сессии редактирования. + * *Красный* - Данный параметр был исключен из конфигурации пользователем текущей сессии редактирования. Он не будет сохранен в конфигурации. +* Чекбокс `Restart` - доступен только при редактировании конфигураций сервера или клиента. Автоматически перезапускает сервер или клиент после сохранения конфигурации. +* Кнопка `Save as` - Позволяет сохранить копию файла конфигурации на компьютер. В диалоговом окне выберите расположение и имя файла. **Внимание!** Конфигурация *не будет* проверена на соответствие спецификации! +* Кнопка `Save` - Сохраняет файл конфигурации. Перед сохранением будет проведена проверка файла на соответствие текущей конфигурации спецификации. При возникновении ошибок будет выведен детальный отчет по ним и будет предложено продолжить редактирование. При сохранении конфигурации сгенерированной на основе спецификации будет предложено выбрать новый путь для сохранения файла. +* Кнопка `Cancel` - Отменяет внесенные в конфигурацию изменения и закрывает диалоговое окно редактора конфигураций. #### Контекстное меню и команды редактора конфигураций ![Cconfig editor context menu](../assets/server-config-editor-menu.png) -- `Duplicate` (`Shift+D`) - создает копию параметра или раздела (со всему входящими в него параметрами). К имени опции или раздела будет добавлена нумерация для избегания повторяющихся названий. -- `Toggle exclude` (`Alt+Del`) - исключает параметр или раздел из конфигурации. Параметр или раздел *не* будут удалены, но *не* будут записаны при сохранении. Повторное применение этой команды на уже исключенных параметрах или разделах вернет их к нормальному состоянию. -- `Remove from config` (`Del`) - *удаляет* параметр или раздел из конфигурации. **Внимание!** Это действие необратимо! -- `Clear item value` (`Shift+R`) - *удаляет* значение параметра или всех параметров раздела (если использовано на разделе). Это так же приводит к сбросу типа значения параметра и он будет восстановлен из следующего текстового ввода при редактировании значения. **Внимание!** Это действие необратимо! -- `Reset value to default` (`Ctrl+R`) - приводит к значению по умолчанию (из спецификации конфигурации) значение параметра или всех параметров раздела (если использовано на разделе). Это так же приводит к сбросу типа значения параметра и он будет восстановлен из следующего текстового ввода при редактировании значения. **Внимание!** Это действие необратимо! -- `Reset all changes` (`Alt+R`) - восстанавливает к первоначальному виду параметр или все параметры раздела (если использовано на разделе). **Внимание!** Это действие необратимо! -- `Add option` (`Shift+A`) - добавляет параметр с пустым значением, первое редактирование значения определит его тип -- `Add section`(`Ctrl+A`) - добавляет раздел. +* `Duplicate` (`Shift+D`) - создает копию параметра или раздела (со всему входящими в него параметрами). К имени опции или раздела будет добавлена нумерация для избегания повторяющихся названий. +* `Toggle exclude` (`Alt+Del`) - исключает параметр или раздел из конфигурации. Параметр или раздел *не* будут удалены, но *не* будут записаны при сохранении. Повторное применение этой команды на уже исключенных параметрах или разделах вернет их к нормальному состоянию. +* `Remove from config` (`Del`) - *удаляет* параметр или раздел из конфигурации. **Внимание!** Это действие необратимо! +* `Clear item value` (`Shift+R`) - *удаляет* значение параметра или всех параметров раздела (если использовано на разделе). Это так же приводит к сбросу типа значения параметра и он будет восстановлен из следующего текстового ввода при редактировании значения. **Внимание!** Это действие необратимо! +* `Reset value to default` (`Ctrl+R`) - приводит к значению по умолчанию (из спецификации конфигурации) значение параметра или всех параметров раздела (если использовано на разделе). Это так же приводит к сбросу типа значения параметра и он будет восстановлен из следующего текстового ввода при редактировании значения. **Внимание!** Это действие необратимо! +* `Reset all changes` (`Alt+R`) - восстанавливает к первоначальному виду параметр или все параметры раздела (если использовано на разделе). **Внимание!** Это действие необратимо! +* `Add option` (`Shift+A`) - добавляет параметр с пустым значением, первое редактирование значения определит его тип. +* `Add section`(`Ctrl+A`) - добавляет раздел. ### Column preset editor Позволяет редактировать, удалять и добавлять наборы настроек отображения столбцов в таблице. -Окно редактора столбцов таблицы имеет два вида: +Окно редактора столбцов таблицы имеет два вида: #### Контекстное меню @@ -309,10 +307,9 @@ Может быть открыто с помощью нажатия правой кнопкой мыши на шапку таблицы. - -- `Список столбцов` - Нажатием на чекбоксы (флажки) можно скрывать или отображать столбцы таблицы. Перетаскиванием строк (drag-and-drop) можно изменять порядок столбцов. Изменения будут применены моментально и автоматически сохранены в конфигурацию сервера. -- `Выпадающий список наборов настроек` - Позволяет загрузить и автоматически применить любой из записанных в файле конфигурации наборов настроек. -- Кнопка `Manage presets` - открывает диалоговое окно для редактирования наборов настроек (см. далее) +* `Список столбцов` - Нажатием на чекбоксы (флажки) можно скрывать или отображать столбцы таблицы. Перетаскиванием строк (drag-and-drop) можно изменять порядок столбцов. Изменения будут применены моментально и автоматически сохранены в конфигурацию сервера. +* `Выпадающий список наборов настроек` - Позволяет загрузить и автоматически применить любой из записанных в файле конфигурации наборов настроек. +* Кнопка `Manage presets` - открывает диалоговое окно для редактирования наборов настроек (см. далее). #### Диалоговое окно @@ -320,10 +317,10 @@ Может быть открыто из контекстного меню или меню сервера в разделе `Table`. -- `Список столбцов` - Нажатием на чекбоксы (флажки) можно скрывать или отображать столбцы таблицы. Перетаскиванием строк (drag-and-drop) можно изменять порядок столбцов. *Изменения будут применены или сохранены только после нажатия кнопок `Save` или `Apply` .* -- `Выпадающий список наборов настроек` - Позволяет выбрать для редактирования любой из записанных в файле конфигурации наборов настроек. - - Пункт `` - Создает новый набор настроек. В появившемся диалоговом окне введите имя набора настроек. Новый набор настроек будет основываться на наборе по умолчанию (`DEFAULT`). -- Кнопка `Add` - Создает новый набор настроек. В появившемся диалоговом окне введите имя набора настроек. Новый набор настроек будет основываться на наборе по умолчанию (`DEFAULT`). -- Кнопка `Remove` - Удаляет выбранный набор настроек. **Внимание!** Это действие необратимо! -- Кнопка `Save` - Сохраняет набор настроек в файл конфигурации. -- Кнопка `Apply` - Сохраняет набор настроек в файл конфигурации и применяет его к таблице. \ No newline at end of file +* `Список столбцов` - Нажатием на чекбоксы (флажки) можно скрывать или отображать столбцы таблицы. Перетаскиванием строк (drag-and-drop) можно изменять порядок столбцов. *Изменения будут применены или сохранены только после нажатия кнопок `Save` или `Apply` .* +* `Выпадающий список наборов настроек` - Позволяет выбрать для редактирования любой из записанных в файле конфигурации наборов настроек. + * Пункт `` - Создает новый набор настроек. В появившемся диалоговом окне введите имя набора настроек. Новый набор настроек будет основываться на наборе по умолчанию (`DEFAULT`). +* Кнопка `Add` - Создает новый набор настроек. В появившемся диалоговом окне введите имя набора настроек. Новый набор настроек будет основываться на наборе по умолчанию (`DEFAULT`). +* Кнопка `Remove` - Удаляет выбранный набор настроек. **Внимание!** Это действие необратимо! +* Кнопка `Save` - Сохраняет набор настроек в файл конфигурации. +* Кнопка `Apply` - Сохраняет набор настроек в файл конфигурации и применяет его к таблице. From 039dde1aa73775963eb7628efd0a1c453559840c Mon Sep 17 00:00:00 2001 From: artem30801 <38689676+artem30801@users.noreply.github.com> Date: Wed, 20 May 2020 21:52:15 +0300 Subject: [PATCH 02/49] Docs: Fix images --- docs/ru/server.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/ru/server.md b/docs/ru/server.md index 70ad6a8..88b2ec2 100644 --- a/docs/ru/server.md +++ b/docs/ru/server.md @@ -247,7 +247,7 @@ #### Интерфейс -![LED Visual Land](..\assets\server-led-emergency-land.png) +![LED Visual Land](../assets/server-led-emergency-land.png) При нажатии на кнопку `Visual land` все коптеры делятся на 2 равные группы по порядку расположения в таблице. Первая половина коптеров зажигает светодиодную ленту зелёным цветом, вторая - красным. При нажатии на зелёную или красную кнопку происходит выбор группы, соответствующей цвету нажатой кнопки. Коптеры выбранного цвета снова делятся на две половины и каждая половина зажигает светодиодную ленту зелёным и красным цветом соответственно. Остальные коптеры выключают светодиодную ленту. @@ -267,7 +267,7 @@ При редактировании параметра без значения можно указать значение `` или в виде списка в виде `[1, 2, 3]` или `(1, 2, 3)` - это автоматически преобразует значение в список. -![Config editor](..\assets\server-config-editor.png) +![Config editor](../assets/server-config-editor.png) Файл конфигурации отображается в редакторе в виде древа. Каждая "строка" в представлении - раздел или параметр конфигурации. Разделы конфигурации могут быть свёрнуты. Каждая строка может быть перемещена с помощью drag-n-drop для изменения порядка параметров и структуры разделов (возможно перетаскивание строки *на* раздел конфигурации для перемещения другого раздела\параметра в него). При зажатой клавише `alt` при перетаскивании параметр или раздел будут скопированы. From 8a90770eb078bc0d65fcca6cc9223e298998ac37 Mon Sep 17 00:00:00 2001 From: artem30801 <38689676+artem30801@users.noreply.github.com> Date: Wed, 20 May 2020 22:00:11 +0300 Subject: [PATCH 03/49] Docs: Fixed grammar and typos --- docs/ru/server.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/docs/ru/server.md b/docs/ru/server.md index 88b2ec2..e6d7ac8 100644 --- a/docs/ru/server.md +++ b/docs/ru/server.md @@ -136,7 +136,7 @@ #### Управление -Данный раздел команд предназначен для выскоуровневого управления роем дронов. +Данный раздел команд предназначен для высокоуровневого управления роем дронов. * Спинбокс `Start after` - задаёт время задержки синхронного запуска выполнения анимаций коптерами после нажатия на кнопку `Start animation`. Для загруженных, подверженных помехам или имеющих большой пинг сетей рекомендуется использовать значения больше нуля. * Спинбокс `Music after` - задаёт время задержки запуска музыки после нажатия на кнопку `Start animation`. @@ -145,7 +145,7 @@ * Кнопка `Start animation` - посылает время старта анимации на все выбранные коптеры с учётом заданного в спинбоксе `Start after` времени. Все выбранные коптеры начинают синхронное воспроизведение анимации после нажатия на данную кнопку и через время, заданное в спинбоксе `Start after`. По окончанию анимации все коптеры выполнят посадку на месте окончания своей анимации. Кнопка активна только в том случае, если все коптеры готовы к воспроизведению анимации. При нажатии запрашивается дополнительное предупреждение. * Кнопка `Pause/Resume` - ставит на паузу и возобновляет выполнение полётных задач. После каждого нажатия кнопка меняет состояние на обратное. * Состояние`Pause` - ставит на паузу очередь заданий всех выбранных коптеров: приостанавливается выполнение любого полётного задания. Рекомендуется использовать в чрезвычайных ситуациях для определения неисправного коптера. **Внимание!** Данная команда НЕ прерывает полёт коптера в уже указанную точку (например: элементы взлёта, посадки; следование до начальной точки анимации и т.д.) - * Состояние `Resume` - все выбранные коптеры *синхронизированно* продолжат выполнение своих очередей заданий (например исполнение анимации) + * Состояние `Resume` - все выбранные коптеры *синхронизировано* продолжат выполнение своих очередей заданий (например исполнение анимации) #### Средства перехвата в экстренных ситуациях @@ -218,7 +218,7 @@ * `check_git_version` - Будет ли производиться проверка соответствия git-версий клиента и сервера для индикации в ячейках столбца `version` * `check_current_position` - Будет ли производиться проверка корректности текущих координат коптера для индикации в ячейках столбца `current x y z yaw frame_id`. -* `battery_percentage_min` - Минимальный заряд батарии коптера, допустимый для взлёта. Указывается *в процентах* (дробное значение от 0 до 100). Значение меньше указанного будет отмечено в столбце `battery` как неудовлетворительное. +* `battery_percentage_min` - Минимальный заряд батареи коптера, допустимый для взлёта. Указывается *в процентах* (дробное значение от 0 до 100). Значение меньше указанного будет отмечено в столбце `battery` как неудовлетворительное. * `start_pos_delta_max` - Максимальное расстояние от текущего положения коптера до его точки взлёта в файле анимации, допустимое для взлёта. Указывается *в метрах* (дробное значение от 0 до 'inf'). Значение больше указанного будет отмечено в столбце `start x y z` как неудовлетворительное. Допустимо использование строки 'inf' для любого допустимого расстояния. * `time_delta_max` - Максимальная разница (абсолютное значение) между временем сервера и клиента (включая сетевую задержку), допустимая для взлёта. Указывается *в секундах* (дробное значение от 0 до 'inf'). Значение больше указанного будет отмечено в столбце `dt` как неудовлетворительное. @@ -249,19 +249,19 @@ ![LED Visual Land](../assets/server-led-emergency-land.png) -При нажатии на кнопку `Visual land` все коптеры делятся на 2 равные группы по порядку расположения в таблице. Первая половина коптеров зажигает светодиодную ленту зелёным цветом, вторая - красным. При нажатии на зелёную или красную кнопку происходит выбор группы, соответствующей цвету нажатой кнопки. Коптеры выбранного цвета снова делятся на две половины и каждая половина зажигает светодиодную ленту зелёным и красным цветом соответственно. Остальные коптеры выключают светодиодную ленту. +При нажатии на кнопку `Visual land` все коптеры делятся на 2 равные группы по порядку расположения в таблице. Первая половина коптеров зажигает светодиодную ленту зелёным цветом, вторая - красным. При нажатии на зелёную или красную кнопку происходит выбор группы, соответствующей цвету нажатой кнопки. Коптеры выбранного цвета снова делятся на две половины, и каждая половина зажигает светодиодную ленту зелёным и красным цветом соответственно. Остальные коптеры выключают светодиодную ленту. -Нажимая на кнопки, соответствующие цвету группы, в которой находится неисправный коптер, можно определить его номер и выполнить экстренную посадку за логарифмическое количество шагов от количества коптеров, т.е. гораздо быстрее, чем перебирая коптеры по одному. +Нажимая на кнопки, соответствующие цвету группы, в которой находится неисправный коптер, можно определить его номер и выполнить экстренную посадку за логарифмическое количество шагов от количества коптеров, т. е. гораздо быстрее, чем перебирая коптеры по одному. На любом шаге можно произвести посадку или выключение моторов всех коптеров, на которых включена светодиодная лента, нажав кнопку `Land` или `Disarm`. ### Config editor -Встроенный редактор конфигураций позволяет открывать и интуитивно редактировать файлы конфигурации сервера и клиента, ровно как и сторонние файлы конфигурации формата `.ini`. Редактор может быть запущен с помощью контекстного меню в таблице коптеров в сервере, из меню сервера, или запущен как отдельное приложение. +Встроенный редактор конфигураций позволяет открывать и интуитивно редактировать файлы конфигурации сервера и клиента, равно как и сторонние файлы конфигурации формата `.ini`. Редактор может быть запущен с помощью контекстного меню в таблице коптеров в сервере, из меню сервера, или запущен как отдельное приложение. Значение каждого столбца может быть отредактировано по двойному клику на него. -Значения столбца `Option` (названия секций и параметров) не могут повторяться в одном и том же уровне древа - названия секций и параметров будут автоматически изменятся с добавлением нумерации при повторяющихся наименованиях. +Значения столбца `Option` (названия секций и параметров) не могут повторяться в одном и том же уровне древа - названия секций и параметров будут автоматически изменяться с добавлением нумерации при повторяющихся наименованиях. Для значений столбца `Value` (фактические значения параметров) будет использован соответствующий встроенный редактор значений (для целочисленных, дробных и булевых значений). Значения массивов любых типов также отображается в виде элементов древа. Каждое из этих значений можно редактировать, перемещать, удалять и добавлять независимо. @@ -279,7 +279,7 @@ * *Красный* - Данный параметр был исключен из конфигурации пользователем текущей сессии редактирования. Он не будет сохранен в конфигурации. * Чекбокс `Restart` - доступен только при редактировании конфигураций сервера или клиента. Автоматически перезапускает сервер или клиент после сохранения конфигурации. * Кнопка `Save as` - Позволяет сохранить копию файла конфигурации на компьютер. В диалоговом окне выберите расположение и имя файла. **Внимание!** Конфигурация *не будет* проверена на соответствие спецификации! -* Кнопка `Save` - Сохраняет файл конфигурации. Перед сохранением будет проведена проверка файла на соответствие текущей конфигурации спецификации. При возникновении ошибок будет выведен детальный отчет по ним и будет предложено продолжить редактирование. При сохранении конфигурации сгенерированной на основе спецификации будет предложено выбрать новый путь для сохранения файла. +* Кнопка `Save` - Сохраняет файл конфигурации. Перед сохранением будет проведена проверка файла на соответствие текущей конфигурации спецификации. При возникновении ошибок будет выведен детальный отчет по ним и будет предложено продолжить редактирование. При сохранении конфигурации, сгенерированной на основе спецификации будет предложено выбрать новый путь для сохранения файла. * Кнопка `Cancel` - Отменяет внесенные в конфигурацию изменения и закрывает диалоговое окно редактора конфигураций. #### Контекстное меню и команды редактора конфигураций @@ -289,8 +289,8 @@ * `Duplicate` (`Shift+D`) - создает копию параметра или раздела (со всему входящими в него параметрами). К имени опции или раздела будет добавлена нумерация для избегания повторяющихся названий. * `Toggle exclude` (`Alt+Del`) - исключает параметр или раздел из конфигурации. Параметр или раздел *не* будут удалены, но *не* будут записаны при сохранении. Повторное применение этой команды на уже исключенных параметрах или разделах вернет их к нормальному состоянию. * `Remove from config` (`Del`) - *удаляет* параметр или раздел из конфигурации. **Внимание!** Это действие необратимо! -* `Clear item value` (`Shift+R`) - *удаляет* значение параметра или всех параметров раздела (если использовано на разделе). Это так же приводит к сбросу типа значения параметра и он будет восстановлен из следующего текстового ввода при редактировании значения. **Внимание!** Это действие необратимо! -* `Reset value to default` (`Ctrl+R`) - приводит к значению по умолчанию (из спецификации конфигурации) значение параметра или всех параметров раздела (если использовано на разделе). Это так же приводит к сбросу типа значения параметра и он будет восстановлен из следующего текстового ввода при редактировании значения. **Внимание!** Это действие необратимо! +* `Clear item value` (`Shift+R`) - *удаляет* значение параметра или всех параметров раздела (если использовано на разделе). Это так же приводит к сбросу типа значения параметра, и он будет восстановлен из следующего текстового ввода при редактировании значения. **Внимание!** Это действие необратимо! +* `Reset value to default` (`Ctrl+R`) - приводит к значению по умолчанию (из спецификации конфигурации) значение параметра или всех параметров раздела (если использовано на разделе). Это так же приводит к сбросу типа значения параметра, и он будет восстановлен из следующего текстового ввода при редактировании значения. **Внимание!** Это действие необратимо! * `Reset all changes` (`Alt+R`) - восстанавливает к первоначальному виду параметр или все параметры раздела (если использовано на разделе). **Внимание!** Это действие необратимо! * `Add option` (`Shift+A`) - добавляет параметр с пустым значением, первое редактирование значения определит его тип. * `Add section`(`Ctrl+A`) - добавляет раздел. From 9e8d18d02f0e92c23a19c245401ca7724a7bcd58 Mon Sep 17 00:00:00 2001 From: Arthur Golubtsov Date: Wed, 20 May 2020 23:48:02 +0300 Subject: [PATCH 04/49] Drone: Add takeoff and land indication options --- Drone/config/spec/configspec_client.ini | 2 ++ Drone/copter_client.py | 8 ++++---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/Drone/config/spec/configspec_client.ini b/Drone/config/spec/configspec_client.ini index d054bf0..6f881b9 100644 --- a/Drone/config/spec/configspec_client.ini +++ b/Drone/config/spec/configspec_client.ini @@ -72,6 +72,8 @@ yaw = string(default=180.0) use = boolean(default=False) pin = integer(default=21, min=0, max=100) count = integer(default=60, min=1) +takeoff_indication = boolean(default=True) +land_indication = boolean(default=True) [PRIVATE] # Available options: /hostname ; /default ; /ip ; any string 63 characters length diff --git a/Drone/copter_client.py b/Drone/copter_client.py index 6fdcf33..1754f3a 100644 --- a/Drone/copter_client.py +++ b/Drone/copter_client.py @@ -489,7 +489,7 @@ def _command_takeoff(*args, **kwargs): "z": client.active_client.config.copter_takeoff_height, "timeout": client.active_client.config.copter_takeoff_time, "safe_takeoff": client.active_client.config.copter_safe_takeoff, - "use_leds": client.active_client.config.led_use, + "use_leds": client.active_client.config.led_use & client.active_client.config.led_takeoff_indication, } ) @@ -520,7 +520,7 @@ def _command_land(*args, **kwargs): "z": client.active_client.config.copter_takeoff_height, "timeout": client.active_client.config.copter_takeoff_time, "frame_id": client.active_client.config.copter_frame_id, - "use_leds": client.active_client.config.led_use, + "use_leds": client.active_client.config.led_use & client.active_client.config.led_land_indication, } ) @@ -584,7 +584,7 @@ def _play_animation(*args, **kwargs): "timeout": client.active_client.config.copter_takeoff_time, "safe_takeoff": client.active_client.config.copter_safe_takeoff, # "frame_id": client.active_client.config.copter_frame_id, - "use_leds": client.active_client.config.led_use, + "use_leds": client.active_client.config.led_use & client.active_client.config.led_takeoff_indication, } ) # Fly to first point @@ -651,7 +651,7 @@ def _play_animation(*args, **kwargs): task_kwargs={ "timeout": client.active_client.config.copter_land_timeout, "frame_id": client.active_client.config.copter_frame_id, - "use_leds": client.active_client.config.led_use, + "use_leds": client.active_client.config.led_use & client.active_client.config.led_land_indication, }, ) From ca173200d9a51ac141b2b23e6283cb10ffc50f45 Mon Sep 17 00:00:00 2001 From: Arthur Golubtsov Date: Wed, 20 May 2020 23:56:16 +0300 Subject: [PATCH 05/49] Drone: Fix first point yaw --- Drone/copter_client.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/Drone/copter_client.py b/Drone/copter_client.py index 1754f3a..799f965 100644 --- a/Drone/copter_client.py +++ b/Drone/copter_client.py @@ -589,10 +589,15 @@ def _play_animation(*args, **kwargs): ) # Fly to first point rfp_time = start_time + client.active_client.config.copter_takeoff_time + if client.active_client.config.animation_yaw == "animation": + yaw = frame["yaw"] + else: + yaw = math.radians(float(client.active_client.config.animation_yaw)) task_manager.add_task(rfp_time, 0, animation.execute_frame, task_kwargs={ "point": animation.convert_frame(corrected_frames[0])[0], "color": animation.convert_frame(corrected_frames[0])[1], + "yaw": yaw, "frame_id": client.active_client.config.copter_frame_id, "use_leds": client.active_client.config.led_use, "flight_func": FlightLib.reach_point, @@ -604,12 +609,6 @@ def _play_animation(*args, **kwargs): elif start_action == 'arm': # Calculate start time start_time += start_delay - # Arm - # task_manager.add_task(start_time, 0, FlightLib.arming_wrapper, - # task_kwargs={ - # "state": True - # } - # ) frame_time = start_time # + 1.0 point, color, yaw = animation.convert_frame(corrected_frames[0]) task_manager.add_task(frame_time, 0, animation.execute_frame, From 541a64a3ca5810e844029cacc61cccb5c5d9b367 Mon Sep 17 00:00:00 2001 From: Arthur Golubtsov Date: Thu, 21 May 2020 00:11:07 +0300 Subject: [PATCH 06/49] Drone: Begin code organisation for GPS support --- Drone/config/spec/configspec_client.ini | 6 +++++- Drone/copter_client.py | 9 +++++---- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/Drone/config/spec/configspec_client.ini b/Drone/config/spec/configspec_client.ini index 6f881b9..a6d56d5 100644 --- a/Drone/config/spec/configspec_client.ini +++ b/Drone/config/spec/configspec_client.ini @@ -49,7 +49,6 @@ land_timeout = float(default=10.0, min=0) common_offset = float_list(default=list(0, 0, 0), min=3, max=3) [FLOOR FRAME] -enabled = boolean(default=False) parent = string(default=map) # Frame translation (x, y, z) # __list__ x y z @@ -58,6 +57,11 @@ translation = float_list(default=list(0.0, 0.0, 0.0), min=3, max=3) # __list__ roll pitch yaw rotation = float_list(default=list(0.0, 0.0, 0.0), min=3, max=3) +[GPS FRAME] +lat = float(default=0) +lon = float(default=0) +yaw = float(default=0) + [ANIMATION] takeoff_detection = boolean(default=True) land_detection = boolean(default=True) diff --git a/Drone/copter_client.py b/Drone/copter_client.py index 799f965..4349565 100644 --- a/Drone/copter_client.py +++ b/Drone/copter_client.py @@ -99,10 +99,9 @@ class CopterClient(client.Client): LedLib.init_led(self.config.led_pin) task_manager_instance.start() # TODO move to self if self.config.copter_frame_id == "floor": - if self.config.floor_frame_enabled: - self.start_floor_frame_broadcast() - else: - rospy.logerr("Can't make floor frame!") + self.start_floor_frame_broadcast() + elif self.config.copter_frame_id == "gps": + self.start_gps_frame_broadcast() start_subscriber() telemetry.start_loop() @@ -120,6 +119,8 @@ class CopterClient(client.Client): trans.child_frame_id = self.config.copter_frame_id static_bloadcaster.sendTransform(trans) + def start_gps_frame_broadcast(self): + return def restart_service(name): os.system("systemctl restart {}".format(name)) From 9988299a11ecdfa47c594bda5e7b9256e7cfaad9 Mon Sep 17 00:00:00 2001 From: Arthur Golubtsov Date: Thu, 21 May 2020 02:28:19 +0300 Subject: [PATCH 07/49] Drone: Add gps frame broadcast prototype --- Drone/copter_client.py | 32 ++++++++++++++++++++++++++++---- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/Drone/copter_client.py b/Drone/copter_client.py index 4349565..d4d7477 100644 --- a/Drone/copter_client.py +++ b/Drone/copter_client.py @@ -33,6 +33,16 @@ from geometry_msgs.msg import Point, Quaternion, TransformStamped from tf.transformations import quaternion_from_euler, euler_from_quaternion, quaternion_multiply import tf2_ros +from geographiclib.geodesic import Geodesic + +Earth = Geodesic.WGS84 + +def dist(x, y): + return math.sqrt(x**2+y**2) + +def azi(x, y): + return 90 - math.atan2(y,x)*180/math.pi + static_bloadcaster = tf2_ros.StaticTransformBroadcaster() emergency = False @@ -98,13 +108,12 @@ class CopterClient(client.Client): from FlightLib import LedLib LedLib.init_led(self.config.led_pin) task_manager_instance.start() # TODO move to self + start_subscriber() + telemetry.start_loop() if self.config.copter_frame_id == "floor": self.start_floor_frame_broadcast() elif self.config.copter_frame_id == "gps": self.start_gps_frame_broadcast() - start_subscriber() - - telemetry.start_loop() super(CopterClient, self).start() def start_floor_frame_broadcast(self): @@ -120,7 +129,22 @@ class CopterClient(client.Client): static_bloadcaster.sendTransform(trans) def start_gps_frame_broadcast(self): - return + gps_frame_thread = threading.Thread(target=self.gps_frame_broadcast_loop, name="GPS frame broadcast thread") + gps_frame_thread.start() + + def gps_frame_broadcast_loop(self): + rate = rospy.Rate(1) + while not rospy.is_shutdown(): + telem = telemetry.ros_telemetry + if telem is not None: + if math.isnan(telem.lat) or math.isnan(telem.lon) or math.isnan(telem.x) or math.isnan(telem.y): + logger.info("Can't get position from telemetry") + else: + new = Earth.Direct(telem.lat, telem.lon, azi(-telem.x,-telem.y), dist(telem.x,telem.y)) + lat_init = new['lat2'] + lon_init = new['lon2'] + logger.info("Initial lat: {} | lon: {}".format(lat_init, lon_init)) + def restart_service(name): os.system("systemctl restart {}".format(name)) From e399915c782d49063cc9606f6a4d93c86c05df97 Mon Sep 17 00:00:00 2001 From: Arthur Golubtsov Date: Thu, 21 May 2020 02:40:58 +0300 Subject: [PATCH 08/49] Drone: Fix telemetry get --- Drone/copter_client.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Drone/copter_client.py b/Drone/copter_client.py index d4d7477..803a67f 100644 --- a/Drone/copter_client.py +++ b/Drone/copter_client.py @@ -135,7 +135,7 @@ class CopterClient(client.Client): def gps_frame_broadcast_loop(self): rate = rospy.Rate(1) while not rospy.is_shutdown(): - telem = telemetry.ros_telemetry + telem = telemetry.get_ros_telemetry() if telem is not None: if math.isnan(telem.lat) or math.isnan(telem.lon) or math.isnan(telem.x) or math.isnan(telem.y): logger.info("Can't get position from telemetry") @@ -144,6 +144,7 @@ class CopterClient(client.Client): lat_init = new['lat2'] lon_init = new['lon2'] logger.info("Initial lat: {} | lon: {}".format(lat_init, lon_init)) + rate.sleep() def restart_service(name): @@ -802,6 +803,9 @@ class Telemetry: self.time_delta = time.time() self.round_telemetry() + def get_ros_telemetry(self): + return self.ros_telemetry + def update_telemetry_slow(self): self.animation_id = animation.get_id() self.git_version = self.get_git_version() From 7311e5b59aca9d4b6d8e4276df06826a84e9eb51 Mon Sep 17 00:00:00 2001 From: Arthur Golubtsov Date: Thu, 21 May 2020 02:45:03 +0300 Subject: [PATCH 09/49] Drone: Update copter client --- Drone/copter_client.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Drone/copter_client.py b/Drone/copter_client.py index 803a67f..4b56886 100644 --- a/Drone/copter_client.py +++ b/Drone/copter_client.py @@ -94,6 +94,7 @@ class CopterClient(client.Client): super(CopterClient, self).__init__(config_path) self.load_config() self.frames = {} + self.telemetry = None def load_config(self): super(CopterClient, self).load_config() @@ -109,7 +110,8 @@ class CopterClient(client.Client): LedLib.init_led(self.config.led_pin) task_manager_instance.start() # TODO move to self start_subscriber() - telemetry.start_loop() + self.telemetry = Telemetry() + self.telemetry.start_loop() if self.config.copter_frame_id == "floor": self.start_floor_frame_broadcast() elif self.config.copter_frame_id == "gps": @@ -135,7 +137,7 @@ class CopterClient(client.Client): def gps_frame_broadcast_loop(self): rate = rospy.Rate(1) while not rospy.is_shutdown(): - telem = telemetry.get_ros_telemetry() + telem = self.telemetry.get_ros_telemetry() if telem is not None: if math.isnan(telem.lat) or math.isnan(telem.lon) or math.isnan(telem.x) or math.isnan(telem.y): logger.info("Can't get position from telemetry") @@ -943,7 +945,6 @@ def emergency_callback(data): if __name__ == "__main__": - telemetry = Telemetry() copter_client = CopterClient() task_manager = tasking.TaskManager() rospy.Subscriber('/emergency', Bool, emergency_callback) From 3f0990173f4b46c083d72a0529906fe17c38efe1 Mon Sep 17 00:00:00 2001 From: Arthur Golubtsov Date: Thu, 21 May 2020 02:58:29 +0300 Subject: [PATCH 10/49] Drone: Add gps frame broadcast --- Drone/copter_client.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Drone/copter_client.py b/Drone/copter_client.py index 4b56886..d61314d 100644 --- a/Drone/copter_client.py +++ b/Drone/copter_client.py @@ -131,6 +131,14 @@ class CopterClient(client.Client): static_bloadcaster.sendTransform(trans) def start_gps_frame_broadcast(self): + trans = TransformStamped() + trans.transform.translation.x = 0 + trans.transform.translation.y = 0 + trans.transform.translation.z = 0 + trans.transform.rotation = Quaternion(*quaternion_from_euler(0,0,0)) + trans.header.frame_id = "map" + trans.child_frame_id = self.config.copter_frame_id + static_bloadcaster.sendTransform(trans) gps_frame_thread = threading.Thread(target=self.gps_frame_broadcast_loop, name="GPS frame broadcast thread") gps_frame_thread.start() From 67d77694b2d6d5ac614c118986cea35ce22a1865 Mon Sep 17 00:00:00 2001 From: Arthur Golubtsov Date: Thu, 21 May 2020 16:27:00 +0300 Subject: [PATCH 11/49] Drone: Add gps xy and dy counting --- Drone/copter_client.py | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/Drone/copter_client.py b/Drone/copter_client.py index d61314d..4bb98fc 100644 --- a/Drone/copter_client.py +++ b/Drone/copter_client.py @@ -43,7 +43,10 @@ def dist(x, y): def azi(x, y): return 90 - math.atan2(y,x)*180/math.pi -static_bloadcaster = tf2_ros.StaticTransformBroadcaster() +def get_xy(dist, azi): + return dist*math.sin(azi), dist*cos(azi) + +static_broadcaster = tf2_ros.StaticTransformBroadcaster() emergency = False @@ -119,6 +122,8 @@ class CopterClient(client.Client): super(CopterClient, self).start() def start_floor_frame_broadcast(self): + if self.config.floor_frame_parent == "gps": + self.start_gps_frame_broadcast() trans = TransformStamped() trans.transform.translation.x = self.config.floor_frame_translation[0] trans.transform.translation.y = self.config.floor_frame_translation[1] @@ -128,7 +133,7 @@ class CopterClient(client.Client): math.radians(self.config.floor_frame_rotation[2]))) trans.header.frame_id = self.config.floor_frame_parent trans.child_frame_id = self.config.copter_frame_id - static_bloadcaster.sendTransform(trans) + static_broadcaster.sendTransform(trans) def start_gps_frame_broadcast(self): trans = TransformStamped() @@ -138,22 +143,28 @@ class CopterClient(client.Client): trans.transform.rotation = Quaternion(*quaternion_from_euler(0,0,0)) trans.header.frame_id = "map" trans.child_frame_id = self.config.copter_frame_id - static_bloadcaster.sendTransform(trans) + static_broadcaster.sendTransform(trans) gps_frame_thread = threading.Thread(target=self.gps_frame_broadcast_loop, name="GPS frame broadcast thread") gps_frame_thread.start() def gps_frame_broadcast_loop(self): rate = rospy.Rate(1) while not rospy.is_shutdown(): - telem = self.telemetry.get_ros_telemetry() + telem = FlightLib.get_telemetry_locked(frame_id = "map") if telem is not None: if math.isnan(telem.lat) or math.isnan(telem.lon) or math.isnan(telem.x) or math.isnan(telem.y): logger.info("Can't get position from telemetry") else: - new = Earth.Direct(telem.lat, telem.lon, azi(-telem.x,-telem.y), dist(telem.x,telem.y)) - lat_init = new['lat2'] - lon_init = new['lon2'] - logger.info("Initial lat: {} | lon: {}".format(lat_init, lon_init)) + #pos_init = Earth.Direct(telem.lat, telem.lon, azi(-telem.x,-telem.y), dist(telem.x,telem.y)) + #lat_init = pos_init['lat2'] + #lon_init = pos_init['lon2'] + #logger.info("Initial lat: {} | lon: {}".format(lat_init, lon_init)) + geo_delta = Earth.Inverse(telem.lat, telem.lon, self.config.gps_lat, self.config.gps_lon) + dx, dy = get_xy(geo_delta['s12'], geo_delta['azi2']) + gps_dx = telem.x + dx + gps_dy = telem.y + dy + logger.info("GPS frame dx: {} | dy: {}".format(gps_dx, gps_dy)) + rate.sleep() From 69bb8d7158981268a57b06cc089729c7e330a041 Mon Sep 17 00:00:00 2001 From: Arthur Golubtsov Date: Thu, 21 May 2020 16:56:50 +0300 Subject: [PATCH 12/49] Drone: Update configspec for GPS --- Drone/config/spec/configspec_client.ini | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Drone/config/spec/configspec_client.ini b/Drone/config/spec/configspec_client.ini index a6d56d5..4678684 100644 --- a/Drone/config/spec/configspec_client.ini +++ b/Drone/config/spec/configspec_client.ini @@ -58,8 +58,8 @@ translation = float_list(default=list(0.0, 0.0, 0.0), min=3, max=3) rotation = float_list(default=list(0.0, 0.0, 0.0), min=3, max=3) [GPS FRAME] -lat = float(default=0) -lon = float(default=0) +lat = string(default=0) +lon = string(default=0) yaw = float(default=0) [ANIMATION] From 95917545c299cc8ca757b7f839de508f155c9e65 Mon Sep 17 00:00:00 2001 From: Arthur Golubtsov Date: Thu, 21 May 2020 16:57:55 +0300 Subject: [PATCH 13/49] Drone: Update getting GPS frame values --- Drone/copter_client.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Drone/copter_client.py b/Drone/copter_client.py index 4bb98fc..6a6426e 100644 --- a/Drone/copter_client.py +++ b/Drone/copter_client.py @@ -159,7 +159,9 @@ class CopterClient(client.Client): #lat_init = pos_init['lat2'] #lon_init = pos_init['lon2'] #logger.info("Initial lat: {} | lon: {}".format(lat_init, lon_init)) - geo_delta = Earth.Inverse(telem.lat, telem.lon, self.config.gps_lat, self.config.gps_lon) + lat = float(self.config.gps_frame_lat) + lon = float(self.config.gps_frame_lon) + geo_delta = Earth.Inverse(telem.lat, telem.lon, lat, lon) dx, dy = get_xy(geo_delta['s12'], geo_delta['azi2']) gps_dx = telem.x + dx gps_dy = telem.y + dy From 1f70520ac5d3ca2e5dcd4177e9e9209c07758daf Mon Sep 17 00:00:00 2001 From: Arthur Golubtsov Date: Thu, 21 May 2020 17:02:26 +0300 Subject: [PATCH 14/49] Drone: Fix typo --- Drone/copter_client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Drone/copter_client.py b/Drone/copter_client.py index 6a6426e..bc015e2 100644 --- a/Drone/copter_client.py +++ b/Drone/copter_client.py @@ -44,7 +44,7 @@ def azi(x, y): return 90 - math.atan2(y,x)*180/math.pi def get_xy(dist, azi): - return dist*math.sin(azi), dist*cos(azi) + return dist*math.sin(azi), dist*math.cos(azi) static_broadcaster = tf2_ros.StaticTransformBroadcaster() From bef932871f237c75cd26e5e2e94ec8be3958083e Mon Sep 17 00:00:00 2001 From: Arthur Golubtsov Date: Thu, 21 May 2020 16:51:57 +0000 Subject: [PATCH 15/49] Drone: Fix GPS delta position counting --- Drone/copter_client.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Drone/copter_client.py b/Drone/copter_client.py index bc015e2..8cd4f36 100644 --- a/Drone/copter_client.py +++ b/Drone/copter_client.py @@ -44,7 +44,7 @@ def azi(x, y): return 90 - math.atan2(y,x)*180/math.pi def get_xy(dist, azi): - return dist*math.sin(azi), dist*math.cos(azi) + return dist*math.sin(math.radians(azi)), dist*math.cos(math.radians(azi)) static_broadcaster = tf2_ros.StaticTransformBroadcaster() @@ -162,7 +162,8 @@ class CopterClient(client.Client): lat = float(self.config.gps_frame_lat) lon = float(self.config.gps_frame_lon) geo_delta = Earth.Inverse(telem.lat, telem.lon, lat, lon) - dx, dy = get_xy(geo_delta['s12'], geo_delta['azi2']) + logger.info("dist: {} | azi: {}".format(geo_delta['s12'], geo_delta['azi1'])) + dx, dy = get_xy(geo_delta['s12'], geo_delta['azi1']) gps_dx = telem.x + dx gps_dy = telem.y + dy logger.info("GPS frame dx: {} | dy: {}".format(gps_dx, gps_dy)) From 72e1d1e9164978fd016028b8e2a13445cace6cae Mon Sep 17 00:00:00 2001 From: Arthur Golubtsov Date: Thu, 21 May 2020 20:07:47 +0300 Subject: [PATCH 16/49] Drone: Add GPS frame static broadcast --- Drone/copter_client.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/Drone/copter_client.py b/Drone/copter_client.py index 8cd4f36..83534d4 100644 --- a/Drone/copter_client.py +++ b/Drone/copter_client.py @@ -142,7 +142,7 @@ class CopterClient(client.Client): trans.transform.translation.z = 0 trans.transform.rotation = Quaternion(*quaternion_from_euler(0,0,0)) trans.header.frame_id = "map" - trans.child_frame_id = self.config.copter_frame_id + trans.child_frame_id = "gps" static_broadcaster.sendTransform(trans) gps_frame_thread = threading.Thread(target=self.gps_frame_broadcast_loop, name="GPS frame broadcast thread") gps_frame_thread.start() @@ -155,10 +155,6 @@ class CopterClient(client.Client): if math.isnan(telem.lat) or math.isnan(telem.lon) or math.isnan(telem.x) or math.isnan(telem.y): logger.info("Can't get position from telemetry") else: - #pos_init = Earth.Direct(telem.lat, telem.lon, azi(-telem.x,-telem.y), dist(telem.x,telem.y)) - #lat_init = pos_init['lat2'] - #lon_init = pos_init['lon2'] - #logger.info("Initial lat: {} | lon: {}".format(lat_init, lon_init)) lat = float(self.config.gps_frame_lat) lon = float(self.config.gps_frame_lon) geo_delta = Earth.Inverse(telem.lat, telem.lon, lat, lon) @@ -167,6 +163,15 @@ class CopterClient(client.Client): gps_dx = telem.x + dx gps_dy = telem.y + dy logger.info("GPS frame dx: {} | dy: {}".format(gps_dx, gps_dy)) + trans = TransformStamped() + trans.transform.translation.x = gps_dx + trans.transform.translation.y = gps_dy + trans.transform.translation.z = 0 + trans.transform.rotation = Quaternion(*quaternion_from_euler(0,0, + math.radians(self.config.gps_frame_yaw))) + trans.header.frame_id = "map" + trans.child_frame_id = "gps" + static_broadcaster.sendTransform(trans) rate.sleep() From 9f22603ecb286f18cffd3290fb20d89883c2db1b Mon Sep 17 00:00:00 2001 From: Arthur Golubtsov Date: Sat, 23 May 2020 15:56:43 +0300 Subject: [PATCH 17/49] Drone: Update animation_lib module --- Drone/animation_lib.py | 346 ++++++++++++++++++++++------------------- 1 file changed, 188 insertions(+), 158 deletions(-) diff --git a/Drone/animation_lib.py b/Drone/animation_lib.py index 2018fdb..6d2c4ad 100644 --- a/Drone/animation_lib.py +++ b/Drone/animation_lib.py @@ -1,6 +1,8 @@ +import os import time import csv import copy +import numpy import rospy import logging import threading @@ -15,165 +17,211 @@ try: except ImportError: print("Can't import LedLib") -import tasking_lib as tasking - logger = logging.getLogger(__name__) interrupt_event = threading.Event() -anim_id = "Empty id" +def moving(f1, f2, delta, x = True, y = True, z = True): + return ((abs(f1['x'] - f2['x']) > delta) and x + or (abs(f1['y'] - f2['y']) > delta) and y + or (abs(f1['z'] - f2['z']) > delta) and z) -# TODO refactor as class -# TODO separate code for frames transformations (e.g. for gps) +class Animation(object): + def __init__(self, config = None, filepath = "animation.csv"): + self.id = None + self.static_begin_time = None + self.takeoff_time = None + self.original_frames = None + self.static_begin_frames = None + self.takeoff_frames = None + self.route_frames = None + self.land_frames = None + self.static_end_frames = None + self.output_frames = None + self.output_frames_min_z = None + self.filepath = filepath + if config is not None: + self.update_frames(config, filepath) -def get_id(filepath="animation.csv"): - global anim_id - try: - animation_file = open(filepath) - except IOError: - logger.debug("File {} can't be opened".format(filepath)) - anim_id = "No animation" - return anim_id - else: - with animation_file: - csv_reader = csv.reader( - animation_file, delimiter=',', quotechar='|' - ) - row_0 = csv_reader.next() - if len(row_0) == 1: - anim_id = row_0[0] - logger.debug("Got animation_id: {}".format(anim_id)) - else: - anim_id = "Empty id" - logger.debug("No animation id in file") - return anim_id - -def get_start_xy(filepath="animation.csv", x_ratio=1, y_ratio=1, z_ratio=1): - try: - animation_file = open(filepath) - except IOError: - logger.debug("File {} can't be opened".format(filepath)) - anim_id = "No animation" - return float('nan'), float('nan') - else: - with animation_file: - csv_reader = csv.reader( - animation_file, delimiter=',', quotechar='|' - ) - try: - row_frame = csv_reader.next() - except: - return float('nan'), float('nan') - if len(row_frame) == 1: - anim_id = row_frame[0] - logger.debug("Got animation_id: {}".format(row_frame[0])) - row_frame = csv_reader.next() - if len(row_frame) == 2: - logger.debug("Got frame delay: {}".format(row_frame[1])) - row_frame - csv_reader.next() - try: - frame_number, x, y, z, yaw, red, green, blue = row_frame - except: - return float('nan'), float('nan') - return float(x)*x_ratio, float(y)*y_ratio - - -def load_animation(filepath="animation.csv", default_delay = 0.1, x0=0, y0=0, z0=0, x_ratio=1, y_ratio=1, z_ratio=1): - imported_frames = [] - global anim_id - try: - animation_file = open(filepath) - except IOError: - logger.debug("File {} can't be opened".format(filepath)) - anim_id = "No animation" - else: - with animation_file: - current_frame_delay = default_delay - csv_reader = csv.reader( - animation_file, delimiter=',', quotechar='|' - ) - row_0 = csv_reader.next() - if len(row_0) == 1: - anim_id = row_0[0] - logger.debug("Got animation_id: {}".format(anim_id)) - elif len(row_0) == 2: - current_frame_delay = float(row_0[1]) - logger.debug("Got new frame delay: {}".format(current_frame_delay)) - else: - logger.debug("No animation id in file") - frame_number, x, y, z, yaw, red, green, blue = row_0 - imported_frames.append({ - 'number': int(frame_number), - 'x': x_ratio*float(x) + x0, - 'y': y_ratio*float(y) + y0, - 'z': z_ratio*float(z) + z0, - 'yaw': float(yaw), - 'red': int(red), - 'green': int(green), - 'blue': int(blue), - 'delay': current_frame_delay - }) - for row in csv_reader: - if len(row) == 2: - current_frame_delay = float(row[1]) + def load(self, delay=0.1, filepath="animation.csv"): + self.original_frames = [] + self.corrected_frames = [] + self.filepath = filepath + try: + animation_file = open(filepath) + except IOError: + logger.debug("File {} can't be opened".format(filepath)) + self.id = "No animation" + else: + with animation_file: + current_frame_delay = delay + csv_reader = csv.reader( + animation_file, delimiter=',', quotechar='|' + ) + row_0 = csv_reader.next() + if len(row_0) == 1: + self.id = row_0[0] + logger.debug("Got animation_id: {}".format(self.id)) + elif len(row_0) == 2: + current_frame_delay = float(row_0[1]) + logger.debug("Got new frame delay: {}".format(current_frame_delay)) else: - frame_number, x, y, z, yaw, red, green, blue = row - imported_frames.append({ + logger.debug("No animation id in file") + frame_number, x, y, z, yaw, red, green, blue = row_0 + self.original_frames.append({ 'number': int(frame_number), - 'x': x_ratio*float(x) + x0, - 'y': y_ratio*float(y) + y0, - 'z': z_ratio*float(z) + z0, + 'x': float(x), + 'y': float(y), + 'z': float(z), 'yaw': float(yaw), 'red': int(red), 'green': int(green), 'blue': int(blue), 'delay': current_frame_delay }) - return imported_frames + for row in csv_reader: + if len(row) == 2: + current_frame_delay = float(row[1]) + logger.debug("Got new frame delay: {}".format(current_frame_delay)) + else: + frame_number, x, y, z, yaw, red, green, blue = row + self.original_frames.append({ + 'number': int(frame_number), + 'x': float(x), + 'y': float(y), + 'z': float(z), + 'yaw': float(yaw), + 'red': int(red), + 'green': int(green), + 'blue': int(blue), + 'delay': current_frame_delay + }) + self.split_animation() -def correct_animation(frames, frame_delay=0.1, min_takeoff_height=0.5, move_delta=0.01, check_takeoff=True, check_land=True): - corrected_frames = copy.deepcopy(frames) - start_action = 'takeoff' - frames_to_start = 0 - time_to_start = 0 - if len(corrected_frames) == 0: - raise Exception('Nothing to correct!') - # Check takeoff - # If copter takes off in animation file, copter must be armed first and then all animation can be played - if (corrected_frames[0]['z'] < min_takeoff_height) and check_takeoff: - start_action = 'arm' - # If the first point is low, then detect moment to arm, - # delete all points, where copter is standing, and count time_delta - for i in range(len(corrected_frames)-1): - if corrected_frames[i-frames_to_start+1]['z'] - corrected_frames[i-frames_to_start]['z'] > move_delta: + ''' + Split animation into 5 parts: static_begin, takeoff, route, land, static_end + * static_begin and static_end are arrays of frames in the beginning and the end of animation, + where the drone doesn't move + * takeoff and land are arrays of frames after and before static frames of animation, + where the drone doesn't move in xy plane, and it's z coordinate only increases or decreases, respectively. + * route is the rest of the animation + Count static_begin_time and takeoff_time + ''' + def split_animation(self, move_delta=0.01): + if len(self.original_frames) == 0: + return + frames = copy.deepcopy(self.original_frames) + self.static_begin_frames = [] + self.takeoff_frames = [] + self.route_frames = [] + self.land_frames = [] + self.static_end_frames = [] + self.static_begin_time = 0 + self.takeoff_time = 0 + i = 0 # Moving index from the beginning + # Select static begin frames + while i < len(frames) - 1: + if moving(frames[i], frames[i+1], move_delta): break - time_to_start += corrected_frames[i-frames_to_start]['delay'] - del corrected_frames[i-frames_to_start] - frames_to_start += 1 - start_delay = time_to_start - # Check Land - # If copter lands in animation, landing points can be deleted - if (corrected_frames[len(corrected_frames)-1]['z'] < min_takeoff_height) and check_land: - for i in range(len(corrected_frames)-1,0,-1): - # print i - if abs(corrected_frames[i-1]['z'] - corrected_frames[i]['z']) < move_delta: + self.static_begin_time += frames[i]['delay'] + i += 1 + self.static_begin_frames = frames[:i+1] + frames = frames[i+1:] + i = 0 + # Select takeoff frames + while i < len(frames) - 1: + if moving(frames[i], frames[i+1], move_delta, z = False) or frames[i]['z'] - frames[i+1]['z'] <= 0: break - del corrected_frames[i] - for i in range(len(corrected_frames)-1,0,-1): - if (abs(corrected_frames[i-1]['x'] - corrected_frames[i]['x']) > move_delta or - abs(corrected_frames[i-1]['y'] - corrected_frames[i]['y']) > move_delta): + self.takeoff_time += frames[i]['delay'] + i += 1 + self.takeoff_frames = frames[:i+1] + frames = frames[i+1:] + i = len(frames) - 1 # Moving index from the end + # Select static end frames + while i >= 0: + if moving(frames[i], frames[i-1], move_delta): break - del corrected_frames[i] - return corrected_frames, start_action, start_delay + i -= 1 + self.static_end_frames = frames[i:] + frames = frames[:i] + i -= len(frames) - 1 + # Select land frames + while i >= 0: + if moving(frames[i], frames[i-1], move_delta, z = False) or frames[i-1]['z'] - frames[i]['z'] >= 0: + break + i -= 1 + self.land_frames = frames[i:] + # Get route frames + self.route_frames = frames[:i] -# Needs for test -def save_corrected_animation(frames, filename="corrected_animation.csv"): - corrected_animation = open(filename, mode='w+') - csv_writer = csv.writer(corrected_animation, delimiter=',', quotechar='"', quoting=csv.QUOTE_MINIMAL) - for frame in frames: - csv_writer.writerow([frame['number'],frame['x'], frame['y'], frame['z'], frame['delay']]) - # print frame - corrected_animation.close() + def make_output_frames(self, static_begin, takeoff, route, land, static_end): + self.output_frames = [] + if static_begin: + self.output_frames += self.static_begin_frames + if takeoff: + self.output_frames += self.takeoff_frames + if route: + self.output_frames += self.route_frames + if land: + self.output_frames += self.land_frames + if static_end: + self.output_frames += self.static_end_frames + self.output_frames_min_z = min(self.output_frames, key = lambda p: p['z'])['z'] + + def update_frames(self, config, filepath): + self.load(config.animation_frame_delay, filepath) + self.make_output_frames(config.animation_output_static_begin, + config.animation_output_takeoff, + config.animation_output_route, + config.animation_output_land, + config.animation_output_static_end) + + def get_scaled_output(self, ratio = (1,1,1), offset = (0,0,0)): + x0, y0, z0 = offset + x_ratio, y_ratio, z_ratio = ratio + scaled_frames = copy.deepcopy(self.output_frames) + for frame in scaled_frames: + frame['x'] = x_ratio*frame['x'] + x0 + frame['y'] = y_ratio*frame['y'] + y0 + frame['z'] = z_ratio*frame['z'] + z0 + return scaled_frames + + def get_scaled_output_min_z(self, ratio = (1,1,1), offset = (0,0,0)): + x0, y0, z0 = offset + x_ratio, y_ratio, z_ratio = ratio + return self.output_frames_min_z*z_ratio + z0 + + def get_start_point(self, ratio = (1,1,1), offset = (0,0,0)): + x0, y0, z0 = offset + x_ratio, y_ratio, z_ratio = ratio + first_frame = self.output_frames[0] + x = x_ratio*first_frame['x'] + x0 + y = y_ratio*first_frame['y'] + y0 + z = z_ratio*first_frame['z'] + z0 + return x, y, z + + def get_start_action(self, start_action, current_height, takeoff_level): + if start_action is 'auto': + if current_height > takeoff_level: + return 'takeoff' + else: + return 'play' + elif start_action in ('takeoff', 'play'): + return start_action + else: + return 'error' + + def check_ground(self, ground_level = 0, ratio = (1,1,1), offset = (0,0,0)): + return ground_level <= self.get_scaled_output_min_z(ratio, offset) + + # Need for tests + def save_corrected_animation(self): + name, ext = os.path.splitext(self.filepath) + filepath = name + '_corrected' + ext + with open(filepath, mode='w+') as corrected_animation: + csv_writer = csv.writer(corrected_animation, delimiter=',', quotechar='"', quoting=csv.QUOTE_MINIMAL) + for frame in self.corrected_frames: + csv_writer.writerow([frame['number'], frame['x'], frame['y'], frame['z'], frame['red'], frame['green'], frame['blue'], frame['delay']]) def convert_frame(frame): return ((frame['x'], frame['y'], frame['z']), (frame['red'], frame['green'], frame['blue']), frame['yaw']) @@ -189,24 +237,6 @@ try: if color: LedLib.fill(*color) - - - - def execute_animation(frames, frame_delay, frame_id='aruco_map', use_leds=True, flight_func=FlightLib.navto, - interrupter=interrupt_event): - next_frame_time = 0 - for frame in frames: - if interrupter.is_set(): - logger.warning("Animation playing function interrupted!") - interrupter.clear() - return - execute_frame(*convert_frame(frame), frame_id=frame_id, use_leds=use_leds, flight_func=flight_func, - interrupter=interrupter) - - next_frame_time += frame_delay - tasking.wait(next_frame_time, interrupter) - - def takeoff(z=1.5, safe_takeoff=True, frame_id='map', timeout=5.0, use_leds=True, interrupter=interrupt_event): if use_leds: From fa897d6e52a0020828b8c8e4da128cf28166cf0c Mon Sep 17 00:00:00 2001 From: Arthur Golubtsov Date: Tue, 26 May 2020 10:40:18 +0300 Subject: [PATCH 18/49] Update addon and modify example animation --- blender-addon/addon.py | 30 +++++------ blender-addon/examples/basic.blend | Bin 0 -> 453148 bytes blender-addon/examples/basic/clever-1.csv | 51 +++++++++++++++++++ blender-addon/examples/two_drones_test.blend | Bin 669372 -> 0 bytes 4 files changed, 65 insertions(+), 16 deletions(-) create mode 100644 blender-addon/examples/basic.blend create mode 100644 blender-addon/examples/basic/clever-1.csv delete mode 100644 blender-addon/examples/two_drones_test.blend diff --git a/blender-addon/addon.py b/blender-addon/addon.py index 221e6cf..e0cb41f 100644 --- a/blender-addon/addon.py +++ b/blender-addon/addon.py @@ -8,13 +8,13 @@ from bpy.types import Operator from bpy.props import StringProperty, BoolProperty, FloatProperty, IntProperty bl_info = { - "name": "Export > CSV Drone Swarm Animation Exporter (.csv)", - "author": "Artem Vasiunik", - "version": (0, 4, 0), + "name": "clever-show animation (.csv)", + "author": "Artem Vasiunik & Arthur Golubtsov", + "version": (0, 5, 0), "blender": (2, 80, 0), #"api": 36079, - "location": "File > Export > CSV Drone Swarm Animation Exporter (.csv)", - "description": "Export > CSV Drone Swarm Animation Exporter (.csv)", + "location": "File > Export > clever-show animation (.csv)", + "description": "Export > clever-show animation (.csv)", "warning": "", "wiki_url": "https://github.com/CopterExpress/clever-show/blob/master/blender-addon/README.md", "tracker_url": "https://github.com/CopterExpress/clever-show/issues", @@ -23,20 +23,20 @@ bl_info = { class ExportCsv(Operator, ExportHelper): - bl_idname = "export_swarm_anim.folder" - bl_label = "Export Drone Swarm animation" + bl_idname = "export_animation.folder" + bl_label = "Export clever-show animation" filename_ext = '' use_filter_folder = True use_namefilter: bpy.props.BoolProperty( name="Use name filter for objects", - default=True, + default=False, ) drones_name: bpy.props.StringProperty( name="Name identifier", description="Name identifier for all drone objects", - default="copter" + default="clever" ) show_warnings: bpy.props.BoolProperty( @@ -61,7 +61,7 @@ class ExportCsv(Operator, ExportHelper): filepath: StringProperty( name="File Path", - description="File path used for exporting CSV files", + description="File path used for exporting csv files", maxlen=1024, subtype='DIR_PATH', default="" @@ -96,11 +96,11 @@ class ExportCsv(Operator, ExportHelper): distance_exeeded = False prev_x, prev_y, prev_z = 0, 0, 0 - + animation_file_writer.writerow([ os.path.splitext(bpy.path.basename(bpy.data.filepath))[0] ]) - + for frame_number in range(frame_start, frame_end + 1): scene.frame_set(frame_number) rgb = get_rgb_from_object(drone_obj) @@ -135,9 +135,7 @@ class ExportCsv(Operator, ExportHelper): round(rot_z, 5), *rgb, ]) - - - + if speed_exeeded: self.report({'WARNING'}, "Drone '%s' speed limits exeeded" % drone_obj.name) if distance_exeeded: @@ -195,7 +193,7 @@ def calc_distance(start_point, end_point): def menu_func(self, context): self.layout.operator( ExportCsv.bl_idname, - text="CSV Drone Swarm Animation Exporter (.csv)" + text="clever-show animation (.csv)" ) diff --git a/blender-addon/examples/basic.blend b/blender-addon/examples/basic.blend new file mode 100644 index 0000000000000000000000000000000000000000..39d58b0e6c9a7b75a920b3ba45debca017b8470b GIT binary patch literal 453148 zcmeEv31D1Rx&O`5}6}|0+gjq(gg-6P?|z3Pi@mSkZ9WUB`Fl;*@*Aq zxI9HvtSC^yr3gF`(T56M0Cf=XRa__{77?}Xy!ZD#QIYw7zkAMa=FG{xGxyFUWm@L8 zH)s2{^L^i0@4aUhELnW-R4@zriln8B8PI@&)z!W}!SXj^IDGiz z{MWOrrluy<-q9bx2VPHkij6n)VYU%dzTv~F_MJMiZr{wBx_u);^bsa*vBc50!S1MI zP(6!9*4Il8v=N(kT~)Q$=8qmxv#;tP8u#sbAPpa{tE(%u{Qx`K_MhyI*musTG5aRC z*~bMxT^C~>>W8+Eejvzw{&B*{QTtApv3``a|Doi0plD+qK>x-( zhw%f&|6t$)9gP;PaU3KD;+HM z%_e7%tS=*sK918Oy_U&QYby#0H z*lmwJoD)Wj+INPm?P>znc89{+2z7|sj{)aU@ckek9VvUDSlgW>Ydbf~KJLM8f7m^r zINi39E~fk_Ydfr$vdx)_sa}4v)YjJKJ%>fT3&Ho3=k?CR+U{)W??;Dxb_@SM zSl7ky-9lwQRMrHpjP*0}cy5P&5PE#Q{WdCp+AS)cA3S_0pE1Ob*6WeSwcRm!KDP_n zGkHDg5|(GKwSHJSgbUg?WO=cML2J7x@@Dh>s;bO=<0y283KsMBp_seFrwScUhN{js zU2|<$=yN;hGH4z=`IPE?!$%d54{+XY>tis|q+RUuVXW;=(`&oIcy0&V2eqeBW2^Q} zomsuFeo}EfA4Fdj1I4~37&ipZ?cn#>#sGK3uzd~Fs=d2<`0Ljfpl?19b5;0P zG0usOlxMY3)^<_aLpJ++bYvcYK49uh(T(&8QD_eaEc<4JgF!DB4fKZx!q;-ifpS;g z&B$kM7lt-rP}eZJ#asY$g7HUH@2jdlaD4&fJJ548w21@FllEYkxwacR-$y;e))jUi z*I2!8@^pEAR6BGsQ3vRcbM{ZtP6$6(A^V)yYd$zhKes#V0KAilx<>8qYQ|LUn{sOP zzR{D1)(0>ahv}OJ%kCx}@IBCb#8}%+)6eY+UE7&;$raD1jbT1`*zwi-CZ15e5A(!a zJffCk91dEWM5UE4Jm-T4!UxT@UAE_T`PL;HUOx6Gy}F5dZa8l6`hvmS64OE zrc6o7oUmZorjDvh_D(uFdH%SG$x$PVqplMtP855oHfP#DAKllD8JV1ZQEhVeWp&B= z@gqa)tD&JGIePSHPmX-}4mA#J4<9JRKTjJyF8Pkh$0VoMk1eWtqW!a8rv0O|e^dYP zr6W!ql{{;0eR9f4xqTdVhy8(P(kZt1m@#9LY%5V@8#Q8N^3w5>lN%--m7FB)f25?N z;14w{#$DI|_BQQb2w(L7V;V*#Pg_}+JULdI998c>9>C`4_tD1|!tX%gPM9zui90O6 z2{ogVZ<}~za>Zd&ll4W?-`ldIZyP^;yjLgF{`t0llRoB#$ITm+oV%(nIsS-&eF1bi zZrr${Z~Lqh{1tr_+6?o8!fAD*lh;arfA-i3MO{DEX)xQrt&_u!tVzyYU6(xe?AqkW z8r2uT?=ZJ6_I}^43+JkG)SEF61S^6JS)Cyy2%I2h{3ac!{Me@b^U9@LH=nLOo! z+T@%Sb;;2aMtZi-ro$9&i1o*ThUI+F9=AqIAOF_F4o_ljH(tiHkwqxGpRUmV4`ur& zPqc-pr_^|J!^WA?7ZmxNk8=Bk_4W1M+=X?RI;ytl)^>h!`{iN&hxzT0wSS`>pv_D^ zYOL1}oN{5U^b7uTLq9o-DGzqX+y(Q2)Y@)zajosb>I-AqfoT7b2l_(W!93vDvqvS* zlz9Q>2VrF`Cb&`J1Is5&PIh~1yMwzw!yI6!+dujl>^HE_#5!?;tP8L{IBs5gZdgn< z4wA)O+fANun74)wf;|}W(f;u~fIcwTGQ;LUd%Tz%VxM5nn`CZySaI!-`00UbyL0r~ z&KuKY9|QA3KlozLLw~H-Vk!H>9|luyto1R+VB5E8qc50xvf4L1;?&|D4;IMU?i!!9 z9c&-u-*#P#B@Mfy--m6qy;JrdOc}Ax;~Fy?UHCw~tPSz}V1_(LK);X;{y>)Dxt&_u zHF|41Ki>K75&O{^$~@WwV=?+Jv`KxZ>_3?8AAL)dzOkleWb$Zvj)>=phskrrq0~hf z+FaXRFh2WpJLnK==_q@|VfYUgjA?h+!|aP;Z&?SV>_1pMu||SUOkQDWk3YO7iT4VQ zZ>mk!jCy_6iF5RGyBQ&$+o7Ie_lU#lp-?#38Zx21+jci;i|i@;4@Ub(--5Q!=Zhw< zu<_dZk;#+JtxcYGMO|{t!~-`T;JMvWd2WZb-QWL^^b}VyQ8g#vAapz zeBZUu_K&%cxz;!NpmVmnZ0+5~g}t%%7aHDoA$EC`H0oPuzaPX8`-VrKA#=ku^~r`~2X}6WwH=<@ z9WzMJ?Vx`=D@HqvVvC@<2jv%HcatXUk3K;1_Q4;}hRyP@arhwi`i82`=nKY8k-6cU z>ypzJ$lS0t``mE)_z}smS>}&e+r35l`gILzZ8w4O?I>4IK}5|7y2?QzrTWc*Bo_`~dw0#yh~9_(L6s z57dp7x#6XCa?4s*_7HEMTb0~#N@el`!^)GVmpR_Oc0yV5!FiR*Wk(K|wcRAWw#(cH z8A@4U`+W9@q0gbRyIEhm{hMWon|?qaBnfo&aNx!C`QM_~y#b*8Xi=$icpd z{Q=jSHtc~+W8SuXUUl-e6DpIPb6wB&H;NBjCpUfIZKDrMzG+mwypLTTI42ltnbDTB z?Gf8{hpvZOCeY8-{%si`C)z!H!0r!h*aMM1|L_sXy)DC%?=3G&zE5oaF>U)RrQg3| zWLfe;Jr2+p?wnbXyk@#9Z87`$VX$R~-q2p`_lS{?Jm?bPfwDX57p481`hbkEKl~uq z{J_K;iui|5bCcgVp(15_*neeJSu%OWu;lk+mC36l-L8G$3<-BNRU|KNJn;5M-Jv(w znftP@m)%V}L~Z}T#rl(L5cUV=T*ah6l<_**7y8xGs^ll!aU!Gc?{J6F~ec*$V#yo&&gm;W8OaAoYVaa;^Jwi6xLs5o371$l~0Q8^G z*HG|@LNiMHhmG0q2k`(5#s$`KsQqB9>;v94uPXV|mdfN8$7l8frtR;Qx!*5Zhb5OB zec=0k%>Q9`v<xwSIg?mr>%|2nH8dDr}^>+J<&V9|*fcHa}eS1?W$Qx{oAp@I$`?JHqx++COmM1Edu- zH2nay3axYVmEKF{r7;{{WOC%>_`OXjO1^z%jkrzgk+8iOet^-n(ledy4SznM3=rZ%~8;^02F8`DtZeV29;&Mb(y zE~MSz%g}dJyPI`GyM!JJp#fa9RnQ456WTwX|Ksd4_ygKG>_XWIPqcJYi^sl{E+sJm3f5YxV~<(pN8$vy;}#nHrPjx&2Vdf_Z^= zW`TT{cFbAQhx_ai429iIeV}frKl%bQ-!Beb!9Hj+Y_s|3hPFote*iDq0<>WZ_yFqe zC&ystfj4+T4~LJRkZf-_B6+!t_oKz92b-54J)T*B?@@4O0bnQ({W2dt`|?_tlE?=L0|@fb8f zKIsdlo}#`*X`CV7qcn_9>3RUH_{U)9p>3PG!*(cB^2Sc_x@v#*XrX<^>&mS*iy~!W+=?9Q6Xr5@|M2*A0 z;n%1W=0Tp%%XwCq58#`m35`QL27w>f0jT4Fn19*rAAW~Bj6FU5h|VB0`-FV$GD+*|ui4)H z^}n&qt4@JkTj_t|Q^sL9%0Q z7aP|g&uHrwxlRSGadK|jDmgc; zRmS^zX$$$%Cm!U^h+MRqDVr__bk54{7_n8II0rYP6i&6EAx&a^lLK$*} zfJHrFZ`_DO7Vsv2jQ{vO0D(>I|K&S|XOjo~*e4t>-&bQl=Pa4$Q;ux7QOZD%>m~LF z$b%2U?qTOPVR+)K)1&0wV|R% z(_2`&HXP=LFpb7fW8T6H{zPZXk5Avda2R-)h z_%03KCLTUL{5YM97s?^K*{_2y@>stxH2h%f^}ipCnFoHf75g0MA*i1_N!E5a5B=l_ zXQ`lGpp81ho?+@pTR=yUB?t%kW`7XX4}c4M!;c`F%?sl}{W!c|QtO>>m+yI2kdy5V z{7^628)c9i`U%pzX|td>+dq9E2rgSb_(M*N+4eYJ0lrDkj^jM5Mme_x^8$NrXu~oMKCCa~ zLL72Y7ldO-U(6*7;mr_qL0|H`wIAkx=J;>IhKW-~$TWG}cyAwfshpEKR@U`l@Uy{y zHf#mmv)$!ucbgArQ;+a5`T_ExAC5=h$2!_Hg3{;<#!jyB&M(2-(C<7e)Qi3a{@@eE zkI~+s7swVQ2lBxOu;2lB#M%Ba{=@!|gJpovMlY!B2(kCt!;VOvK1SNB_!#KHmqDAl z4Z;nR5Bwl$kcQ`LORt2!B91`v&5IHheNj2IkMHAD3Js^SmSF*)91X#0`1i17@9x z7bXNg>;jn}ALV4*K^R6xKVHCttx2CgMqYk6QSv~4^qA4fsVCMaXUTg7ji=V7>Oeh3 zp=ZN_2K7ptp+5AhARcW0^Z}bL(!izdKpT37|C%&wM~qCy#!gHwlm333*v5|z^MD8Y zK|c0H+1fC8qi*00J(_$eAM(sL#lD(6gF@o4-vu4gCr$E>+ILZB#HUO+v*1KI+YW2R zeCLL!59pgZL>mRKsB+TwZ2v*JqHbUV)CGP3n@o}Z{!Q|J4&K2JTH9r#NnfCz=v$~C z{R1$xOAsyO1IDBW8Et(a4Y|P&`pZSz_5;-4PX@w~Hh7~B$j5EsjFh<{o*m3wCToch z=ime1&)znD)CKjj`I)@UINLw=|F8!f77sK4i|;RRKJE$i(w24K0N)7Gk;&VRgC6jp zKdj%mSDOzl%10l9PGArE81-h$kSz`Q;G3iYxp1T1!B24`PJha!OUw;17r;6Z-=f%a zL)shqz-`jbCMRSBd=P)+gEr_vC$K%|fA9z7pY`tid1^=HaH?q%brBpdYF4s>w? z4>TYH!fbR*+R&TX-fi8o|1@!O#ZCL8PV@uh=fVvt2QKJf%?Tfc9wBRxzJus6A9KT_ zfd=3avR&i%jxLmU zG_aRfm*w~EKm&RK9%#THKnHwTcZ7gJ8E&>6vz+pP4q)ITl*NQ&9Q42kHX=XJU^(;w zc~A~IW*K435aqxjZ{Q-0eCUWim5Yu|lkG(LyvQT(7U6ePr<{zjF28pQp+D4#^@q$z zn|ih7r0s*of578LosW`!{xW%9J6p#3(VDJVub{X|1MLNJ;ig|upF#M{2Oatp?ZNhk zdb0n>)#sRWhyxi+J|;czpbmjcID|;&@&m%Kz6foausP;~$IY)zPHP#Jb8ZMdfG=zf zdZdYZpbR&31i4Ll%{bdX*8aFzM-ztelVu;bRp$3o^?MrN109*Xv*D8tQK0|$j2W^dVKf2%tdF(QzQD@M^_>Z=aIRN|tJU|;EbWkTW@ILN& z(&vwr=Q#HFcH|9uwm(2V@$Emhq*44zx7KdEW3GE6Q zaigAW{~QBA2mBdMkhXq-*!^^w>y51+?a=@|!uauJ8Duqeg1r^+&DB1Ej~lv&%=CA} zsauqhk1d0VPkSJw?o9h4&U~95^66&?O}PO}y;3I7GHneQ$cb{`+w>_n_yCvngsh;A ze7s+R-_<^0k?11F`-Y$o9N>Z<=pe)m`jo+z1GY!skM$q?f#r>2`-|kf#A9mH-wUID zs1xX!^!@AsI;Oq7xY*U z;6o1FrtB!sCI`yEk9D99Xm8ks`tzfOc>&I=z48~+L`qs4d8<( zeG71)0XV{dF6!$?18~Fz3}A`J5Ikrn+8b#T7d*51uBjKuK{@FM*>Fucz|+&mgnDnC zc#J$p8Y^+g4H~9CiD!p^0X$_u{UJX>w0rsh`~hSATs*rG+N=ZM%p3Cf$?wT2bw!99 zJa`A;Kvv2}o6&CIY1-AVUbGe3HQFfKDezE++r}eL)B!wbABJoz)UQngY3dU=s2kF# zD`iKVbvETd9Qp7A@Fs0j*Wk~aIv@@FHqx{OGGCS7$+7EY(f}THk2rWjCy&NVuSo^@0z84}2FPeN~QeFFRfH`j+|Ux9f8%b3q}5IpCZfa5!-fHmXn ziwJ9mz>~Uo@W8>1JQK!_gC2Mgk0J1ac(Lx}!TRKq6L`cSeS|hX%P1?-q|H2pl!N#T z0bgBRwQtn8s(oV)7y2Wre030nPdX?EpDDvf>>FX&ThSk=pCEd6eOL#=qntchPx2;g zP<>5YKRzrcUBWP*e32#(G)$N`s9gxKRo1_Ft(g1vvCQ7(9|#KFhsd`yTkU? z9#*xlej<}~w%aC#s7WFsvAA|#V>WXz?NZHB59bUDsu0huFGB@COKtAM) z(8ebnhAcNjlrc>>Q#O+i;0QxJlRsrJVF(9YKUma}@RY+2O}Zu>!kPHwVZ#B2`lL*R zHACPLjv?V`FTw+sya|iY=8d#@6Nhv`kL9F2keBoUqs0$oJiy%0ra^p$VafqZIO63B zAs=P4eS`E!gLPrZa@L!0exWHB@Cjq$n)xVanz(+jwk)(8VOh=)bV<*IWjR91W`@L} zTqeA!H|7%#A#o{_Nf)+9Uof&h(+7w{SQD3J3{4({XP67dq)QxzX1(YmEHgvu%&a^4 zW}^=pxynsGX55dyDFj;sl=HmDsheYcDa zW_h-_pA3X&8PbgVg)C?22V>_^cXnvPu?%^Lv&@vwh9O)OS^VfSk0I$YjSzYCZ-%Bk zg!Ri~nJK5ugY`6Fh=H)~db3>fz}< zM#9=5X+{lAIwmgjna8vlqRdX)@OIjSH{-M&LUXJ&^AR_3$e*EEX2x@so3Lh_vYVk@ zj&x8tVaY!moLOeZU9mmp1tV(Ha~QLnaq@~15;xzFynv55Lo?sbW0`GB<_CqQ9|XZ5 z->!=Z8zhSj69i||w&BcjnpScXw> zqr$RYQSk^*{GgCYZ}LE6(VCGr}`Zn=^B5`>eIIPM^63 z=@}i}>)U5s)!ntOt*5Vj#@ep#4ejeBXurC>=ak-#?rUbO>1yAwP7-bF)^%@~(Z0ED z{l>2L-WhA!dOO!n_Xz?}o;OubjEADL)&+noS{>(Q z3El52I$KEhX_W?y-?gH3_M*niJKL{mJaH|DyF^!zJp}e5TOa)1H*OKY$j&Bov>k1^SG2Zl=v?2{ z*V&za7nzT}wrhJaocn^Z7eialY-!VPX)n?ZvX?tr*{jgn(_WN2m%S)g%3j(&DSNTq z2DGyktqZ%?Z|v^v?CaccRW_w$Da*%RaYywd47WGD*Xv&xW??s=NL)H~(O#q*WUs_H zWv@bOPkT}BT=t?|DSK)Ar0n%0-M+LPQx(&Z6|IZfuWZ}Y)n^w(NJp_(too=sFb=!A z-%o`TmAzaY+Uy%{y}|208J3MoLzr_F+KY69?8Wi3(Av{plslKbC>QWLqzbIhbpico zYkODw+P=mG-J8?(jTS3&psM{(Gl5frA{ zmu^p~_Vr`kzI3~@@mI8-*S5aBaY^@8oonr~u<0o6i!?iQ`(n6P_lFD#7bdJUfMB1M zk|y1t_7&TEkLPcN)*k#Cg&*b4)xIcK%3j(&DSJJm?WOG~q%*D+trvA;veCG_r)}-q z^BcqRu~)o8k6${Rw)$i>ZoI+cS7_~NFUpDpmVpm>4zP^LK`nFHBf% z0MTBg8)Pqcld@N#wWqx(cP@KTu9UsBeP}OM#npCXn&FDp%RAS#cNb)`eA?G`Z7+uV zYn8niQob-D_7Ql|UZfjjuh`YfUWL}4_M+Um>_xd+Wn*VsgUpll^N2lC0nU+F7r(G| zVe8uV4ed@}+g0sOTW4O2W0)e~187+UW@N9pn#FP)Xj=v0)H7FDU zSuf!6-sQ=qz{}+EpHtR7<~?U2J)>{^#u-`ZlnP54C~4qO)xZ(5z^XY^D_5#lNdqA@ zAoHmv-fG-55&P$JU$)BO3RjJ-Y7FmhTCimC#fug%TeW!c<%<`as774P(~OKaSae2d zqVWc^c&WN24Hz0IcgpMast0Q?r>vaast44pLEX>k=PKV+At|W7mMMlo>!bdr3XfO6 z>^|V?Z~A|gr|rc3XMJz3QTs5e{EhyXYehR8sFy|eAg{ma%Fo{|y6taD?>x{J199ve z&;CYcl=O}IJ)J#sGvC+gU!dOGBH8`#`$fk4PRvh-lsCf-)5SXW=>JaH?dN;L%bnu} z087Tj^1iHJe%QQ1pW`Y%tXrCE@3Qk2q*Kvjh2q;B`Cc-^c&+uGvPL-Tsj*GGu?L21 znXAo*@5x?(dXcKN52r|^MY*eZwwx9dB0_D zzqkL-khIyuQ7)EelWXm@^4&(>Z@7@N3b$`i-;;DavMuBJ1XWMD2uHcxM>FA3kG8f| zz3QuxOdf-gqCBKUxhO|UE?pnW3%NG-v|lZ6)UFx|`2ywAa!@Ynm-52B*K}>_S#?!c zcW>{ttk#f))EDKTTx95Kc_EiF;;OY>V$4;Y8`gF9$SYn19OMvk)ysY*pN~B*^kK8P zpH}bFZN5c?9qW~!4rJfoq@H^SR$1y9NSRzmqMtk583z=v?~`Y1P3FzM2mmFxv~CV2 zy})+n1^>mkG*V|Ko^AH}wfJ)?G-30JyH~3BM`G99?a}4VA!)^)&*UR(pnmu>ZolgZ z8DKG9;wpER;t#5j_f;+5SPXNL$yweLNVIbAYRXLaTWKfHG|9&nFebi(c|xkbtPI~1 zaQA0E2Zyg=T-Hx?t1F!PM$yLrb>S)N(Q-fet!uqG4&vkQf43J`{wf(h*G!+hq0CZC z7QWGeIZyifV%w*Ymfp^Bo~t|GD}%41vX4A?|2*V9SNFKw=Qjh!ywFc}NCx}G-4X|$ zyZqhCzc;FV59*t^@gGL>$H&DV?J*6sz&|bwN$&xPgFkLw={V~>FjVpd@84|K^&rhR zb_o6X-2=4(`^f7!4)u4x7{m63G%aOM^gD4w->{#%=vL34+?6V%-*Ft?EifLO_Pc3+ zz1;f_%jOmPai-rT650E~?~32C)d-8!MZY7xq4K*9Lk}tDZ`g>R&#^6MDusSW{e|^A z`VFWB`CVMfIPcp*xD)mGJN|RtcYogO4n*|j?#=YO#hfh}Hu6u(H zuhZen{i^@$(djk1Kkd-zMvZ@zPCLKy{Me1@{L@vw;wpTbHQdFTPL~cVRX+WY^ISha zBrVndBgM9y4y(P|29L`f!~DO4P1AlO#iIG)a#0QS2S1c9$Gc?iC-Yv%k@@fZf1WqO zn>TOz=+^nazw|*bE=79$cN}>?w2AriMyzNeJZwoi^aZYU^7VyW&)8nk@1}c|c*FmR z<_pJ&zNkO=g0~kdd9G5EW4zJez{#*5UVD$gnfTy3Ye_7LsThH0M+OI5V3vbmD zAv}l|`l@Y0k8)COvS59BI>Hin=qZYR_$TgVRB2VU()?ZS~kC!x%R_;24H?W zZ9;Qm_co6oLqA;Pb$sV~@7rnUXPTrb-!%dH`Hp62?7r0v#8oMXB~QlIO}G^n(6bFUDVdw*WS~(u)7OiffsTHhDkSSe41V(F$}_Q3@XTx z5B;ETC;c-5^h13w!PzeYL;Lpy*xP2iq}B7xzH7VM?V?EOFt*E05My6+v!>NlE)5fL z+-lB(XS&AKJmuWfZnMrNJo`TbIu+IQQ|W^_^59nWtcoH0rb8;kbbEjwwBn&@exGuC z>QenZ-i_nc*n-$4Vxg5%$bNF0GnwEw3>Aw_Nt4W-yc+S3hnmPv?7W z*DFVcT>bLj(w&zd=@0D6c~hBsGEjkZnIo_4dX$DqVC#u#hLKnX0=qv^YnniSK`6-P z$GOO_y!5^S?Ku|?%GuwuADgKAd-u7auPH+1IlE?OLmZNMx%Cv3n zAnqi*N{O%PFvo(CZgnm&sFK`D$dq<$iCA=g$liFR15w?ypoh z03k;P^s!BNlFk@ipZJSD^mcVEQsnlucwytqnduz7a7F;GiQZq}CWH>dbl+JnZ@E{> zm%1aIcUMpHZVw^jkDqP`Kz%Z8hVm+$Br(z>#5G9;2iafRjdr5lVlOGX(Vkwl`tM)p z^mja#u3~>P)0L7rUKe~uFO)u{(|ETC7uQcLSGlRbn1eNs5TG*I?HMD9Dxr9XSnv)a zra&EEpEin}y^=sXu&**_u}~W(vM9UV?${?(*cio`rrl^K+Kug(_VlFp>~^=pGTnDak`4vYJFjDf{S)5S4oHEI>|uUP1KpT+c@VPl@)RmBpn8|H?%djH-Edq zUX}7s+3k@u$%86JyU|Xx8!JJ3GF>cTkli3(HoIXAGV7qy%7$isvBlkSM`f&0V7L3E zu`vJpBCx8(2M4SjFIb!$koTdg{+%)+%dM`FPcOkMH_oCzeSiM!FF&>8!P_qNaL&D^ zv+2VZoIC&MlfUkj-?98n4}Jc~_NEi(y_B&&Kl17B-9oPk_`tbO;6QJV(E5s2pSV!_19Z6ZVI zDh3Vw_w@I9{$k1!HV=Me;zf`5H@$iO8BKG)@xqQjJn^v|6~F)O4)_-7%(?4Y@7yJ( z5!#1p`8x=jud#Sn@;tZvoCA2p2HBW(Ym-e2ogzuVH$r$SfnTg4C4`)x<7Fq=Gu z?6*-4?qfapAthH3Ka3}hQb;)B?bY=mJ>qk&OgzF7uR-rGk{~q^oVEf zw>3&8;fUwz{W8)c-a@TJ%1JolB{UxCrSa1Hd*DYn;_cP-BfT`o5_y3m^vP@kJM z9PtQ8dQ5MrPUCs|!Krqy=4CN0C*j$irfGdKAAEsNc<#q;UYFUQ5YKL3#3wxQx2{q6 zDY^meh4_RgzWXlz{(x{@bASN4n%6gim<#->&gVm+i;y z$H<@X#D9?fqv^O>|3UbKCw@ZLpL84aIL7|f_8-C%{}nxdB3-uoAbi3Tf3udGbSZZb zKH-Va`5Wode`yC>e!>%<<)lmh^Ru7#JWbCN`8>^?kU3wRru~n0q(0cMlHVek|8TDM zjQCQe>=~R>w%1$f`t3Eu#;9|c7$$zLo}a{i=kxrFOtOUVr)|QQbhu9(|BWBL-3t|| zJuND{%}QrsBt>TnA6(w^wEo)#SDvRa)N2%!>*r~eBSaIEyc@Zhm!EC`L4EEJUeKp~ zfg(m67OAuiRs3n%jdr5l=m)fCvDGBVZsFt{o_e+y4!-!HXgAu4cB7=Ur=;Z8q+K%9UWo~-+aBiSE`?C!}gi@V}A7fxeR`;pKii)Nsou&af%C)cB7qWxA@D-ZnWotlG;r%GN0I_ zGE?Sj%P`yx!UC~8oH`P%lhIP4K{CrQT)KQTfU}oyL5Jz9s-k>LI)xbYj_<^&XS_FP;?N#|H81-KKj>h- z#rYNITdtmOaURBf2YVld`w-;8`TMgX%mo$l?xfu3l=TGeKe%V8`Bp0Yvl?F$|C`_Z zi2aDG>98O1r}t-B58>L=qQXMp$!MA33nwJ&z(bUHlFQ1&@*VN(phDqC>7@xo@;W=PM}wRnMqzk%oeOBUv$c>)4fL z=ai8iArS`k|7JTlRjAmPP0dc*@Wq-&ds`}cWc&Sz2;LCPN^U)_^{>lJvr+-!M|9&<`|7~OxTZtjaqXsxNe=FTm!EC`W$N?u z1JN`!{Y9#j`&0KpEV3;4;Wlv++KF~^|E}ytdn&Q(7CO!6K9;UR-NNJ+mHx0~Zj&41 z+DY0z_ID~so8^);FK>Sq-GYif@POQ~%SW}I?@&mo?^KXB%XKjWAG zbbhw)R6g5vBicm9HDy%hcPbY=Q`mPZKmLLGPGzz6oyuuBzEk<^>P0Qx*WSD9mG7s& zQ~Bfg{X0&5ApM=n^oLf>pVXB8PUXZ!?{0ek^33m4hJU@QMPz7MtG`oOs=rhD`;xEi zc>LiR^IsZsq3B@iR~B|{dgMQ!+}qOe>0yuj@GrOT`rZ7V1!uqZqusauykfx_?zZL( zC6yAdn+AsccijM6gw1ojIaeW~5ByFAzD0_^pHgu);|%RjBZB%T!ZXCU>3yf7=e>v8 zcPck}6*a$8`P)L)Jsp<5Q%UQh6dOzpl)h6bj_*`b`wMzMf%^*yo#y^I-`C}PgM9yw z`wpgWd`VVL*k5>$OvTG3eqZ@tyd4w2zBfKry|w6)hRx|C;o(#kt;zYdV}a z#~eR;@3Qt2PPWq7I5`gLiObtx*maWN%KidF-~EOA$|s37qUWY2ySe4cZnUSW#hWlzJ(8{w(r4e;P2MO!YpUREvJ1R)6Q}K)m7t2+O zf7QPE`>VNc?#RCRv(EG0O_+4(=hG|&pD)Obc3@wi=yA{mDs4j*f0}lqooKg&Zojl= zvDGBVZfI+`gY2gEdJm!f1#KtVjdr3v)q4upOZ(ckOYUY>{tijweFb$6C!R;Ky&?Ru z_IKtZ&2s#fo%efo*WDCzmM%O`(Q}+mM!cVAdZrAm!|w{R8`^v}yV(@&v>&fx%tL#_ zyZIb1(T8buXBAgm`k=bvJ3i~335`C|IBGO!9iX3$0!94Aq8#YQeEyS@NFD&T4Iqd2 zKEhGzISuDk@uOAR)%l!H@w*H@zv2AK6b1hMZ^8)so_PQBzVfZ*;Oo~W;&b);pA3ho zaWpZ)ZyscF@sG2gk83*YFJjd`^qSvh&7+EUUIl&LW*bjbaLCQ8w4G=-cetKcX?rTG zpCSFaIj@RIdh%ob9x-rU^&J63KmRYjosF7T;r&nbK0>+7s}jz;$`ekP54@jGQx5RC zUAF_~F$6_(UZv73J$OR4H` z={};$m{#o#-)QZZ+iXwfyb3s+S7o#}%&Tx#z4x1=>GwaSO~@y7`^(Te{H`FoVLgR$ zC#b#I6z#MhuVTzYds{8~=D5lCKXb`?u)hB})bk;{<1^qYuc)xykMktXi#SgrF887M zz9!#m3H%OMo((Ipk>_gl^>`@S~^^z(7|8~Xhg9mZ?axEQPTnb%OLEFt@C(qa4e zr+1|_Px>6j-{$mtoLLx2(fQ)9xDLag{?7_7-fz+2!2KsVbLxV!2JZ$!=H;gwK=A!P z=o7bnsrB%oa}F!*M7y!Rv?tTW5{9vx-+K~h6Xs>7&9Y*OyHf(5x9fa9Z$B$Js@azN zt}kCcAn%rz%;)W~vWvVk7N7t4jHb)pb;l0(bH{tn=x3jMR@2Y_e*657FQ2j#ba&33 z`OxX7wKvWB%P+ihA+I>@-W{(!ziY?V;d6H5Ip;pXKSOYK2^`>82(6a{f3DylT`hEP z7FxvL(Eg|9Pt4!5>!Rcrn}I*;@kg3gpBvx#=f54lQ)Jk=>$Bf$`uIsVG(GUyDNQ0n z(~F;c?4b{y+1%`Qy|hDU?C2lgyz}ilKeKc1%$Y4BL(91HZkzwP-+W>I>YcZL({Wyy z_0iqCAC5n|W8Rrxh>N^iXT7$0{3Bl*R=(i8_>Xq~>BqP48g~5H1$F`|9e8L>Eb*7Z^y~*6+0@9zFXRc+Vg`<760!n>v{Xj zE&k8jvGz+}e&;h{-1MHe>v8xHd*1Gt&)e19d}yAxbADAWV|D3V$h6PB(PO3O?ZxrD zo##UG97vuE8E;U}!FgV!?XRuN7Iv@S*xlRNhaXbAD(|Bq^^(CRADE8m^M^K1`n9(o zzg>r~OjhaGF={_McDM=y2}yx0%_3hI7a9eNa_~I6ggzH8h#>I@N4$>lT5er0;`6K_ z;t`H`4^C8gq(?maTu8zZFRt-Ok9hXEkc3O&9jVR-Bt7EIsp|Hklryg3h__OoS4Vop z+ftp0M>yhjXnT?#@#a)zo_|O<;;pPydLcdH$qu^bYLt_3#M@83XuiaoQ2tNfcR)^u|>UKuDls^cc@Z|4m zeA1=-f%Vt$#P7KEAKq^+k}l<^|JwQ^Jn>!DU(=<(1<9X+XZ{As#jLFD&bpHZ8xTm_%;o<({RUGbj4gZ*99b zs`Hv-AJ2Ynb0(E^3jNVG@j=qzK3w7>`O!;L9@?0nZo+d^yQtyeiw}}^qn&8C_(zr9 zXwL&BIi54Ciz{NLGsD8{b_ks_r0qnzvHjAXnOb}OQ-#^>(0R^GJJD{m4BH*g8IoTa zdqkDh@5Z@9=`=B-g{5WB^PsH#OYJ82~lHdG@2mupdcm^`mEF*9BVP>O6)o z=;zfs9QeFi&N;qNP9?@Y@CxexsZaUYO5g9p6}9wy%b)r{e#MqUyU|Xx8~uRxEH=4= z>=sVWL+6}h+KF~!{b^5?M*B*ibDZ4rxrMLoT=y8t=1W_;M7JlNb8?|hxKtbB-@8`)CDjU}s)Ic4K=(|5Yw+&c1AFcG`w7)->811`JpCyZGKf zt2?X6J}SuyUo3@o-Ey6C%<+%oVSJJr2RTl1--P=pLHi-2#P??Fz11&?5O|(^yzK2l z9zP#+ck1`IbQt@z9uGg`H-6>TF8fu|Vf%D%^Py*-Pqgd&MT$eX@$fj^&eV7aT)e-f zL*Mc6csZL0_rS|fH-Iwr>G82p6}9yDgZ|Vlq#{hxZnP8ambhKnjrLTc7ytQ0up921 zL}0W7`zmt|m)t|=d?MP3c8lxwOM9yN%Xvh*x*kjGLekEIk0O1i+&rJ?-CAFFXh4pK zNaKBKmg`~$-guaKuG9n0^<{TD8-4U2)ZWl{;YJ(vw;PBZI{Oc_6YWN2(wj9#NL;5&G#kM?`RLr`;s!n@-Cc^TzX$pE29)RrS~O^<9$i=bJ%a^eggLw z_`V_c9r%6(-}B+VJNFq(m&W}3%hIW0zr9`_Ak3-i3jB^f_C>vZWO%1a$G7|J2bfF( z;#{Z3G#$>H-Ou^ZYsUAXxc0PoLI1Zi(_UuqAMT@g>;BcimHh>VzWWPvswRmxqUwnmvRKHcUb;}ZU;;=R8L3_q5TDI zC)$nem-bZkp1n|hRqavgAeo#k=T?t<4Cy=N#{L4XOUqUG6&0k;m13IZx|o5tzhLaQ z%lps|ILpd$6C@o5*{xrGmk{kO$Zm=n_7~LUPd&}2SYvE&v=i+{dD-q9?>l-&`Zjz& z?=1Si>%RKvJ9k{M$ob>#JN`P>S=251g3hq-L%VE{G%oH7A)PK)=Ttb(-_FJT`%533 zH{zMG&Da-`FP`v6RNFsnS$X%c<_LBJ;uhf(WH(R*3@)Mz1w72KN z$Se<|V-o{*^4=TO-)(SSMHtSv_`WIUSA2fM=Q)H&IMQn#YTvU;`Y61U54rsMGPhps zOQphl^f>xupLvkUBp}ZI!qs%xUnK7Jp|`6-`U_lpT2#2uN~_$UzpSS>uiCXhaAjV_ z(05){BhPGb54`+zL#PZs%+#TxmY!GnQ@4FXe{8||9iZimi%n0BJwSbw%VmF}1Jg$v<(b~^oK&5m>P4yXTf);Q+@ z4fs{?h-czw5Gy508YpR?q=AwKN*X9>prnD421*(zX`rNm zk_Ji|C~2UifszJF8YpSt4Wt3=E|&HS-#`X0Rkfsnk_Ji|C~2UifszJF8YpR?q=AwK zN*X9>prnD421*(zX`rNm0yPkAKQZ$+h*ED));Ie~!QbiN?{e^WI%0e8@y^rW?|0b9 zt;-g*U)i>)tIsZom^R>YpEgCE*FX1g6?QN@QlI00gg%$@I28udEc1S+ zgZ$k~fBX1*x1^!_Up<#a=}d{?{nSPYDF=T)VWpN!imYp#mnI(Jh!@v*q(|g)sx$Ej zN4y6$9_bO!alY-njZA)oBi??EM|#9Nr#cglaKvkvs`N{G#G6tHIm=RdAsq2y8jtkS zc;J`DBOLKIYdq3R<9%1YA4}sAj(FQO9_bP9oNC~u?MFD`?bUdsM?Cpr&hrE6lW@dy zwf~VG@upO&H_nMiIO5TNNsoAr^ARsfJi-x=aHPldluFK~I=1}@PyFo~pL82E-yr^kC;ncIPrB2z|G8GXp#2F? zd{>Vzq)WMj@Ci@+xf-8zDfip}{)8uf2gf%Z(tm^SQ}EJznFCphuIB59@16Us$5DPS znb2w4iTrIpqF)jYzp8YOoV_|;G9H&680S7WR(v&e4u$)+6;GSvuKUbA-n=9Jz-PVR z;c~yCzK@H4&FA|@p_y3w3vb-+_@D~Omvx3cw@HF@_#IH<0Y7>Z7ApKbEh>yH%1o2G ze|Us9@;Z(>=kni#7S6fUq3`#SjqrajlxF2?$>dbY-YykpsGyD?2po_@zo(zm?+MQK;H7spOaJu8l;2lMxo(=rOI4T{w(o1`@3v1+ z;j~ue@B4LVvZm$iKEnO)7 zqP)0fO7t%2cj15kotd3V9hZs)N2ovQi~4i7>G`PkLz_an{%srjI@`KB+j{ZCwV|&< z@mqxJDJ_xNX2^H4*56r1uG)Q48DT~*>W})O{$ig|`lDVA7G5-6|MlH!yzkl+r4bj3 z-`ci*V^{mS#)~}ejSHf2SG5_Z^=H-%`jes0yRe^<>!SXsFX}IThtePQYST&Azqg}p zT_o4@NEM3Tnitp1TDH2SM@42$OXMNj- zwNZ{FDinWdUR*P4@lp=`otO#ko1*@xFY3?zobLa$AKJ)9{kwbnR&DI*UfbRqp$jY& zf2nY7TpE>YW-Xq~p}&(d!F^NIAN583#Xhh5KkbJ$a=QM#ebN=##W~Z3;$PI>*S4;$ zuPrB|EC3JW(BH{fa9KqCQD4+w{0qAO(|$;JhUxluZ|d8)DdOzeaSFx1BrmR%UWMga zf45|2r&6b+V!;vWkNTqi65DnEr~S~Tkgorlwzo&=_%xwH@fYO9HM17KeKq^|F+CI9 zH%0wXU(}!bMcw~tKeUloT(WG*A}l}j>TT6E-926FY^dOLq4>d+!yQ5v@Syb@ z_s6nPr2eQc>MwS;?*FtO+B8!2-`LZBb!YoEc6qLJq4-PlW1AsXgVtX>BR8GEUQn)! z`lG(6zxX}6|I>bG(@E7oqTi8D!T8bKooqA0n`QnC|9e|TZaQ&zIvN?zL;XHenfTL;9x+#cxq;8!w5*Hba;zwEoQYYp&mp$bj}w zP=C}H_2+&`_kY?CZR}M2+c#X@+0(s2_7M73b#-3V(HCwlQYil7G~31{;n`+X+ES?8%cRWtWf;(HQT(ECo()P^B1&VbN|Zp zza#BR`=qHq>Wlh|e_8i`+7E5iRQ=^~K}YoMzqJ1H#6K@Twi(r!rS&J<)4tbHzPXv1 z)F1Ul{Uz?#{h#*3Of18H)_3+so;(Y&l>YO?zbHSp8P%Ai^=D?n|G58hv{?`kr~arf z>d$>Z_kY?CZS++A*KX?V>t2rq@Wv<`sDQD4+w z;y-o&r~S~xLENnWy3Q-F+|(Ow5nL$#qC9xob+}6FugNaZX?bTM=SBTdU(}!bb>07I zKeP#%_3z!-zIIbrw5R8V;%!{h-cJ4 zkG=Fl@dso7{$nzJp#3hjE2q=uTrdE{dw+PEya+&jQGfAo>i$prp^b0WzptllLvO?_ z)I#x>dpz6Z;q%&SZP~)6Ptf`^{m;}H-Wkeu(f_C~>M!w-?*FtO+H}nNukY;X>5lU7 zO^6kYp9fF74x_dH%zShHH^D3jh*N*m7xm}v(EXqGLmS)N(HOq`-Te)g*KSD*b;Q-6oqmG((ff7BQC7u%`(KkbJ$YDWFhfY;d&+0%vM zx8}jqmgwg``U4E-&kZ($Pn!CpzNo+WF5UlWKeSQJ`gd={6ZB|4SSbD_dGNIB@Ylbq z@ner&pwr&_v;k$zrv9ie>MyZd_kY?C3D3~1Kc1j>N8Euf6d&VnRGyTb;m@`Hm}W@- z^LxLTFbt_b>WlhwAJzSz_CvxmH0z(w61Y(O!C3!3oc7w_{#7y{G!&kx^we<%V!Kc)6*{bBwL{iWy8>Q^ny_hbF=kw^VeU({dX z+q(bLe(3%*yZ)j+2C?-Q6@M_Ef4o`i4|?D`&uadn^yl-sJwP)q>W})O{@jG_|Fj>* zf0kW;Yr49-*Jn`9p0M>76@NiQetyy{%Ar4T$($GUM}1L$vB!1)r~T0Vd3OD+Yv0({ zv8wyZRabVlhk1@}>n|$)BJgYMzOwP$aQyrfadHm*aUAx;WghiMeNlh$@9O@Ke)x;* z`s+=t2UhilSr6Fyi;6!O>yL3c^f!!3$`Vq4)ED)actZDo+7I1d<Hd#?_?PVZ>uBqYw1Z{q zFDia4B0sCzUh)AS{h^@JJE6jnuL5&l)F1Ul{U!G3{!ja1{MGFGQ)}SxdkD7vqT;Ld zZ+L!wn)q}M{i)wH_QOo)QGe7I_2)jV`#+j0Wt|-%IWBsf29~J-Hi2VE%(3L}f_|4>87xhPd zQGf9t>i&;@I4MYfYrDI;dsfN!3vE}m<1MUQ994PR|MJ6M2(FD6MdX?(+wvUx8T3W_~j=!F^NEUqVXw-Eoa< z+F;*i+A{V1v5lnay`<$$^6~0rOZ3O9U2WI4_w=r^VZ)@u>=A{b69mKhu|BL{>{3-f z)|>cN{bWn7y|=wDzq*0ufvO+t!}@jT@5fnh)-SbhEyk1gm3q6n^RJvNf0GBQeyk7c z$NI3|OkZ|B+_FpF&Rg~N_G@K>a}~_T(r6*-2MmVMu){&aKUWGUzw@sZg+izN-k(%9 zb?P^kR;|eE&2_$BKz^i8e&j_&U4I&54hx_&fWA4%^T`54l_miuL0Fg;+S z|4-S=@!DK-<~b)XJ2T30YeX~fdNMO39S5I(5o6r#V>d-@n4IsUu9uxpRG4;)?en)= z;{C69^R4*%RG8+bVjb^RZQ?G{k(YqyTFmz@qjdO7TdcdV}S!Bf<#9KM5(v#C3?eoCgA!(Yte#K=-W?TR5i=NJ@5+ZC(-87 zPrgGMlwW&O)GAYbBhuzRTF1})?KV(?*EffDqn&6sN=19B^z#dy=9ON*=%r!D<~Moi zMPieSBr2WqYzpD7IJNCQR;-c0+r+)(6j~ zhP2t;Bj;y7m$+FE&f_@F(Z2SxF8BSv@#Z%8DlR7mz^`sQein-Rs+D zT-DvRuC1r9ea70Z?hWnhBuKHFv8KyALg|#Yb?dq}%xK^29mvufX3d?QbE&kXfszIe z1r4l`Gqu(_UO#atRHsyjk_Ji|IDi_E>CaBgfpC#+zeI*RuFQeVS(z%uTnT4j^UQ0W zfgNvDzbWA!p+kL!HqQbNI@23eC(DHSCJ9FiyBc{QIHj`R;|z)1^_gd2$It$>T0816 z_Mkok`(eK`s~zV-efIU{8?&E*O*&jdxZm)hx9e?-Ag|->Y4L*o_hhEM%-}z$CoXS~ zY+FKbWsi)Z@7ixl_&SkKCy*z2KFOqo=87wq|+sc zeMcv`_G8-r>0Rd9Pga1k_DeWddUqyWq*9G+V3Ei79?3^LurE-A)wnD~+Po4kYP)^+ z-Cp}}b!fum6W{)y>g;m&Qf0qfG`8!#=O8$)*=Z-*Ew0-y?HPpsdgTYTH?%d}L3UGp zXr;Wbls0NQCeL7gaqz6!(cWk$+Ku+0Jsqchx8q!&YH#oD`pxc1Uq2(&-YygeUM_c? zAhJERN*WjPa52qt!`-Z=5@q{qKvoE~M z+*t8<&z9aiX-(!l$(^U>Nr|86cN}-BanarFGp`Y$_kT#uk2ZfG$2>{XVf*)`*Z-k~ zN)LNlR9L`og*fOtWS*1?)p!fKYOl3Mo+aX!JnUcdP@IGMZR#`VSt6*R?`B)Esc-*| z9&fMtx%$lz9V)K*|K0zmHQqcaaeuh>zc)nt-zJryooF{o7}NIj(*5^}u0LeVi(t35 zM^W|%xnGlejNG@%jf=nO!ZS#ie!(lX|0uiNBF`mYH<{_nJgKqb=iY6@2B&E^v;)6q z8=y#=feO;*mH4){+rMbL>ClA9C!SdPF0bF-{~Kk$Tr^&pmAl_w3cKlc{1YLFdc~hr z?Kz0PDE6Sb5_jF8#ycIx%JklRFFxAe)U`3>yPCNx&DjMY&s&x_@d|I8KtGK>oqXqr zlA&in<6f%e0UVysFg;swRj{{Fi!}+C8<5B&-vKeY(j|0d%7%tk4 zcB0*4-&S@@XnPi0U4q8%aB|`|qYmzicB7qWH`a>w^wPT)E^_)We2ird(oXlINaGBm zMy-rLrT{V{wb{#?1iJb9VdxF2F%R&@?>`CUKV_2fQbBg(`2D!L*%x;VcEj%ybDaOFh>P_ZFRtI&U(&uD z9|%|Mp}ST61~K|b{P(VZTYYvexuKtp0z~}9q8#Xl>&;yF`IehD01e22=h)nj<^F8^ zIJG~^eOo@i=5KOypO(+FO?mJKXh^lv$@v-W52+v2UqU55Xq`s3IS zeda;JB%$|Z6E~>+*@j!yz6tBa{=(IC*k2^}_|WU`T%_dQ)8d7NJjY&rlhBfNIl}?# za_rCM{~R0oJeid=lpXXOo1oL4(EEH(>F1$3G)UwX-M(BszwTHb?s@3mdAaxbwu#WR z6YUm%TG@^ERKgx1^Qccgnil+sg~_Jl9z*&=a^J2CUeN9FHl61FEYn-rUUj(+)cQ`w z*7W-BcIT7jxHAF0RMIC;XMxWtQYW+n`vSExC;hDlsnc$;?`pfPOnTpTxD0=)zm?yo z!^6~X)h2Xk^Rd(0zpVDt_kS_mzPdXn`@6(wZ#a|J)pCuHIO-64KKuUc<>xP6G28o5 zq`vO%uHIGNxxDyoJzL;_bfNg(nYVZ>pg`u5)ZCzvgDcP=C}H^_S4? zgL<{`?D}`eSFGJV*VM24>>+^p2Tre@x?yF#oln9k8F=D*ToFf_?WT( z`K*j)xCvr}0psIBaZbz=gT_ZtD|4`ZRTrtVPwjYIkB@C{@}3jBudY$^$e*izGyaF{ zMl}A4dTzP>Kh*fR^-(pyWS9XgiC4mok6JFauh@&(o}sw)kFpi<~(O{oUkS>gbcd0t$6D^+$bCe~Fir{-{@*%7FUoL-%B!w)X8IDpn}I zKC@4zX`$HJ;(O_My=u_kZ-m_uiTD4X983GYRKN z6^ftIUk*Omim#LT8RjZoGUzi@@bZyz$cy@;zNkOCbPvmiUcsmpY{Wm{0wsNa=`sUJm`OZCl^o z)3&Osd+pn8T)*@H{YAuI=&@^D5|W*t2oCjkv6Mo82-OvBecMCP?akGi?TkYzNkOW@7a#s%Y*cH zRZn|cZ~Ll^^6N{TU1{NR#M1i955Hx@hHeg3U5!h;L)&t&98lb>`@dtP1o}g$E;V!E zT8O!RA=@wYMg0+$dS#kny8gQV+8_;zg(1R1@z3vT54#l@C{2rAf28%63CeLeGdD8@ z{bAkBbvD=CgyMRf>0$|ko{7XfOZ%i_O@-$hds-s`__24sLage0s?a z#BTq_Sxq-~{cgvVca?c`K0R?p(%3?ZGblpEDjBvFYrlzg;@rEC0@0zPaPu za~JQp{hf8YgkBTyZxx(2ffI85Hwmq!f@tH=o}0t1G7O6d87| zJK<|PF5c0!xSlUjvluA7k|Hf*Vo-gnm_W1x9$4Tm49v?Te+Fv=H9`{*~${>#VbCHg+#m3Msc;T@~r{?xphNw4k% zo!^Z6-+5-hj0G=FXuh|#P)d)-*5+L%ya*|?Hip{pPl!+cYfeuq4DsV zp8wj>bMFiDzWl)#n}miZGx)sGuHQfS*nGRrVe+1O@=3vi4GX#Yn~vzaqG`eOhj;wv z(#bm>`N-WnUj4(QW>P$7d&N21zx9=etB-ndN9~`lfuDQNfmx0)TmNLjp&jp$j+)Ph zeN@Y1PfncAxfZ@SDa$yWY#_H%&KpMq-nl8OW=?P4-1Ek% zbY@MZ-7QJonMJ7-`b)Xd{?JaB%;U{GK(1~&)lqF2u;!)k6s(u8>3MKo9Q_>L!{K{3 zd=H0I_wLl^#5@n*#h{N-=Sf`W^_}Q@2b?D{UwtR(UU#>8 zj`Cyu9?o+<@68CkhWD!XZ({Gu{+*<&>2Thhc*cib|6JYY?P(FxqNW1QlUNPhguI8N zLv1sVRo_YC{pS4N!@*BKsy-VO(-+^v;kbjed8N*ic$5C#Zkc+o!-UBv?p~(){n(e@ z>^%p|MPu#{m3@QYxZa_iXt(&Y`aK+N&merqdF+wkvgjr0J!W&n9!N)AI_rgYaJ3JF zdQVn7!tF67~2n&rsLexAgX z$~6G;yRtO6Y<2@h(@r*rp*l}uyY3%5R%XtV{i=q%ts%ZkUM&@g>v5CkNpSoOqFXvo z!e29d_r&xKdBR;$z9kbL{oJXNMLpwP6%&W{$i#96tngZ8GrvI)V|&#;e3isO$K9yU zWN!G`*RUUDU#-i|lZ{Y$u5gtcH#q&&Q)k)$A=9SJVNkXnspfs#k5=JchR3ON?0EIQ zcI-qIW}_MY=~P&uwBVJ77ksk44!!q#*N4(7%kMz=bnffFg@#w## zN4&*lnfwSxJi?J4(~HZ}{H96fUM&~CJ%NGTW0b!Tp7yux0=)vC@Wh|1@ky6>wta|C zc;a_xeA1=g+IAv-3SP^x- z?T_u-=1+Ly@6~vuOT7i*6Q1~_Pr8)b4--q}gA|H5#<{tLc?T2VI1yMd5-`RN7_)aUz!7xZagpokuy zMJjDW6@Qv`qn&6s`T^}(Y&8k8TR1rno%2a(C)$nmr#-!N{~gl47CgqXe$>_WrVS@Oo<{ptN|i?Yd<e!2VJp8ra_+@hOkF#GT9kx&RrTpmm za~b?xKi!1qk{%Dk;}jPp?M6G%Zi!ziyV0HpO6t(hBeLv|=9V?{c|>mb;)9^wXeZh& zuG=r|S!|LQ*4{FoM-*G#!pK9r(N44*acR%=^N1R6-ZC&vjA&t&=Mhe}F}EMHicw{yk z0>Ky(2Asekz<4~~!s1zN%Ycm;8!$1@Zb>bv8A;uAYaNymgajoaU`Pmtund?OLjbcF zFq=JIAOag0vjnreg!fNEoV>imc_G0Np#R@@PJMlE-@dJ7Gy}4I?@w2?+^SP`}~ z)v2o6;&XaELh^`dxG%lqjPfJi6221Nnt#08AB4kXe~}$T_9WSbRL;3LTuJ-GU*ZM0 zE_gH!8=c-g)BFyL(#h=^xx@FB1h?;>eSBT`*F(cc(j%fHJ<{`kGU#pV+U4~)xZT_X z4`>amk{#UBy8VIKA1p8I4{|f@52tq@4{o&X&~O@^^hlDQ*R?hZ%jneN-xJBE-r)t- zP_*PGIZ1A62a=~%dG2F7qiqknIj;UO&F~u#xj#I3VUSOp&V8?6{JG6z@4JqA%)YaU z^RD}&7j?e@cbZc69D=DRH{REWevr=T2zh*=d2`XQe3dvS2?#+T$KIZ18{q5n#r z{)(pnpNu8sc7liJ-gwg$NN`Qy!({aS?BR4t5dd%`>n(58Gr+tB3JH=N;K|u(Bt}zMyKz?x6i9>HVa# zJ19NfU0tz#f#=`P>gsG?nEwyochYqK%jc7QKhCfRBn#=_7jB*pbcCC;>HP@v8(s%} z46%J;iLgEE{EzR+%lBcv8?<0wm$Zn&jPnb;8X|DSl=RR8B3nFJx11FK?~I zNN$pol@cKtL>RCj&&H1_re;<+jqWwVja>+^aRE71tNyj{nFongc;({fx)1&3j!E$E zoZX^BK68y+<)(ix^kAqD)7^=53bQ}xJSrul-#hW&3ys74PAhFEdY=7zp4m&qNjY=Ib6Gt|kWcjDd_4ab^Hb@R@3_!zMNj#n=pz_Cr?<6#E5F*M=qkU` zEB_fGzv^*DC_mkvlwawUelbI}$5+VoJU zztSuJ86m&us{WnwE4}iM2u}o8^`A-UHMPHHdiDR1PjvO(o$@#FOUN(%AiAn|R(?AV z6L6305(^zmO86jTj=n8v08-AXLnZsC3Oqex?>-kT&ODs%=?{obm*d>0k zI=e(qpriTi!cPy49^dDeab@vl`Q@e&{_=CKm6YrU`_V`wC&_L8XD&C%^OsfM{5;I^ zI4!wJPLf++=)aQZ@~HjtQmwAP-E+>vNKTTQBp`X}{L6En`>h-N*|Bh|JL0f%tjx{N z!>o*7O3nUuDfFbq&2!+#U!?I<>C$fYuE%t$dtdn2@XF+2e04q#BR;pChY2_)rs2Mh z=V4NKYyOGhJdEs50avAQqi|fZoSiU4JqHn2W9I)57#MdQRp@vgX8tKYuaMhwQkbWm z;ck-da{SW&q9Z-BusMs~-(o$1*TL=P%q{FPS78ry{LlB~W$QrEoyd!Ipr#w|S%|-f zy6dRp!Ht!!1Bst6@#0$kJGFQnD4TjqmNglW+$1N-ZGMZ(P4e{0Z-m_LjMsr8gy(*E z6Q60HDgBp*b(?oFfJ^H@3g6n=$vly*yW+bJ=qT}i#r@G&x_B!|>@GHLvz))j&3=%+ zoErC;`n5kgD?$1VMia{Fnk+_pXFX1-NYZnrNXw_Vws!g_hT z+|KIG=4>aqy%*bt*x&f$N!fCvoZ{ch%%;vaTHBZ8COJuNJ*T_eBu{?}?&}^mIe8dz zJWx)ATi;dvE7{+;E_D1zRO*ZxR^4406uZVOvo zZj$F(tM_7ZqwEGkKSToByQFrm~EH{ow_Z&O2 zW-^qmO!Dn;e%Lm)%}h@;+{3 zN#cO=_W9N8GxL+!sK znVd_v)?n%1k!(#*tCvr{`FnTy??|S(lZVBD{QTS+J3klf39>Krgnjy2Kiw4U3nRg< zAUlKX4w5TfVm#RwxNbVQuCsk%;XI!|%I(|f;htw@*aMPX{ePM9j@!-Zv>ong{ z(oXXow_eh~?W3~N-`!lvO>&al=KDfFPWC&K+mmjJ-;r|ja8hm`j1#o@B)1OFGv)Rn z$n8TdR6MXcxzVtA&ybtsB)RoH!{sJI1I{ zdeXy^+s6UZZ^q5_Y{}(g2=kKM7*0O7hhg2+=L~&2o?($u_pwnoezNH1?p7;~Kji;T za@&t*lvyXaIYk)!!qvG1rC6J;uZmO;1sc`JhH85}9hda3OlTN-9gB-K;UUIG% zzPB3PH#)KVLw2`Q>?F7Ll&vxG>fAj;PYR#j{nt$wAGv(pv^%<|tea{cqj`5%2|7#vf6Z$5kH$hfL`{6H6wG=RU9Ly2s3GG>_4|M(?coj?S~`y9Aog==%an ze-`=__0)3^!E++~-RY-xbw20T_tNnFso*YL67W#LUC~{xt9B?l(jW6L%A$8e&>shV z9y9lj*6{ULt-Ie1BarZ1-!x7BRw>0E0s zEV)Tel3QPB2a@Mnt9d86Ew7&UoZp?6oFq5BzvSs*_Ko;=r>(Dqb8fxpOHU2`slz$8 z=<$m`PtQkT_kU{FS>3(3Rh!uDBscXp>aRCvuW{PU|~~`Yxirlh`xqPd>8WNmM=bJ-NF=y*kyO?QbI< zDxLCeI?>-l^psz7F6C1?<-0iK6FudNqVKPxU-^_y`Q}4D(Nn&C8(Q-zo$_59@`;}E z?K-M8pVBGc9U-6S#rfQ)dMcgr9T(&)ddi2Ho;4ZeQ#$4A3;9G(`F0)EnosGJZzkju zJ>~mZcWXYSQ@*Q0KG9RYT}K_)A_t{YzS~1S(Nn&C8(Q-zo$@K2=qdcO?pO|+5ZM#@ zg}(cBMhGjtWq(y;U9`cE<+IyRvLVl%J`3oVx z=xz$_s#AWYSN^*~e$mx?9~a~xzNr36ul$=H?fOM@_1>NGE4}hx9P*2<>fP3E!uu<| z^3R9-qN{Q{~fI^7(kuT}A;0Ku3hnRq4*XMk<-a}T7hUnMQ+}ma{+=+t ziLT_{DZkPyzs5(=Rr@=xL;aOr`PDx}SN&J*NY5!hJ8v1rVa>1mPHG*Gm2W1LFF8oA zq9=UH@WY@{eSUr1I_i`xOuJBb+#mKjPvWc zcVX_p-xCOK+kVLVg(sini|a@J~?dXhj-dN-e6yQDN8YW&l9*t0pzQ^Po!9?yjj z(&IDR5Wh?GD)9Py(BC|tbLbUXDo>Mq`Y0qJDRjh*lw^!3a0sdAf~{il?6Z2=s~KZ^Hv07TVA6-}S~@*e6=93%dC2f%y9a|2uqtVAsFj zcIK9ke&xDr-}xNNbM|z3>l>bN_L(O<@zd9lzEGUE+Y*fze(Qlxm0xc9`vaZylhVs&Keq1=1pK@5?+>Kkb>sH*_Xju*W?#xHH#^lz zV_gq*`u@OAG1jNA{QCoGj^yFP!-4$!18eL11F|Q`zOWGN3$i=NUZ1x6%MPDx6~yPf z_Ceu17!v}nol^H?>E8Jth2QTDZr_;CCwnRx_5h>0_&?24MMtR9JHPP59)Yz!+>{5`W;Hfwtk7O5jUif- zl?}SnTD{gb9*+4kZ8__3Keg*7lW|tR>iKUUE95RzeTM z+c!;HG-<%i94iieZI=8z8F%{9+N=yM9!{rP`(d`j{nQIV?vk5oE_r%5{af$k`QvZm zvtH2PnjZDVRh~G~nqGR1PtQZL;P_b4`djZ$?Rr0`zP~l0g|gZ-AE+N}fy}aOnrUk3 za7IyUGKzA$0Q^?}{Vr;W;F4EHQ(iL9T1qg#_gC{8?JTYu|a z4RUW{A0eu<4V|dfcM-m0NFp`adD?pSbtpemQQ1nU3N7 z$~oX1$o^GRowd^0DxJ0JoAN!&I)jzw;)T1P`@D^34VLY&Rr=uy!Ygf}m+MT`Z-u@4 z!M**rJ>0j&_ptSx(z>@ht4hISm-VP)9Q*f1JXJk(24*4Dt5ZhhQ#$3lE94VB;--gzFN~e7DA)n|e-@$dQ`IJuiluq;% zJ{TroHJEs z{4*iH=&IhG%2#^jze?>nxSIk$L?>Oo(kuV%A)n}~opdT+>6KsfMOXP%4?QRUXungx z(>y2L|I+-^d!*|_x?c-zgTG~U6gnx}81+OJw&?l6+k7u+aOY>kJm-}e^AZwY;^O=4 zM`5BP+|~E840;FF?Q(LO=S-x+ix;N%^m$Glc{$GsZst7aAoh{+Zojg4qf{;BC^EQC zEuQCOQ%_}DgF(qna+2J7Ug2_+JiY2K1TL=L7{3O>m(JbH@GZE%A0BkCchC1iSoS-G z^*lV5WAmKW-{}2*v~E3bC5hcma{Cc5B;`g~vmc}{rxyR#Yc}z>z>hL^ce)|2Wd;KlAL&k*DNp4EgNpAj?kbBS` z;2n0*KgVK?X5#Ye{%hnj~}&7=@)a^7kFj$AAhp^|BK1(KT%F6 zx&5fSwe)m&`8D{}reuG+`0<#ZhVk+aRA)IDd@-y%*z!tAYR9%b!FMpXGvaM;eXH#! zWj^{_^QW@p!JGp*2XYSN9LPD4b0FtH&Vif*IR|nM2NfmL&0W%k7V z+$Ws4!hXkNwfbV#C_rX2-ek$ zosW3e-+t20fd_YfE}W0}{fzSrDr6B?`?Wl5!}r45m)1E1n>5%)4ty%KeN7t<<#Qy5!uw+%Uf$PlAGitx%IpzoR0|Y z!t1>e=OaFNQ~VAHALzfC;p92+KML}BS4gPy5en;hOvik-&%X71#Qx}y*YQrJL~t>= zRnh+6gfv;_Bb*xd+55Hs5O#A7(%(TS04&U?+@wXuZ`}O?(PqF={uJ-SIKk@;s-}HXQx1VL-$3E0=dPfg09N;&@ z^iA*m;hWy4Eb&e6&wc2$ZS#e9U;n57^hMNrZtK-={`IW~fAWLtPP)dw>HUGJ_icUM z|GxOl6C9% zkdNpoUlfg+SNW7q`7RFmL{IrHN`03`>6C9iQC-R5+m0tP#LjES*AfGh9lwawUe6QPC zkY9AgcgZ(hf2CLck&s_>&j|eQl)sri*m*7w`9)X!>y%&VRsKTA-=rJLS3T18S9;~Y zE94j5%R_$gF`ZxOm4B1;ZJ;Cmb>hF$E5G!$=&Jpx9i+=wdga%1(N+0b`RzO8VO)@% ze*V&%^ zOI+*^`%#$a$S&0PhlfUQiQ*31<1F4RpSWA~mY;s@rIg$xC&{hnkAs~)w2NO>&3l>! z*|69Qr9G>YTQ}_VD@Aze8vJU*lAGitxh;hLD|!BEYuR4<>g0A0vY!VzNp9kl1J~rx(!tzNb!PUtvljPkhlKt&c zj&EVyJO_Si5RIRmUMBl1sGU;Zd*bz_Ch{=8j#}5fFAmX1zMlkFe7+t2WFE-kCe-Iw6b zzdqotKRq;jB>gWs(j$GBWzd_O-sSZ^xZT}5Q{m)WwfluDeZKn;`#F%|EZuF52a5unBhY#`H=AL?Xg!_YE zc--mH1vj1JR2KV6o~P&}H$J@r*(tLux%tcnf^E^ZPV?^cUviV2BsaZ-bU+$X&9(r2_ej)KCF7@;I zKu7(&?=4yMUJ1R->)>{GpPLGMY^VQxPhP%H^djWtKGEQ2?i0NTdqjD+Us=3SD)I9% z8RM!`i}6r4bx~TouH+^;Np3xF4R|QX)8F6))Xz7@Z^Q7_jhnp|NbO&p1+2l zm+%nh4!rc-L;CMH_Kz^+5yv8S{ZXL!(=Ogh6T6GajpdvW`azt-eNK(`r(`&tYVC(5 zH_1tITL}GE@?2|g(n)UYYrH$j?H;#(GssDDlbj?^w?DmlXB6!}fP0%iCmy05%CJon z_pm>)-|Y>0N_dE2^~cJCfA1;04G&$g^BkuaMM*pqMK9~l>T68fNp9@(i^(lbF&$Pt z*1F67#x*95oBYjy47Jqu8bKs&@oP0={>t~-?+8*0M*&|ir>^8r6$zF9EHH=s={e5r zF3X{DC~49%U_FSdgrkJ3L|b@E_(^z5>zuMTDgASylQ_Q?uA-gk3F{ZZRX_FjotOLe zfUDm4&cIhuUY;vGKOg8wU-Z2*16Lh5wG~HQ`unZnC5r4_?+FZ7Szh2OxtX}?3G0ss zH&%kHlKjlVRZcC&RoT>+c#$>mSaOq`B)6W+yd6lMR#pDaMbx+{t~P$>B7~E6rk|%9 zd&r>Qx#;lR>`eDXJ5Jh}e$jPH^otC7o%B5W0PlOt_$)a|ZVRFRN}jO4LSA=5POpXg z+MShuzx`(1yA}?dcQfwL?_!B&`PAQ9@Nj246ME+p*8jANcRRICa#MfnBsZr9!G`YNl^yThn zWyp-VJIRgD7Q5n|4;=_EmU#_5qugj`(s+4qu`^u|>`X@*A3p!t*jozzF3h;S=Yx>_ za$3yupZ)wwZFF$eEhSyn!szPoU~S-nr|X6#m}46*x#*1+*1!GRe65UO&Y9wLN+EaV zi?3ebx#^`Yw%0i%(tG&1#r9k)fX?liGh#N~u-|_7POg%Ozp9s>FIKOC*@4m0Y7UR8 zaJZX-Jmu<9TfIWMPW58?dU32eS{@%RjFl!vDywgMstet8KO?`kAzpIldbsKg?;&41 zcEhHNzTLCy9VV~Ofhg(robTaIgm>)TJ~$YSoOsenn>!_0D&Agk-BPKRer|ng{!bmJ z#^cP=8CQ~`t2?WvZUa<5{oXNO+9sNwTK%^*ou~J>uiW~qa1Je*X0?9Sf;8l&{R$_a zdznX)f9>tn%T!(04*yiIcK#$~XY4gTD?Q!($~lm8Am_mHIM5Z@j=!xZAo+UCn9~>W z$1`yc4GnGe^R2P2iN}s~E)U;$?Z>vZ>)ePsinMIpc{BpL;SFPv=YQkN{uSqGyYe~PVb)fKPj`Mzx_QaAMTeF|2_uc#LciNhch4b`+LIJwr%TuzkLPT1us3vh85w+ zfE$}Q@?yu4!k3;e{=Z}Qj?qegvTR zS}V5vkTsum%TM1arM+LzTXGy3<3X}Y?MCfMamjExZVvNT&K3{>GcJt*6Gc?#;gBxSI60^F%mO{Og1xmu-&>xUq>NM+1%w_%dCC9lLjq7H8Lv zJ#yjl97neH_hes8`xT~ClkF_Wk*)k_=g-p5(>)~*=N!m6kaM6D2U_frwtF<0a<%rz zHiB-${H5C?H9lwI$irce?Ahzz+e`B9-s^82)8jF#og>1L;$J5mxomr6z>Q5DITdhZ zz?bP7IF8(S;Y6)mtw)EnNz$56+=kL6EIVxW$oaSBII^|92rH^hk)QJGSvoGqk?lN6 z@+|Fd=O1J8E9XGYft&-&<3J0JY|FA*9LaNWsjH(sa{u)CHkK;A_D3eywCN8zaz*Rf z8lSUpWV|M+ajD%++auf7wP$i1DY>EgDQuWQG=@V1@|r~U2x z^E~y^(9ErzA?3|6p=PVo<*PHs4 zSG$|Wk!|bRbNM+N=biYM9;?LDI2C=uk$r)Gop9u`*R=y~Y~sjg1RNRgWozj?pR==8 zD%Mv6Bo8Z&?0M&r$B}6swBr6K%5h{nhZK|Vj}#|zH|Idkft&;4K*An*l%pj*p}V*} za;^pUCG*iw9BC-E^}&j9NFA6v^;LFz1i6eIv zYkNxL(MhMJA1ya*9jM*Lx7_T_boPD=@5*sxjBCjowHvi5#U;b(xO`nZ=3$zD;#<;f z=O1J8E9XGYft&-&;lO?FCExwg_?*U(1_GLlO6puj#^E%MOxq)S@^dy#F1C(g>G3L& z3|`?#@vjq(T(&(j;KnA7d~v{$0bjP3ZaA`5oaTA$HS+z)cOQ8i*^YTzWt7_^)2$>9 z=k~~$7r8IzK+b`j1I~f?ymqu6pYBgxOV}fWjVP5V1jD*^`r)s{9yuSz=PVoOA{KeDf$+aqH^Cvi#AAE%H{I8yxUgd>-I&L-f-CXO5lI5OZ%l`D70?z79I zqY538dsx@C`>t>td2GhlWe%x!sZ9449F+4f-S3j&w4aS6D=yV|@?g$^oC7%rGC9DN z=eZ;H{Ycn8;$>qm-*BX=@@RN;D^Bjp&e-;WIAa~6(_?K9M+yxQF?9QnlX z{m6&m`;k4fIgWJRiGS&FNj!~H(I*@!{;e8E2He=hk+py$1HNo6o%TrL$S3bAm8x9a zJ`}6uVa1U>e|hBXk!c>ZVu*ZyWIKQ2n0pHjkKZ`=ZO~ZZ_anpjoP{IfwQXUYb~g)0wtR;?U)N4ZItxRJr-@|n3P&yk{;e8E z2He=hkuMK8GT_VB(hWzB7AGc3<4@jM8K0;X>k}&mlZO>YE?k-8$QajBT530HQ;JK5 z({cI!$e4#|{)ul%x1E2C$*-IPIR|nMERO>T9Qj*m)8SI*Y@%Sl%PxuQ+8f#*{Yr48 z#^)o2Bm44mHVH{5$Ec(~ktBG9BgMZ}g|h0*(y$GF^imyU(c=*U;}G&R><| z$QT1rT5LaZ(|(1M&%M3n-~DkuwDT_+FMm7#jOF>ioC7%rattzOEgMTH{mNjZ?@c94Y>-8b=1)*u;@{&iVHv z1HM$ba(C=LcYFvI$r6^f&0VoM@-Y9t_6Kqtnd8WGzgcaVt$i^XS?YRVb zab&=aO&s~tfFlFGRJn3@?0!~pwp3d=dK7`^@Z!jx59T;B#yn&)+mhV0U*Y6)Z+p2t z()rNNzhu1p?ff&A=l^mJ)HWds$99ik>%kL?2n92Rn$sd>p<~Yt(9hP zrZ>E_@Sz+><~TClZ&n*-8(WM<*7Ih&%sn{=at`DiSd;_n?=9bt4C8Yaj*J;ZUCXQ8 zb-OCDt3Pl1eq{c;h|Ze8pX_lfktBG9BYT$kUBqR7KQiFPCXReyz>xu8rfaZc_j$$o zo^$flLypxhprh%Y&YCH;vc!7Cgo{;k>`8E|70M;;e&WWbl{8URO@XQGjG^h&~q)gIaR z(Huv{n2};g_Q|wgVWo@PT8<-QUU*|phSUCb{&}AKf6jrN133ql!-4oa2Nfu%XH?q0I4 zt?~It;mG-ke1Bxz+S22c!f^`ugd@ejRpZEj8=E-ti2+9je5rEf?$~|)NPYXCQ;7gS&cgOAv%HsngPd=|WJYF7}Jrq1?%_nX{qP$|)+TQ#pa~v6C9`Z2R z7t?-)l`d{`xji!Gg}20HIPGuepXbT{=N!m6kaJ);95}K#GUNQg3UTB%q}Sd2<`1#u zioW}!@%c#M$ew(EWTLU9$0>yqNrG26Qv6#rjtsc5i6b|@+P@ze@MXFNJ9h7`OyKj8 z(V_ezq7yr;II{2B97pCjQvKy{xooJ5(aL)6jXVFJb0FtH&Vi*l;5gF0Lmox(k&}>d zwcjCsV0#5th$9U>;CkqJq68nY5fg13ny50^bM;KnA7>`zpjNDwo_s;6xTk(7=E%eR{m9ScI5NgOlos2P+_Yce^|r z86q4h{;k>`8E|70N1hUJWWbl{8W2ZLj#ejj)r!;UXUhy*2gsM}lR@lEB$0eU1 znqOte?NpECb1y%)M<(Ua&YxC!WIbnGe&rmTSC?K|DbMSlgZ z&0+ekKk_)Ty-t#kxAz=JwsS}^UES;2H~%E?G}Eme{z&LqkKbIo$g6R(%w;*bmdWi6b+{*A?Q(ZAh;>S;ETB*=T&u!jW+u zsbhJyyJ;MmKCeChvK&V`@5DdyFnz}<|?f-=v({4Logd@ejPB?Pe>)HV~HgV)oz>xu8 zrfUEkIoUr^D-KMgpDa6U6R~t7Uv};|p|!pFn~yw>jEj^nDW|oxF3b8Q`(oO!u+qiv zk=r9#56!Hm2 zihrxdkpVY0ab)yEJ4fFW@TJO?yJPo7rI}S6)&?EJk?~nAQC_iYII{1HM;=GEV_wOJ zZjK`tlU#0(T#P&Uvz!At2kxC5Xt783qM;zk*$ot1Z`IzUIzdtgJ&sjJ! z#sJi>yxQHRaOC`}^Y2GG@5I0KSS6muspu1q6#ottN1n4gih5vZ>)HDUw!h#E_}lGn ztz|QG&hDL+(b3YtM7c7)adZ3g<;E(K+v|C_45i@U!ny3*&&@viN!4Svx2DOHp;CR4b+Hu#)gf`MDJBp2$mYst^L>3uTBB0j!HT^IjFyL^t6Jjl4!(k6|?exjA7hQ5ndP49{)+tOL_g+>1`#wAD-J4{TIUbL4ML@D#jt-^~}#WQezzI zVPTK+yyBbmt@sw(JW<^vaA{m)`Imvd!rS0iyY*Igf|c&|ald2a6RcG_rglHi>POKD zws5QTe9pGdc*boAe$r5S`W4SOrKhWS@kx3@d^#R!7Q4!150ufx>6QANObPp?p8SFW-TF@{jL!G;Y4f{c!W0IM-qt=?&&#I))ifzwT}{3&mS~Y@!euPK59t&> z3x2*2FY_gJNPf0c`MF9WKkcnfdG>%8sf;ny!~<>F|^ zB+Gihvi^lk57+!2*Ov;*?R)4W-b0+a>8(Dlr|bCDTc5S{n%6({Y4`tT|5GE^Nn2Z8 zqZHyIxPDO+WnIVpwd3N}XW4VN)-;QUD9<@0(R;GZrr(7WBxCvy$92c`j??9erpi{m z^n9^;4a^RVmg=IgI`@v3xV+_xT3fwBx=!_C`oVI&Ito=bRvMoujFl!vDuW_-*j?!b zwa14;iLeem-}n9FhrPfSf^%D0{x-y^ow9$k{EfJIZM6T4>x!P!^_;Gr(cO#A@6-oL zHv+L_deU9#1>LiS_hX{yt&hgN5%)v!nHt`ZA@SW|zi6RJgoZm%~vBmiq*h`wW!zY}~tXUxfQY+!x?J4|gB#?YLQ&(?dN}ALPCD zQ0wuVc!#sPqFY}G+C|)hxJ$w9hkGe*K7)?8`mio%<0fxdKI_7p{CU_4`Ehbr^wt9C zyrjFUF^>DC;jY20hPY97*5wl1&%;eVkteK&%KshI;RNt;IlS1^6%Dc6I_??V zdvU)E_sfGj3zzYexS6Jcdk^m6uC508LY}Z5=i??n)JC3yI{elWb$A@gCOww95BIBa zzh+&0{~r8|o5TG|+^iStGJ%_X5l>hT@`G)KZRhE@$%Cii2EORnHeb};bt~!qKFXQL z{YSX}829Uf%kUrKel2dMVO>~H@<#Pw{mDPd<;A$ki)Z0xzabAO7yNc+TR%Ms@BS*} zBmF2>Xwtm6< zd{>t7X54SVefj#X##_UEd5HUSxPOM5b$bJD)r0(C{a=BbZG6vnABNCB zUW9U=gPUz(2X2<%i+3j2j#^2 zQ+8}SY!|Ep`-$qnzCj&EeZ@Y(w(uK}0r)UP+4B8Z?q7lahj3q=xIYa4hmY!ReDJ7s zjSt|y3ip+`uR#2M)Pr&%FW5HNE+`MxfqYTK%vr=Wbk2g_zTAI1GK z+#kn%4R4h5@eS)6AG_~SjgLOyzKyGKf9U>4H~#8=J&mh+?$h{7Jbw?;y%Y6F)5a+&Mfy6xIcyaTD_)1d-PF_Yma$Ac`nD4(+C zd$Zin;QlPi{v68w{QWjGKL2aSHm>`PM>ek8^ti@#Cq23G`BSzuKC}6x#wU9p-}u<0 zezWo6hd!k70q`WQgUf>Lfo+ogf_;;EjkB$i1^R24&=kJ4{W@j?E!SyhuNQ~BiJX%1NH~D z1ImE<;CE3z+ux(QqAedm9`c-J-;DdOd)7A=@b0(#_6d!zZQItkz3}43-@p9jjsJ7m zWsU#)mbWzi=?$-Me0T3`<5t8U03SZ}_)U$Ef(IYG?*{9mSA@RF_9z~(9k4HO++;ss z`O<-G_n+r`vuxa7M)|iq^udjhLYZe-SeIEO>Oy!;foRjef!Yc_qrHo+1y}C)p0zAE^)61~^{70Ob>3Q3sXRb-k74 ze*yQGHgq>`MfqPvySwd)y^Y%;>wm1*8~=s(ZlLVOXFlC%eDtG@f4}_l#`mVC8eiW1 zoW}K#!KWT^eB)y%|3fIBdW!m!I-9zMI)P&++W^N&mXEgA#3PS^49I_$e+$b0D#}0j zpa(R*@z~#Le5dc6#y^aWHvS)Y{);QGMA;u{{QLv&Z~WVv-qiTsOJCZ!1@$}dbyzVXQ8 z8{aGp#y`H|6^(EA7aL#RvAuD_;~v}i>_Z;Z z_#|`%`|$^HzYpb725bY#@~MZ=_nTvn%6~L?z<&2-E5Ez(^`qA}zKJ&YH)w<3J@e^} zzq|PPjei{7)A(Vn*7*O>{=au=p>f-}=QbA5H*b98BO0GO=6;P&q2APC?9W-{Q~vCK zuv4_*57y%u-CaXpL><10I(!|pzIosKG`@v)_&2|GLgV(+PiuVdoUedaS8-|p*c zeEsQ9Yux(eCpG>W`tZhMAJF(b^aXV$^>(^^%AY!b_yl8L3*Ytra$kzJ_$AQ!8tU*3 z)&X?Cg|_gmhd-?GtrH&I_~xd^HogvifA!IiYJ3?w_=|Y&8|}jNoAtOAW$9)gVI2Fb-krSXyAwYUOR(Qz z-f}CkU6S6R_{YJEx2*5J^+uG(_vKCh^|(KWTYM*8;(PC1*L5p##jhRWJ1 z)Q5F?19ZmgaK9FJ5##7KJo`=bTcU0^AD$M&6E2j6b{ z3eq%J5?-kdV*X?A_4Vm@yPGbnC&bH5KUetKJKRUR$PB!q`)jiI>~E#N6upHn`TkhZ zPulsC@3=7F=lU4;zrO#%roB@6RG*$uzUnjo(eHlJ*7L*rFIAuUTb8a5>oEVa6R#j$ z)iGTs)kozw>+?D1kLtNpefqxAxjwYZ^8)Lmkw5bN7^)BBlXYs=CzP-H^!)Jc_8zL| zQuXQiTIc#C%f2d6pJY8)FJ7vT%2$2nLpz)opE|Txr-F5WgG<-P zN9CxVOYvv^>r2;%^4gTDPr6R5&qq*k^$*za%m#j+|22DnY10?tx25A4?q2UJ8<&3N zJNyl5r+{dv&05Mk0M*(|_U0l{mM_K#j zvQ{W|JJX@8by3!SxvUk6-A;5QYmE)e(IV@M-u79^T6+CZW!=@G{jaoNccSC{TK$gw z+Wp?I6(94YGo6+7>rQmMU#q;OW!;(1%4FS%j%2NIcQILO%sfc4Bbn_@2HrC)QlzKuGYc%E0c*L^}1MhnqHt zu*YIosy?z!q)XVoQ+t(GXNSmo_jFmwaL-44F5VO7$z;D$@<*S|wcXAiL-Cm!+|Nf`3EFZW3Zusw*X|;*)d9qHd564IGJE^PH8u*Gc_nB+~aa{ha5lgpS5X z^#{HI+u&e%XlSxtDhyOcE465%BzdbnC*>W#b?iG78^(j4eK7n!!p7})KKfYW3*~+B zBb`Sv^~K~gmuMgRn|{iBC48a0?}^c$Q0Ly`B}KYbFm^6={0 zAwT6Uzd!T;o$4cbOW!bcySx+lH|UF`y#M~1r-gb%k^Gdm{Hc0aFl)Xu`iK8!dMAR- zN`A^){uG^#{re?<^4LIU|91H)Z~2!;=U^iJolQUGy%IW<_s9P7cY?mz*z{B0aeQ2( zPI6-Zu3t7D==3%Hly@AzG#$xX`kXm3<$eD4%`Y75ihl5b*EG%cOn2d~^S7VB{k*f@ z4G`TsUYePRdaJe4RMb0IoG3=UL*-f>QKLotpNVFpz0u5Ac^vcy$jU}S}i;+HKZFg?Q& z&he;HE!EuERxJ(|>YFb)`9)DLNR%f^V})922uBjf2TBFfiQ{RY8}+#Mq~-BJ91T#s zC-u1A+IX>0E#ilMo2AyH-ew_DwZ`~~GWH9jGM8*&O(rU};xLLHFHM&R!I*M?c&F1| z6iwg-C+ksppfX;-!&(Wo8emrPv`MPBGF7V8%7djsom0|*sJBup4>Ln=b+%9*M=ahj zVtA-LS~3Z8M*@_#qr_NU}2&>RzfYts>Xc0#)Nr>tCKinF)>*yF-VTU>8w1dTVyGWhOnW;WeyJQPq*!<zNr0~+o;%IqdHX6d$!I9cv0b9H%7s!<21b&5P(z|CJ?Snh%dj~5+sAaua ziH2qh*2&p4PJdBkDgo6SE!N@+qHjQI;0ijiXrr_^a*XY#TAsq>t6CX_@R_=Rh6%s1 zxh-}A0w|9aqcZSJE$ST@Emy%X`YS_2^^!H-Iv7%lilfz$VpOkyo26*5HVyPs8%Ga8 zL%!sc7qK}rasnqq7!!hnr7C(`2pGE|MJ7jQWb$!}bukErQAS|Jo zwaNMjx?$C;<3aBiR=>(nolIaeqKp2Hu0B3k8S~C#l0Av-*j}AkMx(0N&mC6K8gtc{ z5KWJi2Sy5&$qDHF2xEKHAMJ?-OG5?dZW4j2pc>pIM&Z+psVEYLP^_0cr7JU45Itc zMfZZvvXYgI9f6NrJsP>u$uX<%Aaoid_LRr>$PG~dHp3%~Lt_iHhik`Y#xio5TGck=W%O~}b;$UT(zP(VTRvJ8r8fP5G21{S$Xu?6!8cT2sBa;~E zIAM!h(B*n7fjFo9fBObOEU%VxmNDS&}%r%##M)f(5EGgenzt3Xr@xe%+vJUxOu%> zy1zU=G&;%Q0gZ~u`iu3l)p|2qwZ|DFt0h1cSD(#yO~myMRQd^E15bz=0!b?vFs6TU z6rBo#-vszEj-~^pS})>(4{+9KhD|E`2MC3RWO>Ji3w;s(;k?%5c(KM|6QRn$1TZZ$ zB)qOr;TtXjyHH&hienWh;b^?h9_7`Z8ZS(5j`;s%`@hBhpThrRr5WI8C@f?hqlb-~ zDB2j1;8z^!jI!tEJvqW~V4AOPi3R{c4ZPE1*xh3YsVoK_2-4p(Dh_eLpQ)9qC194> z-~yqcfx0RITr5E=*K9BvF9J*e)j+G5xM^Bo0AQvv5Y!Q!c$fs-@`xEaCM;|H-WD0> zLGYe{!~^ysrtRt@gVTh3gVV$Izu*3I@WKFB8=f_$L%CqYF{}iSksyZI%zwaWl@dA& zL=U;UAHXa40EaLPS_0A}77Hy2*tCct7QKpfDh*oDd=y##4A02%3vC z)_R%|s9t59Qw60MLb7IHz*;B!0SXBjL%`lpPwE(bjWwQd0%OD;Q!mux1$Gqh*7NuJ zu*Rt%@V22r%mwO%Y5>!fJ*7fzxWC9cqs@&r17qcxra#QxJT;mzFr1}zFH?r*0bM*; z=Fv$T5HSx8!KTvVC@@|a#=Ohq(@bDJk)vW9L%_wDz}Y}jp=Jg}#2~X70}Mf>pm4Q3 z(^{yB9s=XL!`V!-~qNuXJbvH@};!T!W@%wMgQG1)}brVv|ja9V|m0^cxWnSxR#D)X3uQ8X8iz=&WT z)59mTZ(7#E#7M1#S=T7%xjXA9REt z&h8zqVvbCqBMqjsRZKMc`y=#C;%`hBf!y#9^Rt=iUXE|PPmTZ)LBi$)6FyxtU=sC~ z24O|uR4(eZ6zqvGQdB0u3{!oq)^r)~#9kOME4}`nZU(UlWN&|Q0@%o5?o^2y&bcyF zjHXLAq>OBiMox}~Hb+DFzXg*lbhZ-4G9o#k*$DE%glm$M9lR}0`wpFo+4=xq47D!+ zoma;v>XY_=3YDo>Mkn2w!qfqPdD5LJn>$Y0f0%9dj6>s126rl&z^DbZ4u)c0Z2&+a zO!mNP9LFghB*mf3>nV0Z^Kl5qy+5hDta1RbU-sXN>jn)=!i>h29v2- z+|1<_!ps}fH`pcM;rNGtg=sR7upj@}u!f*5F?${>kIp*ynX;H(bHG4z%fao3XQH1? zufKxX$ACGc1@u$lY~Nh41?p`x*#r0F;BMv|z-I&=-wdNiolM1L?*~p5F?*w#tXP98 z2~J@evt!nuxCpib)q3mb}3b zvJB8gu*pEF1{g+JL}2jsAU!de$qQ|}ns+C-K1$H^%uiZGd@%CLz~mE9fB#>voV3eXa`5}@*w zs5EG1xdF6^IxJT~MR6{N4&FOB&6XG<2P)KLmHHH!BdF40AIpyMi=KH^UkC{q}RLDl@u15JncLU_Xg%=l<7 z?S*9)!!49Z8B;fMJD@9!7gZQrY|1`4Wj2p^o}-8f47tdl?&;mj%rvUK6(MF4%)0W^cB%5fEY2;MgO2LNN=<}7JQN?2l<3}*@6HU!rU0v z1lW|y%b_?8rH>O;~e8euGhvHHdo*U zs#a)1|Li2&v^RPmQfTmJW2wNnxcMaXCzxB~DB7Fw(CkidF;J1+F!IrX-KPZmBpNxZ z4NC^zoR+-VF#xLCY{A&;91AtZn{$d}3~+hC3-;O6Ef|_e5Y((CpddDg*(eNnn8Vg= z{ABZ?4Fsgjl3*`M!D1{ND)}3mhBlRD)7RR>F z1)s9*(+_sfbhx<$Ld$#^-vN?QrWtnTV6KY608=Pfwqtc*N+}&_I;&a?t4Y)@6X?(p zhoCxEKB^P+p@tSw=8B17t1VC^Fv$T!v%>(IP0n28USF1qOWji{Rd>LOl%i0kq``#oyc}@UgcrH5|r-!hlUE zeeeuwH3VF*hCmIg0Gz(S7%jKJI2)9#N-$f*YXBCgBk#23=&cNticquWcsEBc#Kgy> z3e7r!Z+5xia2hfK1Otb~N;K4c@ z3o;8#P|&`(r2rNUWVs9`3wh^=>k;KauB2q-zB9coxJk6{2;RPk=M zz|7@XgwjKvMM9wz3_oExcpNYe*at}yNXSFB6ah%&l$c4tN9hX!Mos1b#TUw(`2reg z3q>0)k}>^?NxWpCn0o6%KZaZs$(BzKR%|#&C=6^SK0*(eVOI$3kAu!Yj?UpZz*6Xh zP+@MjYFf@nuf7?Yy*NGvn_sUjxnR)5QZdckQ0@JMI3oehai*CVrGZ{(MbyW<#)q*h zAecnZm-+{bSpA|?=(p*7P=yc>=cUw!)Sn?}g*j++2!?I}lZ-E90%n&+CyH*3V8%qy zGU7c>rV1uBF*ah~pAj zXl?WaJ+?g< zkHTvhWX0+#EVfwHHjrc+SHdO`KJaOzF#!5`Fz_P0GPV}jDz!K8=AiC_G9KbrcsvhO zXC0Q%D$PMYyQURt8XIKH7-bIZPaeq$z@%;RfuRZhX|rSiJ;E9Vv($7nI2jF2MVMZq zg~E166^5M{6JaS5Mo${N&M?y%*PTx?oFK_y&@=W{`bQ{4w0N{9TQDXjhX}Ag#De2o z2!asICgOB}{TzVug{_G?gfIk3d}W3OhgtxFHc^7RnSqn}Zwvo%bjGFcZw%~T;^ zTOWGS3)rBrn1;rLtw-jGP`SaVI0cc9acGKqjR{!3fU2+2y1Uagc|F2{2jK}`6H7!; zoME0@hxMBsB)nrBjYS}g*0kLM2@P@h;sz)zE>P{#hKh|kSR@}X3+iB4>M;vDIX&2% zMGgWNS&VGA2os@LT*6N;cY0uBg?yF%7(?0{#d8X*BnVy7OWPn?Q0QEE{ZXr(nMQ2F z0=5#Jnco<0J}ko0xH3NKi)G{;MyN?lOtE$hWiy3N++XJA81G`_T*8N!ruyL~2`di8 z2FcN9trW!?!}?*u@YR1#YD_mk`6~|ljbP+PDQrU4Wa6MhZCfrbfvMy;dlVc>$oi96 z{(FM_Bo+VTVdIQmO2rjo;C6(4D<#T$mGKy{i{o zQLDscgG(u67iy;jx;Qsv4eqkez#ebGv)y_(fcit;a^33&qN_K!dAPoGw5B2qz9IBOG>OSWKUmsW*Z-_%HKhgB*K@0e{n8*NhH-FWHO|ur$_>UWk#4 zJ2J3?kcx#9LvCUb6(_S`7$3O&REJFi2*`LHm&dEvu!TohC!$$4*7;#z_rVw=jI1J~XgzGWqZlyYnpZ=p z1-M(zTOVe=jOiU3Ll190C=IHeEQCUw%|P4 zW9Vayz0b3K!^|+d9C^RH1oIE}K#!N7R~nrOE*69=ycc$jnytfJ5buNT?>Dk66<%mv z`Z$W7?Neqm;GU0({Q&j>MpLjUjFrwEFHc-V^E^kq3o%piw?hK%K`&hdOvddS3SXdc z(aOiO3vE@?rhVKVf(Tp0iAT_L$A?&z=7@^$cH0m-RIHuNtzq`mY+QIxnliZ8e4tvt z2sT1zQwWe#@IEK((gld-a=Q&v7i#QDM(*~_UNVrhCTxifDcP)tE0v(;D6ZXL0Xl?l z9>T1v26#{#=D&U}xK|(qOw+LE1#@6@9junp>O5MpEmOz`6DTr6qUMHyuZ|5PQ4z2c zGsXk4NmWvoha2WDAg`B)qqE#Sxlcxv-oHUwj$4j0fb9s17@g?{hh zc;#TJ!MqaI2JqHw{6k~a(lFO)dO~M#Dx%`L13)}>i2>vF)9{Qm^j{H9mu8t1)XQ$l(w)p%*$pASVcu7Z72Xa+4K6Ehe->vxTrE&=z5t!MkR{VDN||V15J(U{0q2fM-S^ z;rJZ{mA}eeIBxB-}9wFi?KC8umM|JqA@mS?7y7k_P}65H_gwo^-2Y<$FjK+ zc2LQhK?Dlc#ceUgO2M}d0McX0oZGiC`wz=WFgIF@<+Rl1wD!Kn!$}`s2Ym$P6ZUl zDFP>CwUX@?g8_GNkUlf8n~==MZIRsP57}5p^4YwxlJg@=$oV812GkH2Q#5uGbXW0~ zzMPENovj;T1>LHFJ(IBHS#t@z!OL)06Lbd6YhfCxp|3d*V{bDu!g^-;wMviKs7ol6 z241)_!Ar5YW*9AqVQU5G%hYrL-WVb2tB(f*CU#PWGmbv5V{*6N+Yu)HHZ8891GAkp17XjFg<&?KPQkQuB|~E6aZHMR(VK(pob`B6qb778MYg5iI|)<%XBLcpD8zh zkm)BJVnag!kzmrtRE(2RiNW3nkO6a^P%0)Xwp@vQ|8+kxX<8~Ujdd~@M;INZ>272S zsfnYw=mymhVmO1uei9qyAv3IQdJo`grz;pLD1sgXI@l@Q{3RLFO$qUPB?un1E0k(2 z#M;JASGKKD#O5j@s5U=97i8+n3&JL^fq94BM%E0mxH!P$2HHJtHA#e8o=J~+Pe4nf zGQjCM%hdF{TE_kzmclu=tk~KP1=dda<{eZS-?YwU)bu$84hSWqckFNeYFY+ zKm}<%K3G~^!(e(dfsP#v<_ehNMyz2;Z@adI2m?A{3AdVBgSI_gLCZrQ(1#v~<{maS z4wv_iRh-+Hf#IAN*A?x=DD0^;g~hQZ5~|G5Kj@<|ED?m@tSn0vrw@z578}#Ex>%wl z2aFv~B^e#`G0Xu;ZrS{(B?b_MqfSfQkaciZ;AlPAq|MpmL?xUyr9v~_xm>*ELQT}d zMihb-%n!ZC;D{I=VKH9fZTh%s?&loiC;}3;W*(Y`^6_7y@^<0fCC)@}8Me8EM?iD1 zng@;Bl1Wc8#*fT&el|h5>^0+pUHhdX0gM6lnk${K^gu~)U^0~m4eu6u!{E)c(Y`$F zD;@D*;u|Z`-k{GRFD#nVXN%tE3nM;>zjP7?cemO)44J?hr>T1vE5|;ID)25!?q!$ z^T})Tn0cZgM{sP>t>ohPi6YKOVFtw61^sTHY(~`~AU&r4C59Liqg}mTYZgqu*-4s5 zXc!agMTUGD38VmQ#xOpDfg`5SX0UI$;e%^yVT4RXaC}Sz4QLzoNJOflZBZsmJ3&ch zka+Bk!}nh$qC=Yyz2S*ubFffAI_Af^s~zv}hg#+#Vr-Zp$2}OsLVU>qIL~kf8=(i& zkFmaloR*SymM}lIZ~!-fjBObk!#+=#R(zrrgxC`2lP|S#DI9O{Y>WY@hlp^x&1#SD zDzM-9ib%qb!7Hq_5N)8tbI%)7#&3cZI6y1MwNWpQf^cgmRi>E)aJ;9EPU;Kkn~C5# z+ky?jB5BO0mTW3do|eF|BiJCY@fz#sZrwwlbiEx9ABNp{@}Ec53_AL;Do*XO#0s+; zR5TCh_tLP%V?qVMC>S^6r?tucI1U?pF*1ZOmO_SbvWD-@)z`3(0}rtXL5+g}6oW6i zCttJa!?_pgwyM^9G$8Pu&cK@!_|q1FHiP{ZsW8VpEXn|l*oj>tWFgd8StpiwozG?q z3#J+FEwiaN7R1Bs%N9!Hw`9P{&9A2b?~ylA37b_*I4?8~4F~p-gQVLUinj^?u3?X$ zQZf`jV?NBG%^k8%fjV0(S zH*1(|CPpAF9Cag#wn;Sv%+1c);srArc1S1QW&#ruZrcL5Wyfp=eVH^4LNRfpGt+Pn zubDphPx~(Lo;dTx|JY_8jwK?r7u}x6ZTOy8YPSE(#tEeH)n<|)GKl@4IO>6D8`!3) zqzw<*Y8>{xCdwLhIu^&z5rkQS`NG~0P2b4K*p}7w;jj|c7<@Ku(9pUu`slzOuA#}= z6)Yr{0-uDSq23bWWD52ILNVyG6oE5l>r zx@eMs{jg@wY7)Rw0Cw%$x=-v4ujZBmOn_k#)zmb+4#Wra8 zIJ%WAO(u?--Ck@bc6fe-w8`i~g~#P!>vUjcV)a>50NZ{RCO|!d!pZ?P%c-hoY!>BdIcUso@=Dl?)a+ARK5keL zDuR;>p6-B;CPbLTuorhcwog;LU|I+SN)+u!%&@x*=5C&ixBW|@c?XLe+6xx#2nJCg z9W-H#=eAT7hx|)_SZEE9BhcU!7d#AY5KAZ?M!!RgVLhP8aALF{Ln_fNygqeqdZ5di zkEp^5m_6e}7+Rl&@VOw&72`15I$w+ldYJ;#_x@VJeH5!5-5?gV{7lBiVsE|~VzeoF zU;}za`~UzLuT=K^Zz{!!$049%%q6`B*qsa=jic*O;nv3Z*#d9ou!LA2v`^UBX9Zvp z?1d^oH(_nf!ybakd3Zs8&Vp+Q=w#bM6Nfp^tl>bt zRuOGhWkzbO%b^W{M0N=(clEEL(l{;WnkL`12mU# zKT}v)Yg*8#bi)(_iie9hIA~0uXhRI7HHoI6gY*M_4vf#n|9{5L1i;Fw{Qu{@cNP#u zL{!`nHv|_p6-@vU|KN7c;qr-S`=0vZv9rG0EA zYBWP0jNW1dA!l$T8j$IoJ1e@XualLcABt>j?ct2Rh8$mB#t!1U5|pg&YLu-hSc^yk z4;wl7Ja;6fF9BTjs&`h`+k#3VyY?bJydb{)Ty`j3miCyvN1_%OpdL>*&OGd(v zO+7dfCSOE^TFzPF7jcq|xoJU}Sh==U?5HU`QD!<54!5l{O%^huePyntw-!&$nz*@0 zW5n!qw;_edyvx+5y9TLY#6ZbC(uW-wF|%0(ST@xHyXi=**^p-T93XeGsE@rJ+tSrg z=Pg<)wPj5ob_YrvG%XRXwy_jZyp|=plp%h7o+L9V8xM1}c0w%@7H3^sYQPk+oK!`I zeM&p((2Iu3qVjKE+OD1WzK(QHo<7c!g7bx}5_;{%3~{+SICp|1V6%>e`YOUczCDb5 zgK;dJnsK$Q2$*n?1u~W(BpDL^OPiWGWJOouBw(@H9XdV3et}#uN77}WdEB*Yz1@0K z(lse!*}IOrC8EMwrKwjz>e9Vf2mK%ky^XH&~pfqxOyd(K^I-5J1>K2jGdmT z7@itccF|5FsC$8j+U-gg+)O<$(qRa5B2qI|*k!0`1WnCuuEEz;J$;C}s=&>?%q@XM z+M+bT$Xd>oAm&t;k(k2!v#aScoDM-m#%wsYfS5{_g`N)fQnHBYZ;aF4I2+qKo4Kz@ zC1fe^E?_*=V!clh8j}#eDQ zGl`Tbj?|-=#pT|c8Z?+T6pHk*&!yG{4_b*hr%%qM;%wpCEL&L-w=Jek;FWaFjen(1;T z6k{qAhFGivIaVrK%gTqS5EDg&Se0HJjr40&gES0Za2BN!Z`w?phbpC=Bay<_vr-jG z&xUFlnWDIu(=A9@YW3H)s-V)?5hHY~M!R>S$N}_HD)3?1(B`MCZ4z++~e#VU6))wd{drfr3yRLnXKL5ab9VpiP5!~}r~X6A2;X7It~ z%vnuR#rzix;LB+-cHgX+2yrqI-PRMtfVO_cBnU4u=q1YQC2u3!rDzXwm4wg zC^dj(Qf#IZ7A9!wIo7I1ehbEy#nf0gU^VuMJe4EUtw?nu^5IPv+fgH%xRy!g!$?33 zjIL0C&g@5ssm;QVTCJgrY_+jjq&3b(v0l5xwp-+LSqwxGZxXJIHVNC3R~o*#f)uxw zP!yQ$Mi<4yiC0aSkP}J_I_AYv1H^~Y(xC=KF@yYLmsc1-rPrxOs4C?4^F3*%wn3Hx>(iMfr}G zC9@rE9~P|Lwp(msvV5fmY;F~KG1;DCgSh-Fa#Dye_Cn*h#0daSv@Ts!J7)xN=4Tft z0mK7`lOsykmt|J5uCy`Ql{RC#(yGUmcF&V5Z9~PCRx_@&abK$ynh<-og?tyx;tGI# z6wKnTeud$&$aXe?pR05=v>@HGwmjJSznHvs*C_n_3$!uL8BVAyl!Hw$!!b9 zig^ph9u^neZplv{XRH;~R;&w^CkY!{5h?~W+;#+8P8#h-S?0Z)B0eMOYU6&$x|cJT zh^F}DtR+*?ENI#;QOX&aXGUp$?P!hyVg1<14R1|^WwdCfw}Z}^3(+!JQ#Ci4iJDjH zE^WkeA$75xZDXen@CDl)JOM&wT$*e*<6sw;KCRTEt?FXkcYOC6aU zKpLbuQ@6{4HCgdeUFP4nvNQm~G}CJYa#w8VeN3h*0kP$qtz=VO8dFBnENA~=IRrF3 zFC_EAaxopCQIM%c1dBa;UA!BXQ=|b7{h6X(P?21Tl5;kDxrV2p3-6avyX}D^_cmpq zplb)#^gUvJ>A)3^Ovw)0!m+Tpy>TG@z_e--3gQLw45l$xtmoN517ia?*PO5|{bC|% zv4@lZcUXz8N=>)}qL_Eo-4ng=Kt-{6G_^&NcXDIFX+s}po&ZzZ^?SbkZTz6oI+-ih zm6fnWVB=ZTl}$^RY^F@NNo^4jZ#6qZN1CHr*S>0_&KTM{-0#~6h!%@Tq?kK zEy#PO@hv;wEzs*LaSP9Qf|(-~6}!&Sf6r>(b>M(^)LIwFbRA~7M#s%eOkNn_4atMT1Y#|zBlryN%7hN|z*p04xr}3W1}C&Ob72KMDg=p@&6SJW zxT*^RH7@UAaGB_;eZ~d^(0w;CW!Fh;&>LH-aC?<<=I}k=QwH+`s zRXo{b>PQhhp z!aLC^>aM;_&}U4k)-$Q<7MIMLpY;l*N6bEbtqf6+chRTF!WA?$XHKK39<*oJ+DaF+ zEiNr!t$9bMN`n}MtKEZG&FVO8Dcil>xYEN6oM?hks`oooqSiiBIV~o>&Ze%#Cdwxf zXHAT4ebi+u7i47yTv-p}9II^CR^>5focfG!GudWPLCb|h5s$7~wXM9*Wi{_vW z6(lIk)e-WqHclIYQjjt-N34!5%JH`S+SX|uBOW2w!j_rM$Zy-MlYnKhsJLBO(cP@= z3CbEN!T`Bx3E>-W=J!1(?TWBx62)UwT5M}9;^hZ8VH{Pk9S`$pECbq!E~fXPi~+V$ z*Y{BwK4kZrR;BLqt-034)r&2+V{9+b1`=O57&p2|pVDe<_K4+>t}du)cCS$q$$ji_4DpUkZ^j@C6yWU@yE7RnY9*kAI74k#;^7ng&L2dmX@uf zY(t5A7Sd~!O5}FaeO4;KgpcA?GL(G0GEWw2M_`KxCqeDF!RmiQL9P4s%40fJ8wbs(oeTkBqQmGuS;uDE# zax5ZYO(BUxQy{NEJ~{8gt`vtwHL`1<)2;|iSx&Lob`@XxC2ebQYShS$yY_-WbV6Kg zbNC`Mb7Px5P@>GJ5=gW~VGFW|TpPtEP$V#1%myt5rlMjenF7+t;R2JCG#kz|i5297 zyQjIKcqCU8kF=pUI{>CQB4H?5Kk8v83uJM6Xa%fT@%LEIoya^RtB(Yr>Oe_JpP8iQRG|DEk_GRFv$Lxrt<8Hu?rL9c*Jja$L!T6bxc=45@H##$q zQ>&?&pLGYnP71NvCvIAi&daPL6{=`norUk~UQcXPE17Pf+d2J|p2Qhl(vP9ShN74z z6Hb*hpY|R!HBidqz<9R**%=Uz*GN4p_F_#Yn&PZwv-diU>kB4n_BMSJ)1_IgkbGSA zgSKquYuG{XfU{9;i!{OENiV*s8tvsbiIPL5c*qus@v!JnIPzwiGIFET57G`aXCX~h z?P^~m2<%w9jotKreKVqUIh&jomwK2r!`^7H+&5EGttUH`7K1v{5l{{WvqYxdvDNDe zyXfApP#>wMXC$^1&+%x)&_Qf2X|Zz+BXy^?g`uPpf>S8a4HbSL=uX42WdQR81d&>x($d|G7>NgLjmc>LEU%`E8MduQKVam_ zRA6r;+)hPCa-sByS!d&P8zDQHny!^1x7V_iuqB7U$it{`rZISo1W-lC#fGdT=~gge zsm5J&1-&ME$pKi`Y!tWMRW{+c{hQwuYKbuLVD=XwvC4-WusBZ#o1I_{v?)P#mY2a+DlyL z2f}za!MPR;Xm%r_U1Z@WOx3F)U%B!?H&_%zo?&0~KqaWSIM5kSAr+r2=3y`HZ#2QY zMpx_pTq{@C(gf}9-6A7r)kfzg6~f*phlmxF_7QdQjuu%k)YX&Khjk`gXAEuI2;Lo{ zull7q`y*<|r9`dnBZo-j=5XanQCW9~aMmo#Ra6+sMrER!kb8Y?K9@qXCm$+J13x7u zmr4ghP>7}W1TG9>sY4y$#CyCHYUN|~P@V`e-wK47F-10i7qpElY}w~GR59=<^~gt& zYotr~fh)n)$Uc1NeQLkUY06O=`6HW-pbeb zhdpoetmDMy6H$<*OL>@;U*6#olWls}f>d#mD`xdAk4C6Y(%U(0yb8lFM{P6=OlJ_) z7hA^MMIs-VhkWA@g2;Q4>m}{zdfpFWRx)A$c`8$zD5O+Y7BDD~#3dGnW^O9<(Neaq zk{N2hDxGG=_9YX76{zk^=AJ>jG@NlzH%;oKP!t-naorb-YUxpBoR7lcv*@^Sg(CJR zhU6ku$DnLkWZlP9)>=)a%JpH`IPRPcD(HIKnRejA!#T8Xv!0AXPmAnP~&I*ub-2Y9ua=&YJKF@)C=@Nq6=7lndd_8;Dw$_`=(iBCRRx67hqiD(ba{T)ka8u6rg2bb-2MYX+f&*%vTqEuom&JY zd&020irdHRkqI$5bRnPagI8WemB&h~W_i{kjR}&MCfB#5SdVE-#H5_XJffA+swDAF z{w$JhZ zqnJoH+ppcE5LMKbt0Ie@(V>mX;ilYFHCA;`O0C3UC{Wo}qH2QJsOD`5HHl-d7Dj)b zLe+r4sSxA-Q1lFUTrx=VU0?XE7tslU>P-->?T=mSz<0s+yAWLN`_O>Di8HJn5hi3lGUMiG6wwjfjE7 zB8B$FILBID4>ehaZS}RW%xasZDo(gh@e)p_JuV>wt@Sv-w$hPOSg3vXUWQGNjWeRJ*dz_@(%0yRY$fpVN zb~u6LgR>Z0da#`1M%w0ORb+R}a67TI&~>~Nt!1H)drJp2UbTNX?zzu70)U} zr|tP@Lb9_A6tNZ$T=*tW?v$^oTrSs)!5H?2t=34@PUPk4CD%tc*tJ zIzkhF)eXEO^pe(cMBpJ>T#u+V^Pt58iacy&(24s!H%EaG|_TjfFlO6_y6-oqBHZaW3pHi6w7 z$^fO~B0j=Ena7=s^qNJPsQILNqn#FNhUe)?9-dr2j#m^R*=!c#u4*nF)XG_NNz+oe znp_Xas}fGNgjHk0sWYNW>^lau5*-q;2bHZ38^?3iiuhM_2?|1EGPidUGIO@g`5GSrX$o3T(`e6tk;ovt zlzj%ZFuQ?V3Lj!%u-S789i7SqOcDwk79B&V%{i=2rPoqbU#>T@FE3W=LaNAEh+K!L z>s{jSfWTPq(?cqei=ZgkU1;uLYZ1nSH8=KOnvpe&Obo9|rI?{ZEQo?qm(VfUXy|9x z3#*uT_Aky1z4UO#og%ddsK(sRqt&{ewzE&KS|H_(r+Z1oMuk`Pj9(eAB^B$^uPbfs(7v{aSbsZ1<)C{kH#0~dc(ZFc80(p~8Dd%3 z!1E9-Q4sbpo|TaTMx61Qdx>>37weknfhGaV@W|9i3}mlTFMfocXgXdfcB#8KlsX$9 ziM@DV9>Sk64%Ke-zy!HrhJk4HuFQbOv09yGW6IDTsydD@pnxcM7NU3Z#kqcyca`I9%5wVgf8_*0E=#Vusr z&INBgQ;&lS6+`vdgZ0L!rw7BPME}U$$8@ZJa*B0%H zyFogno6bdf-Knpe%-Dxh0?~P5GZ6!l1%jFbuWxBvD4JwbM2TTvbBx|nqo?1@n#CG- zoaw?xrZ4reClg#57K5FD)dUiSj~gl!D3`BmOMy}(o&B4 zSkCIs{;;>-4yqeh=)N2%H5KHcPCG$O*|qt0>ZhaA7|%L1Be@mra?tPngt;HnA?J3k zG0y&eo*w~4wS4?}Jej{5nKNSo-Y^$}*&}!3+(pNHkIg;wUt;Xo=fSI__ENZQA z{KT0(WnE-o)GmaQ*!@Rx38PqvD`%hXQCww*3b@vSaD2Iaq)rM=y|ZMQYYV$uO{ z>~x_;BV(Z!+a0>FhK#{=!UW|9Qhv{eK$@(YrpokTwJ zBTNk^T}ESzjeLA|3p?0vPNdS&2U;Ugad7H zg6)dhZia1-B735*ufsu=a^s?|zma+7e@}ge+}`UyBe*nfoJbC<^iU>Zr3cKp3+9 z$%NF$2CK4)(kx7&y%w`AN^gwL-hjrjRUGUcp5QeeHznJ*!q`tNH^j*HefEW8=wmAu zA=JBwcfZD2rLgMcqU<>IGHz*Dh5}*k^=Ip%0XK8lZ4kP5CK}nDMyft0CS0?0g4gO< zPibQ3=Qdh0s~Ofq^@=fA}N0bnvaf=vTc-6tD z4*G}hNQv?9#Wx!;VS-63P<4Q@ZAz?_FnV?}H(;d8gpbc*7D=p4Uk(QRM zQ>S_n8g`KFA*O5^vV@_BV#lO?C@WH?St1}20Co=7-Mf-CpN3N{{n#bd^1$|?AOK_w$ri%ekvRmp&;~pQyB(hWWZbXr!*~~O_cc~hT zFfAm|B{3!XqugrFuv5xxaWc*5)lyF0V3EqxDPqfF#^&ZpGH59KWr#tNd69WC2@=ij zQ7bEHN7BMZ_At|GD zV;Ys}kp)BFDWvG^GDRaT;{zfHymm-zN|c`GKTT9DFt>7zd0Z1W2%XR)axY)SJJ#9? ziww(!3%g`RwPI1G8%KwQY1c5sFh=2$K7Z-jmvuIKHB##syA$wAT%6CiXiyAdo=6m$aguiL z*T_jslh_&TwQZ;4N9vQbUrj75Mx14eh8CigmyEg6%6c#FsX*IvtOE86%X}mObRbZt znkaDIqtNjNTV3c8^ftC>Jc8bEMt8GBu+{l&op1a!CTBV{t2ho<(H!D7t@Ly#ow9Nz z1Qk!KU})*Yp>ZpSJf_>CcR~w-mMG$$qW0QIAjM#}1w!le>Y)+tTSU4;R~C{pR6We# z$SBzMGP_Z}VHi)ZWG!2FMK3Xeg!>)4k-hVmNB!Ps-P9%VbT|iWe|2+6A(ok~dO5RV zR}^a9>X}zV=LHEX2=RoPqmn$^s~=*7o4f>^7OpG^ z?mg|rtLn~rEF1AqI6PqtsXvm9L^UgA!S4%FO}a|A&YOzGtVlb!04{isf;AG9NIc1~ zq>~HgTiJGn1+~;SSfvtG%_h1Irpj<+L5lz&OSr7EJYU9-aiwys{c$Uu*yv53d>I0F z-8~3nwsb>U!BwEc;E&7geHWW(d9&CAcC3^6K`Iu+WZ4bky4VXYYS?Q<53$7bZfgM+ z{yO;XY}aTk`7Ckj%_3@S7v0O$pO|4VL&DaUfhH1G*q^Z;Uad_$c7Ij!PDc0Y{H%B? zQR_DovSGsVcvVX1#QWuBzzD?p5RMT_<16bJsoko^t>V0DqgzFs^=Txl3MO)|2#jZL z#l(-Zj2Xldpatn+X@Aaw>Xz0hRhJVXi+V#6dW!~+5}8)k{}hlnTKWW%YSsizWp1cR zl3??^E5$yP%$u$M7*7f}KQ0_QUOT#!icthwH27G?K+hPZ!v*O5aJ;s)G^8NsMv+P= z0i~sv^(Mm~9Vm?mXWR@ZGfq=e5DL_{DlLK173E1c^~+%lW`3#(*U!AjiFxs&Zt8WnZ;L1FG~1qDH*b1KtU)L?Gv+#^_WP6 z#b_2BJhYY~4SPmw)@Z8;c?*p!SrBlsZ3*m^ZVsB8x^#a)Xj)_v5d;`u$7pH5`bDcf z(`mI+qJs~T*)Uj}n%SY(ACIxMog!7dbQ%U6(HT06@99SOL)kQ^y&7ID_oO} za2Sn?c=qC|vZ~ZOgjxy9jR@|Hdmdj7=P5dSa!)i1EgUwcH3B7rq>?tSM5SJgm*K;f zD2^{83c5|7kEBN@jz z9Y(B{ZGK@iAUcuzBv2_)a%~4rJ*K(OZmSSynqv!UKN?JV+R)X7a9z((;Iyi1eW^-T zihLF#LwXg)OgzA=je6dcC^B)4ZQvWfIBk81hR79tr_mPgQSa0n#SVL&C04d%V={nT zjNFH)vG8mp5v$AEzJ6FW!i6^uvmW8{lf{x(Fn$6eEn>6L@J&L4ynFEqgH*>LEVN zV(gGK5?1T+$v%y^IxnnkOxS4921N#TOQjpc@Z1TsxiMcPUbo3HyQq(+>}-IxF3(EC z<|s8Sotfg-FF+O-#61GltF{m_P-(mAu@T-M{F=ZK%BQ4Heu!Tk=)U_2R=Sr@kv^1nU%M?98 zZ~G?dbu8^Qxr!;mwI{rpZfD8tLI^fRxF=*eJ5B_n^IlMHTIRHLumo18UNR;u85@?2 zBQU@0xe=3sdaOuw>--WoS=L2CB7b9d^T;$_i_R=ptYbXH4l=?<$yDj4p0{4zgsdqw zEx2e*rP?b4ij=WJLC~*tD8epmH?@^N4>Z{22Dq?fF?3Pl@$DQhj7%2IgCLC^ozQ%C zhzq`@W@u+tYhFj-wS>%hCz`PX-K-u=cSC1RC2Z?_SA5bRHVjRN=<5)=E*49 z)G(GniCIZWqz)-bUH*}3)4;rdQd29)m|jyuEm~-_hN#kU0D28n9!ozpF>^-MTjtxU@*PKgCwd zzEn`U<3S;4mGsb+=BL!uNrSYEL@~3}hSv)xCY=eQVd}-%gsCiLMhwIRtKD+f=NfO! zMxGa?HlA{WG`B)0Yf7PONFeK3Mi8h0ShhL_?~jB!xyRC1T#;C&j8d#iw0!b*FLEJL z3#Zz59YU)(B@ruVxp^cd=J%obV#|BE67z)=BO-1)8Rm>)6UG zs!sKI9MTsEDd*2Qqt_B17hdqzSu~V*EHR4wdk>fejP6>KY!V6)*4ZK@&hpOUiW-S; z8P8jDj%hp?Q;PrMZ8q94`lhZ-n8cP3aYQkxcw2x)nH{DWZBJ^P zmNnWCrIl%22`{1p;Xq9v){5iE?#9sf*z`&A#J1fU`&hXEr*xe85T^yofV0xpYi#_d ztc}YYH~P#%6dH^g7buKh-_VWgWjFJ+a~eu~Pt0HZ8adLtB<_8JzV;zmFAaOWmKoWFYgPZjAKxP-2 zAo9A2o7J3ZD0Q=DGdF8W+$iOxak)uxI5H`X%S|?QQyQ1tq;bVO7UW-a7FxZVR-KB+ zWP&dRBi_i`)Rm=f%f?#>z|xSqj@xF9S=^~X<}o$M;!O=oVl6h@%&W%;y}&%HT8p!6 z)|uT#uC{C=KkEG*+~vd5fTGg~N$!hHN2U+$75YKk0uZfsW(WU8s?p(4X!F>lztzOV zw?j?=YPrZrN04aty7Y~8pXLIMFKjv6{eF@~S=|B&s`|=^G)fhb@JybubqVi~#BIwc z)<>cV(1O;MYP^!e21I9XLfQFA73GackdC$$QB+}u3zO1}V)&E`^R2&DW_E>zrAntr zhd+;A=BbKvY0F_JXUk~~xKWg(yJa~V?F{-I37j^8iqWyjRECX9hf*cnL|xM5>q$m< zf4&^vlODZfyrq*yyEG6drquGZMBz}1-38g$7r$`%bvLB0he%h-k}r%KO_`Akr15I2 z#4s59G&^e0%;fA;Troq+cT}2NhTZkB^N*RwU7^4&! z2Jvfab4h=R=C$z)TEsRwJpv2b1GV5hAe7^nZ#bI3LPD3c(8^4>g*jOVWQz2K(kQrD z)P|THPwW&qgK+2K)6sg6Dc*t*8FP__mOWGz9F6n>jFbf%$u`I?F)1}-L;S*%vIQ*{ zqA{6JXRLx*Sy7yeBm&P1Kdc(!S&$K&OKWSh?SpvH3v3?fJat2xqbc{)ASEw#XojXH zA~dplQXeCkx3x*aOsR=XL0hjR5bbHfFOE5~5F*#O)J*3mQnpfb>|&3bCXWOxqeW=y zB%u&Xoc9vP*fmg}zVIW_aVtdxHm{ZXFVtan%+8FR9EdS$Rmt&N_z_M$_i>37R4Npa zotTU{_nF?~6AKeTfx@ns?J7pom?SsSDz=NO0kSp+=>k!u?k~Dql$cg4`eGw#OcxwB z1#@<*i4nUF+Ao#%li;?3xN zl=f&X*Nbxj`A2`?7DcG7BJ%=^HI1xb0+A9EtiYUV+ca3Gq~Txa8gyt>JC$4%z86cu zezgLyecVlL*2tzPlW7U2nT9XJWKrb625>v%m9mz#Q|1*UW-7y~2se9{S28?Sz*2Ow z%cEh-s*Oytux{5v$X(d&47}v4?N#$D=TYffaTbs%LkM$Ds-N<9E@-Ue1e*QYsUIZj zbofW37u}sPw9kIqmv)P(+uf(*K-e8IzHvD>enrI!2^r6jz>t1wv@JS=x~-C^Cw$DY zaCP}@*Y7yUd%CuM+CvSyq;f6CnI=rHsrPQa+CnO5v6%&-tbM2kmYp3fsk>PW3ch&V znv2c6Vn7;s1dq$pYFUU{RP{`2ZLQLVdS~2C*J?9Nt)Dw5E+nV-bl2FnRG4MU;ebK4 z4rzp{dQtVEs%_02R(k}2O*o-#b(Pq0QiREHjw~JpMoF~=DHM9mtlEb3RbaFY4U=aT z>uVQex@zN?G%$^}b#G!=HPusF^gCB~m)7Zi^=f#-1(lh5P>r+WK<8R6ebTnPg`xD? zxoFqUgOTZ~qH>yToYjV39>7p#@ww16f&`Nn?ZLLqe)0P}W*$g~+uGECCI6u&KIws&z5t zH#C@xp3V+hqG-u6sl->;U@|YEuAWVG58r?1&hsy&6Y6T2rb68u`}l9&JS{})rm&_C zbv4GGaTB?(!rYTYeze8!@ku^S!%%cT-;Tu)xQ2|hhlaZ8T}7G(@tzlDma|xBH)zhZ zlw>*;5}`tI6L^fp(p^w{4x+w9<5`F{GfV=fZg!g}^)QtY#2P}K81IBLBOT^h_I9TVuPZjElEgyU)C@Lwn8D=2UQmgZx8dRds&oZli5n%c#9*>^!%Ix+ zI+iV?gB6FP3^a&TEitW9DPS_QdzmWIvXYt^k!dCk+B7TiXC^gV*45Ef=(Tc16|XEc zp?a2PCaO8T0$38*+<8>HxVI&yHC%SX3|{J#c)7v9#9cM@S7I=ql}U-gWNklkFsj6G z8pKm9(_3k}U%U1vj*RkW zdz~c-vR2555US=)vyrTUhO}wfMY~!KQEOmeFtXcdN&}+T(lDbag!*JPd&oAjcz-T& zT^~7i4V;;2>--3-kh+|1$AC7e)G4camNF!j_)mLmiDB(h34(W&#*`XnhUT2AIjqas|n$g^}Rg3DTt`$waSrHZcsnTiA%w{$eZEtBa*63CeFb$1$ zKQHKQ(&J_{sl#HorLGrmEnLyI1u7MMr>E3#D}`k9Ej## zHh@bk7{++lYO^{}ybWzGlFOJJDl7kpP~Ptr}? zZB-R(1P>H)t#4`1@`fl>-#pn@*Uj}@Bo~^e*0+ilZx$gpuVYQtM4Ic^wd(Gwmw=Y~ zHDPI$9W*LdF)E5=OPAd>P)UfM+ITa&V^IghPwVPg%^_1)vznjPtPwWMo;fv4v8|l$ zWnMzPp?;1feToi5rJ6bjsnnc_C^S^5;Gy}_1T@m4xn-LHu61=Jt5G!=zZ2IDEs*={ z)7rq{UgyOF8DW0+G6W+gujr^|Kx(luxVE;gr)^O|KUtUWn~f2>PPfj`+0OzAK(548 zC~{CBf1Ogb04S)8wv~gdh>I-f~5)*bcm&RG4Y1iHxFbVZR4-R9h4OHI^yGH2eD!rT>6F}24-p68#b^^I=P zXh9UTQyJ2=MoO)-05O8xh}S&%}KD#&Cw?(EGz1FrK0zDR{pY?YIlywYmiPnOO`qON?{@Q)UCDRn+nOSIzFk;8~2pbb< zON5o3Y7@(1lv)PE_=&n;#b)boc^O+%+9%eSBkDLa1 zXBG?1l)g>}O!jUT=r*)zwkIruswbb~TUY_H#6_=xLG!WY*?;GVSK8OXi~#4?jjAag zU9?XyFmtdI$I3scYfK=@=sxjIm7);?myw^8#+Y@cW~FboYSWYAwQ6BvGSyB#D2diE zVy-gJQE#qSc23nD`^w77PBGSK%{m~Z2JOMvXyMbG*55tG-Z7Q;beUCKF-w)@BbXDb zYWN+tk#8oVWogH9n{{@|U;Ghcj(Abc=LT}RTW=r@D@~CFY&xq_3*5C5L#4TM_Gzhm zYwF{?U{hBWr+?f#{mm5S2}0Zb#nGmO5-Uz~sfAs7T4FSi`hw!WmOVd=4B29TxV11qQVKMU@QRCw7X@?)31aoX!0xCWRMNeMPyoJ zMv~)5w^i$mGV~KeNOQU*wkgVu3Iw3_2L@+-H2|VCf|HI64GCXMm&Rr-4hGryP{R$x zC$h^>;-o~AGIm4r^b>4UN5S3r9O7~U)oEcHP23=hpvWwB9Mce*H4IV@kTzQ|Dz=_u z@>-3(Y4mrotcSXel-nr=+WG`U%BmX_*z9(Q;ds%;A>&G#R0JHd5Nl8L5}h zdJ3j9*eBcEw1LxHo4Rah>TH|GZbfH_f9oQfI;~?3*R-lQ8ZWwH6Cz$>sVp|Ef%mzY zVJ$7Cd!>)ng@n_ab%XLIc}%JPz-1_*6wDt7rV0DNYJa;uwHw*#RepO zhQv9F%_!Z?%)AsgaR^Db1(z9rv^Y%9_-5r1`Rc}~DR%B1i%|-StJu7dDKUMDZM;^} zexEjCA193r@PW~lIyFZ>5)IH`Y8%TJ~JCMU4vjo_4e3Ou-;Ue;JJ zfTcHAJ~x_EW}7VN8qGbKuF9_eV^_7rlS)Q}6%pf$SEQdU!!iU5-;PBx~V!S;zdi)pOP z(m7o&&A{$XH2WIM>Xc>Jm_92lXkOdfv(VdCH7r?e_81pR8%_1%Hk$Xc)Q-6n+9~>( z(#3;=-Cf#zO^sM(_zh&y$xIz$NG<1e z>Y=3AvSLC}9WL{<TKvGK&)?0iak`o~_7wg5E25n>$bRUqP=LnbP0}eO*<&%;RmP zdfDn8_HVS|?W=9PIiJP%BnZTq1#qt+AWRvVQQ6ws*5~FLO2t?Y)_Txrk`RHF2AJ*? zdG{*p9KjOu+or`V*P2Kak0>3ftJqRb^`^0sOHCpt$R?ZAEb|^`|J1yukLlgHQJE79 ztf79IF_4O~Cu)0alUN451tsm% z#js=^?-XbeXUTj<15-o)FsNnJl7-{<1W0Mj(-KQFF`rBA(TsF&QLfEIcC9c8i+xE@ zAZ0$L^H^-S^vKu{N-TVLa!WRIB%rlL3)mT3Ht$-#C39FNlqTJhosS8%tGs2~xaXRz zVryp4kGw9YzBPZ9__7j)MlCU{Wu7cC&^oTfz}Dj>CiQwkw!~yMn`cF7V(VH*Jxh&N zb}TFPq%pn3$!xnoAu@#K+f4~BR)DB@r|VJBH6h2uyq$~Cp%YsxJL`HERWVV*lS}ze zy(W}yeCw5+Q|#P7YiMN3ba&;-&gpeitSl`+hDYOD*pSw?OjJ~AUW16P*`)?t#m($o zln!Io6gXH$jn$^zC)D~FDuc$Uh+e?i7MDxf+P*Gh=NB1Q*EKS8!t3twsl?o+Chn`+ z&$^q9FM!0}ctN=-~h6J6@;ywupq`5C^KlNwGYCrFz#*qPSUuvRO1i-0!4yjME|&SB%YnlOLP z+}TxgXH1z{Jq7NIosBO@$~c!J6QOaguxTxuB2`Otk!xRK<^^I#NFdlck+V?nIUsM@ z6e)4LB{QQHwiIxIh$syto3l!SCfh~Fd`hf}k_Ay+8dzj4l(<(3B9kG(qO=&q3$`Ob z>DqiNwvsMY-T1+#t%MPZw>RT{9MdY4MY=hseJ#>$)WXS-3v=f_fbr$HOos~wWC4o45k}QZVa4T_sl*`Sd{wUX5PeE*P z{~syX)a6H~U{lwnDbP4M6{Xp=8?|>xxoYU(VR~A=L@7+V_#;Au-MJhg#7$~*vmoo)?(66> zS7VAA=SR6?7jWxItYY1%^BasW;`gye-@4m59X*JYXxRP;M-nq!q{~LqQ>0rDPus;5 zvmEq(M2L{5ZQAc&{t<4*);JmckxrPP{F3{Ra1|-_j(8DJmiWyZQgss)h{=E;&x=54 zb4QC0D@N>0Zf=>(k$otsX-KR>&uBl^<`&=MHHuQqan~9tXx6m&RN%rkjD3PlH-|DB zJ|kc8q`fJqN*zF82Q;%Os1(`kcv6A9L;XO^@E&eK>C}wrB@8vE!-FG2q9UF z{d2e-MhHX0;;_DaL|E6qUw9()%dH6B3TzE z|S_4wI9x)qn|azvPr8%m8UEhEC9a)mf7 ztjaIQhh!CQN1&_Ub^$rqkdILj?!#px!u4oJ6Hk5O>FAgAsLT?~@V7Ax>91OJSA&Ra zS~wT|u(AaQts638$W^(G<&!Wi3+wPdBCNvq`ebbwS~e8FBM5m=Sf3x1Ck00>2t)IW zN$>TfbA3XtLeF7CX;+?R+AJ!S$wqxh=>q&NrX2K-hHU@p%lfGuCWA0Jm{``*4&N^u zL0eINm228+t{-)^KD?j2Pu_b-?>e(xOZbz_%}~<$KH&|rFh|($P|BrDIUt17@izh0 z_4vL6v!SG=-v0WLf3DvP(|_Ks z)z8uVpl+Ym>p~B8x0q0$OX_jEI1Hu5hGZkHe519ajnvP5)bM@S1b)>%1FiKw z>RqGH2HZVI+}C5iE?JOVZ*dHZF{!?XS#Mf^Y88Jf6R%nQ4&jed&W}!1e;Wy%_L>Z% z)F)6bO4&4i&&6+GZ0JX=o=$on#&iPloy%8wzlxOh5YOl^v_fMFV^Q*)(t}yR)tkw% zaJq%<{r+6iaV{mM^hhmJjR)suZlqItaC28n?=4W~gJ^Cli3vcXM16Quu_fzC`>@b3 zXLj{Pxj;&CK;I;67xEy0Bn-fOAQ*tz09xwysB@tlR8S7NP>H${^$ysl>Pl3DajO}k zEc6H4fB|7gek(Dn#H%m)waa99jbv~`>+e{b|EdhkQVPbJK$q5 zYK1lgKdH}MDBu1-bAaZpq2?|ZcEw~jusiPe0DEFmW~RGZn0pb^-nb~o#W2+SM1RB4 z?+dmkl>sEE_alV;(H&rJ%EEz!Gy)t1MuJhK^I*a{1hC*T zuva)7_eT)UkzfKTP&pk1jz<46a182WNzrk94?=$+ekYQ;iZGm(mJ5?X6;LgxJgQ0U zP9T7Ul-7{aDU?zzm|En18tUobcyIz33}$5YQ5Nb@&jhnTJ@K7LZYwPh1H){>YQVkX zo&)BBQKW2pOWlFQTN&n=KTp?u>=yvFhLga_lnITEP(oOUyHmial;HLj{z2h1!fC|r zblf(9QPf8>@wZ^#ioVUl@^;XU-$j^lSrTP35?>>&K08pIfw@}K5<(bYVTZ63RVQX$ zpqp^Hk0P82mZ9$j%W0W?)C+@JSOHdAS`W7Nx{A0~gEipe;4JV7+?);0p|&f~p9?+- z&I9LTegPOoD=4Eyd{M(+)lhd1f#$m;7)KC7)%ey+Xyv*{2mp4L@!WJ2tfT_?PVOjq6`F(SpSikddV@E z9TVLh8}6pO_K$TwmfmxZ^`U{`$MmLqN&9`ocRzRlpKAM80MU9s;rmn2j`~3he_;3- z{qiC78!`U@_&MrdfL{{hufVS{`wivuTktS=gdX}k%zqCa1%CjK5$EId*eB>UQ*i$z z_#^lec#6LKG`;v4+&v4P1J8p$gBQSy;4fezVZQ`k2Csm>V)iQjUju&we+RFFH?VsX zyanC{?|^>*_3wXzcfot$eef^vZ}1=R0r)TY5d05>q%tHR2lAi{l!FRT3HpKlU>h(1 zYzwvn1HtxS5ZD0>20MZwU?;FM*aZv)yMo=o?n$-vpW<}yf%!bjxq(rPRD?Z~9O@AE zqA~0ZhJk(XH$0g_P5T(OFMjp|`{P!1dH~-8!H9(MGf_D+Mx!4E4#xi>$$)Su?hgZ_ zF&hKMf^lFxW`~0#z>$Dst5C?b;i#mF)~E4YV^w818oQ5yW5BWCI4}`R0+T@%&^T4i zw+2iBwO}fk2Bw4K!3kgnr~@;Je-@|*CxY3a0n7n&f!eIb{CT*Y4;Fxvz{%uuAvgs$ zr-IW!BRCy1jfI6iSs9ve-wfv4xH2`gBpPEi&M2R1^C7fSMr)y3wj=#*xNQe76OQJZ z=fLy8+o|YC#$xOjgAU4i5;IJH!a4(ep={*0GAzMtDQW5i!`1-_SqFM=z;mEbDyB`a5z z^$C=s$~uHESP9#!Nd>dR3ZU8re z?||=uo51%7w+q}%8aE_$%yenH^)PS2O>r5k&)o`c1K$TdCP|==@$Heh4cm(_o{2n|C z{s4By{xQCfC$pg+(=r(to*@1w!5^)D>%yN%m)iPM#Pc+G255d(UwE0X`opu}Iq*Db z`!nOo3(5Xa!j<7g%>Dvi0{aur@r3g--&eq2!K>gk@Hg;x+`JCn0B@py3k(nY5ej2C zW*$xse~k6$_XndG4R^K?v4OJia(Kt=Rkvl~AJqFl(NDzgUB2&u_rbrwzrlY9;{)*D zB&8E`;X};-hrf`UL;2@IlFNm6NQ=rq^L0Dap6K_qoO3zmhMY!eDD~X@P@XFb6*;xF z1>`p^SJl(NP>H*Kx#6Kd*ai&9X}n=Ym{U0ym#OFF8RFIawjFT{1ltq#Akwn~c$ze7 z92t!Hj$jC8X&!e%y)$li0Ygd4u3$H?I~Y!QJ^0%LKYN0`z}{dO*ar*;`-1(z{`gnU z4&Zwr7y)*s91hBz6h;!(sNBqOFgOGp3J%N74Wn^028^Zr#)0vKvxxW(=X(Txjsz3H zQRt5b4?UA$Ocx6i&=74zqI| zp&@rhn3G!)=H`}$dAZIopRg9>y244h?r<_#2u=Z~g40OTbaK$h_jJ$%nn4R_C69%^ z;{8YU)<*i;!6?de5#Pn21Ds)X%UfFH|EACZjXWSMC5%qO>cU>_up2jBw9_8GXOd

^1Y_kvt6-?H$j+;ZG7H}P{(t}nV-Vc+v}D{)a4))M}w$=7H2 zem1uX6WTldt1!;5#{DYFbB(3{V%&W$=2PW1k=2dT{CP7!i?#Pz#CR5d)`UxPtHZk7 z$HS%Asm@j3s`pWp*=4z&Fp$45&~`2(yvxBC!4=?2a25Cx_%ayG>PzkD6w2)@=&#Ow zg7B5Uv#ASFd&+~tJqI;w6x4&mxt4d8@o-jIc*9KU^XL)hp*qjP5FM{M?`wpuc~fmn zYlpAr&WFZ4FRag1g=-jpzCi%j67F^2o4E^UX|l_OZ-Fm>Z-XxUU(fdjFcB9w(ht9r z`&9UD?n25=?nP57eCAui+mKweiFq!35A&OIYh&D%VFPt@3*jkkw-Wblxlc#C;oh4f%5XQ~+yj0Lw6?t$^ZRn2B?QmY{T5$k zc)-jn!%qm~r@4zMYuPgnnZM6be)Gf6a-Sz|H-8B8jrdhL{G9JEz%RkCz^}n?z;D6B zK(y5(g!Mb{d-CyU?sMS}xl3Z+)`iD%mxjl4mxU*=f0A$qv4Zt7-%@?3Jj=o#Eu6~m zC)7{nE|2ahbM)##&k)wL;5qO-_%rU)IvoY|;^9|@7f9cW*!=~(gu9nxxUXQYdQf}$ zYwn6zca`B)vv<4Ka#u1Ac^y1W9$&@3=HC_6`=inSAXeKunVT);Wq9~oQFx*s{!SRL zgExTYus6Y5;BD{@_y_nWm~Sh1ufunV>pjc2+8paQ3-ila`%pXl7iQ`c`_hkZ#{a*; zf51CA(ZFTl1N{FN{fD^Q5w1XDX}%ghgW~vV_+Rd8A>_XvlKlFR%MT3s{57F0&wLLm z@{A;W`{ln8`sc4@tuH@kgl+O?gaKe%upJl(w$EP|2H}1OFc?2Of+6`3b^>eRDD0g7 z7C9*R-zDD!U-jFV!<7TO<*(2Bgz_bh-bAoF;p~yWff|E{hx(=DMpKK=uvh*&%+Ctb z`=sh-@4VOHL^u}12zMX+4Ckw{Yv25hVL!s!AN>K~K(H6~BfvpmBp3w_&JPHOkh7vmWP_ap7ElSrA?nrdI_e@{{F;|cF@AfC+;e2)YZD3hbW z(fONWp5buB@Q%U$SnQr<<+h#WErjFHPXroEC-I#Ox~zPBoYHu8l#NjXLRJ12Vpo~m z8mjZR#d;ndYVyOw6#Uf^{#4?chI%?U9{mY?XMj2|5w4BKR+Z^Y^l~pA*DTasw6Xh1 zLp|!ja7Mf>-JYfMQSy5WZcoI|Y|sGI_UG`O3+7R7Dx3Mlzaam^tZ$!$`N?1*c#$wp z!R^a@?pj1+;=T&<+-X#X#dv2ky@x z?MuK?&QQi|yxniPpD&|ad-LjR8XGi*u|ADqdY`8~8_d34c1hvxVHj*x{Kjmc*_!j2h2G@fdz>VN)>hxUfzJq!#DgQ3%xe0s^+zhl6 zIu1^v&nqF^LV9imuMzicj5psW?dluD;WT>x^K=*YryoQ&>OT|VM(z;%>FtE`LvRPU z6Wj%UM4Wend%%y;e-hja?gJNMcAd3X(GI`L%Hz?T#)*^`$ywVH5Ar(lc0YbZe>{-? zbsV#P6MmBaZTKnqdJz1~^8GM&W#J*>*bRuEvk`wk2fxTa0`2i|_$B7Q!tI9ScQK#8 z55K0&Yp}nJ@ZX_r%I|OFpSTw#kH+{^9^RHzPFiC>pZtMcm2|z6WBr@6_3v+EIX%qx z5%4?EPTl;Tcpn9S01uMR$4J-X;0f>~_#^lecq+eNcsif1owObjjrLf0hI)D-d7Sq8 zIH5hx_~&${+QqZ^Coq2k)f1bTpGdx;SICdtJ)eIP-IJ)E#NF`l=a|nINYnGEU(ElJ z`q(D?1^r9lWzzFXUhCs|5Nx|{;BZy{L|re%C0LbgCXIK{4?QA@+%(6 zTZH#EcnAE0u>OhL@x=Kq-}k`#;9r){XT!hq&xZfxp9>$5$6RvY2ZF!v&yL!VEN$NU8F4(Xr4w+_q% zvp_v=PXx0;1DFHmf_Y#*SO87}CxeCH6mTjy4K#w&K@(^OEua;&fp)M6ECwCm46vk3 zdm68WrKmf}ujbJ%)ZL&5%qM?m@@=OLF5}w^MzNDo!7j&g)O}zDX;%MUiT_dTaFheM zWt5N4HLJ?hK3`|-cpdZC&3rX(*8q)ygXujV$IV%k^(V^K!(UyGyY=Qy^UK+|I|rN# zJ_*hP=YtEtDC`xE+Qp|x#|(UG9eE-3cM(_%K25lvDSIP)mN+ghdy{a*uX~F$tq-5W zE}y4u#Be@e_I9|W?47U=`x8DKZ1vg>;J#aJF0B!-dg4@9N z!4Jy*5pKub55XN}Z-qN4H_yvU;jXg3g&$#lH@FAB2!FPKfo>?QOsgIB;`!K>gk z@Hg!L&iD1QciH_;=M;^n8kc7I$r$l_tF0g{2TlS zd;tE7z1sGNF%O@|t@!B^ZNE{xboSwJH=j}Am&tq3H;ldI$RL!Lg&fF(GEfdGKqcr0 z`h#tN+Sq{d_f0lJblJ9;Z3hN|?ZF_h0~lP+Sj%?^*a?V#uruFXz)-L&*bVFs_5kxu zzTsc=Io4qK-3#muhGDl)x#vlA{&39N*$kB*p{9$9P>H!eL-E7|t%H`sogBg~1|jZ=J# zt8aiwmRI)pNKX}Z)u0AU0mJ#LEl-kp=I^~Qm3o+lJFRWKud?rl{&;W#m;vfy**!p= z&m>*5FssMxL@*nyAnpdt=770i9+;2$STF`G0OC`fMEoa%h1j10)CNz*?ljN{)>1mB zm+u&w$_Iz$@`0fRvsR$-xJ`8i7JjpiPti z-Un7-w-T%ZtHB!VKMu|Up8#j$<{WS?{y)k0Jlvd*8oDgGfbXYpb0N5h^gT|#*P?!m z@2B~Gro1+Mwmg^gXJj<}~62iSF&57Z+Y@X(sBiMSC;ont|Hzq(f_`T{wtWPpIpuNtHkp) zzVM@xo7fTGK2#=OCrtJ2^?a`Z-vHOjPck4}S3V|u6MPF?0zLO_{9ljV)zC?!*iEm% zpX&N-#)lj5cP})s#;==l0|?v0y%F>8fbW)XliWm_t|N@^ff=OfX2RM4ZUMKJOCCe~ zkm2FB@@*63tw@`v{rkB80qWcN{t(;&?gXQ7dsq2(l!e?5jNk1^o%G(9h7-^C%Lg$+ zd0IVv@FVemH+YE>zo&c$;tS!&__-I{2ee+gAA7CmAKnqkR7W9z*>&-zSLkN$^MTC-4+zPhD?oc^f5qLa;5G0!@OSV!c%yt5^QU~giQBg@dmFq1{sI1p z*}LF9)bE3Tfq#Slln+g&B5CnK`S!_wsoOX6+a(`TU;o1{R3N)iLGJ*02-lnOUxvCI zRDeqC`(fT6^)_Gt*cNOD27>Le8w7T!7@Q2Q*fH6$qJJ`^VnDJJ{&ohtU?=ItUBPZ( zcd!T86YK@{2E)KUU^v(p><9J-2Y>^?2yhS>2}Xf~!6D#Ka2Oa3#(=S492gG{2S*V1 zkzhgvvT~&5Xz;O$A;~cnJ156h?2;UZej;|0Dt1jIV>KB+RiGL_s+StnQ$Q_v6zLP% zb(o5KwYO<}XCRFtv+0C$JU9W&sL&1yvKgpnf?1#*oQV6`6?-NP6?-Lz;$LCR!E7#= zhy8r80GtFgC!WlAA$F&LQ^9HI8^P(I2{eNi&J8l+%#i%>L8K{@=T?#ru7w85( z;7qU#^n&G}53IobO1`W3t_Ews$1C-6-wn5&GQ%^wLZm(o2N zE8h*DtT-~9S1~Z0U$IYeLB)XZsfyvrg@mWM{35=h_vBad5c|+FTatZ~wG~5?PviD8 z6}u;&tw@qLkvO`TFg^!9PrR34w+>tiE(2cxmsiXWUnD%uUsvGnO6u{d3avpU`?g#1 zCER?uVw><4aCJpGM^6l2t=KR5T7}k91H;!VUJC0g_D`;<*d=^}y1SP8xvt`XM0M~@ z^879EZE!uf0o(|_1HKDx0^h4RG2D#%4HX9_w^WQsZY9p!Dh^7%UokTI0rt0pAA&o; zo!~C;BOqCnyZPP&ehgkBzI!Rt`@sF+PRi#2zCQuOvHK})@Ai;BoK-coO^( z{0Te-o(9i=XTfve`HE4AXhpT-KcjyEyhvUA1-wLfHg;q73ivB#ukw8j{0;maybj)| zI5;^1$tp=#CDg^6_V>=^z< zcpGr{Z_NJ#KET~YeE$nR1pfm{?^Be$==MJG%`lfn=20u z{VE59{*`K5xv&jh2Y|Z>XIuPk2L|G2doZX{eSB21L*=2#o=^~bCX#_290peo4m*M& zxZMft40ZuS@v|$~4eSo~0DA(_1bgw_8w>;cfZ+uR{Z z#a7F7G~?XX%9}Bku+}2kw&Paa9GZ+Iu2JA%a0oaQ9EQKqKx^nRq-!h~SGju_PnjKF zIW`&ck>SA`sT`LaiT??e9nE3{P4wwt(f%#woI0>9gxC_B4;8buLXe14%1C?be57mVJTKdYd ze49ZFep@Rienh>A&V^r4d0f(7IVo909V`YNm6MY*Dn}+uz|zX9q?53^KsV?CXI54l z9leZn^n&G;HR!98KGZ9~s4xXOa0+!Wh4FxO0r_8r`RdATpwIfV7sL91w0#_$Rar}0 zuEkj`ZskTY+ssv@<81t#1J13SnhZvIGJsMyQ<6_&e;zm=e-{wXrz)o<7oxrhv$dr4 z)1Zqn=`-j*3ofplPQPM4Lj8Gg30Ma%#m{Ax$0vJlwJnSZ7gkCh`vmHCM)Cz4L)h!c z+6S})>Mw#T2uph&SMt3I+(sRJiFAA!^;f{v;HyAwR%7|sP!C69^6Qm#$v#_+e^gkH z`)j~AEKk%m-|O)EP4F$uzYVSjH(-7v_zw6kxQR4m&Nx%;bK;mMp=w_?u67 z4^-AO+SXG#_0*+kh7*&YV5fQdr-b)l9m`rw zGGHC#S+vg}q;UsswT|G)SzZQ)M~MG-g#CN)C}w{Ek5$fN4ZF3m2_6@2p8!u*&QFGY zgu5%5)Y82Ek-Yv19A7y;JXI-rX#wq1?aIeVjZIHu|BT9nGJ2W(sH~qw{~Yd~ryTwa zBwL~OsJZa+O0`8v7oS8qoJ2VwmyVkk!C$~jr14cGkGCNX>0bt~fWLxQ!E4}e;P1ro zI(VaUA^mqD!7Q|}cEs#t-h)Z+A*c@puTnpUVKy4|J*?x#@Er@rf$`vQa0ECKOaMoLqru0( zG2qyKrza1Qex?05FcG}WL_3ME%4srC-l{+~r~y+zEtm?Xf$89QZ~~YC>cC7e3)F)X z!R&sgB@KM%fVp5Em=6|!lfcPfAvgt`3Qhx!;B?Rgnn4R_1#O@mECP!`2RH*P0ZTzA z=mOoK2b>9(fnKm2^nn#%C0GSkgEipe;4JV7a5gvxoC`h)&I9Lz3&5wqh2SEv7JM3f z27DG=3_b@w4=w@gz@^|a@C9%=_#(IhTnVlMUjknSUjbKxuY#|EuY>jA8t@HpEw~PR z6MPGN8(a@=05^i~fbW8v!1ut-U<0@X+zM_3-v>Vcw}T&oJHVabF7P98H@FA<7~Bi) z1NVamz)!$W!Gqvuw7G})ZUjFEzW~1kzXHDozX87m4}(X*@4)ZDqu>wVF(7`%<9we0 zPl7*!KY^#f)8HBKEO-t)5B>~Z055{SfS16_;1zH`Vf~fwtKc>8H}H4xI(P%T3El#4 zgLl9`z(2ve;63m@_!sy$_z(C1{1<%KuL)X=@dNcja5{ZY^YE*jju;APnKn*cT;7<3 z{--Ai$bmd41LdFsRDyn>KiCEg0NaA?z(BA)7zB3c-;@mQUlw-kud$_tv89DLS}cws z{l)ukB|Tb2x0=~bxKnzb&9_0Tv|-+6<~w8lEd6+w{sY6%{_Xf_$FAM%cEwKX#Aox1 z%zQWWyIa`sY0TeZsOI)W=6m+<$lUCOes9t?jPLXLGlV41;@2@>k<{G|lCrc9X zR`x@^KR5s!2u6T|`g{4kQ?WF;0RKkPYZn1tPAP=$Flr~y+zEtpEWrtzH)jz@h0n1Q+u zJi@#&6MwS^qaK_HW}|PQT<3tfU>=wc7J!q$$zUOFPQmS||JT@gKvz+$0eE*w0=MnG zCKN$L2t>-I69EyVNGCLrA}U3iQbP;9_ufHz?=6HPMWhQNAWcO&1Og&e6h%ee_s`s$ z1mV5&a?Y38o$}8=v$M0)FXnyP%}tQml>27T96p8?xV3b&HCy4$Wuw3P3FfEV zx5aD+?YZuNE*+s0eq*`sjC~jA3f-VP#6b_}3BB;s8~Q+B=m-4?KY;K9xgLa{!7v1d zVmAzi!w47&qwqT#w=po5`*E0=(d}gzPxuKA_F-c`2`1w<1-q#*4W`2k!hZ%cai4|z zY?uRcVIItf1&(&+Ld-?5m@rEm9eAgtEomD|$-`x^9PTr=w2tR0?UWVxU5VdSuo~9D zT383`As#kB0^~3|nj0PY%uNoLvDwkd+~Sb&pIFLmEbl@r??SBkxx;I0MW)DZYjvg_ z(;2tU%59q?*4&O;bH)gyeEQi{l;#=Ty3!x(ivO6;Hyy4-MdG$U0HsU!E&rVlr663T96@&D!+Vm_kW{O%ZP z{(+v4c^-c{2BFh1+VgxLkn=Y@fq!scZ*?%AI!e%B9B!IUy%)oBG6(BqS38p&p^%t% z1bI1vyc{yd&g6IA#Q-$1MY#W@nlQXrZ8k-j|*cIho*P=SVX%etgcc z=I?1nnOTVY72?e5l>MsX%@RPXhHx(9RnizmoO-TIj}5E+gY*}&A?vkdnS3j_Zo#}} z4$MgO&I!373cX&3+}P)Vyv}iEKBvsr>p3aEQ~D<|1|s`%Y4Z}MfOEWA5DGzIr)?B* zPB4q2OEKJJJ~u#K`RF54)FeR=Yz0=xqi@ly#ZLlvm%j4-M>C-EHVV-e>Ko_a4-T2G9_r?Wft+R~T&N+)RJkwlYjWOF3u7h(nGUUFp(a||4 z)%Dzfzu;cadB_!i^ONm65uQ;C74HJt5wYCMddAL}T|oBEcE#*Q{62QohOs-gyR)hp z2R)!C^n%{bg}5z5_l3lfGTzlko&!51(*dpN7q~G#OMD6Jpez8iwx9FUfE3MR-nB1! z^n?EJ8+ktfom!E1vW96Oc7tFr41u9A4Ah!q(lDIs5g`5Qk(i@kG`fv}u`mwC!vvTJ zlVCE)9>Xb^KVnYh*-eA#gv}VT#GK)jHBn0$4_%7hOJAV(f8SH^88SpSy{90WvZeEE zCUHq!Ju7@NB2nH%oW_P;C==7NyfskB-WmN{i!XSliCxzfzdu2*_T$dLDZr5TN%737~pT-c}bdNpSW%8NB0Bp6>bNetIb17C)VQd9r#-18RH$)Co;}{nEUO>{l>Y5XCZsh zME^u`bd7lgH>nSfVjd&@LHPTY>u`4b9(S%YPrym?y=U61tQ?hbEz3AX9-fBp;Cpmj zPxv1Qa|V8dv(ELrQz6DV+~Uo6^Sm?OyZ{%S8_Y}21hWjgf@yID@>$kEN*?R-a~YYk z2J;GGuR$_9^+Sf8{c3y8G@3$bsHZmYz+5{OH^kM~%naOpD5p@0D zyo?S{$=}SF;~1M1e_8PV3dsCMI{aNtBYu*Mtb~6R`)u$Uh~Mn4&nauczUEf*rT;qS zC~Y)ZBV5AFfsT<%@02m_ihOT!B0m@M*0NhX9WVlc{#(o_mwf+a{jkKTzc;HK+o_Xe zNBMUA==FT!_PT2aZ86zpz5{y~r7c+3Ge3s>JSq=#*~p6?`IKzxU9R)HzAzb~ChQl4 zjWi12_c8BxL3BOOJ0|s>%##$tudG#xH+PcHGNvx|h|Z6~B+P zA~2(s+(_dM%;Lx@q2#fq4D(IgB@S8l-p$-a8&1MAm&^N73b(fca!O-Y2Hu9UP|mem z)y0wr@#H`e*B;(Y3CsRpr~uL?@ZN$k4C2 z7C9pGU_e*;(dZ+4t0ceXFy>kjopt+~xi;nZP{6NF>oT5eJavnFH!BmiiYi-|jH=4- z*U36$#a}gLcla4Qze>|Lf%NM3L0#IG)rnWH@jjBQo20h}@?(gnCOn&ml8&R)zmld% z<0f^Sk95|;Uu~!Zb>Us82k$|BXb^ZN(r?r0JjSy~$=~zlx0KbCd#RTis&pJ@Ogor= zti?ml``9;z50LvId<0FPDKvxT@G-Q2me2}XLmStLKpeWvZl}C{qVzhc+L1Eul5%zy z>p}?AHlX);^VI+0=k)*KM@~ryrbmA#2blW3k@fw4PEU~XTrrKf>HTA}XRIB%$o{eF zAmz6`>GyNWLRwZ%PU60UDi4%j>^t#XWR5@9b%s0^cm3Y^X#s^jb1!}+pT+-K{HES( zat=M@l!kM>=PC6=XZ-7PSvXmYyy-&NarVeL8ohr2GBBXB#)Sj$M_9>w=%H1=a)EcWAo6BTH4 zF^@?ePjFo{C%P`1lUx_f$*wDWf3FbN72>*LPEm1P4an1VaIij@yD|ymp6g{t)GqC#%owQzjyT~Ki*E$osSuh*sz+Cj2ho1A1w*VGm zw+I$zsVmDsI<)vyLyv)g7Z{@1~JS6$M=-XiN7<)N+_i_W>x-OouG zp~f{co_ID8CIL1Quiv;#nZHaPTvmCoi8O2`kG8<)fimegsu=prC~2=H1@h;Hxz#0e z_%a`98{0_hcKl0!X$R&Pfw1$4XD3KsTjtKbehr7=8#n?-;h5_dZyam2ReI@*;cEb=L98S&ZKR2io85cTKtr5>Ep`z zM$%W4e%p7*{$8c!A#T5zKe&D|&%lpx7PoV7-t{ZbM9(!|;Qk_9g3GSolss`0xw0o# z?}wJWdGtTrksPql(C?pTqmOIHIOss4pM%v6PN7SPQtu_{V+5R25BvKP2k(fVQ&eP))&uNh z+{5NX6`3n6s@8qWcm#96Ax}sP^AfHn#zW%McI?4Y`SuSv$y_J$9&t}glYJ%Pk2wo? z(|W3O`vdup;ZKnBO8&z98=esEA9zZ2lMXo?rec_2K^iyhQV4}GNb5Gyn0sK2u`HgZ zu1RC5`B45jK-T;?2`l;NB8;4O=f?Da)O+%b_NECzj?1v!F2m~%wPd_(B~i z5LvQ6-iP@TVZ$K;WIt$n%nY!Xg{kr_OFa%S6!(m{$!g0?ZuQNkHNluPI%S3|;O7(> z3HJ*AWDjCi%vXtLC*wmYV`mOa)=_7}|7*Bsha3x9V-c_6RbCT_8pwI9r9prarA{FpM1Q$X%Hsm4@(e91Bj zVqXY!ou3nN^mETlzUq3cIQ~kguvvJPq8IZmDxVo!L+2>;XiDE++Rcrmp5^V! zgOCDdC8HGK-h$SQFlo72m08;TD&>lLg79xcS^Sj4EDsgn9qubaC8!KlpsG8YdM>4T zmet&^p*QUat{Y*lx3ZIG%x8071Eg)y0}I)Y+A}N%c|)55J;lBl#NelY%^sMcUQg*UFrx+*w$oo+T{pz}NBa`+a*Y%L~9y02KU!4M!{0^yh zaRYZ=(rU3+OzKd*|ED408o~R}*qz7v06v6|xNibY-IdwTquV?6FqUHTzL zXX5IDzpj|wFuOw>*F7+MV)nx9?T#_~VD^Q6&>sfiHW2)r5GZH5iabAgJ&61njJxcA z>g=p+453~a9ro0a@-Wn0R+$l;m?Y<&4QE_(3i`=QyU^4eph7oBF{_8HeRVHVf3VGhj2?>zE-J}iKR?wZCT zcbHjT^`G-ui?RE_UEWv%OR-<(u4*oK%Y0HrV}(1vwUY3wkh2=rh%U?%Pod69qTIy= z@?$M->xg$f#6wn6y#c!f*a))!K+-OI5H@kY89!T)`#Ed{$b=^?tAh?yBD-%OzH>R&$tU&%!sJ=k?uFr&!eyR zBW`Em95g14(#EwoElbKsV%koWr$W|w+%ACZO}~hF2`;;fnOEF}t*h=Ly#F!|#u{cw zqHN1qQPTfPbQe|cs<`c@3|>RG>+qvh4EsnU$sKKFz+aUxzU$b>a2l6?~xvK>FMjGYt8$8!T`u3vF19wUO%gJ6P1Ab)RjxMjV ze>8?Kc?73|t#%Z(ejzOn(RqfmnDs01wL%w>A?LmP#=YqAC{_Hjhvj$dWZdbGz&r5R zUCMe1dpU>ZPyF75zu<3p0{_5MU=g!nf(2d9WzTdQkp0t@xvxT)svzs{s&QW(YCsIs zgj!G=>Oft17wW-#P#+pVLudr=Lt|up03UiP8y`Uv?3zL|PkE!cNA|9B#xiCLPo&Wj zT6ts~PWHB6Gg^~BZJ?yATfnSGz49qx+d>Tbw!>@>9gy46<1#vV)-uDlmN$DXb^lvd ztmjRuv!}Rq(;3_+UB>F-DPwi@*hV+#?wMo6d72wNpeJ$n^1N;J#+0%ABD8^JEI%Xh zWTsu-hj{vuj*Zkj{V?SlB+s)y*8^Z641#>vOF0~jzC&Orv__+0n8RTNjAXOcdTT*o zZ?kQT^1NYnUrE^T;_x<*f0A4Mc6jE~=WJ?x z!Szn~5_Z9E*aLfEpNgCP-N-ti^pSZg);n-7`<@Tt=a8qos-J`XJbEI`Q_43}PB9F& zrNmagDYDN|>O*<9e7^{P*z*oGDc>-zk9aELT9JCTqWX4>KyR7zJW71h_B-aO#CM_+ z?v-%2jZ=ItN1%^(tBh2>d&oIXKAgZj>8WD*j4JrAg8vG9$KJ6{k&e84`A#GAJ0+K~ zkkE9-_k{TYH#uYTj7QD^smy6mvM#MEVbR%B4c)3CtD1`YEPl@g!k_n4#cy?$j=Y4~ z8Ct`7g%cX(lt$4ro-h{zelMbzP7`Y^0yZxQW}0(CWk;V|i#1{zMtRh5gTP8-LP9yn}fc?!kThA4Apy%wKqJ4?Q*2 zcXtqR|4NuC=q`H%VpSTh@a0IE-)f=8&FRY$_C9m&kMRFH`~i=V`6pZ?j%W4byXBFy zLM(m0s+)BegnxqHf1o#=={Am+BKqY0y-=qT{}bTJ>xg1qqGqRBA($Uqdaqg zqSuVj_f*<-nu2<E$|j)}fR&pG|wfZWKt1#QXd+ zl5_t;ZI=;lzfY_pJLoT%Zk^Vhp^a1NFJtA;%F@qM(!_ie4b2F2k@HmaSt@d7ik#E+ z!3*_^(MuaG}kLG+Y7(|%f_nU*8p+E;A}*GlP@3BTFkHQ21mjxOI>h+`-xgRV7N z2juJWCG~k%;>nKx9LYMgL4Pg(hKiRqor>cV%CWYe60pxnxLnAVcSy$()bUfxtNiMC zg8rlMug_Da9Z0y`+)EwS7kSTyZ_BfgxPp4J4-Y+MZ$uuYOS?ci{w*(cKDDRbu#LRL zlMno$`^Wjr{B{Q`ld2~>S_SNmRzce~3gKRuccBOr<-Qn1+wHA4FpEP8C<$-covc#u z7L?|`jGfPX+m5x$l7Ho(yxrNVfXpa$I&MeGipzM1cc&tD(Q2Jl38NDJD%)MGDs~sM zs@=h?Mx5251_am8#c*8{s`0HHPCKs_*R`Pz)P;ERT-Ig2%XK}wtM#7U&8lyAw;G^R zG{~B|hM0|PS#!l2S#)S@$64X(6y7-WlDP|6qsV+1GCl<9Q`qnk*G-@)G=t_K{rrzH z3uF2@wYP=clc&*>_n{}U;;fd)Ze{o4o7M}vUf9VRXxnH_T92Y<8^V6V^``+@ZSCGx zJ7|wy9c($}w-4d^BX zYX>_cw~*4az14+qUG4H_H(S<<>a;|1lCWEyCfpDC{TUCE{;#~lIv?cSI86IC0dom$ z8a=->K)u`D(IF0cfF9!-sJ_WkwtG^hdqHp9^ct=~DvTcQmv)psGyIl082dr0{ZWlL z`VdC@8A4vL-ib8!gZ?nU9*RG|u}95?52LRfVGKmRF3-bN+|uuncG~O6krO6c&>k6N zk5F+BR(8Q}fP@=K7@03(EgESaLL5UuXRI|2 zf07S%&}SaHOL=2;zcHRTBGGFCW;V?1XAbk`c@9d0#-mN)xISkMw5Qon9h3FQ+>B}x)Wj1a) zKR+YfFyzbxsk24r30%*D+4e-r+(i6ORI=ufrlYK7mG8z}uICYUz6uxgFa4oOJi|$Z znM9bNJegz4%XpE$&{sF8*vJ< zryzR@vV+e}&Tg4Xnx|N+sDEW_Wi{c}AZsmTa82X6ikr4yXHTar1pVqYL+kM`eM1=& zUyZ+bdxnZ<74{oI&cc?nbz?YzTFcUNJPCF*WuI|5{FI_yx(S^-nlouT%E_uTRi14o zZCl`T@@p(@pTg)T<#sD!wk7MxH;XpgL#s6Pyw+hm?mIxn<-Wj_b#fBVPLTJeEBzcf zC+bV?cfoGh1AAc~?1uyJ6&!>^=5|6U>vq*F-F52p`BzLffn@JkJ_`XV|I4V zTgz^Ii+LPQz)3g-r{O#J9)3XH8Tb*-!Z|n(7vLgklD6L^xD3+oP7hbOzKUBST!ZV7 z1UIn12~$b;Pu$<)`e%?a&fDDIfx966(|hFaeR#nAFZhvm-b2h^x&I9w!SCerAMhB; z8Vig+N%vp2^jA6e4!_c8euBS$xPFSs;yJ?v3(`OcghH5?b}^&_mj4@0>|Edmk5_)* zgt$)J9E1I(=<+qqDqYB@lJl?sy7o?YQw&42lyy%n9TZ}p( z_>RilC1dHZ5B$hHP8=uHdiHtNPDAFx=Ue%`?5amzLDC@agRBjg^@xSMvX;D&I;Ti} zE2%K>FYKzE`gskoBdF zy-U>h=>y#4x3R)Gao!EA{G(Gg?8Mx7eA!68*RD{*mcBjCy3?x0H@yT?c6xoR_?H@2^kQ8LX%8p9(ZnZnXk+l_XT-q6ssCfWi>-0kr|7cW8c&)R z*-Nbn*iXdoB$(`7Wliy}wWfO4Sw+?9{_BWyH)naR=M7IXrg^2l3XUuCE>X8gJMSUm z=hCK`fexR+Oqd0;VGeSojlh|jkOo2^6v7}aqyq;yeJ;c0+e%(-Wcw~uwy(2!?i`&ds8K6nXQ^FDS9*=B|Nwo?wa6LvdcrEkMr6k#IyKAkq+534Xg5mz{o?8BafPS=Xke%I^I*zZN(_Q-!`OGLkqr2DzWiTd$tVrLX zKzJ?lKHtuJJQtbo@$(BL|IWu0_rrlSQzipp$Hjd6RGsk@;Mn%zEjW12$q>B-)ZU%c}}$ZR2lj% z`Ch)atj$f%i`=AF#tCHni*>lk_F^Y(_B^V*%P+EMd3gi=)z}wqlJ5=G?DYR~KbstX zKBdpOQ`JuzRr*Q=pb?mKtU+vyOgYVuwSj`k}e0zEAw@f zqr!wM0yjuaQOsfxt>V9u9N%8z@bfD%`uzw#Puk8Zf3{ijo4vu`b^P~tO~&}&KxT1d z%D1QlW=Rlz--KWpDaG|$*q5e$DFbgqSttkPp#pC2Kt=pj;$G&{$Ag^x8%bST8M`V_ z6{9}UiVPDI;_^ZbP zy<&We-op(3RzZDqQ0tlL;}xQ=mv2Z6)p7%04!*}Z_+sT?tV`QBM2|-BzE6*n%DI1y zeb>p$>%@JXxRb08sHbz%R{zjia5IDYl|}Z{ zs&D7B?oE9+X=lksQX9Y8tPhWhfA!i9^GMfATGCn4K_tNArTlg9pEqzhw z9)-xLfLzvCsJLZJN!Hj%TaK|pUq99a%P)2GQ~qS!FgN;TWWRJrc75mP7lGRNZc$=x zk&auWL&i?!8~-!;{WEqytNf1O*L{L}*FHgyPoXWeBeM3;0j8pFNA#CEsT23HzB|ah zgN!@Ka51;X7@|*p#q74#g*dzVZd=_jyZi21nUl+;)?eBkameVwv+D`HKz=hQ_`8<9 zx$gsgp$mPoe#F}!20%kf*g&7G!%8v+;XW9KKv^|X!MX{qMeZ<&gW)g&M*8m47nQyk zb79Dl`K-~{kAbl;4#vX-mL nf$YyhY={8|m*5WI6|IdGx(G7)|Ts;)en%CUk zOmV(p_S9LkZQ__t?Ywrphkue7@&hoD%i_ z?OD6owXKoRa7Fz;JDF`SSW*AqUNo71I~s`;SJeOWm*a4xjQam@)Y{P2xsll6iu(W1 z%C^|KMg70nHMHgH*GNU+iu(VSmv=Fh5cU6J(#E!_OCuGFE9(E-MhDxf)TsY&E6Ep6 zxs6m6Tv7io9=&XhnneA7Ye>1c%V?zP;fnfyaqDWRYZvwZp_b_I_dLZr8S&^A&ta7F!p`_O5&t%&-6wvyU@@fxXVxT5~Q{pvZWWl{eRlFE*i)=168 z74`oeOZQR#-?8)^)LQg?j-&n`B%$p$E$aWB#E_5b0gv!SkWBQeAk_5Y!kZgKOA`hRijXKU21kqW^T_5ZCQ@!}~X z>i=Q7IBP_2?7Z?-VcFYJdLy+3SJeMmu`|k++4t<;AG>0we(UrRNlw~ML)8C66dSvG zr&S|9J*tngV<9NpOL8MM1XtAmw->Eu*N&+FXD6qDv1-dM8yKOea~r94xT5}_tEv*E z%gV0UxxeI`5vy_~EduXjVtQ^B#fLxY*^}@ zn;G^0oRz)Z5j0YZa7F!pyHoFg4T}2zfa2L9G8?ItxT5~QL#W(oWYqs1QS7icjnr~n zQUBku%pmIjJC?qKT8qBVan%2VB((jeMg4#K)or#dYa~QmQUA|YYTE~1)c>~+Jr>f6 zMk2-)_5VU7Is9m&{y+S5HqnDAQX#ma{=YROUOZ() z{l9p0v8`&-NCo4H`v11l!nP?l>i^qD?!{DUBUJ`h)c=b~Gh4ncQUBlaGA{NK8mWS~ zqW)j(+8WyWMg4zhbE*$;CJ7z`U~XV+Fz3y79N&D?-YVX$q|!QfH20h+YzYbU5!2*PwQth=S+{QJr+~4Yo0@sIA0@Hw zdyFg|w&|f8=PKSl{*+d-AvW2nq+HlexpkEF6~?ZxtVv~LGxRS`FkfS>-RSSR3inL9 z`=E7z@~d=fCHW8Gj--D&peEz(czLaiRmFkMo+QJCy4|G47 zVqUM55T`iK;~J)8+s4WR_z0d8t$lmn)r@C`oh<7hGmYUh=q*@KdbVi|%?p~Av~_2T z9)g!?8(lL`t_wlVD!EX4vk+dFt)fJ%XWm8kEN62)lhYvo3#|(+`zXCG3VPr>^b_>D zI`}dz_Zg1AWPQQ14>ITlAM_hPs_DU(>ELK@v#zo1!~FEX2mN}i%L-qnrzJ7Xq2R`T-l>_)Py|P|nqUdE^nA@MEieY{B=Y+?&>))EC`gP)M9ov^4=UYE!JLfeo z?rVyI7Q@sU7}HbOn`Xb<2lv*st~rWgz#IeSm_iNk81ROp6p#$e&(()9D|^y8s$D1} zb>x{om>!8qZa-gPp~RZO#^4qoh8U1uL0T9=Pu`=H$8fv`$%pR&Ck@!j6GjCRo<5tT zb$qvdgEt~sGxC`OCu_WSm2YL8YaiN2{%=!l;)P3Sj>mT3K8NZ{tkM#n6nV8YGw?f= zYyz+T9!S??k6g^YyN2)gy6>~%IQ(EXlnRY)9`oBi$%!jM?`11bo7e?|9P zE?)@lJ=&KUYkLVFzpa@5yTI`R%Gj}Dx8HdCkXHt*m$8%px&4xqklA;^$Fg8Q@ZVwf zV^8qgPdYuY|H~}XTT5MTlIjM&TgrayxPI^JFMUUapm#HNQ}fNn;}U??9Y$anmAq<{s`r4F?*7I1m3cD+`CO7jXkkpV7#@Z^q7jCE3T3}E09|@dhDYT@V@*Wx)18-Gim2$bLX_Ci<>*G zPEU>Uz3uNG?U42_bLUc$sK!zbg6GEjywYP-%X+!$1M&3u)0H1Xto#ho0pW=NzJe|@*w_9^jgPoqJ(P)cwI9$u)0*pRZOsevuN3i{JB|Jf zVL-i6Pt+SlpgoxGo6WnR-sLp_v< zy?r-P8p|7NQj8N6rq|UsEo$y;vee%RM;wvQd5VRmo9e& z1}%)y8)@BMe?BO;Y~0@bTKvZ9C;HCl|86|IB={frBVfAeA9?>P%5ROobq4&GAO+EY z9Ra#rpIUSBw+Hn@ zIf(sb=+xjeTAio0_vA#<*8M^~k$n=UF-+a^e!A{rJcbGH7`hH2cm?Be{r9Fib3Hkq z+j0QapX=T*JumK?o$Am68H2mecTkwslz6jG2P!X3vv zKH#!``ob8hW0Kd|s{e6pWz98fme6`zX5js^mc@SJygH_jS@eoep_Nb+Ci6@_`K9QR z{In=DNQZvR!Jx!5{&$Ll;SZJ{RNKL53dzTq2<`YLF;jKu3*rj71?6Rz#^YUn2R8DV zJ0@pG;bKw0af0)_k#`=>^G4ob;5p+J)%s9LPo;Owh)bAvKv$gez zauH8TyfNTfrd{n{v*r5wpC@R+)_LB{d0xNEog^`ru4~}k=a1G|^xkIg8vX86`JX9| zD!bzKes6%iy8ZmJ-&ZQX?_#-mI?aSR)tlzMx2!Wyess!x2PGX_zW1QyLuXK$%U|DN zp2xhr-!1rggUVlqP7O;7O9wB+!FWHZ*su1Y$!U4ESXS5Mbj?ai1C<-=ao+Je9k}|T z&Zl}Ut8#U4-Cc@o?p!@v^!<(7!?o_U4d8i0?|ewE`gz0h)Ua7flI_BL-t=pJBd7qD zLy#;?KH0+HCvhA#_tJZDyy;v1-hN_(`liOVZ&&`ne7EnB|CX-|m4JB){N)(Z2-hL? zhW_SZ%T^*;!{ysl2>cC~=hYbJlY6(|hrIdDGe@2=&ND~e+lS3{@XR^N%5QqZb+7`S zIm?UenX@$BUTbM(af#qYrvc@(2=Io%9l zX<_Nv(epFs63;VdF+Fo09N?LA>h86xmmJ%$dMKYcQq$;}W2$TjV7qai@&k5@j*I1) z1AmEUPRpBH_n8BK!y*oP=D-hm^POjoJY$?^j_kFt)9a=#THJlHnVcIO@XX1L zLGfX;+_&XCr!a}Ycbon3x!{67Vy4~J^r%aIv8TUp zC(oQ9IY3WAx(dSIdTA_wj}EqY!H(Y+KMlW)g0a^ZFyiTT)94KipgiL&{I>c2EYFg* z7@7T!|7?h2_1iZuzC8Xv_a$oZ;>^Fs*m|nsKU}|LPx8^ZIRbtH0fGnu7i9 zVP1ZI%ph-A? zpBP`A`}>d)R9u+Mv#;}jm~qu20|jLU>DoXY1hm(0AMZoZj^Xk?WY!t9>z#CCPx!X_ zCZeD2S=0968V*z2teF!VD$j0hzNqq~C2ehCSOceDN!g}VjXhF-Yf|-={w8kayE;zE zH*#sQlhp5n?Zr8>&nY^alV9}uVkOa_e6ASRZLEB=;;=Jrc-^m((i}XYO9-9A&!Ibx zp0aLvz(aZIH+6d*enPjb!cPZ0&`lCLho3{IZ%|L*fevzZ_z9h5eTc@JF}3bmNol|X z9nc(plCZ48rK_Se=#Rf8V0lhDThI zMBhWm=x^nsKeOLKZYCep3*~XVOg0DZ%j3*gQdtUl8v26B#j+>sJ=lm76E&PT!M``# zrSRWzyOUH`&{asi?PCJy``Q{b^%J!!uH58@{j4(}KHG`I-s&&Yril+?JR5oMMrmmo zeM2_V`fSBGbt!^D|3F?ri24g8e)B~QRAF2YTG6CIFVF+@l0H`SD|%KHPTp5=_R8AakCk@>WLI*Yc zW<6!G9h)I$O(iZPs18~$x6(Oa#T=j)=mC03P11USzEm4nFV?wh(Vm*x&&J2?r(v&N zo)LsFy&xZaBo5TedaKN0S?3ah_{S8Na%;T9C|9ly;a0hUUZ4l)1%43vQk7Bt<$Ip~ z5|3Mrw-Efs8aC-YrN$SFz1}V``b&wCFCZeV7cytJzwBvUQdUYAP{L#OVen(Y{e}BU z8U54!WlcD%qR4?>paE4c7AFa?Ny<1`fE>CPyErZdRN|ZrlL9NqSl&gPCcpm zz{4I?`M1yh*h6<7&|LG8DSuJx3PNrj@KncY;#b4;Omi>cao*9y>uREB8ipSvzRc@T zp|76XP&;wyJ$+qI{5HIwUtzN-7~Ggna^RxLSqSY(bxd=L0uTP@Y0cK)VS)$QG9)x;k>iOQe(K-bA<^nUV>Kbvye zFJ7ejrY1hJc*&ako>*V|$6H3MdFj1d`<^?tv+hIh{C4&6&z08gUDiW&h(a+);PnY# zRQBSnOSWljN9R=^S2O8il9JKX73E^-zrYALfIc#{u(1 z%mdANy*a;R8h-xlM^71E*{G;U(%eW}gxA)Vu3e}0pCL|9(c{uoO~8DPnHmAlYtZ3& z9Y`O)Q*)+=;=68EGKt(jzn{u0WMRXh;uB~H~re|w7VvysDqA|@WrI!2cVw7 z1KnmR&*3M8_2vHZfCsweLg(;v=+-OKbIKDu&?Vl`a&hSxlJ_Gh7wxmG zFRNRW2RzWtk@aVXpHoji9q>TcB64*23EjOVemdZRZll!G;pfo#3;0+bISA60S|QW0}el-t1mxI4S^s};DHX+a`;KYvI>`u-($ePU>$z* z3-9^9vq3*J`xTeV^uP!GSn*fj3wpDkfFAgupCobu-?Yd(tbE{ue!0+tFX+R{2R`Ui z;&;Ip{jEj(8RUxk10VFZln=h3hhH=4fe-q$(1S1fE!tty10VED$Nxg<4}4L+*^g0w z;Df$O+7G_y$6@scKIm77{=paYVdVoK^h@!)lX}1(gvlTHpl=cRfiL7|`UkWh_@G}Q z@&jMUFN_}epr0epY4An;!{~tz`j)Hp8K2+_decui^%s25Z4ANWG+&29Es-KIq3jp!E;FsCO7W@IhaN{wI7P_b~efKIqXt@P+_7U)+Aq2jEXL#$z;p#9a3ovZJ!H$+7k9)F^n@E$s^Zn}3MY z4YF=|Qf~eObgz zQ&$*AH@!jR)k|ftyv)Fycm)Jm8aAI64tL0Bp5d0zhAN*nJSv{VDpTs;6 z<9f_*;3vJT>ts#sOO!Zzbba}kMe;s>XvsRS#EFEAr%uhyAA%13&JLna-q4`yC+}?< z(|*`n`G9-0M?EnWE%`&lf&Q?*e4NEscEQKrAkkdh_c+;eH5hFr5dIK)fL>5M^hK_! z&$2F)=Tf3>@x#0I{qv;HI568Xq*+7-BrN8lRw`}~Odot;^Zb5G4FO&Aczrmlu zza^g2{tbQ)<2-ZTi*X-H$8+-cB)n5g={Ly0SbkH;$(M6h`P&1PFZILo2G18fZ|vvw^9Ii$tZQR^6YIqo zS93WWeni_ucpSpN9j?EVxvuouQr$k^czTYEpCwLA*Kp$O+@3d}gI}}H44_Yb%0GTi z&iBKr=HY+7DxFS^rxh(fZb8*Mh*>MeEw3U+Bo|2?) z=mC03&lLTNo^^Fb*IV(E*11(1UEjYoU z4?#1Fuhz?dZACdCR{A-vX>j*$q4*7RZzQu z%BQ=cj9JDJ}-t-q6*YJ4Yi%pl*%e7musw&UV^`Zho5ekb$l zUK@Llc)uX!SIbzLk-nq5!@70}kC%*sN)!%xdwC*^&C+-tc1Z*=2 zjCg%?hAy&A^@k;TK6nK=>XMRsOWqNW?VS%MZ2i6&ar$F2E}a`NpYt-8dpxf}hv#+b zzk}#6Gvy7htTC%C>vHNJJ$-b=b^av;bGrV`0$!I#f8qFdVz)?V82S8r z;p#WT`F-S!`ja(3%WB09Pu^$;3|o0Nf&1z}dNDbfa5iOZO&snoHpvUmr{7ZHBF+J; zL8dt@L+V-mJ}|TL)FPVan}OxS$9ICRiG;)Q@veiLG(!QKI^cTr5Mzv2Fn+;za2VHM z+=6|#Sl7ll##=YGvu-_C<^>eJrFdV;#9-tRA82X|y%4*AMAwOoX(RqM%f8o8h8|m`) z2k-cWX)u2A^%ow$@b9EPbKqp&S9Pjp&bD1vMjYA7vk5%kV80*MUrbIWoJ|>96Nmc? z@3X-3>3q7U|AU56gJKjn!j>L?@ZJJ%paU3`g<%|maR4h3jwFZnMP#I4QzY03^-(dO$#yI3V{QQf%;}Q?! zfZJ2V06)li$!`-azgL$ya~*}wk~uXtR$T$ij3n$AVtMgp20bt2brkf1Y*%MYPFn|t zUZ4l)1*Jn@fQvQu>LutsAzQUKn6Ni*Ti)B>G~P!EjQ3F|^zRZk>v#8$&dx}?xz_(a zDiJ$foq_h;4Tsm9b=mE`_ID0eYwZ(HI-%x;_inAeVfMbOnD44dM?Q4yG0incy`z86 z_uPZ->3#pXzTV|K9le_01N@2Tk0Y8s!ejh%h}U0;eiG3zTtR$qBwnEB-vji3P4HAnqoO-id+;i%!>Yb-KVEagp`WjL;M@DwP#tRieABvzzI=Qrd&xo1?B@z53zQ+TJP0-<6_s z+m;{o{?fhH{A5H)-N~unu72%zxAu)Vc#pc#Kj>fmhvuhh|5()%=Q>7V+e_f7-N(hv zT4bBXcKG@S5;bSs{?FdS>~nid5Bwh4wqBcJo~3V|V7!moR2x%IPq#$za?V|nbC&v5 zduY~BD&OOTobx|xW_`z^#T{L(-L35lt%VcvIeO_zDa&h5Dfw=Ext#N{Ud|I*A?N-l zHpq8Kf6=j*mhUsiqXCO)CK18>{I8@K<@2zGM=n*n`+qNnl6(Pl4xZ4Bm3lk;94i00 zOTa^UiOu@FABUgNnddG64|Gd~&f(|K=^NA&c%WN}^L~Whsitx667WEmlHXl$_&MeI z&+P#o=&GbVho8_LUEZPYP*322ZnM~f!_O(tPX|2EjfFg=Tp_cLHqL7T9_Tg-zQYfM z*1c>Nj=ca6bYtJs`g8aR-O=TKI^cnBv&>5zenK~@)K3RI(4n6={2V%c19<`ubb@9% z{3PLM=`KuHMQP}NypMq!ZXbA{`rTj9!`@7L1wHT``btd?zObKAdcg<%Bq<+!QNC$U zC?EI^z3{Uo1|8~wb~*G$`7KgD_`*(|{wNGU4}6rL7_ZwuN%(@^v}@1CKCJ%02YpJ)2Vb;5tp30U{bng2e9_Os>JNO- zS4sKci~5`W4)O=SL;qndKkx+|>S4;iO7KCShTf$fpfmd~$_GB^=LkRWb;=jNW0ntm z&~KLV!597Cd`_W!;DbIT<%2KimxrCpWwd{@{JsnLf-bE80N*M92z`DV>H+#N`%4Kv z=$Fbk0(?>bF!=)?^caVLFUp4=O!)&J^vDNa=s!sQJ%o($4%WBq7yRQM*ggDbm_Oul z!_T#GCk12I>GO0E+l%BqOnYwYTcE=_N;-%>I@gBPvQ+~2=OjJAp$F&%g+pHo-n3{c z&Hwt4C+F!h826rQqXt5h7vbD8aQjUIhktV7i_ z=G)V1m&m+P;?!d8Hxiw>`47;+PJ`($^{P=fO+0n=%}-uI zvgJVMF$4tY0q^M<=J%4b6)yXW!On3wEp}D_-=HMug>}ewYzC8)37h!a8fLw0ph}?M z@gB--{Kx2RUGY52UncE?e}g}Re@p#Y)`!H;!LMzW@hRpR@N=jJ?1J}L@q3f9veRPs z1bkPU+DY%DMr?P>_-ILP{ta~KN9jO%xcE2d z0eXRbKwlc>XM}rhSO2o_)W5%K{yNU9rUp-!M&rBY(**YVH^#lLl>cS~UTcqEP7iqZ z+^*AN*O&5@ft!)wwPQZy#tbGG6E^X;HO&2d49O4uEjqVLf46Pw1^y8J5B@Omf7&0y zPnz>x%v&)I1m9iA)+W-JkHZg1{;}A>v=XTe*|#4eDdRhdQx|DCby;ry5OnBwiHig1 z&2t9&PHE6J%x=7Qkn|rt-eX!G?@1i!4-cj@2KX90c;yu|-qT#%@m_XZ2QPO^VX!?V zN!`!`^kQEs`V~Fv@{9MJ0oDs&*HJ~;j?EBLU%3yza|WOX=q3GWtrzG^x6?gWfZ<{~ zX8?NPzV%gIS>NB`dd>j!0KLEuLSMR(bgn>On;I`Tu(fh6-`_zO&KdZu*z4^Y$@vb8 zVV)l$U$POY@q_rG6#IZA?duvf@oC!EbuI1ds-S&ct7%`?9}n82ZrwfotG_|}y6%bg zb(v+}@?^l|>ag@erhl0WmqIy3Yyw}*q8uQf6)%?A+&&IgaC zGo1O#En*E(E!y1QKatJ@+;bhnchJT24i0C^i`e@$=*&9+BVV!+nd_GH z8S?|y%o4tU5*{b%cM7<_gy}_d;dzG+*>MdoZ%gN(zd#Sr3(AGQEcKn9MZTwUHRUz@ z^V2_Gdui{v4c4o-_Wo-RtD%GB!m{D{9^~FmFbAwBGwkN;_v@DR?n!*Ta^}h@BcItL z&htV2E7L5(G+A@^zy7P?^Ovu!8>}4c`tImFqho&a?9S%q!6^Ls z*~r>U!TRc<1dLlSjsXtFH`qUg@eKBJVZ9sp9PiA7I|{tMQC`kFY;~=?>)Q6cdLJ+1 z^i}eli|cgFA^b1;Kj_f^Q(q1k$Mls_|L1^p?0N&%xnY0N9yN~Xt0P((#~=MkK$euZ5WB(d6x4#4Rmr=(0sR`#+ z(w4_&KQ`wOdM?Uy>MvAe7H&fyEi@(`Si$+8RQJ1WPBce9n`$}r=9_M!HA%~2I==VP zb9xja2}BZzBoIj;l0YPZNCJ@rA_+tih$Ik6Ad)~Nfk*<81R@DU64)^%z-yJ!df|?# z`>55C1R@DU5{M)aNg$FyB!Nf*kpvX_7;N{X zb56dTi{DQbH9iP%XglMzxmILMX~00Au+Wx)L#nk>R(YY`{D7+EbH2D9jh{V-)SW=LHS%U&S?49 zz~4oH&cPEpTh0S^_z^E1_AlcYbii}U6FP^VP*qp>=~@I2bjt;H_&Id?2IT<{bSp%@ z4nLu@tPy40e$W9Abjw994nL1KlJ!x8C9Bw9iimJkTZNylRIZ3bfu#@>G*d5;9vMtDYo~OZ~6N>`k~pcxLl?O zKIp6P`w{3@A^imOzz6+iv2*Z6zY8lL_@K9i9(+M>_EVG(e9%u4dhmt+GW#9qfe-pB zu@CSCo#|gd4}8#X7I}d${6JX!fe-o>(vQFw?FSz*1LfCPf4~QQ8tsSPByN#(lOFgE zz4&49MY*B$f)DzZH|04e<)QwjKY{#!4|-ebn-;#H3zI+aL7zr@r5+A_So;MZ^y}q! zNx&EW%=AyFKkz|6NBSS?0s1ig10VEN|IzXTU&ueKeBgtAj?jZI%AX|dH1!XB(5HkR zd{M6HztDc*gMKB-ml)-S(E}g!8-)&h(SJ=pi1L9C`mr+30$-FLMh|?@Cxjk+;g`ee z4}8#LoCm(3UmhZV;Da9e0bkGu)xU?3F`mTtx2YHX<4V|FiWO00IpQUjee#6$Jl<@P-yukyWv^v8U90UiRquc9fN$dZy%nC_sAJY! zk{(o`!*^?e^znxpG#~l?cEd}47=?ty+#U}4T*ofL=fkeQ9`;=wXHa zF1_zX4fkErFo9%VBXWOPz8eG+0nKUqWHDb}#u=$s$<9fF#Qzn4y+3mgWIE6cPU zo0$3>5l+>bVa_~mLa;?IfVXwbdWlmd(C?n2d#*3~AKeB95piH1Cqmzesk+}n-}cD? z{W$(9(f1Ja!#?iA{2jc+L(~u1Hp)onX3)La)q3{5+vK!$VCV&UfL_uGtrzG^*Jg3O zWo=rEn#C-F`zqHl{3p6LNyg=HLINXtZQy+Bhvs?Lo2)BJ_yUK4W9HvN#v0@YG#CEf zT8B+mF1>r;a<@b-^a4FVFR4?tUZ5{sHtVIr(Tl};u^v#cS1${_j>l7J=p`gSP%ka9 zO-j}tHleoz@z-3+t?@#gJ=f6UwrB?Q0zE)4iD_Cd(3h&jhF^#uli#~A;X!J8hMY@w zB;)lluZB7&4)kNw%a5}78a!}c!q(%!PO-5>2&cqN-Ie=9H0lBv&67BUt{5~9#7Z=me6>jj2;u^#PPw> z>E(9`KVSyM&|y8qeq7 zmVOnu9((RY<6hzSN2$lGrVE=ZYr87z+M3(vH+NQ^)Y_KgNk#+L?C_S3Ma_rL>u6qj zctgiU?QI=R^SchWnmcD*)YQ4?@P^Kg_U6O8o4dN_Hnq1dYU*z7Xg_>lhEZ-Lfk*;7 zlmxmj>XwO*>nNqVg}b!CNKNOvn zwfLF=qm%RFkui>wdxav+>gI|%m zUidfFP(B~y&f>I3>H44^i5owo-W>q0J8Smr>1WKEJ7f0Tc^&QD%}aCK)Kn;aeLjVv z@)AEw4v}r?!%q4^&YgL~{*dJaxj;^dU9_AaKa+0e)H52U&SWuls>!99KNwsveO{&d zYxM<^6Zg9*{$%S217;9%f?Ob{)UH}ike^BFwsU^#qUQE4ngn;5I0M0j(l_K)s=r2; zy|3l;x1X9ts=pQg{*Uf=NQIn^MuI1XoFEs-DYb|AC$TFJiMn;$xwyG=K}YAJruKQcED{z< zKO?VF{jK;*h@6h`qbUe-f?Ob{^q%6M#I8(IPdhtkMRRdy$GqmQ99&eP^fRc`${D$q z>TktkA#ys_kLHsT)-ODpPpB#{#KNToNo3fTayf!LC6Vm zft*rf#XpH%nWV0q&TqOP7sn)w3Z<{ht5kn0UjBEmA3xrYrXa`(a)F%Edy9V(yD~}L zb}pdbtLW;!ILB8#h0>ptSE>G1Jmry-cU-{ZDKkJ$kPGBw?<4+6?8>A&y>|B0nWxlF z^GxvO(z~hVP8xHXxIu8C^g%pxP5Xq*TP`yQ-T9uI0Xab~kW*q`@lRq`E``3EJ$26P zvt~~9iisz~OFFacgN4$Y^~nbwFZ1C4HHZ&1U#NpSkyFZ-YFYajFoTd2uvgOC&C0y!nll=X74E0fe|XVaqdTbtXv=XT7S*S3V0S4_-cxKR39U2Ns_ z+=}&8=3J2z`)z*SA$$loo)~h1Tp*{^OnILmcI6>aw@y2o+b?XT_pG!A)jhYZbzw_) zc5~uF>8HBIR!+;lSYI>t3z5@)J}eDDPLK=al%A#K1o@eyPCHxM=eKq?&&x?j#H>*I zQ>56ug+z7u1&znK-|>2;*Piw_8%r?c1i3&?_F3Ye#I8(Ir=1I%x?7raS$Wc&3Z*|e zzhZsOI7;M1uNi!y(g$e(a)MkSr^MOfpTw?AQm37ZTDx+d+!3=v=}*tESYI>7iJUmm zJ03s4hou3?337p)Qs;<&61y@dsIZqiKzyxxFTp%a=T=7q0S0<_3&hE~p_AXx0G*JeG3#Ff(SE>Fg9V~LX z*q@y73ssyAIYBOvQ{unHKZ#u#tUUHKzqzfuX>P94a^;jCeFK+TS(vnbNaF&wr$(c3 zcwg>KhMXW5$SHN6_$RR|FAd+j?Q|qGx25q$l61y_#+;(<0x2e6p*-A)rDwKX^UZwh5@i6&uwx{#`$<`hQ%pl|hxj;_# zT=7q0S0-sdJLl56N@r7+;wqHhkyGW=97^@K;y{tp<^E*fm=jr$6XXIpB^t#)iCqC7 zvD?mtogGVZ8WJcLh0>p#SE;DIj$e}XO!IlSmmf_*kQ3wrIi;G!KZ#wLq;5Osw{|UV zYcii1CcX(5O5c!Iss36l5IME_ldTUJFoTd2v(+B-%3WA&<7s$z;FaAmF$|QB$Df5)3&(E|61tq4+1UE0fgQ&iSni z7A)z?@JPLK=a zWVefd61y@ja6%3 z337p)5*^~7#I8(IUpu+u%{MUyh6|;il}DLojgI!niSyvc4>J)7hMXW5$SJj0{FB&~ zN$PE9$70^`o10A)NX(ER+>9=>3N0HPvZjVWpG|E52Z0z%YKzJjK=XdU}k`v zAQ#9fwM6`r*p*2a)Xup%II2SFm7L5<&$rKu+lk#XpH% znWTa3T-?;vmE{}9Lg{B@UzAyO2mYAI2?==K?SceP3^_qAkdwVs{FB&~heX{P?c}Xu zxpX`E?!)P~In$q=Yk_712(I4C{EO%2vPHU3`k7p8WgQh; zc}~7tq#vjC0v>0woV>v}qC{muPLK=aWM3}+N$kqYhwq(scD2$uO05fW)oj7^`4#JH z#)m~t*HNn1p6H0Xi~u=7E|3$>n}GaGLZ_YiOoEA7q4YyB4<03QLfhcSgUdLh}6a&R5QwCWm3@)q=Smw5)G~>XbEc7Kmsc+JpABydvkY$aySaWwfuY zl}>7-FtF0sd#?f@9e`~c5?Lqr+eh%7= z_MLUgTxwYxts~9Nb7T)r7M*9M&)7oj<_z5(>=W%l`%nVf4H(}UQX}d6#DdhP+84AZ z5@iiV%)jBw5ouQ{>J;JhQ0v7}XYF3inuxPjN0C=dKm9?C&I-11J(;cHlMp;Ni# zmdoSwgmB*3)0EEbOUt=uE`|RN&n|yz@k1l&G%bGK^6yiW(LapkR=lkJO+UTl=B+Kbcef}HH4NJ@lI1jFGS%YRV6z93UTCdM@lQ`o%H~#I8+|P64`UUM9 z(&x9izujS;=jIVjQQSf=&;#^hpQZHzed)^1Az98|>sI_@^_HCH#$$-zOGeOnYMixx zz?m!y9zYp5{WFg-3Q=2Pkk0XF)H} z1N4$UTk8e-+QQW+m_45r$ybG7_s zSFZl^SKqAr(=B)Ob$$1tIH&V>8~ps`{=zXo`#B(;VdPs@l5&#PhW=%z^!G1wKIQZ5 zA*M&l2zB-$^B63gKz(H(y_lR#crgB3JD>Gplf3ZU{Vi3%Yf*SU!=FKaC0o0qLsVX> zS^tJsCOc}8EZD8RjD4yOEFT`TfUb#z!}2kIGgi=tEb{7rnQ}dPD8~7-IFHtTNuNiH zb85}sZo_Yx;hb9Lg)mnZau-s<=+TWMT14~eJla})9<5y_zv;VjyqfQ)=7=9XH(>lh zd{%r#pF_Lxzx27Z==Y#QzfT0wH{RT!>xS{m_YD{|5023uHNI%Pj%evTTErRS7e0@+ zal|-v1@kjgef*6~<}`xjhkn7ZH`xEw<2n1s^4nt)dvWr)Pu#7~JFWU&?&qE+$;hAw z=mm9vzAP)=M=O!_Iz3T0=MM|lR9*iS2L`@3hMe$y9mDL8Pgh9Xx9GQEK7Cz+<6k}Y z7=4CbSxW$ik}`7VHtEFNi=eZE|1y7;Hrufon3HsCm1S~HyshkPUko2Vf$^AGbA%t>(;S`CLVDuQI_1$uy95=~k!&{q*u zb-*gH?;gm157&7P^Z>m;2+)^hjahA3mplEX#DR5x`2sx%m(%6#51%KP1NsZY-h9r( zJZI!H2U534)Sb$SWY}Zq(OO0vvwg1ey2L`o_oeiE1!<*0`TdNz{C>uRdY+O<$$pA?;t%HsjU%X!$vh=J-alWlg%15U z9Z3H+^j%teU!4CK3f_)hEP<_e5MM$tIr`PdKC<;M(yymtI@X(k49;xP^a!`_g1L-canefoDz zB=+LubDwxv>m&7G?s}O)@=Hm-`wbP&;o0VF*N1&A%BvzFvz!l`x%(@;`Pt!9d#jE5oT_0>Px%7t@?uui+urgh{~!ZjVBn|TFz|EW z&%bA6A7=0u82nq_H261YeBSe<AQRl;-EmFBa(MCj4!pCFnW2 zrk?9JB+tR*mU-_u&~s2*qHclt9Mq}B^A~!79-tT0FeUm@aPqIDm%4SxvlRS|wG4Zo zgU7I;1cb*VJtUvzN$99>{qd4L-=`%z;p1&@v8^MdpB_}agp;VSFc7+-_Sh4u0>UB4!s@I28Bnv1)iAv>-nm#qgw zFVF+@lIYNSfxfn0jq)oA`x(OY!ec?w82!sa$@x`bNU5L~=mB~`APS_cP$RYwl;jycGTn`U`O3Mb)mb%Gj^!-@(kxrxwYJ&wHyM=CPUz zEFa#_0J?3xp8@NESRYJn)cYB*Za8U^_H)bS_jIvNn0PO9yuj<#|0LbB-;c!_M~vo= z7icv_?PsXd>x1cw<$3l~y-sLl_Hj*Y7QL*Kb=x26IQ2!n9+$pG z*2S;ae#4e{;2KeV)@XgC*XelUrdev8X6gGn4x{J(qV{=9`S_kFeU*QGFkNw}v#!Lv z&OT-8ITO_n=5%*-v~}ft?sTE_>b$L-PX%|S!0TyO&<7BgP~7Y!AEfS}J13cLAScKL zaehVwa}L?9i10v@DEEc?I~CKM2_W|^XE>qSuX0E|3%C0yzN>@&k-GsGS!z^AG$0 zW8k<@`g2rS`AdrB^a+s@Y6`!Eu@TZaW;xBE7|+#GlC2?LgnTdnW;FT-=z@|}0Q^9(0YFvmN9k*~3wzk0kwuE<>nzLk7AU)crb zKZb<)Qh-32%g!{B8856%t~SP@Ju==w{8jOD5@%Nv7i-1OWW00eWBUD`n>FAa`iU*& zn*9XCsn35#ecuJxwez+n`UaDKWu}Q9P{{>IZuD2?0S_fu_Sk?8%B;Qr`+e?YRmW;Qgqunkr z-seATV7&fo8ddiiBWs}h;3hxuL!eWXT*?AH60w+e)FR*blkud z`818M*q+)P4e9Z^Q%TPa`Z?qRIi76%RT%q!=7@aKc%~QQ8@SPa%x3RA9m2SoG>1BlIaF=f?Ob{^iATQ#I90r4I-x; ze=a6NPC3#K#qY!%B65N}%48v&9@7~Ca)MkSC;J=XpI}#;2ayvWCY-|&+8J`nk)F>H z&Y`%Bc5}iq`dtt9PuDrQ+Me`ZCsEx%PLK=al=!CjC$X#azXp+${9Ft@<>y_HMFTSA zlq0?TP7DnXvMSEsXn|rPrv(J_^A7f;S6}=jkQ3wrIidAZH28*oliC8-?D0- zKeu%e9UAE89|pN{%8$OzDYfzxE;Z*t0aa|e$cgLdl@sqZGXvxVxj;_oTf{$!U8PFG z=c9Ah% z*qT)rD3o5wskXhTjZSsUsz6lS6Q3Y*a>wI3llOd@f#t;OMwn+~-VHp=#{m~>%UZ3!VgZA4c?n@xxE9J>mv@e7%9d>?d8b8{t%?TJ@icuB>MGmoF@< z`TVp0oP6ZnpZY1o-0{inp^eZ;TkZK8jaXx@DC zDuw?H@nU+WVS45}hIoPgf_Go689D#R-v5mMS)A!t-cvs1kQ%%1k;|^^CEVUiU;9!` zYR}Y^YkvHx6R8d-{?Bi}{?kK7?q2hg*%z%M9;-G#F}}KD-8ZVAf92 z*B<)hd8=N1=e62H%X(BDxE@5SLiZ<^PyXN8YaTxG`6tw8qc2SQ#gxi@zc%^kYp&Jp z+xPhgFL?dw$u&#4SF&}t# z%JZijIOXOWuRW1?ocN{J-gxkjcehTN``jZB6OV_#^W?9q)4#c8)q=jBIPr)x&mA$` z^TlHv9iaUKrzLA1du$rnnBEWg_)*8!Y<}dShkFit_~EDTKVnteGmpi==kU}2al*e} z7~Pwgw0HH$%_AS;ba&3JvEF+R*k+upxuW4?HGlrYqME*G3zSWm7z}g&TtOWY`$u@q zk^84D`$!lkC@{{%i4!y0?zKrxS6TZjYA@CXn2y)eZO``;E`QiFVmD`~?{q!rdplL1 zQp+BDE!~Z(h~jf z?j=4Guy4mQ&lmWGv=QI=4P3>OWdfznA;Gx zgHcGnjQsdHX{~3QP9~g98Cw&F`-?hXK*j^-lOOQ9XN()*&!E5Pe1Uw53O9ssmnG0^cQsO=V)1zWZ&@EIsW~_phN#nTp2`vpfTmxm@#P>dYFjmkD^Z>n}zd>Jv@!8h- zyhntuFueqgL%iK8HCp$VZQTx(54}JS&)d7nWHdqZNEEu)vJIl7;$7-MHVr@ZBx z_pFzVlxWLy>UVU{c9eQZfA3=A1NN?!B8_8AeNuD7&Q0}VZ~J?0|6-~J>bteRZz?mI zj{cGQqJDm$e@+Rvzy0QyAfBFKUz7&AY zoV>w=y?MoO&;3Q6BP4$6H&iM1Tegg|;m@ES^rO;ZsQeJr!Cv}KPWv=-DUwXg2`p!R zo}oOPBgARyl6o*6D`1Sk2lKtdh3Ms6pgkx}=}BOI@9%~6IX^Bwv*3LHIYOMO!)S^) z-y5=l2Rk>&@V0)^iYiTD6CIWk_uS2T0cRrLt&i?>ryzkANU;e zJf)T5Q>lPaqZ&v2Sv;j$pE^a>r+VeP_6k{_x>oz8^bG<2iFl>Y(d$x)bB*;Wp+i4P zTo*)dz9%cb^(mIu<#d^W*Q0hL`C)#Ou|Bo4tWP!Erq(NLiA_Ga!W(zf^SPEibD!5G zN$=1D^kRQa>m?=n()Ho>sN2@Mbz}Hg5A5dfUD)~*(*4-M>r-i3pZbX(F%)9HSJ1_F zYzF4DJCBGZgJEw-LoZLr_c9WDaq_uOd_eTFcYb<-9-tTWH|Q&$I&DoFtWSmMWzhAh zQCnsmBwra|oCZBWFW?7#x$9F$ss6G5m#r0-ooXryvfTukcgXeDdmg2mNIq#X;?>(YMZD z%_aPK?jRP)4fFy%Krg5s^p($i1#-fCg<+nroT-@e^Lw!QO5=!`s7Mwu^XFYh7X;v% zoJ=@?WSb_jUN%q-;E&AtiYG;Dg>S(RKvBC^w8A z_@JL8^xzBnbV&Pw5Be6N2Vc;c_KErfAN0$G9(*TBxm996ru=~q`n1r4FUk$02R`UG z3qAP4zo8vw`M?K#RjKr2;RE`x@_`TfIYJM<(3{!cAV1)PJ|*gMOpXgKtXoX7*pyANZgjTc+g)zG#0KJ@7%F5PI-My-^QSf4~R*QlST5=zU{| z{Q)2JD})|=QEnJL@Ik*`=)t!|?0>A`mmojjgC64u@P*vM=z$M<$On8;?;!dfLdJLn z>%eIlzkm(u*1#P&Dz zI3)eefO(&nx!kkdlH}+?hxth$ef-V_O@{9yHW_e00iGmo4+lN3V_N3 z%#$K_ex7sSG!B?Iwj2367k{;0*y88|A6{-ydMP0;t`!0|Jf2jh?byW7>y4b63pbO? z)`M9uaVie|?J2s4TnDO!h&U95`SwsQ{000+>hE$+viOlI@w?`8Xp%7=IfSM`=x=vW z191`m+eRa6Gc%M6-}YpvOFHMwj(82$r#fL>4o=u5%L+ewyn z>yY=K{@d&S-M?-v!zYm}&JlThQDRQx0RJ)7$k(~fUs{KQH5 z5m3GtnK=yvd-ICnp7p}#B%|LwO_lOBAf%GyK8JNE32Eg(zaP^>Er$PtKeYcL-+hQb z#C!tdQusyqNw3fjmZc%?-@EO`D+yZiUVmsmqQ_HCe7p98sqf_G4{f1Czf0c{L_gc` ze~&ci`eJw30=nt#tt5LqP3qJk^iRR*tr?Tg2FxpBW{2}xJ zy+E(fm#)rvq!;h+Su^~I@`t}9glDDB@JD|X82%72@+BLQ_J=eekaJY&)T(E#t73cs zW#E|kvkrrvQ<_Ve^IkoBt^=33rE;Mc=mB~`dC-@F`>r7yzi|!n9@zY7!%KhtyVA>X z^ll;{?>`{-V+rN}y__bn&c}S?-IFYfFN^fjS2L1)1nY&~v(Y}?X(O(R@uk=d_=jzR z`%9Q!OfDuoAoFdI$$Cjpx$uK8(|s0VaAciX<$m7~nECfGfAQz=?`eVI_fy7t3FgUI z$3Qu!kZyUr`yHy}zGarR(@w{gER_%Eeg6Gv*-s*I;x6r%)87s9&*1LHuvgH*UhTUB z=+$qKF|GLXzFo+EJ+KBs{~jw-BIVx|E%|rEf&Trtl5rMag9op?dIJ=zxw!s4JFbJ5 zyQMIo7w7?cfnK36RetimdMp0Hb#7foJ1~Fs(<2$@g-l8DeNsri^6w){z5cz&`gqAT zbh#yl-LYQyS|xUD;$eRd|Ay#r2+#}k0KK5@(3gUfKc?p_h2$SpeEByl)n7i)y_57C zl^gm8{Uu1w`uj0`hKU?hLhBgEm3*RvuTYjw2Kq~wUQ8|~>@X^l8`jGTYPT(Z@I|Uu zHWn3h))+S_y~+K(+Wn=C*vrFw%jn96zmP^zBmt2CtjI4Aw%;B~!gvAWhm`CSz_`L( zugCg7#ueboYZ@n$-^KVL7Aq+W8b2g{d#c{2AaUyZ`njKaTBn0wSPXv%I{3rH4+6#y z$xn&jlkr15>4(D#k@OgMItpt1plE6QfH-jcP*OI|;%o5Wl~>UCL344(57}`Yyxc8? z0lh#E(2M+@dD-{414nxlKV%G9|(5m zhdtI|rPo;FgrAF=dB=L;>p1f5JPtNPO#KQB|E}2Z@W#*!^Z>o2pOk*+>o;R)-0=7s zwE3k4F6`CIMS>8f7vzJF#DRKQO6M#rFc5=4=!M4*7^isgV%Aau-QPcNz4d+cg6lHUI(9Gpso*3jmJ4fGlH)|2ZicY5u=MQc^QgavakA#g&WCF~ z3F@~0(Ycvv{r_5P;*l5v+L?(JrEGZ#tiAr@akG?c)3hG_y1e0-UpqE%@}5(S{?J2t zvNkAf)3B?VEVUc+{6L|4|t$k zDs&D%p<7nrrvo17RtTNLPw3{C`ssiNy7fZm@N?+=`U4*5Z0U~3|11_yLEX&^46%^#DB3p;`_dKegb;ngT87f>5t$e{j$oiL(l^s^mBwBd{J%~J@7%F5_<5RB;`B)26B}4 z10VD&g&urC@AON99{8Z&DD>cqa?SpP_5&aEV@FEA6+S38j2`%)PY6BuLVjbVzrZd~ zKJY=mROrDMayR=Y=z$OV6+#cbC^w8A_@G}e^xzBnr6KhPKIrY8wfw*r<%ZD%AM}%i z9(*A`(_f(dzz2Pc(1S1J7e)_!&@UHy@I}4D=z$OVw9tbu>}P$5{D2So%|Z{pV}&k^ z9{8ZI8YTT-_(1*{{!jWJ@IgOE=)o6uA4U&+(5HkRd{OT(dfN*fXn5vUgmdpw(mQ;IkGPtaq189{_)w|)_FjO^@Kzaee|6jt7WSM z?oUaYdq5A+3krw66x{b(gHES)GY;xIyA5$VTdFqt&Q3{W$o{ZiNU#?A+faXJ2Y&(o zk^Z>$AMh)a#9zPNx&Y$mWu5p>x%m%U z=wPSmAo}DDVyF0yr1-wmWud5bXjeq)ZrFaOD|&!l&@AYSK7s4AtlZ9{gH`<2;PJh=mjCX^rVjf__Nv8}{6@p?q$@cNOV>h(D7! z^@4`&S90@jphG`O{9h3L^YreD1KxwB4S%O|!~Sx6IB5Ta^9K;S?~3(%F#g`XfxemJ zE4u)BeKG)Db8+8;WzRK$*;Yt$#z&$D=*50f>jnDKRjDMsY+B=1i{Rv6*D`z!T|In5 zp;6vj-Ks(7n<2oD>Bpz!IuZFh`i_`Tx+<&}@9&8b9N%pn=Xv$AUMvxMF!j<9 zY^i~InOc&aWq+Ht*RUUBy#)6c?jL3JPlxQd3i39Hpcm)?dP%&j^#XnAs`38rcBKam zlV18-J+Qw4L3-hF+DbSRAG&_N*p#N<=ay5ex9HHzhX9I8fr$YZ^}dQ^(*J7Q-)t4*fszdH}uoom%4+4Vo|3pTbJf z>Eoy!9Q1c;Ov~S?Nu04Sk>_Lk&^Ky)4IaGmIzK>Jnu|Lh%Z}^dVr7h~q8DsgU);$t8YrVosuCmmnbCx`F$3yy#=e=eaJneV= z`>!T`L_0WcP&q_jt7-GA|3g36zm|F2w(BP|>VM+B|5a_aXrK50wf5fkPhNxc^Oyar z{wMt>*8z^=Z7YET%J0h1W438QcBt)3x9O~%|8j!aI^N&n4g8&2aDa^m*||%>&olL% znvCmzO<6_}F0$v2ae}{7^B~FXuO6FLjj1(wd-ug>Qf&DYt_NQ{B;oUWY|nRU-Zg^Y znvAhVTJ(eo7|#Ub7oJ)|t9u+smQ!xNY42(j&HC%1bj<%SA57Ki_j8yhj+OZ!_Ay~z zh$>Gy)>gOAp0KA8V~ zET{P(#+_ zUZ5{kE}wg5&32wmv|Gi2z3&^24yokTk{*)Jel>2Mdv*-H$3m#VhXi}$@L>qWy}y@b4fbIz9u$q&>EpIeg4I^V#0;TP(| zOE1s^^kT=fAB4VKy?k`7Yg()q4SV(SeHX!ZhyD_hAE=jmNH6#JxC{&otQTHb8(w;W z9-x7~k}C%?>Y70~dl{!X*iQdv82F{K@ZSNO2!S)m&NbplK&&3PJ(S2g3Db+_L<=kuvgZtn zts6rx&;#^>T0mcb-8k7#^O$9HN$lo?-AxcZ1^FfK*c$`vk=BHgd(;)iG5d7)Ez$7t zWBh(DA>-`f`+jcVCx?DNS7L21@8>Z8!h8(hNn@UdeUg}$;k_KbbMaa+d$P zydD)l<8AM--48B$fL>BNYrQ~Ux{dBQ--VxA!}s3tWsAHMwnh82tr$fL;>2XubIR4UZ>_cfTY{Yhb;& z^ReNwUlMwNUhL6YFVL5^5biJD{gMn1r~cy3$A(LPfgYfj^cbxd=*!Vdk?xn&^&70e z@PlXgj4zW^RpQ!~B@$Bo!itxpB_621AGEJYPf)&Fnp&A{HfGe*w36v3H%*HS=ouPpzqgGx9i`*L~MUR#)Ti$G5CeW z=>MQY|4#?fua^Fg@7KQNhrhP(nQDtzLIdnq#f*~a!$;~(fYid zv14>>?&|{T(&a z0T1QbyXtm2{QjT4F9EEpDEGfzXfMbn0u758TBWH#NzyF^o2DCFpwQA52#c?THYK%b z(|?*m5T#-9d4=~}5FT|wD+okIQ7BqKnhF&!LfI4uvOLrRL1j^V5EkwK`;Bb1BBW{D@e0_d;Wg%WDM_iBMe0_>r zGrTNRf0QFm#bUlbKc7MURmgX2f80kYq3w})+No=I;0Jih6W^|Q>LtEL>z8_or#$hU zil<(tn`Td%KIMt;Ry_4G-E4Tu6CeMewolbV+}bSpr#$gBil^SV;!;`glqbGP@zmR{ z{Y;vjYWXQo{946RFUy||PkG`~il<(dyF%^Itv|{W->Z1)Wx2EADNlUGUWR|_Wxmte zjh3JC#MdjHdYNuEJmrZ`DxP|o|74c(Q=a$@il<)kmkm#O;(HWNy-jL2ZaZfCp*-03Xe^|)fuSfU4b$v)h3``V5WN4?ZZM%dmSl{jrGSN@dZtRn0` z*k7>!NY2#vz_lM?zmwGdi~Tsq1t!4x0i2JJJ_Pez#xo7~AF1oiJQu_GhjpIopyK)u z;%KL-jQD<>=j!{HeX-QFu)lt!Md2jwB}EDpd?^VosS5sgId@3AdZz_j^(99qIRcSufuZ_V<#yagX=x z@A+yyuwK{?vfY?+%J27fe9`k4{PWoU-uZR%#d`VRy^-^kmuroXfB%7V`?_9c;Q0@` z$XT$@z?VnCA~l24PvTSo%H#yHXS;}b&`iU`n;u6zjWNuwWAKZeIpcHyWq5+-*9l_)Q?Zu8rpl{@!z!kLS{Lp&JsK@ zKNdO2PY_*S6g)6Ezg_e~*P)94?pZUZwVeOMO}&piRtNko*F0bQz?VmLJ-hCtF3CgJ z>eJUut^Cn7wL6c!9eJqz%KGI$KY9F5r(QI5>qg0gmnY~w>(Z8Kl80%vuXfem+;;NR z$}LAr`m1hxZT>|YuRZOpDSzK<&#AGF{n?Px9Sk?jAC6znR}U z_4D^^dE|x}SN}8GCjngU2*>b;4y<$YQRsKDBlov>VH?zU=3~1b#W+LEzuJdA=V^Zr z-Q}N~T>I+MQB#|Lzgt?nqAc2{w@-rl^$*DfCkcv%Lday^C&As9p?sRVPXflP%=;t| z%P^E=zn&of_`p@fdaU$)65gJXJ;5Dsp9J3j@qRFMw7DPTeIn=Q-Fbe_o7rWBUw~tc z5VY4`Gl!Wc#o_s}kj{66NuoA=BR3y$~dKIW_k)(cC-`tr^_4iTGY>gTL`g>ki$f9Iat*LH+B+6&%8 zsL}U_{w(oO0W_2Ru5-hiq2GA|eyGUU-%}RXl5)Z}{g*7dH3>h!y-B8H_e@{Pcg#D( z+S=OkETZp=uUDSm*3_~hdr?OmAam~WLH(=~G4|=~!$!+i$A>nf&!>Epw{*?!cZ?@7IUFD#hxjP){3>Lk+~hO5OrfY_$BHEzuC_A z_FKsd`{-QR%3zYY#COOrsnZ%^x#v9_;uO%mBz}F2@UzN|4raI?#?$hHa4-U`g-&8AD z8E+V79Bw)#cdS!ii>0*zsx;-}s zdB8I#b8fHouj61F2zs$E>UoyyxX(-5du`g_FU7*Ms*Y0UG~3*)S?x1aW)tk*#sTj@ zr>qy&1M7uZWPN$(&)sF($EJN}_WsX^nej1;XWsC{PU5G{PoF$8?*RJ!&)q_-KSDAyu z0lh;t#QBy79+>i?eEGV3J}sZm$cN{U!+CT>3>XG>%}9j57E9M&Z4)V&O?)?9i=}I{ zT=4vPC!bP2Yw=QVF_wF=!1GH*&Na~{oio|6k9lXjN5j+zbDl`(yEDg{&`pYdUGTk5 zTm@fbjG-Nu%N3@Nj3IApH9r!~co65yDK4crU!RZ0o6x};Aj&blUd8$P6z8tRq8xD* z0jH4WJtXE&3;vOCn#!-$q zX3N*-<4gqk#GG4?=8NyXm+fophIsPo^R5?&r#$fq#Zxcw6`Bt95>I*J+Z9i}OxGZLvV_2ckIdE#pnPrbx5A1;5CC%#GX z)XRKlOP}(@uT?ztk~ivc)2BS~DaBJS+ex=EUz!pB zZ?N`)%)M9EUa%fmFRVS*mv{c!@>%j+Z4p5xk!Se?E0!dOo%>)H!m_w{&@OwWco zh7T4k2)*1T`_806w zQrds8Uvc|i_JeMJ2GtMgWF>x+ZP+j~LgV|;bBEY$Zx8;shRIK8|M8XL`VZo0r-|AO z_)SA*B7OfivSV$|xw)F{FUw=*fmR6?^FSJAoCkVX-ZRFhup^}3ffy~pJW%0U9Yx#e zl>IvEf%OvqvZ)u=m&wkVGOw`m?jUP&epbh3oZl~>r}e_%p(gT_p7Xf_&v{)hGw|kt z!oQLIbZp2_d?=DV{JV4us29{B?ASewmf$yk3)AXq*?#A&7uEyoC8h0+_2uQro1ZiF zU~+i8%+ICQ%b*OrK>pVT6w7iw!~ERSvB5*{myZGq2(s0S>DS+T?|`!&STCf2^<~b3 zz0#H}8QV2(+!IrmEcx%QakmbuqkOIqHjSeY`!|xs{e4~ZXRDXL4-Tj5o)?$Wz)PdO z)Q=JWRL7^Sl4K#{tv741XI;h&F1EfM9MXm-Rrc}PG@O0?YhUx;YXuH*8*fWT@rZ$l zfrx>Kfrx>Kfrx>Kfrx>Kfrx>Kfrx>Kfrx>Kfrx>Kf%g{!n5&HD3*TRL9+fs?AYve5 zAYve5AYve5AYve5AYve5AYve5AYve5AYve5pf3y*H=no;@A+Cs?zbno4uR_qxDJ5| zxITgF4Y>Y*>!G+lit>-hyW3pDi1)V#4Z7J|DTwR#I)ubG&3ow#lV8>M)W4o#9RZbF zA>XSej_>a#uF8N<+w*+cOrwhXQ&@^KM1Q2~D$F_rV6hHC!;I??Zk9C&_~buqzB27` zXS4+C5DL?pKiz?nV7;&&STFIbwSKjpP5R}s4&kPIg1pH2rn5KW+}@AkGUflX5j~DaacMq>aSQoo|2tJNvhEvqbO0nN7!0<_`cJJj^-sGnNgO;Q7AyDHpJ+>~^?g(Z&W&NWKebYJbc6GTFi2>KA(LkmuXF%=vZl1@~6pRoVxRYfX^0w>q!uWd>foxO=Pb zHuSpKJ8Vpp%n)L1!6(~#2ksLyG^2HAQ1~zYiuJ;JV7)L~tS_GLsNw$;TgX)}dkhJ$ zLvhb{lyb%Y?(D6u=MPE-(O)Ll{S5Z&9O?V5AGlk1Z}sDznC(|(Z}sn=d*jsq`h9)v z{!`7~>Zw0|Ld?!7oKuO?agS2-#b`eEmIp{CwlZ z$4{v3{w(&2-*oG$6Bhqs&mZ1bciWY{o9=(}+nZiG?$l{-jNQ1Y{!@RdJ3PLw&LJ*3 z{TDH?>Eu_uHBNT&g?O;|#ICf_>Qc0`~XNUDV#{dLI$(t==&e z`%A@Q?~T3H{e6sd-=b||1h$Frx8=jO5sS^AcgoVmjV*oJ%IH!jEu_)QeM|N?`_I&9 z*z{p@o;*(Xd_Q4e_Az4mAOSp7l!f=fK_2Y`enYu@;mawmUU9xY9~9olh;mFXsW@Ms z;@o|VC`a4|#rgVtoQW`>lq0T3alSr3pW%IlC`Vk`eufucpPybBM>*mWiu3jPxcFd1 znNP|Q*RD8UpW5if7$q_ zJnxK2edSPm;FLVCdOcNeB z-3!e=Thn5(@@dgNTV$+%Lexv0sA7BT?|rt|U$Fm(&sKk4{d)GlZa?n!$0#lO4;;(n zv&SAC;wiyn4?o|E@2>l?YMA_v&VOH5T>n8F?KF`I-*J`NDW7N2Zh7uyFY6zc$2@-m zR-SLg7@Gay01ke`3A^bPzN21Pe>fXPOYnTFaIIlI+v|+|2kU|L!t_~RvK9KKSnNW1 zzJVh#edS#r-1NrLpY_kp_u;?fiO)B1zL$ucSUfdFX%7yV;&uL>>jQr_uQ5LSD&L^o|8% zg7AZJaNj`oZ#?I9Bjx{<{w`K`@es7*;a)#DOm-%`Xb?WWOxDPEhZ#Hmyhvo)xYTH| z_Yo{aYu4lj`~t@v)&uJ$q3w~58?e0f8e{rim__w&(>aCP40P+?2Zvbz0?Vh z9G{+)Y{_ZfBc4}c#};52&CZ4$prBt5`Wy5w=x-$Txd{Cb`Wc*;Y1jEVcYX<(n=gI* z&e9*?J*&Zk7Yx~Zu*tRUZ^W-R&*lB_CiORdlEGidP-3>zxZ>DOQ$Ngvzfk&39K3%z z**P~?v;9SRaCrAqn-&Nb?+a=u&o}Xm6W4?LsRi<`AU=g1qlkKCS%; z{SBP6J+I!H#~6LFnC)-GViyc9w5_16=BgK0lY7qeca#wAB`!R2e0oeW zCZ~B1r97|PjwTTAInaA=IXz!eBK2GCYv5<4d_i1;kOO|wfB(rw^*!#^0rIg<)Y4y} z|H72$*U(R)-@@leWzO^6lcjILdK_z8U2^d`g_j|Z&m+gLni-&xd}q{$8p86 z-KW-Pz;Bum_E*mOT=+atn=$tBq2_DPfAO&5zc9?`zZ^Pr{}?{`51X${{)^EP_%DTN z&7bZ-Nw8j653HBuPt||X`ts7p{{FFT_XK&7{r&aao-g@_YqSdB{{C66d-El`h&|f# zCI1|Bf9#)G^ac4aHVixdcN-^J53HAjwl~(7Jm--5cunfz^Adi3M$4<+o8>bzwdsV- z^3ir27bsakN?LqjY>vF$gd&yQWMvGLO9))S8^!5+gURV#T7v_ZZ zWzGwmFF9U#gr9Ps-{ZRaJh|pe$IBekb;Z0(m`k&Z#Y}&5ONU_2o~# zI&57ucM3nhn)uOA@Donc%vW&U0@t=YaNsl}Qy%Mx?GzUG?laG^28;6c^_V^{ z7AuoR8~^Ea&mMm(N})IYaGC!*#+lNXK5yFM#j&Qbm6cVQE*J8hS5*a+!J8Y4-(mcx zpdT$HiNa+D4Hic!?VxQLdw*(=`|3JW#z@=kfU*!^+_PQBPJ?Tl{-E6 zz`Ez-<+EhT5?!wq^h4tJq^-$w@q>a+7vm&!c$kCbgdd59^nB%lzlc8-q!ZTJF+;9p z{qTN`Wy`0hi;tb;1coR6ZwMy7sk@;b2pyOpTR_b zBL*S{A_fX!!1IvQ9}GrlB6InJBBNtBrPD*c+oG~X`Gb4^=vTG&N5_e*zMx#6L4T0r zaVCE-@CnhM;z$o&fAG0&Yr4iR+;#`T)`o3&B+fr5@&^rh%AY$nky9f?e~`Ro^9P@} zZl%^q-PAALy*j%;sQ#bt4{lX|Fv~n4{K3_&F_|A6@8`P6NKx~H$-5$dF!1ZZH0^k9u8BgaA-$X~mK*T`AKw%7&K0g@r@!3-v6u6ie=Zo?O!{Z6l z$UZkH^1eUF@i>z|NKHZh?Y_7S& zF%U5jF_52u(EMQ9>y&++AG8z}?GM^>gNSS2SJ3<*$Ky=?V1LXHCPzm8U|?I}F+3Qf z0tw$ARNk`rg9XnIs{iNvgT3kxs-GC7Y+}YAET7-jx^(5LSV=Dt7BxQ@|3%~vhV6hp zA8iHo!Z@$F-$njlI!88VZakeod=niJ0}%re1BEdV@(0r(ef0;i9!ig_{@}XvPSj_F zMw0Ij_QfCMc$~=}9Ksxho?+ojj!WtDgYNU<3{$TpMjO5+eakQSqwSwRr>v0O< zFIrC$a2CZP1|kOj-5Kz9aw};)$+&UjLVjE(rUv;tQpK|RgM#b#^&}jRGx>u-KFzpk z`+??Kxknd1ASh+w{@~Vzn>SWpvU2M0USHF-%g?U)dGf+& zJxNg8whw5>0||eAPF|fXPRQB~G>i_xv;J)e)s-GC7Y+}>r&1`60*0>^8 zInljT=y_58VB+4$9}N6cP;9p~H_mg);oN+p^&|lo#!&5ZH=fR)!9;%}1|kL`1`1)I z^z|eK?Kczb_|CpwTAXPx%$5)Px=BCs2km-D=OF1sgn_^xj1847&7C99{Gj?%4#+yS#w^GdwZ6D7^Mf3ZGx>wTm~M|t$aHY-`h#ij z4}L6KPZCtKJ!aYQK!W~YQhCef4;H+hMEyVCAN;iXgX$*+DVx~zc}Fj8Zg#&d>b$7= z!PI@mAKWv;TWUq6nnn2i^1L(R&uvfkxf_SEWcn_P5k+4|3`7h>41^fK6Wxvh+kbG7 zIGK8Q{vb~dmIy!mj$v`@Nr*Bb{EiNBLOC91@&^MS6@4d;^e~e@c$n@#I0E|*j*k36 z!yWl^$02fRr05Tlw?6xW>i_xv;HByhs-GC-&BTm9c*M-c#+7(zT*@qAQT|}^{(+w# zbjuj_13`7h>4CG?K8|DVgAKW=ey6E{qjBmQn4ELH5 z9fdtF=6IaR9}J!u(`QK!Gx>w|e$>gfXnk=|wV8ZDa%!aL50bY&`-AHL`TpP+)gM$p zG02;Vi9guf(AL(t;)oe7E81EcR<#xF6c*(VCbmTWVBnvk4rmXw6@F)*yWd6o%>*2} z{E;_1Oy>{ZL`TFx#6ZMAVGLOQ;79$rJRh3-d2vkV*L?A`Nbt+#o@`Oii_sUB>JM@} z9w>h>H7c4P3@Y0mlk9jPL4T0E_1Pa(|Ihaa*Q!6LeqxZaiA|q(Ol!k-{JpyPLy^v7_kyLBm5jfA;r`r}O7g9{q_Jh!}_%D2M^$58^k9V;$*$-2Nc? zq1?w#!DYx6;}3F9FtitqWO7EX51$uvJRUHAusreygR13tF1M_M6EtJs8XNyg1ma zEIW&Xf(OP$`-4H>XX`J_ALMwP$sY`~p>M^J9-4Vgq-CzB{XR+b{-B{o`3dUE9blZ;LuF${&nBzGL`QbU+V#b> z3GmH$rZD|_G(Twbl+I6>&rH|xU35eYL<~d>6v6<0VR+#7oB0q2B=fry>VE}$&0xBzTQ+~N;Qa^H|MUI9U#mZ; zeqxZaiHSeBv_013eqGFYQT|}+i5nn&K|e>KF$&+JUo~OA`N3qgp2Se4 zd>QUIE-?&5=ns;&Z2n-u^MmUD`TpQ<)gM$pF-Y0O#2;MU+_rRO^ZD+Tg3pWc2a``m z{$OAe;5cYo>2Y489PKCCZzkZ790lBjuVuSluzg2@5d#qe?|TMH_Xi7lUTh{N!18wW z2RR-Om_PW%G2J8Ayzh-i3CK1^1^oE_pz_vde^C8D-yeKd{Xz8;gOp8d`n(fYFFvPn zyR9clJQewafxm(}aQzj2N82&=6kyEv(flC!arv{qXWZqK7;+nh5d#qe5d*mx@TNTm zY(MG`1W6a`5B6!l8IH%9{6Vk8UcT60ribqQp!<7u@o0W9s9A5U=a^)Nfe8IU@|MjX zEckh``hUJZ*rWcS`iVixCN_QE+@&iPH63w6!#OLKE;+vxzp$wJ!T2+gKN$FSr~|u= zxpAITF0i4<9}KuKmTaH9@pS$SCi)vO5HS!jPzVD9>kkIggRvt0!8(!W$KPuPKH`jA zANU>K`-3_j50pQcx-j~ElEAv$af#=F1pPts)@OfE{XgFy{G0lN>L&&%o7nVu^IF&AGazYzlw0}%uH88H5!w;y#Z7A&<; zV6OGWrfHs#v(2eulzHV$P znjbVgr1NKg&v-h29_7)Wh=GWKh=GC_7})ti_>+1R>kq;gEcy59I35p_KN$bc=;-}H z)@;@}3tu2ce~`TO*&kH@&-Vw1oqE^EHL|W;{lp+;6N5jvx_M>W%+`kU+-n7%dkw|= zlN;dqJ&8X={$S(}(ogHJ5QdjaSElQ^jWxO&F%U5jF%U2?u>PPm;h5DQTzB4y-WbX} z^~ZiQ9FH^kgTXUw^sPA3!+h@#CO;beUY+5N{AC)a0txzq!rn+2E%Omu&z|7-+M{B~Q2SAG-cv+J4mQyYC*kCeZZ$ zj@e_D9S>NcKbTP7viXAr?>D3VpYIRuul}I=i9yOH27j=nrLC!?C&fmEcjJ`qgSjci zJR3?qKk)uwTFRDRhij2Pn9dNt88moE`pYpH_>0Xlh<~j=%{GO+FrF#Qblv1X`WrD2 zF%U73p8N#a|aJh3HL{4>`O-l-&oJriu4ETM4q2OU!ndW$Ky=? zV2~&DsW{R@*B?wxisX<1gHPgs;cn0ztv2LpScjWD#^8*PR)#&Z)!^Mf`YHh=c_jAyz|UZXH# zAYve5ARhz9AJp|EX~MbY2LnGU4d#Ebn;(l6=@0g8JqgF-0rLkJM(+;>Rm<_n4Fd_^ zA5`A@><_B{=lg?W)gM$pF-Y0O#2;)~f}bzmPV0*kFAcmum_Ao&^B>I*x;9{+NArU= z8&N!BAYve501OQD^J0J0D%Kyg=9}!AD(rbN$Ky=?V4w}-Cyw;cogZ|c7st!5`pw8S z1|9vi{SZ4IFys4!%3Cν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%0D|dQNQRpL6)mNcjlj+ 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 MTQ`cu(|!N`50`DY!vFvP From 673cd20a43a00b5694db3adbaec09cee10a211b8 Mon Sep 17 00:00:00 2001 From: Arthur Golubtsov Date: Wed, 27 May 2020 15:34:07 +0300 Subject: [PATCH 19/49] Update animation lib and client specification --- Drone/animation_lib.py | 183 +++++++++++++++--------- Drone/config/spec/configspec_client.ini | 6 + 2 files changed, 121 insertions(+), 68 deletions(-) diff --git a/Drone/animation_lib.py b/Drone/animation_lib.py index 6d2c4ad..b75e745 100644 --- a/Drone/animation_lib.py +++ b/Drone/animation_lib.py @@ -6,7 +6,6 @@ import numpy import rospy import logging import threading -import ConfigParser try: from FlightLib import FlightLib @@ -21,13 +20,66 @@ logger = logging.getLogger(__name__) interrupt_event = threading.Event() -def moving(f1, f2, delta, x = True, y = True, z = True): - return ((abs(f1['x'] - f2['x']) > delta) and x - or (abs(f1['y'] - f2['y']) > delta) and y - or (abs(f1['z'] - f2['z']) > delta) and z) +def moving(f1, f2, delta, x=True, y=True, z=True): + return ((abs(f1.x - f2.x) > delta) and x + or (abs(f1.y - f2.y) > delta) and y + or (abs(f1.z - f2.z) > delta) and z) + +def get_numbers(frames): + numbers = [] + if frames: + for frame in frames: + numbers.append(frame.number) + return numbers + +class Frame(object): + params_dict = { + "number": None, + "x": None, + "y": None, + "z": None, + "yaw": None, + "red": None, + "green": None, + "blue": None, + "delay": None, + } + def __init__(self, csv_row=None, delay=None): + for key, value in self.params_dict.items(): + setattr(self, key, value) + if csv_row: + self.load_csv_row(csv_row) + if delay: + self.delay = delay + + def load_csv_row(self, csv_row): + number, x, y, z, yaw, red, green, blue = csv_row + self.number = int(number) + self.x = float(x) + self.y = float(y) + self.z = float(z) + self.yaw = float(yaw) + self.red = int(red) + self.green = int(green) + self.blue = int(blue) + + def get_pos(self): + if None in [self.x, self.y, self.z]: + return [] + else: + return [self.x, self.y, self.z] + + def get_color(self): + if None in [self.red, self.green, self.blue]: + return [] + else: + return [self.red, self.green, self.blue] + + def pose_is_valid(self): + return self.get_pos() and (self.yaw is not None) class Animation(object): - def __init__(self, config = None, filepath = "animation.csv"): + def __init__(self, config=None, filepath="animation.csv"): self.id = None self.static_begin_time = None self.takeoff_time = None @@ -43,7 +95,7 @@ class Animation(object): if config is not None: self.update_frames(config, filepath) - def load(self, delay=0.1, filepath="animation.csv"): + def load(self, filepath="animation.csv", delay=0.1): self.original_frames = [] self.corrected_frames = [] self.filepath = filepath @@ -67,42 +119,32 @@ class Animation(object): logger.debug("Got new frame delay: {}".format(current_frame_delay)) else: logger.debug("No animation id in file") - frame_number, x, y, z, yaw, red, green, blue = row_0 - self.original_frames.append({ - 'number': int(frame_number), - 'x': float(x), - 'y': float(y), - 'z': float(z), - 'yaw': float(yaw), - 'red': int(red), - 'green': int(green), - 'blue': int(blue), - 'delay': current_frame_delay - }) + try: + frame = Frame(row_0, current_frame_delay) + except ValueError as e: + logger.error("Can't parse row in csv file. {}".format(e)) + return + else: + self.original_frames.append(frame) for row in csv_reader: if len(row) == 2: current_frame_delay = float(row[1]) logger.debug("Got new frame delay: {}".format(current_frame_delay)) else: - frame_number, x, y, z, yaw, red, green, blue = row - self.original_frames.append({ - 'number': int(frame_number), - 'x': float(x), - 'y': float(y), - 'z': float(z), - 'yaw': float(yaw), - 'red': int(red), - 'green': int(green), - 'blue': int(blue), - 'delay': current_frame_delay - }) + try: + frame = Frame(row, current_frame_delay) + except ValueError as e: + logger.error("Can't parse row in csv file. {}".format(e)) + return + else: + self.original_frames.append(frame) self.split_animation() ''' Split animation into 5 parts: static_begin, takeoff, route, land, static_end - * static_begin and static_end are arrays of frames in the beginning and the end of animation, + * static_begin and static_end are chains of frames in the beginning and the end of animation, where the drone doesn't move - * takeoff and land are arrays of frames after and before static frames of animation, + * takeoff and land are chains of frames after and before static frames of animation, where the drone doesn't move in xy plane, and it's z coordinate only increases or decreases, respectively. * route is the rest of the animation Count static_begin_time and takeoff_time @@ -123,36 +165,41 @@ class Animation(object): while i < len(frames) - 1: if moving(frames[i], frames[i+1], move_delta): break - self.static_begin_time += frames[i]['delay'] + self.static_begin_time += frames[i].delay i += 1 - self.static_begin_frames = frames[:i+1] - frames = frames[i+1:] - i = 0 + if i > 0: + self.static_begin_frames = frames[:i+1] + frames = frames[i+1:] + i = 0 # Select takeoff frames while i < len(frames) - 1: - if moving(frames[i], frames[i+1], move_delta, z = False) or frames[i]['z'] - frames[i+1]['z'] <= 0: + if moving(frames[i], frames[i+1], move_delta, z = False) or (frames[i+1].z - frames[i].z <= 0): break - self.takeoff_time += frames[i]['delay'] + self.takeoff_time += frames[i].delay i += 1 - self.takeoff_frames = frames[:i+1] - frames = frames[i+1:] + if i > 0: + self.takeoff_frames = frames[:i+1] + frames = frames[i+1:] i = len(frames) - 1 # Moving index from the end # Select static end frames while i >= 0: if moving(frames[i], frames[i-1], move_delta): break i -= 1 - self.static_end_frames = frames[i:] - frames = frames[:i] - i -= len(frames) - 1 + if i < len(frames) - 1: + self.static_end_frames = frames[i+1:] + frames = frames[:i+1] + i = len(frames) - 1 # Select land frames while i >= 0: - if moving(frames[i], frames[i-1], move_delta, z = False) or frames[i-1]['z'] - frames[i]['z'] >= 0: + if moving(frames[i], frames[i-1], move_delta, z = False) or (frames[i-1].z - frames[i].z <= 0): break i -= 1 - self.land_frames = frames[i:] + if i < len(frames) - 1: + self.land_frames = frames[i+1:] + frames = frames[:i+1] # Get route frames - self.route_frames = frames[:i] + self.route_frames = frames def make_output_frames(self, static_begin, takeoff, route, land, static_end): self.output_frames = [] @@ -166,24 +213,25 @@ class Animation(object): self.output_frames += self.land_frames if static_end: self.output_frames += self.static_end_frames - self.output_frames_min_z = min(self.output_frames, key = lambda p: p['z'])['z'] + self.output_frames_min_z = min(self.output_frames, key = lambda p: p.z).z def update_frames(self, config, filepath): - self.load(config.animation_frame_delay, filepath) - self.make_output_frames(config.animation_output_static_begin, - config.animation_output_takeoff, - config.animation_output_route, - config.animation_output_land, - config.animation_output_static_end) + self.load(filepath, config.animation_frame_delay) + if self.original_frames: + self.make_output_frames(config.animation_output_static_begin, + config.animation_output_takeoff, + config.animation_output_route, + config.animation_output_land, + config.animation_output_static_end) def get_scaled_output(self, ratio = (1,1,1), offset = (0,0,0)): x0, y0, z0 = offset x_ratio, y_ratio, z_ratio = ratio scaled_frames = copy.deepcopy(self.output_frames) for frame in scaled_frames: - frame['x'] = x_ratio*frame['x'] + x0 - frame['y'] = y_ratio*frame['y'] + y0 - frame['z'] = z_ratio*frame['z'] + z0 + frame.x = x_ratio*frame.x + x0 + frame.y = y_ratio*frame.y + y0 + frame.z = z_ratio*frame.z + z0 return scaled_frames def get_scaled_output_min_z(self, ratio = (1,1,1), offset = (0,0,0)): @@ -195,9 +243,9 @@ class Animation(object): x0, y0, z0 = offset x_ratio, y_ratio, z_ratio = ratio first_frame = self.output_frames[0] - x = x_ratio*first_frame['x'] + x0 - y = y_ratio*first_frame['y'] + y0 - z = z_ratio*first_frame['z'] + z0 + x = x_ratio*first_frame.x + x0 + y = y_ratio*first_frame.y + y0 + z = z_ratio*first_frame.z + z0 return x, y, z def get_start_action(self, start_action, current_height, takeoff_level): @@ -221,20 +269,19 @@ class Animation(object): with open(filepath, mode='w+') as corrected_animation: csv_writer = csv.writer(corrected_animation, delimiter=',', quotechar='"', quoting=csv.QUOTE_MINIMAL) for frame in self.corrected_frames: - csv_writer.writerow([frame['number'], frame['x'], frame['y'], frame['z'], frame['red'], frame['green'], frame['blue'], frame['delay']]) - -def convert_frame(frame): - return ((frame['x'], frame['y'], frame['z']), (frame['red'], frame['green'], frame['blue']), frame['yaw']) + csv_writer.writerow([frame.number, frame.x, frame.y, frame.z, frame.red, frame.green, frame.blue, frame.delay]) try: - def execute_frame(point=(), color=(), yaw=float('Nan'), frame_id='aruco_map', use_leds=True, + def execute_frame(frame, frame_id='aruco_map', use_leds=True, flight_func=FlightLib.navto, auto_arm=False, flight_kwargs=None, interrupter=interrupt_event): if flight_kwargs is None: flight_kwargs = {} - - flight_func(*point, yaw=yaw, frame_id=frame_id, auto_arm=auto_arm, interrupter=interrupt_event, **flight_kwargs) + if frame.pose_is_valid(): + flight_func(x=frame.x, y=frame.y, z=frame.z, yaw=frame.yaw, frame_id=frame_id, auto_arm=auto_arm, interrupter=interrupt_event, **flight_kwargs) + else: + logger.debug("Frame pose is not valid for flying") if use_leds: - if color: + if frame.get_color: LedLib.fill(*color) def takeoff(z=1.5, safe_takeoff=True, frame_id='map', timeout=5.0, use_leds=True, diff --git a/Drone/config/spec/configspec_client.ini b/Drone/config/spec/configspec_client.ini index 4678684..6e8ac74 100644 --- a/Drone/config/spec/configspec_client.ini +++ b/Drone/config/spec/configspec_client.ini @@ -71,6 +71,12 @@ frame_delay = float(default=0.1, min=0.01) ratio = float_list(default=list(1.0, 1.0, 1.0), min=3, max=3) # Available options: 'animation', 'nan' or a number in degrees yaw = string(default=180.0) + [[OUTPUT]] + static_begin = boolean(default=False) + takeoff = boolean(default=True) + route = boolean(default=True) + land = boolean(default=False) + static_end = boolean(default=False) [LED] use = boolean(default=False) From d34e53110b037a6e07d753696fcd519393ed54e1 Mon Sep 17 00:00:00 2001 From: Arthur Golubtsov Date: Wed, 27 May 2020 15:38:14 +0300 Subject: [PATCH 20/49] Add tests for animation_lib --- tests/animation_1.csv | 51 ++ tests/animation_2.csv | 1066 +++++++++++++++++++++++++++++++++++++++ tests/animation_3.csv | 12 + tests/animation_test.py | 76 +++ 4 files changed, 1205 insertions(+) create mode 100644 tests/animation_1.csv create mode 100755 tests/animation_2.csv create mode 100644 tests/animation_3.csv create mode 100644 tests/animation_test.py diff --git a/tests/animation_1.csv b/tests/animation_1.csv new file mode 100644 index 0000000..e7e37cb --- /dev/null +++ b/tests/animation_1.csv @@ -0,0 +1,51 @@ +basic +1,0.0,0.0,0.0,0.0,204,2,0 +2,0.0,0.0,0.0,0.0,204,9,1 +3,0.0,0.0,0.0,0.0,204,21,2 +4,0.0,0.0,0.0,0.0,204,37,3 +5,0.0,0.0,0.0,0.0,204,56,4 +6,0.0,0.0,0.0,0.0,204,77,5 +7,0.0,0.0,0.0,0.0,204,97,6 +8,0.0,0.0,0.0,0.0,204,116,6 +9,0.0,0.0,0.0,0.0,204,131,7 +10,0.0,0.0,0.0,0.0,204,143,7 +11,0.0,0.0,0.1,0.0,199,153,7 +12,0.0,0.0,0.2,0.0,185,163,6 +13,0.0,0.0,0.3,0.0,163,172,6 +14,0.0,0.0,0.4,0.0,134,181,5 +15,0.0,0.0,0.5,0.0,102,188,5 +16,0.0,0.0,0.6,0.0,69,194,4 +17,0.0,0.0,0.7,0.0,40,199,3 +18,0.0,0.0,0.8,0.0,18,201,3 +19,0.0,0.0,0.9,0.0,4,203,2 +20,0.0,0.0,1.0,0.0,0,204,2 +21,0.02217,0.0,1.0,0.0,0,204,2 +22,0.08889,0.0,1.0,0.0,0,204,2 +23,0.19737,0.0,1.0,0.0,0,204,2 +24,0.33926,0.0,1.0,0.0,0,204,2 +25,0.5,0.0,1.0,0.0,0,204,2 +26,0.66074,0.0,1.0,0.0,0,204,2 +27,0.80263,0.0,1.0,0.0,0,204,2 +28,0.91111,0.0,1.0,0.0,0,204,2 +29,0.97783,0.0,1.0,0.0,0,204,2 +30,1.0,0.0,1.0,0.0,0,204,2 +31,1.0,0.0,0.9,0.0,3,204,2 +32,1.0,0.0,0.8,0.0,14,204,2 +33,1.0,0.0,0.7,0.0,32,204,2 +34,1.0,0.0,0.6,0.0,56,204,1 +35,1.0,0.0,0.5,0.0,84,204,1 +36,1.0,0.0,0.4,0.0,112,204,0 +37,1.0,0.0,0.3,0.0,138,204,0 +38,1.0,0.0,0.2,0.0,159,204,0 +39,1.0,0.0,0.1,0.0,175,204,0 +40,1.0,0.0,0.0,0.0,183,204,0 +41,1.0,0.0,0.0,0.0,188,199,0 +42,1.0,0.0,0.0,0.0,192,185,0 +43,1.0,0.0,0.0,0.0,196,163,0 +44,1.0,0.0,0.0,0.0,199,135,0 +45,1.0,0.0,0.0,0.0,201,102,0 +46,1.0,0.0,0.0,0.0,202,69,0 +47,1.0,0.0,0.0,0.0,203,40,0 +48,1.0,0.0,0.0,0.0,203,18,0 +49,1.0,0.0,0.0,0.0,203,5,0 +50,1.0,0.0,0.0,0.0,204,0,0 diff --git a/tests/animation_2.csv b/tests/animation_2.csv new file mode 100755 index 0000000..addc6f0 --- /dev/null +++ b/tests/animation_2.csv @@ -0,0 +1,1066 @@ +parad +0,-1.00519,2.65699,0.21,-2.76315,7,255,0 +1,-1.00519,2.65699,0.21,-2.76315,7,255,0 +2,-1.00519,2.65699,0.21,-2.76315,7,255,0 +3,-1.00519,2.65699,0.21,-2.76315,7,255,0 +4,-1.00519,2.65699,0.21,-2.76315,7,255,0 +5,-1.00519,2.65699,0.21,-2.76315,7,255,0 +6,-1.00519,2.65699,0.21,-2.76315,7,255,0 +7,-1.00519,2.65699,0.21,-2.76315,7,255,0 +8,-1.00519,2.65699,0.21,-2.76315,7,255,0 +9,-1.00519,2.65699,0.21,-2.76315,7,255,0 +10,-1.00519,2.65699,0.21,-2.76315,7,255,0 +11,-1.00519,2.65699,0.21,-2.76315,7,255,0 +12,-1.00519,2.65699,0.21,-2.76315,7,255,0 +13,-1.00519,2.65699,0.21,-2.76315,7,255,0 +14,-1.00519,2.65699,0.21,-2.76315,7,255,0 +15,-1.00519,2.65699,0.21,-2.76315,7,255,0 +16,-1.00519,2.65699,0.21,-2.76315,7,255,0 +17,-1.00519,2.65699,0.21,-2.76315,7,255,0 +18,-1.00519,2.65699,0.21,-2.76315,7,255,0 +19,-1.00519,2.65699,0.21,-2.76315,7,255,0 +20,-1.00519,2.65699,0.21,-2.76315,7,255,0 +21,-1.00519,2.65699,0.21,-2.76315,7,255,0 +22,-1.00519,2.65699,0.21,-2.76315,7,255,0 +23,-1.00519,2.65699,0.21,-2.76315,7,255,0 +24,-1.00519,2.65699,0.21,-2.76315,7,255,0 +25,-1.00519,2.65699,0.21,-2.76315,7,255,0 +26,-1.00519,2.65699,0.21,-2.76315,7,255,0 +27,-1.00519,2.65699,0.21,-2.76315,7,255,0 +28,-1.00519,2.65699,0.21,-2.76315,7,255,0 +29,-1.00519,2.65699,0.21,-2.76315,7,255,0 +30,-1.00519,2.65699,0.21,-2.76315,7,255,0 +31,-1.00519,2.65699,0.21,-2.76315,7,255,0 +32,-1.00519,2.65699,0.21,-2.76315,7,255,0 +33,-1.00519,2.65699,0.21,-2.76315,7,255,0 +34,-1.00519,2.65699,0.21,-2.76315,7,255,0 +35,-1.00519,2.65699,0.21,-2.76315,7,255,0 +36,-1.00519,2.65699,0.21,-2.76315,7,255,0 +37,-1.00519,2.65699,0.21,-2.76315,7,255,0 +38,-1.00519,2.65699,0.21,-2.76315,7,255,0 +39,-1.00519,2.65699,0.21,-2.76315,7,255,0 +40,-1.00519,2.65699,0.21,-2.76315,7,255,0 +41,-1.00519,2.65699,0.21,-2.76315,7,255,0 +42,-1.00519,2.65699,0.21,-2.76315,7,255,0 +43,-1.00519,2.65699,0.21,-2.76315,7,255,0 +44,-1.00519,2.65699,0.21,-2.76315,7,255,0 +45,-1.00519,2.65699,0.21,-2.76315,7,255,0 +46,-1.00519,2.65699,0.21,-2.76315,7,255,0 +47,-1.00519,2.65699,0.21,-2.76315,7,255,0 +48,-1.00519,2.65699,0.21,-2.76315,7,255,0 +49,-1.00519,2.65699,0.21,-2.76315,7,255,0 +50,-1.00519,2.65699,0.21,-2.76315,7,255,0 +51,-1.00519,2.65699,0.21,-2.76315,7,255,0 +52,-1.00519,2.65699,0.21,-2.76315,7,255,0 +53,-1.00519,2.65699,0.21,-2.76315,7,255,0 +54,-1.00519,2.65699,0.21,-2.76315,7,255,0 +55,-1.00519,2.65699,0.21,-2.76315,7,255,0 +56,-1.00519,2.65699,0.21,-2.76315,7,255,0 +57,-1.00519,2.65699,0.21,-2.76315,7,255,0 +58,-1.00519,2.65699,0.21,-2.76315,7,255,0 +59,-1.00519,2.65699,0.21,-2.76315,7,255,0 +60,-1.00519,2.65699,0.21,-2.76315,7,255,0 +61,-1.00519,2.65699,0.21,-2.76315,7,255,0 +62,-1.00519,2.65699,0.21,-2.76315,7,255,0 +63,-1.00519,2.65699,0.21,-2.76315,7,255,0 +64,-1.00519,2.65699,0.21,-2.76315,7,255,0 +65,-1.00519,2.65699,0.21,-2.76315,7,255,0 +66,-1.00519,2.65699,0.21,-2.76315,7,255,0 +67,-1.00519,2.65699,0.21,-2.76315,7,255,0 +68,-1.00519,2.65699,0.21,-2.76315,7,255,0 +69,-1.00519,2.65699,0.21,-2.76315,7,255,0 +70,-1.00519,2.65699,0.21,-2.76315,7,255,0 +71,-1.00519,2.65699,0.21,-2.76315,7,255,0 +72,-1.00519,2.65699,0.21,-2.76315,7,255,0 +73,-1.00519,2.65699,0.21,-2.76315,7,255,0 +74,-1.00519,2.65699,0.21,-2.76315,7,255,0 +75,-1.00519,2.65699,0.21,-2.76315,7,255,0 +76,-1.00519,2.65699,0.21,-2.76315,7,255,0 +77,-1.00519,2.65699,0.21,-2.76315,7,255,0 +78,-1.00519,2.65699,0.21,-2.76315,7,255,0 +79,-1.00519,2.65699,0.21,-2.76315,7,255,0 +80,-1.00519,2.65699,0.21,-2.76315,7,255,0 +81,-1.00519,2.65699,0.21,-2.76315,7,255,0 +82,-1.00519,2.65699,0.21,-2.76315,7,255,0 +83,-1.00519,2.65699,0.21,-2.76315,7,255,0 +84,-1.00519,2.65699,0.21,-2.76315,7,255,0 +85,-1.00519,2.65699,0.21,-2.76315,7,255,0 +86,-1.00519,2.65699,0.21,-2.76315,7,255,0 +87,-1.00519,2.65699,0.21,-2.76315,7,255,0 +88,-1.00519,2.65699,0.21,-2.76315,7,255,0 +89,-1.00519,2.65699,0.21,-2.76315,7,255,0 +90,-1.00519,2.65699,0.21,-2.76315,7,255,0 +91,-1.00519,2.65699,0.21,-2.76315,7,255,0 +92,-1.00519,2.65699,0.21,-2.76315,7,255,0 +93,-1.00519,2.65699,0.21,-2.76315,7,255,0 +94,-1.00519,2.65699,0.21,-2.76315,7,255,0 +95,-1.00519,2.65699,0.21,-2.76315,7,255,0 +96,-1.00519,2.65699,0.21,-2.76315,7,255,0 +97,-1.00519,2.65699,0.21,-2.76315,7,255,0 +98,-1.00519,2.65699,0.21,-2.76315,7,255,0 +99,-1.00519,2.65699,0.21,-2.76315,7,255,0 +100,-1.00519,2.65699,0.21,-2.76315,7,255,0 +101,-1.00519,2.65699,0.21,-2.76315,7,255,0 +102,-1.00519,2.65699,0.21,-2.76315,7,255,0 +103,-1.00519,2.65699,0.21,-2.76315,7,255,0 +104,-1.00519,2.65699,0.21,-2.76315,7,255,0 +105,-1.00519,2.65699,0.21,-2.76315,7,255,0 +106,-1.00519,2.65699,0.21,-2.76315,7,255,0 +107,-1.00519,2.65699,0.21,-2.76315,7,255,0 +108,-1.00519,2.65699,0.21,-2.76315,7,255,0 +109,-1.00519,2.65699,0.21,-2.76315,7,255,0 +110,-1.00519,2.65699,0.21,-2.76315,7,255,0 +111,-1.00519,2.65699,0.21,-2.76315,7,255,0 +112,-1.00519,2.65699,0.21,-2.76315,7,255,0 +113,-1.00519,2.65699,0.21,-2.76315,7,255,0 +114,-1.00519,2.65699,0.21,-2.76315,7,255,0 +115,-1.00519,2.65699,0.21,-2.76315,7,255,0 +116,-1.00519,2.65699,0.21,-2.76315,7,255,0 +117,-1.00519,2.65699,0.21,-2.76315,7,255,0 +118,-1.00519,2.65699,0.21,-2.76315,7,255,0 +119,-1.00519,2.65699,0.21,-2.76315,7,255,0 +120,-1.00519,2.65699,0.21,-2.76315,7,255,0 +121,-1.00519,2.65699,0.21,-2.76315,7,255,0 +122,-1.00519,2.65699,0.21,-2.76315,7,255,0 +123,-1.00519,2.65699,0.21,-2.76315,7,255,0 +124,-1.00519,2.65699,0.21,-2.76315,7,255,0 +125,-1.00519,2.65699,0.21,-2.76315,7,255,0 +126,-1.00519,2.65699,0.21,-2.76315,7,255,0 +127,-1.00519,2.65699,0.21,-2.76315,7,255,0 +128,-1.00519,2.65699,0.21,-2.76315,7,255,0 +129,-1.00519,2.65699,0.21,-2.76315,7,255,0 +130,-1.00519,2.65699,0.21,-2.76315,7,255,0 +131,-1.00519,2.65699,0.21,-2.76315,7,255,0 +132,-1.00519,2.65699,0.21,-2.76315,7,255,0 +133,-1.00519,2.65699,0.21,-2.76315,7,255,0 +134,-1.00519,2.65699,0.21,-2.76315,7,255,0 +135,-1.00519,2.65699,0.21,-2.76315,7,255,0 +136,-1.00519,2.65699,0.21,-2.76315,7,255,0 +137,-1.00519,2.65699,0.21,-2.76315,7,255,0 +138,-1.00519,2.65699,0.21,-2.76315,7,255,0 +139,-1.00519,2.65699,0.21,-2.76315,7,255,0 +140,-1.00519,2.65699,0.21,-2.76315,7,255,0 +141,-1.00519,2.65699,0.21,-2.76315,7,255,0 +142,-1.00519,2.65699,0.21,-2.76315,7,255,0 +143,-1.00519,2.65699,0.21,-2.76315,7,255,0 +144,-1.00519,2.65699,0.21,-2.76315,7,255,0 +145,-1.00519,2.65699,0.21,-2.76315,7,255,0 +146,-1.00519,2.65699,0.21,-2.76315,7,255,0 +147,-1.00519,2.65699,0.21,-2.76315,7,255,0 +148,-1.00519,2.65699,0.21,-2.76315,7,255,0 +149,-1.00519,2.65699,0.21,-2.76315,7,255,0 +150,-1.00519,2.65699,0.21,-2.76315,7,255,0 +151,-1.00519,2.65699,0.21,-2.76315,7,255,0 +152,-1.00519,2.65699,0.21,-2.76315,7,255,0 +153,-1.00519,2.65699,0.21,-2.76315,7,255,0 +154,-1.00519,2.65699,0.21,-2.76315,7,255,0 +155,-1.00519,2.65699,0.21,-2.76315,7,255,0 +156,-1.00519,2.65699,0.21,-2.76315,7,255,0 +157,-1.00519,2.65699,0.21,-2.76315,7,255,0 +158,-1.00519,2.65699,0.21,-2.76315,7,255,0 +159,-1.00519,2.65699,0.21,-2.76315,7,255,0 +160,-1.00519,2.65699,0.21,-2.76315,7,255,0 +161,-1.00519,2.65699,0.21,-2.76315,7,255,0 +162,-1.00519,2.65699,0.21,-2.76315,7,255,0 +163,-1.00519,2.65699,0.21,-2.76315,7,255,0 +164,-1.00519,2.65699,0.21,-2.76315,7,255,0 +165,-1.00519,2.65699,0.21,-2.76315,7,255,0 +166,-1.00519,2.65699,0.21,-2.76315,7,255,0 +167,-1.00519,2.65699,0.21,-2.76315,7,255,0 +168,-1.00519,2.65699,0.21,-2.76315,7,255,0 +169,-1.00519,2.65699,0.21,-2.76315,7,255,0 +170,-1.00519,2.65699,0.21,-2.76315,7,255,0 +171,-1.00519,2.65699,0.21,-2.76315,7,255,0 +172,-1.00519,2.65699,0.21,-2.76315,7,255,0 +173,-1.00519,2.65699,0.21,-2.76315,7,255,0 +174,-1.00519,2.65699,0.21,-2.76315,7,255,0 +175,-1.00519,2.65699,0.21,-2.76315,7,255,0 +176,-1.00519,2.65699,0.21,-2.76315,7,255,0 +177,-1.00519,2.65699,0.21,-2.76315,7,255,0 +178,-1.00519,2.65699,0.21,-2.76315,7,255,0 +179,-1.00519,2.65699,0.21,-2.76315,7,255,0 +180,-1.00519,2.65699,0.21,-2.76315,7,255,0 +181,-1.00519,2.65699,0.21,-2.76315,7,255,0 +182,-1.00519,2.65699,0.21,-2.76315,7,255,0 +183,-1.00519,2.65699,0.21,-2.76315,7,255,0 +184,-1.00519,2.65699,0.21,-2.76315,7,255,0 +185,-1.00519,2.65699,0.21,-2.76315,7,255,0 +186,-1.00519,2.65699,0.21,-2.76315,7,255,0 +187,-1.00519,2.65699,0.21,-2.76315,7,255,0 +188,-1.00519,2.65699,0.21,-2.76315,7,255,0 +189,-1.00519,2.65699,0.21,-2.76315,7,255,0 +190,-1.00519,2.65699,0.21,-2.76315,7,255,0 +191,-1.00519,2.65699,0.21,-2.76315,7,255,0 +192,-1.00519,2.65699,0.21,-2.76315,7,255,0 +193,-1.00519,2.65699,0.21,-2.76315,7,255,0 +194,-1.00519,2.65699,0.21,-2.76315,7,255,0 +195,-1.00519,2.65699,0.21,-2.76315,7,255,0 +196,-1.00519,2.65699,0.21,-2.76315,7,255,0 +197,-1.00519,2.65699,0.21,-2.76315,7,255,0 +198,-1.00519,2.65699,0.21,-2.76315,7,255,0 +199,-1.00519,2.65699,0.21,-2.76315,7,255,0 +200,-1.00519,2.65699,0.21,-2.76315,7,255,0 +201,-1.00519,2.65699,0.21,-2.76315,7,255,0 +202,-1.00519,2.65699,0.21,-2.76315,7,255,0 +203,-1.00519,2.65699,0.21,-2.76315,7,255,0 +204,-1.00519,2.65699,0.21,-2.76315,7,255,0 +205,-1.00519,2.65699,0.21,-2.76315,7,255,0 +206,-1.00519,2.65699,0.21,-2.76315,7,255,0 +207,-1.00519,2.65699,0.21,-2.76315,7,255,0 +208,-1.00519,2.65699,0.21,-2.76315,7,255,0 +209,-1.00519,2.65699,0.21,-2.76315,7,255,0 +210,-1.00519,2.65699,0.21,-2.76315,7,255,0 +211,-1.00519,2.65699,0.21,-2.76315,7,255,0 +212,-1.00519,2.65699,0.21,-2.76315,7,255,0 +213,-1.00519,2.65699,0.21,-2.76315,7,255,0 +214,-1.00519,2.65699,0.21,-2.76315,7,255,0 +215,-1.00519,2.65699,0.21,-2.76315,7,255,0 +216,-1.00519,2.65699,0.21,-2.76315,7,255,0 +217,-1.00519,2.65699,0.21,-2.76315,7,255,0 +218,-1.00519,2.65699,0.21,-2.76315,7,255,0 +219,-1.00519,2.65699,0.21,-2.76315,7,255,0 +220,-1.00519,2.65699,0.21,-2.76315,7,255,0 +221,-1.00519,2.65699,0.21,-2.76315,7,255,0 +222,-1.00519,2.65699,0.21,-2.76315,7,255,0 +223,-1.00519,2.65699,0.21,-2.76315,7,255,0 +224,-1.00519,2.65699,0.21,-2.76315,7,255,0 +225,-1.00519,2.65699,0.21,-2.76315,7,255,0 +226,-1.00519,2.65699,0.21,-2.76315,7,255,0 +227,-1.00519,2.65699,0.21,-2.76315,7,255,0 +228,-1.00519,2.65699,0.21,-2.76315,7,255,0 +229,-1.00519,2.65699,0.21,-2.76315,7,255,0 +230,-1.00519,2.65699,0.21,-2.76315,7,255,0 +231,-1.00519,2.65699,0.21,-2.76315,7,255,0 +232,-1.00519,2.65699,0.21,-2.76315,7,255,0 +233,-1.00519,2.65699,0.21,-2.76315,7,255,0 +234,-1.00519,2.65699,0.21,-2.76315,7,255,0 +235,-1.00519,2.65699,0.21,-2.76315,7,255,0 +236,-1.00519,2.65699,0.21,-2.76315,7,255,0 +237,-1.00519,2.65699,0.21,-2.76315,7,255,0 +238,-1.00519,2.65699,0.21,-2.76315,7,255,0 +239,-1.00519,2.65699,0.21,-2.76315,7,255,0 +240,-1.00519,2.65699,0.21,-2.76315,7,255,0 +241,-1.00519,2.65699,0.21,-2.76315,7,255,0 +242,-1.00519,2.65699,0.21,-2.76315,7,255,0 +243,-1.00519,2.65699,0.21,-2.76315,7,255,0 +244,-1.00519,2.65699,0.21,-2.76315,7,255,0 +245,-1.00519,2.65699,0.21,-2.76315,7,255,0 +246,-1.00519,2.65699,0.21,-2.76315,7,255,0 +247,-1.00519,2.65699,0.21,-2.76315,7,255,0 +248,-1.00519,2.65699,0.21,-2.76315,7,255,0 +249,-1.00519,2.65699,0.21,-2.76315,7,255,0 +250,-1.00519,2.65699,0.21,-2.76315,7,255,0 +251,-1.00519,2.65699,0.21,-2.76315,7,255,0 +252,-1.00519,2.65699,0.21,-2.76315,7,255,0 +253,-1.00519,2.65699,0.21,-2.76315,7,255,0 +254,-1.00519,2.65699,0.21,-2.76315,7,255,0 +255,-1.00519,2.65699,0.21,-2.76315,7,255,0 +256,-1.00519,2.65699,0.21,-2.76315,7,255,0 +257,-1.00519,2.65699,0.21,-2.76315,7,255,0 +258,-1.00519,2.65699,0.21,-2.76315,7,255,0 +259,-1.00519,2.65699,0.21,-2.76315,7,255,0 +260,-1.00519,2.65699,0.21,-2.76315,7,255,0 +261,-1.00519,2.65699,0.21,-2.76315,7,255,0 +262,-1.00519,2.65699,0.21,-2.76315,7,255,0 +263,-1.00519,2.65699,0.21,-2.76315,7,255,0 +264,-1.00519,2.65699,0.21,-2.76315,7,255,0 +265,-1.00519,2.65699,0.21,-2.76315,7,255,0 +266,-1.00519,2.65699,0.21,-2.76315,7,255,0 +267,-1.00519,2.65699,0.21,-2.76315,7,255,0 +268,-1.00519,2.65699,0.21,-2.76315,7,255,0 +269,-1.00519,2.65699,0.21,-2.76315,7,255,0 +270,-1.00519,2.65699,0.21,-2.76315,7,255,0 +271,-1.00519,2.65699,0.24386,-2.76315,7,255,0 +272,-1.00519,2.65699,0.30495,-2.76315,7,255,0 +273,-1.00519,2.65699,0.36315,-2.76315,7,255,0 +274,-1.00519,2.65699,0.41951,-2.76315,7,255,0 +275,-1.00519,2.65699,0.47568,-2.76315,7,255,0 +276,-1.00519,2.65699,0.53229,-2.76315,7,255,0 +277,-1.00519,2.65699,0.58898,-2.76315,7,255,0 +278,-1.00519,2.65699,0.64513,-2.76315,7,255,0 +279,-1.00519,2.65699,0.70179,-2.76315,7,255,0 +280,-1.00519,2.65699,0.75979,-2.76315,7,255,0 +281,-1.00519,2.65699,0.81297,-2.76315,7,255,0 +282,-1.00519,2.65699,0.86881,-2.76315,7,255,0 +283,-1.00519,2.65699,0.93843,-2.76315,7,255,0 +284,-1.00453,2.65334,0.9872,-2.76315,7,255,0 +285,-1.00118,2.61466,1.0,-2.76315,7,255,0 +286,-0.99839,2.52817,1.0,-2.76315,7,255,0 +287,-0.99588,2.44199,1.0,-2.76315,7,255,0 +288,-0.99316,2.37257,1.0,-2.76315,7,255,0 +289,-0.99048,2.297,1.0,-2.76315,7,255,0 +290,-0.98782,2.21961,1.0,-2.76315,7,255,0 +291,-0.98515,2.14444,1.0,-2.76315,7,255,0 +292,-0.98247,2.06873,1.0,-2.76315,7,255,0 +293,-0.9798,1.9928,1.0,-2.76315,7,255,0 +294,-0.97713,1.91706,1.0,-2.76315,7,255,0 +295,-0.97446,1.84129,1.0,-2.76315,7,255,0 +296,-0.97178,1.7655,1.0,-2.76315,7,255,0 +297,-0.96911,1.68972,1.0,-2.76315,7,255,0 +298,-0.96644,1.61394,1.0,-2.76315,7,255,0 +299,-0.96377,1.53816,1.0,-2.76315,7,255,0 +300,-0.96109,1.46238,1.0,-2.76315,7,255,0 +301,-0.95842,1.3866,1.0,-2.76315,7,255,0 +302,-0.95575,1.31083,1.0,-2.76315,7,255,0 +303,-0.95308,1.23504,1.0,-2.76315,7,255,0 +304,-0.9504,1.15926,1.0,-2.76315,7,255,0 +305,-0.94773,1.08349,1.0,-2.76315,7,255,0 +306,-0.94506,1.00771,1.0,-2.76315,7,255,0 +307,-0.94239,0.93192,1.0,-2.76315,7,255,0 +308,-0.93971,0.85615,1.0,-2.76315,7,255,0 +309,-0.93704,0.78037,1.0,-2.76315,7,255,0 +310,-0.93437,0.70458,1.0,-2.76315,7,255,0 +311,-0.9317,0.62881,1.0,-2.76315,7,255,0 +312,-0.92902,0.55304,1.0,-2.76315,7,255,0 +313,-0.92636,0.47725,1.0,-2.76315,7,255,0 +314,-0.92366,0.40136,1.0,-2.76315,7,255,0 +315,-0.92101,0.32571,1.0,-2.76315,7,255,0 +316,-0.91843,0.2505,1.0,-2.76315,7,255,0 +317,-0.91542,0.17384,1.0,-2.76315,7,255,0 +318,-0.91299,0.09409,1.0,-2.76315,7,255,0 +319,-0.90963,0.02185,1.0,-2.76315,7,255,0 +320,-0.87383,0.00163,1.0,-2.76315,7,255,0 +321,-0.77046,0.0,1.0,-2.76315,7,255,0 +322,-0.69735,0.0,1.0,-2.76315,7,255,0 +323,-0.63239,0.0,1.0,-2.76315,7,255,0 +324,-0.55624,0.0,1.0,-2.76315,7,255,0 +325,-0.48324,0.0,1.0,-2.76315,7,255,0 +326,-0.41137,0.0,1.0,-2.76315,7,255,0 +327,-0.33828,0.0,1.0,-2.76315,7,255,0 +328,-0.26549,0.0,1.0,-2.76315,7,255,0 +329,-0.19282,0.0,1.0,-2.76315,7,255,0 +330,-0.12004,0.0,1.0,-2.76315,7,255,0 +331,-0.04728,0.0,1.0,-2.76315,7,255,0 +332,0.02546,0.0,1.0,-2.76315,7,255,0 +333,0.09822,0.0,1.0,-2.76315,7,255,0 +334,0.17097,0.0,1.0,-2.76315,7,255,0 +335,0.24373,0.0,1.0,-2.76315,7,255,0 +336,0.31648,0.0,1.0,-2.76315,7,255,0 +337,0.38924,0.0,1.0,-2.76315,7,255,0 +338,0.46199,0.0,1.0,-2.76315,7,255,0 +339,0.53475,0.0,1.0,-2.76315,7,255,0 +340,0.6075,0.0,1.0,-2.76315,7,255,0 +341,0.68025,0.0,1.0,-2.76315,7,255,0 +342,0.75301,0.0,1.0,-2.76315,7,255,0 +343,0.82576,0.0,1.0,-2.76315,7,255,0 +344,0.89852,0.0,1.0,-2.76315,7,255,0 +345,0.97127,0.0,1.0,-2.76315,7,255,0 +346,1.04403,0.0,1.0,-2.76315,7,255,0 +347,1.11678,0.0,1.0,-2.76315,7,255,0 +348,1.18953,0.0,1.0,-2.76315,7,255,0 +349,1.26229,0.0,1.0,-2.76315,7,255,0 +350,1.33504,0.0,1.0,-2.76315,7,255,0 +351,1.4078,0.0,1.0,-2.76315,7,255,0 +352,1.48055,0.0,1.0,-2.76315,7,255,0 +353,1.55331,0.0,1.0,-2.76315,7,255,0 +354,1.62606,0.0,1.0,-2.76315,7,255,0 +355,1.69881,0.0,1.0,-2.76315,7,255,0 +356,1.77157,0.0,1.0,-2.76315,7,255,0 +357,1.84432,0.0,1.0,-2.76315,7,255,0 +358,1.91707,0.0,1.0,-2.76315,7,255,0 +359,1.98983,0.0,1.0,-2.76315,7,255,0 +360,2.06258,0.0,1.0,-2.76315,7,255,0 +361,2.13534,0.0,1.0,-2.76315,7,255,0 +362,2.20809,0.0,1.0,-2.76315,7,255,0 +363,2.28084,0.0,1.0,-2.76315,7,255,0 +364,2.3536,0.0,1.0,-2.76315,7,255,0 +365,2.42635,0.0,1.0,-2.76315,7,255,0 +366,2.49911,0.0,1.0,-2.76315,7,255,0 +367,2.57186,0.0,1.0,-2.76315,7,255,0 +368,2.64461,0.0,1.0,-2.76315,7,255,0 +369,2.71737,0.0,1.0,-2.76315,7,255,0 +370,2.79012,0.0,1.0,-2.76315,7,255,0 +371,2.86288,0.0,1.0,-2.76315,7,255,0 +372,2.93563,0.0,1.0,-2.76315,7,255,0 +373,3.00839,0.0,1.0,-2.76315,7,255,0 +374,3.08114,0.0,1.0,-2.76315,7,255,0 +375,3.15389,0.0,1.0,-2.76315,7,255,0 +376,3.22665,0.0,1.0,-2.76315,7,255,0 +377,3.2994,0.0,1.0,-2.76315,7,255,0 +378,3.37216,0.0,1.0,-2.76315,7,255,0 +379,3.44492,0.0,1.0,-2.76315,7,255,0 +380,3.51765,0.0,1.0,-2.76315,7,255,0 +381,3.59042,0.0,1.0,-2.76315,7,255,0 +382,3.66323,0.0,1.0,-2.76315,7,255,0 +383,3.73581,0.0,1.0,-2.76315,7,255,0 +384,3.80863,0.0,1.0,-2.76315,7,255,0 +385,3.88208,0.0,1.0,-2.76315,7,255,0 +386,3.95309,0.0,1.0,-2.76315,7,255,0 +387,4.02606,0.0,1.0,-2.76315,7,255,0 +388,4.10654,-0.02619,1.03815,-2.766,7,255,0 +389,4.17222,-0.07,1.10188,-2.76964,7,255,0 +390,4.21934,-0.10777,1.15672,-2.77183,7,255,0 +391,4.26563,-0.144,1.20926,-2.77475,7,255,0 +392,4.31231,-0.18027,1.26178,-2.77798,7,255,0 +393,4.3582,-0.21679,1.31456,-2.78112,7,255,0 +394,4.40386,-0.25346,1.36747,-2.78438,7,255,0 +395,4.44921,-0.29032,1.42055,-2.78777,7,255,0 +396,4.49413,-0.3274,1.47382,-2.79127,7,255,0 +397,4.53865,-0.36469,1.52728,-2.7949,7,255,0 +398,4.5828,-0.40218,1.58092,-2.79865,7,255,0 +399,4.62655,-0.43987,1.63474,-2.80252,7,255,0 +400,4.66991,-0.47776,1.68872,-2.80652,7,255,0 +401,4.71287,-0.51586,1.74289,-2.81066,7,255,0 +402,4.75539,-0.55417,1.79725,-2.81494,7,255,0 +403,4.79746,-0.59271,1.8518,-2.81938,7,255,0 +404,4.83905,-0.63148,1.90655,-2.82397,7,255,0 +405,4.88015,-0.67049,1.9615,-2.82875,7,255,0 +406,4.92073,-0.70975,2.01666,-2.83371,7,255,0 +407,4.96076,-0.74927,2.07204,-2.83887,7,255,0 +408,5.00021,-0.78905,2.12764,-2.84425,7,255,0 +409,5.03906,-0.82911,2.18347,-2.84985,7,255,0 +410,5.07727,-0.86945,2.23954,-2.8557,7,255,0 +411,5.11481,-0.91009,2.29584,-2.86182,7,255,0 +412,5.15163,-0.95103,2.35239,-2.86822,7,255,0 +413,5.18772,-0.99229,2.40919,-2.87493,7,255,0 +414,5.22301,-1.03387,2.46625,-2.88196,7,255,0 +415,5.25747,-1.07579,2.52357,-2.88936,7,255,0 +416,5.29105,-1.11805,2.58116,-2.89714,7,255,0 +417,5.32371,-1.16067,2.63901,-2.90533,7,255,0 +418,5.35538,-1.20365,2.69714,-2.91397,7,255,0 +419,5.38603,-1.24701,2.75554,-2.9231,7,255,0 +420,5.41557,-1.29075,2.81421,-2.93275,7,255,0 +421,5.44396,-1.33489,2.87316,-2.94297,7,255,0 +422,5.47112,-1.37943,2.93238,-2.95382,7,255,0 +423,5.49698,-1.42437,2.99186,-2.96534,7,255,0 +424,5.52148,-1.46973,3.05161,-2.97757,7,255,0 +425,5.54449,-1.51552,3.11161,-2.99065,7,255,0 +426,5.56591,-1.56175,3.17188,-3.00459,7,255,0 +427,5.58555,-1.60846,3.23241,-3.0195,7,255,0 +428,5.60301,-1.65558,3.29306,-3.03606,7,255,0 +429,5.61879,-1.70323,3.35405,-3.05318,7,255,0 +430,5.63399,-1.75332,3.41792,-3.07028,7,255,0 +431,5.64777,-1.81252,3.49266,-3.0953,7,255,0 +432,5.65454,-1.84833,3.53759,-3.11273,7,255,0 +433,5.65454,-1.84833,3.53759,-3.11273,7,255,0 +434,5.65454,-1.84833,3.53759,-3.11273,7,255,0 +435,5.65454,-1.84833,3.53759,-3.11273,7,255,0 +436,5.65455,-1.84833,3.53759,-3.11273,7,255,0 +437,5.65455,-1.79891,3.53759,-3.11273,7,255,0 +438,5.65455,-1.72513,3.53759,-3.11273,7,255,0 +439,5.65455,-1.66957,3.53759,-3.11273,7,255,0 +440,5.65455,-1.61072,3.53759,-3.11273,7,255,0 +441,5.65455,-1.55011,3.53759,-3.11273,7,255,0 +442,5.65455,-1.49082,3.53759,-3.11273,7,255,0 +443,5.65456,-1.43129,3.53759,-3.11273,7,255,0 +444,5.65456,-1.37163,3.53759,-3.11273,7,255,0 +445,5.65456,-1.31206,3.53759,-3.11273,7,255,0 +446,5.65456,-1.25248,3.53759,-3.11273,7,255,0 +447,5.65456,-1.19289,3.53759,-3.11273,7,255,0 +448,5.65456,-1.13331,3.53759,-3.11273,7,255,0 +449,5.65456,-1.07373,3.53758,-3.11273,7,255,0 +450,5.65456,-1.01414,3.53758,-3.11273,7,255,0 +451,5.65456,-0.95456,3.53758,-3.11273,7,255,0 +452,5.65456,-0.89497,3.53758,-3.11273,7,255,0 +453,5.65456,-0.83539,3.53758,-3.11273,7,255,0 +454,5.65456,-0.7758,3.53758,-3.11273,7,255,0 +455,5.65456,-0.71622,3.53758,-3.11273,7,255,0 +456,5.65456,-0.65663,3.53758,-3.11273,7,255,0 +457,5.65456,-0.59705,3.53758,-3.11273,7,255,0 +458,5.65456,-0.53746,3.53758,-3.11273,7,255,0 +459,5.65456,-0.47788,3.53758,-3.11273,7,255,0 +460,5.65456,-0.41829,3.53758,-3.11273,7,255,0 +461,5.65456,-0.35871,3.53758,-3.11273,7,255,0 +462,5.65456,-0.29912,3.53758,-3.11273,7,255,0 +463,5.65456,-0.23954,3.53758,-3.11273,7,255,0 +464,5.65456,-0.17996,3.53758,-3.11273,7,255,0 +465,5.65456,-0.12037,3.53758,-3.11273,7,255,0 +466,5.65456,-0.06079,3.53758,-3.11273,7,255,0 +467,5.65456,-0.0012,3.53758,-3.11273,7,255,0 +468,5.65456,0.05838,3.53758,-3.11273,7,255,0 +469,5.65456,0.11797,3.53758,-3.11273,7,255,0 +470,5.65456,0.17755,3.53758,-3.11273,7,255,0 +471,5.65456,0.23714,3.53758,-3.11273,7,255,0 +472,5.65456,0.29672,3.53758,-3.11273,7,255,0 +473,5.65456,0.35631,3.53758,-3.11273,7,255,0 +474,5.65456,0.41589,3.53758,-3.11273,7,255,0 +475,5.65456,0.47548,3.53758,-3.11273,7,255,0 +476,5.65456,0.53506,3.53757,-3.11273,7,255,0 +477,5.65456,0.59465,3.53757,-3.11273,7,255,0 +478,5.65457,0.65423,3.53757,-3.11273,7,255,0 +479,5.65457,0.71381,3.53757,-3.11273,7,255,0 +480,5.65457,0.7734,3.53757,-3.11273,7,255,0 +481,5.65457,0.83298,3.53757,-3.11273,7,255,0 +482,5.65457,0.89257,3.53757,-3.11273,7,255,0 +483,5.65457,0.95215,3.53757,-3.11273,7,255,0 +484,5.65457,1.01174,3.53757,-3.11273,7,255,0 +485,5.65457,1.07132,3.53757,-3.11273,7,255,0 +486,5.65457,1.13091,3.53757,-3.11273,7,255,0 +487,5.65457,1.19049,3.53756,-3.11273,7,255,0 +488,5.65457,1.25008,3.53756,-3.11273,7,255,0 +489,5.65458,1.30966,3.53756,-3.11273,7,255,0 +490,5.65458,1.36925,3.53756,-3.11273,7,255,0 +491,5.65458,1.42883,3.53756,-3.11273,7,255,0 +492,5.65458,1.48841,3.53756,-3.11273,7,255,0 +493,5.65458,1.548,3.53756,-3.11273,7,255,0 +494,5.65458,1.60764,3.53756,-3.11273,7,255,0 +495,5.65459,1.66702,3.53755,-3.11486,7,255,0 +496,5.65574,1.72639,3.53755,3.11304,7,255,0 +497,5.66228,1.78358,3.53755,2.83459,7,255,0 +498,5.6874,1.82462,3.53755,2.39285,7,255,0 +499,5.73331,1.84438,3.53755,1.98516,7,255,0 +500,5.78976,1.85001,3.53755,1.7291,7,255,0 +501,5.84936,1.85028,3.53754,1.61928,7,255,0 +502,5.9089,1.85028,3.53754,1.59963,7,255,0 +503,5.96848,1.85028,3.53754,1.59962,7,255,0 +504,6.02807,1.85027,3.53754,1.59962,7,255,0 +505,6.08765,1.85027,3.53754,1.59962,7,255,0 +506,6.14723,1.85026,3.53753,1.59962,7,255,0 +507,6.20682,1.85025,3.53753,1.59962,7,255,0 +508,6.26641,1.85024,3.53753,1.59962,7,255,0 +509,6.32599,1.85023,3.53753,1.59961,7,255,0 +510,6.38558,1.85022,3.53753,1.59961,7,255,0 +511,6.44516,1.85021,3.53752,1.59961,7,255,0 +512,6.50474,1.8502,3.53752,1.59961,7,255,0 +513,6.56433,1.85019,3.53752,1.59961,7,255,0 +514,6.62391,1.85018,3.53752,1.59961,7,255,0 +515,6.6835,1.85017,3.53751,1.59961,7,255,0 +516,6.74308,1.85017,3.53751,1.59961,7,255,0 +517,6.80267,1.85016,3.53751,1.59961,7,255,0 +518,6.86225,1.85015,3.53751,1.5996,7,255,0 +519,6.92184,1.85014,3.5375,1.5996,7,255,0 +520,6.98142,1.85013,3.5375,1.5996,7,255,0 +521,7.04101,1.85013,3.5375,1.5996,7,255,0 +522,7.10059,1.85012,3.53749,1.5996,7,255,0 +523,7.16018,1.85011,3.53749,1.5996,7,255,0 +524,7.21976,1.8501,3.53749,1.5996,7,255,0 +525,7.27935,1.8501,3.53748,1.5996,7,255,0 +526,7.33893,1.85009,3.53748,1.5996,7,255,0 +527,7.39851,1.85008,3.53748,1.5996,7,255,0 +528,7.4581,1.85007,3.53747,1.5996,7,255,0 +529,7.51768,1.85007,3.53747,1.5996,7,255,0 +530,7.57727,1.85006,3.53747,1.5996,7,255,0 +531,7.63685,1.85005,3.53746,1.5996,7,255,0 +532,7.69644,1.85004,3.53746,1.5996,7,255,0 +533,7.75602,1.85004,3.53746,1.59959,7,255,0 +534,7.81561,1.85003,3.53745,1.59959,7,255,0 +535,7.87519,1.85002,3.53745,1.59959,7,255,0 +536,7.93478,1.85001,3.53745,1.59959,7,255,0 +537,7.99436,1.85,3.53744,1.59959,7,255,0 +538,8.05395,1.85,3.53744,1.59959,7,255,0 +539,8.11353,1.84999,3.53743,1.59959,7,255,0 +540,8.17312,1.84998,3.53743,1.59959,7,255,0 +541,8.2327,1.84997,3.53743,1.59959,7,255,0 +542,8.29228,1.84996,3.53742,1.59959,7,255,0 +543,8.35187,1.84996,3.53742,1.59959,7,255,0 +544,8.41145,1.84995,3.53741,1.59959,7,255,0 +545,8.47104,1.84994,3.53741,1.59959,7,255,0 +546,8.53063,1.84993,3.53741,1.59959,7,255,0 +547,8.59021,1.84992,3.5374,1.59959,7,255,0 +548,8.64979,1.84992,3.5374,1.59959,7,255,0 +549,8.70938,1.84991,3.5374,1.59959,7,255,0 +550,8.76896,1.8499,3.53739,1.59959,7,255,0 +551,8.82855,1.84989,3.53739,1.59959,7,255,0 +552,8.88813,1.84988,3.53738,1.59959,7,255,0 +553,8.94772,1.84988,3.53738,1.59959,7,255,0 +554,9.0073,1.84987,3.53738,1.59959,7,255,0 +555,9.06689,1.84986,3.53737,1.59959,7,255,0 +556,9.12647,1.84985,3.53737,1.59959,7,255,0 +557,9.18606,1.84985,3.53737,1.59959,7,255,0 +558,9.24564,1.84984,3.53736,1.59959,7,255,0 +559,9.30522,1.84983,3.53736,1.59959,7,255,0 +560,9.36481,1.84982,3.53735,1.59959,7,255,0 +561,9.42439,1.84981,3.53735,1.59959,7,255,0 +562,9.48398,1.84981,3.53735,1.59959,7,255,0 +563,9.54356,1.8498,3.53734,1.59959,7,255,0 +564,9.60315,1.84979,3.53734,1.59959,7,255,0 +565,9.66273,1.84978,3.53734,1.59959,7,255,0 +566,9.72232,1.84978,3.53733,1.59959,7,255,0 +567,9.7819,1.84977,3.53733,1.59959,7,255,0 +568,9.84149,1.84976,3.53733,1.59959,7,255,0 +569,9.90107,1.84975,3.53732,1.59959,7,255,0 +570,9.96066,1.84974,3.53732,1.59959,7,255,0 +571,10.02024,1.84974,3.53731,1.59959,7,255,0 +572,10.07983,1.84973,3.53731,1.59959,7,255,0 +573,10.13941,1.84972,3.53731,1.59959,7,255,0 +574,10.199,1.84971,3.5373,1.59959,7,255,0 +575,10.25858,1.8497,3.5373,1.59959,7,255,0 +576,10.31816,1.8497,3.53729,1.59959,7,255,0 +577,10.37775,1.84969,3.53729,1.59959,7,255,0 +578,10.43733,1.84968,3.53729,1.59959,7,255,0 +579,10.49692,1.84967,3.53728,1.59959,7,255,0 +580,10.5565,1.84967,3.53728,1.59959,7,255,0 +581,10.61609,1.84966,3.53727,1.59959,7,255,0 +582,10.67567,1.84965,3.53727,1.59959,7,255,0 +583,10.73526,1.84964,3.53727,1.59959,7,255,0 +584,10.79484,1.84963,3.53726,1.59959,7,255,0 +585,10.85443,1.84963,3.53726,1.59959,7,255,0 +586,10.91401,1.84962,3.53725,1.59959,7,255,0 +587,10.9736,1.84961,3.53725,1.59959,7,255,0 +588,11.03318,1.8496,3.53724,1.59959,7,255,0 +589,11.09277,1.8496,3.53724,1.59959,7,255,0 +590,11.15235,1.84959,3.53724,1.59959,7,255,0 +591,11.21194,1.84958,3.53723,1.59959,7,255,0 +592,11.27152,1.84957,3.53723,1.59959,7,255,0 +593,11.33111,1.84956,3.53723,1.59958,7,255,0 +594,11.39069,1.84956,3.53722,1.59958,7,255,0 +595,11.45028,1.84955,3.53722,1.59958,7,255,0 +596,11.50986,1.84954,3.53722,1.59958,7,255,0 +597,11.56944,1.84953,3.53721,1.59958,7,255,0 +598,11.62903,1.84952,3.53721,1.59958,7,255,0 +599,11.68862,1.84952,3.53721,1.59958,7,255,0 +600,11.7482,1.84951,3.53721,1.59958,7,255,0 +601,11.80779,1.8495,3.5372,1.59958,7,255,0 +602,11.86737,1.84949,3.5372,1.59958,7,255,0 +603,11.92696,1.84948,3.5372,1.59958,7,255,0 +604,11.98654,1.84947,3.5372,1.59957,7,255,0 +605,12.04613,1.84946,3.5372,1.59957,7,255,0 +606,12.10571,1.84946,3.5372,1.59957,7,255,0 +607,12.16529,1.84945,3.53719,1.59957,7,255,0 +608,12.22488,1.84944,3.53719,1.59957,7,255,0 +609,12.28446,1.84944,3.53719,1.59957,7,255,0 +610,12.34405,1.84943,3.53719,1.59957,7,255,0 +611,12.40363,1.84943,3.53719,1.59957,7,255,0 +612,12.46322,1.84943,3.53719,1.59956,7,255,0 +613,12.5228,1.84942,3.53719,1.59956,7,255,0 +614,12.58239,1.84942,3.53719,1.59956,7,255,0 +615,12.64197,1.84942,3.53719,1.59956,7,255,0 +616,12.70156,1.84941,3.53719,1.59956,7,255,0 +617,12.76113,1.8494,3.53718,1.59956,7,255,0 +618,12.82075,1.84938,3.53718,1.59952,7,255,0 +619,12.88031,1.84936,3.53718,1.59853,7,255,0 +620,12.93977,1.84932,3.53718,1.57955,7,255,0 +621,12.99975,1.8492,3.53718,1.53822,7,255,0 +622,13.05807,1.84683,3.53718,1.39467,7,255,0 +623,13.10874,1.8321,3.53718,1.02962,7,255,0 +624,13.14285,1.79679,3.53718,0.55984,7,255,0 +625,13.15555,1.74517,3.53718,0.18196,7,255,0 +626,13.15648,1.6865,3.53718,0.04504,7,255,0 +627,13.15648,1.62648,3.53718,0.0302,7,255,0 +628,13.15648,1.56702,3.53718,0.02887,7,255,0 +629,13.15648,1.50746,3.53717,0.02886,7,255,0 +630,13.15648,1.44785,3.53717,0.02886,7,255,0 +631,13.15648,1.38827,3.53717,0.02886,7,255,0 +632,13.15648,1.32869,3.53717,0.02886,7,255,0 +633,13.15648,1.2691,3.53717,0.02885,7,255,0 +634,13.15648,1.20952,3.53717,0.02885,7,255,0 +635,13.15648,1.14993,3.53717,0.02885,7,255,0 +636,13.15649,1.09035,3.53717,0.02885,7,255,0 +637,13.15648,1.03076,3.53717,0.02885,7,255,0 +638,13.15649,0.97118,3.53717,0.02885,7,255,0 +639,13.15649,0.9116,3.53717,0.02884,7,255,0 +640,13.15649,0.85201,3.53717,0.02884,7,255,0 +641,13.15649,0.79243,3.53717,0.02884,7,255,0 +642,13.15649,0.73284,3.53716,0.02884,7,255,0 +643,13.15649,0.67326,3.53716,0.02884,7,255,0 +644,13.15649,0.61367,3.53716,0.02884,7,255,0 +645,13.15649,0.55409,3.53716,0.02884,7,255,0 +646,13.15649,0.4945,3.53716,0.02884,7,255,0 +647,13.15649,0.43492,3.53716,0.02883,7,255,0 +648,13.15649,0.37533,3.53716,0.02883,7,255,0 +649,13.1565,0.31575,3.53716,0.02883,7,255,0 +650,13.1565,0.25616,3.53716,0.02883,7,255,0 +651,13.1565,0.19658,3.53716,0.02883,7,255,0 +652,13.1565,0.137,3.53716,0.02883,7,255,0 +653,13.1565,0.07741,3.53716,0.02883,7,255,0 +654,13.1565,0.01783,3.53716,0.02883,7,255,0 +655,13.1565,-0.04176,3.53716,0.02883,7,255,0 +656,13.1565,-0.10134,3.53716,0.02883,7,255,0 +657,13.1565,-0.16093,3.53715,0.02883,7,255,0 +658,13.1565,-0.22051,3.53715,0.02883,7,255,0 +659,13.1565,-0.2801,3.53715,0.02883,7,255,0 +660,13.1565,-0.33968,3.53715,0.02883,7,255,0 +661,13.15651,-0.39926,3.53715,0.02883,7,255,0 +662,13.15651,-0.45885,3.53715,0.02883,7,255,0 +663,13.15651,-0.51843,3.53715,0.02883,7,255,0 +664,13.15651,-0.57802,3.53715,0.02883,7,255,0 +665,13.15651,-0.6376,3.53715,0.02883,7,255,0 +666,13.15651,-0.69719,3.53715,0.02883,7,255,0 +667,13.15651,-0.75677,3.53715,0.02882,7,255,0 +668,13.15651,-0.81636,3.53715,0.02882,7,255,0 +669,13.15651,-0.87594,3.53715,0.02882,7,255,0 +670,13.15651,-0.93553,3.53715,0.02882,7,255,0 +671,13.15651,-0.99511,3.53715,0.02882,7,255,0 +672,13.15651,-1.05469,3.53715,0.02882,7,255,0 +673,13.15651,-1.11428,3.53715,0.02882,7,255,0 +674,13.15652,-1.17386,3.53715,0.02882,7,255,0 +675,13.15652,-1.23344,3.53714,0.02882,7,255,0 +676,13.15652,-1.29303,3.53714,0.02882,7,255,0 +677,13.15652,-1.35262,3.53714,0.02882,7,255,0 +678,13.15652,-1.41219,3.53714,0.02882,7,255,0 +679,13.15652,-1.47179,3.53714,0.02882,7,255,0 +680,13.15652,-1.53141,3.53714,0.02882,7,255,0 +681,13.15652,-1.59085,3.53714,0.02882,7,255,0 +682,13.15652,-1.65054,3.53714,0.02774,7,255,0 +683,13.15644,-1.71064,3.53714,-0.01199,7,255,0 +684,13.15416,-1.76717,3.53714,-0.20779,7,255,0 +685,13.13611,-1.81192,3.53714,-0.58791,7,255,0 +686,13.09665,-1.83671,3.53714,-1.05263,7,255,0 +687,13.04295,-1.84623,3.53714,-1.37746,7,255,0 +688,12.98346,-1.84808,3.53714,-1.50019,7,255,0 +689,12.9238,-1.84831,3.53714,-1.53617,7,255,0 +690,12.86434,-1.84831,3.53714,-1.54141,7,255,0 +691,12.80472,-1.84831,3.53714,-1.54193,7,255,0 +692,12.74513,-1.84831,3.53714,-1.54193,7,255,0 +693,12.68556,-1.84831,3.53714,-1.54193,7,255,0 +694,12.62597,-1.84831,3.53714,-1.54193,7,255,0 +695,12.56638,-1.84831,3.53714,-1.54193,7,255,0 +696,12.5068,-1.84831,3.53714,-1.54193,7,255,0 +697,12.44722,-1.84831,3.53714,-1.54193,7,255,0 +698,12.38763,-1.84831,3.53713,-1.54193,7,255,0 +699,12.32805,-1.84831,3.53713,-1.54193,7,255,0 +700,12.26846,-1.84831,3.53713,-1.54193,7,255,0 +701,12.20887,-1.84831,3.53713,-1.54193,7,255,0 +702,12.14929,-1.84831,3.53713,-1.54193,7,255,0 +703,12.08971,-1.84831,3.53713,-1.54193,7,255,0 +704,12.03012,-1.84831,3.53713,-1.54193,7,255,0 +705,11.97054,-1.84831,3.53713,-1.54193,7,255,0 +706,11.91095,-1.84831,3.53713,-1.54193,7,255,0 +707,11.85137,-1.84831,3.53713,-1.54193,7,255,0 +708,11.79178,-1.84831,3.53713,-1.54193,7,255,0 +709,11.7322,-1.84831,3.53713,-1.54193,7,255,0 +710,11.67261,-1.84831,3.53713,-1.54193,7,255,0 +711,11.61303,-1.84831,3.53713,-1.54193,7,255,0 +712,11.55344,-1.84831,3.53713,-1.54193,7,255,0 +713,11.49385,-1.84831,3.53713,-1.54193,7,255,0 +714,11.43427,-1.84831,3.53713,-1.54193,7,255,0 +715,11.37469,-1.84831,3.53713,-1.54193,7,255,0 +716,11.3151,-1.84831,3.53713,-1.54193,7,255,0 +717,11.25552,-1.84831,3.53713,-1.54193,7,255,0 +718,11.19593,-1.84832,3.53713,-1.54193,7,255,0 +719,11.13635,-1.84832,3.53713,-1.54193,7,255,0 +720,11.07676,-1.84832,3.53713,-1.54193,7,255,0 +721,11.01718,-1.84832,3.53713,-1.54193,7,255,0 +722,10.95759,-1.84832,3.53713,-1.54193,7,255,0 +723,10.89801,-1.84832,3.53713,-1.54193,7,255,0 +724,10.83842,-1.84832,3.53713,-1.54193,7,255,0 +725,10.77884,-1.84832,3.53713,-1.54193,7,255,0 +726,10.71925,-1.84832,3.53713,-1.54193,7,255,0 +727,10.65967,-1.84832,3.53713,-1.54193,7,255,0 +728,10.60008,-1.84832,3.53713,-1.54193,7,255,0 +729,10.5405,-1.84832,3.53713,-1.54193,7,255,0 +730,10.48091,-1.84832,3.53713,-1.54193,7,255,0 +731,10.42133,-1.84832,3.53713,-1.54193,7,255,0 +732,10.36174,-1.84832,3.53712,-1.54193,7,255,0 +733,10.30216,-1.84832,3.53712,-1.54193,7,255,0 +734,10.24257,-1.84832,3.53712,-1.54193,7,255,0 +735,10.18299,-1.84832,3.53712,-1.54193,7,255,0 +736,10.1234,-1.84832,3.53712,-1.54193,7,255,0 +737,10.06382,-1.84832,3.53712,-1.54193,7,255,0 +738,10.00423,-1.84832,3.53712,-1.54193,7,255,0 +739,9.94465,-1.84832,3.53712,-1.54193,7,255,0 +740,9.88506,-1.84832,3.53712,-1.54193,7,255,0 +741,9.82548,-1.84832,3.53712,-1.54193,7,255,0 +742,9.76589,-1.84832,3.53712,-1.54193,7,255,0 +743,9.70631,-1.84832,3.53712,-1.54193,7,255,0 +744,9.64673,-1.84832,3.53712,-1.54193,7,255,0 +745,9.58714,-1.84832,3.53712,-1.54193,7,255,0 +746,9.52755,-1.84832,3.53712,-1.54193,7,255,0 +747,9.46797,-1.84832,3.53712,-1.54193,7,255,0 +748,9.40839,-1.84832,3.53712,-1.54193,7,255,0 +749,9.3488,-1.84832,3.53712,-1.54193,7,255,0 +750,9.28922,-1.84832,3.53712,-1.54193,7,255,0 +751,9.22963,-1.84832,3.53712,-1.54193,7,255,0 +752,9.17004,-1.84832,3.53712,-1.54193,7,255,0 +753,9.11046,-1.84832,3.53712,-1.54193,7,255,0 +754,9.05088,-1.84832,3.53712,-1.54193,7,255,0 +755,8.99129,-1.84832,3.53712,-1.54193,7,255,0 +756,8.93171,-1.84832,3.53712,-1.54193,7,255,0 +757,8.87212,-1.84832,3.53712,-1.54193,7,255,0 +758,8.81254,-1.84832,3.53712,-1.54193,7,255,0 +759,8.75295,-1.84832,3.53712,-1.54193,7,255,0 +760,8.69337,-1.84832,3.53712,-1.54193,7,255,0 +761,8.63378,-1.84832,3.53712,-1.54193,7,255,0 +762,8.5742,-1.84832,3.53712,-1.54193,7,255,0 +763,8.51461,-1.84832,3.53712,-1.54193,7,255,0 +764,8.45503,-1.84832,3.53712,-1.54193,7,255,0 +765,8.39544,-1.84832,3.53712,-1.54193,7,255,0 +766,8.33586,-1.84832,3.53712,-1.54193,7,255,0 +767,8.27627,-1.84832,3.53712,-1.54193,7,255,0 +768,8.21669,-1.84832,3.53712,-1.54193,7,255,0 +769,8.1571,-1.84832,3.53712,-1.54193,7,255,0 +770,8.09752,-1.84832,3.53712,-1.54193,7,255,0 +771,8.03793,-1.84832,3.53712,-1.54193,7,255,0 +772,7.97835,-1.84832,3.53712,-1.54193,7,255,0 +773,7.91876,-1.84832,3.53712,-1.54193,7,255,0 +774,7.85918,-1.84832,3.53712,-1.54193,7,255,0 +775,7.79959,-1.84832,3.53712,-1.54193,7,255,0 +776,7.74001,-1.84832,3.53712,-1.54193,7,255,0 +777,7.68043,-1.84832,3.53712,-1.54193,7,255,0 +778,7.62084,-1.84832,3.53712,-1.54193,7,255,0 +779,7.56125,-1.84832,3.53712,-1.54193,7,255,0 +780,7.50167,-1.84832,3.53712,-1.54193,7,255,0 +781,7.44208,-1.84832,3.53712,-1.54193,7,255,0 +782,7.3825,-1.84832,3.53712,-1.54193,7,255,0 +783,7.32291,-1.84832,3.53712,-1.54193,7,255,0 +784,7.26333,-1.84832,3.53712,-1.54193,7,255,0 +785,7.20374,-1.84832,3.53712,-1.54193,7,255,0 +786,7.14416,-1.84832,3.53712,-1.54193,7,255,0 +787,7.08457,-1.84832,3.53712,-1.54193,7,255,0 +788,7.02499,-1.84832,3.53712,-1.54193,7,255,0 +789,6.96541,-1.84832,3.53712,-1.54193,7,255,0 +790,6.90582,-1.84832,3.53712,-1.54193,7,255,0 +791,6.84623,-1.84832,3.53712,-1.54193,7,255,0 +792,6.78665,-1.84833,3.53712,-1.54193,7,255,0 +793,6.72706,-1.84833,3.53712,-1.54193,7,255,0 +794,6.66748,-1.84833,3.53712,-1.54193,7,255,0 +795,6.6079,-1.84833,3.53712,-1.54193,7,255,0 +796,6.54831,-1.84833,3.53712,-1.54193,7,255,0 +797,6.48873,-1.84833,3.53712,-1.54193,7,255,0 +798,6.42914,-1.84833,3.53712,-1.54193,7,255,0 +799,6.36956,-1.84833,3.53712,-1.54193,7,255,0 +800,6.30997,-1.84833,3.53712,-1.54193,7,255,0 +801,6.25038,-1.84833,3.53712,-1.54193,7,255,0 +802,6.1908,-1.84833,3.53712,-1.54193,7,255,0 +803,6.13123,-1.84833,3.53712,-1.54193,7,255,0 +804,6.07158,-1.84833,3.53712,-1.54193,7,255,0 +805,6.01204,-1.84833,3.53712,-1.54193,7,255,0 +806,5.95275,-1.84833,3.53712,-1.54193,7,255,0 +807,5.89215,-1.84833,3.53712,-1.54193,7,255,0 +808,5.83329,-1.84833,3.53712,-1.54193,7,255,0 +809,5.77773,-1.84833,3.53712,-1.54193,7,255,0 +810,5.70396,-1.84833,3.53712,-1.54193,7,255,0 +811,5.65454,-1.84833,3.53712,-1.54193,7,255,0 +812,5.65454,-1.84833,3.53712,-1.54193,7,255,0 +813,5.65454,-1.84833,3.53712,-1.54193,7,255,0 +814,5.65454,-1.84833,3.53712,-1.54193,7,255,0 +815,5.65454,-1.84833,3.53712,-1.54193,7,255,0 +816,5.65454,-1.84833,3.53712,-1.54193,7,255,0 +817,5.65454,-1.84833,3.53712,-1.54193,7,255,0 +818,5.65454,-1.84833,3.53712,-1.54193,7,255,0 +819,5.65454,-1.84833,3.53712,-1.54193,7,255,0 +820,5.65454,-1.84833,3.53712,-1.54193,7,255,0 +821,5.65454,-1.84833,3.53712,-1.54193,7,255,0 +822,5.65454,-1.84833,3.53712,-1.54193,7,255,0 +823,5.65454,-1.84833,3.53712,-1.54193,7,255,0 +824,5.65454,-1.84833,3.53712,-1.54193,7,255,0 +825,5.65454,-1.84833,3.53712,-1.54193,7,255,0 +826,5.65454,-1.84833,3.53712,-1.54193,7,255,0 +827,5.65454,-1.84833,3.53712,-1.54193,7,255,0 +828,5.65454,-1.84833,3.53712,-1.54193,7,255,0 +829,5.65454,-1.84833,3.53712,-1.54193,7,255,0 +830,5.65454,-1.84833,3.53712,-1.54193,7,255,0 +831,5.65454,-1.84833,3.53712,-1.54193,7,255,0 +832,5.65454,-1.84833,3.53712,-1.54193,7,255,0 +833,5.65454,-1.84833,3.53712,-1.54193,7,255,0 +834,5.65454,-1.84833,3.53712,-1.54193,7,255,0 +835,5.65454,-1.84833,3.53712,-1.54193,7,255,0 +836,5.65454,-1.84833,3.53712,-1.54193,7,255,0 +837,5.65454,-1.84833,3.53712,-1.54193,7,255,0 +838,5.65454,-1.84833,3.53712,-1.54193,7,255,0 +839,5.65454,-1.84833,3.53712,-1.54193,7,255,0 +840,5.65454,-1.84833,3.53712,-1.54193,7,255,0 +841,5.65454,-1.84833,3.53712,-1.54193,7,255,0 +842,5.65454,-1.84833,3.53715,-1.54193,7,255,0 +843,5.65454,-1.84833,3.53721,-1.54193,7,255,0 +844,5.65454,-1.84833,3.53726,-1.54193,7,255,0 +845,5.65454,-1.84833,3.5373,-1.54193,7,255,0 +846,5.65454,-1.84833,3.53735,-1.54193,7,255,0 +847,5.65454,-1.84833,3.5374,-1.54193,7,255,0 +848,5.65454,-1.84833,3.53746,-1.54193,7,255,0 +849,5.65454,-1.84833,3.53749,-1.54193,7,255,0 +850,5.65454,-1.84833,3.53751,-1.54193,7,255,0 +851,5.65454,-1.84833,3.53752,-1.54193,7,255,0 +852,5.65454,-1.84833,3.53753,-3.11273,7,255,0 +853,5.65454,-1.84833,3.53754,-3.11273,7,255,0 +854,5.65454,-1.84833,3.53755,-3.11273,7,255,0 +855,5.65454,-1.84833,3.53756,-3.11273,7,255,0 +856,5.65454,-1.84833,3.53756,-3.11273,7,255,0 +857,5.65454,-1.84833,3.53757,-3.11273,7,255,0 +858,5.65454,-1.84833,3.53757,-3.11273,7,255,0 +859,5.65454,-1.84833,3.53758,-3.11273,7,255,0 +860,5.65454,-1.84833,3.53758,-3.11273,7,255,0 +861,5.65454,-1.84833,3.53758,-3.11273,7,255,0 +862,5.65454,-1.84833,3.53758,-3.11273,7,255,0 +863,5.65454,-1.84833,3.53758,-3.11273,7,255,0 +864,5.65454,-1.84833,3.53759,-3.11273,7,255,0 +865,5.65454,-1.84833,3.53759,-3.11273,7,255,0 +866,5.65454,-1.84833,3.53759,-3.11273,7,255,0 +867,5.65454,-1.84833,3.53759,-3.11273,7,255,0 +868,5.65049,-1.82643,3.51012,-3.10193,7,255,0 +869,5.6412,-1.78182,3.454,-3.0815,7,255,0 +870,5.63027,-1.74017,3.4012,-3.06561,7,255,0 +871,5.61879,-1.70323,3.35405,-3.05319,7,255,0 +872,5.60705,-1.66743,3.30825,-3.0404,7,255,0 +873,5.59457,-1.63197,3.26272,-3.02754,7,255,0 +874,5.58083,-1.59674,3.21726,-3.01564,7,255,0 +875,5.56591,-1.56175,3.17188,-3.00459,7,255,0 +876,5.55,-1.52703,3.12665,-2.99406,7,255,0 +877,5.53318,-1.49257,3.08158,-2.984,7,255,0 +878,5.51549,-1.45835,3.03665,-2.97444,7,255,0 +879,5.49698,-1.42437,2.99186,-2.96534,7,255,0 +880,5.47771,-1.39062,2.94722,-2.95664,7,255,0 +881,5.45769,-1.35711,2.90273,-2.94832,7,255,0 +882,5.43697,-1.32382,2.85839,-2.94036,7,255,0 +883,5.41557,-1.29075,2.81421,-2.93275,7,255,0 +884,5.39352,-1.25791,2.77018,-2.92546,7,255,0 +885,5.37084,-1.22528,2.7263,-2.91847,7,255,0 +886,5.34756,-1.19287,2.68258,-2.91176,7,255,0 +887,5.32371,-1.16067,2.63901,-2.90533,7,255,0 +888,5.29931,-1.12867,2.5956,-2.89914,7,255,0 +889,5.27437,-1.09687,2.55233,-2.8932,7,255,0 +890,5.24894,-1.06527,2.50922,-2.88748,7,255,0 +891,5.22301,-1.03387,2.46625,-2.88196,7,255,0 +892,5.19661,-1.00265,2.42343,-2.87665,7,255,0 +893,5.16977,-0.97162,2.38076,-2.87153,7,255,0 +894,5.1425,-0.94076,2.33823,-2.86659,7,255,0 +895,5.11481,-0.91009,2.29584,-2.86182,7,255,0 +896,5.08672,-0.87958,2.25359,-2.85721,7,255,0 +897,5.05825,-0.84924,2.21147,-2.85275,7,255,0 +898,5.02941,-0.81907,2.16949,-2.84843,7,255,0 +899,5.00021,-0.78905,2.12764,-2.84425,7,255,0 +900,4.97068,-0.75919,2.08592,-2.84019,7,255,0 +901,4.94082,-0.72948,2.04432,-2.83626,7,255,0 +902,4.91064,-0.69991,2.00285,-2.83245,7,255,0 +903,4.88015,-0.67049,1.9615,-2.82875,7,255,0 +904,4.84938,-0.64121,1.92026,-2.82515,7,255,0 +905,4.81832,-0.61207,1.87915,-2.82165,7,255,0 +906,4.78698,-0.58306,1.83814,-2.81825,7,255,0 +907,4.75539,-0.55417,1.79725,-2.81494,7,255,0 +908,4.72354,-0.52542,1.75647,-2.81171,7,255,0 +909,4.69144,-0.49678,1.71579,-2.80857,7,255,0 +910,4.65911,-0.46827,1.67521,-2.80551,7,255,0 +911,4.62655,-0.43987,1.63474,-2.80252,7,255,0 +912,4.59377,-0.41158,1.59436,-2.79961,7,255,0 +913,4.56077,-0.38341,1.55408,-2.79676,7,255,0 +914,4.52756,-0.35535,1.5139,-2.79398,7,255,0 +915,4.49413,-0.3274,1.47382,-2.79127,7,255,0 +916,4.46048,-0.29957,1.43385,-2.78864,7,255,0 +917,4.4266,-0.27186,1.39398,-2.78607,7,255,0 +918,4.39247,-0.24428,1.35423,-2.78355,7,255,0 +919,4.3582,-0.21679,1.31456,-2.78112,7,255,0 +920,4.32383,-0.18938,1.27495,-2.77877,7,255,0 +921,4.28909,-0.16211,1.23548,-2.77639,7,255,0 +922,4.2539,-0.13496,1.19615,-2.77395,7,255,0 +923,4.21934,-0.10777,1.15672,-2.77183,7,255,0 +924,4.18494,-0.07986,1.11621,-2.77023,7,255,0 +925,4.14273,-0.04879,1.07106,-2.76811,7,255,0 +926,4.08671,-0.016,1.02331,-2.76493,7,255,0 +927,4.02606,0.0,1.0,-2.76315,7,255,0 +928,3.97066,0.0,1.0,-2.76315,7,255,0 +929,3.91803,0.0,1.0,-2.76315,7,255,0 +930,3.86381,0.0,1.0,-2.76315,7,255,0 +931,3.80863,0.0,1.0,-2.76315,7,255,0 +932,3.75394,0.0,1.0,-2.76315,7,255,0 +933,3.69956,0.0,1.0,-2.76315,7,255,0 +934,3.64504,0.0,1.0,-2.76315,7,255,0 +935,3.59042,0.0,1.0,-2.76315,7,255,0 +936,3.53584,0.0,1.0,-2.76315,7,255,0 +937,3.48129,0.0,1.0,-2.76315,7,255,0 +938,3.42673,0.0,1.0,-2.76315,7,255,0 +939,3.37216,0.0,1.0,-2.76315,7,255,0 +940,3.31759,0.0,1.0,-2.76315,7,255,0 +941,3.26303,0.0,1.0,-2.76315,7,255,0 +942,3.20846,0.0,1.0,-2.76315,7,255,0 +943,3.15389,0.0,1.0,-2.76315,7,255,0 +944,3.09933,0.0,1.0,-2.76315,7,255,0 +945,3.04476,0.0,1.0,-2.76315,7,255,0 +946,2.9902,0.0,1.0,-2.76315,7,255,0 +947,2.93563,0.0,1.0,-2.76315,7,255,0 +948,2.88107,0.0,1.0,-2.76315,7,255,0 +949,2.8265,0.0,1.0,-2.76315,7,255,0 +950,2.77194,0.0,1.0,-2.76315,7,255,0 +951,2.71737,0.0,1.0,-2.76315,7,255,0 +952,2.6628,0.0,1.0,-2.76315,7,255,0 +953,2.60824,0.0,1.0,-2.76315,7,255,0 +954,2.55367,0.0,1.0,-2.76315,7,255,0 +955,2.49911,0.0,1.0,-2.76315,7,255,0 +956,2.44454,0.0,1.0,-2.76315,7,255,0 +957,2.38998,0.0,1.0,-2.76315,7,255,0 +958,2.33541,0.0,1.0,-2.76315,7,255,0 +959,2.28084,0.0,1.0,-2.76315,7,255,0 +960,2.22628,0.0,1.0,-2.76315,7,255,0 +961,2.17171,0.0,1.0,-2.76315,7,255,0 +962,2.11715,0.0,1.0,-2.76315,7,255,0 +963,2.06258,0.0,1.0,-2.76315,7,255,0 +964,2.00802,0.0,1.0,-2.76315,7,255,0 +965,1.95345,0.0,1.0,-2.76315,7,255,0 +966,1.89888,0.0,1.0,-2.76315,7,255,0 +967,1.84432,0.0,1.0,-2.76315,7,255,0 +968,1.78975,0.0,1.0,-2.76315,7,255,0 +969,1.73519,0.0,1.0,-2.76315,7,255,0 +970,1.68063,0.0,1.0,-2.76315,7,255,0 +971,1.62606,0.0,1.0,-2.76315,7,255,0 +972,1.57149,0.0,1.0,-2.76315,7,255,0 +973,1.51693,0.0,1.0,-2.76315,7,255,0 +974,1.46236,0.0,1.0,-2.76315,7,255,0 +975,1.4078,0.0,1.0,-2.76315,7,255,0 +976,1.35323,0.0,1.0,-2.76315,7,255,0 +977,1.29867,0.0,1.0,-2.76315,7,255,0 +978,1.2441,0.0,1.0,-2.76315,7,255,0 +979,1.18953,0.0,1.0,-2.76315,7,255,0 +980,1.13497,0.0,1.0,-2.76315,7,255,0 +981,1.0804,0.0,1.0,-2.76315,7,255,0 +982,1.02584,0.0,1.0,-2.76315,7,255,0 +983,0.97127,0.0,1.0,-2.76315,7,255,0 +984,0.9167,0.0,1.0,-2.76315,7,255,0 +985,0.86214,0.0,1.0,-2.76315,7,255,0 +986,0.80757,0.0,1.0,-2.76315,7,255,0 +987,0.75301,0.0,1.0,-2.76315,7,255,0 +988,0.69844,0.0,1.0,-2.76315,7,255,0 +989,0.64388,0.0,1.0,-2.76315,7,255,0 +990,0.58931,0.0,1.0,-2.76315,7,255,0 +991,0.53475,0.0,1.0,-2.76315,7,255,0 +992,0.48018,0.0,1.0,-2.76315,7,255,0 +993,0.42561,0.0,1.0,-2.76315,7,255,0 +994,0.37105,0.0,1.0,-2.76315,7,255,0 +995,0.31648,0.0,1.0,-2.76315,7,255,0 +996,0.26192,0.0,1.0,-2.76315,7,255,0 +997,0.20735,0.0,1.0,-2.76315,7,255,0 +998,0.15279,0.0,1.0,-2.76315,7,255,0 +999,0.09822,0.0,1.0,-2.76315,7,255,0 +1000,0.04365,0.0,1.0,-2.76315,7,255,0 +1001,-0.01091,0.0,1.0,-2.76315,7,255,0 +1002,-0.06547,0.0,1.0,-2.76315,7,255,0 +1003,-0.12004,0.0,1.0,-2.76315,7,255,0 +1004,-0.17463,0.0,1.0,-2.76315,7,255,0 +1005,-0.22918,0.0,1.0,-2.76315,7,255,0 +1006,-0.28366,0.0,1.0,-2.76315,7,255,0 +1007,-0.33828,0.0,1.0,-2.76315,7,255,0 +1008,-0.39314,0.0,1.0,-2.76315,7,255,0 +1009,-0.44752,0.0,1.0,-2.76315,7,255,0 +1010,-0.50114,0.0,1.0,-2.76315,7,255,0 +1011,-0.55624,0.0,1.0,-2.76315,7,255,0 +1012,-0.61376,0.0,1.0,-2.76315,7,255,0 +1013,-0.66693,0.0,1.0,-2.76315,7,255,0 +1014,-0.7127,0.0,1.0,-2.76315,7,255,0 +1015,-0.77046,0.0,1.0,-2.76315,7,255,0 +1016,-0.85023,0.00069,1.0,-2.76315,7,255,0 +1017,-0.90212,0.0055,1.0,-2.76315,7,255,0 +1018,-0.9114,0.03656,1.0,-2.76315,7,255,0 +1019,-0.91299,0.09409,1.0,-2.76315,7,255,0 +1020,-0.91465,0.15423,1.0,-2.76315,7,255,0 +1021,-0.917,0.21249,1.0,-2.76315,7,255,0 +1022,-0.91909,0.26934,1.0,-2.76315,7,255,0 +1023,-0.92101,0.32571,1.0,-2.76315,7,255,0 +1024,-0.92299,0.3824,1.0,-2.76315,7,255,0 +1025,-0.92502,0.43932,1.0,-2.76315,7,255,0 +1026,-0.92703,0.4962,1.0,-2.76315,7,255,0 +1027,-0.92902,0.55304,1.0,-2.76315,7,255,0 +1028,-0.93103,0.60986,1.0,-2.76315,7,255,0 +1029,-0.93303,0.66669,1.0,-2.76315,7,255,0 +1030,-0.93504,0.72353,1.0,-2.76315,7,255,0 +1031,-0.93704,0.78037,1.0,-2.76315,7,255,0 +1032,-0.93905,0.8372,1.0,-2.76315,7,255,0 +1033,-0.94105,0.89403,1.0,-2.76315,7,255,0 +1034,-0.94306,0.95087,1.0,-2.76315,7,255,0 +1035,-0.94506,1.00771,1.0,-2.76315,7,255,0 +1036,-0.94706,1.06454,1.0,-2.76315,7,255,0 +1037,-0.94907,1.12137,1.0,-2.76315,7,255,0 +1038,-0.95107,1.17821,1.0,-2.76315,7,255,0 +1039,-0.95308,1.23504,1.0,-2.76315,7,255,0 +1040,-0.95508,1.29188,1.0,-2.76315,7,255,0 +1041,-0.95709,1.34871,1.0,-2.76315,7,255,0 +1042,-0.95909,1.40555,1.0,-2.76315,7,255,0 +1043,-0.96109,1.46238,1.0,-2.76315,7,255,0 +1044,-0.9631,1.51922,1.0,-2.76315,7,255,0 +1045,-0.9651,1.57605,1.0,-2.76315,7,255,0 +1046,-0.96711,1.63289,1.0,-2.76315,7,255,0 +1047,-0.96911,1.68972,1.0,-2.76315,7,255,0 +1048,-0.97112,1.74656,1.0,-2.76315,7,255,0 +1049,-0.97312,1.80339,1.0,-2.76315,7,255,0 +1050,-0.97512,1.86024,1.0,-2.76315,7,255,0 +1051,-0.97713,1.91706,1.0,-2.76315,7,255,0 +1052,-0.97913,1.97386,1.0,-2.76315,7,255,0 +1053,-0.98114,2.03073,1.0,-2.76315,7,255,0 +1054,-0.98314,2.08772,1.0,-2.76315,7,255,0 +1055,-0.98515,2.14444,1.0,-2.76315,7,255,0 +1056,-0.98715,2.20073,1.0,-2.76315,7,255,0 +1057,-0.98915,2.2579,1.0,-2.76315,7,255,0 +1058,-0.99115,2.3165,1.0,-2.76315,7,255,0 +1059,-0.99316,2.37257,1.0,-2.76315,7,255,0 +1060,-0.99521,2.42414,1.0,-2.76315,7,255,0 +1061,-0.99521000000000004,2.42414,1.0,0,0,0,0 +1062,-1.0301400000000001,2.4494266666666666,1.0,0,0,0,0 +1063,-1.06507,2.4747133333333333,1.0,0,0,0,0 +1064,-1.1000000000000001,2.5,1.0,0,0,0,0 diff --git a/tests/animation_3.csv b/tests/animation_3.csv new file mode 100644 index 0000000..1f725db --- /dev/null +++ b/tests/animation_3.csv @@ -0,0 +1,12 @@ +route +20,0.0,0.0,1.0,0.0,0,204,2 +21,0.02217,0.0,1.0,0.0,0,204,2 +22,0.08889,0.0,1.0,0.0,0,204,2 +23,0.19737,0.0,1.0,0.0,0,204,2 +24,0.33926,0.0,1.0,0.0,0,204,2 +25,0.5,0.0,1.0,0.0,0,204,2 +26,0.66074,0.0,1.0,0.0,0,204,2 +27,0.80263,0.0,1.0,0.0,0,204,2 +28,0.91111,0.0,1.0,0.0,0,204,2 +29,0.97783,0.0,1.0,0.0,0,204,2 +30,1.0,0.0,1.0,0.0,0,204,2 diff --git a/tests/animation_test.py b/tests/animation_test.py new file mode 100644 index 0000000..decf80a --- /dev/null +++ b/tests/animation_test.py @@ -0,0 +1,76 @@ +import os +import sys +import shutil + +# Add parent dir to PATH to import config +import inspect +current_dir = os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe()))) +parent_dir = os.path.dirname(current_dir) +sys.path.insert(0, parent_dir) +sys.path.insert(0, os.path.join(parent_dir,"Drone")) + +from config import ConfigManager + +config_path = 'animation_config/config' +spec_path = os.path.join(config_path,'spec') +if not os.path.exists(spec_path): + try: + os.makedirs(spec_path) + except OSError: + print("Creation of the directory {} failed".format(spec_path)) + else: + print("Successfully created the directory {}".format(spec_path)) + +shutil.copy("../Drone/config/spec/configspec_client.ini", spec_path) + +config = ConfigManager() +config.load_config_and_spec(os.path.join(config_path,'client.ini')) + +assert config.config_name == "client" + +import animation_lib + +a = animation_lib.Animation(config, "animation_1.csv") + +assert a.id == 'basic' +assert a.original_frames[0].get_pos() == [0.,0.,0.] +assert a.original_frames[0].get_color() == [204,2,0] +assert a.original_frames[0].pose_is_valid() + +# print animation_lib.get_numbers(a.static_begin_frames) +# print animation_lib.get_numbers(a.takeoff_frames) +# print animation_lib.get_numbers(a.route_frames) +# print animation_lib.get_numbers(a.land_frames) +# print animation_lib.get_numbers(a.static_end_frames) + +assert animation_lib.get_numbers(a.static_begin_frames) == range(1,11) +assert animation_lib.get_numbers(a.takeoff_frames) == range(11,21) +assert animation_lib.get_numbers(a.route_frames) == range(21,31) +assert animation_lib.get_numbers(a.land_frames) == range(31, 41) +assert animation_lib.get_numbers(a.static_end_frames) == range(41, 51) + +a.update_frames(config, "animation_2.csv") + +assert a.id == 'parad' +assert a.original_frames[269].get_pos() == [-1.00519,2.65699,0.21] +assert a.original_frames[269].get_color() == [7,255,0] +assert a.original_frames[269].pose_is_valid() +assert animation_lib.get_numbers(a.static_begin_frames) == range(271) +assert animation_lib.get_numbers(a.takeoff_frames) == range(271,285) +assert animation_lib.get_numbers(a.route_frames) == range(285,1065) +assert animation_lib.get_numbers(a.land_frames) == [] +assert animation_lib.get_numbers(a.static_end_frames) == [] + +a.update_frames(config, "animation_3.csv") + +assert a.id == 'route' +assert a.original_frames[9].get_pos() == [0.97783,0.0,1.0] +assert a.original_frames[9].get_color() == [0,204,2] +assert a.original_frames[9].pose_is_valid() +assert animation_lib.get_numbers(a.static_begin_frames) == [] +assert animation_lib.get_numbers(a.takeoff_frames) == [] +assert animation_lib.get_numbers(a.route_frames) == range(20,31) +assert animation_lib.get_numbers(a.land_frames) == [] +assert animation_lib.get_numbers(a.static_end_frames) == [] + +shutil.rmtree('animation_config') From a8e808b2af02477da8aa19a60066859433a317cf Mon Sep 17 00:00:00 2001 From: Arthur Golubtsov Date: Thu, 28 May 2020 10:21:34 +0300 Subject: [PATCH 21/49] tests: Update tests for animation_lib --- Drone/animation_lib.py | 28 ++++++----- tests/animation_test.py | 102 ++++++++++++++++++++++++++-------------- 2 files changed, 84 insertions(+), 46 deletions(-) diff --git a/Drone/animation_lib.py b/Drone/animation_lib.py index b75e745..3519d1e 100644 --- a/Drone/animation_lib.py +++ b/Drone/animation_lib.py @@ -81,8 +81,8 @@ class Frame(object): class Animation(object): def __init__(self, config=None, filepath="animation.csv"): self.id = None - self.static_begin_time = None - self.takeoff_time = None + self.static_begin_time = 0 + self.takeoff_time = 0 self.original_frames = None self.static_begin_frames = None self.takeoff_frames = None @@ -138,7 +138,7 @@ class Animation(object): return else: self.original_frames.append(frame) - self.split_animation() + self.split_animation() ''' Split animation into 5 parts: static_begin, takeoff, route, land, static_end @@ -150,9 +150,6 @@ class Animation(object): Count static_begin_time and takeoff_time ''' def split_animation(self, move_delta=0.01): - if len(self.original_frames) == 0: - return - frames = copy.deepcopy(self.original_frames) self.static_begin_frames = [] self.takeoff_frames = [] self.route_frames = [] @@ -160,26 +157,33 @@ class Animation(object): self.static_end_frames = [] self.static_begin_time = 0 self.takeoff_time = 0 + if len(self.original_frames) == 0: + return + frames = copy.deepcopy(self.original_frames) i = 0 # Moving index from the beginning # Select static begin frames while i < len(frames) - 1: + self.static_begin_time += frames[i].delay if moving(frames[i], frames[i+1], move_delta): break - self.static_begin_time += frames[i].delay i += 1 if i > 0: self.static_begin_frames = frames[:i+1] frames = frames[i+1:] i = 0 + else: + self.static_begin_time = 0 # Select takeoff frames while i < len(frames) - 1: + self.takeoff_time += frames[i].delay if moving(frames[i], frames[i+1], move_delta, z = False) or (frames[i+1].z - frames[i].z <= 0): break - self.takeoff_time += frames[i].delay i += 1 if i > 0: self.takeoff_frames = frames[:i+1] frames = frames[i+1:] + else: + self.takeoff_time = 0 i = len(frames) - 1 # Moving index from the end # Select static end frames while i >= 0: @@ -203,6 +207,7 @@ class Animation(object): def make_output_frames(self, static_begin, takeoff, route, land, static_end): self.output_frames = [] + self.output_frames_min_z = None if static_begin: self.output_frames += self.static_begin_frames if takeoff: @@ -213,12 +218,13 @@ class Animation(object): self.output_frames += self.land_frames if static_end: self.output_frames += self.static_end_frames - self.output_frames_min_z = min(self.output_frames, key = lambda p: p.z).z + if self.output_frames: + self.output_frames_min_z = min(self.output_frames, key = lambda p: p.z).z def update_frames(self, config, filepath): + self.__init__() self.load(filepath, config.animation_frame_delay) - if self.original_frames: - self.make_output_frames(config.animation_output_static_begin, + self.make_output_frames(config.animation_output_static_begin, config.animation_output_takeoff, config.animation_output_route, config.animation_output_land, diff --git a/tests/animation_test.py b/tests/animation_test.py index decf80a..955af02 100644 --- a/tests/animation_test.py +++ b/tests/animation_test.py @@ -1,6 +1,7 @@ import os import sys import shutil +from pytest import approx # Add parent dir to PATH to import config import inspect @@ -30,47 +31,78 @@ assert config.config_name == "client" import animation_lib -a = animation_lib.Animation(config, "animation_1.csv") +a = animation_lib.Animation() + +def test_animation_1(): + a.update_frames(config, "animation_1.csv") + assert a.id == 'basic' + assert a.original_frames[0].get_pos() == [0.,0.,0.] + assert a.original_frames[0].get_color() == [204,2,0] + assert a.original_frames[0].pose_is_valid() + assert animation_lib.get_numbers(a.static_begin_frames) == range(1,11) + assert animation_lib.get_numbers(a.takeoff_frames) == range(11,21) + assert animation_lib.get_numbers(a.route_frames) == range(21,31) + assert animation_lib.get_numbers(a.land_frames) == range(31, 41) + assert animation_lib.get_numbers(a.static_end_frames) == range(41, 51) + assert animation_lib.get_numbers(a.output_frames) == range(11,31) + assert approx(a.static_begin_time) == 1 + assert approx(a.takeoff_time) == 1 + assert approx(a.output_frames_min_z) == 0.1 + +def test_animation_2(): + a.update_frames(config, "animation_2.csv") + assert a.id == 'parad' + assert a.original_frames[269].get_pos() == [-1.00519,2.65699,0.21] + assert a.original_frames[269].get_color() == [7,255,0] + assert a.original_frames[269].pose_is_valid() + assert animation_lib.get_numbers(a.static_begin_frames) == range(271) + assert animation_lib.get_numbers(a.takeoff_frames) == range(271,285) + assert animation_lib.get_numbers(a.route_frames) == range(285,1065) + assert animation_lib.get_numbers(a.land_frames) == [] + assert animation_lib.get_numbers(a.static_end_frames) == [] + assert animation_lib.get_numbers(a.output_frames) == range(271, 1065) + assert approx(a.static_begin_time) == 27.1 + assert approx(a.takeoff_time) == 1.4 + assert approx(a.output_frames_min_z) == 0.24386 + +def test_animation_3(): + a.update_frames(config, "animation_3.csv") + assert a.id == 'route' + assert a.original_frames[9].get_pos() == [0.97783,0.0,1.0] + assert a.original_frames[9].get_color() == [0,204,2] + assert a.original_frames[9].pose_is_valid() + assert animation_lib.get_numbers(a.static_begin_frames) == [] + assert animation_lib.get_numbers(a.takeoff_frames) == [] + assert animation_lib.get_numbers(a.route_frames) == range(20,31) + assert animation_lib.get_numbers(a.land_frames) == [] + assert animation_lib.get_numbers(a.static_end_frames) == [] + assert approx(a.static_begin_time) == 0 + assert approx(a.takeoff_time) == 0 + assert approx(a.output_frames_min_z) == 1 + +def test_animation_no_file(): + a.update_frames(config, "zzz.csv") + assert a.id == 'No animation' + assert a.original_frames == [] + assert a.output_frames == [] + assert animation_lib.get_numbers(a.static_begin_frames) == [] + assert animation_lib.get_numbers(a.takeoff_frames) == [] + assert animation_lib.get_numbers(a.route_frames) == [] + assert animation_lib.get_numbers(a.land_frames) == [] + assert animation_lib.get_numbers(a.static_end_frames) == [] + assert a.static_begin_time == 0 + assert a.takeoff_time == 0 + assert a.output_frames_min_z is None -assert a.id == 'basic' -assert a.original_frames[0].get_pos() == [0.,0.,0.] -assert a.original_frames[0].get_color() == [204,2,0] -assert a.original_frames[0].pose_is_valid() # print animation_lib.get_numbers(a.static_begin_frames) # print animation_lib.get_numbers(a.takeoff_frames) # print animation_lib.get_numbers(a.route_frames) # print animation_lib.get_numbers(a.land_frames) # print animation_lib.get_numbers(a.static_end_frames) - -assert animation_lib.get_numbers(a.static_begin_frames) == range(1,11) -assert animation_lib.get_numbers(a.takeoff_frames) == range(11,21) -assert animation_lib.get_numbers(a.route_frames) == range(21,31) -assert animation_lib.get_numbers(a.land_frames) == range(31, 41) -assert animation_lib.get_numbers(a.static_end_frames) == range(41, 51) - -a.update_frames(config, "animation_2.csv") - -assert a.id == 'parad' -assert a.original_frames[269].get_pos() == [-1.00519,2.65699,0.21] -assert a.original_frames[269].get_color() == [7,255,0] -assert a.original_frames[269].pose_is_valid() -assert animation_lib.get_numbers(a.static_begin_frames) == range(271) -assert animation_lib.get_numbers(a.takeoff_frames) == range(271,285) -assert animation_lib.get_numbers(a.route_frames) == range(285,1065) -assert animation_lib.get_numbers(a.land_frames) == [] -assert animation_lib.get_numbers(a.static_end_frames) == [] - -a.update_frames(config, "animation_3.csv") - -assert a.id == 'route' -assert a.original_frames[9].get_pos() == [0.97783,0.0,1.0] -assert a.original_frames[9].get_color() == [0,204,2] -assert a.original_frames[9].pose_is_valid() -assert animation_lib.get_numbers(a.static_begin_frames) == [] -assert animation_lib.get_numbers(a.takeoff_frames) == [] -assert animation_lib.get_numbers(a.route_frames) == range(20,31) -assert animation_lib.get_numbers(a.land_frames) == [] -assert animation_lib.get_numbers(a.static_end_frames) == [] +# print animation_lib.get_numbers(a.output_frames) +# print a.static_begin_time +# print a.takeoff_time +# print a.output_frames_min_z shutil.rmtree('animation_config') From 63847de0d9182239854772175ddd58a3f1040c82 Mon Sep 17 00:00:00 2001 From: Arthur Golubtsov Date: Thu, 28 May 2020 12:25:57 +0300 Subject: [PATCH 22/49] tests: Update --- Drone/animation_lib.py | 46 ++++++++++++++++++++++++----------------- tests/animation_test.py | 21 +++++++++++++++---- 2 files changed, 44 insertions(+), 23 deletions(-) diff --git a/Drone/animation_lib.py b/Drone/animation_lib.py index 3519d1e..a505db4 100644 --- a/Drone/animation_lib.py +++ b/Drone/animation_lib.py @@ -32,6 +32,17 @@ def get_numbers(frames): numbers.append(frame.number) return numbers +def get_start_action(start_action, current_height, takeoff_level): + if start_action is 'auto': + if current_height > takeoff_level: + return 'takeoff' + else: + return 'play' + elif start_action in ('takeoff', 'play'): + return start_action + else: + return 'error' + class Frame(object): params_dict = { "number": None, @@ -80,6 +91,11 @@ class Frame(object): class Animation(object): def __init__(self, config=None, filepath="animation.csv"): + self.reset(filepath) + if config is not None: + self.update_frames(config, filepath) + + def reset(self, filepath): self.id = None self.static_begin_time = 0 self.takeoff_time = 0 @@ -92,8 +108,6 @@ class Animation(object): self.output_frames = None self.output_frames_min_z = None self.filepath = filepath - if config is not None: - self.update_frames(config, filepath) def load(self, filepath="animation.csv", delay=0.1): self.original_frames = [] @@ -222,7 +236,7 @@ class Animation(object): self.output_frames_min_z = min(self.output_frames, key = lambda p: p.z).z def update_frames(self, config, filepath): - self.__init__() + self.reset(filepath) self.load(filepath, config.animation_frame_delay) self.make_output_frames(config.animation_output_static_begin, config.animation_output_takeoff, @@ -234,36 +248,30 @@ class Animation(object): x0, y0, z0 = offset x_ratio, y_ratio, z_ratio = ratio scaled_frames = copy.deepcopy(self.output_frames) - for frame in scaled_frames: - frame.x = x_ratio*frame.x + x0 - frame.y = y_ratio*frame.y + y0 - frame.z = z_ratio*frame.z + z0 + if scaled_frames: + for frame in scaled_frames: + frame.x = x_ratio*frame.x + x0 + frame.y = y_ratio*frame.y + y0 + frame.z = z_ratio*frame.z + z0 return scaled_frames def get_scaled_output_min_z(self, ratio = (1,1,1), offset = (0,0,0)): + if self.output_frames_min_z is None: + return None x0, y0, z0 = offset x_ratio, y_ratio, z_ratio = ratio return self.output_frames_min_z*z_ratio + z0 def get_start_point(self, ratio = (1,1,1), offset = (0,0,0)): + if not self.output_frames: + return [] x0, y0, z0 = offset x_ratio, y_ratio, z_ratio = ratio first_frame = self.output_frames[0] x = x_ratio*first_frame.x + x0 y = y_ratio*first_frame.y + y0 z = z_ratio*first_frame.z + z0 - return x, y, z - - def get_start_action(self, start_action, current_height, takeoff_level): - if start_action is 'auto': - if current_height > takeoff_level: - return 'takeoff' - else: - return 'play' - elif start_action in ('takeoff', 'play'): - return start_action - else: - return 'error' + return [x, y, z] def check_ground(self, ground_level = 0, ratio = (1,1,1), offset = (0,0,0)): return ground_level <= self.get_scaled_output_min_z(ratio, offset) diff --git a/tests/animation_test.py b/tests/animation_test.py index 955af02..a3fee6a 100644 --- a/tests/animation_test.py +++ b/tests/animation_test.py @@ -2,6 +2,7 @@ import os import sys import shutil from pytest import approx +import pytest # Add parent dir to PATH to import config import inspect @@ -36,7 +37,7 @@ a = animation_lib.Animation() def test_animation_1(): a.update_frames(config, "animation_1.csv") assert a.id == 'basic' - assert a.original_frames[0].get_pos() == [0.,0.,0.] + assert approx(a.original_frames[0].get_pos()) == [0,0,0] assert a.original_frames[0].get_color() == [204,2,0] assert a.original_frames[0].pose_is_valid() assert animation_lib.get_numbers(a.static_begin_frames) == range(1,11) @@ -48,13 +49,16 @@ def test_animation_1(): assert approx(a.static_begin_time) == 1 assert approx(a.takeoff_time) == 1 assert approx(a.output_frames_min_z) == 0.1 + assert approx(a.get_scaled_output(ratio=[1,2,3], offset=[4,5,6])[0].get_pos()) == [4.,5.,6.3] + assert approx(a.get_scaled_output_min_z(ratio=[1,2,3], offset=[4,5,6])) == 6.3 + assert approx(a.get_start_point(ratio=[1,2,3], offset=[4,5,6])) == [4.,5.,6.3] def test_animation_2(): a.update_frames(config, "animation_2.csv") assert a.id == 'parad' - assert a.original_frames[269].get_pos() == [-1.00519,2.65699,0.21] - assert a.original_frames[269].get_color() == [7,255,0] - assert a.original_frames[269].pose_is_valid() + assert approx(a.original_frames[271].get_pos()) == [-1.00519,2.65699,0.24386] + assert a.original_frames[271].get_color() == [7,255,0] + assert a.original_frames[271].pose_is_valid() assert animation_lib.get_numbers(a.static_begin_frames) == range(271) assert animation_lib.get_numbers(a.takeoff_frames) == range(271,285) assert animation_lib.get_numbers(a.route_frames) == range(285,1065) @@ -64,6 +68,9 @@ def test_animation_2(): assert approx(a.static_begin_time) == 27.1 assert approx(a.takeoff_time) == 1.4 assert approx(a.output_frames_min_z) == 0.24386 + assert approx(a.get_scaled_output(ratio=[1,2,3], offset=[4,5,6])[0].get_pos()) == [2.99481, 10.31398, 6.73158] + assert approx(a.get_scaled_output_min_z(ratio=[1,2,3], offset=[4,5,6])) == 6.73158 + assert approx(a.get_start_point(ratio=[1,2,3], offset=[4,5,6])) == [2.99481, 10.31398, 6.73158] def test_animation_3(): a.update_frames(config, "animation_3.csv") @@ -79,6 +86,9 @@ def test_animation_3(): assert approx(a.static_begin_time) == 0 assert approx(a.takeoff_time) == 0 assert approx(a.output_frames_min_z) == 1 + assert approx(a.get_scaled_output(ratio=[1,2,3], offset=[4,5,6])[0].get_pos()) == [4,5,9] + assert approx(a.get_scaled_output_min_z(ratio=[1,2,3], offset=[4,5,6])) == 9 + assert approx(a.get_start_point(ratio=[1,2,3], offset=[4,5,6])) == [4,5,9] def test_animation_no_file(): a.update_frames(config, "zzz.csv") @@ -93,6 +103,9 @@ def test_animation_no_file(): assert a.static_begin_time == 0 assert a.takeoff_time == 0 assert a.output_frames_min_z is None + assert a.get_scaled_output(ratio=[1,2,3], offset=[4,5,6]) == [] + assert a.get_scaled_output_min_z(ratio=[1,2,3], offset=[4,5,6]) is None + assert a.get_start_point(ratio=[1,2,3], offset=[4,5,6]) == [] # print animation_lib.get_numbers(a.static_begin_frames) From d775ea4e8b79a48bffa80bc2d2d6d4991fbc8585 Mon Sep 17 00:00:00 2001 From: Arthur Golubtsov Date: Fri, 29 May 2020 12:27:31 +0300 Subject: [PATCH 23/49] Drone: update animation_lib and tests --- Drone/animation_lib.py | 112 ++++++++++++++++++++++++++++++---------- tests/animation_test.py | 2 +- 2 files changed, 86 insertions(+), 28 deletions(-) diff --git a/Drone/animation_lib.py b/Drone/animation_lib.py index a505db4..ae14b79 100644 --- a/Drone/animation_lib.py +++ b/Drone/animation_lib.py @@ -1,7 +1,8 @@ import os -import time import csv import copy +import math +import time import numpy import rospy import logging @@ -32,17 +33,6 @@ def get_numbers(frames): numbers.append(frame.number) return numbers -def get_start_action(start_action, current_height, takeoff_level): - if start_action is 'auto': - if current_height > takeoff_level: - return 'takeoff' - else: - return 'play' - elif start_action in ('takeoff', 'play'): - return start_action - else: - return 'error' - class Frame(object): params_dict = { "number": None, @@ -86,6 +76,10 @@ class Frame(object): else: return [self.red, self.green, self.blue] + def set_yaw(self, yaw): + if yaw != "animation": + self.yaw = math.radians(float(yaw)) + def pose_is_valid(self): return self.get_pos() and (self.yaw is not None) @@ -97,27 +91,34 @@ class Animation(object): def reset(self, filepath): self.id = None - self.static_begin_time = 0 - self.takeoff_time = 0 + self.start_delay = 0 self.original_frames = None self.static_begin_frames = None + self.static_begin_time = 0 self.takeoff_frames = None + self.takeoff_time = 0 self.route_frames = None + self.route_time = 0 self.land_frames = None + self.land_time = 0 self.static_end_frames = None + self.static_end_time = 0 self.output_frames = None self.output_frames_min_z = None self.filepath = filepath + self.state = None - def load(self, filepath="animation.csv", delay=0.1): + def load(self, filepath="animation.csv", config = None): self.original_frames = [] self.corrected_frames = [] self.filepath = filepath + self.state = "OK" + delay = config.animation_frame_delay try: animation_file = open(filepath) except IOError: logger.debug("File {} can't be opened".format(filepath)) - self.id = "No animation" + self.state = "No animation" else: with animation_file: current_frame_delay = delay @@ -133,13 +134,20 @@ class Animation(object): logger.debug("Got new frame delay: {}".format(current_frame_delay)) else: logger.debug("No animation id in file") + self.id = "No animation id" try: frame = Frame(row_0, current_frame_delay) except ValueError as e: logger.error("Can't parse row in csv file. {}".format(e)) + self.state = "Bad animation file" return - else: - self.original_frames.append(frame) + try: + frame.set_yaw(config.animation_yaw) + except ValueError as e: + logger.error("Can't set yaw from configuration") + self.state = "Bad yaw from config" + return + self.original_frames.append(frame) for row in csv_reader: if len(row) == 2: current_frame_delay = float(row[1]) @@ -149,9 +157,15 @@ class Animation(object): frame = Frame(row, current_frame_delay) except ValueError as e: logger.error("Can't parse row in csv file. {}".format(e)) + self.state = "Bad animation file" return - else: - self.original_frames.append(frame) + try: + frame.set_yaw(config.animation_yaw) + except ValueError as e: + logger.error("Can't set yaw from configuration") + self.state = "Bad yaw from config" + return + self.original_frames.append(frame) self.split_animation() ''' @@ -171,6 +185,8 @@ class Animation(object): self.static_end_frames = [] self.static_begin_time = 0 self.takeoff_time = 0 + self.route_time = 0 + self.land_time = 0 if len(self.original_frames) == 0: return frames = copy.deepcopy(self.original_frames) @@ -201,6 +217,7 @@ class Animation(object): i = len(frames) - 1 # Moving index from the end # Select static end frames while i >= 0: + self.static_end_time += frames[i].delay if moving(frames[i], frames[i-1], move_delta): break i -= 1 @@ -208,28 +225,48 @@ class Animation(object): self.static_end_frames = frames[i+1:] frames = frames[:i+1] i = len(frames) - 1 + else: + self.static_end_time = 0 # Select land frames while i >= 0: + self.land_time += frames[i].delay if moving(frames[i], frames[i-1], move_delta, z = False) or (frames[i-1].z - frames[i].z <= 0): break i -= 1 if i < len(frames) - 1: self.land_frames = frames[i+1:] frames = frames[:i+1] + else: + self.land_time = 0 # Get route frames self.route_frames = frames + for frame in self.route_frames: + self.route_time += frame.delay def make_output_frames(self, static_begin, takeoff, route, land, static_end): self.output_frames = [] self.output_frames_min_z = None + self.start_delay = 0. + if not self.original_frames: + return + if not static_begin and not takeoff and not route and not land and not static_end: + return if static_begin: self.output_frames += self.static_begin_frames + if not static_begin: + self.start_delay += self.static_begin_time if takeoff: self.output_frames += self.takeoff_frames + if not static_begin and not takeoff: + self.start_delay += self.takeoff_time if route: self.output_frames += self.route_frames + if not static_begin and not takeoff and not route: + self.start_delay += self.route_time if land: self.output_frames += self.land_frames + if not static_begin and not takeoff and not route and not land: + self.start_delay += self.land_time if static_end: self.output_frames += self.static_end_frames if self.output_frames: @@ -237,14 +274,14 @@ class Animation(object): def update_frames(self, config, filepath): self.reset(filepath) - self.load(filepath, config.animation_frame_delay) + self.load(filepath, config) self.make_output_frames(config.animation_output_static_begin, config.animation_output_takeoff, config.animation_output_route, config.animation_output_land, config.animation_output_static_end) - def get_scaled_output(self, ratio = (1,1,1), offset = (0,0,0)): + def get_scaled_output(self, ratio=(1,1,1), offset=(0,0,0)): x0, y0, z0 = offset x_ratio, y_ratio, z_ratio = ratio scaled_frames = copy.deepcopy(self.output_frames) @@ -255,14 +292,14 @@ class Animation(object): frame.z = z_ratio*frame.z + z0 return scaled_frames - def get_scaled_output_min_z(self, ratio = (1,1,1), offset = (0,0,0)): + def get_scaled_output_min_z(self, ratio=(1,1,1), offset=(0,0,0)): if self.output_frames_min_z is None: return None - x0, y0, z0 = offset - x_ratio, y_ratio, z_ratio = ratio + z0 = offset[2] + z_ratio = ratio[2] return self.output_frames_min_z*z_ratio + z0 - def get_start_point(self, ratio = (1,1,1), offset = (0,0,0)): + def get_start_point(self, ratio=(1,1,1), offset=(0,0,0)): if not self.output_frames: return [] x0, y0, z0 = offset @@ -273,9 +310,30 @@ class Animation(object): z = z_ratio*first_frame.z + z0 return [x, y, z] - def check_ground(self, ground_level = 0, ratio = (1,1,1), offset = (0,0,0)): + def check_ground(self, ground_level=0, ratio=(1,1,1), offset=(0,0,0)): return ground_level <= self.get_scaled_output_min_z(ratio, offset) + def get_start_action(self, start_action, current_height, takeoff_level, + ground_level, ratio=(1,1,1), offset=(0,0,0)): + # Check output frames + if not self.output_frames: + return 'error: empty output frames' + if math.isnan(current_height): + return 'error: bad current_height' + # Check that bottom point of animation is higher than ground level + if ground_level > self.get_scaled_output_min_z(ratio, offset): + return 'error: some animation points are lower than ground level' + # Select start action + if start_action is 'auto': + if self.get_start_point(ratio, offset)[2] - current_height > takeoff_level: + return 'takeoff' + else: + return 'play' + elif start_action in ('takeoff', 'play'): + return start_action + else: + return 'error' + # Need for tests def save_corrected_animation(self): name, ext = os.path.splitext(self.filepath) diff --git a/tests/animation_test.py b/tests/animation_test.py index a3fee6a..5fde3b8 100644 --- a/tests/animation_test.py +++ b/tests/animation_test.py @@ -92,7 +92,7 @@ def test_animation_3(): def test_animation_no_file(): a.update_frames(config, "zzz.csv") - assert a.id == 'No animation' + assert a.id == None assert a.original_frames == [] assert a.output_frames == [] assert animation_lib.get_numbers(a.static_begin_frames) == [] From 4c5032b97585964fbce984a6aeaeab7deb804875 Mon Sep 17 00:00:00 2001 From: Arthur Golubtsov Date: Fri, 29 May 2020 12:28:28 +0300 Subject: [PATCH 24/49] Drone: Rewrite copter_client with new animation module --- Drone/config/spec/configspec_client.ini | 30 +- Drone/copter_client.py | 397 +++++++++++++----------- 2 files changed, 232 insertions(+), 195 deletions(-) diff --git a/Drone/config/spec/configspec_client.ini b/Drone/config/spec/configspec_client.ini index 6e8ac74..1574baa 100644 --- a/Drone/config/spec/configspec_client.ini +++ b/Drone/config/spec/configspec_client.ini @@ -39,11 +39,11 @@ decrease_thrust_after = float(default=5.0, min=0) [COPTER] frame_id = string(default=map) +arming_time = float(default=0.5) takeoff_height = float(default=1.0) takeoff_time = float(default=5.0, min=0) -safe_takeoff = boolean(default=False) reach_first_point_time = float(default=5.0, min=0) -land_time = float(default=1.0, min=0) +land_delay = float(default=1.0, min=0) land_timeout = float(default=10.0, min=0) # __list__ x y z common_offset = float_list(default=list(0, 0, 0), min=3, max=3) @@ -63,20 +63,28 @@ lon = string(default=0) yaw = float(default=0) [ANIMATION] -takeoff_detection = boolean(default=True) -land_detection = boolean(default=True) +# Available options: +# 'auto' - automatic action selection from 'takeoff' or 'fly' based on current copter level +# 'takeoff' - takeoff to first output animation point after static_begin_time then execute 'takeoff logic' +# 'fly' - execute animation frames after static_begin_time +start_action = string(default=auto) +takeoff_level = float(default=0.5) +ground_level = float(default=0) frame_delay = float(default=0.1, min=0.01) # Animation ratio (x, y, z) # __list__ x y z ratio = float_list(default=list(1.0, 1.0, 1.0), min=3, max=3) -# Available options: 'animation', 'nan' or a number in degrees +# Available options: +# 'animation' - take yaw from animation +# 'nan' - don't change yaw during flight +# or a number in degrees yaw = string(default=180.0) - [[OUTPUT]] - static_begin = boolean(default=False) - takeoff = boolean(default=True) - route = boolean(default=True) - land = boolean(default=False) - static_end = boolean(default=False) +[[OUTPUT]] +static_begin = boolean(default=False) +takeoff = boolean(default=True) +route = boolean(default=True) +land = boolean(default=False) +static_end = boolean(default=False) [LED] use = boolean(default=False) diff --git a/Drone/copter_client.py b/Drone/copter_client.py index 83534d4..8b87daf 100644 --- a/Drone/copter_client.py +++ b/Drone/copter_client.py @@ -9,7 +9,10 @@ import numpy try: from clever import srv except ImportError: - from clover import srv + try: + from clover import srv + except ImportError: + print("Can't import clever or clover") import datetime import logging @@ -17,8 +20,17 @@ import threading import psutil import subprocess from collections import namedtuple +from watchdog.observers import Observer +from watchdog.events import FileSystemEventHandler -from FlightLib import FlightLib +try: + from FlightLib import FlightLib +except ImportError: + print("Can't import FlightLib") +try: + from FlightLib import LedLib +except ImportError: + print("Can't import LedLib") import client @@ -46,6 +58,12 @@ def azi(x, y): def get_xy(dist, azi): return dist*math.sin(math.radians(azi)), dist*math.cos(math.radians(azi)) +def valid(pos): + for coord in pos: + if math.isnan(coord): + return False + return True + static_broadcaster = tf2_ros.StaticTransformBroadcaster() emergency = False @@ -96,8 +114,8 @@ class CopterClient(client.Client): def __init__(self, config_path="config/client.ini"): super(CopterClient, self).__init__(config_path) self.load_config() - self.frames = {} self.telemetry = None + self.animation = animation.Animation(self.config, "animation.csv") def load_config(self): super(CopterClient, self).load_config() @@ -111,7 +129,7 @@ class CopterClient(client.Client): if self.config.led_use: from FlightLib import LedLib LedLib.init_led(self.config.led_pin) - task_manager_instance.start() # TODO move to self + task_manager_instance.start() start_subscriber() self.telemetry = Telemetry() self.telemetry.start_loop() @@ -158,11 +176,11 @@ class CopterClient(client.Client): lat = float(self.config.gps_frame_lat) lon = float(self.config.gps_frame_lon) geo_delta = Earth.Inverse(telem.lat, telem.lon, lat, lon) - logger.info("dist: {} | azi: {}".format(geo_delta['s12'], geo_delta['azi1'])) + #logger.info("dist: {} | azi: {}".format(geo_delta['s12'], geo_delta['azi1'])) dx, dy = get_xy(geo_delta['s12'], geo_delta['azi1']) gps_dx = telem.x + dx gps_dy = telem.y + dy - logger.info("GPS frame dx: {} | dy: {}".format(gps_dx, gps_dy)) + #logger.info("GPS frame dx: {} | dy: {}".format(gps_dx, gps_dy)) trans = TransformStamped() trans.transform.translation.x = gps_dx trans.transform.translation.y = gps_dy @@ -314,13 +332,13 @@ def _execute(*args, **kwargs): def _response_id(*args, **kwargs): new_id = kwargs.get("new_id", None) if new_id is not None: - old_id = client.active_client.client_id + old_id = copter.client_id if new_id != old_id: - client.active_client.config.set('PRIVATE', 'id', new_id, write=True) - client.active_client.client_id = new_id + copter.config.set('PRIVATE', 'id', new_id, write=True) + copter.client_id = new_id if new_id != '/hostname': - if client.active_client.config.system_restart_after_rename: - hostname = client.active_client.client_id + if copter.config.system_restart_after_rename: + hostname = copter.client_id configure_hostname(hostname) configure_hosts(hostname) configure_bashrc(hostname) @@ -347,28 +365,14 @@ def _response_selfcheck(*args, **kwargs): @messaging.request_callback("telemetry") def _response_telemetry(*args, **kwargs): - telemetry.update() - return telemetry.create_msg_contents() + copter.telemetry.update() + return copter.telemetry.create_msg_contents() @messaging.request_callback("anim_id") def _response_animation_id(*args, **kwargs): # Load animation - result = animation.get_id() - if result != 'No animation': - logger.debug("Saving corrected animation") - offset = numpy.array(client.active_client.config.private_offset) + numpy.array(client.active_client.config.copter_common_offset) - frames = animation.load_animation(os.path.abspath("animation.csv"), client.active_client.config.animation_frame_delay, - offset[0], offset[1], offset[2], *client.active_client.config.animation_ratio) - # Correct start and land frames in animation - corrected_frames, start_action, start_delay = animation.correct_animation(frames, - check_takeoff=client.active_client.config.animation_takeoff_detection, - check_land=client.active_client.config.animation_land_detection, - ) - logger.debug("Start action: {}".format(start_action)) - # Save corrected animation - animation.save_corrected_animation(corrected_frames) - return result + return copter.animation.id @messaging.request_callback("batt_voltage") @@ -405,9 +409,9 @@ def _response_cal_status(*args, **kwargs): @messaging.request_callback("position") def _response_position(*args, **kwargs): - telem = FlightLib.get_telemetry_locked(client.active_client.config.copter_frame_id) + telem = copter.telemetry.ros_telemetry return "{:.2f} {:.2f} {:.2f} {:.1f} {}".format( - telem.x, telem.y, telem.z, math.degrees(telem.yaw), client.active_client.config.copter_frame_id) + telem.x, telem.y, telem.z, math.degrees(telem.yaw), copter.config.copter_frame_id) @messaging.request_callback("calibrate_gyro") @@ -436,60 +440,61 @@ def _command_test(*args, **kwargs): print("stdout test") +@messaging.message_callback("update_animation") +def _command_update_animation(*args, **kwargs): + copter.animation.update_frames(copter.config, "animation.csv") + + @messaging.message_callback("move_start") def _command_move_start_to_current_position(*args, **kwargs): - x_start, y_start = animation.get_start_xy(os.path.abspath("animation.csv"), - *client.active_client.config.animation_ratio) - logger.debug("x_start = {}, y_start = {}".format(x_start, y_start)) - if not math.isnan(x_start): - telem = FlightLib.get_telemetry_locked(client.active_client.config.copter_frame_id) - logger.debug("x_telem = {}, y_telem = {}".format(telem.x, telem.y)) - if not math.isnan(telem.x): - client.active_client.config.set('PRIVATE', 'offset', - [telem.x - x_start, telem.y - y_start, client.active_client.config.private_offset[2]], - write=True) - logger.info("Set start delta: {:.2f} {:.2f}".format(client.active_client.config.private_offset[0], - client.active_client.config.private_offset[1])) - else: - logger.debug("Wrong telemetry") + offset = numpy.array(copter.config.private_offset) + numpy.array(copter.config.copter_common_offset) + try: + xs, ys, zs = copter.animation.get_start_point(copter.config.ratio, offset) + except ValueError: + logger.error("Can't get start point. Check animation file!") else: - logger.debug("Wrong animation file") + logger.debug("start x = {}, y = {}".format(xs, ys)) + telem = copter.telemetry.ros_telemetry + logger.debug("telemetry x = {}, y = {}".format(telem.x, telem.y)) + if valid([telem.x, telem.y, telem.z]): + copter.config.set('PRIVATE', 'offset', + [telem.x - xs, telem.y - ys, copter.config.private_offset[2]], write=True) + logger.info("Set start delta: {:.2f} {:.2f}".format(copter.config.private_offset[0], + copter.config.private_offset[1])) + else: + logger.error("Wrong telemetry") @messaging.message_callback("reset_start") def _command_reset_start(*args, **kwargs): - client.active_client.config.set('PRIVATE', 'offset', - [0, 0, client.active_client.config.private_offset[2]], - write=True) - logger.info("Reset start to {:.2f} {:.2f}".format(client.active_client.config.private_offset[0], - client.active_client.config.private_offset[1])) + copter.config.set('PRIVATE', 'offset', + [0, 0, copter.config.private_offset[2]], write=True) + logger.info("Reset start to {:.2f} {:.2f}".format(copter.config.private_offset[0], + copter.config.private_offset[1])) @messaging.message_callback("set_z_to_ground") def _command_set_z(*args, **kwargs): - telem = FlightLib.get_telemetry_locked(client.active_client.config.copter_frame_id) - client.active_client.config.set('PRIVATE', 'offset', - [client.active_client.config.private_offset[0], client.active_client.config.private_offset[1], telem.z], - write=True) - logger.info("Set z offset to {:.2f}".format(client.active_client.config.private_offset[2])) + telem = copter.telemetry.ros_telemetry + if valid([telem.x, telem.y, telem.z]): + copter.config.set('PRIVATE', 'offset', + [copter.config.private_offset[0], copter.config.private_offset[1], telem.z], write=True) + logger.info("Set z offset to {:.2f}".format(copter.config.private_offset[2])) + else: + logger.error("Wrong telemetry") @messaging.message_callback("reset_z_offset") def _command_reset_z(*args, **kwargs): - client.active_client.config.set('PRIVATE', 'offset', - [client.active_client.config.private_offset[0], client.active_client.config.private_offset[1], 0], - write=True) - logger.info("Reset z offset to {:.2f}".format(client.active_client.config.private_offset[2])) + copter.config.set('PRIVATE', 'offset', + [copter.config.private_offset[0], copter.config.private_offset[1], 0], write=True) + logger.info("Reset z offset to {:.2f}".format(copter.config.private_offset[2])) @messaging.message_callback("update_repo") def _command_update_repo(*args, **kwargs): - os.system("mv /home/pi/clever-show/Drone/client_config.ini /home/pi/clever-show/Drone/client_config_tmp.ini") - os.system("git reset --hard HEAD") - os.system("git checkout master") os.system("git fetch") os.system("git pull --rebase") - os.system("mv /home/pi/clever-show/Drone/client_config_tmp.ini /home/pi/clever-show/Drone/client_config.ini") os.system("chown -R pi:pi /home/pi/clever-show") @@ -512,7 +517,7 @@ def _command_service_restart(*args, **kwargs): @messaging.message_callback("repair_chrony") def _command_chrony_repair(*args, **kwargs): - repair_chrony(client.active_client.config.server_host) + repair_chrony(copter.config.server_host) @messaging.message_callback("led_test") @@ -527,13 +532,12 @@ def _command_led_fill(*args, **kwargs): r = kwargs.get("red", 0) g = kwargs.get("green", 0) b = kwargs.get("blue", 0) - LedLib.fill(r, g, b) @messaging.message_callback("flip") def _copter_flip(*args, **kwargs): - FlightLib.flip(frame_id=client.active_client.config.copter_frame_id) + FlightLib.flip(frame_id=copter.config.copter_frame_id) @messaging.message_callback("takeoff") @@ -541,30 +545,36 @@ def _command_takeoff(*args, **kwargs): logger.info("Takeoff at {}".format(datetime.datetime.now())) task_manager.add_task(0, 0, animation.takeoff, task_kwargs={ - "z": client.active_client.config.copter_takeoff_height, - "timeout": client.active_client.config.copter_takeoff_time, - "safe_takeoff": client.active_client.config.copter_safe_takeoff, - "use_leds": client.active_client.config.led_use & client.active_client.config.led_takeoff_indication, - } - ) + "z": copter.config.copter_takeoff_height, + "timeout": copter.config.copter_takeoff_time, + "safe_takeoff": False, + "use_leds": copter.config.led_use & copter.config.led_takeoff_indication, + }) @messaging.message_callback("takeoff_z") def _command_takeoff_z(*args, **kwargs): - z_str = kwargs.get("z", None) - if z_str is not None: - telem = FlightLib.get_telemetry_locked(client.active_client.config.copter_frame_id) - logger.info("Takeoff to z = {} at {}".format(z_str, datetime.datetime.now())) - task_manager.add_task(0, 0, FlightLib.reach_point, - task_kwargs={ - "x": telem.x, - "y": telem.y, - "z": float(z_str), - "frame_id": client.active_client.config.copter_frame_id, - "timeout": client.active_client.config.copter_takeoff_time, - "auto_arm": True, - } - ) + try: + z = float(kwargs.get("z", None)) + except TypeError: + logger.error("takeoff_z: No z argument!") + except ValueError: + logger.error("takeoff_z: Wrong z argument!") + else: + telem = FlightLib.get_telemetry_locked(copter.config.copter_frame_id) + if valid([telem.x, telem.y, telem.z]): + logger.info("Takeoff to z = {} at {}".format(z, datetime.datetime.now())) + task_manager.add_task(0, 0, FlightLib.reach_point, + task_kwargs={ + "x": telem.x, + "y": telem.y, + "z": z, + "frame_id": copter.config.copter_frame_id, + "timeout": copter.config.copter_takeoff_time, + "auto_arm": True, + }) + else: + logger.error("Wrong telemetry!") @messaging.message_callback("land") @@ -572,12 +582,11 @@ def _command_land(*args, **kwargs): task_manager.reset() task_manager.add_task(0, 0, animation.land, task_kwargs={ - "z": client.active_client.config.copter_takeoff_height, - "timeout": client.active_client.config.copter_takeoff_time, - "frame_id": client.active_client.config.copter_frame_id, - "use_leds": client.active_client.config.led_use & client.active_client.config.led_land_indication, - } - ) + "z": copter.config.copter_takeoff_height, + "timeout": copter.config.copter_land_timeout, + "frame_id": copter.config.copter_frame_id, + "use_leds": copter.config.led_use & copter.config.led_land_indication, + }) @messaging.message_callback("emergency_land") @@ -591,8 +600,7 @@ def _command_disarm(*args, **kwargs): task_manager.add_task(-5, 0, FlightLib.arming_wrapper, task_kwargs={ "state": False - } - ) + }) @messaging.message_callback("stop") @@ -612,109 +620,104 @@ def _command_resume(*args, **kwargs): @messaging.message_callback("start") def _play_animation(*args, **kwargs): - start_time = float(kwargs["time"]) - # Check if animation file is available - if animation.get_id() == 'No animation': - logger.error("Can't start animation without animation file!") + + # Validate start_time + try: + start_time = float(kwargs["time"]) + except ValueError: + logger.error("start: Wrong time argument!") + return + except KeyError: + logger.error("start: No time argument!") return + # Check animation state + if copter.animation.state is not "OK": + logger.error("start: Bad animation state") + return + + # Get output frames + offset = numpy.array(copter.config.private_offset) + numpy.array(copter.config.copter_common_offset) + frames = copter.animation.get_scaled_output(copter.config.animation_ratio, offset) + if not frames: + logger.error("start: No frames in animation!") + return + + # Get current telemetry + telem = copter.telemetry.ros_telemetry + if not valid([telem.x, telem.y, telem.z]): + logger.error("start: Position is not valid!") + return + + # Get start action and delay + start_action, start_delay = copter.telemetry.start_action_and_delay + + # Reset task manager task_manager.reset(interrupt_next_task=False) - logger.info("Start time = {}, wait for {} seconds".format(start_time, start_time - time.time())) - # Load animation - offset = numpy.array(client.active_client.config.private_offset) + numpy.array(client.active_client.config.copter_common_offset) - frames = animation.load_animation(os.path.abspath("animation.csv"), client.active_client.config.animation_frame_delay, - offset[0], offset[1], offset[2], *client.active_client.config.animation_ratio) - # Correct start and land frames in animation - corrected_frames, start_action, start_delay = animation.correct_animation(frames, - check_takeoff=client.active_client.config.animation_takeoff_detection, - check_land=client.active_client.config.animation_land_detection, - ) - # Choose start action + # Set animation logic if start_action == 'takeoff': - # Takeoff first - task_manager.add_task(start_time, 0, animation.takeoff, + # Takeoff first at start_time + start_delay_time + takeoff_time = start_time + start_delay + task_manager.add_task(takeoff_time, 0, animation.takeoff, task_kwargs={ - "z": client.active_client.config.copter_takeoff_height, - "timeout": client.active_client.config.copter_takeoff_time, - "safe_takeoff": client.active_client.config.copter_safe_takeoff, - # "frame_id": client.active_client.config.copter_frame_id, - "use_leds": client.active_client.config.led_use & client.active_client.config.led_takeoff_indication, - } - ) + "z": copter.config.copter_takeoff_height, + "timeout": copter.config.copter_takeoff_time, + "safe_takeoff": False, + "use_leds": copter.config.led_use & copter.config.led_takeoff_indication, + }) # Fly to first point - rfp_time = start_time + client.active_client.config.copter_takeoff_time - if client.active_client.config.animation_yaw == "animation": - yaw = frame["yaw"] - else: - yaw = math.radians(float(client.active_client.config.animation_yaw)) + rfp_time = takeoff_time + copter.config.copter_takeoff_time task_manager.add_task(rfp_time, 0, animation.execute_frame, task_kwargs={ - "point": animation.convert_frame(corrected_frames[0])[0], - "color": animation.convert_frame(corrected_frames[0])[1], - "yaw": yaw, - "frame_id": client.active_client.config.copter_frame_id, - "use_leds": client.active_client.config.led_use, + "frame": frames[0], + "frame_id": copter.config.copter_frame_id, + "use_leds": copter.config.led_use, "flight_func": FlightLib.reach_point, - } - ) + }) # Calculate first frame start time - frame_time = rfp_time + client.active_client.config.copter_reach_first_point_time + frame_time = rfp_time + copter.config.copter_reach_first_point_time elif start_action == 'arm': # Calculate start time - start_time += start_delay - frame_time = start_time # + 1.0 - point, color, yaw = animation.convert_frame(corrected_frames[0]) - task_manager.add_task(frame_time, 0, animation.execute_frame, + arm_time = start_time + start_delay # + 1.0 + task_manager.add_task(arm_time, 0, animation.execute_frame, task_kwargs={ - "point": point, - "color": color, - "frame_id": client.active_client.config.copter_frame_id, - "use_leds": client.active_client.config.led_use, + "frame": frames[0], + "frame_id": copter.config.copter_frame_id, + "use_leds": copter.config.led_use, "flight_func": FlightLib.navto, "auto_arm": True, - } - ) + }) # Calculate first frame start time - frame_time += corrected_frames[0]["delay"] # TODO Think about arming time - logger.debug(task_manager.task_queue) + frame_time = arm_time + copter.config.copter_arming_time + logger.debug("Start queue {}".format(task_manager.task_queue)) # Play animation file - for frame in corrected_frames: - point, color, yaw = animation.convert_frame(frame) - if client.active_client.config.animation_yaw == "animation": - yaw = frame["yaw"] - else: - yaw = math.radians(float(client.active_client.config.animation_yaw)) + for frame in frames: task_manager.add_task(frame_time, 0, animation.execute_frame, task_kwargs={ - "point": point, - "color": color, - "yaw": yaw, - "frame_id": client.active_client.config.copter_frame_id, - "use_leds": client.active_client.config.led_use, + "frame": frame, + "frame_id": copter.config.copter_frame_id, + "use_leds": copter.config.led_use, "flight_func": FlightLib.navto, - } - ) - frame_time += frame["delay"] - + }) + frame_time += frame.delay # Calculate land_time - land_time = frame_time + client.active_client.config.copter_land_time + land_time = frame_time + copter.config.copter_land_delay # Land task_manager.add_task(land_time, 0, animation.land, task_kwargs={ - "timeout": client.active_client.config.copter_land_timeout, - "frame_id": client.active_client.config.copter_frame_id, - "use_leds": client.active_client.config.led_use & client.active_client.config.led_land_indication, - }, - ) + "timeout": copter.config.copter_land_timeout, + "frame_id": copter.config.copter_frame_id, + "use_leds": copter.config.led_use & copter.config.led_land_indication, + }) # noinspection PyAttributeOutsideInit class Telemetry: params_default_dict = { "git_version": None, - "animation_id": None, + "animation_info": None, "battery": None, "armed": False, "fcu_status": None, @@ -723,6 +726,7 @@ class Telemetry: "selfcheck": None, "current_position": None, "start_position": None, + "start_action_and_delay": None, "last_task": None, "time_delta": None, "config_version": None, @@ -759,16 +763,16 @@ class Telemetry: @classmethod def get_config_version(cls): - return "{} V{}".format(client.active_client.config.config_name, client.active_client.config.config_version) + return "{} V{}".format(copter.config.config_name, copter.config.config_version) @classmethod def get_start_position(cls): - x_start, y_start = animation.get_start_xy(os.path.abspath("animation.csv"), - *client.active_client.config.animation_ratio) - offset = numpy.array(client.active_client.config.private_offset) + numpy.array(client.active_client.config.copter_common_offset) + x_start, y_start, z_start = animation.get_start_pos(os.path.abspath("animation.csv"), + *copter.config.animation_ratio) + offset = numpy.array(copter.config.private_offset) + numpy.array(copter.config.copter_common_offset) x = x_start + offset[0] y = y_start + offset[1] - z = offset[2] + z = z_start + offset[2] if not FlightLib._check_nans(x, y, z): return x, y, z return 'NO_POS' @@ -807,14 +811,25 @@ class Telemetry: def get_position(cls, ros_telemetry): x, y, z = ros_telemetry.x, ros_telemetry.y, ros_telemetry.z if not math.isnan(x): - return x, y, z, math.degrees(ros_telemetry.yaw), client.active_client.config.copter_frame_id + return x, y, z, math.degrees(ros_telemetry.yaw), copter.config.copter_frame_id return 'NO_POS' + def get_ros_telemetry(self): + return self.ros_telemetry + + def get_start_action_and_delay(self) + offset = numpy.array(copter.config.private_offset) + numpy.array(copter.config.copter_common_offset) + start_action = copter.animation.get_start_action( + copter.config.animation_start_action, self.ros_telemetry.z, + copter.config.animation_takeoff_level, copter.config.animation_ground_level, + copter.config.animation_ratio, offset) + start_delay = copter.animation.start_delay + return start_action, start_delay + def update_telemetry_fast(self): - self.start_position = self.get_start_position() self.last_task = task_manager.get_current_task() try: - self.ros_telemetry = FlightLib.get_telemetry_locked(client.active_client.config.copter_frame_id) + self.ros_telemetry = FlightLib.get_telemetry_locked(copter.config.copter_frame_id) if self.ros_telemetry.connected: self.armed = self.ros_telemetry.armed self.mode = self.ros_telemetry.mode @@ -832,13 +847,12 @@ class Telemetry: self.time_delta = time.time() self.round_telemetry() - def get_ros_telemetry(self): - return self.ros_telemetry - def update_telemetry_slow(self): - self.animation_id = animation.get_id() + self.animation_info = [copter.animation.id, copter.animation.state] self.git_version = self.get_git_version() self.config_version = self.get_config_version() + self.start_position = self.get_start_position() + self.start_action_and_delay = self.get_start_action_and_delay() try: self.calibration_status = get_calibration_status() self.fcu_status = get_sys_status() @@ -905,7 +919,7 @@ class Telemetry: def transmit_message(self): # todo if connected try: - client.active_client.server_connection.send_message('telemetry', kwargs={'value': self.create_msg_contents()}) + copter.server_connection.send_message('telemetry', kwargs={'value': self.create_msg_contents()}) except AttributeError as e: logger.debug(e) @@ -935,7 +949,7 @@ class Telemetry: self.update_telemetry_fast() self.check_failsafe_and_interruption() - if client.active_client.config.telemetry_transmit and client.active_client.connected: + if copter.config.telemetry_transmit and copter.connected: self.transmit_message() rate.sleep() @@ -944,14 +958,14 @@ class Telemetry: rate = rospy.Rate(1) while not rospy.is_shutdown(): self.update_telemetry_slow() - if client.active_client.config.telemetry_log_resources: + if copter.config.telemetry_log_resources: self.log_cpu_and_memory() rate.sleep() def start_loop(self): - if client.active_client.config.telemetry_frequency > 0: + if copter.config.telemetry_frequency > 0: telemetry_thread = threading.Thread(target=self._update_loop, name="Telemetry getting thread", - args=(client.active_client.config.telemetry_frequency,)) # TODO MOVE? Daemon? + args=(copter.config.telemetry_frequency,)) # TODO MOVE? Daemon? slow_telemetry_thread = threading.Thread(target=self._slow_update_loop, name="Slow telemetry getting thread") slow_telemetry_thread.start() @@ -971,8 +985,23 @@ def emergency_callback(data): emergency = data.data +class AnimationEventHandler(FileSystemEventHandler): + def on_any_event(self, event): + logger.info('{} is {}'.format(event.src_path, event.event_type)) + if event.src_path == "./animation.csv" and event.event_type == "modified": + copter.animation.update_frames(copter.config, "animation.csv") + + if __name__ == "__main__": - copter_client = CopterClient() + copter = CopterClient() task_manager = tasking.TaskManager() rospy.Subscriber('/emergency', Bool, emergency_callback) - copter_client.start(task_manager) + copter.start(task_manager) + event_handler = AnimationEventHandler() + observer = Observer() + observer.schedule(event_handler, ".") + observer.start() + while not rospy.is_shutdown: + rospy.sleep(1) + observer.stop() + observer.join() From 414314deb60445b746f1256a941624be8d205edc Mon Sep 17 00:00:00 2001 From: Arthur Golubtsov Date: Sat, 30 May 2020 21:26:26 +0300 Subject: [PATCH 25/49] animation: Fix bugs, add get_start_yaw --- Drone/animation_lib.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/Drone/animation_lib.py b/Drone/animation_lib.py index ae14b79..f96fd46 100644 --- a/Drone/animation_lib.py +++ b/Drone/animation_lib.py @@ -310,6 +310,11 @@ class Animation(object): z = z_ratio*first_frame.z + z0 return [x, y, z] + def get_start_yaw(self): + if not self.output_frames: + return float('nan') + return math.degrees(self.output_frames[0].yaw) + def check_ground(self, ground_level=0, ratio=(1,1,1), offset=(0,0,0)): return ground_level <= self.get_scaled_output_min_z(ratio, offset) @@ -324,12 +329,12 @@ class Animation(object): if ground_level > self.get_scaled_output_min_z(ratio, offset): return 'error: some animation points are lower than ground level' # Select start action - if start_action is 'auto': + if start_action == 'auto': if self.get_start_point(ratio, offset)[2] - current_height > takeoff_level: return 'takeoff' else: - return 'play' - elif start_action in ('takeoff', 'play'): + return 'fly' + elif start_action in ('takeoff', 'fly'): return start_action else: return 'error' From c1d8667b3732f9e2cc033ce52d9430bbc3897063 Mon Sep 17 00:00:00 2001 From: Arthur Golubtsov Date: Sat, 30 May 2020 21:31:28 +0300 Subject: [PATCH 26/49] copter_client: Fix telemetry sending --- Drone/copter_client.py | 60 ++++++++++++++++++++++-------------------- 1 file changed, 31 insertions(+), 29 deletions(-) diff --git a/Drone/copter_client.py b/Drone/copter_client.py index 8b87daf..d45ea8d 100644 --- a/Drone/copter_client.py +++ b/Drone/copter_client.py @@ -650,8 +650,11 @@ def _play_animation(*args, **kwargs): return # Get start action and delay - start_action, start_delay = copter.telemetry.start_action_and_delay - + try: + start_action, start_delay = copter.telemetry.start_action_and_delay + except ValueError: + logger.error("start: Can't get animation start position and delay") + return # Reset task manager task_manager.reset(interrupt_next_task=False) @@ -726,7 +729,6 @@ class Telemetry: "selfcheck": None, "current_position": None, "start_position": None, - "start_action_and_delay": None, "last_task": None, "time_delta": None, "config_version": None, @@ -765,17 +767,23 @@ class Telemetry: def get_config_version(cls): return "{} V{}".format(copter.config.config_name, copter.config.config_version) - @classmethod - def get_start_position(cls): - x_start, y_start, z_start = animation.get_start_pos(os.path.abspath("animation.csv"), - *copter.config.animation_ratio) + def get_start_position(self): offset = numpy.array(copter.config.private_offset) + numpy.array(copter.config.copter_common_offset) - x = x_start + offset[0] - y = y_start + offset[1] - z = z_start + offset[2] - if not FlightLib._check_nans(x, y, z): - return x, y, z - return 'NO_POS' + try: + x, y, z = copter.animation.get_start_point(copter.config.animation_ratio, offset) + except ValueError: + return [float('nan'),float('nan'),float('nan'),float('nan'),'error: no start pos in animation',float('nan')] + else: + start_delay = copter.animation.start_delay + yaw = copter.animation.get_start_yaw() + if not self.ros_telemetry: + start_action = 'error: no telemetry data' + else: + start_action = copter.animation.get_start_action( + copter.config.animation_start_action, self.ros_telemetry.z, + copter.config.animation_takeoff_level, copter.config.animation_ground_level, + copter.config.animation_ratio, offset) + return [x,y,z,yaw,start_action,start_delay] @classmethod def get_battery(cls, ros_telemetry): @@ -809,7 +817,10 @@ class Telemetry: @classmethod def get_position(cls, ros_telemetry): - x, y, z = ros_telemetry.x, ros_telemetry.y, ros_telemetry.z + try: + x, y, z = ros_telemetry.x, ros_telemetry.y, ros_telemetry.z + except AttributeError: + return 'NO_POS' if not math.isnan(x): return x, y, z, math.degrees(ros_telemetry.yaw), copter.config.copter_frame_id return 'NO_POS' @@ -817,15 +828,6 @@ class Telemetry: def get_ros_telemetry(self): return self.ros_telemetry - def get_start_action_and_delay(self) - offset = numpy.array(copter.config.private_offset) + numpy.array(copter.config.copter_common_offset) - start_action = copter.animation.get_start_action( - copter.config.animation_start_action, self.ros_telemetry.z, - copter.config.animation_takeoff_level, copter.config.animation_ground_level, - copter.config.animation_ratio, offset) - start_delay = copter.animation.start_delay - return start_action, start_delay - def update_telemetry_fast(self): self.last_task = task_manager.get_current_task() try: @@ -852,7 +854,6 @@ class Telemetry: self.git_version = self.get_git_version() self.config_version = self.get_config_version() self.start_position = self.get_start_position() - self.start_action_and_delay = self.get_start_action_and_delay() try: self.calibration_status = get_calibration_status() self.fcu_status = get_sys_status() @@ -988,20 +989,21 @@ def emergency_callback(data): class AnimationEventHandler(FileSystemEventHandler): def on_any_event(self, event): logger.info('{} is {}'.format(event.src_path, event.event_type)) - if event.src_path == "./animation.csv" and event.event_type == "modified": - copter.animation.update_frames(copter.config, "animation.csv") + # logger.info(os.path.splitext(event.src_path)) + if os.path.splitext(event.src_path)[-1] == '.csv' and event.event_type != "deleted": + if os.path.exists("animation.csv"): + logger.info("Update frames from animation.csv") + copter.animation.update_frames(copter.config, "animation.csv") if __name__ == "__main__": copter = CopterClient() task_manager = tasking.TaskManager() rospy.Subscriber('/emergency', Bool, emergency_callback) - copter.start(task_manager) event_handler = AnimationEventHandler() observer = Observer() observer.schedule(event_handler, ".") observer.start() - while not rospy.is_shutdown: - rospy.sleep(1) + copter.start(task_manager) observer.stop() observer.join() From 57280d0ea4643d4ba6225a356e66387574ddc609 Mon Sep 17 00:00:00 2001 From: Arthur Golubtsov Date: Sat, 30 May 2020 21:33:57 +0300 Subject: [PATCH 27/49] Server: Update default widths in configspec --- Server/config/spec/configspec_server.ini | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Server/config/spec/configspec_server.ini b/Server/config/spec/configspec_server.ini index 7242d1d..6a191c8 100644 --- a/Server/config/spec/configspec_server.ini +++ b/Server/config/spec/configspec_server.ini @@ -17,17 +17,17 @@ config_version = float(default='1.0') [[[DEFAULT]]] copter_id = preset_param(default=list(True, 100)) git_version = preset_param(default=list(True, 75)) - config_version = preset_param(default=list(True, 140)) - animation_id = preset_param(default=list(True, 100)) + config_version = preset_param(default=list(True, 105)) + animation_info = preset_param(default=list(True, 100)) battery = preset_param(default=list(True, 100)) fcu_status = preset_param(default=list(True, 100)) calibration_status = preset_param(default=list(True, 65)) mode = preset_param(default=list(True, 100)) selfcheck = preset_param(default=list(True, 65)) current_position = preset_param(default=list(True, 250)) - start_position = preset_param(default=list(True, 150)) - last_task = preset_param(default=list(True, 250)) - time_delta = preset_param(default=list(True, 100)) + start_position = preset_param(default=list(True, 240)) + last_task = preset_param(default=list(True, 275)) + time_delta = preset_param(default=list(True, 70)) [[[__many__]]] __many__ = preset_param From 5297ee0385744f020f78363d1a497a316bff1c0d Mon Sep 17 00:00:00 2001 From: Arthur Golubtsov Date: Sat, 30 May 2020 21:37:43 +0300 Subject: [PATCH 28/49] copter_table_models: Handle telemetry --- Server/copter_table_models.py | 34 ++++++++++++++++++++++++++-------- 1 file changed, 26 insertions(+), 8 deletions(-) diff --git a/Server/copter_table_models.py b/Server/copter_table_models.py index 5af3894..c609cda 100644 --- a/Server/copter_table_models.py +++ b/Server/copter_table_models.py @@ -81,16 +81,19 @@ class ModelChecks: def check_ver(item): if not ModelChecks.check_git: return True - + version = get_git_version() if version is not None: return version == item return True -@ModelChecks.column_check("animation_id") +@ModelChecks.column_check("animation_info") def check_anim(item): - return str(item) != 'No animation' + if item: + return str(item[1]) == 'OK' + else: + return False @ModelChecks.column_check("battery") @@ -150,6 +153,7 @@ def check_start_pos(item, context): delta = get_distance(get_position(context.current_position), get_position(context.start_position)) + if math.isnan(delta): return False @@ -157,7 +161,7 @@ def check_start_pos(item, context): def get_position(position): - if position != 'NO_POS' and position[0] != 'nan': # float('nan')? + if not isinstance(position, str) and position[0] != float('nan'): return position[:3] return [float('nan')] * 3 @@ -276,6 +280,17 @@ def place_id(value): msgbox.exec_() return None +@ModelFormatter.view_formatter("animation_info") +def view_animation_info(value): + try: + id, state = value + except ValueError: + return "" + else: + if state == 'OK': + return id + else: + return state @ModelFormatter.place_formatter("battery") def place_battery(value): @@ -314,8 +329,11 @@ def view_current_position(value): @ModelFormatter.view_formatter("start_position") def view_start_position(value): if isinstance(value, list): - x, y, z = value - return f"{x: .2f} {y: .2f} {z: .2f}" + x, y, z, yaw, action, delay = value + if action in ['fly', 'takeoff']: + return f"{x: .2f} {y: .2f} {z: .2f} {int(yaw): d} {action} {delay: .1f}" + else: + return f"{action}" return value @@ -340,14 +358,14 @@ class CopterDataModel(QtCore.QAbstractTableModel): columns_dict = {'copter_id': 'copter ID', 'git_version': 'version', 'config_version': 'configuration', - 'animation_id': ' animation ID ', + 'animation_info': 'animation ID', 'battery': ' battery ', 'fcu_status': 'FCU status', 'calibration_status': 'sensors', 'mode': ' mode ', 'selfcheck': ' checks ', 'current_position': 'current x y z yaw frame_id', - 'start_position': ' start x y z ', + 'start_position': 'start x y z yaw action delay', 'last_task': 'last task', 'time_delta': 'dt', } From 494c30fad054186270d057cd48bc60fc675bdbe4 Mon Sep 17 00:00:00 2001 From: Arthur Golubtsov Date: Sat, 30 May 2020 21:38:08 +0300 Subject: [PATCH 29/49] Update tests --- tests/animation_4.csv | 161 ++++++++++++++++++++++++++++++++++++++++ tests/animation_test.py | 21 +++++- 2 files changed, 181 insertions(+), 1 deletion(-) create mode 100644 tests/animation_4.csv diff --git a/tests/animation_4.csv b/tests/animation_4.csv new file mode 100644 index 0000000..530caf5 --- /dev/null +++ b/tests/animation_4.csv @@ -0,0 +1,161 @@ +two_drones_test +1,0.2,1.4,1.0,0.15708,0,0,0 +2,0.2,1.4,1.0,0.15708,0,0,0 +3,0.2,1.4,1.0,0.15708,0,0,0 +4,0.2,1.4,1.0,0.15708,0,0,0 +5,0.2,1.4,1.0,0.15708,0,0,0 +6,0.2,1.4,1.0,0.15708,0,0,0 +7,0.2,1.4,1.0,0.15708,0,0,0 +8,0.2,1.4,1.0,0.15708,0,0,0 +9,0.2,1.4,1.0,0.15708,0,0,0 +10,0.2,1.4,1.0,0.15708,0,0,0 +11,0.20441,1.4,1.0,0.15708,0,0,0 +12,0.21774,1.4,1.0,0.15708,0,0,0 +13,0.24002,1.4,1.0,0.15708,0,0,0 +14,0.27111,1.4,1.0,0.15708,0,0,0 +15,0.31063,1.4,1.0,0.15708,0,0,0 +16,0.3579,1.4,1.0,0.15708,0,0,0 +17,0.41193,1.4,1.0,0.15708,0,0,0 +18,0.47141,1.4,1.0,0.15708,0,0,0 +19,0.53472,1.4,1.0,0.15708,0,0,0 +20,0.6,1.4,1.0,0.15708,0,0,0 +21,0.66528,1.4,1.0,0.15708,0,0,0 +22,0.72859,1.4,1.0,0.15708,0,0,0 +23,0.78807,1.4,1.0,0.15708,0,0,0 +24,0.8421,1.4,1.0,0.15708,0,0,0 +25,0.88937,1.4,1.0,0.15708,0,0,0 +26,0.92889,1.4,1.0,0.15708,0,0,0 +27,0.95998,1.4,1.0,0.15708,0,0,0 +28,0.98226,1.4,1.0,0.15708,0,0,0 +29,0.99559,1.4,1.0,0.15708,0,0,0 +30,1.0,1.4,1.0,0.15708,0,0,0 +31,1.0,1.40441,1.0,0.15708,0,0,0 +32,1.0,1.41774,1.0,0.15708,0,0,0 +33,1.0,1.44002,1.0,0.15708,0,0,0 +34,1.0,1.47111,1.0,0.15708,0,0,0 +35,1.0,1.51063,1.0,0.15708,0,0,0 +36,1.0,1.5579,1.0,0.15708,0,0,0 +37,1.0,1.61193,1.0,0.15708,0,0,0 +38,1.0,1.67141,1.0,0.15708,0,0,0 +39,1.0,1.73472,1.0,0.15708,0,0,0 +40,1.0,1.8,1.0,0.15708,0,0,0 +41,1.0,1.86528,1.0,0.15708,0,0,0 +42,1.0,1.92859,1.0,0.15708,0,0,0 +43,1.0,1.98807,1.0,0.15708,0,0,0 +44,1.0,2.0421,1.0,0.15708,0,0,0 +45,1.0,2.08937,1.0,0.15708,0,0,0 +46,1.0,2.12889,1.0,0.15708,0,0,0 +47,1.0,2.15998,1.0,0.15708,0,0,0 +48,1.0,2.18226,1.0,0.15708,0,0,0 +49,1.0,2.19559,1.0,0.15708,0,0,0 +50,1.0,2.2,1.0,0.15708,0,0,0 +51,0.99559,2.2,1.0,0.15708,0,0,0 +52,0.98226,2.2,1.0,0.15708,0,0,0 +53,0.95998,2.2,1.0,0.15708,0,0,0 +54,0.92889,2.2,1.0,0.15708,0,0,0 +55,0.88937,2.2,1.0,0.15708,0,0,0 +56,0.8421,2.2,1.0,0.15708,0,0,0 +57,0.78807,2.2,1.0,0.15708,0,0,0 +58,0.72859,2.2,1.0,0.15708,0,0,0 +59,0.66528,2.2,1.0,0.15708,0,0,0 +60,0.6,2.2,1.0,0.15708,0,0,0 +61,0.53472,2.2,1.0,0.15708,0,0,0 +62,0.47141,2.2,1.0,0.15708,0,0,0 +63,0.41193,2.2,1.0,0.15708,0,0,0 +64,0.3579,2.2,1.0,0.15708,0,0,0 +65,0.31063,2.2,1.0,0.15708,0,0,0 +66,0.27111,2.2,1.0,0.15708,0,0,0 +67,0.24002,2.2,1.0,0.15708,0,0,0 +68,0.21774,2.2,1.0,0.15708,0,0,0 +69,0.20441,2.2,1.0,0.15708,0,0,0 +70,0.2,2.2,1.0,0.15708,0,0,0 +71,0.2,2.19559,1.0,0.15708,0,0,0 +72,0.2,2.18226,1.0,0.15708,0,0,0 +73,0.2,2.15998,1.0,0.15708,0,0,0 +74,0.2,2.12889,1.0,0.15708,0,0,0 +75,0.2,2.08937,1.0,0.15708,0,0,0 +76,0.2,2.0421,1.0,0.15708,0,0,0 +77,0.2,1.98807,1.0,0.15708,0,0,0 +78,0.2,1.92859,1.0,0.15708,0,0,0 +79,0.2,1.86528,1.0,0.15708,0,0,0 +80,0.2,1.8,1.0,0.15708,0,0,0 +81,0.2,1.73472,1.0,0.15708,0,0,0 +82,0.2,1.67141,1.0,0.15708,0,0,0 +83,0.2,1.61193,1.0,0.15708,0,0,0 +84,0.2,1.5579,1.0,0.15708,0,0,0 +85,0.2,1.51063,1.0,0.15708,0,0,0 +86,0.2,1.47111,1.0,0.15708,0,0,0 +87,0.2,1.44002,1.0,0.15708,0,0,0 +88,0.2,1.41774,1.0,0.15708,0,0,0 +89,0.2,1.40441,1.0,0.15708,0,0,0 +90,0.2,1.4,1.0,0.15708,0,0,0 +91,0.2062,1.4,1.0,0.15708,0,0,0 +92,0.22355,1.4,1.0,0.15708,0,0,0 +93,0.25043,1.4,1.0,0.15708,0,0,0 +94,0.28553,1.4,1.0,0.15708,0,0,0 +95,0.32771,1.4,1.0,0.15708,0,0,0 +96,0.3759,1.4,1.0,0.15708,0,0,0 +97,0.42903,1.4,1.0,0.15708,0,0,0 +98,0.48579,1.4,1.0,0.15708,0,0,0 +99,0.54421,1.4,1.0,0.15708,0,0,0 +100,0.6,1.4,1.0,0.15708,0,0,0 +101,0.66257,1.40492,1.025,0.15708,0,0,0 +102,0.72361,1.41958,1.05,0.31416,0,0,0 +103,0.7816,1.4436,1.075,0.47124,0,0,0 +104,0.83511,1.47639,1.1,0.62832,0,0,0 +105,0.88284,1.51716,1.125,0.7854,0,0,0 +106,0.92361,1.56489,1.15,0.94248,0,0,0 +107,0.9564,1.6184,1.175,1.09956,0,0,0 +108,0.98042,1.67639,1.2,1.25664,0,0,0 +109,0.99508,1.73743,1.225,1.41372,0,0,0 +110,1.0,1.8,1.25,1.5708,0,0,0 +111,0.99508,1.86257,1.275,1.72788,0,0,0 +112,0.98042,1.92361,1.3,1.88496,0,0,0 +113,0.9564,1.9816,1.325,2.04204,0,0,0 +114,0.92361,2.03511,1.35,2.19912,0,0,0 +115,0.88284,2.08284,1.375,2.35619,0,0,0 +116,0.83511,2.12361,1.4,2.51327,0,0,0 +117,0.7816,2.1564,1.425,2.67035,0,0,0 +118,0.72361,2.18042,1.45,2.82743,0,0,0 +119,0.66257,2.19508,1.475,2.98451,0,0,0 +120,0.6,2.2,1.5,-3.14159,0,0,0 +121,0.53743,2.19508,1.525,-2.98451,0,0,0 +122,0.47639,2.18042,1.55,-2.82743,0,0,0 +123,0.4184,2.1564,1.575,-2.67035,0,0,0 +124,0.36489,2.12361,1.6,-2.51327,0,0,0 +125,0.31716,2.08284,1.625,-2.35619,0,0,0 +126,0.27639,2.03511,1.65,-2.19911,0,0,0 +127,0.2436,1.9816,1.675,-2.04203,0,0,0 +128,0.21958,1.92361,1.7,-1.88495,0,0,0 +129,0.20492,1.86257,1.725,-1.72788,0,0,0 +130,0.2,1.8,1.75,-1.5708,0,0,0 +131,0.20492,1.73743,1.775,-1.41372,0,0,0 +132,0.21958,1.67639,1.8,-1.25664,0,0,0 +133,0.2436,1.6184,1.825,-1.09956,0,0,0 +134,0.27639,1.56489,1.85,-0.94248,0,0,0 +135,0.31716,1.51716,1.875,-0.7854,0,0,0 +136,0.36489,1.47639,1.9,-0.62832,0,0,0 +137,0.4184,1.4436,1.925,-0.47124,0,0,0 +138,0.47639,1.41958,1.95,-0.31416,0,0,0 +139,0.53743,1.40492,1.975,-0.15708,0,0,0 +140,0.6,1.4,2.0,0.0,0,0,0 +141,0.6,1.4,2.0,0.0,0,0,0 +142,0.6,1.4,2.0,0.0,0,0,0 +143,0.6,1.4,2.0,0.0,0,0,0 +144,0.6,1.4,2.0,0.0,0,0,0 +145,0.6,1.4,2.0,0.0,0,0,0 +146,0.6,1.4,2.0,0.0,0,0,0 +147,0.6,1.4,2.0,0.0,0,0,0 +148,0.6,1.4,2.0,0.0,0,0,0 +149,0.6,1.4,2.0,0.0,0,0,0 +150,0.6,1.4,2.0,0.0,0,0,0 +151,0.6,1.4,2.0,0.0,0,0,0 +152,0.6,1.4,2.0,0.0,0,0,0 +153,0.6,1.4,2.0,0.0,0,0,0 +154,0.6,1.4,2.0,0.0,0,0,0 +155,0.6,1.4,2.0,0.0,0,0,0 +156,0.6,1.4,2.0,0.0,0,0,0 +157,0.6,1.4,2.0,0.0,0,0,0 +158,0.6,1.4,2.0,0.0,0,0,0 +159,0.6,1.4,2.0,0.0,0,0,0 +160,0.6,1.4,2.0,0.0,0,0,0 diff --git a/tests/animation_test.py b/tests/animation_test.py index 5fde3b8..ce0abd7 100644 --- a/tests/animation_test.py +++ b/tests/animation_test.py @@ -75,7 +75,7 @@ def test_animation_2(): def test_animation_3(): a.update_frames(config, "animation_3.csv") assert a.id == 'route' - assert a.original_frames[9].get_pos() == [0.97783,0.0,1.0] + assert approx(a.original_frames[9].get_pos()) == [0.97783,0.0,1.0] assert a.original_frames[9].get_color() == [0,204,2] assert a.original_frames[9].pose_is_valid() assert animation_lib.get_numbers(a.static_begin_frames) == [] @@ -90,6 +90,25 @@ def test_animation_3(): assert approx(a.get_scaled_output_min_z(ratio=[1,2,3], offset=[4,5,6])) == 9 assert approx(a.get_start_point(ratio=[1,2,3], offset=[4,5,6])) == [4,5,9] +def test_animation_4(): + a.update_frames(config, "animation_4.csv") + assert a.id == 'two_drones_test' + assert approx(a.original_frames[11].get_pos()) == [0.21774,1.4,1.0] + assert a.original_frames[11].get_color() == [0,0,0] + assert a.original_frames[11].pose_is_valid() + assert animation_lib.get_numbers(a.static_begin_frames) == range(1,12) + assert animation_lib.get_numbers(a.takeoff_frames) == [] + assert animation_lib.get_numbers(a.route_frames) == range(12,141) + assert animation_lib.get_numbers(a.land_frames) == [] + assert animation_lib.get_numbers(a.static_end_frames) == range(141,161) + assert animation_lib.get_numbers(a.output_frames) == range(12,141) + assert approx(a.static_begin_time) == 1.1 + assert approx(a.takeoff_time) == 0 + assert approx(a.output_frames_min_z) == 1 + assert approx(a.get_scaled_output(ratio=[1,2,3], offset=[4,5,6])[0].get_pos()) == [4.21774,7.8,9] + assert approx(a.get_scaled_output_min_z(ratio=[1,2,3], offset=[4,5,6])) == 9 + assert approx(a.get_start_point(ratio=[1,2,3], offset=[4,5,6])) == [4.21774,7.8,9] + def test_animation_no_file(): a.update_frames(config, "zzz.csv") assert a.id == None From e6fa25e27e283718f10bd216e0cf80c25869cab2 Mon Sep 17 00:00:00 2001 From: Arthur Golubtsov Date: Sat, 30 May 2020 21:39:06 +0300 Subject: [PATCH 30/49] Update update_configspec script not to rewrite default values from existiong configspec --- update_configspec.py | 32 ++++++++++++++++++++++++++------ 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/update_configspec.py b/update_configspec.py index 590b32d..e1d2bf2 100644 --- a/update_configspec.py +++ b/update_configspec.py @@ -1,14 +1,34 @@ +import os +import shutil import config from Server.copter_table_models import CopterDataModel -cfg_server = config.ConfigObj('SERVER/config/spec/configspec_server.ini', list_values=False) -widths = {"copter_id": 150} -default_width = 100 +from config import ConfigManager, ConfigObj -default = {key: f"preset_param(default=list(True, {widths.get(key, default_width)}))" +config_path = 'temp_config/config' +spec_path = os.path.join(config_path,'spec') +if not os.path.exists(spec_path): + try: + os.makedirs(spec_path) + except OSError: + print("Creation of the directory {} failed".format(spec_path)) + else: + print("Successfully created the directory {}".format(spec_path)) + +shutil.copy("Server/config/spec/configspec_server.ini", spec_path) + +config = ConfigManager() +config.load_config_and_spec(os.path.join(config_path,'server.ini')) + +preset_params = config.table_presets_default +default_param = (True, 100) + +default = {key: f"preset_param(default=list{preset_params.get(key, default_param)})" for key in CopterDataModel.columns} +cfg_server = ConfigObj('Server/config/spec/configspec_server.ini', list_values=False) cfg_server['TABLE']['PRESETS']['DEFAULT'] = default - cfg_server.write() -print('Server configspec updated') + +print('Server configspec updated!') +shutil.rmtree('temp_config') From a5170aa204eba22cb2f6f2c4a98e0ed8df26e0cc Mon Sep 17 00:00:00 2001 From: Arthur Golubtsov Date: Sun, 31 May 2020 09:21:32 +0300 Subject: [PATCH 31/49] Drone: Fix starting animation --- Drone/copter_client.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Drone/copter_client.py b/Drone/copter_client.py index d45ea8d..ace4ed2 100644 --- a/Drone/copter_client.py +++ b/Drone/copter_client.py @@ -651,7 +651,7 @@ def _play_animation(*args, **kwargs): # Get start action and delay try: - start_action, start_delay = copter.telemetry.start_action_and_delay + start_action, start_delay = copter.telemetry.start_position[-2:] except ValueError: logger.error("start: Can't get animation start position and delay") return @@ -681,7 +681,7 @@ def _play_animation(*args, **kwargs): # Calculate first frame start time frame_time = rfp_time + copter.config.copter_reach_first_point_time - elif start_action == 'arm': + elif start_action == 'fly': # Calculate start time arm_time = start_time + start_delay # + 1.0 task_manager.add_task(arm_time, 0, animation.execute_frame, From d4d51231b3d8b8227627e2a1b40053603444202e Mon Sep 17 00:00:00 2001 From: Arthur Golubtsov Date: Sun, 31 May 2020 09:23:08 +0300 Subject: [PATCH 32/49] Server: Update UI to implement fly to point button and send animation action --- Server/server_gui.py | 45 ++++++++++++++++----------- Server/server_gui.ui | 72 ++++++++++++++++++++++++++------------------ 2 files changed, 70 insertions(+), 47 deletions(-) diff --git a/Server/server_gui.py b/Server/server_gui.py index 448c60a..79532a6 100644 --- a/Server/server_gui.py +++ b/Server/server_gui.py @@ -13,7 +13,7 @@ from PyQt5 import QtCore, QtGui, QtWidgets class Ui_MainWindow(object): def setupUi(self, MainWindow): MainWindow.setObjectName("MainWindow") - MainWindow.resize(1360, 816) + MainWindow.resize(1360, 869) self.centralwidget = QtWidgets.QWidget(MainWindow) self.centralwidget.setEnabled(True) self.centralwidget.setObjectName("centralwidget") @@ -44,30 +44,22 @@ class Ui_MainWindow(object): self.verticalLayout.setSizeConstraint(QtWidgets.QLayout.SetMaximumSize) self.verticalLayout.setObjectName("verticalLayout") self.formLayout = QtWidgets.QFormLayout() - self.formLayout.setLabelAlignment(QtCore.Qt.AlignHCenter|QtCore.Qt.AlignTop) + self.formLayout.setLabelAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignTop) self.formLayout.setFormAlignment(QtCore.Qt.AlignHCenter|QtCore.Qt.AlignTop) self.formLayout.setObjectName("formLayout") self.start_text = QtWidgets.QLabel(self.centralwidget) self.start_text.setLayoutDirection(QtCore.Qt.RightToLeft) - self.start_text.setAlignment(QtCore.Qt.AlignCenter) + self.start_text.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignVCenter) self.start_text.setObjectName("start_text") self.formLayout.setWidget(0, QtWidgets.QFormLayout.LabelRole, self.start_text) self.start_delay_spin = QtWidgets.QSpinBox(self.centralwidget) self.start_delay_spin.setObjectName("start_delay_spin") self.formLayout.setWidget(0, QtWidgets.QFormLayout.FieldRole, self.start_delay_spin) - self.music_text = QtWidgets.QLabel(self.centralwidget) - self.music_text.setLayoutDirection(QtCore.Qt.RightToLeft) - self.music_text.setObjectName("music_text") - self.formLayout.setWidget(1, QtWidgets.QFormLayout.LabelRole, self.music_text) self.music_delay_spin = QtWidgets.QDoubleSpinBox(self.centralwidget) self.music_delay_spin.setDecimals(1) self.music_delay_spin.setMaximum(1000.0) self.music_delay_spin.setObjectName("music_delay_spin") self.formLayout.setWidget(1, QtWidgets.QFormLayout.FieldRole, self.music_delay_spin) - self.music_play_text = QtWidgets.QLabel(self.centralwidget) - self.music_play_text.setLayoutDirection(QtCore.Qt.RightToLeft) - self.music_play_text.setObjectName("music_play_text") - self.formLayout.setWidget(2, QtWidgets.QFormLayout.LabelRole, self.music_play_text) self.music_checkbox = QtWidgets.QCheckBox(self.centralwidget) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) @@ -82,6 +74,14 @@ class Ui_MainWindow(object): self.music_checkbox.setChecked(False) self.music_checkbox.setObjectName("music_checkbox") self.formLayout.setWidget(2, QtWidgets.QFormLayout.FieldRole, self.music_checkbox) + self.music_text = QtWidgets.QLabel(self.centralwidget) + self.music_text.setLayoutDirection(QtCore.Qt.RightToLeft) + self.music_text.setObjectName("music_text") + self.formLayout.setWidget(1, QtWidgets.QFormLayout.LabelRole, self.music_text) + self.music_play_text = QtWidgets.QLabel(self.centralwidget) + self.music_play_text.setLayoutDirection(QtCore.Qt.RightToLeft) + self.music_play_text.setObjectName("music_play_text") + self.formLayout.setWidget(2, QtWidgets.QFormLayout.LabelRole, self.music_play_text) self.verticalLayout.addLayout(self.formLayout) self.line = QtWidgets.QFrame(self.centralwidget) self.line.setFrameShape(QtWidgets.QFrame.HLine) @@ -186,7 +186,10 @@ class Ui_MainWindow(object): self.formLayout_4.setLayout(2, QtWidgets.QFormLayout.FieldRole, self.horizontalLayout_2) self.flip_button = QtWidgets.QPushButton(self.centralwidget) self.flip_button.setObjectName("flip_button") - self.formLayout_4.setWidget(3, QtWidgets.QFormLayout.FieldRole, self.flip_button) + self.formLayout_4.setWidget(4, QtWidgets.QFormLayout.FieldRole, self.flip_button) + self.fly_button = QtWidgets.QPushButton(self.centralwidget) + self.fly_button.setObjectName("fly_button") + self.formLayout_4.setWidget(3, QtWidgets.QFormLayout.FieldRole, self.fly_button) self.verticalLayout.addLayout(self.formLayout_4) self.line_4 = QtWidgets.QFrame(self.centralwidget) self.line_4.setFrameShape(QtWidgets.QFrame.HLine) @@ -212,7 +215,7 @@ class Ui_MainWindow(object): self.gridLayout.addLayout(self.horizontalLayout, 0, 0, 1, 1) MainWindow.setCentralWidget(self.centralwidget) self.menubar = QtWidgets.QMenuBar(MainWindow) - self.menubar.setGeometry(QtCore.QRect(0, 0, 1360, 25)) + self.menubar.setGeometry(QtCore.QRect(0, 0, 1360, 22)) self.menubar.setObjectName("menubar") self.menuOptions = QtWidgets.QMenu(self.menubar) self.menuOptions.setObjectName("menuOptions") @@ -301,6 +304,8 @@ class Ui_MainWindow(object): self.action_configure_columns.setObjectName("action_configure_columns") self.actionSomething = QtWidgets.QAction(MainWindow) self.actionSomething.setObjectName("actionSomething") + self.action_send_animation = QtWidgets.QAction(MainWindow) + self.action_send_animation.setObjectName("action_send_animation") self.menuMusic_2.addAction(self.action_select_music_file) self.menuMusic_2.addAction(self.action_play_music) self.menuMusic_2.addAction(self.action_stop_music) @@ -319,10 +324,12 @@ class Ui_MainWindow(object): self.menuTable.addSeparator() self.menuTable.addAction(self.action_configure_columns) self.menuSend.addAction(self.action_send_animations) + self.menuSend.addAction(self.action_send_calibrations) + self.menuSend.addSeparator() + self.menuSend.addAction(self.action_send_aruco_map) + self.menuSend.addAction(self.action_send_animation) self.menuSend.addAction(self.action_send_configurations) self.menuSend.addAction(self.action_send_launch_file) - self.menuSend.addAction(self.action_send_aruco_map) - self.menuSend.addAction(self.action_send_calibrations) self.menuSend.addAction(self.action_send_fcu_parameters) self.menuSend.addSeparator() self.menuSend.addAction(self.action_send_any_file) @@ -357,8 +364,8 @@ class Ui_MainWindow(object): MainWindow.setWindowTitle(_translate("MainWindow", "Clever Drone Show")) self.start_text.setText(_translate("MainWindow", " Start after")) self.start_delay_spin.setSuffix(_translate("MainWindow", " s")) - self.music_text.setText(_translate("MainWindow", " Music after")) self.music_delay_spin.setSuffix(_translate("MainWindow", " s")) + self.music_text.setText(_translate("MainWindow", " Music after")) self.music_play_text.setText(_translate("MainWindow", " Play music")) self.check_button.setText(_translate("MainWindow", "Preflight check")) self.start_button.setText(_translate("MainWindow", "Start animation")) @@ -374,6 +381,7 @@ class Ui_MainWindow(object): self.z_checkbox.setText(_translate("MainWindow", " Z =")) self.z_spin.setSuffix(_translate("MainWindow", " m")) self.flip_button.setText(_translate("MainWindow", "Flip")) + self.fly_button.setText(_translate("MainWindow", "Fly to point")) self.reboot_fcu.setText(_translate("MainWindow", "Reboot FCU")) self.calibrate_gyro.setText(_translate("MainWindow", "Calibrate gyro")) self.calibrate_level.setText(_translate("MainWindow", "Calibrate level")) @@ -389,7 +397,7 @@ class Ui_MainWindow(object): self.action_send_aruco_map.setText(_translate("MainWindow", "Aruco map")) self.action_update_client_repo.setText(_translate("MainWindow", "Update clever-show git")) self.actionSend_launch_file_for_clever.setText(_translate("MainWindow", "Send launch file for clever")) - self.action_send_launch_file.setText(_translate("MainWindow", "Launch files")) + self.action_send_launch_file.setText(_translate("MainWindow", "Launch files folder")) self.action_restart_clever.setText(_translate("MainWindow", "clever")) self.action_restart_clever_show.setText(_translate("MainWindow", "clever-show")) self.action_select_all_rows.setText(_translate("MainWindow", "Select all drones")) @@ -410,7 +418,7 @@ class Ui_MainWindow(object): self.action_send_calibrations.setText(_translate("MainWindow", "Camera calibrations")) self.action_reboot_all.setText(_translate("MainWindow", "Reboot")) self.action_restart_chrony.setText(_translate("MainWindow", "chrony")) - self.action_send_fcu_parameters.setText(_translate("MainWindow", "FCU parameters")) + self.action_send_fcu_parameters.setText(_translate("MainWindow", "FCU parameters file")) self.action_toggle_select.setText(_translate("MainWindow", "Toggle select")) self.action_toggle_select.setShortcut(_translate("MainWindow", "Ctrl+A")) self.action_select_all.setText(_translate("MainWindow", "Select all")) @@ -424,3 +432,4 @@ class Ui_MainWindow(object): self.action_restart_server.setText(_translate("MainWindow", "Restart server")) self.action_configure_columns.setText(_translate("MainWindow", "Configure columns")) self.actionSomething.setText(_translate("MainWindow", "something")) + self.action_send_animation.setText(_translate("MainWindow", "Animation")) diff --git a/Server/server_gui.ui b/Server/server_gui.ui index e00e6f4..d193528 100644 --- a/Server/server_gui.ui +++ b/Server/server_gui.ui @@ -7,7 +7,7 @@ 0 0 1360 - 816 + 869 @@ -71,7 +71,7 @@ - Qt::AlignHCenter|Qt::AlignTop + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop Qt::AlignHCenter|Qt::AlignTop @@ -85,7 +85,7 @@ Start after - Qt::AlignCenter + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter @@ -96,16 +96,6 @@ - - - - Qt::RightToLeft - - - Music after - - - @@ -119,16 +109,6 @@ - - - - Qt::RightToLeft - - - Play music - - - @@ -157,6 +137,26 @@ + + + + Qt::RightToLeft + + + Music after + + + + + + + Qt::RightToLeft + + + Play music + + + @@ -367,13 +367,20 @@ - + Flip + + + + Fly to point + + + @@ -426,7 +433,7 @@ 0 0 1360 - 25 + 22 @@ -470,10 +477,12 @@ Send + + + + - - @@ -538,7 +547,7 @@ - Launch files + Launch files folder @@ -639,7 +648,7 @@ - FCU parameters + FCU parameters file @@ -707,6 +716,11 @@ something + + + Animation + + start_delay_spin From 0c54b97b2b0ec7f28e9d315e468f9960ef1c7ae8 Mon Sep 17 00:00:00 2001 From: Arthur Golubtsov Date: Sun, 31 May 2020 09:39:39 +0300 Subject: [PATCH 33/49] Server: Add send animation action handler --- Server/server_qt.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Server/server_qt.py b/Server/server_qt.py index 2c315a8..cef05e1 100644 --- a/Server/server_qt.py +++ b/Server/server_qt.py @@ -161,6 +161,7 @@ class MainWindow(QtWidgets.QMainWindow): self.ui.action_send_animations.triggered.connect(self.send_animations) self.ui.action_send_calibrations.triggered.connect(self.send_calibrations) + self.ui.action_send_animation.triggered.connect(self.send_animation) self.ui.action_send_configurations.triggered.connect(self.send_config) self.ui.action_send_aruco_map.triggered.connect(self.send_aruco) self.ui.action_send_launch_file.triggered.connect(self.send_launch) @@ -481,6 +482,10 @@ class MainWindow(QtWidgets.QMainWindow): self.send_directory_files("Select directory with animations", ('.csv', '.txt'), match_id=True, client_path="", client_filename="animation.csv") + @pyqtSlot() + def send_animation(self): + self.send_files("Select animation file", "Animation files (*.csv)", onefile=True, client_filename="animation.csv") + @pyqtSlot() def send_calibrations(self): self.send_directory_files("Select directory with calibrations", ('.yaml', ), match_id=True, From 010c13b28c93150352c392465b13208f40586bb6 Mon Sep 17 00:00:00 2001 From: Arthur Golubtsov Date: Sun, 31 May 2020 09:54:17 +0300 Subject: [PATCH 34/49] Server: Remove fly to point button --- Server/server_gui.py | 6 +----- Server/server_gui.ui | 9 +-------- Server/server_qt.py | 4 ---- 3 files changed, 2 insertions(+), 17 deletions(-) diff --git a/Server/server_gui.py b/Server/server_gui.py index 79532a6..a163801 100644 --- a/Server/server_gui.py +++ b/Server/server_gui.py @@ -186,10 +186,7 @@ class Ui_MainWindow(object): self.formLayout_4.setLayout(2, QtWidgets.QFormLayout.FieldRole, self.horizontalLayout_2) self.flip_button = QtWidgets.QPushButton(self.centralwidget) self.flip_button.setObjectName("flip_button") - self.formLayout_4.setWidget(4, QtWidgets.QFormLayout.FieldRole, self.flip_button) - self.fly_button = QtWidgets.QPushButton(self.centralwidget) - self.fly_button.setObjectName("fly_button") - self.formLayout_4.setWidget(3, QtWidgets.QFormLayout.FieldRole, self.fly_button) + self.formLayout_4.setWidget(3, QtWidgets.QFormLayout.FieldRole, self.flip_button) self.verticalLayout.addLayout(self.formLayout_4) self.line_4 = QtWidgets.QFrame(self.centralwidget) self.line_4.setFrameShape(QtWidgets.QFrame.HLine) @@ -381,7 +378,6 @@ class Ui_MainWindow(object): self.z_checkbox.setText(_translate("MainWindow", " Z =")) self.z_spin.setSuffix(_translate("MainWindow", " m")) self.flip_button.setText(_translate("MainWindow", "Flip")) - self.fly_button.setText(_translate("MainWindow", "Fly to point")) self.reboot_fcu.setText(_translate("MainWindow", "Reboot FCU")) self.calibrate_gyro.setText(_translate("MainWindow", "Calibrate gyro")) self.calibrate_level.setText(_translate("MainWindow", "Calibrate level")) diff --git a/Server/server_gui.ui b/Server/server_gui.ui index d193528..f6c70c1 100644 --- a/Server/server_gui.ui +++ b/Server/server_gui.ui @@ -367,20 +367,13 @@ - + Flip - - - - Fly to point - - - diff --git a/Server/server_qt.py b/Server/server_qt.py index cef05e1..6627a74 100644 --- a/Server/server_qt.py +++ b/Server/server_qt.py @@ -112,10 +112,6 @@ class MainWindow(QtWidgets.QMainWindow): self.init_model() - # self.statusBar = QStatusBar() - # self.setStatusBar(self.statusBar) - # self.statusBar.showMessage("Hey", 2000) - self.register_callbacks() self.player = QtMultimedia.QMediaPlayer() From 022a2ae20d58b2baadd7c0ae01d72e53abd805e2 Mon Sep 17 00:00:00 2001 From: Arthur Golubtsov Date: Sun, 31 May 2020 11:55:10 +0300 Subject: [PATCH 35/49] Drone: Add config update monitoring --- Drone/copter_client.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Drone/copter_client.py b/Drone/copter_client.py index ace4ed2..8c615e6 100644 --- a/Drone/copter_client.py +++ b/Drone/copter_client.py @@ -990,7 +990,7 @@ class AnimationEventHandler(FileSystemEventHandler): def on_any_event(self, event): logger.info('{} is {}'.format(event.src_path, event.event_type)) # logger.info(os.path.splitext(event.src_path)) - if os.path.splitext(event.src_path)[-1] == '.csv' and event.event_type != "deleted": + if (os.path.splitext(event.src_path)[-1] == '.csv' and event.event_type != "deleted") or event.src_path.split('/')[-1] == 'client.ini': if os.path.exists("animation.csv"): logger.info("Update frames from animation.csv") copter.animation.update_frames(copter.config, "animation.csv") @@ -1002,7 +1002,7 @@ if __name__ == "__main__": rospy.Subscriber('/emergency', Bool, emergency_callback) event_handler = AnimationEventHandler() observer = Observer() - observer.schedule(event_handler, ".") + observer.schedule(event_handler, ".", recursive=True) observer.start() copter.start(task_manager) observer.stop() From 3db15f672236b33891f37581a9f72212ae7226cd Mon Sep 17 00:00:00 2001 From: Arthur Golubtsov Date: Sun, 31 May 2020 11:55:37 +0300 Subject: [PATCH 36/49] Server: Fix yaw handling --- Server/copter_table_models.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Server/copter_table_models.py b/Server/copter_table_models.py index c609cda..211e3bd 100644 --- a/Server/copter_table_models.py +++ b/Server/copter_table_models.py @@ -322,7 +322,7 @@ def view_selfcheck(value): def view_current_position(value): if isinstance(value, list): x, y, z, yaw, frame = value - return f"{x: .2f} {y: .2f} {z: .2f} {int(yaw): d} {frame}" + return f"{x: .2f} {y: .2f} {z: .2f} {yaw: .0f} {frame}" return value @@ -331,7 +331,7 @@ def view_start_position(value): if isinstance(value, list): x, y, z, yaw, action, delay = value if action in ['fly', 'takeoff']: - return f"{x: .2f} {y: .2f} {z: .2f} {int(yaw): d} {action} {delay: .1f}" + return f"{x: .2f} {y: .2f} {z: .2f} {yaw: .0f} {action} {delay: .1f}" else: return f"{action}" return value From 1a686cdf933910fdfa16922cde928421a426b711 Mon Sep 17 00:00:00 2001 From: Arthur Golubtsov Date: Sun, 31 May 2020 11:56:37 +0300 Subject: [PATCH 37/49] Drone: Update requirements --- Drone/requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/Drone/requirements.txt b/Drone/requirements.txt index f46bac3..260a5f8 100644 --- a/Drone/requirements.txt +++ b/Drone/requirements.txt @@ -1,3 +1,4 @@ selectors2 psutil configobj +watchdog From 19ffb1bf58cfe726bf930de1ec93d2a62792d935 Mon Sep 17 00:00:00 2001 From: Arthur Golubtsov Date: Sun, 31 May 2020 17:54:01 +0300 Subject: [PATCH 38/49] Drone: Make observer daemon --- Drone/copter_client.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Drone/copter_client.py b/Drone/copter_client.py index 8c615e6..437a327 100644 --- a/Drone/copter_client.py +++ b/Drone/copter_client.py @@ -1003,7 +1003,8 @@ if __name__ == "__main__": event_handler = AnimationEventHandler() observer = Observer() observer.schedule(event_handler, ".", recursive=True) + observer.daemon = True observer.start() copter.start(task_manager) - observer.stop() - observer.join() + #observer.stop() + #observer.join() From c311a9d4e2d9c31a33981d7cc8ac44c5d169def8 Mon Sep 17 00:00:00 2001 From: Arthur Golubtsov Date: Sun, 31 May 2020 18:10:33 +0300 Subject: [PATCH 39/49] Drone: Turn off observer for tests --- Drone/copter_client.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Drone/copter_client.py b/Drone/copter_client.py index 437a327..075b147 100644 --- a/Drone/copter_client.py +++ b/Drone/copter_client.py @@ -1000,11 +1000,11 @@ if __name__ == "__main__": copter = CopterClient() task_manager = tasking.TaskManager() rospy.Subscriber('/emergency', Bool, emergency_callback) - event_handler = AnimationEventHandler() - observer = Observer() - observer.schedule(event_handler, ".", recursive=True) - observer.daemon = True - observer.start() + #event_handler = AnimationEventHandler() + #observer = Observer() + #observer.schedule(event_handler, ".", recursive=True) + #observer.daemon = True + #observer.start() copter.start(task_manager) #observer.stop() #observer.join() From db6c16599dae9b75a9e7de4498c1411c3c66fb4b Mon Sep 17 00:00:00 2001 From: Arthur Golubtsov Date: Sun, 31 May 2020 18:19:24 +0300 Subject: [PATCH 40/49] Server: Fix handling start position --- Server/copter_table_models.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Server/copter_table_models.py b/Server/copter_table_models.py index 211e3bd..aa4917a 100644 --- a/Server/copter_table_models.py +++ b/Server/copter_table_models.py @@ -145,6 +145,10 @@ def check_time_delta(item): @ModelChecks.column_check("start_position", pass_context=True) def check_start_pos(item, context): + if len(item) == 6: + if not item[4] in ["takeoff", "fly"]: + return False + if ModelChecks.start_pos_delta_max == 0: return True From f38dbe08cdf11e435b9134fa132a7266fcda77bb Mon Sep 17 00:00:00 2001 From: Arthur Golubtsov Date: Mon, 1 Jun 2020 04:56:24 +0300 Subject: [PATCH 41/49] Drone: Make threads be daemons --- Drone/copter_client.py | 6 +++--- Drone/tasking_lib.py | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Drone/copter_client.py b/Drone/copter_client.py index 075b147..d258358 100644 --- a/Drone/copter_client.py +++ b/Drone/copter_client.py @@ -162,7 +162,7 @@ class CopterClient(client.Client): trans.header.frame_id = "map" trans.child_frame_id = "gps" static_broadcaster.sendTransform(trans) - gps_frame_thread = threading.Thread(target=self.gps_frame_broadcast_loop, name="GPS frame broadcast thread") + gps_frame_thread = threading.Thread(target=self.gps_frame_broadcast_loop, name="GPS frame broadcast thread", daemon=True) gps_frame_thread.start() def gps_frame_broadcast_loop(self): @@ -966,9 +966,9 @@ class Telemetry: def start_loop(self): if copter.config.telemetry_frequency > 0: telemetry_thread = threading.Thread(target=self._update_loop, name="Telemetry getting thread", - args=(copter.config.telemetry_frequency,)) # TODO MOVE? Daemon? + args=(copter.config.telemetry_frequency,), daemon=True) # TODO MOVE? Daemon? slow_telemetry_thread = threading.Thread(target=self._slow_update_loop, - name="Slow telemetry getting thread") + name="Slow telemetry getting thread", daemon=True) slow_telemetry_thread.start() telemetry_thread.start() else: diff --git a/Drone/tasking_lib.py b/Drone/tasking_lib.py index c64dbcb..743e940 100644 --- a/Drone/tasking_lib.py +++ b/Drone/tasking_lib.py @@ -31,8 +31,8 @@ class TaskManager(object): self.task_queue = [] self._counter = itertools.count() # unique sequence count - self._processor_thread = threading.Thread(target=self._task_processor, name="Task processing thread") - self._processor_thread.daemon = True + self._processor_thread = threading.Thread(target=self._task_processor, name="Task processing thread", daemon=True) + #self._processor_thread.daemon = True self._task_queue_lock = threading.RLock() self._running_event = threading.Event() From c924f3ad0193fb7d5cf20b8f43ec4e9b80adb555 Mon Sep 17 00:00:00 2001 From: Arthur Golubtsov Date: Mon, 1 Jun 2020 05:06:02 +0300 Subject: [PATCH 42/49] Drone: Fix daemon argument --- Drone/copter_client.py | 13 ++++++++----- Drone/tasking_lib.py | 4 ++-- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/Drone/copter_client.py b/Drone/copter_client.py index d258358..fc6604c 100644 --- a/Drone/copter_client.py +++ b/Drone/copter_client.py @@ -162,7 +162,8 @@ class CopterClient(client.Client): trans.header.frame_id = "map" trans.child_frame_id = "gps" static_broadcaster.sendTransform(trans) - gps_frame_thread = threading.Thread(target=self.gps_frame_broadcast_loop, name="GPS frame broadcast thread", daemon=True) + gps_frame_thread = threading.Thread(target=self.gps_frame_broadcast_loop, name="GPS frame broadcast thread") + gps_frame_thread.daemon = True gps_frame_thread.start() def gps_frame_broadcast_loop(self): @@ -966,11 +967,13 @@ class Telemetry: def start_loop(self): if copter.config.telemetry_frequency > 0: telemetry_thread = threading.Thread(target=self._update_loop, name="Telemetry getting thread", - args=(copter.config.telemetry_frequency,), daemon=True) # TODO MOVE? Daemon? - slow_telemetry_thread = threading.Thread(target=self._slow_update_loop, - name="Slow telemetry getting thread", daemon=True) - slow_telemetry_thread.start() + args=(copter.config.telemetry_frequency,)) + telemetry_thread.daemon = True telemetry_thread.start() + slow_telemetry_thread = threading.Thread(target=self._slow_update_loop, + name="Slow telemetry getting thread") + slow_telemetry_thread.daemon = True + slow_telemetry_thread.start() else: logger.info("Telemetry loop is not created because of zero or negative telemetry frequency") diff --git a/Drone/tasking_lib.py b/Drone/tasking_lib.py index 743e940..c64dbcb 100644 --- a/Drone/tasking_lib.py +++ b/Drone/tasking_lib.py @@ -31,8 +31,8 @@ class TaskManager(object): self.task_queue = [] self._counter = itertools.count() # unique sequence count - self._processor_thread = threading.Thread(target=self._task_processor, name="Task processing thread", daemon=True) - #self._processor_thread.daemon = True + self._processor_thread = threading.Thread(target=self._task_processor, name="Task processing thread") + self._processor_thread.daemon = True self._task_queue_lock = threading.RLock() self._running_event = threading.Event() From 708e8ad3d6bf8811c3ecdb5644cc935634d946d6 Mon Sep 17 00:00:00 2001 From: Arthur Golubtsov Date: Mon, 1 Jun 2020 05:30:24 +0300 Subject: [PATCH 43/49] Drone: Turn on file observer --- Drone/copter_client.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/Drone/copter_client.py b/Drone/copter_client.py index fc6604c..6c9444b 100644 --- a/Drone/copter_client.py +++ b/Drone/copter_client.py @@ -1003,11 +1003,9 @@ if __name__ == "__main__": copter = CopterClient() task_manager = tasking.TaskManager() rospy.Subscriber('/emergency', Bool, emergency_callback) - #event_handler = AnimationEventHandler() - #observer = Observer() - #observer.schedule(event_handler, ".", recursive=True) - #observer.daemon = True - #observer.start() + event_handler = AnimationEventHandler() + observer = Observer() + observer.schedule(event_handler, ".", recursive=True) + observer.daemon = True + observer.start() copter.start(task_manager) - #observer.stop() - #observer.join() From 22073c0e808f40d45486695ee10e4810bc196646 Mon Sep 17 00:00:00 2001 From: Arthur Golubtsov Date: Mon, 1 Jun 2020 08:01:26 +0300 Subject: [PATCH 44/49] Server: Update default logging level for server_qt --- Server/server_qt.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Server/server_qt.py b/Server/server_qt.py index 6627a74..daac548 100644 --- a/Server/server_qt.py +++ b/Server/server_qt.py @@ -288,7 +288,7 @@ class MainWindow(QtWidgets.QMainWindow): try: col = self.model.columns.index(key) except ValueError: - logging.error(f"No column {key} present!") + logging.debug(f"No column {key} present!") else: row_data = self.model.get_row_by_attr("client", client) row_num = self.model.get_row_index(row_data) @@ -647,7 +647,7 @@ if __name__ == "__main__": msgbox_handler.setLevel(logging.CRITICAL) logging.basicConfig( - level=logging.DEBUG, + level=logging.INFO, format="%(asctime)s [%(name)-7.7s] [%(threadName)-19.19s] [%(levelname)-7.7s] %(message)s", handlers=[ logging.FileHandler("server_logs/{}.log".format(now)), From 8e7ee15da94e09038be91311df4639bbb0983b45 Mon Sep 17 00:00:00 2001 From: Arthur Golubtsov Date: Mon, 1 Jun 2020 08:01:51 +0300 Subject: [PATCH 45/49] Server: Fix config getting from copter --- Server/copter_table.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Server/copter_table.py b/Server/copter_table.py index 2a1df89..f216432 100644 --- a/Server/copter_table.py +++ b/Server/copter_table.py @@ -226,8 +226,8 @@ class CopterTableWidget(QTableView): @pyqtSlot(QtCore.QPoint) def open_menu(self, point): menu = QMenu(self) - index = self.indexAt(point) - item = self.model.get_row_data(index) + id = self.indexAt(point).siblingAtColumn(0).data() + item = self.model.get_row_by_attr('copter_id', id) edit_config = QAction("Edit config") edit_config.triggered.connect(partial(self.edit_copter_config, item)) From b8f6210bbadc6273dc3a29986423adf5cb216de6 Mon Sep 17 00:00:00 2001 From: Arthur Golubtsov Date: Mon, 1 Jun 2020 10:31:29 +0300 Subject: [PATCH 46/49] Drone: Make base client loop to thread --- Drone/copter_client.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Drone/copter_client.py b/Drone/copter_client.py index 6c9444b..6b32daa 100644 --- a/Drone/copter_client.py +++ b/Drone/copter_client.py @@ -137,7 +137,10 @@ class CopterClient(client.Client): self.start_floor_frame_broadcast() elif self.config.copter_frame_id == "gps": self.start_gps_frame_broadcast() - super(CopterClient, self).start() + client_thread = threading.Thread(target=super(CopterClient, self).start, name="Client thread") + client_thread.daemon = True + client_thread.start() + #super(CopterClient, self).start() def start_floor_frame_broadcast(self): if self.config.floor_frame_parent == "gps": @@ -1009,3 +1012,5 @@ if __name__ == "__main__": observer.daemon = True observer.start() copter.start(task_manager) + while not rospy.is_shutdown(): + rospy.sleep(0.1) From 6473607df413a18c46ac6d7cf03ed998198b709f Mon Sep 17 00:00:00 2001 From: Arthur Golubtsov Date: Mon, 1 Jun 2020 10:32:12 +0300 Subject: [PATCH 47/49] Server: Change clover service name for restarting --- Server/server_qt.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Server/server_qt.py b/Server/server_qt.py index daac548..bb0cb4a 100644 --- a/Server/server_qt.py +++ b/Server/server_qt.py @@ -168,7 +168,7 @@ class MainWindow(QtWidgets.QMainWindow): self.ui.action_retrive_any_file.triggered.connect(b_partial(self.request_any_file, client_path=None)) self.ui.action_restart_clever.triggered.connect( - b_partial(self.send_to_selected, "service_restart", command_kwargs={"name": "clever"})) + b_partial(self.send_to_selected, "service_restart", command_kwargs={"name": "clover"})) self.ui.action_restart_clever_show.triggered.connect(self.restart_clever_show) self.ui.action_restart_chrony.triggered.connect(self.restart_chrony) self.ui.action_reboot_all.triggered.connect(b_partial(self.send_to_selected, "reboot_all")) From 187f6e09eb2d0ece396d2c59e50f8a350e14618c Mon Sep 17 00:00:00 2001 From: Arthur Golubtsov Date: Mon, 1 Jun 2020 13:24:40 +0300 Subject: [PATCH 48/49] Drone: Add variants for restarting clever service --- Drone/copter_client.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Drone/copter_client.py b/Drone/copter_client.py index 6b32daa..03c5d31 100644 --- a/Drone/copter_client.py +++ b/Drone/copter_client.py @@ -516,6 +516,9 @@ def _command_reboot(*args, **kwargs): @messaging.message_callback("service_restart") def _command_service_restart(*args, **kwargs): service = kwargs["name"] + if service=="clover": + restart_service("clever") + restart_service("clover@{}".format(copter.client_id)) restart_service(service) From 9e3553e4aa051d01c500f888f0707e57b0b6986c Mon Sep 17 00:00:00 2001 From: Arthur Golubtsov Date: Mon, 1 Jun 2020 13:28:42 +0300 Subject: [PATCH 49/49] Drone: Fix clever-show service handle --- Drone/copter_client.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Drone/copter_client.py b/Drone/copter_client.py index 03c5d31..5c87562 100644 --- a/Drone/copter_client.py +++ b/Drone/copter_client.py @@ -518,7 +518,8 @@ def _command_service_restart(*args, **kwargs): service = kwargs["name"] if service=="clover": restart_service("clever") - restart_service("clover@{}".format(copter.client_id)) + if service=="clever-show": + restart_service("clever-show@{}".format(copter.client_id)) restart_service(service)