From 50450e6d7e6ff8d173ab85e5cabc05415857dab9 Mon Sep 17 00:00:00 2001
From: Matthew Lee <{ID} {username}@users.noreply.github.com>
Date: Sun, 16 Aug 2026 14:01:39 +1200
Subject: [PATCH 1/6] Migrate to uv, rebuild CI/release pipeline, add template
test suite and docs
---
.github/workflows/ci-main.yml | 8 +-
.github/workflows/ci-release.yml | 43 +-
.github/workflows/initial-setup.yml | 38 +-
.../reusable-build-and-deploy-docs.yml | 18 +-
.github/workflows/reusable-maya-tests.yml | 93 +-
.github/workflows/reusable-release.yml | 119 +++
.../workflows/reusable-semantic-release.yml | 22 -
.../workflows/reusable-static-analysis.yml | 32 +-
.github/workflows/reusable-template-tests.yml | 24 +
.github/workflows/template-ci.yml | 17 +
.github/workflows/template-release.yml | 92 ++
.gitignore | 3 +
.pylintrc | 7 -
AGENTS.md | 81 ++
CLAUDE.md | 1 +
CONTRIBUTING.md | 202 +----
README.md | 40 +-
_new_project/AGENTS.md | 34 +
_new_project/CONTRIBUTING.md | 156 ++++
_README.md => _new_project/README.md | 9 +-
_new_project/docs/index.md | 3 +
.../resources/images/maya_python_logo.png | Bin
docs/ci.md | 47 +
docs/documentation.md | 38 +
docs/getting-started.md | 48 ++
docs/index.md | 23 +-
docs/project-layout.md | 17 +
docs/reference.md | 3 +
docs/releasing.md | 54 ++
docs/resources/images/ci_main.png | Bin 62841 -> 0 bytes
docs/resources/images/ci_release.png | Bin 50874 -> 0 bytes
docs/resources/images/gh-pages.png | Bin 268158 -> 0 bytes
docs/testing.md | 41 +
docs/writing-your-tool.md | 36 +
initial_setup.py | 218 ++++-
install_module.py | 331 ++++++++
mkdocs.yml | 41 +-
mypy.ini | 4 -
pyproject.toml | 26 +
release.py | 264 ++++++
requirements-dev.in | 7 -
requirements-test.in | 2 -
requirements.txt | 1 -
scripts/build_module.py | 388 +++++++++
.../mayapythonprojecttemplate/__init__.py | 0
src/mayapythonprojecttemplate/example.py | 3 +-
tests/conftest.py | 5 +-
tests/maya/conftest.py | 9 +-
tests/maya/test_example.py | 4 -
tests/template/conftest.py | 6 +
tests/template/test_build_module.py | 140 +++
tests/template/test_initial_setup.py | 285 +++++++
tests/template/test_release.py | 69 ++
tests/template/test_workflows.py | 84 ++
uv.lock | 800 ++++++++++++++++++
55 files changed, 3690 insertions(+), 346 deletions(-)
create mode 100644 .github/workflows/reusable-release.yml
delete mode 100644 .github/workflows/reusable-semantic-release.yml
create mode 100644 .github/workflows/reusable-template-tests.yml
create mode 100644 .github/workflows/template-ci.yml
create mode 100644 .github/workflows/template-release.yml
delete mode 100644 .pylintrc
create mode 100644 AGENTS.md
create mode 100644 CLAUDE.md
create mode 100644 _new_project/AGENTS.md
create mode 100644 _new_project/CONTRIBUTING.md
rename _README.md => _new_project/README.md (94%)
create mode 100644 _new_project/docs/index.md
rename docs/resources/images/_maya_python_logo.png => _new_project/docs/resources/images/maya_python_logo.png (100%)
create mode 100644 docs/ci.md
create mode 100644 docs/documentation.md
create mode 100644 docs/getting-started.md
create mode 100644 docs/project-layout.md
create mode 100644 docs/reference.md
create mode 100644 docs/releasing.md
delete mode 100644 docs/resources/images/ci_main.png
delete mode 100644 docs/resources/images/ci_release.png
delete mode 100644 docs/resources/images/gh-pages.png
create mode 100644 docs/testing.md
create mode 100644 docs/writing-your-tool.md
create mode 100644 install_module.py
delete mode 100644 mypy.ini
create mode 100644 pyproject.toml
create mode 100644 release.py
delete mode 100644 requirements-dev.in
delete mode 100644 requirements-test.in
delete mode 100644 requirements.txt
create mode 100644 scripts/build_module.py
rename requirements.in => src/mayapythonprojecttemplate/__init__.py (100%)
create mode 100644 tests/template/conftest.py
create mode 100644 tests/template/test_build_module.py
create mode 100644 tests/template/test_initial_setup.py
create mode 100644 tests/template/test_release.py
create mode 100644 tests/template/test_workflows.py
create mode 100644 uv.lock
diff --git a/.github/workflows/ci-main.yml b/.github/workflows/ci-main.yml
index 2525f48..911bb0e 100644
--- a/.github/workflows/ci-main.yml
+++ b/.github/workflows/ci-main.yml
@@ -1,3 +1,5 @@
+# Thin trigger wrapper, job logic lives in reusable-*.yml so ci-main and
+# ci-release share it.
name: CI
on:
@@ -9,10 +11,12 @@ on:
jobs:
+ # Guarded so the template doesn't run these twice: template-ci.yml already
+ # runs static analysis, and Maya tests need a real project.
static-analysis:
- if: github.repository != 'matthewlee-dev/MayaPythonProjectTemplate'
+ if: ${{ !github.event.repository.is_template }}
uses: ./.github/workflows/reusable-static-analysis.yml
tests:
- if: github.repository != 'matthewlee-dev/MayaPythonProjectTemplate'
+ if: ${{ !github.event.repository.is_template }}
uses: ./.github/workflows/reusable-maya-tests.yml
diff --git a/.github/workflows/ci-release.yml b/.github/workflows/ci-release.yml
index 5917a9f..3055866 100644
--- a/.github/workflows/ci-release.yml
+++ b/.github/workflows/ci-release.yml
@@ -1,28 +1,57 @@
+# Thin trigger wrapper, job logic lives in reusable-*.yml so ci-main and
+# ci-release share it.
name: Release
on:
workflow_dispatch:
+ inputs:
+ version:
+ description: "Version to release (e.g. 1.2.3)"
+ required: true
+ type: string
+ platforms:
+ description: "Platforms to vendor runtime deps for (e.g: win64, linux, mac)"
+ required: false
+ type: string
+ # release.py never passes these, so the defaults are what ships. Keep
+ # in step with reusable-maya-tests.yml: a Maya version with no .mod row
+ # won't load the module, even though CI went green on it.
+ default: "win64,linux,mac"
+ maya_versions:
+ description: "Maya versions to vendor runtime deps for (e.g: 2024,2025,2026)"
+ required: false
+ type: string
+ default: "2024,2025,2026,2027"
+ vendor_deps:
+ description: "Vendor runtime dependencies into the module (uncheck to ship without them)"
+ required: false
+ type: boolean
+ default: true
jobs:
static-analysis:
- if: github.repository != 'matthewlee-dev/MayaPythonProjectTemplate'
+ if: ${{ !github.event.repository.is_template }}
uses: ./.github/workflows/reusable-static-analysis.yml
tests:
- if: github.repository != 'matthewlee-dev/MayaPythonProjectTemplate'
+ if: ${{ !github.event.repository.is_template }}
uses: ./.github/workflows/reusable-maya-tests.yml
do_release:
- if: github.repository != 'matthewlee-dev/MayaPythonProjectTemplate'
- needs: [tests]
- uses: ./.github/workflows/reusable-semantic-release.yml
+ if: ${{ !github.event.repository.is_template }}
+ needs: [tests, static-analysis]
+ uses: ./.github/workflows/reusable-release.yml
+ with:
+ version: ${{ inputs.version }}
+ platforms: ${{ inputs.platforms }}
+ maya_versions: ${{ inputs.maya_versions }}
+ vendor_deps: ${{ inputs.vendor_deps }}
permissions:
contents: write
- id-token: write
docs:
- if: github.repository != 'matthewlee-dev/MayaPythonProjectTemplate'
+ if: ${{ !github.event.repository.is_template }}
needs: [tests, do_release] # only build docs when release is successful
uses: ./.github/workflows/reusable-build-and-deploy-docs.yml
permissions:
diff --git a/.github/workflows/initial-setup.yml b/.github/workflows/initial-setup.yml
index 66dd90e..0940ca5 100644
--- a/.github/workflows/initial-setup.yml
+++ b/.github/workflows/initial-setup.yml
@@ -12,7 +12,8 @@ env:
jobs:
setup:
- if: github.repository != 'matthewlee-dev/MayaPythonProjectTemplate'
+ # Only run on repos generated from the template, never on a template itself.
+ if: ${{ !github.event.repository.is_template }}
runs-on: ubuntu-latest
permissions:
@@ -20,38 +21,45 @@ jobs:
steps:
- name: Checkout repository
- uses: actions/checkout@v4
+ uses: actions/checkout@v5
- - name: Rename README.md and delete setup workflow
+ - name: Swap in the generated project's files
run: |
# Delete template LICENSE
rm -f LICENSE
-
- # Delete existing README.md
- rm -f README.md
-
- # Rename _README.md to README.md
- mv _README.md README.md
-
- # Delete the template logo placeholder
- rm -f docs/resources/images/maya_python_logo.png
-
- # Rename template logo
- mv docs/resources/images/_maya_python_logo.png docs/resources/images/maya_python_logo.png
+
+ # _new_project mirrors the repo layout. Add files there, not here.
+ cp -r _new_project/. .
+ rm -rf _new_project
+
+ # Drop the docs pages that only document the template itself.
+ rm -f docs/getting-started.md docs/project-layout.md \
+ docs/writing-your-tool.md docs/testing.md docs/ci.md \
+ docs/releasing.md docs/documentation.md
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.x'
+ - name: Install uv
+ uses: astral-sh/setup-uv@v7
+
- name: Run initial setup script
run: |
python initial_setup.py
+ - name: Regenerate uv.lock for the renamed project
+ run: uv lock
+
- name: Remove setup files
run: |
rm -f initial_setup.py
rm -f .github/workflows/initial-setup.yml
+ rm -f .github/workflows/template-ci.yml
+ rm -f .github/workflows/template-release.yml
+ rm -f .github/workflows/reusable-template-tests.yml
+ rm -rf tests/template
- name: Commit and push changes
run: |
diff --git a/.github/workflows/reusable-build-and-deploy-docs.yml b/.github/workflows/reusable-build-and-deploy-docs.yml
index 67eec57..ca9f04b 100644
--- a/.github/workflows/reusable-build-and-deploy-docs.yml
+++ b/.github/workflows/reusable-build-and-deploy-docs.yml
@@ -1,4 +1,4 @@
-name: build and deploy docs
+name: reusable / build and deploy docs
on:
workflow_dispatch: # Manual trigger
@@ -13,17 +13,19 @@ jobs:
steps:
- name: Checkout code
- uses: actions/checkout@v4
+ uses: actions/checkout@v5
- - uses: actions/setup-python@v5
+ - name: Install uv
+ uses: astral-sh/setup-uv@v7
with:
- python-version: 3.x
+ python-version: '3.13'
- - uses: actions/cache@v4
+ - uses: actions/cache@v6
with:
key: ${{ github.ref }}
path: .cache
- - run: |
- pip install mkdocs-material mkdocstrings[python]
- mkdocs gh-deploy --force
\ No newline at end of file
+ - name: Install dependencies
+ run: uv sync --only-group docs
+
+ - run: uv run --no-sync mkdocs gh-deploy --force
\ No newline at end of file
diff --git a/.github/workflows/reusable-maya-tests.yml b/.github/workflows/reusable-maya-tests.yml
index fb6b703..706eb2c 100644
--- a/.github/workflows/reusable-maya-tests.yml
+++ b/.github/workflows/reusable-maya-tests.yml
@@ -1,44 +1,63 @@
-name: maya tests
+name: reusable / maya tests
on:
workflow_call:
workflow_dispatch: # Manual trigger
jobs:
+ # Cheap gate: skips the disk purge + multi-GB Maya image pull when there are
+ # no tests, the common case for freshly created template projects.
collect_tests:
runs-on: ubuntu-latest
outputs:
tests_collected: ${{ steps.check_tests.outputs.tests_collected }}
steps:
- - uses: actions/checkout@v4
- - name: Set up Python
- uses: actions/setup-python@v5
+ - uses: actions/checkout@v5
+
+ - name: Install uv
+ uses: astral-sh/setup-uv@v7
with:
- python-version: '3.x'
+ python-version: '3.13'
- name: Install dependencies
- run: |
- python -m pip install --upgrade pip
- pip install pytest
+ run: uv sync --only-group test
- name: Check for tests
id: check_tests
run: |
#!/bin/bash
set +e # Disable exit on error
- pytest --collect-only
+ # --ignore=tests/template: CI also fires on a generated repo's
+ # first commit, before setup has deleted the template's own tests.
+ output=$(uv run --no-sync pytest --collect-only --ignore=tests/template)
exit_code=$?
set -e # Re-enable exit on error
-
+ echo "$output"
+
+ # Exit 5 is the only non-zero status meaning "nothing to run". A
+ # collection error (exit 2) also prints "collected 0 items", so
+ # treating it as an empty suite would skip every test and stay green.
if [ $exit_code -eq 5 ]; then
echo "tests_collected=0" >> $GITHUB_OUTPUT
echo "No tests were collected."
- else
- collected=$(pytest --collect-only | grep -oP 'collected \K\d+')
- echo "tests_collected=$collected" >> $GITHUB_OUTPUT
- echo "Collected $collected tests."
+ exit 0
fi
+ if [ $exit_code -ne 0 ]; then
+ echo "::error::pytest --collect-only failed (exit $exit_code)." \
+ "Tests must import outside Maya: move 'from maya import ...'" \
+ "into a test or fixture, as tests/maya/conftest.py does."
+ exit 1
+ fi
+
+ collected=$(echo "$output" | grep -oP 'collected \K\d+')
+ if [ -z "$collected" ]; then
+ echo "::error::Could not parse a test count from pytest output."
+ exit 1
+ fi
+ echo "tests_collected=$collected" >> $GITHUB_OUTPUT
+ echo "Collected $collected tests."
+
no_tests_found:
needs: collect_tests
if: needs.collect_tests.outputs.tests_collected == '0'
@@ -58,28 +77,38 @@ jobs:
fail-fast: false
matrix:
- # add or remove versions as needed, a job per version will be created
- # supported tags here: https://github.com/mottosso/docker-maya/
- maya_version: ["2024",]
-
- container: mottosso/maya:${{ matrix.maya_version }} # ty mottosso!
+ # add/remove versions to match what you target; tags: https://github.com/mottosso/docker-maya/
+ maya_version: ["2024"]
steps:
- name: Checkout code
- uses: actions/checkout@v4
+ uses: actions/checkout@v5
- - name: Install dependencies
+ # No job-level `container:`: that pulls before any step can free disk space first,
+ # and large Maya images can exceed the runner's free disk mid-pull.
+ - name: Free disk space
run: |
- mayapy -m pip install --upgrade pip
- mayapy -m pip install pip-tools
- pip-compile requirements.in
- pip-compile requirements-test.in
- mayapy -m pip install --user -r requirements-test.txt
+ sudo rm -rf /usr/local/lib/android /usr/share/dotnet /opt/ghc \
+ /usr/local/share/powershell /usr/share/swift /opt/hostedtoolcache/CodeQL
+ df -h /
- - name: Set Maya environment variables
- run: |
- echo "XDG_RUNTIME_DIR=/var/tmp/runtime-root" >> $GITHUB_ENV
- echo "MAYA_DISABLE_ADP=1" >> $GITHUB_ENV
+ - name: Pull Maya image
+ run: docker pull mottosso/maya:${{ matrix.maya_version }} # ty mottosso!
- - name: Run tests
- run: mayapy -m pytest tests
+ - name: Install test dependencies and run tests in Maya container
+ run: |
+ docker run --rm \
+ -e XDG_RUNTIME_DIR=/var/tmp/runtime-root \
+ -e MAYA_DISABLE_ADP=1 \
+ -v "${{ github.workspace }}:/workspace" \
+ -w /workspace \
+ mottosso/maya:${{ matrix.maya_version }} \
+ bash -c '
+ set -e
+ if command -v curl >/dev/null; then curl -LsSf https://astral.sh/uv/install.sh | sh
+ else wget -qO- https://astral.sh/uv/install.sh | sh
+ fi
+ export PATH="$HOME/.local/bin:$PATH"
+ uv pip install --python "$(command -v mayapy)" --group test .
+ mayapy -m pytest tests --ignore=tests/template
+ '
diff --git a/.github/workflows/reusable-release.yml b/.github/workflows/reusable-release.yml
new file mode 100644
index 0000000..9a5ed34
--- /dev/null
+++ b/.github/workflows/reusable-release.yml
@@ -0,0 +1,119 @@
+name: reusable / release
+
+on:
+ workflow_call:
+ inputs:
+ version:
+ description: "Version to release (e.g. 1.2.3, no leading v)"
+ required: true
+ type: string
+ platforms:
+ description: "Comma-separated platforms to vendor runtime deps for (win64, linux, mac). Ignored if the project has no runtime deps."
+ required: false
+ type: string
+ default: "win64,linux,mac"
+ maya_versions:
+ description: "Comma-separated Maya versions to vendor runtime deps for. Ignored if the project has no runtime deps."
+ required: false
+ type: string
+ default: "2024,2025,2026,2027"
+ vendor_deps:
+ description: "Vendor runtime dependencies into the module. Uncheck to ship without them even if the project has dependencies."
+ required: false
+ type: boolean
+ default: true
+
+jobs:
+ release:
+ runs-on: ubuntu-latest
+ concurrency: release
+ permissions:
+ contents: write
+
+ steps:
+ - uses: actions/checkout@v5
+ with:
+ fetch-depth: 0
+
+ # Workflow inputs reach the shell through `env:`, never by `${{ }}`
+ # expansion inside `run:`. Direct expansion pastes the raw string into the
+ # script before bash parses it, so an input can close the quote and run
+ # arbitrary commands with this job's `contents: write` token, including in
+ # the guard step below, which would otherwise validate its input too late
+ # to matter.
+ - name: Guard version + tag
+ env:
+ VERSION: ${{ inputs.version }}
+ run: |
+ if [[ ! "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
+ echo "::error::version '$VERSION' is not x.y.z"
+ exit 1
+ fi
+ if git rev-parse -q --verify "refs/tags/v$VERSION" >/dev/null; then
+ echo "::error::tag v$VERSION already exists"
+ exit 1
+ fi
+
+ - name: Install uv
+ uses: astral-sh/setup-uv@v7
+
+ - name: Set version in pyproject.toml
+ env:
+ VERSION: ${{ inputs.version }}
+ run: |
+ sed -i "s/^version = \".*\"/version = \"$VERSION\"/" pyproject.toml
+ uv lock # keep uv.lock's root-package version in sync with the bump
+
+ # Build and zip BEFORE any push: everything likely to fail (missing
+ # wheels, bad src/ layout) fails while the remote is untouched, so a
+ # failed release is a no-op that can simply be re-dispatched.
+ - name: Build Maya module
+ env:
+ VERSION: ${{ inputs.version }}
+ PLATFORMS: ${{ inputs.platforms }}
+ MAYA_VERSIONS: ${{ inputs.maya_versions }}
+ VENDOR_DEPS: ${{ inputs.vendor_deps }}
+ run: |
+ args=(--clean --version "$VERSION" --platforms "$PLATFORMS" --maya-versions "$MAYA_VERSIONS")
+ if [[ "$VENDOR_DEPS" != "true" ]]; then
+ args+=(--skip-vendor)
+ fi
+ python3 scripts/build_module.py "${args[@]}"
+
+ - name: Zip module (dot-free name)
+ # Hyphenate dots: macOS Archive Utility truncates the extracted folder
+ # name at the first '.'.
+ env:
+ VERSION: ${{ inputs.version }}
+ run: |
+ NAME=$(python3 -c "import tomllib; print(tomllib.load(open('pyproject.toml','rb'))['project']['name'])")
+ VERSION_SAFE="${VERSION//./-}"
+ echo "ZIP_PATH=/tmp/${NAME}-${VERSION_SAFE}.zip" >> "$GITHUB_ENV"
+ echo "NAME=${NAME}" >> "$GITHUB_ENV"
+ (cd dist && zip -rq "/tmp/${NAME}-${VERSION_SAFE}.zip" .)
+
+ - name: Commit version bump
+ env:
+ VERSION: ${{ inputs.version }}
+ run: |
+ git config user.name 'github-actions[bot]'
+ git config user.email 'github-actions[bot]@users.noreply.github.com'
+ git add pyproject.toml uv.lock
+ # Skip the commit on a re-run where the bump already landed, so a
+ # release that failed after pushing can be re-dispatched.
+ git diff --cached --quiet || git commit -m "chore: release v$VERSION"
+ git push
+
+ # gh creates the tag along with the release, pointing at the bump
+ # commit; tag + release are one final step, so the guard's "tag
+ # exists" check really means "this release already happened".
+ - name: Create GitHub release
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ VERSION: ${{ inputs.version }}
+ run: |
+ gh release create "v$VERSION" "$ZIP_PATH" \
+ --target "$(git rev-parse HEAD)" \
+ --title "v$VERSION" \
+ --generate-notes \
+ --notes "Download the zip, extract it, and drag \`${NAME}_drag_and_drop_installer.py\` into a Maya viewport (or extract into your Maya modules dir manually)."
diff --git a/.github/workflows/reusable-semantic-release.yml b/.github/workflows/reusable-semantic-release.yml
deleted file mode 100644
index 8f63a25..0000000
--- a/.github/workflows/reusable-semantic-release.yml
+++ /dev/null
@@ -1,22 +0,0 @@
-name: semantic release
-
-on:
- workflow_call:
-
-jobs:
- release:
- runs-on: ubuntu-latest
- concurrency: release
- permissions:
- id-token: write
- contents: write
-
- steps:
- - uses: actions/checkout@v4
- with:
- fetch-depth: 0
-
- - name: Python Semantic Release
- uses: python-semantic-release/python-semantic-release@v9.8.6
- with:
- github_token: ${{ secrets.GITHUB_TOKEN }}
\ No newline at end of file
diff --git a/.github/workflows/reusable-static-analysis.yml b/.github/workflows/reusable-static-analysis.yml
index 88c4e81..a9ac6a0 100644
--- a/.github/workflows/reusable-static-analysis.yml
+++ b/.github/workflows/reusable-static-analysis.yml
@@ -1,4 +1,4 @@
-name: static analysis
+name: reusable / static analysis
on:
workflow_call:
@@ -13,30 +13,24 @@ jobs:
fail-fast: false
matrix:
- python-version: ["3.10",] # maya 2024
+ # Ruff's lint/format targets come from requires-python in
+ # pyproject.toml, not this interpreter version.
+ python-version: ["3.13"]
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@v5
- - name: Set up Python ${{ matrix.python-version }}
- uses: actions/setup-python@v5
+ - name: Install uv
+ uses: astral-sh/setup-uv@v7
with:
+ enable-cache: true
python-version: ${{ matrix.python-version }}
- name: Install dependencies
- run: |
- python -m pip install --upgrade pip
- pip install pylint mypy
+ run: uv sync --only-group dev
- - name: Analysing the code with pylint
- run: |
- pylint src
+ - name: Analysing the code with ruff
+ run: uv run --no-sync ruff check .
- - name: Analysing the code with black
- uses: psf/black@stable
- with:
- src: "src"
- options: "--check --diff"
-
- - name: Analysing the code with mypy
- run: mypy src
\ No newline at end of file
+ - name: Checking formatting with ruff
+ run: uv run --no-sync ruff format --check .
diff --git a/.github/workflows/reusable-template-tests.yml b/.github/workflows/reusable-template-tests.yml
new file mode 100644
index 0000000..2be138a
--- /dev/null
+++ b/.github/workflows/reusable-template-tests.yml
@@ -0,0 +1,24 @@
+name: reusable / template tests
+
+on:
+ workflow_call:
+ workflow_dispatch: # Manual trigger
+
+jobs:
+
+ template-tests:
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v5
+
+ - name: Install uv
+ uses: astral-sh/setup-uv@v7
+ with:
+ enable-cache: true
+ python-version: "3.13"
+
+ # pyyaml stays out of pyproject.toml so generated projects don't inherit it.
+ # Must be `python -m pytest`: the console script misses the --with overlay.
+ - name: Run template tests
+ run: uv run --only-group test --with pyyaml python -m pytest tests/template
diff --git a/.github/workflows/template-ci.yml b/.github/workflows/template-ci.yml
new file mode 100644
index 0000000..caba1dd
--- /dev/null
+++ b/.github/workflows/template-ci.yml
@@ -0,0 +1,17 @@
+# The template's own CI. Deleted by setup, along with the reusable it calls.
+name: template / ci
+
+on:
+ workflow_dispatch:
+ push:
+ branches:
+ - main
+ pull_request:
+
+jobs:
+
+ static-analysis:
+ uses: ./.github/workflows/reusable-static-analysis.yml
+
+ template-tests:
+ uses: ./.github/workflows/reusable-template-tests.yml
diff --git a/.github/workflows/template-release.yml b/.github/workflows/template-release.yml
new file mode 100644
index 0000000..c978d7a
--- /dev/null
+++ b/.github/workflows/template-release.yml
@@ -0,0 +1,92 @@
+# Releases the template itself, not projects made from it. Deleted by setup.
+name: template / release
+
+on:
+ workflow_dispatch:
+ inputs:
+ version:
+ description: "Version to release (e.g. 1.2.3, no leading v)"
+ required: true
+ type: string
+
+jobs:
+
+ static-analysis:
+ if: ${{ github.event.repository.is_template }}
+ uses: ./.github/workflows/reusable-static-analysis.yml
+
+ template-tests:
+ if: ${{ github.event.repository.is_template }}
+ uses: ./.github/workflows/reusable-template-tests.yml
+
+ # Before the tag, so a docs failure stops the release.
+ docs:
+ needs: [static-analysis, template-tests]
+ uses: ./.github/workflows/reusable-build-and-deploy-docs.yml
+ permissions:
+ contents: write
+
+ do_release:
+ needs: [static-analysis, template-tests, docs]
+ runs-on: ubuntu-latest
+ concurrency: release
+ permissions:
+ contents: write
+
+ steps:
+ - uses: actions/checkout@v5
+ with:
+ fetch-depth: 0
+
+ # Workflow inputs reach the shell through `env:`, never by `${{ }}`
+ # expansion inside `run:`. Direct expansion pastes the raw string into the
+ # script before bash parses it, so an input can close the quote and run
+ # arbitrary commands with this job's `contents: write` token.
+ - name: Guard version + tag
+ env:
+ VERSION: ${{ inputs.version }}
+ run: |
+ if [[ ! "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
+ echo "::error::version '$VERSION' is not x.y.z"
+ exit 1
+ fi
+ if git rev-parse -q --verify "refs/tags/v$VERSION" >/dev/null; then
+ echo "::error::tag v$VERSION already exists"
+ exit 1
+ fi
+
+ - name: Install uv
+ uses: astral-sh/setup-uv@v7
+
+ # initial_setup.py resets this to 0.0.0 in generated projects, so the
+ # template's own version never leaks downstream.
+ - name: Set version in pyproject.toml
+ env:
+ VERSION: ${{ inputs.version }}
+ run: |
+ sed -i "s/^version = \".*\"/version = \"$VERSION\"/" pyproject.toml
+ uv lock # keep uv.lock's root-package version in sync with the bump
+
+ - name: Commit version bump
+ env:
+ VERSION: ${{ inputs.version }}
+ run: |
+ git config user.name 'github-actions[bot]'
+ git config user.email 'github-actions[bot]@users.noreply.github.com'
+ git add pyproject.toml uv.lock
+ # Skip the commit on a re-run where the bump already landed, so a
+ # release that failed after pushing can be re-dispatched.
+ git diff --cached --quiet || git commit -m "chore: release v$VERSION"
+ git push
+
+ # No zip to upload: GitHub attaches source archives to every release.
+ - name: Create GitHub release
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ VERSION: ${{ inputs.version }}
+ run: |
+ gh release create "v$VERSION" \
+ --target "$(git rev-parse HEAD)" \
+ --title "v$VERSION" \
+ --generate-notes \
+ --notes "Template source for v$VERSION. Use the green **Use this template** button to start a project, not these archives."
diff --git a/.gitignore b/.gitignore
index 44716ea..4a22b1d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -232,3 +232,6 @@ fabric.properties
# Android studio 3.1+ serialized cache file
.idea/caches/build_file_checksums.ser
+
+# JetBrains IDE
+.idea/
diff --git a/.pylintrc b/.pylintrc
deleted file mode 100644
index 52404fd..0000000
--- a/.pylintrc
+++ /dev/null
@@ -1,7 +0,0 @@
-[TYPECHECK]
-
-# List of module names for which member attributes should not be checked
-# (useful for modules/projects where namespaces are manipulated during runtime
-# and thus existing member attributes cannot be deduced by static analysis). It
-# supports qualified module names, as well as Unix pattern matching.
-ignored-modules=maya
diff --git a/AGENTS.md b/AGENTS.md
new file mode 100644
index 0000000..177fb94
--- /dev/null
+++ b/AGENTS.md
@@ -0,0 +1,81 @@
+# AGENTS.md: MayaPythonProjectTemplate
+
+This repository is a **GitHub template** for Autodesk Maya Python tools. If an agent
+is helping a user start a new Maya tool, create the project **from this template**
+instead of scaffolding from scratch. It comes preconfigured with CI that runs tests
+in real Maya (Docker), Ruff static analysis, mkdocs deployment, and a manually
+triggered release workflow.
+
+## Create a new project from this template (non-interactive)
+
+```sh
+gh repo create \
+ --template matthewlee-dev/MayaPythonProjectTemplate \
+ --public \
+ --description ""
+```
+
+Use all-lowercase with hyphens for `` (e.g. `my-cool-tool`).
+
+Creating the repo triggers the `initial repository setup` workflow: it rewrites
+placeholders, renames `src/mayapythonprojecttemplate/`, regenerates `uv.lock`, and
+pushes a commit. **Wait for it before cloning**:
+
+```sh
+sleep 20 # give the workflow a moment to appear
+gh run watch --repo / \
+ $(gh run list --repo / --limit 1 --json databaseId -q '.[0].databaseId') \
+ --exit-status
+gh repo clone /
+```
+
+The new repo has its own `AGENTS.md` with day-to-day commands (generated from
+`_new_project/AGENTS.md` in this template).
+
+## Working on the template itself
+
+- Dependencies and all tool config live in `pyproject.toml`, managed with **uv**.
+ Setup: `uv sync --all-groups`. Dependency groups: `test`, `dev`, `docs`.
+- Lint/format: `uv run ruff check .`, `uv run ruff format --check .`.
+- `tests/template/` exercises the template's own machinery (`initial_setup.py`,
+ `scripts/build_module.py`, `release.py`'s pure version-bump logic) with plain
+ pytest, no Maya needed: `uv run pytest tests/template`. It's deleted by
+ `initial-setup.yml` in generated repos.
+- Maya tests run under `mayapy` (Maya's bundled interpreter), not a uv-managed Python.
+ CI frees runner disk space, `docker pull`s `mottosso/maya:`, then runs one
+ `docker run` that installs `uv` and the test group into `mayapy` and runs pytest
+ (`.github/workflows/reusable-maya-tests.yml`); direct install is fine there since
+ the container is ephemeral. For local runs against a real Maya install, use the
+ `PYTHONPATH` + disposable-venv pattern in [_new_project/CONTRIBUTING.md](_new_project/CONTRIBUTING.md#tests)
+ instead.
+- `pyproject.toml` and `mkdocs.yml` deliberately contain **real** values
+ (`name = "mayapythonprojecttemplate"`, real description, real wheel path,
+ `site_name: MayaPythonProjectTemplate`) rather than `{{...}}` placeholders, so both
+ files stay valid (TOML / YAML) and `uv`/`mkdocs build` work on a clone of the
+ template itself. `initial_setup.py` string-replaces those literals during setup; if
+ you change them in `pyproject.toml` or `mkdocs.yml`, update the matching literals in
+ `initial_setup.py`.
+- Templated files (everything under `_new_project/`, plus
+ `tests/maya/test_example.py`) use `{{PROJECT_OWNER}}` / `{{PROJECT_NAME}}` /
+ `{{PROJECT_TITLE}}` / `{{PROJECT_DESC}}` placeholders handled by `initial_setup.py`
+ and `.github/workflows/initial-setup.yml`. `{{PROJECT_TITLE}}` is `{{PROJECT_NAME}}`
+ with `-`/`_` replaced by spaces, for display headings only; `{{PROJECT_NAME}}`
+ itself must stay a raw URL-safe slug.
+- Release pipeline: `scripts/build_module.py` stages `src//` into a Maya
+ module tree under `dist/` (name/version read from `pyproject.toml`) and copies
+ `install_module.py` in as `_drag_and_drop_installer.py`. `release.py`
+ (repo root) dispatches `ci-release.yml`, which runs `reusable-release.yml` to zip
+ `dist/` and attach it to the GitHub Release. `release.py` lives at the repo root,
+ not under `scripts/`, so `build_module.py` never sweeps it into the shipped module.
+- **Workflows can't move to `_new_project/`.** `GITHUB_TOKEN` may delete files under
+ `.github/workflows` but not create them (there is no `workflows` permission for
+ it), so `initial-setup.yml`'s push is rejected if setup adds one. Every workflow
+ ships in `.github/workflows/`; setup only deletes the template's own.
+- Because they all live on the template too, the generated project's workflows
+ (`ci-main.yml`, `ci-release.yml`) guard every job with
+ `if: ${{ !github.event.repository.is_template }}`. The template's own checks run
+ from `template-ci.yml`. `tests/template/test_workflows.py` enforces both the
+ guards and the rule below.
+- A local `uses:` is resolved at parse time, before any `if:`, so a workflow that
+ survives setup must never reference one that setup deletes. Delete such
+ references with the file, never guard them.
diff --git a/CLAUDE.md b/CLAUDE.md
new file mode 100644
index 0000000..43c994c
--- /dev/null
+++ b/CLAUDE.md
@@ -0,0 +1 @@
+@AGENTS.md
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 8a961fa..a2423d0 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -1,177 +1,55 @@
-
+# Contributing to the template
-
-
+Guidelines for the template repo itself. Projects made from it ship their own
+`CONTRIBUTING.md`, from `_new_project/`.
-
-
-
-
-
+## Setup
-
{{PROJECT_NAME}}
+```sh
+uv sync --all-groups
+```
-
- Development and Contributing Guidelines
-
-
+## Checks
+```sh
+uv run pytest tests/template # template tests
+uv run ruff check . # lint
+uv run ruff format . # format
+uv run mkdocs serve # preview the docs site
+```
+CI runs the same template tests and ruff checks on every push.
-
+## How the template becomes a project
-
-
- Table of Contents
-
- - Contributing
-
- - Project Dependencies
- - Tests
- - Linting and Type Hinting
- - Documentation
- - Continuous Integration
-
-
-
+`.github/workflows/initial-setup.yml` runs once in a generated repo. It copies
+`_new_project/` over the root, runs `initial_setup.py`, then deletes itself and
+the other template-only files.
-## Contributing
-1. Fork the Project.
-2. Create your Feature Branch (`git checkout -b feature_name`).
-3. Commit your Changes (`git commit -a -m "feat: a wonderful new feature"`).
-4. Push to the Branch (`git push origin feature_name`).
-5. Open a Pull Request.
+Two conventions:
-### Commit Messages:
-This project (optionally) uses [Python Semantic Release](https://python-semantic-release.readthedocs.io/en/latest/). To use this feature, please follow the [commit message convention](https://github.com/angular/angular.js/blob/master/DEVELOPERS.md#commits) to ensure changes are correctly versioned in any future releases.
-
-Cheat sheet:
-
-- Patch release, backwards-compatible bug `fix` _in git commit message title_.
- - `git commit -a -m "fix: a great fix"`
-- Minor release, backwards-compatible `feat` _in git commit message title_.
- - `git commit -a -m "feat: a wonderful new feature"`
-- Major release, incompatible API change `BREAKING CHANGE` _in git commit message footer (use `fix` or `feat` for message title)_.
- - `git commit -a -m "feat: total refactor" -m "BREAKING CHANGE: breaks api"`
+- **`_new_project/` belongs to the generated project.** It mirrors the repo
+ layout and setup copies it over the root, so `_new_project/README.md` becomes
+ the new project's `README.md`. Check you're editing the right copy.
+ Workflow files are the exception: they must stay in `.github/workflows/`,
+ because `GITHUB_TOKEN` can delete files there but not create them.
+- **A few files carry the real project name rather than a placeholder**, so the
+ template itself keeps working: `pyproject.toml`, `mkdocs.yml`,
+ `docs/reference.md` and `src/mayapythonprojecttemplate/example.py`. Change a
+ name in one of those and change its copy in `initial_setup.py` to match, or
+ setup silently does nothing. `tests/template` checks that they agree.
+## Adding or removing a docs page
-(back to top)
+Template-only pages are listed twice: in `mkdocs.yml`'s `nav`, and in
+`TEMPLATE_DOCS_NAV` in `initial_setup.py`. Update both, and add the file to the
+`rm -f` list in `initial-setup.yml` so generated projects don't inherit it.
+Pages that should ship to generated projects go in `GENERATED_DOCS_NAV` instead,
+and live in `_new_project/docs/` if the project needs its own version.
-## Project Dependencies
-> Update to reflect the new project's specific requirements!
-> The below instructions assume a virtual environment created from mayapy is being used for the project,
-> for more info see: https://help.autodesk.com/view/MAYACRE/ENU/?guid=GUID-6AF99E9C-1473-481E-A144-357577A53717.
+## Testing a change to the generated project
-Project dependencies are available in the [requirements.in](requirements.in) file, which should be compiled with [pip-tools](https://github.com/jazzband/pip-tools).
-
-1. Install pip-tools:
- ```sh
- pip install pip-tools
- ```
-2. Dependencies can be added to:
- * [requirements.in](requirements.in): User requirements.
- * [requirements-dev.in](requirements-dev.in): Development requirements.
- * [requirements-test.in](requirements-dev.in): Test requirements.
-3. Compile the requirement files as needed (below combines all three files into one `requirements.txt`, however it's possible to have separate files for each):
- ```sh
- pip-compile --output-file requirements.txt requirements.in requirements-dev.in requirements-test.in
- ```
-4. Install dependencies:
- * Windows:
- * ```sh
- pip-sync --python-executable %VIRTUAL_ENV%/scripts/python.exe requirements.txt
- ```
- * Linux:
- * ```sh
- pip-sync --python-executable $VIRTUAL_ENV/bin/python.exe requirements.txt
- ```
-
-
-
-(back to top)
-
-
-## Tests
-
-Tests are written with [Pytest](https://docs.pytest.org/) and should adhere to the ["Arrange, Act, Assert" pattern](https://docs.pytest.org/en/stable/explanation/anatomy.html).
-
-To run tests locally:
-- ```sh
- pytest
- ```
-
-with coverage:
-- ```sh
- pytest --cov=src/
- ```
-
-(back to top)
-
-
-## Linting and Type Hinting
-Static code analysis is performed with [Pylint](https://pypi.org/project/pylint/), formatting with [Black format](https://github.com/psf/black), and type hinting with [mypy](https://mypy.readthedocs.io/en/stable/).
-
-To run pylint locally:
-
-- ```sh
- pylint --rcfile=.pylintrc src/
- ```
- - A modified [.pylintrc](.pylintrc) file is provided with modifications to ignore Maya import errors. Append to this file as needed.
-
-Black formater can be run locally with:
-
-- ```sh
- black src/
- ```
-
-Run [mypy](https://mypy.readthedocs.io/en/stable/) checks locally with:
-
-- ```sh
- mypy src/
- ```
- - A modified [mypy.ini](mypy.ini) file is included with modifications to ignore Maya import errors. Append to this file as needed.
-
-(back to top)
-
-
-## Documentation
-Documentation is built with [mkdocs](https://www.mkdocs.org/). To build the docs locally:
-- ```sh
- mkdocs build
- ```
-To serve the docs locally:
-- ```sh
- mkdocs serve
- ```
-
-(back to top)
-
-
-
-## Continuous Integration
-Continuous integration is set up with [GitHub Actions][github-actions-url], workflows can be found in the [.github/workflows](.github/workflows) directory. There are 2 main workflows:
-1. [ci-main.yml](.github/workflows/ci-main.yml): This workflow is intended to be a fast check to ensure the code is in a good state, and code standards are met. It runs automatically on every push and pull request to main or can be triggered from the `Run workflow` button on the [actions menu](https://github.com/{{PROJECT_OWNER}}/{{PROJECT_NAME}}/actions/workflows/ci-main.yml). It runs tests (if they exist), and performs linting, formatting, and type hinting checks.
- 
-
-2. [ci-release.yml](.github/workflows/ci-release.yml): This workflow will do all the above, create a release version of the repo and deploy the documentation to GitHub pages. It can be run from the `Run workflow` button on the [actions menu](https://github.com/{{PROJECT_OWNER}}/{{PROJECT_NAME}}/actions/workflows/ci-release.yml).
- 
-
-
-### Further useful information on the workflows:
-* Versions of Maya to test against can be specified in the [reusable-maya-tests.yml](.github/workflows/reusable-maya-tests.yml) file.
- * The Maya tests workflow will only run if tests exist in the `tests` directory, otherwise the CI will skip this workflow to avoid pulling the Maya Docker image unnecessarily.
-* [Coding standards enforced](.github/workflows/reusable-static-analysis.yml) with [pylint](https://pypi.org/project/pylint/), [black](https://github.com/psf/black), and [mypy](https://mypy.readthedocs.io/en/stable/), with minimal configuration.
-* [Docs are built](.github/workflows/reusable-build-and-deploy-docs.yml) with [mkdocs](https://www.mkdocs.org/) and deployed to GitHub pages: [https://{{PROJECT_OWNER}}.github.io/{{PROJECT_NAME}}](https://{{PROJECT_OWNER}}.github.io/{{PROJECT_NAME}}).
- * > To enable GitHub pages, go to the settings of your repository, scroll down to the GitHub Pages section, and select the `gh-pages` branch as the source.
- *
-
-
-(back to top)
-
-
-[github-actions-url]: https://github.com/features/actions
+Workflows and files that only exist after setup can't be exercised here. Create
+a repo from the template with **Use this template**, let setup run, and check
+the result.
diff --git a/README.md b/README.md
index 9ecbfae..d228d3c 100644
--- a/README.md
+++ b/README.md
@@ -9,6 +9,9 @@
+[](https://github.com/matthewlee-dev/MayaPythonProjectTemplate/actions/workflows/template-ci.yml)
+[](https://github.com/astral-sh/uv)
+[](https://github.com/astral-sh/ruff)
[![Python][python_3-shield]][python-url]
[![Maya][maya-shield]][maya-url]
[![GitHub Actions][github-actions-shield]][github-actions-url]
@@ -23,30 +26,41 @@
-> Example project built using this template can be found [here](https://github.com/matthewlee-dev/locator_creator).
+## Why this template exists
+Maya Python projects often start as a small collection of scripts and grow into production tools without the development infrastructure growing with them.
-## HOW TO USE THIS TEMPLATE
+This template provides a modern, reproducible foundation for that infrastructure from the outset, so a tool can go from initial development to reliable distribution. The goal isn't to prescribe how a Maya tool should be written, just to remove the repetitive setup work around it.
-### DO NOT FORK this is meant to be used from [Use this template][use-template-link] feature.
+## How to use this template
-1. Click on [Use this template][use-template-link] button.
-2. Give a name and description to the new project (e.g. my_awesome_project, please use all lowercase and underscores separation for repo names).
-3. GitHub Actions will process the template and commit to the new repo, this may take a few minutes. Check the progress in the Actions tab of the new repository.
-4. Once setup is complete, clone the new project and start coding!
+### Do not fork. Use the [Use this template][use-template-link] button instead.
-> NOTE: WAIT until first CI run on GitHub actions before cloning the new project. This will be kicked off automatically after creating the new repo from the template, and should take a couple of minutes to complete.
+1. Click [Use this template][use-template-link].
+2. Name the new project (lowercase, hyphen-separated, e.g. `my-awesome-project`) and add a description.
+3. GitHub Actions processes the template and commits to the new repo. Check progress in the Actions tab.
+4. Wait for the first CI run to finish, then clone and start coding.
+
+### CLI / agent quickstart
+
+No browser needed:
+
+```sh
+gh repo create my-cool-tool --template matthewlee-dev/MayaPythonProjectTemplate --public
+```
+
+Wait for the `initial repository setup` workflow to finish before cloning. See [AGENTS.md](AGENTS.md) for the full non-interactive flow.
## What is included in this template?
* Basic project structure.
-* [README.md](_README) and [CONTRIBUTING](CONTRIBUTING.md) templates.
+* Dependency and packaging management with [uv](https://docs.astral.sh/uv/), configured via `pyproject.toml`.
+* [README.md](_new_project/README.md) and [CONTRIBUTING.md](_new_project/CONTRIBUTING.md) templates.
* Bug report and feature request templates.
* Continuous integration using [GitHub Actions][github-actions-url] with jobs to:
* [Run integration tests](.github/workflows/reusable-maya-tests.yml) across a range of Maya versions in isolated Docker containers.
* `Note: You should hold a valid Maya license.`
- * [Enforce coding standards](.github/workflows/reusable-static-analysis.yml) with [pylint](https://pypi.org/project/pylint/), [black](https://github.com/psf/black), and [mypy](https://mypy.readthedocs.io/en/stable/).
+ * [Enforce coding standards](.github/workflows/reusable-static-analysis.yml) with [ruff](https://github.com/astral-sh/ruff).
* [Build and deploy documentation](.github/workflows/reusable-build-and-deploy-docs.yml) to GitHub pages with [mkdocs](https://www.mkdocs.org/).
- * [Automated releases](.github/workflows/ci-release.yml) using Python Semantic Versioning (optional).
-
+ * [Manually triggered releases](.github/workflows/ci-release.yml): pick a version number and it tags, releases, and deploys docs (optional). The release also builds an installable Maya module zip (`.mod` + `_drag_and_drop_installer.py`) and attaches it to the GitHub Release.
## Acknowledgments
@@ -62,8 +76,6 @@
[python_3-shield]: https://img.shields.io/badge/Python-3.X-grey?logo=python&logoColor=ffdd54&labelColor=%233670A0
[python-url]: https://python.org/
-[pytest-shield]: https://img.shields.io/badge/tests-pytest-%230A9EDC
-[pytest-url]: https://docs.pytest.org/
[github-actions-shield]: https://img.shields.io/badge/GitHub%20Actions-%232671E5?logo=githubactions&logoColor=white
[github-actions-url]: https://github.com/features/actions
diff --git a/_new_project/AGENTS.md b/_new_project/AGENTS.md
new file mode 100644
index 0000000..2e92d3e
--- /dev/null
+++ b/_new_project/AGENTS.md
@@ -0,0 +1,34 @@
+# AGENTS.md
+
+This is an Autodesk Maya Python tool, created from
+[MayaPythonProjectTemplate](https://github.com/matthewlee-dev/MayaPythonProjectTemplate).
+
+## Layout
+
+- `src//`: the tool's Python package (src layout).
+- `tests/maya/`: pytest tests that run **inside Maya** via `mayapy` (Maya's bundled
+ Python). `tests/maya/conftest.py` boots `maya.standalone` once per session.
+- `pyproject.toml`: single source of truth for metadata, dependency groups
+ (`test`, `dev`, `docs`), and all tool config (ruff, hatchling).
+- Dependencies are managed with **uv**; `uv.lock` is committed.
+
+## Commands
+
+- Setup: `uv sync --all-groups`
+- Lint: `uv run ruff check .`
+- Format: `uv run ruff format .` (CI enforces `ruff format --check .`)
+- Tests: must run under `mayapy`, not plain Python. Don't install directly into Maya's
+ own environment; use the `PYTHONPATH` + disposable-venv pattern in
+ [CONTRIBUTING.md](CONTRIBUTING.md#tests). Without Maya locally, rely on CI, which runs
+ the suite in `mottosso/maya` Docker containers.
+- Docs: `uv run mkdocs serve` (mkdocs-material + mkdocstrings, reads `src/` directly).
+
+## Conventions
+
+- `from maya import cmds` for Maya commands.
+- Releases are manual: `python3 release.py --bump patch` (or `minor`/`major`), or
+ `gh workflow run ci-release.yml -f version=1.2.3`. It bumps `version` in
+ `pyproject.toml`, tags `v`, builds an installable Maya module zip
+ (`.mod` + `_drag_and_drop_installer.py`) and attaches it to a GitHub
+ release, and deploys docs to GitHub Pages.
+- CI on push/PR runs static analysis (ruff) and the Maya test matrix.
diff --git a/_new_project/CONTRIBUTING.md b/_new_project/CONTRIBUTING.md
new file mode 100644
index 0000000..0082d30
--- /dev/null
+++ b/_new_project/CONTRIBUTING.md
@@ -0,0 +1,156 @@
+
+
+
+
+
+
+
+
+
+
+
+
{{PROJECT_TITLE}}
+
+
+ Development and Contributing Guidelines
+
+
+
+
+
+
+
+
+
+ Table of Contents
+
+ - Contributing
+ - Project Dependencies
+ - Building the Maya Module Locally
+ - Tests
+ - Linting and Formatting
+ - Documentation
+ - Continuous Integration
+
+
+
+## Contributing
+1. Fork the Project.
+2. Create your Feature Branch (`git checkout -b feature_name`).
+3. Commit your Changes (`git commit -a -m "add a wonderful new feature"`).
+4. Push to the Branch (`git push origin feature_name`).
+5. Open a Pull Request.
+
+(back to top)
+
+
+## Project Dependencies
+> Update to reflect the new project's requirements.
+> Managed with [uv](https://docs.astral.sh/uv/), declared in [pyproject.toml](pyproject.toml).
+> Tests run under Maya's bundled interpreter (`mayapy`); see
+> https://help.autodesk.com/view/MAYACRE/ENU/?guid=GUID-6AF99E9C-1473-481E-A144-357577A53717.
+
+1. Install [uv](https://docs.astral.sh/uv/getting-started/installation/).
+2. Add dependencies to:
+ * `[project] dependencies` in [pyproject.toml](pyproject.toml): runtime requirements.
+ * `[dependency-groups]` in [pyproject.toml](pyproject.toml): `dev`, `test`, `docs`.
+3. Sync a local environment:
+ ```sh
+ uv sync --all-groups
+ ```
+4. For tests against a local Maya, don't install into Maya's own environment:
+ keep test dependencies in a disposable venv and expose them to `mayapy` via
+ `PYTHONPATH` (see [Tests](#tests)). CI installs directly into `mayapy` since
+ its Docker container is ephemeral.
+
+(back to top)
+
+
+## Building the Maya Module Locally
+Stage the module tree the same way a release does, without cutting one:
+- ```sh
+ python3 scripts/build_module.py --clean
+ ```
+
+If the project has runtime dependencies (anything in `[project] dependencies`), pass
+platforms/Maya versions to vendor them for (requires [uv](https://docs.astral.sh/uv/)):
+- ```sh
+ python3 scripts/build_module.py --clean --platforms mac --maya-versions 2026
+ ```
+
+This stages `dist/.mod`, `dist//`, and
+`dist/_drag_and_drop_installer.py`. To test it, drag that installer script
+into a Maya viewport; it installs straight out of `dist/`, no need to zip/extract
+first.
+
+(back to top)
+
+
+
+## Tests
+
+[Pytest](https://docs.pytest.org/), run under `mayapy` (plain Python can't `import maya`).
+Keep test deps out of Maya's site-packages with a disposable venv on `PYTHONPATH`:
+
+- ```sh
+ # --python must match your Maya version (2024 = 3.10, 2025/2026 = 3.11)
+ uv venv --managed-python --python 3.11 .venv-maya
+ uv pip install --python .venv-maya --group test .
+ PYTHONPATH=.venv-maya/lib/python3.11/site-packages mayapy -m pytest tests
+ ```
+
+Assumes `mayapy` is on `PATH`; otherwise use its full path. No local Maya? CI runs
+the suite in Docker across the versions in [reusable-maya-tests.yml](.github/workflows/reusable-maya-tests.yml).
+
+(back to top)
+
+
+## Linting and Formatting
+Static analysis and formatting use [Ruff](https://github.com/astral-sh/ruff). Configuration lives in [pyproject.toml](pyproject.toml).
+
+Lint:
+
+- ```sh
+ uv run ruff check .
+ ```
+
+Check formatting:
+
+- ```sh
+ uv run ruff format --check .
+ ```
+
+Format:
+
+- ```sh
+ uv run ruff format .
+ ```
+
+(back to top)
+
+
+## Documentation
+Docs are built with [mkdocs](https://www.mkdocs.org/). Build locally:
+- ```sh
+ uv run mkdocs build
+ ```
+Serve locally:
+- ```sh
+ uv run mkdocs serve
+ ```
+
+(back to top)
+
+
+
+## Continuous Integration
+[GitHub Actions][github-actions-url] workflows live in [.github/workflows](.github/workflows):
+* [ci-main.yml](.github/workflows/ci-main.yml): tests (if any) + lint/format checks. Runs on every push/PR to main.
+* [ci-release.yml](.github/workflows/ci-release.yml): the above, then bumps `pyproject.toml`, tags, builds the Maya module zip, creates a GitHub release, and deploys docs. Trigger with `python3 release.py --bump patch` (or `minor`/`major`), or `gh workflow run ci-release.yml -f version=1.2.3`.
+
+Maya versions to test against are set in [reusable-maya-tests.yml](.github/workflows/reusable-maya-tests.yml); that job is skipped if `tests` don't exist. Docs deploy to [https://{{PROJECT_OWNER}}.github.io/{{PROJECT_NAME}}](https://{{PROJECT_OWNER}}.github.io/{{PROJECT_NAME}}); enable once via repo Settings > Pages, source: `gh-pages` branch.
+
+(back to top)
+
+
+[github-actions-url]: https://github.com/features/actions
diff --git a/_README.md b/_new_project/README.md
similarity index 94%
rename from _README.md
rename to _new_project/README.md
index ff47a8b..29d2ab7 100644
--- a/_README.md
+++ b/_new_project/README.md
@@ -5,9 +5,8 @@
[](https://github.com/{{PROJECT_OWNER}}/{{PROJECT_NAME}}/actions/workflows/ci-main.yml)
[![pytest][pytest-shield]][pytest-url]
-[](https://github.com/pylint-dev/pylint)
-[](https://github.com/psf/black)
-[](https://mypy-lang.org/)
+[](https://github.com/astral-sh/uv)
+[](https://github.com/astral-sh/ruff)
@@ -20,7 +19,7 @@
[![Maya][maya-2024-2027-shield]][maya-2027-url]
[![Maya][maya-cmds-shield]][maya-cmds-url]
-{{PROJECT_NAME}}
+{{PROJECT_TITLE}}
{{PROJECT_DESC}}
@@ -98,7 +97,7 @@ Please replace this text with a list of pre-requisites for the project.
### Installation
-1. Either clone the repo or download the zip file from the [project page][project-link] and extract it.
+1. Clone the repo, or download and extract the zip from the [project page][project-link].
```sh
git clone https://github.com/{{PROJECT_OWNER}}/{{PROJECT_NAME}}.git
```
diff --git a/_new_project/docs/index.md b/_new_project/docs/index.md
new file mode 100644
index 0000000..97d2786
--- /dev/null
+++ b/_new_project/docs/index.md
@@ -0,0 +1,3 @@
+# Documentation.
+
+---
diff --git a/docs/resources/images/_maya_python_logo.png b/_new_project/docs/resources/images/maya_python_logo.png
similarity index 100%
rename from docs/resources/images/_maya_python_logo.png
rename to _new_project/docs/resources/images/maya_python_logo.png
diff --git a/docs/ci.md b/docs/ci.md
new file mode 100644
index 0000000..c8b28ba
--- /dev/null
+++ b/docs/ci.md
@@ -0,0 +1,47 @@
+# Automatic checks
+
+GitHub runs a set of checks on your code every time you push, and marks the
+commit with a green tick or a red cross.
+
+## What runs
+
+**Code style.** [Ruff](https://github.com/astral-sh/ruff) checks your code for
+common mistakes and inconsistent formatting. Fast, and catches real bugs like
+unused imports and undefined names.
+
+**Tests.** Your [tests](testing.md) run inside a real Maya, in a container,
+across the versions you've chosen. If you have no tests yet, this step notices
+and skips.
+
+## Seeing the results
+
+Click the **Actions** tab in your repo. Green means everything passed. Red means
+something didn't, and clicking into it shows which check and where.
+
+## Running the same checks yourself
+
+```sh
+uv run ruff check . # find problems
+uv run ruff format . # fix formatting
+```
+
+Running `ruff format .` before you commit avoids most style failures.
+
+## Choosing Maya versions
+
+Edit `.github/workflows/reusable-maya-tests.yml` and change the list:
+
+```yaml
+maya_version: ["2024"]
+```
+
+Add the versions you support, e.g. `["2024", "2025", "2026"]`. Each one runs
+separately, so more versions means a slower check.
+
+The Maya containers come from
+[mottosso/docker-maya](https://github.com/mottosso/docker-maya). You should hold
+a valid Maya licence.
+
+## Turning automated checks off
+
+The checks can be disabled in the repo's Actions settings.
diff --git a/docs/documentation.md b/docs/documentation.md
new file mode 100644
index 0000000..e9d0ed2
--- /dev/null
+++ b/docs/documentation.md
@@ -0,0 +1,38 @@
+# Documentation
+
+Your project's docs site is built from the Markdown files in `docs/` by
+[MkDocs](https://www.mkdocs.org/), and published on GitHub Pages.
+
+## Editing it
+
+Add or edit `.md` files in `docs/`, then list them in `mkdocs.yml`:
+
+```yaml
+nav:
+ - index.md
+ - usage.md
+```
+
+Preview as you write with `uv run mkdocs serve`.
+
+## Building docs from docstrings
+
+Point a page at one of your modules and its docstrings become the
+documentation. `docs/reference.md` already does this, and you can see the result
+under [Reference](reference.md):
+
+```markdown
+# Reference
+
+::: my_cool_tool.example
+```
+
+Every function in that module is listed, with its arguments and docstring. Add a
+line per module you want documented.
+
+## Publishing
+
+Docs deploy automatically when you [cut a release](releasing.md), to
+`https://your-name.github.io/my-cool-tool`.
+
+Enable it once: repo **Settings** → **Pages** → source **gh-pages** branch.
diff --git a/docs/getting-started.md b/docs/getting-started.md
new file mode 100644
index 0000000..8454f0c
--- /dev/null
+++ b/docs/getting-started.md
@@ -0,0 +1,48 @@
+# Getting started
+
+## 1. Create your repo
+
+Go to
+[the template repo](https://github.com/matthewlee-dev/MayaPythonProjectTemplate)
+and click **Use this template** → **Create a new repository**.
+
+Name it in lowercase with hyphens, like `my-cool-tool`. That name becomes your
+package name and shows up in the installer, so pick something you can live with.
+Add a short description too, as it gets written into your README.
+
+## 2. Wait for setup to finish
+
+Give GitHub a few minutes to process your new repo. You can watch progress under
+the **Actions** tab.
+
+## 3. Clone and set up
+
+```sh
+git clone https://github.com/YOUR-NAME/my-cool-tool.git
+cd my-cool-tool
+```
+
+Install [uv](https://docs.astral.sh/uv/getting-started/installation/) if you
+don't have it. It manages your Python versions and packages.
+
+Then create the local environment:
+
+```sh
+uv sync --all-groups
+```
+
+This reads `pyproject.toml`, downloads everything the project needs, and puts it
+in a `.venv` folder. You only need to rerun it when dependencies change.
+
+## 4. Check everything works
+
+```sh
+uv run ruff check .
+```
+
+If that passes with no complaints, you're set up correctly.
+
+## Next
+
+Head to [Writing your tool](writing-your-tool.md) to add your first bit of code
+and run it in Maya.
diff --git a/docs/index.md b/docs/index.md
index 9398901..94b8751 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -1,3 +1,22 @@
-# Documentation.
+# Maya Python Project Template
----
\ No newline at end of file
+A starting point for anyone writing Python tools in Maya. You get a repo that
+already knows how to test itself, check its own code, build docs, and produce a
+tool users install by dragging a file into Maya.
+
+No prior GitHub Actions or packaging knowledge needed; each page explains what a
+piece does and why you'd care.
+
+## Start here
+
+1. [Create your project](getting-started.md): make the repo and clone it.
+2. [Project layout](project-layout.md): what all these files are.
+3. [Writing your tool](writing-your-tool.md): where your code goes and how to
+ run it in Maya.
+
+Then, when you need them:
+
+- [Testing](testing.md): checking your tool still works.
+- [Automatic checks](ci.md): what happens when you push code.
+- [Releasing](releasing.md): shipping a version people can install.
+- [Documentation](documentation.md): this site, for your project.
diff --git a/docs/project-layout.md b/docs/project-layout.md
new file mode 100644
index 0000000..c7c88c2
--- /dev/null
+++ b/docs/project-layout.md
@@ -0,0 +1,17 @@
+# Project layout
+
+```
+my-cool-tool/
+├── src/my_cool_tool/ ← your code goes here
+├── tests/ ← your tests go here
+├── docs/ ← your docs go here
+├── scripts/ ← builds the Maya module
+├── .github/workflows/ ← the automatic checks
+├── pyproject.toml ← project settings and dependencies
+├── release.py ← cuts a release
+├── README.md
+└── CONTRIBUTING.md
+```
+
+Your code folder is named after your repo, and that name is what you import:
+`import my_cool_tool`.
diff --git a/docs/reference.md b/docs/reference.md
new file mode 100644
index 0000000..5e75304
--- /dev/null
+++ b/docs/reference.md
@@ -0,0 +1,3 @@
+# Reference
+
+::: mayapythonprojecttemplate.example
diff --git a/docs/releasing.md b/docs/releasing.md
new file mode 100644
index 0000000..6f50897
--- /dev/null
+++ b/docs/releasing.md
@@ -0,0 +1,54 @@
+# Releasing
+
+A release turns your code into something a user can install.
+
+## Building a release
+
+On GitHub, go to **Actions** → **Release** → **Run workflow**, then type the
+version number (e.g. `1.2.4`).
+
+GitHub bumps the version, runs the checks, tags the code, builds the zip,
+publishes it on your repo's Releases page, and updates your
+[docs site](documentation.md).
+
+The other three fields only apply if your tool has dependencies:
+
+- **Vendor runtime dependencies** bundles your dependencies into the module, so
+ the user doesn't have to install them. Untick to leave them out.
+- **Maya versions** and **Platforms** decide which combinations those
+ dependencies are built for.
+
+## From the command line
+
+`release.py` works out the next version for you and starts the same workflow:
+
+```sh
+python3 release.py --bump patch
+```
+
+| | from `1.2.3` |
+|---|---|
+| `--bump patch` | `1.2.4` |
+| `--bump minor` | `1.3.0` |
+| `--bump major` | `2.0.0` |
+
+Run it from `main` with everything committed and pushed. Add `--dry-run` to
+check the version and preflight without triggering the release.
+
+## What users get
+
+A zip on your Releases page. They extract it and drag
+`my_cool_tool_drag_and_drop_installer.py` into a Maya viewport, which copies the
+tool into Maya's modules folder. Any dependencies are bundled in.
+
+## Testing the package first
+
+Build it locally without releasing:
+
+```sh
+python3 scripts/build_module.py --clean
+```
+
+That stages everything in `dist/`, so you can drag
+`dist/my_cool_tool_drag_and_drop_installer.py` into Maya to try the real
+install.
diff --git a/docs/resources/images/ci_main.png b/docs/resources/images/ci_main.png
deleted file mode 100644
index 36eed47eed0b5dbf03fd0ede9caee9ec3da0b434..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001
literal 62841
zcmeEtcTkgEw=cf>iWL+@L<9sWQUZzs(wp=uy+`Q~dNp)GMLkBaL25*5{7gbU{>zj@N9w@LYN##>KCfvT$S&N5|i&Ou&Fo{Fj_{_@d_zbWI3
z9;&9^R8%*)PXC=z)8qU_MMb1hdn#}6(rRsr`Z>KF`O6lNk)>Z4HLc8-3Wwus6NUU~q$BhvCkJ208g_j40g??174=MQ<=f^d{0W<87hVI6l1|
zhbH}y@@HzX*l6Ew!acIKo=v)ZO2EIKuzS{(fxib-R5W+xssD_gygSG8XYlhbozI^^
zScGc-nLi^Mj{o`SA7=ayBmS)u|GzULIx0#>upTw6`Dl5lI>+LriS$_OPn!eBNhwoz
z>F{U?Ic?^kn#_I$+o|{Q_%@&r6kC9i{#9AglvfA+DN2UE#gODde+=NYrHEF)Z&F}M
zv(N}@0-vY7I8d1UQt|w5h;E2z_kFm1DvB8>#pNKT^VtZGY7{!Tw`jF}=3>99TsY0Y
z85X8)EFIaw7)#Up
zIPoSrGQ@DHsizx0MGRL%Tp&_h8kL7e*MCpTO)t;B*a##i7#*RH-uUnW`};Kx(R{pR2`ME)cBkrM^*OAL-M2p2-RGG=l_!m5Ov
zcrRij+%EDu>9rAGcoF{g@5+Mvz_H4?_QCT}PLFJRY*JB3mUJ~|>rA3tCTFo|(l^2V
z`1gk^f#CdVmG&Q-pdIg#RIJKw0<+bzvVCii)AGd(dmd!}NNW9YkQ+Bk+{j)g`{iOO0S8ha%Nv714PeUhplos^DTjk4y-0ma#m80W7>gN(
z0;dml9l$dff_u+oDi4ZH92*W;zb0OM-_tDJ9#gHtqgJ0XX@p$c(y#DxYD8XTJ7UMR
zNIVT-8#7x8T=UpIW(kE#iGlN*{cXfRl>~Bn!*>Ix@tGS4d=2gVD%vg8rjq!ZMR$dj
zEW@?w#U8Z267{cJeEIpT2_=
z2Hw-tAGg?f*Gvp^NbKw$_V3PO^hrE_X0z)_ssDFh!sHTld-^%&fMv(D{hbbs40K25
zKCkOMnYCtD16&bk7P4{KaR=-;FW}Qc#rS+*vQ4rX@06s{N!Hgch
zQ^GkdSdEkrzpkau%xkN*{5sGadjWC1O1sMIU}D!MC?PKaS&~T=3e^cTT`Cy#1pWReb)hJ4LRdCX*QkM$X_Kf!ND!=i9Rd7
zX|K7ceH_Kajp#p*b=yUQGPGfBgS&wF(HJ(!GIP*cY5^J$s<4tJWV>r$?gH{G%=C;l
z+?`vf|Y{ybn1RUB~(}&8VV%y
z7RPyVrUT3Fj3~El`(buJ>D%o`l2u{g;>d=x^LTl?px;VCrR$W>DLj!bJLL|Xq+jtM9WZZgxP})nfTLz(|-LeSU
z>SI>3PL$+Lme!fR;}a*fa*&o93_x2-naEhoW_?jl+_e;hqO1t8?%FMKf>q>hB0D++
z&|NXiRY9`KmfGVzc+muPS@n3G$y|k4A{e7ez6pLPZ-F@-(+a7RS5=U>>FpmKEO)ce
z^(c8}o}8nGAAA#L$brR_l%KZ+r{eF6H{12G4dM~C+uq83zyf^ww14-xc-oSjb%MM*
zDrghEaCkyT)bM9n)!%55d;30C3*{JL3}^9-(9VQR6pbI1GF^
zmm04?LZ+sqJEQH8M`W6mlj=TLN`Y=)weZ3QZ8W`LwoHIL_i
zZEkhAQGFaRFzh})%?AeWa4dtrIpU%abkix-A!E)I#VuWI^U&
z%#82FMg(CXp2RaT6vqZkBFX-O>R^#w7iHITdR#{p+6jNWSlFeQ=yR5-A)UxGR*L2e
zLQF!mGPgRA;;EK(JSmt}=PJ#NHY{IYSK2*#;4d;_M9758yuvHthbx|=+{B7akoI$+
ziPAioD=FurHDF)V>jZP0_yM|Vl-o8sxo4N+FI@laAqK7Ub|c+}79id{zl*aQmo!2M
zXtbt2D++2JXcJ02mKZ?ZJ_y+f4d(G0R?)Dl+@oNEP{
zu8}r@WF-xwE)=tbIr_6mvOTp~hwgZ@`UxLDV}nb9t%;Jkb+19W0ju{sRA})f*N<_H
zMem^48E{sn&r@U@)NlM2lPjdV*7qQ%s?8{xIAK>}C{XL0cVyt)M*xoDTNHoOD)&7)
z{M^)m*YnlH?ck^iNXH|Vl>r7(bGs>7yP)$UbJIvzC5XI*ODeOV`7n51R)~#UtD90a
zB{0~wuR9%lNWg8iDx82>x1RjCM>kp~R@trFCwgnXGJd2XI_nHk;9vy1oFzVi=e$PA4bVVWqHmotGAlSf73(_Q
zEwAE}+8W%KR33@D7vJ{Ks&xW^OVuR??IiS=*+61B#hVvB%GbQ?gqF=l_{uZIg_rFx
zlwA2=&)p}_KIBetH0_yma8wq4OEbFuRnqQ5X3%q_r=nP>Z*^wi$&~OyY7sU)F?~a}
z&ynG=l5%j{C|?s7tJixA8wmBTgx-pY9psUFmI>E#xCshqA$cpDBIRpsm
zhh*6Uuw!b3P1bJ#7V{U>&T_zba1;opasyGAiJ9(1VSBe=Ppxdr|5_L8xe
zyBKIJzBo81!9QCXXBKXTut_znEz-#N)?{Mm-djkP{DyyuMvUZl)riHVIK_-08?~5i
z51Ns}`R=Akj8#dVj}}r@FNjGsr%qy5J+^w#m06<7%I0>zHw}CKR3%({I%V<45&IZ6
z5g2*&%Z;)LPH)J*{zj0Mt9p7+MFOkl0r;+FJ$G+Doe`HPc|6YnSk1}S6=t6-jInt6
zk{%>qnSkTgu0j1Kh3acm;<>D@Nfm13*(9-di#yMi70xb?ljXq<*z=~XE@=X0vyA|A
zfNUrUHaWgXr520o(fb%IrN0mu&y?8l3t?{NitN!tf9S6McxkI?jT
zDoJJ|Y|){L8;5T2K=$aNcS7W64-St@Yq5N($wK6w03ZcZ{oaV`Yjpm(taJc6D=_-X
zOP^YS?wzdS37w6brU;i9DAfd-mL{26#%kl9+9EFy)*B*U#k!w+M%@#_mbJ>y6
ztR;U~&M}|IXdXPZudYR96Ud31KyK+j@Czk`qVsXi(hPOV-B|;_y%OrxtZ81W?Ps?^
zTrx}*5>IQgJx(+go`<}^kUlEnn^j88;`2r&4m_4>Fr**Gwx~^hJCI)$bQ((SEo@10
zFMHq8)Rq~tVIv%zMU<;QSaL>eohFr$A)+ImHCz5n;w~SguIq-lmc7s3jqmiFPkFS+
z43;$8`$N&Y3lAS|A>SA=g+&VL^n4JilIpi<7}87&0ZJ~Di=h{O^G28E*=Pifhk`qz
zG1bAV&yFNSxQZ12^H!d7i3(VC=PnQQGb*LGuIcf9dzQq%Ax94XE2#XrQg?UgDt8F0Y
zehXXu&1)j*RuanG(*6Nao3eqs^qB3*McWqWe`MHJS3ED?5SQxE>hlS^aqL%TF%^=!hKx=4ZdRIj%RyR~Q)8hG3k;BbGGTUFW&G#)bSt1g
zZ(KuFjiPd`iQWgbo`e?RclbNM-(O&-^9d6=NUL=@vh>=5usx{f`typHj
zcw|5QN~5}l?U5F`+hx!B4^xs~vHriUi2l1OHNaleawkyRUHs$&HqlB_$@$|An^$Yy
z(_Bn34&A9aItd*UUcLIk%c6K+L~ca+XV;Wwso7vt2Arxy7C3EZ1FMJ8cUG+cS6?xe
z4onP^T4o$eSq!nPIp-AkwZ+R4{kCfFBM)2KueUv8FQjke9>e4l3p>=#&gauhs())#
zqgxF4^c)DYtvJ*#4n2uFY7VFdTdGlaf$zQT-XdCbaOHHlR%`lJ@8ke=O<_L0YjuTMALX0*U8zW(6>h|lD6CVOoXjSeOs+K@E<#rgEA^fFVPjM
zq4T`Zb8yrfo;w0sE8|FU27VE}71g9~f!!kavXjmG#0}^wRn!d^Zy*+CNb>~!agDP{
znfixTR|}7M&)V%*GOA9ztiveMibEX~lft6QM@-|SzPG?8XK53?H-gRHW{r#y(<;7y
z)NqNt;pFjU%WK9p9tm(@;Uu{#U*Pg<&^*zv2gbm^La!%Uz@zT?v=3mgX7fp_k9Jz;
zYK~$SwNfwPBniG@gxHjEsAgSY}$^yk7gOd~ZI&
zlirSynq;>tmx7b@zIv@bR`ev7oh93gxQUO`#wJh}RHb_|C#!p1G
zb)N^aOsYStzZwWrJDPFmuF>+c9h}_6&+T=hz6L<8@2k-`?|9C7arrnG!~38RlBP@$
zNimHuC6IQ!XJ0y8T=2MR`LKO`_b_qJ4SfLMjBP?9L)=w|N
z`V#ze0?b-ZKoM)GzQoC+;98!*CKuD)rk+FBq(U~VQhM=sb@gs6otS^4z=Uf{WYK-`
z6bl^;rPQ?;FU^%BWWv+5yOwk`DKG{F=N*ai=!~=Ye;A61PcXYmXE$GuNSp~lXuNU(
zKDI0l5m9R;vG3zE`KI6gz&r*hhJ?7jexH5_cNv44dwCJJT@s+7WX`%2EaY3f8!zv}
zijJ&TGxEq-e@?|`4DxO?=Jdi`a(pJv}h@T9KmspF>Gs>pKIrjj<#->YRb|f
z=*Mb0IDj`UKwUYcNf+j@nUI+|mDu9?ribiQRLJ7{{;|I_pxCW;?bx%pVstS?`Zsgr
zd>I%NEm%X0Xq{YOK21F#?#V~5gMQ)V>&C@ow~P4BFyMaOkE~QAZu8rI&{m@5^W)VC|In~
zL)EmlV1c;>#*zmyct9d=oss$F-lFxq|5RDN9TR@eQfzs$<}EKn#YbLT#bc#5!b!H^
zc4Y!Ue}a{}cS?!Ps4E9YQspzf2>?1k5;;p#46h)D3ETK0qcON)o8S%qXhB1>JN@62
z6?pk4v}s_ebA3*Rafl7SE}U#HL<)p7$6`dVyPfJm*5SOraW$2JLqbk2Xj(a)voR(k
z^@P``4W=gmfq*JKZ>)+X-rT^Pz+97uPiobjRf>u574>iS#n%ERMv#}P*`Msyc}2KD
zqf336O^f=+4!`-Bx_J52VDQQ0u48}Nlx$bM2-P9i6koEL5)kWlGm!gBK&+YhpQstO
z!cU<0*{V@uPD2Do81hZQEbU6RP{~l^b{R{v7q9T<9dbhzmGmQTMmLrTeV2hKLd9Pr
z3KfNqHv}ATgk-71Aw{oYrmP~XUzRKCsBXq!hu^}bYEb%mE%o#IM20Iwf#3a@6TIFk
zIS_Cih~A~?zLQbe<+>A^8GVNLqm{K+Djr!K|Ke{Vy>nXeP*TbKE5Df#zORP5Ie
zE;d3ywsjCY(CFb$ss&{byQ?|Yhf%B&f+R#Y4uP`CivhC*z1VSQ%A=b7`HUtA$95ZK4H;O;jBKgNMQFQ
zQ{(7vXLo6=)*h(KR|+y5Y+qy-
z;WF{UPSD)vgZ7DV>Q<*lYMfDgdoCUYTLvO0*Efnj_k{idRJvCwS(u=)eW0O?tBM1u-Lu_ny;>#YzeOS19TTR`%YzWkEL?dJKc~WLJ
zKpfV(dXK>cVoDCyKiN@te4`v*-|{mAy!K{f9(g^Ys2q(q3-fbNU4O`e4=1go^>y@|
zMH3IHGX)l}20lHXql*iY|J&RFIeJlJs(R}K@tBs78hAxrO?KusQ@|J!QzcKYR&I%I
z?(Bd|^Nrj|;$$+f>)8%28#B{xT6aP$HjPvK_%+y(jzUcC)mps_-Vg`^`^0f
z(_Z$!o=+eDg_7>fB;v!o-5_UpL{gJ6m}qduJbXY1sj~g3f`QAInb2s_|J86*RKtpO
ztF^+bdK$RejnAIx)UY)6Tvi$B4s}2n$7FJhwD{!Rc@BUC2|UQ2co3kFm>FzHDK%`T
z5#YT^@xYgg3u~W^UX}(QJwOo2G&^-VmpE+}_z4MDJ}_(d>(oB|f!k?*Sv(#M>&OXr
zF+Ez=C-m)T&8BU-_zu_z51|8744T_u=pb#@Z!EC|z4PhOrCnq;pd1fJ-kUFNV*Qnoc{!Geu)cG(7ESCF0q>G<*cC5SrmT8)
zh+}XHqYz$$^o1A1?HL^JntG7Vx#*^S|i6ydDoCh0F
znO@d|iXdU{JeSdISD7U9?6s*DB_p!1lCq3X$@?AkzCX?`_bC4pwdL@244B$
zoSNlgXP2j=2dP@?C4T?$UHv`ic-NJw*7KVLaw=8hvfAC~>q37~QRPa#V=Q32C=vPn
z#}9R9v-HPq<2}BKoN6KRuWTLcklAYjNVL%B?7u1NCIbWojS}^p)zy2`=@!cz7VvA3
z*wqoWg(^$EpJfhzy%YQ%Lz#AgVYXvQKD_VDZI|IHbDeY%clL)>-;!c>#Lnw4Aj
zKdig^a_i!jUb#^^Cl|MR-fHM7wkjcO0)E}+%(K@oPcID2$TialG&GwwbNkoRx=qr4tFd)ohps^S$4&5mA)vb*A>z&0v(>*S2S=(~RGb%%!hm#wL
znBLP*PD#6_ZMHbnSb~*@3SPSF^$)YZsC|z~h&*TcamnJZWg@fM3;8?>lOKK+qfBqt
zUd=Sy8s>p`b+=oV4H{=T0tth}w&W^&xJDf}HoV?;=M4SS$x7WA&bl#oWs)(CrzIs#%
zZ~u`Q6ULSHVC*8hyaiKLg4B1Kv96tq`Xq-pX&OE*Ok3JjRq_CHQfizFq8!RuM
zhd2dCnbsrn>k_6<{fgz=PXaDQi
z@!#Gz{hxwp7~k3#McO3Tx}<87eQ%N9x&$4);svTFYG`rky`4Kl-p|l#6h2bD``5*z
z3weDicbqmxs?8lXPUAd}h%2^-4`N_qa$geWp0qw8%;`NM2m*xZYG`RBQ8c_rdn%yn
zi{uY8bGm=2Q3Q)nA8F3tJ02y!eR@$K_x8ZkSKL?Yd32i2N>H4N>*{r?yxhg)Z1aG)
zWyFHeKlR1jfC6gEj|Oj8eMHi^)KO
zU#$@7@Y?p`;&I5qZ*(V2PD=51b*F~6|Ft?m1(_4OoB58Ba^QSM=_QV{7aJc$Vb59W
zP24D*D5x4*(KDYNoUMcwb))-IE82wZI(ya^SSe>qC|9<=s-{-D+*o7+n)LG8MWq|^
zbAk5UKv%=$hARv;%<7#1QBi8x+wiqDml1%*?)+LE_NmyZ?uV7VI%jEnmtt@ODY@m|
z*3zYcdX1b=RTwrI-3lPtcG8;Lk!U9hpX+=8D4bf($Nksm)HF08g@uX4Hbeds2+IWA
zT!RXwSK?;rG+dko#l!qJS;wIK`UEfUN|AxsQ!C7MrkFGn#dy@}IhUM$)gj8MqDi-}
zI-7tL?zuZuxt!P*tYvm|P^zY3swsMP#Y3g~on~J5)Tqz>F!plmjAKd6g4h9+{!MWd
zh2nQHQChcNK&~sqW;_?`ysD=rPd6rNY#>#7%lE3!&k)XBhk9Ak3rx1z-ZU`5X|1UtH{l
zQG$~{+6tt#!*S|!K8@=QVtS5?uden=^F>Pjr2?f(rQ-o?I`R;;E8QzE=F}Igs;&l3
zXjp`8(^Dn5Y#f1=#DIsp>*hQA{x}^d#phi+@Cm7S|NctS^UupcW;Hc=Z4YP-{tj?k
z61N`oRzc^LJs7#O#SymhX~MG;*#%cdt^w^CJBWi0J)c>X9=I`uXbi|4c}$v6RPYNU
zozJkQOXU+MUmZy`C*YtNeTamPd-XaOa_ls8-0zk;S!DhtwJe^u_*M&@roJxDn2)MF`X?95CO=4?
z*wv)hFa33GWP6E
zL5us++Dn3|K+!Lg+S2m|b*z-|8%1$gRfY0CWw8J7Sp4Q*o#~jvI6ti3yxH-rIuaB0
zg(V-b(ush1|6^u~tyF4_;$1o_q#2SEqp+B8oV;GR-6V0n4Q4(L5&69n9
zF*7jEM>@Y;iq!Ln(7^~aiGSL9CKV`Hk{wW1SW?oR7JO&|I`cF}asz?m``c}#FN<)<
zFJz{XpeD4!hAoXL91;jrmC)i8_$X^^BmlIp+ycn3%ik7taRfvO0AWvs{l$$#&vHPt
zi*c}EYLJ@k_Cl{6jVn`}_v4{5@8(;v4^siR;g*5DP7!^?z-#5
z$(&9dC)5a_-%6e)<@3qJ8c0x2F`T7NujQ5|?KOQ*e&wjwq-`m_h1a(rHuxoeTyitX
zzgm|fXv%>LmL4N+ns8lpWmVA15P7t>eX>6_S>V+lQFZ%Wly{bgG4yi^A26vqJ^H((
znBU+-6*hp_b~C%+;YD@nA!Al
z!oB03w{%HeGseSrLXWlN%eZ3B*TpSFxVq4A~nJfV})>Fof}
zfSW04g%6nt+zmS8$?KW8FfMAq5>ee|g>i0*6vz|-rBLAB-EklB{PMyP|GmkNTZ(uq
zsrsaM42D<|{w3#KJ*l^0f0f-sdwFja}$|AODpwh2`8G{{Lh~gR0=D6+PormO{hdkmT6%$zqP-;(>q_I`V
z7ME!aM?L8z>Ec{bFlje2Cr60l|Kv_iNbai%(N2TKu@*y{*5Wg-H|9^Ic2C>~VX=k9
zH*;v4{2h=BS{e939a+f3%@&7BqI=aa#PVQ#kFp9Rk`+9f47YcISn*_8qlyp*3+6rk
z!wqUSYVZ8x(bW@B#{mU!Xb(yFIFhZWQSMw_HD5sH-`(CgNO8sS&FJId?eFySWWht(
zDe@P;OEtY4b^N`{3LrKR=VncpyI=unO2d
zlC$taa#ii`ayVh?JqI?Yv!%ysz2^Bw+^27OYrgz35Kuw%4~A3hl-wRO9~Q^L<&TpA
zZ#a?3em;^L=axMaNft-5bz8OfpN`07Ve3sFdCP9b<0xQ?T6(GM^Jt-oN_eGZTk;jy
zPLgGD)m)gIv`Pr!>>05RT+PZk6N^HVIE+C47oczb(gT
z#h=N!B+D=TAY<3xj{1i>eDza7Tp}Yd;2CS^tS88InXj>$!}9)aYBu~H?zUZa$s)9E
z?nOV;*c`<7W4VnwcQ#IDw;}k{vM{L;)`rFFivM2QW!|T;PTne1?qBsA^o05Z}MH
zcg<=BQvtqQe&cpg3((Ci+mNm_!QjaKvMvia`)u~&TQMH=K=fTJ0olsM7BTzxQoH*$
z+McqEVYoa$%{zFLX(K
z9Mw#LTEU-|L{~#vPu?=Cf4`U>*5CP92Ga?(xT)39_wk_MV52^}is-B3cT}}i^y8o?
z7!I-f2{Ha?gF1m^J7$3H1!gzn({@5Q}PBO2)
z-c0P9u(xzto12Ym+IXBZvF)$ZP0ix-;Xo^Be?X-ID!1b_-EO&P)R{>Bw;SR%+jy
zZeEl~0dqX5Krkfg`ufXfHY%15WEQ!B2tiGbJZj6zZ+b-?fSJk*Ye(Rwics%ZD8AVH
z@A|;z0?r||&)9wVA!0s}IN}E4^>Mo`su?GRI%eVI;!=yGVbL@-U#+x&qLN*cc(hVH
zty+xh+XF^lbTE#9FdjnemO3;#mVTP!r+h!LV^*;NC{@(T`R4*S06(m3N($
zFvXQk*a|btdh$-7<3L$asllPMcrj^iBXE=Vx{ot|zgC8rxdun287y%e+iG-ZDMQ2$
z5=!}>{Ngf@UTTJG$xgIsq#|1__H@-M&EbL%(eZ65o-o2%If2z-o|wIA@fqqiTy|d$
zSZfNom7?cfV=ar=N=yFDwpFr1N2$Y3%DqNbJHWcoWXt9$
zGN*t|@WW3WwRN@l7_>}Hih499nf{@bVWM%pu>aDFo}lMdUN7FC;F4`D$d6Wgq_43u
zkGOxJXlubicrPD?YdJ)9T%us
zM{AzutIQGQjjbpo>+GTJ&Yr&!%yzr2+COVCo49wD=Kcn6DMhgCa2mE&95jqjw&Z(d
z6a6z-6TC?uDm{ksW+`Z%uwoi{SigmbMpW0OMB7M-;P_u1EijWIlzXIR8hNtX?NbpO
z5qr2)?>=cX=XL45%8e1fs`Z^_D`C&sr-;q$z{;Y;=J?CdL58MkGs~l?09N4Tn*#S6
ziz&^Ar!n~C8;IqU2~BXHds~-3F31&iG8r<`VqYbpg$cr6FAUZ&56xKuJK?nHt}K2
zlNWR}*=k90eCM+nSS#^uTPvX1sVSg2P0PuTRVDefYSc-VKhEy0;*XRh?#zmw>hfpeZYC?y3j+lDz=~&-oU|`UFVXbHv&H~(c|BR+8!{}MAH~s
zZy!yn^JnYGY~e%XD^FZmiu2soCA=21{LMg9fbUk|bx6MY){m>q_^;G#!laH2SV$8o
zJ22X;#mRadqair8>0YYKNl|xKm^9`<48;XWB61+Ia&^uPg4Ke
zJM&Gr!xvqnYjkDDzBQ{B&E3A*d|KaSJaA_Ty<)hJb~=qYeO@f|CvW#Z$G@t5J9}jq
zclPI^p`V{5eHdh_OQ!a(E+eYD@to4#xJBc)i{bL*d=m%Zzo-!V>wS6&Xp
z+WB@7I$4A39Y4xu#Pw|LJVM{-xYa5c6v+{C*?wZ3oqL#vOnOSePkYxwB`RM0z`mzb
zi;C_aFv<>{`CH#=;Sf%2-xhi>Paquin4z(h6nm{{v$5&vl|tg$q9vN>@?cR&d?|~Q
z!07!+KH@y`C$sx~br*q?MK;8KevFoQa(GMJ%rR5Q(U!`H|4~=(rM)|Onqr`$ERxC0nhi9W}YuQX`iYxhNN4?0y{qM3*YO`hIwcbi1KIFwCPyMuuay%b$W3F8BG=0M*%J0U+
z;GA~>W?-H4%hd+#_^ZFpK}R1Oa}+O4__W{XQN{4tJQC;3_dq@A!l9W?cID@KVSKq>
z`A$oap>u?tv9oWJ%KMl7$-x8SjS8lvErc@u7&Eorbdl~!^Q#6A
z&Nb6(Qz`;A_wx$mI~a_w?4)Jt-`1pMmKZV+h8V>~UIC0n@Fz>bwfM^zk8w&e{5v|b
z#CpvfnWlJTi|I&56
zE_8k~9aPQhnP4_|VH2N;8J#QTiC(M{Ki(PCZ0|}++REV8T*GIIKHg5v)R%BD`jqIs
za&$SG-U}B@$}+rkHJ1Lda{rIo>4zT#akJ5}_18-){c~m$9!gzpJY30tt>E-964TgZ
z#nZp3mAFoz_i!8VBqQ`ku{I@;
zyof~5A9tq+KJ?7
z1ep|d<$jH@oWaB#pS+bYkU)?3q;*^@P14!IWpJa*em&_hR+6#^*!9@^I2b)nvY8f}
zkM92-eazzc;F^njl_HJ!(?*|VYe1CL$*7Ip2JNwAPN)U(SvgS;IlMYq8M%ng0Bs!CFc+;&
zXE|-~f6HEzHsXD(9w^H)TU+)(5qOhp%~~_MkCm7Hf|1kW+oVNDrE3&P?mX)ItZpr+
zOVY5quf;tYb?q2l;Et=)8e`;{gNFy<6zkqbaC60r|B%kf;zlVvWC-V}B*~I?9$_&j
zK5Zu(TP(}{@27q6F$=v@(ia{t^cPy0?nr#qVZAD+QycKu@Fqoc7fpaIWm2)E&kgJY
zFN$_*eR?68k1?-y|ey?~-)2pn2ii
zr|bNHxA2P+6k$QTv;DuQS<=W(@yTF9y9R9RwS3qLue#l@KpL-fXFS1yy0AXW116i{2<#WkelAAP_sq)3~L8*XS?zk~2>NsrZUl
zpKZiMB1pSw2j|so$mRq2cKlBL4}uiGLF^}BSN;XVU7AV$4Sd2ZYZheB#U%?emuckP
zWIwkj;5egmTh<=+TOX$;RLFn}g`SgbkzPmnQT7;T8V6rDap5Qwu
zLE4b^DH#LaH+sc`r9dj5_+;Q#V}&d6C<_AyNH6cXch;s~+tb`g`SIu>je*#0{jxX~
z+LWOo1O4;ris4m!mp{L-*sXOL+a($yaS)y+@+en1E6LakzQ*RW{xN}_S-BdTGU*e(
zd0N~}XVLImylUXm+l5#vxR^F%wt>z&qj~pRfXjFTk`KWr$ZDB4ECytxbt?JB6!c_Q
zk;&s=9-H1jsGh%`s3DlJ_v8J;x%x=`&FPMdA9(foWDup?wrf;w>*K!j=~B(6kNn$p
zdyngOtgdC(V;cRmwk$Xz{iWOa-e_mQI@HobZvtx7i?&@ny}z8HHYw^~`$uX;uU=0z
zY14nUk=d7XYduK${CB1{S(<9TBCXukDhI=bkHFILX%53@1VWEWl-$(4aHvP8|J1I*~=Na
zgK0sFuI9>~UeG9!P(&|sU(3eawb`+0u1|7)-WbUHP}cm(>wtO&cYfo_di4|VkG0mv
z&IT5;WbiK9sMO_lsNKf2E2{OjzJwa{)v=S0+mVzjbXA&rs#NK>wkLOg%iWX$)C~#f
zZGV(`wN*>>?Otb_Q8{M2)ZJ{k$M0hJCGGT@{8-8jFh}6fbW%Fh8AXZniS#-$!@egf
zM$`;XDIHlulNWB!uNzx9BXfED1FI^6tkc#e@NUS$$*)GpsD3Stt_*Vx)S0jF*Yyl*
z-hN%LF+-I6nGew|@yx?iEO6F_VU;*{Jka22FYI9+5YRXoFi}ZZ-E2#b3RYyfvV5+;
z+?bJ;Yp~Rt2fS6pI6qN}d&~7O7obYJ>hqCz_1E8rVE!`S)!8rYK;y02p(_0&T72K#
zrX&Zjg-;ZmFE1~7&GXD2rdx0utO>?R#nYo*FLks=`{YdmZ>zo!^0pm#5l1qGcBSYS1!RDe)`0%Z#>yrq5N(y-AI=nYS6jdC%YB$#i-C$&(f95inkq@^x+A|3Gj`&pTIrOgacj(V_LpkW&&
z^ERA$!W;kgDziyZS>V)}&rU%O8r8s{HIhx|hv~()eM0arM9Fe%OY$`9U}eUYCL#^f
zgI|13n8F7AGtckI8^4*~yeRNd%_aH%vIFGK3cGt?c13_S0`D@)KT+&9B|EC_^S3#?
zUeI4d)m+{vtXH&PD0pv@-yi1-M|`jyG}7nE395a6@H2Q&Lcl-~=S2It)gcQ&Db4|I
zb1R5A_V;t1=MFhgQjL7y;PS;zNq%96=}}Mceb=$t4n7F3jKL26Y3D|Cn<-PiZ&OyW
zx78Jdr)m_t6y5CHJf+GaEdO&~hes7Y7j3R=oO}wH4(?znxhHx&i;Le_Ky1FY=ftVF
z`KY~)fX;uT_nd2Xbyy=cMi60e-YU5Ao?@E7N4A$jTbN{`OjK0Iwyd2w;7ftLb|355
z%%v1)$yB0JXgBWi0x})K-`jZ9T)Wx@1&yT8P_uAUj@NsyM{t4wWs?6<2j+Ok$e%j6
zl|l12c&mf)wD8C|ViSkI2}sX4*YiG27L_X>Q2}LOTp2LR{56dK3G-gXRvMK$mr??)
zLS7>)kA=USA$)l5mX0=G;OiSr&k&-s1kuxi_DbxCEwK!ubX77#6hvt``9k^a*V=#u
zEC%kJlu}u6#xeTp&&rvDSgsa-B=!tg(!2K;SBA`eaYXqPU2Xo?6oQK{lbX-F!h`Ka
zYG3nx_$K>4w@x}|Nw!y+ln42^)^Q*|pH<}0rlRtj%$>bFrm6=9*+EAa#Yq*d!iVbv
zB*Ra>UGFIMqg#naj-70w&4>~8`aaxEXQUcK+b!%TPY>_2B96L#m(z3y^0sH{I2d;lS6!v%nUtvW0~i)?U3FWfnon}NPB>F=-=IsKfMM1=$AD|qVCgcbtk6duFIGrY`KOPUxCY6-5NZ!Z?Vt($7_y%YX{AJu0>lH~4-LI`Gy~(pP{-W+Q!K)9@u7lrWtLHw|
zPc+oD^4JP)QvC4DIMDf!R<=tdqAI2CFw-gg%T&|UH(jSMrJepGRhGS8pPo>h@wsCC
z#Xci7+pT=U)s~_*!|jtJ#p$4922&_TnkRwVcO2@E1~F=f9-V*?^i+ou!NE>?QYV%}
z*$!{s7#8xxa5MXFB%j_gctf$u#^G}H9>4;Cn_GFMXhbZkQ8_ka>C)
zDV3m$d*Wu#zQ=q;Zhy*fj=u#YqA_l?lm2bQmE$*(A=@zPj!PqfaUWQDe8eu^vV=Lc
z>`;nvbt$M4AXy_ifcmvfJw%tUOXvh%X}F3t;(Z_yU)k$Dbfp9w@>WOYBnLfLU2pER
zy=h#udW+#1LtIicA@gRjz}D8uqSN0TW3Vg!@gp|@(It>Ar4sSa$Ey!J%Y{h0^Qa0o
zhpyG?=I6IB@M&SdC!)Clr>@pc=xWGxgfgR8LSsu1obH|4yMz=tBsAO|SeyE^PghMs
z^HtWhPv#|Vqh=0cU;Cx~rww=Z9*+&fH^++aIH%ug2>L>GntelmF!->T*4Td8>STo9
z%6JgcY*^NyL_V^CJWYo?8dYsRpGVEU0MN>C{@U6ix-aAEdyfaDOF{QtM?Zh%TYfqX
zsMI@n{g3Hx2!*cQ^%(8XQ)&V3Shph8x{XGzi
zgcf?IA((4lXZ$br-YcxBt!@9sUI7&mrK=PH0YMPyDk9Q*Zz>{SXwo4RL6L
z3B5yz3P>l^L~5i42qaPiguour^}W~oU3(wwYai^R|2lIqGs_rbj`57=xqtVx_hqL;
zR@qs7-MITY(-CCXj8q90UT<$u7f8oR-g?uc95t=zx&Yk*Pm|8{?yC2!Q%`ip;z8dz
zogklmiK>|CbV>be@S{z%aX#JR4icmk)FsWDcNbU^rE9%3LgU~nPN~|$vXlI=DD#q!
zRkFE_bPN|)%Ff?IO{>HSn(|;Z$x|^352Le9;b{JbaD@+XQvw6=cDi{c++RR$HtFme
z&P{SnNXa0IF@grtM|3ULdDCxVk@3^8S#R_HIRm?v&mUX@Vij?7zwIZ-t3G|@g$He@
zo*vneT#Zi5M3udMCY)6S8BP+tRg()$1
z5c)linB268cSp#Tpw7MQXsW83!PZYECPrOrYcV~kLK>=QI|Ob)sxLKb4y6jWneYGl
z`YuO1Iycvot&mBUX)~qF8zyk@ewLN?#7r9{)_@7US3CK&(BH?ezRpr!moEOay*jhOL0RrB~y;TerbXKin`^kylYy0Pf%2cN9CYB0{K}D+^
zzc&AA;~3+L`=^Q;#O;iR2yN|YDGkw@%P39H#f%UmZtSLs8u=JqSzOB6()%-tWJ`wd
zKsD5}l51=p`PHD;EdwZRVpDnu4ZQg<%f!$ZB{sn&bbqyp=SKEMPpW%gy_{v<>zpfx
zJaJc?(tWN!G;DJ?6e+viT>FHQq(W&X7zCD!&t~Tc{5)%|`n1}0n~tx?9X0(cCMIUk
zR2sE@b!CW)+SO(ubL>%AVGPDF}&R{*$N_JAs4{t&+
zPQAVf6rFteR_~t+k#hei{0e$*{^0Dk?}VMX@0!xPmcfW3EwD|>zyPv+=%$Lpnyt4*
z+^gQ??y~8_FASOlZg>1hboSg!SM2WZn{&f=eUfpDCngkQ`=cy`mr*Hei2ARG10hx~
zCr%NQpyueUK#NVy0%P4M8J;KDCcX;ck`GBd&*Jtg`_Bi~dzL~=PCF$fKqj&as)$}%
z1N#j5ax$nhvWA-}XCCMG4QSo4xLr`(@SEuRk>xXOgOAo%rX@u1O@9H0aZQ<}*7O?p
zc%@5HMhIx*p`cy{s{^;$low?yv$g2nS1-A@2@10%vk}sLG1U^rb@DdG7!e--14Zpi
z)bg}T_it0RdYMOlY(V(oxen@{!T9^e)jrkwE~pL5G4H}~7mE2Necp+40DZbaAzSJ!
zICaXBCLuG{oXmEE_G80N>c)A3S7qjS>^vWwVjwQTQKPsHzP!}u?3g85-!@;WaO1;y
z79k~eKl@OxwH<=&T)tI=2xv?uGsswj1`FR^Yf0RMYfpvnDGuKO1hUbh7ZN00bsjVK0?Yv-77f5DuX|x~g?=5`xs>{;u?H_ta+Pvd#9?VQKTa
z%0L+lEz7+aPb{0@Wbw!HS~(jSA4-mtvfDiJJ@}mUT=GqINmjSh*Ty?}({2kR_dbG9
z_lHR(^4uZuQKC$aml3@tn0fg7XK(A(*z?0S8%-s!*f2poGUS&&Fueg4T3Jn*0TbP*
zlOHA{1lYT5zU|NK(#d#hr2F@ZV~lbr!OI{VOk3zRHRAr_jz^p1d~I#;ca3~Q@i&K3
zE?m2}@8KHoB(0K5L}WnifHF-c8l!IW+LS8AYuzk=68eMT+BH)ySHms)$LX$*Z(CGp
z-E%M!4o1IUo6^zI$({`ic?ut{2+4Ugl7V{j1mYM6Yes(+aap&gJP|LdHyZVR>*+u`I46lUM?vv;5
zv_~RmA7b$3ceb4I<_@iiPm*v)wM86x*N?~ByU
zX1wb-#?XzWy-EHDZi{h}$x6-(yTU^Dy!6i#*TV!|vCGzQlLD7%MT%NeUTuq1J?@TW
zrBCd>(?m$_N*K?)hU8Y&rL~zevKnHvkMDx$^ZnuKAvtVWfQLt}{*v$!J@eGa`-2mu
zmbHBohPkW|4-Y~R)=G=OoRpnCdX4iQI?l4~CO3`XOHULy2oxhV+YjmjMlUIujIn-rE
zuz4qM_I$0awv2ymDMo!|MGHl-T`S&+A{f&DQtq_tragV}U5CiO2UBJ<~h2p@;nNe}oP_
zgLkL&;7QflibZiz7dVKnhf6gUuB+mTWR0cY{K>`d<6e(c#DeU@1j2Xr1FcIN<1q?d
zi>T*;OcB>$+n&;BG>()mi~J+9gAT!-CD~Ujc~cLsV4sW$Kn`;^)uLc&?K!f23?p*Oe{m<;`7j<#8mIJ9kC>
zSQcTj(NuM24ApI2D{eWkSB;q<^B
znu$ZEiu2=*1M!}A?R{(&ze`W7R~A`3w`~3Ao>aU+1KtyR;5YbOD*msm1wB)Zs)#Gwz{F$cq_9<@n7de&H)I_^|rXZ<2!V;LI
zugtaC4A{J$wP$ku^P?G*&0lK=6B+?9^EsubQne*Ogf)vmiIwSDa2#?=k2o|E~{6$-HFfs(I$
zS0lB|+O-1h8+9=vT{;FSL_XH`PE%r&!;DWdEbDN87IsQ?6qk3e&wP{gXvsUOQTr{D
zLiA?!I^5ow*(LuHzPIHY;Q&evia?2w^v)4ii}lrwN#LGRtQxEsqK1
z?(E~{;kluMgf9_jotH*ib1t10D--XrCTq}N-!5_o-AXP!8&-4Dr&WJ#Fig*S;N#q(
z+<_6yXGR$kJ}!avgP7>YJrQsniz=*2pDD0`ZAdo_
zo35+i#LOR0&uTQhh~kjNAb0ohW#UHFC!lN!P?IJ1PgxOCT_(B>AT_TlgqYKTbPDQW
zM2?Pns3J-Y>JR^uJoH*_-s1Z@U1WBih>5%JZqL>-6>1=ttHD&sMgzI#E6GbxCicCOi($
zb;Ul%l}f)H$FwPYzyEw~rY(0QiY_DF>U6uYYoBdc*^<0kJo12_UDp(DUioASN;2>4
z>Wcdj&aA5AlDRZ$HAHlu9CKeE!B}?wL+1FbZ0T<&t7E?qd6%0{WPq_QPo)CC_2vzs
zE1750gYffur41^MOsF@U;@D_5CZfRj7N{q?tDa_)pFZUK@335s}PH?3~q;Zn!`SmJa#ie`|)8b9_joKt)u`%KWcR;Ni
zY-&8}w!ic}tof$@O|>-MQRwX=4os0{&km065(BWG4K2M#OiZJAE@1uff_%4b-4V2q
zK`Oy-p!gD2#<)bx+u57{d)nn3fZpw1?X&ucx)-cr_La#T;I+(M2XNOFi)y
zo7KX2oG6E36Hl&5dlE$92JRIe@LzE}VCMZidpo9T-fC6KJ=;1gE-Gfu(YquKIyx9I
zzk!mw+YRGyK>F`3@phtmB?RvvBHtAk-;PfA+fKBZ55}blZ{7b{VI8KHETukH?ZivV
zJS;3#D*kNH6(4Uo(Zcy21SW48+zyI;N&@6pu=+V0rAub^x0C}a@80#SKM*XmG8yz4
zn$mM$9=^+zwmaD6ll)3iQ8gTQ7+)yEosQBiHW!ZHnx~x@xBBxyQl(U
z4yM%d##AL9Dy23DGHhJ*GIt*N@9x@cBnZZ=nR1RF6ct4jmOm;KxTmhJD)@S!w2Hma
zqu0b6ym|BJ>6UqHK$w#M=?_0t+S8na_#iV6@Wg57y=yj?#nsHpUj+M7MliW6ns-FT
zN|F2gmc9qUt?liN{@r*2Q@hh-ld*q_hyq~;#kLMYx`eIi%Pa2ZDH5+|m@F@|nYx>o
zuI-m>{T!5>^`~qZ8nUhj$&glD(Kp*3xwGB;?BF>g^@OwQ;|3yl@BRSMVI{=MoGfdc
zBxZLzv}EU}Yk;5ZyueVtE6ArEVLN0p^U4q~aCp|WvCtQrW)r_pSpzG$1o&eZAEZh8
zfC1~u-{=D&`!=c?H;w_;-!X%Ko_rQy|M8{G8>v4uFl!csjepxHuCry*%}3
zSE)nh{UJY0gM)N|@sOX$gsFuE3~1V0)x;tTHHGDBUk~+}xQsV?My`y-UK3ZaxFnpJ
znZoJ-C_6n?faAPtR6beQ^gZRMF?pT86RYbTsPRyv8Ny<4$zJig!*%x_5vAk`jmH`;
zbTn@lb6SjFKYAEawyxH-2#EsE#a{Vy_Bg=@=H5ELuhCyN!IB}
z-Q@+Qu4S8e)opoWH1p$VX=1bL-WJ}bYxYNYG%q`JB1YS9b8(s=Ys@a?T}6jHLX*!4
zuRdiQ9QOY}Ero@w)>yx^|FTtr#RnY~`gE$u5#eSxRiPhx{gFd(aBziV@9WN=%PY=)
z`K;yTZw9LrU6nOG=>-?~B!mqCbfipZW(5>pbWoEo->uta?6g{v{>Oq8oBmmUR>dfh
zN-FZFRN%@<)@gXpY20WfoIBRM1|;daGnKxn%ZLK^Icn*9b?#is6+SP~26MQRn?za$x$0vYY?Y8_p;_`yZtOTtg2
z*&M8A;w_&7=HpdW?3B%B4SRt<3_9&CIhsTiGBUkeqf#2JQ68toynUDE{mC;89bYfR
zkMzTGMl04cawsIRjSe32mWzi+d3^iS)9}{E5Oopt!D5U06v>0ajD!Ruz;s2&
zm{fB~N-DnQ;#PSk1^pr|IYF+yuy_gu+qJ5}fRL&QH--V;)an-pmD&kh+)H&=w
zr-)BGy*UFek3C(-uk`3qP!+y^fD7Mb7vd0
zf;SK&wkfx&rE*kMLQHhBlxZ2>wsFOzl$3GuTDshI;kHqTSTSxg6~)=85ElH0xum5{4FE#XqmG07zxzOiPO8Dx0hgxn_iFM=C>Y=0?O
z=ZN(uJsj4G&KFfc@0HKax8ZE}Zxb9n(sQ6^w
z
z;}r*DaarO&*UtQ(Yj=12Y_JrTiiJ&m<^MAV3NQWreuP#hprk2%f1f1Z=ilRx|BU-`
zeb;*z9cB6`{CI(RW5mB*&kvx=FEv46kpk$n`@o{EkrC&3jg66N+wwqIiOLKs_{#(9
z3?b$}^yS0m#(9Xn{ipxtn={+D;iknp<-=8C%K6$Y0IGZNzj$Y=Z1^~_ynO8sYwG;b
z{8TkQ`yY1b_R5>d3OIKy)s4VVV)Fm~I}5{St7mfn^8XD%9Y4&H+bU5zg@*T{
z@Dp}G)9Zas6K0Vpj!sEIM8zcBDzGa+-=v^_)&R@`X91pZ>CY3q
zXIXnO_c81*d&>i)Qk~5cW|&YdQ($$V;`@lUvl}(()InuWIj>5EJTO_6ii#@!7T3{G
z4S9*_W9#A8QshEEjTs)}#-$EjeH4=Ya0K9|V1{z$1f~dFi>&FV_M1}>xhL#hdM|@n
zB?=^^0b==Oe_uv}`uxYaf#1VxbfI++oLT(ROKdZ%G18;@Oif+BrF&~zoATtQ8>CYm
z<1kt^@Sgz*^39*vsaBc8+iaOf41)0QZ6~r5hvD%;!6$-sw2U|kb0Tt*WtVIK#~!?g9t=V)wxyrBOMlO_0h$
zGkUI0@Le;N=mdE$wcqB!Y-795Ly$gdMdhgh+7WU_pZ3p)J*wDrbrMvDZ^&x9MFb9y
z+38do6khw0{p3;5nGSF|n&gvuQc5HL%kv}S#7pu1wO{YJ`S{vQTugyopO_Ts`DCM1wJiMYWDm?;h*{Faj6lW3YXJdc)osan^jFgP;p-75~oLH}oEUJ2=zu)63_NC+35yn}27^i-qa^?HA(9|4xsb
zWB<;wwwH8m8L20r8TfZz|MQRk^F5tE%>an||MOv?zb*0qx*={+v)cXqhT0+vhv26d
z+lRjwdg-5k5G>|2qB1zdVx;N_>IE-X3-!^Lx+0A(((Gr#&eA;isXuT$Rl5HM)tY1=
zeP{0!(?gn=$3a5F|1cimygICVgywV_e_Xl%UCrP!I(mu%HNbcI0q(8mCJKD)hACr*c;XUXxRSNMo+`F_$Ub6BL90Ie!#(NpSa{kc=g$?
z8ltLd_?ww2{u$g;phNic#J(9{hkGjkA9ilUUv-FSzf?8C){kiu+k-1c9427fn_8
zjPQ?EyYj+gjsWHKg%^r6r>|`30Ur|v%9)0QtH5_NCmSP7XR_h4!3EWja_eN@u|
zn#VAz=>wRJW9BX!N=}&wM3uXT6`NNkUFmutt`t;q6tqzKqx?MJZ(uQyn(lhS3_ey9
zD^-n8YTMuU1=oyUd2_-Hw9fz!*j^02&Qnei-ye%e9k(7?pEuj5DHOTnHvbs3&UHmu
zMqq&>Fls1USZAs*b_6q(f~c^YeR346P8X`H*t52-!7;bsI9op@yex1sx+rk;LB6);
z2^!ALOtFVdRtp!)Lih)|zSY35Z=L`2_Tpok
zpuR1le>WPgR?aD(_7*h1o}>wKz$ZdZN76;9PC2;81Yt$i>4QBYOI-vInI
zO?n~lAwWZ8%;}HPtWbw~%AXeg7}{{$+`bsv5jEla=en{#x7gvQ+~sDTxnNPajdo&HBG96Zk(M
z*82bT<80p@fdKU-F#K3w!lfBQF7SgCadXf967TM6q@+u4HO2^{ebJ|Qd9|JiR*Tky
z0h9BtVPalqiu~c{i}Z4~aK*Yw(fzp0*>optGulKihM)>XF3SNM>-1(YFyAhJ0uD`n
zB+78+xv0DTDbL}wQ`bXSUj)V3Hr06#Ybzy^?>US)mrJ)ckQRlif$P)z9WTkHxoCKG
zKEKcA%JrqCrAtgqyucIVn~7;Xp@}TC!LkX&x++;e!r|2L^K_ieEF%K3zbt^h`K=Of
zyDez)_=O)6BdPQK+QNNS3Ze0@qsXJ6zK3A;>HdWW7(kHg=-Y0!{!5NYk6A=sy!Vyf
z8vo2aw17%>A{Z7MUAOdI3+7WQa$eyG=#wO|dOP)8b=|Wf(Iogb}+#8WL-dam7tSH~9FM6xd&04aQ|I*UXS)!m<;kYE)d9jMRRIBoZ7GpJ&8A|jv(gr@{gX#{C-Gmcsh1NxbEwOlM&KF
zj#;8F2Atp*3ot^nAFt`1fT)ThI@r$B4vHC#CzX$w9HZ&OPd3)pTh`bo`pU0Db-`$P
z&^EgEf}^OJ@qFXOTWdG?lkW+kVd<~;KCNr6fs<*Oe@=Jp_z}+1tM$ICxr6N%v@UAC
z`#_vT^Gl4j=8rP_@PJJGv(wAS7&?;wEKl(_tHx)qi?Q&4Lshle7i<-7Gq?d+1bD&U
zGCb{1#zxr9=
z%2@qS%)7)Z(-lXK^&px(JPqRbj>XRJFlgK%0j}dSsLYK2d2UNJHD8wu{fMI?gS-Ef
zC!DaQ%cB|BNNDPgQQZJTHdis5j*m38
z=m|cd!hR2rWs^0bI;-ez=SCY_Qr!`99axmr)y45uC&tl&re*;o0q^Nxilqd#@;jH-
zchiOpTy`XZJ4^2!euRdUqK5RU*flBpgeu#C37LbDIX)_vm{dV>bzqRA=#AAtAT_Hs
zrcPUuFJaqbyU$imDUzMPS|58k^dtO_D&#
z?%a)Dk6}U`+?h5XFazWn`slKq8dX&79ph0LmP3!80QOwO_|r26T@*Ap`+O*;!mE
zi`#S5klTF;0iTfhQ(<=!nFl$SlWuDH|M7_+Ow=#dX*J6^c`wN!va#GQT?L42e~M33
zO3Di~LfrtkW8
z-h8m56ysKdagDfVW#&`|0f)w?RAJ&w!p}1QtR~V_5Nwg
ztfC6r+cFi;`N4FX7werz(u?N$73le-p2$WDI_yH*z6QSXGsf2Xr%{9()tDvhZl76O
zQ$$t|H6L0~)z5h23~d5e_m*%0xN8o^?S9tl7fnnX$zU1gSkCAgn!{g%_x6Zhz!%
zvSKjxUdA&+N8N7*Q2m4`=0wOwNz2qh+W5RZ2B)s7dUq=aN~mvd{nTaXR|~&>AC&d!Su
zai2k$$?(k1+L(>%0Mjf6Yl-zGt$Hpr-?oxx=B4^GB=(HmJ(Cp}Zt(h)g}By~56%I&
zFxYBs?nG-|=EmuTXIJnRvQzq`bB`8|dw>h_OvUjfDvidf7fCfcM~TjVjt
zHIkfpHG(p}4S5-U_8{!y5-FKP>mf>Rv;%0cJ`;u}|9G<-9Fd3FiQ(Nlf{@!w}gmQ1a`6d_3+=#-law*ya#|uI!IZqq285bJ7me-_b>dm>)6ZRM@tC
zP(sXMy0tPFI9O4_vUC=`%GsNIGFZ5I^TmM8*1n_o)e&NL6^1Oj(Vl3aJogymZOlwN
zCMKCDgEFN+fk4NSP>0uuPS}>?aWjvE$->10&ye2O520R?=2C%1w_ccj@&{FE)XpBz
zv;V}~zqVm@kFwLqnaSi13IXj~-mo-%tP-jKgY}wZ#peu=23#Me5xqYFX^s+yA6NCz
z1lH1Sv(k4T8QDuJXm8}f#W=>x<%Tv=Z6CTaE_7jIo=PLB(NR;8(Aq%)DU8`FCE#J_
z6N_fxAtbut^d|i#>-&mk_VemK=02m=zzxGK8Pdv|P$3U|4!#uV<|VlRdndIQUGWbM
zEgcBe7YCy?W$d~NGuK{}5fDTH4(U
z$*eNbl5ud?9glj>-Ol$0GU`wxv?;eme8Hyx&K|rVGr0i#ADni%nSY~IJC-%1Nn96v*gwhp6nymJI
z6u0U}^pbiS!+Bbj&CEuFZTEoDG{agYVl@VU#t^toeA>x}4Vqg=1O1<@UiX>*8n}>4
z9@h9_A@X}&M|i6JM{Cl1rx!YzqXv^U4R9YB{B{wL2`A0Lg~OR8L`i#z7SJvIjcZ$t
zBdf*;pK9-7Ffua(6HX=-7C%^NWtdd1>6vU)O{w*q@D5@1NPyDS?rtn3ua-wjQtW;D
z7V0C~pB;y?#>dM#}OG08u8RIC`GsZ%ijWxmQOQ!5PF_NeW*pcA=$&)dVS!=GkU`Dcbo
zxKDW#o~Xd89P>J*xB5(pF+Wd%8MB)q60EyB0ZdogOikKH{0g^j!=08qY-LyFJtvy6
zo`v1Dlm5vCxY5#Tyv<&1)l}Y=ylEL$v5aVGu{XIWQk08Icuo+Q0byhk{)$h^*f@C)
zQpz}DxV0B}uW87yu+y5tx+jBJvZ=B=EP%iU5)-v@((0n{oO_HHUHckV3T5eY3^h@YwXz
z(mhDf`r!dF^KDSy16yv=6T=fmh_yYIBfRqZ>0~9-y`_GqgAc>~Fu5_Z@-CEv{Bw=?C4bLh(PBOwnCibmy#
z8Jw$Iu7@cC$4cwIn*cweaosPo_pdgfcr+93{U!KCZ)foIy4^C%K{CE8Y3hpU&Xf&J
z^WHYzAh{=uHxlZGa8BXuEJupvwV`4#1v8yLBWg3f=(tCisQTb8q<3D@tW!W3`2sFD
z4tSky<4#=@5%U_i0zL`*;r@w`@3^#p>5WI8oK-V&uX?WPqS28a=S1KskT$)g*$g%Q
zkwX1${!r>}n?D%cE!?cP>_q}%xrv|>bMi;}hl`pVMYw2-&-6Af8g`E!ykiImu$t?b
ziOUr4GL*eEb8X&{VdOt(x7;ZraCcCqb(WvArU
z&)G;OZrv4>#l?($_0ZOhbKih!;SNbTv=i)pwA^ivwv~j%+!K6+VkewCkaDv~SB}?0
zV~*u);gmT1TTRZwcINDbUJqmj
z*OLsUAiQ^N`ff`pMOKvPRh8m=8BaKJu1H5`{h*!gZmmc;r;wqfl@IN)eJKS
z@TR8Ro{%41zoV6`Ct%|O!(yFwND@8v@WSA1zRV_ZyroslR;z-sP68EnY-%OLEC$DV
zHqfN8#Oprl?%odJ+j1D4WR+KW<79lb?Z)|5X_FSsF*?bgvMHYrw&O;|f5)c<)Toe$
zo*ehcB!<|@CqOOTEa66k<_AN4NvdKgLA!rDth4jYA*_|RRaHlnNgkxScCqb;cS&Q<
zeqel^$D}(BNd8?hd<$}yg9=Jd0o9>C_rer^Bo5|{=9;OZ%MF!;tCFe~9MLCV#$Lm@
z)qFr&9uU`c@Pz?~l=ym0xPH-NHa>xF*_T=T5TW_?(&~iz@NQ#AT!#&_AZ$@15dl1YOO0<#?Y_U6{K5Apg-my*
zLFGklrd09Jb8@&ssjNX&W?mC?`nXC+!&Ly#zq&=s#=n7gYK|L4_FRE>h&RSsD%#N)
z4!l)G{GQn03CutU$tB*gpzb(>?x?>4;Kjb%&PDf~iy__Agwh{r|8#$rlIL!uz~|xT
zcYgS7e%Zed$-O4pZ!q;X<94bP6Fr1sMTSP|aVVX*?igGQ098B^v@EYaj1J6d%5OLY
z^zb$OvK(8>I}9?nh(iACjIM1TFWWT^74K@<7
zULAGL5*On)G##U&`vnBJa>xsH#ghbYeLdhSP<#oDg5N2Jb(jJ~4pB=ygnm*S?Jx?Q5ZT%}@>!Ql`?-PtUE%$xb>KkpsC-(@m
zk#cm(>nNtw_Sdr~JwM05U_uzI^Si{GE5K%;Kx?oymu&jZoYuwCXP$8hi$q=%B7A*E
z7Xt*IO8jG6gJH+Ml=6g>SUcEJcPW(|*c~E^3)nCBuwM1qU$Q}mb}C%2{G{CD&R(*X
zrtw)*{;;PfD+Ba?hGN*lE1mt1{c}3GBd)#D7ltfe>>*yKZ}*K8jUZtZT+MiI3WEwM
z_+&@YC$z)JMvcsOKh|+s-|h|5xdIP1&u2A$?H;Ziw@}9;Z8$BGQVobNownPe1x-IP
zXw6S2?)%Jy^7``pTuGMYO`+5Zd@sPpbjg5TZfY{?ti9Cf-Ze0E5%?&tob+I3v7_fs
zwsFdN#xl*vyNrjeD_j;{b`BX+RmaPCSZO%S87Io%3#Bj>rF-dql{f`mn8FC8iTu@%
zzJoAHfH?vu+2~UoJ{??kPd;6JzWkQ)B7ua#aJcpiZv70Me
zIz=w5P1a%)p-vi>|J{|girxe>()~?dnA;2Owx6zbm!P)M50?5N^z!RvVz$2sl9{nj
z9N=5iAsk7aZKbUD`_*2s7BscvNh_TfUngCVncciQw}r6)O)!D@ym`ZiM3__`Y`y!I
zKk`e~lcZ;c@66PH?pH2y+$DX5ls5BA&|bTdm*&y1)zgtZyMu3C&zWpmdW?xTn_=&|
zgVDc^p)ujD!E}~x-A(iCt51;QbTZ{wq|AQVd9Yha
zeB5+n;a~}7)h&IY5@&9-L%M;Kjd6Rl^IU@VF5=tDafBn@DpH;Y>}ipUo}60KG7;{k
zE9`*>M^$4Y`zSN`lnN(=ui84t86=-ZisflDs+vnvMI{axvx6c8SWMpuH{zoEIw6gV
znPEK$x^cD@+nZmsEKC1UPyUGiNfjbET*W>27?i;J-egJ%X#T_|ZQx-Xo+GC03O4wi
zAfV$vR@(HvSfJFo8QdBI`Q+ml{>vB*8KtLdtmbX5bVN;g%CL#)?XlAF<3ZG#Ngx1h
zL#H_I{+NKw8wtEAywMVS-uZ;1$;ILH<-Kp1C)F-nc}XS+Fk^
zOB=<{VLG2oBgeS(sRyB4tQ3cSTSy+Akz&
zn{#j#qq4kOJNfFUaB5^3CwpUH1iypH`PccM(qxr=ZSt5Tv`LLn^qf5
z#~>xTWWt+W7fm?K4`7|l8&xMQS>Xq
zyV$Ap=6YcVe>v&hiyA}C$tIWYzy|Spt{g(N%=IBSt%D%SH;koI5Ykz4!Im^}s<5hF
zh)#M_t4Pnk@>TsYKCreZZ^yEwpHMiq>4fB?n>wogI5s!4y}YKZhCvYcRZEZ&t0D;k{UGHi_oI!FJ2dKj?R-LB0Oo0@K=l&XD*TLPq%++1#
zksqR|N+fT4a}eelDSJ?N%>=q?Li!yCR$fE_QvXV0gf|GpLVTs&reqtxB+BNw-qPQs
zppd_gx>~{p0{C@rm8Sj-by07yJ3mr-$oh(EgWJFB-&jO}=X#-w>8@@+phqK{fm?9;+_(%1pm)vOC<_`5#hqH#Xi<
zgsgb%F>S-6B>)V?0iQo(RkEt8E3eo#p%Y9dM@QwanNDEy=x{c??6P*>*Q$oPVZw!+
zSig%+8A^9RjcLq%hdD<%l$v^sX?fnw<4J#KULd28JpC~+-c)sZt-G|Xv;396&zcNQ
z>eQow7NwA~0j}_qFKW*t+g7lHCZdo>Pf18*Is(={H|p`(Ebb#~yYatd;ut-KFYx++XCJ3PGu?sp_+**_tF8_C*<-GM=RSW
zpnrGp@lPzFJt71=)3=BN-O)$xtB>vMpGAIjn+cVj|J@RK$2x$_74vW2C*58=qB2C-|?%F#EF6cMFY2b;S6vMV#Qv+B>X$uGao
z$0tu|?p=eDmTpp4^IN}D_argS;UHUl?Ea%h#UN|dY(421bedfF~RGYOVCrJl2k
zDO|*BrR^Q;d2S*OTybo#{5YR{@+!04d92Q(M)6NFgHBezG8OYqCwT%ZNVOEPf=3(7
zJ*g5H{?L6~#Q1!}H%#K{h#z#Lx3(ip7d14$u2$^_IPzV50kBq-Sbg19Ek>BJzxMeWvp3R8E--jFJ(m
zS>Lvwr_$ZEMqzV5`fPqt`skmvg5`oXuKKM})BUx&s*a^KGS$4wV1Pxo2fWx$l*7t>
zaQE&x#gWjyRXYbTq^Z&`@93tp$c7+q+}Lk5H09JFXz53Z9lb*KK4{t>R8>r~28d~I
zc}`mAhIGHHL2b1{d&Gh_4oxP};*@nE6S*HU8e<+Ajq&GCUOclr^Rur_r|<
z`NUW)^pclG&znUnxUji-hl8|ua?JTLP7{qGEsu2XOS8Y;BO)ggbXlOXhh}eja@L3`
zk_2sL4axZ!%8SI0tzk6}W<(sEAsDts)~TGwP1YDp)j=s9C3`3nN_y?ATlq?zfnj`0
znJhi94tTe)OX6wdgj%7`#LOE+%cKK}ut=#OYEeEG0CF1k9zEGDrem}cy#CDrL`lYF
zB%3l-7^5Xe)m3UUcDl)Zmz{R>@Q7Ra+1wIy6TD!W;*374v}k;`ZgDlY-7k@~Zeg_(
zo`f`4>tBon3(hIAMX$v#A?mx616iuaZ>hZd++BPo#4qvwo6LfAyrSMnj;@U>aTV@4
zwLPlZva!n%!2;or$ng_OM&e#WI#@!^i_AMU?>;$j9bW}Gy{t%iQKom*QP6RC?UfT*
z+y44KYyNct`R8K{f>!;0Y1$Eu(FX%WUDN7&j)SeYp+mFVfT8Lo-wFs&4cUM$jM|XM
za;PTZ)|?$nFeRa1z5NpDd@4>q8F9
zPWY(BZYPTGpkFFJ@@=Zl<)WQAG%0&PWnpiV_j;0Ow5-_-lP>hneOe92VDB7h5QdGf
zpn!*M57#sZAa=%KAKBXvCcH`JgQb@8of8Pt1`f}r>U&~h%DX$Wk*cW;IrJH`_*&&?
z&!mn`5;@mpV|`8^Q!clc>IoO;UaBu(x}hS?v|0N!B%5#bsy5Y^d_w-kW9=fVfK9Rt
zG3D2Xt<5VR!AAK$vTF~)zv^ioJphw^f{46G-Y7Ux;&9b@$vW38cF{Y!Qlfnex{M5l
znwd3aV@i3~*1$((YMUlxMw2U*TuAeAQhKkgjT>L*=pX&1x?uq4l10q!?Z>MKu!G^lEqK?Ky(d%wJ$5`krZ8mu2uS_d4^!58g*EZitG
zsKbq=}y$CnE%xCv%B!YTEm+23oA>H^YkH-A@p*T}{Xm
z*JT~(kORGNGAj;8r7TpPM1sI|9S!XabSz=l~p9B-uGxnEc`-J*n0
ztudRQ68M=bF(myhm*ta#)cdJ3pVyn40=c+$A3fp>)S^9k{{2*ugPGk)CTAj|^L5-v
zjuUE9dQingS-?(wu;f6~ziT4VyjLT--UcNYHC69AAK%M-2X5MEV6kNufG}`BGq<-0
zbwpC`Np9E8`hB1s6|Q{MC#pbPsy}(DYMIgm#0xpD@j?sCi*mWZt!95ioA}!=@T7rT>L4>
zkT{ioKJ}xM%k{60oubQ^(L_v1arITA=~iw+~I=>V!A~*2|atBy9Rv>*sl#j>;x%*75ofD2X?&HL(4&$==!K%0GnG+BI^&rNb*kI}a;e(dIx24FMOBj^rL5Wy-tra<<
zD}eVH?OrO>dE!jFY7$P;ImjNz?z=yrc@ifoi89xc#J#bnhm`R
z@OlblJvLoQKX)db{2^Vvp-g__mm_xv_nQ54otox{Nw~h$o4RRfCISm1KVp1Jq}tP+
z{6vL?QLdI(y1RuD_EFE=?G`P>{xA04`Yp;YdK*Q>{3r%0(t?Dvw6sbygp$%K-7$nP
zlm*gCcXvuRsN_&XcS#LB)XXpp@$Nyt=RN2B56*RtKX~apGtcwvT6^ua?t9&?rzhq?
zCs^;J=7@FtNm|MIV4Dkt`GWh{TR|vgs$Q&+LU^LEyR7F&+_=p-5YBp*@|DbrfbdpI
zxq2bH)e^#3HkCyhKYO~4SHgu%+TH9tf`X7&(}5f)FaW)C|K4SRD&u$W(ik!`c36D)qga?r+@?QJwwmGlU)_nR(0IM~Cl_7Vd@6t9=F_Wl
zMj;_nl0OEZ4M<6o*sKnMu4L7$h5m8YwZ|6$O%1jVj`e9XEDhgel
z8ItWzs}lA<$Z3Rm!k!Qlz33r;#=@Y&5zBiBF}o@tdzY;!02iXp+HJl`A<_EmX(~9d
znP1s?XF~_eD*sB{r?Z6{7Bfu}KzmU`X+Fm}AcF)yWV*0<@A@-uXcF0c8G%*WOj~;S
zyj0cHV=dJybqtMfhyHBoV@_+eCsWm`LNUN3-*IwZ$HX_B9$Enh}aU3b-%@U6C{nwKWxkoUt;2^;^|-yAh8YCN>$txw0C
zug0~-oBa9x1)~qZk1P{L^>Tn#K=H)u=soUO^5z78F+m(G0U7p<$hZBh+z|Eyth6DOmE1w)43XbC+x2
z6A3^g`s6D>6imFm8r37T8FRfq0AR%tjH5pl%@?e0b
zubq7XH39j*zR=m(nVIwBB`Yf{10%<R{=0LInL(5i+lfb3qX9!OiTM+!f16SM_RE(F
zSRW%36hHF#cmV5gVIrR%EimS#s}CQksntenXV{KP+6I`YAmY
zPS#Q@jR6tYN=Cx9XE_i5_5j^GV3u|B269RNzV#LUe|MBJHr@Ij5OA$-`5ratBkIA}
zz`t*L_22hyXEHK^-P33^ ~9mjBluR)qWdREGSU7~dLTF5{|^)z$CiQU9p0SML40
z_vb|*iuv~K2mSnk3R&O3y)*G|Cf$1HVmoUJ(U^a?kfD~{`?vjlQUAu{R(N70Bj}?|
zoyekTUWSh~D}<+}^!(#|(V4f8a8#-@3UDgdx)|j^2p7%Q|EKQ;YF2jl_P|d8ALkBj
z@|(ZiWb@AmTzNHlyq`8m$BExxxBEXY;qbc*B38xv`C;E;9#215JDa@|?0+ZWt>2Z_
zGmlL&O~5qJ@~tI|jE_LzzkYb4)C;DuRI#t3%_PG@u8O!2U
zBji#TfJs@Oj>i3MP~ksr!P_@`Z8yLM3d`vtp%zgXEt=jWupNm~IR1^_+Z1408CH)7
zHG&uzv&Z-eSD&4ENZ=oD>6t|!n2i~e(L5=zd`{1O0$#|B?b#A5XZrW{GL<&t?0774
zvV!$h|0^E*?OT8XS)xn05mDinf6X9Fh4CLQ!4Z)oNuiuf0-Beu)6l;XCE`=?TbsqAQYaZZ)MgdrWnj`l_m7
z`;<@mioP%@J;};+{uY$fVEi+Mj%UN?!X_Kx%YB%C1OKGL3}HK7;+e0X&&kiBbhuCX
zX7q)uf@L1E8sGK7h$PJG!e#kU+v%Rl>anNzv#Dy1|2OatCTl$Fz;v#(8g^Dxi9We-
z86ssodGm&v>Y(x5TxwR8Ui-fCo!htH8WK9jr;qgZ?ZlTb)sF(!`@j6+mMRNRbU)C)
ze+O%mpPyrprwU`}r#9NqEZ#lh8E;~pJ-TcA_y-W@Bl2lm?JvvL<~;cWE0gM4o%Hr`
zT8Xh8&FezUGOv#mMD3RH)Majd4GIdQX@JRRE9-AWu`*^ds;aBAGqy^D%5khqnI@_R
zuWSu(e3NNeJftX8i|!q$(=uG0t_o$y&KN5+01VPnw?U@yQnz#v`ItW2hAEh*7NqYu
zFQ>6)W!2Gmrz
zZK2_-x8DIiM<$BhU{SlM;;i+)-&3rNe7l^ZTt8}T$umTH}N)X%___K?$h}pw5+WTR8z#n<`>9?3cP{7w9x1Ujx
zFR(#0rm@koDur+4Xd>wwP*e}8L$)6GTMYY^SO?@}JeH@3rKfIli753nQ7y0OJ9CME
z@3^+vQvt;blu7-UpYv^_fPL^02VJVmLj6jP`RW+A{xu9LdR|j;00-$cF*AeMDi=jW
z58mqI0H@BdtI^6~7f7fW<>lcnR)ekW%yQlcPilV4pbf4uE|3Z>Khq1Z_yPl7T&)W$
zy+X$NXPPiCOS8I6lSQw1j>)8H_Ir$U=om1SelqT-Sc!?bW50v!*hIw|aJv+5)kFR8
zdw#1wqCWXvFSDXIF);~?tca%3;uOJ^^Dg!^lT=w%EE$`1tpPP=W@g!I|6$_kRQcWF
zzI}V_?|(HcBJABA5B;@OXY8H|WGVcn#J2%ecPX%KrU(H47>pdhq58Xs{o00P29j&m
zy*|sVQ+f~MPbv*M9VdJB?gJgIQ&ce_SPJp-YMgSI+$^>K^8&BX<-SPk9UZPL439AD
zj-hj%@$zGNbOa>yoX7!3ho$L>bKcSM_ojHB`mO3clA5Ccwlf`6$5lpBkgV;)0RLHE
zzs@-tEd31~8(;}b9gcNa*r3^?I>yEk*U81}9eah8dQD7Cc``Bs%cCdV$n+UJ5BJM-
z^K_rsg|K6Xid|6G3b495ek45B2Umo4V@mEA8gV|aFv7dIEudG!n(UIN@0@%k>mRt!
zp=}MJH?IR?0z$3~5$yB2o>
zNt`5TVN`;Z-zT4yAKBrYO#^n0wmo@~yDbZ+5#wd1ml6`xCI+|SFYYbndW$hZ?jM_1
zX|5)HrvvrZZI-sB|Af2;TGm7)cbqomq^vXcYoIQ?%Il0#I^Y?|}?URP#L$LUc&cS#AspaTn%EYDhgSLu?=+E8ne8#3WP
zr`KI47cRla7L+4)kOq&*424^d%(bglt(5M(joy@UathMpb0~G%((|MsZn$jMv{PG;
zn;m5kWS+FO5v4xAw{UYdS}RM5U##5&)YIr7MwsE5%qBnP7dPrO;@_sO+7PxkV#=$v
zE*b06(~PNxoBngtY5u>q!g)4y*6Xk*_{HQ<2~!>GOM~Eew*-PUyv;knFX(KdJ&gvZ
zAh2=A1K-bNIADoMTPqrzc1%{jUeGDX4kxKveaz1O?U+C8>52KS5~+hMm2=Sk+O+JQ
z@Gx+^8Ti|b!z3o=wF!S9LLtp#*&?H@t-bpjs?wDg$+i)-JXvoME_%A}ZH&jP?NM63
z+m0Aisj@1}_dY$@oiDd=?6Rp+P;@Gvrl2}2{a_C!Df1m)Ciz47ufq-v_g(+Dl5d|Ik5X|EMXCh8jqk@!Q*YuBn(X4ZAEv#_ZN
zd^yg}uFh`zXpu^r@PKQl3nFbTEge43f1#AJ<(q)QS{+kZe7qUNS>&LvS&7iR8~xZ7
z7~1uvE-m6%6nwZ52MV5xEPXLc4r_q%Zbr?)!3M)6jxD#=Ynrj$5uW?m&tv~)ol%va
z8#~rS6D>I7t|k*qV^VdAc8C6Ve?%82um9#viNg+pA8>>2`6!9h`}t)WEp(+-A(xTk
z&F|)}?SVoobDtF7{h35HMY0vuz6cASj{kJ?3RzSANwldduAIbzu<>7!6>+r=ugKRN
z5n9X~m~m>Bp?R!dXCh~8yhrLxJt#6B)^T!-r%Z5FCbg0D1@`H~;4GcImcFrZMuHpW
zY29)rb}m@Ua7Xllvd
zvUKfpKFHd2F;p(xncuh%JcL_deLV9c6@geszpfiOd?(zhkgYbo*@HbA%4I6KMY&%1
zI4UBlvkW0>)Cp!4QQjbpGFvo9c%n$<=y*J*u&@q~J0{`})2q>G2KTrZe(4*1+|xxC
zjyWf(9L_nNSqw<3!#WT}SW1y32`8Zz1|b1KLvM}UU5c&NF}*mVBh)e+RP`=M(U7}77L&qeO_^YLnb
zeif)dF{yPvJT8v)_U)P50tyO{JVvV{A^V{Rx|X$XHXcz)Ny`F(by}m*`bPY+)p(_I
zpuUy3(u{I~W6jk2b!x}2$uoh%pO-(nLBX8*BTK_kQnDq;!@w5T{Y4#%)^)`9CXZxm+^7s(H_WPPh=Y||K~=1MIqKubV*p0E9?N4<
zswZF)P*BW0O7N(NPRltP+*^6ZYdquCBcdk@D=b25IrZz{KfYh?@cAVPVo@QH{#Azz
zOw5yEn4CNbp=`0)^Rdt(y4suW{r=Z!xmCkFioBn{{m`AvJ6XhU7|p3~lE!>#eK5N?
z1K1vx>}0wfuRGO-Trtc9S6YdIj{CWJLz
z{;MbLY!>MrLywv)BP?5~={UD44Q-!;Mut0EHY%1kE1<2T|ud@lRR=^+kvdzViW
znBz}IYW+*4A@?W4I$~a>+vMU5+?;EJ9&rDTFpLq_I63KQ8Y+ajg52wlV;i}df=uWZs1IUZX~iXzIQR~fbKKp9aCLpe?e(~LJB4Ao
zxSI4rgbyFD#>|vV+{$#TsR61R^lrT
z+9hcCpVx9FdT(wxEM&rDT>bq+1dt_WSld``sA&_E#
zLxfd3hI)4iY4|=Q^~5Zz1ThBY8AmviN*K2hNgl=xhTZrcDsd>41;c3VB}AfTb@)W}a_P
zbp54tw4uWnH>KoI2v@9mCpcne%Oa9YYYzNSJ$G#4N7?}g`K@opbq1W~TCUxqDR9d|
zknL*Tg9dK=Hy-dwsj8|e39(rf-`yy#tC}I~cLojku?j%faSQKG6dNq6P$6Yf4g;Iv
z1i#AlomR7~%lc1Z7N<<2Aul{WPN#_t1uUVw(T1+#b3b402JRpy_`e6Qk9Or3E0$Sp
zS-}sDjn;R61q4p0*5@Gk{)7dZY>XLY0@+h9#A=D`skYsMPJ{2Lo@*g<8@bWR4Tt&b
zn^$8i1iMJBIKPn@yifggskV@ut8z%pV#AeM3B=J)WhmxhwkO1F216)1+v*edy{fYm
zOXEH)64i(=+KsjBbQK`-8MzIjUScc1UmJU5u)%Al?DCkA>c1uqk-;zBt=)q*6E*A2
z5Q#=i>NIn?9wL!%{VMr}{i9A%*rw>JS8sd@dWWYnDcIH%2CC-p`$LI|lO_K>c_l
zt!Ir!6Ti|+F)-CteTAsIpoPQB?ZTt==pqIrv|*RJ7!t~YCFY7O5CYzi8R
zjvGA^6|+6AA2opW5S#pRnXt%)NAsG+AhsL46hfB(a6Mf16leYn`!d<{n6esp8kS;O
zIwNL!$;iv6+usPm&5ue=2Q%ew8wzw96&*{2b@JjI-wMjkAe7Fxio=KhZx@(e?$-?||Ey0!72Co(FPNM>r*c7dvwwJWiSg|hsN`Zf$L{*oSzBAlQ*e^o|
zCLWrnWJX$MzbHhF&O$LIwwtw`3!^_}KV<-%WN(2W0|&p7morL?sd`7O2mt)YUEg2$
za0=_2T;D_`tq)|Y&_ZbrYkKjXsKCaPpBGN8M%S{jM~9R}g2=4Gj>O~D&?1pqGvkir
zlOy}|^FrPUDZ{bb28cnNs2Hsn*Kn%QWw%9?cRt!$L!>bw@2wx`U5(8O6A7-WV)g!?
z$eT&e^3<47(T45_51^@qTN5ilkq@nJCub+;MWyt@dvjf`_d@w?r=AKum4hn*hPxq%
zJP`_>bgLjm_}+S!i>EakU{(d6kv#DHah+DE^ZFNZO0~$(EL`XjrL{Ck+Dnip*n0u)
z*i|ct!DN+Pv?qRdLuU|FVAW<)ZY&rT`^{FKF;@lzT_^)Bs`Ec;zeT7vWQlH=svWlaXHS1a;L(j
zegz|Dp2{MP+wNS}qwb{+XvbV>V@RpHp+5&5fVW_&|UKCvdv>%^`9i4qy2b}&(
zzSY-_5)rFR`l1pX(y-oPh2B;vxn$TSx%rfzVV~+|X6Ry3S{hE%BXN>?mKS%cn(#Iz
z((i{mBOOu2TWFD^wqY$pj?q~sBa>TYj;+^f-v4gR+JLYX=+tNe$)hkr1ql+Blwqh=Y`Gt-%oLys72u5lvxF9LB9rOLZah54@H=%&jUx8By8Q=tzmk8
z2}Yvx!RvGytC-&($Mmb&0opI1Av^r=BS5dE|A38P=a0@unOCK&l-HLX@W8|IGGo;#E
zI>S#7t3>e|Wk3XSlUm^I@6Vmcw4&5!=iS;oiZd^XoQSI>Ul6;gbR`eS)prhS8JKkP
zZF1M%
zgU7Zjm&CTj>}7ZiVBmDo>aB3KivJVa_A&T4D()Sxq4wU
zt|Ot?wDYJ23MP=#S^>PKi!rxN^h2`e?)RhWAAg#5BuT^+m4%KK>zH_8BZ}Hjirw0W
z)CH2HMb
zjGV_QXPSQ3`{q${{qcw{p^L?*L&$X3l8ATpWq&M4BY7#~PM%%lH7KYlR*DyZ+f+^I
zSQ(xqT?G_0qP|M*7RDEvdWgK#TOT1-bB12Hdljr01qrLp3^>y`vU(#R%ll92^9T}_KYec?
zSi@1FVV>Atj9u+7b-4B$V0fE+z(~x*`%Il>M_6@X#CF-O5gN`Ww=d;9=Ag19punjK@;RQGU*D
zN6DeM8}B{DaEsxUi#D%DUOv={1|ajoQq_8st`T=;pQ0`tncf@LX@v{P&vZ&wkx!q8
zdYxoOkIa@j<11~@Tdp(pHI<9#O~Kg%8g3Yt>bo3
zz`<`wu*`R?7zVCkv~IvhU&kfHJB>$HU~=I#(h@_4u6vQuTKpM_YkjGoI4ZboTckUC
z-oGMc>lL!_Y-ACO@>uwY5I-*eRjpvbKHow?@oD`EGumSFeF+OTgXM;BGo!)&baW%K
zr2WLA6YX}Gn{9m+xLvXzIfJC7WX4pRT^cKuG^ZOC1tc1uEARwvj!cxAp&~aC)|FNU
zw@bbc;CoMkm3wnz%E5umQ2A+U-qIYP98A8sIJGkpZ#NNEIg&4)3R8+>_|N#@HZ{$Q
z+5Ga4;$BBj)ocqcc>4An%pGHGaveb2^Baf1$C1M{X~!s^oq@&=dmn^7?Flf=ol!^e
z62Dgy3$%VT4cb-pQxS~1hp1wvif`lbA49)%eWW!?LILh-4p|>>{@B&J#*j{Dp*Z}})6>CH=TMy_l?U>&%4&Qzw^^~-0u4zk|!R3#>-
zamS_DK{3@RT~@qTM`W$OG*z!(ej%ZZWPi4U1a6SJs}d+
z1Lf&8xKG`GjaCjlDU@3iHO(_^EzhdR*O~<5yH%iY>ySn_h0lL}^3gUp5I+^y5
z`KY^x6gM2^v0R?dPWI4Y#1whc<8Dw|sVHyeo7!&!%I{d1cQARQ1!|@tiKxguu&b6(
zLOM!H*k#2W@_`&6F_$W?@AJBDjafhruk+b$b)Hh*zaC!0X32$A;)$I2U9pC6G8A?Te
zda}$OpCjsv17%@L0>XasIK5&S4mW1hW-R^lhhMdRP^7JTmkF6y^@K+ZX^Ud^|3oBRfdaP1>^T_naU|NwL6nQ?H0{fli(P&N9`nE(gs8_CspS{p4R8xd*>3Y
zS^R5n|wH%5M=I}N_yU;R2Znc{!rXBKo-2(LWpVHyEsJff&bb!U#`B9^R%pk;BgA7NWw_W^qKu{ws)Vx|aFRU9K$L^-uYG
z*mFd;ED4=f`tfI&p~Lii#^AXAW}KD<5asoFpLo}XE26=0D>pGAFfd}qb>GSnAtdYO
zHFvMi9u1fMa8o;t055{rs67cOF2S2`@JpFbG{CArCe_EjbT;Hx>ij%KQo8$Um~`ck
z)2}hvoIyG{&*6=WTFK=k)F!)2S_+nyXkTN)o`X_(F(eh7Wf-fa{(9KFT-^V)^>jH2XR9~e7e^ZJtaMT^X?{di8z+!
zt3=H{5I(C`L4hvYK>qZ*WYLkQk$>bR<0;X5be;-^$mcA>{;DO`!@+j*Ydc
zb9e`_Vsm))WTExV(CXj-g{%G{#%?CT!sgYFt+TzBo9`@XPH5^x^`h2jAP>Xw{+fJk
z>CsxlwxT2F70^{*;{UVB^47UlSUu`cBHud~h9K>awY$Ye_T9z3$mwY8Svcori6^
zu3mz;C1OEML1yo)fHH>kKujwbkMUZ;UCEJ&CXf$14N*Fc*1Hih$5ZZmJ(#L^8DxwD
zdyK>^%4`U`x3<5EsmkA@4#=eI??)pH!G<^y5)j}B$B<(Fn~>X4Ma`wMB{2%!|)
ztQ>jR@*p@dU8D!;Hk|6*{2aO^U)>fc=57r3wKRI1@Li%lfo;Q#CzamL3rN!y7YAP_
z(N^!9gJ#r758Z21FjRw@YEtmO<%@jGiH)+e5XYjHC^B%Ih(yE8oLrSB;z-C5WgUp)i@0yM-ehvJS26wErs>klaZ(3`4
z${LK#kT_7@GKSFWR%z1G<#0HxCw*3{uZEco*F!H{;+yLD+3)>K