refactor: Generate extension semantically
parent
bac330ab2f
commit
e6ada5028d
|
@ -83,20 +83,6 @@ from .nodeps.utils import pydeps # noqa: E402
|
||||||
|
|
||||||
log = simple_logger.get(__name__)
|
log = simple_logger.get(__name__)
|
||||||
|
|
||||||
####################
|
|
||||||
# - Addon Information
|
|
||||||
####################
|
|
||||||
bl_info = {
|
|
||||||
'name': 'Maxwell PDE Sim and Viz',
|
|
||||||
'blender': (4, 1, 0),
|
|
||||||
'category': 'Node',
|
|
||||||
'description': 'Placeholder',
|
|
||||||
'author': 'Sofus Albert Høgsbro Rose',
|
|
||||||
'version': (0, 0, 0),
|
|
||||||
'wiki_url': 'https://git.sofus.io/dtu-courses/bsc_thesis',
|
|
||||||
'tracker_url': 'https://git.sofus.io/dtu-courses/bsc_thesis/issues',
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
####################
|
####################
|
||||||
# - Load and Register Addon
|
# - Load and Register Addon
|
||||||
|
|
|
@ -1,62 +0,0 @@
|
||||||
# https://docs.blender.org/manual/en/4.2/extensions/getting_started.html
|
|
||||||
|
|
||||||
schema_version = "1.0.0"
|
|
||||||
|
|
||||||
# Basics
|
|
||||||
id = "oscillode"
|
|
||||||
version = "0.2.0"
|
|
||||||
name = "Oscillode"
|
|
||||||
tagline = "Nodes for oscillating Maxwell sim and analysis"
|
|
||||||
maintainer = "Sofus Albert Høgsbro Rose <contact@oscillode.io>"
|
|
||||||
|
|
||||||
# Blender Compatibility
|
|
||||||
type = "add-on"
|
|
||||||
blender_version_min = "4.2.0"
|
|
||||||
blender_version_max = "4.3.0"
|
|
||||||
|
|
||||||
# OS/Arch Compatibility
|
|
||||||
platforms = ["linux-x86_64"]
|
|
||||||
## wheels = ??
|
|
||||||
|
|
||||||
# Permissions
|
|
||||||
# * "files" (for access of any filesystem operations)
|
|
||||||
# * "network" (for internet access)
|
|
||||||
# * "clipboard" (to read and/or write the system clipboard)
|
|
||||||
# * "camera" (to capture photos and videos)
|
|
||||||
# * "microphone" (to capture audio)
|
|
||||||
permissions = ["files", "network"]
|
|
||||||
|
|
||||||
# Addon Tags
|
|
||||||
## https://docs.blender.org/manual/en/dev/extensions/tags.html
|
|
||||||
tags = ["Node", "Scene", "Import-Export"]
|
|
||||||
|
|
||||||
# License / Copyright (use "SPDX: prefix)
|
|
||||||
## https://spdx.org/licenses/
|
|
||||||
license = [
|
|
||||||
"SPDX:AGPL-3.0-or-later",
|
|
||||||
]
|
|
||||||
copyright = [
|
|
||||||
"2024 Oscillode Contributors",
|
|
||||||
]
|
|
||||||
|
|
||||||
# Support
|
|
||||||
website = "https://oscillode.io"
|
|
||||||
|
|
||||||
# Optional list of supported platforms. If omitted, the extension will be available in all operating systems.
|
|
||||||
# platforms = ["windows-amd64", "macos-arm64", "linux-x86_64"]
|
|
||||||
# Other supported platforms: "windows-arm64", "macos-x86_64"
|
|
||||||
|
|
||||||
# Optional: bundle 3rd party Python modules.
|
|
||||||
# https://docs.blender.org/manual/en/dev/extensions/python_wheels.html
|
|
||||||
# wheels = [
|
|
||||||
# "./wheels/hexdump-3.3-py3-none-any.whl",
|
|
||||||
# "./wheels/jsmin-3.0.1-py3-none-any.whl"
|
|
||||||
# ]
|
|
||||||
|
|
||||||
# Optional: build setting.
|
|
||||||
# https://docs.blender.org/manual/en/dev/extensions/command_line_arguments.html#command-line-args-extension-build
|
|
||||||
# [build]
|
|
||||||
# paths_exclude_pattern = [
|
|
||||||
# "/.git/"
|
|
||||||
# "__pycache__/"
|
|
||||||
# ]
|
|
|
@ -1,180 +0,0 @@
|
||||||
[project]
|
|
||||||
name = "blender_maxwell"
|
|
||||||
version = "0.1.0"
|
|
||||||
description = "Real-time design and visualization of Maxwell simulations in Blender 3D, with deep Tidy3D integration. "
|
|
||||||
authors = [
|
|
||||||
{ name = "Sofus Albert Høgsbro Rose", email = "blender-maxwell@sofusrose.com" }
|
|
||||||
]
|
|
||||||
dependencies = [
|
|
||||||
"tidy3d==2.7.0rc2",
|
|
||||||
"pydantic>=2.7.1",
|
|
||||||
"sympy==1.12",
|
|
||||||
"scipy==1.12.*",
|
|
||||||
"trimesh==4.2.*",
|
|
||||||
"networkx==3.2.*",
|
|
||||||
"rich>=13.7.1",
|
|
||||||
"rtree==1.2.*",
|
|
||||||
"jax[cpu]==0.4.26",
|
|
||||||
"msgspec[toml]==0.18.6",
|
|
||||||
"numba==0.59.1",
|
|
||||||
"jaxtyping==0.2.28",
|
|
||||||
"polars>=0.20.26",
|
|
||||||
"seaborn[stats]>=0.13.2",
|
|
||||||
"frozendict>=2.4.4",
|
|
||||||
"pydantic-tensor>=0.2.0",
|
|
||||||
# Pin Blender 4.1.0-Compatible Versions
|
|
||||||
## The dependency resolver will report if anything is wonky.
|
|
||||||
"urllib3==1.26.8",
|
|
||||||
#"requests==2.27.1", ## Conflict with dev-dep commitizen
|
|
||||||
"numpy==1.24.3",
|
|
||||||
"idna==3.3",
|
|
||||||
#"charset-normalizer==2.0.10", ## Conflict with dev-dep commitizen
|
|
||||||
"certifi==2021.10.8",
|
|
||||||
"pip>=24.2",
|
|
||||||
]
|
|
||||||
## When it comes to dev-dep conflicts:
|
|
||||||
## -> It's okay to leave Blender-pinned deps out of prod; Blender still has them.
|
|
||||||
## -> In edge cases, other deps might grab newer versions and Blender will complain.
|
|
||||||
## -> Let's wait and see if this is more than a theoretical issue.
|
|
||||||
readme = "README.md"
|
|
||||||
requires-python = "~= 3.11"
|
|
||||||
license = { text = "AGPL-3.0-or-later" }
|
|
||||||
|
|
||||||
####################
|
|
||||||
# - Tooling: Rye
|
|
||||||
####################
|
|
||||||
[tool.rye]
|
|
||||||
managed = true
|
|
||||||
virtual = true
|
|
||||||
dev-dependencies = [
|
|
||||||
"ruff>=0.4.3",
|
|
||||||
"fake-bpy-module-4-0>=20231118",
|
|
||||||
"pre-commit>=3.7.0",
|
|
||||||
"commitizen>=3.25.0",
|
|
||||||
## Requires charset-normalizer>=2.1.0
|
|
||||||
# Required by Commitizen
|
|
||||||
## -> It's okay to have different dev/prod versions in our use case.
|
|
||||||
"charset-normalizer==2.1.*",
|
|
||||||
## Manually scanned CHANGELOG; seems compatible.
|
|
||||||
]
|
|
||||||
|
|
||||||
[tool.rye.scripts]
|
|
||||||
dev = "python ./src/scripts/dev.py"
|
|
||||||
pack = "python ./src/scripts/pack.py"
|
|
||||||
|
|
||||||
|
|
||||||
####################
|
|
||||||
# - Tooling: Ruff
|
|
||||||
####################
|
|
||||||
[tool.ruff]
|
|
||||||
target-version = "py311"
|
|
||||||
line-length = 88
|
|
||||||
pycodestyle.max-doc-length = 120
|
|
||||||
|
|
||||||
[tool.ruff.lint]
|
|
||||||
task-tags = ["TODO"]
|
|
||||||
select = [
|
|
||||||
"E", # pycodestyle ## General Purpose
|
|
||||||
"F", # pyflakes ## General Purpose
|
|
||||||
"PL", # Pylint ## General Purpose
|
|
||||||
|
|
||||||
## Code Quality
|
|
||||||
"TCH", # flake8-type-checking ## Type Checking Block Validator
|
|
||||||
"C90", # mccabe ## Avoid Too-Complex Functions
|
|
||||||
"ERA", # eradicate ## Ban Commented Code
|
|
||||||
"TRY", # tryceratops ## Exception Handling Style
|
|
||||||
"B", # flake8-bugbear ## Opinionated, Probable-Bug Patterns
|
|
||||||
"N", # pep8-naming
|
|
||||||
"D", # pydocstyle
|
|
||||||
"SIM", # flake8-simplify ## Sanity-Check for Code Simplification
|
|
||||||
"SLF", # flake8-self ## Ban Private Member Access
|
|
||||||
"RUF", # Ruff-specific rules ## Extra Good-To-Have Rules
|
|
||||||
|
|
||||||
## Style
|
|
||||||
"I", # isort ## Force import Sorting
|
|
||||||
"UP", # pyupgrade ## Enforce Upgrade to Newer Python Syntaxes
|
|
||||||
"COM", # flake8-commas ## Enforce Trailing Commas
|
|
||||||
"Q", # flake8-quotes ## Finally - Quoting Style!
|
|
||||||
"PTH", # flake8-use-pathlib ## Enforce pathlib usage
|
|
||||||
"A", # flake8-builtins ## Prevent Builtin Shadowing
|
|
||||||
"C4", # flake9-comprehensions ## Check Compehension Appropriateness
|
|
||||||
"DTZ", # flake8-datetimez ## Ban naive Datetime Creation
|
|
||||||
"EM", # flake8-errmsg ## Check Exception String Formatting
|
|
||||||
"ISC", # flake8-implicit-str-concat ## Enforce Good String Literal Concat
|
|
||||||
"G", # flake8-logging-format ## Enforce Good Logging Practices
|
|
||||||
"INP", # flake8-no-pep420 ## Ban PEP420; Enforce __init__.py.
|
|
||||||
"PIE", # flake8-pie ## Misc Opinionated Checks
|
|
||||||
"T20", # flake8-print ## Ban print()
|
|
||||||
"RSE", # flake8-raise ## Check Niche Exception Raising Pattern
|
|
||||||
"RET", # flake8-return ## Enforce Good Returning
|
|
||||||
"ARG", # flake8-unused-arguments ## Ban Unused Arguments
|
|
||||||
|
|
||||||
# Specific
|
|
||||||
"PT", # flake8-pytest-style ## pytest-Specific Checks
|
|
||||||
]
|
|
||||||
ignore = [
|
|
||||||
"COM812", # Conflicts w/Formatter
|
|
||||||
"ISC001", # Conflicts w/Formatter
|
|
||||||
"Q000", # Conflicts w/Formatter
|
|
||||||
"Q001", # Conflicts w/Formatter
|
|
||||||
"Q002", # Conflicts w/Formatter
|
|
||||||
"Q003", # Conflicts w/Formatter
|
|
||||||
"D206", # Conflicts w/Formatter
|
|
||||||
"B008", # FastAPI uses this for Depends(), Security(), etc. .
|
|
||||||
"E701", # class foo(Parent): pass or if simple: return are perfectly elegant
|
|
||||||
"ERA001", # 'Commented-out code' seems to be just about anything to ruff
|
|
||||||
"F722", # jaxtyping uses type annotations that ruff sees as "syntax error"
|
|
||||||
"N806", # Sometimes we like using types w/uppercase in functions, sue me
|
|
||||||
"RUF001", # We use a lot of unicode, yes, on purpose!
|
|
||||||
|
|
||||||
# Line Length - Controversy Incoming
|
|
||||||
## Hot Take: Let the Formatter Worry about Line Length
|
|
||||||
## - Yes dear reader, I'm with you. Soft wrap can go too far.
|
|
||||||
## - ...but also, sometimes there are real good reasons not to split.
|
|
||||||
## - Ex. I think 'one sentence per line' docstrings are a valid thing.
|
|
||||||
## - Overlong lines tend to be be a code smell anyway
|
|
||||||
## - We'll see if my hot takes survive the week :)
|
|
||||||
"E501", # Let Formatter Worry about Line Length
|
|
||||||
]
|
|
||||||
|
|
||||||
####################
|
|
||||||
# - Tooling: Ruff Sublinters
|
|
||||||
####################
|
|
||||||
[tool.ruff.lint.flake8-bugbear]
|
|
||||||
extend-immutable-calls = []
|
|
||||||
|
|
||||||
[tool.ruff.lint.pycodestyle]
|
|
||||||
ignore-overlong-task-comments = true
|
|
||||||
|
|
||||||
[tool.ruff.lint.pydocstyle]
|
|
||||||
convention = "google"
|
|
||||||
|
|
||||||
[tool.ruff.lint.pylint]
|
|
||||||
max-args = 6
|
|
||||||
|
|
||||||
####################
|
|
||||||
# - Tooling: Ruff Formatter
|
|
||||||
####################
|
|
||||||
[tool.ruff.format]
|
|
||||||
quote-style = "single"
|
|
||||||
indent-style = "tab"
|
|
||||||
docstring-code-format = false
|
|
||||||
|
|
||||||
####################
|
|
||||||
# - Tooling: Commits
|
|
||||||
####################
|
|
||||||
[tool.commitizen]
|
|
||||||
# Specification
|
|
||||||
name = "cz_conventional_commits"
|
|
||||||
version_scheme = "semver2"
|
|
||||||
version_provider = "pep621"
|
|
||||||
tag_format = "v$version"
|
|
||||||
|
|
||||||
# Version Bumping
|
|
||||||
retry_after_failure = true
|
|
||||||
major_version_zero = true
|
|
||||||
update_changelog_on_bump = true
|
|
||||||
|
|
||||||
# Annotations / Signature
|
|
||||||
gpg_sign = true
|
|
||||||
annotated_tag = true
|
|
|
@ -5,11 +5,14 @@ description = "Real-time design and visualization of Maxwell simulations in Blen
|
||||||
authors = [
|
authors = [
|
||||||
{ name = "Sofus Albert Høgsbro Rose", email = "oscillode@sofusrose.com" }
|
{ name = "Sofus Albert Høgsbro Rose", email = "oscillode@sofusrose.com" }
|
||||||
]
|
]
|
||||||
|
maintainers = [
|
||||||
|
{ name = "Sofus Albert Høgsbro Rose", email = "oscillode@sofusrose.com" }
|
||||||
|
]
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"tidy3d==2.7.*",
|
"tidy3d==2.7.*",
|
||||||
"pydantic==2.9.*",
|
"pydantic==2.9.*",
|
||||||
"sympy==1.13.*",
|
"sympy==1.13.*",
|
||||||
"scipy==1.13.*",
|
"scipy==1.14.*",
|
||||||
"trimesh==4.4.*",
|
"trimesh==4.4.*",
|
||||||
"networkx==3.3.*",
|
"networkx==3.3.*",
|
||||||
"rich>=13.8.*",
|
"rich>=13.8.*",
|
||||||
|
@ -28,6 +31,64 @@ readme = "README.md"
|
||||||
requires-python = "~= 3.11"
|
requires-python = "~= 3.11"
|
||||||
license = { text = "AGPL-3.0-or-later" }
|
license = { text = "AGPL-3.0-or-later" }
|
||||||
|
|
||||||
|
[project.urls]
|
||||||
|
Homepage = "https://oscillode.io"
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Blender Extension
|
||||||
|
####################
|
||||||
|
[tool.bl_ext]
|
||||||
|
pretty_name = "Oscillode"
|
||||||
|
blender_version_min = '4.2.0'
|
||||||
|
blender_version_max = '4.2.2'
|
||||||
|
permissions = ["files", "network"]
|
||||||
|
bl_tags = ["Node", "Scene", "Import-Export"]
|
||||||
|
copyright = ["2024 Oscillode Contributors"]
|
||||||
|
|
||||||
|
# Platform Support
|
||||||
|
## Map Valid Blender Platforms -> Required PyPi Platform Tags
|
||||||
|
## Include as few PyPi tags as works on ~everything.
|
||||||
|
[tool.bl_ext.platforms]
|
||||||
|
#windows-amd64 = ['win_amd64']
|
||||||
|
#macos-arm64 = ['macosx_11_0_arm64', 'macosx_12_0_arm64', 'macosx_14_0_arm64']
|
||||||
|
linux-x86_64 = ['manylinux1_x86_64', 'manylinux2014_x86_64', 'manylinux_2_17_x86_64']
|
||||||
|
|
||||||
|
#macos-x86_64 = ['macosx_10_10_x86_64'] ##TODO: Broken
|
||||||
|
|
||||||
|
# Packaging
|
||||||
|
## Path is from the directory containing this file.
|
||||||
|
[tool.bl_ext.packaging]
|
||||||
|
path_builds = 'dev/build'
|
||||||
|
path_wheels = 'dev/wheels'
|
||||||
|
path_local = 'dev/local'
|
||||||
|
init_settings_filename = 'init_settings.toml'
|
||||||
|
|
||||||
|
# "Profiles" -> Affects Initialization Settings
|
||||||
|
## This sets the default extension preferences for different situations.
|
||||||
|
[tool.bl_ext.profiles.dev]
|
||||||
|
use_path_local = true
|
||||||
|
use_log_file = true
|
||||||
|
log_file_path = 'oscillode.log'
|
||||||
|
log_file_level = 'debug'
|
||||||
|
use_log_console = true
|
||||||
|
log_console_level = 'info'
|
||||||
|
|
||||||
|
[tool.bl_ext.profiles.release]
|
||||||
|
use_path_local = false
|
||||||
|
use_log_file = true
|
||||||
|
log_file_path = 'oscillode.log'
|
||||||
|
log_file_level = 'info'
|
||||||
|
use_log_console = true
|
||||||
|
log_console_level = 'warning'
|
||||||
|
|
||||||
|
[tool.bl_ext.profiles.release-debug]
|
||||||
|
use_path_local = false
|
||||||
|
use_log_file = true
|
||||||
|
log_file_path = 'oscillode.log'
|
||||||
|
log_file_level = 'debug'
|
||||||
|
use_log_console = true
|
||||||
|
log_console_level = 'warning'
|
||||||
|
|
||||||
####################
|
####################
|
||||||
# - Tooling: Rye
|
# - Tooling: Rye
|
||||||
####################
|
####################
|
||||||
|
@ -53,7 +114,6 @@ package = false
|
||||||
[tool.ruff]
|
[tool.ruff]
|
||||||
target-version = "py311"
|
target-version = "py311"
|
||||||
line-length = 88
|
line-length = 88
|
||||||
pycodestyle.max-doc-length = 120
|
|
||||||
|
|
||||||
[tool.ruff.lint]
|
[tool.ruff.lint]
|
||||||
task-tags = ["TODO"]
|
task-tags = ["TODO"]
|
||||||
|
@ -128,6 +188,7 @@ ignore = [
|
||||||
extend-immutable-calls = []
|
extend-immutable-calls = []
|
||||||
|
|
||||||
[tool.ruff.lint.pycodestyle]
|
[tool.ruff.lint.pycodestyle]
|
||||||
|
max-doc-length = 120
|
||||||
ignore-overlong-task-comments = true
|
ignore-overlong-task-comments = true
|
||||||
|
|
||||||
[tool.ruff.lint.pydocstyle]
|
[tool.ruff.lint.pydocstyle]
|
||||||
|
|
|
@ -1,109 +0,0 @@
|
||||||
# oscillode
|
|
||||||
# Copyright (C) 2024 oscillode Project Contributors
|
|
||||||
#
|
|
||||||
# This program is free software: you can redistribute it and/or modify
|
|
||||||
# it under the terms of the GNU Affero General Public License as published by
|
|
||||||
# the Free Software Foundation, either version 3 of the License, or
|
|
||||||
# (at your option) any later version.
|
|
||||||
#
|
|
||||||
# This program is distributed in the hope that it will be useful,
|
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
# GNU Affero General Public License for more details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU Affero General Public License
|
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
# blender_maxwell
|
|
||||||
# Copyright (C) 2024 blender_maxwell Project Contributors
|
|
||||||
#
|
|
||||||
# This program is free software: you can redistribute it and/or modify
|
|
||||||
# it under the terms of the GNU Affero General Public License as published by
|
|
||||||
# the Free Software Foundation, either version 3 of the License, or
|
|
||||||
# (at your option) any later version.
|
|
||||||
#
|
|
||||||
# This program is distributed in the hope that it will be useful,
|
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
# GNU Affero General Public License for more details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU Affero General Public License
|
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
import logging
|
|
||||||
import shutil
|
|
||||||
import sys
|
|
||||||
import traceback
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
import bpy
|
|
||||||
|
|
||||||
PATH_SCRIPT = str(Path(__file__).resolve().parent)
|
|
||||||
sys.path.insert(0, str(PATH_SCRIPT))
|
|
||||||
import info # noqa: E402
|
|
||||||
|
|
||||||
sys.path.remove(str(PATH_SCRIPT))
|
|
||||||
|
|
||||||
# Set Bootstrap Log Level
|
|
||||||
## This will be the log-level of both console and file logs, at first...
|
|
||||||
## ...until the addon preferences have been loaded.
|
|
||||||
BOOTSTRAP_LOG_LEVEL = logging.DEBUG
|
|
||||||
|
|
||||||
|
|
||||||
def delete_addon_if_loaded(addon_name: str) -> bool:
|
|
||||||
"""Strongly inspired by Blender's addon_utils.py."""
|
|
||||||
removed_addon = False
|
|
||||||
|
|
||||||
# Check if Python Module is Loaded
|
|
||||||
mod = sys.modules.get(addon_name)
|
|
||||||
# if (mod := sys.modules.get(addon_name)) is None:
|
|
||||||
# ## It could still be loaded-by-default; then, it's in the prefs list
|
|
||||||
# is_loaded_now = False
|
|
||||||
# loads_by_default = addon_name in bpy.context.preferences.addons
|
|
||||||
# else:
|
|
||||||
# ## BL sets __addon_enabled__ on module of enabled addons.
|
|
||||||
# ## BL sets __addon_persistent__ on module of load-by-default addons.
|
|
||||||
# is_loaded_now = getattr(mod, '__addon_enabled__', False)
|
|
||||||
# loads_by_default = getattr(mod, '__addon_persistent__', False)
|
|
||||||
|
|
||||||
# Unregister Modules and Mark Disabled & Non-Persistent
|
|
||||||
## This effectively disables it
|
|
||||||
if mod is not None:
|
|
||||||
removed_addon = True
|
|
||||||
mod.__addon_enabled__ = False
|
|
||||||
mod.__addon_persistent__ = False
|
|
||||||
try:
|
|
||||||
mod.unregister()
|
|
||||||
except BaseException:
|
|
||||||
traceback.print_exc()
|
|
||||||
|
|
||||||
# Remove Addon
|
|
||||||
## Remove Addon from Preferences
|
|
||||||
## - Unsure why addon_utils has a while, but let's trust the process...
|
|
||||||
while addon_name in bpy.context.preferences.addons:
|
|
||||||
addon = bpy.context.preferences.addons.get(addon_name)
|
|
||||||
if addon:
|
|
||||||
bpy.context.preferences.addons.remove(addon)
|
|
||||||
|
|
||||||
## Physically Excise Addon Code
|
|
||||||
for addons_path in bpy.utils.script_paths(subdir='addons'):
|
|
||||||
addon_path = Path(addons_path) / addon_name
|
|
||||||
if addon_path.is_dir():
|
|
||||||
shutil.rmtree(addon_path)
|
|
||||||
|
|
||||||
## Save User Preferences
|
|
||||||
bpy.ops.wm.save_userpref()
|
|
||||||
|
|
||||||
return removed_addon
|
|
||||||
|
|
||||||
|
|
||||||
####################
|
|
||||||
# - Main
|
|
||||||
####################
|
|
||||||
if __name__ == '__main__':
|
|
||||||
if delete_addon_if_loaded(info.ADDON_NAME):
|
|
||||||
bpy.ops.wm.quit_blender()
|
|
||||||
sys.exit(info.STATUS_UNINSTALLED_ADDON)
|
|
||||||
else:
|
|
||||||
bpy.ops.wm.quit_blender()
|
|
||||||
sys.exit(info.STATUS_NOCHANGE_ADDON)
|
|
|
@ -0,0 +1,67 @@
|
||||||
|
# oscillode
|
||||||
|
# Copyright (C) 2024 oscillode Project Contributors
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU Affero General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU Affero General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU Affero General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
# blender_maxwell
|
||||||
|
# Copyright (C) 2024 blender_maxwell Project Contributors
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU Affero General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU Affero General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU Affero General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import bpy
|
||||||
|
|
||||||
|
PATH_SCRIPT = str(Path(__file__).resolve().parent)
|
||||||
|
sys.path.insert(0, str(PATH_SCRIPT))
|
||||||
|
import info # noqa: E402
|
||||||
|
import pack # noqa: E402
|
||||||
|
|
||||||
|
sys.path.remove(str(PATH_SCRIPT))
|
||||||
|
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Main
|
||||||
|
####################
|
||||||
|
if __name__ == '__main__':
|
||||||
|
pass
|
||||||
|
# Uninstall Old Extension
|
||||||
|
# if any(addon_name.endwith(info.ADDON_NAME) and addon_name.startswith('bl_ext.') for addon_name in bpy.context.preferences.addons.keys()):
|
||||||
|
# bpy.ops.extensions.package_uninstall(pkg_id=info.ADDON_NAME)
|
||||||
|
|
||||||
|
# Install New Extension
|
||||||
|
# with pack.zipped_addon(
|
||||||
|
# info.PATH_ADDON_PKG,
|
||||||
|
# info.PATH_ADDON_ZIP,
|
||||||
|
# info.PATH_ROOT / 'pyproject.toml',
|
||||||
|
# info.PATH_ROOT / 'requirements.lock',
|
||||||
|
# initial_log_level=info.BOOTSTRAP_LOG_LEVEL,
|
||||||
|
# ) as path_zipped:
|
||||||
|
# bpy.ops.extensions.package_install_files(
|
||||||
|
# filepath=path_zipped,
|
||||||
|
# enable_on_install=True,
|
||||||
|
#
|
||||||
|
# )
|
|
@ -1,122 +0,0 @@
|
||||||
# oscillode
|
|
||||||
# Copyright (C) 2024 oscillode Project Contributors
|
|
||||||
#
|
|
||||||
# This program is free software: you can redistribute it and/or modify
|
|
||||||
# it under the terms of the GNU Affero General Public License as published by
|
|
||||||
# the Free Software Foundation, either version 3 of the License, or
|
|
||||||
# (at your option) any later version.
|
|
||||||
#
|
|
||||||
# This program is distributed in the hope that it will be useful,
|
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
# GNU Affero General Public License for more details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU Affero General Public License
|
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
# blender_maxwell
|
|
||||||
# Copyright (C) 2024 blender_maxwell Project Contributors
|
|
||||||
#
|
|
||||||
# This program is free software: you can redistribute it and/or modify
|
|
||||||
# it under the terms of the GNU Affero General Public License as published by
|
|
||||||
# the Free Software Foundation, either version 3 of the License, or
|
|
||||||
# (at your option) any later version.
|
|
||||||
#
|
|
||||||
# This program is distributed in the hope that it will be useful,
|
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
# GNU Affero General Public License for more details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU Affero General Public License
|
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
import sys
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
import bpy
|
|
||||||
|
|
||||||
PATH_SCRIPT = str(Path(__file__).resolve().parent)
|
|
||||||
sys.path.insert(0, str(PATH_SCRIPT))
|
|
||||||
import info # noqa: E402
|
|
||||||
import pack # noqa: E402
|
|
||||||
|
|
||||||
sys.path.remove(str(PATH_SCRIPT))
|
|
||||||
|
|
||||||
|
|
||||||
def install_and_enable_addon(addon_name: str, addon_zip: Path) -> None:
|
|
||||||
"""Strongly inspired by Blender's addon_utils.py."""
|
|
||||||
# Check if Addon is Installable
|
|
||||||
if any(
|
|
||||||
[
|
|
||||||
(mod := sys.modules.get(addon_name)) is not None,
|
|
||||||
addon_name in bpy.context.preferences.addons,
|
|
||||||
any(
|
|
||||||
(Path(addon_path) / addon_name).exists()
|
|
||||||
for addon_path in bpy.utils.script_paths(subdir='addons')
|
|
||||||
),
|
|
||||||
]
|
|
||||||
):
|
|
||||||
in_pref_addons = addon_name in bpy.context.preferences.addons
|
|
||||||
existing_files_found = {
|
|
||||||
addon_path: (Path(addon_path) / addon_name).exists()
|
|
||||||
for addon_path in bpy.utils.script_paths(subdir='addons')
|
|
||||||
if (Path(addon_path) / addon_name).exists()
|
|
||||||
}
|
|
||||||
msg = f"Addon (module = '{mod}') is not installable (in preferences.addons: {in_pref_addons}) (existing files found: {existing_files_found})"
|
|
||||||
raise ValueError(msg)
|
|
||||||
|
|
||||||
# Install Addon
|
|
||||||
bpy.ops.preferences.addon_install(filepath=str(addon_zip))
|
|
||||||
if not any(
|
|
||||||
(Path(addon_path) / addon_name).exists()
|
|
||||||
for addon_path in bpy.utils.script_paths(subdir='addons')
|
|
||||||
):
|
|
||||||
msg = f"Couldn't install addon {addon_name}"
|
|
||||||
raise RuntimeError(msg)
|
|
||||||
|
|
||||||
# Enable Addon
|
|
||||||
bpy.ops.preferences.addon_enable(module=addon_name)
|
|
||||||
if addon_name not in bpy.context.preferences.addons:
|
|
||||||
msg = f"Couldn't enable addon {addon_name}"
|
|
||||||
raise RuntimeError(msg)
|
|
||||||
|
|
||||||
# Save User Preferences
|
|
||||||
bpy.ops.wm.save_userpref()
|
|
||||||
|
|
||||||
|
|
||||||
def setup_for_development(
|
|
||||||
addon_name: str,
|
|
||||||
path_addon_dev_deps: Path,
|
|
||||||
path_addon_cache_path: Path | None = None,
|
|
||||||
) -> None:
|
|
||||||
addon_prefs = bpy.context.preferences.addons[addon_name].preferences
|
|
||||||
|
|
||||||
# PyDeps Path
|
|
||||||
addon_prefs.use_default_pydeps_path = False
|
|
||||||
addon_prefs.pydeps_path = path_addon_dev_deps
|
|
||||||
if path_addon_cache_path is not None:
|
|
||||||
addon_prefs.addon_cache_path = path_addon_cache_path
|
|
||||||
|
|
||||||
# Save User Preferences
|
|
||||||
bpy.ops.wm.save_userpref()
|
|
||||||
|
|
||||||
|
|
||||||
####################
|
|
||||||
# - Main
|
|
||||||
####################
|
|
||||||
if __name__ == '__main__':
|
|
||||||
with pack.zipped_addon(
|
|
||||||
info.PATH_ADDON_PKG,
|
|
||||||
info.PATH_ADDON_ZIP,
|
|
||||||
info.PATH_ROOT / 'pyproject.toml',
|
|
||||||
info.PATH_ROOT / 'requirements.lock',
|
|
||||||
initial_log_level=info.BOOTSTRAP_LOG_LEVEL,
|
|
||||||
) as path_zipped:
|
|
||||||
install_and_enable_addon(info.ADDON_NAME, path_zipped)
|
|
||||||
|
|
||||||
setup_for_development(
|
|
||||||
info.ADDON_NAME, info.PATH_ADDON_DEV_DEPS, info.PATH_ADDON_DEV_CACHE
|
|
||||||
)
|
|
||||||
|
|
||||||
bpy.ops.wm.quit_blender()
|
|
||||||
sys.exit(info.STATUS_INSTALLED_ADDON)
|
|
120
scripts/dev.py
120
scripts/dev.py
|
@ -1,120 +0,0 @@
|
||||||
# oscillode
|
|
||||||
# Copyright (C) 2024 oscillode Project Contributors
|
|
||||||
#
|
|
||||||
# This program is free software: you can redistribute it and/or modify
|
|
||||||
# it under the terms of the GNU Affero General Public License as published by
|
|
||||||
# the Free Software Foundation, either version 3 of the License, or
|
|
||||||
# (at your option) any later version.
|
|
||||||
#
|
|
||||||
# This program is distributed in the hope that it will be useful,
|
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
# GNU Affero General Public License for more details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU Affero General Public License
|
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
# blender_maxwell
|
|
||||||
# Copyright (C) 2024 blender_maxwell Project Contributors
|
|
||||||
#
|
|
||||||
# This program is free software: you can redistribute it and/or modify
|
|
||||||
# it under the terms of the GNU Affero General Public License as published by
|
|
||||||
# the Free Software Foundation, either version 3 of the License, or
|
|
||||||
# (at your option) any later version.
|
|
||||||
#
|
|
||||||
# This program is distributed in the hope that it will be useful,
|
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
# GNU Affero General Public License for more details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU Affero General Public License
|
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
# noqa: INP001
|
|
||||||
import os
|
|
||||||
import subprocess
|
|
||||||
import sys
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
import info
|
|
||||||
|
|
||||||
|
|
||||||
####################
|
|
||||||
# - Blender Runner
|
|
||||||
####################
|
|
||||||
def run_blender(
|
|
||||||
py_script: Path | None,
|
|
||||||
load_devfile: bool = False,
|
|
||||||
headless: bool = True,
|
|
||||||
monitor: bool = False,
|
|
||||||
):
|
|
||||||
process = subprocess.Popen(
|
|
||||||
[
|
|
||||||
'blender',
|
|
||||||
*(['--background'] if headless else []),
|
|
||||||
*(
|
|
||||||
[
|
|
||||||
'--python',
|
|
||||||
str(py_script),
|
|
||||||
]
|
|
||||||
if py_script is not None
|
|
||||||
else []
|
|
||||||
),
|
|
||||||
*([info.PATH_ADDON_DEV_BLEND] if load_devfile else []),
|
|
||||||
],
|
|
||||||
env=os.environ | {'PYTHONUNBUFFERED': '1'},
|
|
||||||
stdout=subprocess.PIPE,
|
|
||||||
stderr=subprocess.STDOUT,
|
|
||||||
text=True,
|
|
||||||
)
|
|
||||||
output = []
|
|
||||||
printing_live = monitor
|
|
||||||
|
|
||||||
# Process Real-Time Output
|
|
||||||
for line in iter(process.stdout.readline, b''):
|
|
||||||
if not line:
|
|
||||||
break
|
|
||||||
|
|
||||||
if printing_live:
|
|
||||||
print(line, end='') # noqa: T201
|
|
||||||
elif (
|
|
||||||
info.SIGNAL_START_CLEAN_BLENDER in line
|
|
||||||
# or 'Traceback (most recent call last)' in line
|
|
||||||
):
|
|
||||||
printing_live = True
|
|
||||||
print(''.join(output)) # noqa: T201
|
|
||||||
else:
|
|
||||||
output.append(line)
|
|
||||||
|
|
||||||
# Wait for the process to finish and get the exit code
|
|
||||||
process.wait()
|
|
||||||
return process.returncode, output
|
|
||||||
|
|
||||||
|
|
||||||
####################
|
|
||||||
# - Main
|
|
||||||
####################
|
|
||||||
if __name__ == '__main__':
|
|
||||||
# Uninstall Addon
|
|
||||||
print(f'Blender: Uninstalling "{info.ADDON_NAME}"...')
|
|
||||||
return_code, output = run_blender(info.PATH_BL_DELETE_ADDON, monitor=False)
|
|
||||||
if return_code == info.STATUS_UNINSTALLED_ADDON:
|
|
||||||
print(f'\tBlender: Uninstalled "{info.ADDON_NAME}"')
|
|
||||||
elif return_code == info.STATUS_NOCHANGE_ADDON:
|
|
||||||
print(f'\tBlender: "{info.ADDON_NAME}" Not Installed')
|
|
||||||
|
|
||||||
# Install Addon
|
|
||||||
print(f'Blender: Installing & Enabling "{info.ADDON_NAME}"...')
|
|
||||||
return_code, output = run_blender(info.PATH_BL_INSTALL_ADDON, monitor=False)
|
|
||||||
if return_code == info.STATUS_INSTALLED_ADDON:
|
|
||||||
print(f'\tBlender: Install & Enable "{info.ADDON_NAME}"')
|
|
||||||
else:
|
|
||||||
print(f'\tBlender: "{info.ADDON_NAME}" Not Installed')
|
|
||||||
print(*output, sep='')
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
# Run Addon
|
|
||||||
print(f'Blender: Running "{info.ADDON_NAME}"...')
|
|
||||||
return_code, output = run_blender(
|
|
||||||
None, headless=False, load_devfile=True, monitor=True
|
|
||||||
)
|
|
|
@ -1,46 +0,0 @@
|
||||||
#!/bin/python
|
|
||||||
|
|
||||||
# oscillode
|
|
||||||
# Copyright (C) 2024 oscillode Project Contributors
|
|
||||||
#
|
|
||||||
# This program is free software: you can redistribute it and/or modify
|
|
||||||
# it under the terms of the GNU Affero General Public License as published by
|
|
||||||
# the Free Software Foundation, either version 3 of the License, or
|
|
||||||
# (at your option) any later version.
|
|
||||||
#
|
|
||||||
# This program is distributed in the hope that it will be useful,
|
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
# GNU Affero General Public License for more details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU Affero General Public License
|
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
import sys
|
|
||||||
import subprocess
|
|
||||||
|
|
||||||
import info
|
|
||||||
|
|
||||||
WHEEL_DOWNLOADS_PATH = info.PATH_BUILD / 'downloads'
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
WHEEL_DOWNLOADS_PATH.mkdir(exist_ok=True)
|
|
||||||
subprocess.check_call(
|
|
||||||
[
|
|
||||||
sys.executable,
|
|
||||||
'-m',
|
|
||||||
'pip',
|
|
||||||
'download',
|
|
||||||
'--requirement',
|
|
||||||
str(info.PATH_BUILD / 'requirements.txt'),
|
|
||||||
'--dest',
|
|
||||||
str(WHEEL_DOWNLOADS_PATH),
|
|
||||||
'--require-hashes',
|
|
||||||
'--only-binary',
|
|
||||||
':all:',
|
|
||||||
'--python-version',
|
|
||||||
'3.11',
|
|
||||||
'--platform',
|
|
||||||
'win_amd64',
|
|
||||||
]
|
|
||||||
)
|
|
|
@ -30,68 +30,52 @@
|
||||||
# You should have received a copy of the GNU Affero General Public License
|
# You should have received a copy of the GNU Affero General Public License
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import logging
|
|
||||||
import tomllib
|
import tomllib
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
PATH_ROOT = Path(__file__).resolve().parent.parent.parent
|
|
||||||
PATH_SRC = PATH_ROOT / 'src'
|
|
||||||
|
|
||||||
# Scripts
|
|
||||||
PATH_BL_DELETE_ADDON = PATH_SRC / 'scripts' / 'bl_delete_addon.py'
|
|
||||||
PATH_BL_INSTALL_ADDON = PATH_SRC / 'scripts' / 'bl_install_addon.py'
|
|
||||||
PATH_BL_RUN_DEV = PATH_SRC / 'scripts' / 'bl_run_dev.py'
|
|
||||||
|
|
||||||
# Build Dir
|
|
||||||
PATH_BUILD = PATH_ROOT / 'build'
|
|
||||||
PATH_BUILD.mkdir(exist_ok=True)
|
|
||||||
|
|
||||||
# Dev Dir
|
|
||||||
PATH_DEV = PATH_ROOT / 'dev'
|
|
||||||
PATH_DEV.mkdir(exist_ok=True)
|
|
||||||
|
|
||||||
####################
|
####################
|
||||||
# - BL_RUN stdout Signals
|
# - Basic
|
||||||
####################
|
|
||||||
SIGNAL_START_CLEAN_BLENDER = 'SIGNAL__blender_is_clean'
|
|
||||||
|
|
||||||
####################
|
|
||||||
# - BL_RUN Exit Codes
|
|
||||||
####################
|
|
||||||
STATUS_NOCHANGE_ADDON = 42
|
|
||||||
STATUS_UNINSTALLED_ADDON = 42
|
|
||||||
STATUS_INSTALLED_ADDON = 69
|
|
||||||
STATUS_NOINSTALL_ADDON = 68
|
|
||||||
|
|
||||||
####################
|
|
||||||
# - Addon Information
|
|
||||||
####################
|
####################
|
||||||
|
PATH_ROOT = Path(__file__).resolve().parent.parent
|
||||||
with (PATH_ROOT / 'pyproject.toml').open('rb') as f:
|
with (PATH_ROOT / 'pyproject.toml').open('rb') as f:
|
||||||
PROJ_SPEC = tomllib.load(f)
|
PROJ_SPEC = tomllib.load(f)
|
||||||
|
|
||||||
ADDON_NAME = PROJ_SPEC['project']['name']
|
REQ_PYTHON_VERSION = PROJ_SPEC['project']['requires-python'].replace('~= ', '')
|
||||||
ADDON_VERSION = PROJ_SPEC['project']['version']
|
|
||||||
|
|
||||||
####################
|
####################
|
||||||
# - Packaging Information
|
# - Paths
|
||||||
####################
|
####################
|
||||||
PATH_ADDON_PKG = PATH_ROOT / 'src' / ADDON_NAME
|
def proc_path(p_str: str) -> Path:
|
||||||
PATH_ADDON_ZIP = PATH_ROOT / 'build' / (ADDON_NAME + '__' + ADDON_VERSION + '.zip')
|
return PATH_ROOT / Path(*p_str.split('/'))
|
||||||
|
|
||||||
PATH_ADDON_BLEND_STARTER = PATH_ADDON_PKG / 'blenders' / 'starter.blend'
|
|
||||||
|
|
||||||
# Set Bootstrap Log Level
|
PATH_PKG = PATH_ROOT / PROJ_SPEC['project']['name']
|
||||||
## This will be the log-level of both console and file logs, at first...
|
|
||||||
## ...until the addon preferences have been loaded.
|
PATH_DEV = PATH_ROOT / 'dev'
|
||||||
BOOTSTRAP_LOG_LEVEL = logging.DEBUG
|
PATH_DEV.mkdir(exist_ok=True)
|
||||||
BOOTSTRAP_LOG_LEVEL_FILENAME = '.bootstrap_log_level'
|
|
||||||
|
# Retrieve Build Path
|
||||||
|
## Extension ZIP files will be placed here after packaging.
|
||||||
|
PATH_BUILD = proc_path(PROJ_SPEC['tool']['bl_ext']['packaging']['path_builds'])
|
||||||
|
PATH_BUILD.mkdir(exist_ok=True)
|
||||||
|
|
||||||
|
# Retrieve Wheels Path
|
||||||
|
## Dependency wheels will be downloaded to here, then copied into the extension packages.
|
||||||
|
PATH_WHEELS = proc_path(PROJ_SPEC['tool']['bl_ext']['packaging']['path_wheels'])
|
||||||
|
PATH_WHEELS.mkdir(exist_ok=True)
|
||||||
|
|
||||||
|
# Retrieve Local Cache Path
|
||||||
|
## During development, files such as logs will be placed here instead of in the extension's user path.
|
||||||
|
## This is essentially a debugging convenience.
|
||||||
|
PATH_LOCAL = proc_path(PROJ_SPEC['tool']['bl_ext']['packaging']['path_local'])
|
||||||
|
PATH_LOCAL.mkdir(exist_ok=True)
|
||||||
|
|
||||||
# Install the ZIPped Addon
|
|
||||||
####################
|
####################
|
||||||
# - Development Information
|
# - Computed Paths
|
||||||
####################
|
####################
|
||||||
PATH_ADDON_DEV_BLEND = PATH_DEV / 'demo.blend'
|
# Compute Current ZIP to Build
|
||||||
|
## The concrete path to the file that will be packed and installed.
|
||||||
PATH_ADDON_DEV_DEPS = PATH_DEV / '.cached-dev-dependencies'
|
PATH_ZIP = PATH_BUILD / (
|
||||||
PATH_ADDON_DEV_CACHE = PATH_DEV / '.dev-addon-cache'
|
PROJ_SPEC['project']['name'] + '__' + PROJ_SPEC['project']['version'] + '.zip'
|
||||||
PATH_ADDON_DEV_DEPS.mkdir(exist_ok=True)
|
)
|
||||||
|
|
240
scripts/pack.py
240
scripts/pack.py
|
@ -30,37 +30,149 @@
|
||||||
# You should have received a copy of the GNU Affero General Public License
|
# You should have received a copy of the GNU Affero General Public License
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import sys
|
||||||
import contextlib
|
import contextlib
|
||||||
import logging
|
import logging
|
||||||
import tempfile
|
import tempfile
|
||||||
import typing as typ
|
import typing as typ
|
||||||
import zipfile
|
import zipfile
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
import itertools
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
import tomli_w
|
||||||
|
|
||||||
import info
|
import info
|
||||||
|
|
||||||
LogLevel: typ.TypeAlias = int
|
LogLevel: typ.TypeAlias = int
|
||||||
|
|
||||||
_PROJ_VERSION_STR = str(
|
BL_EXT__MANIFEST_FILENAME = 'blender_manifest.toml'
|
||||||
tuple(int(el) for el in info.PROJ_SPEC['project']['version'].split('.'))
|
BL_EXT__SCHEMA_VERSION = '1.0.0'
|
||||||
)
|
BL_EXT__TYPE = 'add-on'
|
||||||
_PROJ_DESC_STR = info.PROJ_SPEC['project']['description']
|
|
||||||
|
|
||||||
BL_INFO_REPLACEMENTS = {
|
####################
|
||||||
"'version': (0, 0, 0),": f"'version': {_PROJ_VERSION_STR},",
|
# - Generate Manifest
|
||||||
"'description': 'Placeholder',": f"'description': '{_PROJ_DESC_STR}',",
|
####################
|
||||||
|
# See https://docs.blender.org/manual/en/4.2/extensions/getting_started.html
|
||||||
|
# See https://packaging.python.org/en/latest/guides/writing-pyproject-toml
|
||||||
|
## TODO: More validation and such.
|
||||||
|
_FIRST_MAINTAINER = info.PROJ_SPEC['project']['maintainers'][0]
|
||||||
|
_SPDX_LICENSE_NAME = info.PROJ_SPEC['project']['license']['text']
|
||||||
|
|
||||||
|
BL_EXT_MANIFEST = {
|
||||||
|
'schema_version': BL_EXT__SCHEMA_VERSION,
|
||||||
|
# Basics
|
||||||
|
'id': info.PROJ_SPEC['project']['name'],
|
||||||
|
'name': info.PROJ_SPEC['tool']['bl_ext']['pretty_name'],
|
||||||
|
'version': info.PROJ_SPEC['project']['version'],
|
||||||
|
'tagline': info.PROJ_SPEC['project']['description'],
|
||||||
|
'maintainer': f'{_FIRST_MAINTAINER["name"]} <{_FIRST_MAINTAINER["email"]}>',
|
||||||
|
# Blender Compatibility
|
||||||
|
'type': BL_EXT__TYPE,
|
||||||
|
'blender_version_min': info.PROJ_SPEC['tool']['bl_ext']['blender_version_min'],
|
||||||
|
'blender_version_max': info.PROJ_SPEC['tool']['bl_ext']['blender_version_max'],
|
||||||
|
'platforms': list(info.PROJ_SPEC['tool']['bl_ext']['platforms'].keys()),
|
||||||
|
# OS/Arch Compatibility
|
||||||
|
## See https://docs.blender.org/manual/en/dev/extensions/python_wheels.html
|
||||||
|
'wheels': [
|
||||||
|
f'./wheels/{wheel_path.name}' for wheel_path in info.PATH_WHEELS.iterdir()
|
||||||
|
],
|
||||||
|
# Permissions
|
||||||
|
## * "files" (for access of any filesystem operations)
|
||||||
|
## * "network" (for internet access)
|
||||||
|
## * "clipboard" (to read and/or write the system clipboard)
|
||||||
|
## * "camera" (to capture photos and videos)
|
||||||
|
## * "microphone" (to capture audio)
|
||||||
|
'permissions': info.PROJ_SPEC['tool']['bl_ext']['permissions'],
|
||||||
|
# Addon Tags
|
||||||
|
'tags': info.PROJ_SPEC['tool']['bl_ext']['bl_tags'],
|
||||||
|
'license': [f'SPDX:{_SPDX_LICENSE_NAME}'],
|
||||||
|
'copyright': info.PROJ_SPEC['tool']['bl_ext']['copyright'],
|
||||||
|
'website': info.PROJ_SPEC['project']['urls']['Homepage'],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@contextlib.contextmanager
|
####################
|
||||||
def zipped_addon( # noqa: PLR0913
|
# - Generate Init Settings
|
||||||
path_addon_pkg: Path,
|
####################
|
||||||
path_addon_zip: Path,
|
def generate_init_settings_dict(profile: str) -> dict:
|
||||||
path_pyproject_toml: Path,
|
profile_settings = info.PROJ_SPEC['tool']['bl_ext']['profiles'][profile]
|
||||||
path_requirements_lock: Path,
|
|
||||||
initial_log_level: LogLevel = logging.INFO,
|
if profile_settings['use_path_local']:
|
||||||
|
base_path = info.PATH_LOCAL
|
||||||
|
else:
|
||||||
|
base_path = Path('{USER}')
|
||||||
|
|
||||||
|
log_levels = {
|
||||||
|
None: logging.NOTSET,
|
||||||
|
'debug': logging.DEBUG,
|
||||||
|
'info': logging.INFO,
|
||||||
|
'warning': logging.WARNING,
|
||||||
|
'error': logging.ERROR,
|
||||||
|
'critical': logging.CRITICAL,
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
# File Logging
|
||||||
|
'use_log_file': profile_settings['use_log_file'],
|
||||||
|
'log_file_path': str(base_path / profile_settings['log_file_path']),
|
||||||
|
'log_file_level': log_levels[profile_settings['log_file_level']],
|
||||||
|
# Console Logging
|
||||||
|
'use_log_console': profile_settings['use_log_console'],
|
||||||
|
'log_console_level': log_levels[profile_settings['log_console_level']],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Wheel Downloader
|
||||||
|
####################
|
||||||
|
def download_wheels() -> dict:
|
||||||
|
with tempfile.NamedTemporaryFile(delete=False) as f_reqlock:
|
||||||
|
reqlock_str = subprocess.check_output(['uv', 'export', '--no-dev'])
|
||||||
|
f_reqlock.write(reqlock_str)
|
||||||
|
reqlock_path = Path(f_reqlock.name)
|
||||||
|
|
||||||
|
for platform, pypi_platform_tags in info.PROJ_SPEC['tool']['bl_ext'][
|
||||||
|
'platforms'
|
||||||
|
].items():
|
||||||
|
print(f'[{platform}] Downloading Wheels...')
|
||||||
|
print()
|
||||||
|
print()
|
||||||
|
platform_constraints = list(
|
||||||
|
itertools.chain.from_iterable(
|
||||||
|
[
|
||||||
|
['--platform', pypi_platform_tag]
|
||||||
|
for pypi_platform_tag in pypi_platform_tags
|
||||||
|
]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
subprocess.check_call(
|
||||||
|
[
|
||||||
|
sys.executable,
|
||||||
|
'-m',
|
||||||
|
'pip',
|
||||||
|
'download',
|
||||||
|
'--requirement',
|
||||||
|
str(reqlock_path),
|
||||||
|
'--dest',
|
||||||
|
str(info.PATH_WHEELS),
|
||||||
|
'--require-hashes',
|
||||||
|
'--only-binary',
|
||||||
|
':all:',
|
||||||
|
'--python-version',
|
||||||
|
info.REQ_PYTHON_VERSION,
|
||||||
|
]
|
||||||
|
+ platform_constraints
|
||||||
|
)
|
||||||
|
print()
|
||||||
|
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Pack Extension to ZIP
|
||||||
|
####################
|
||||||
|
def pack_bl_extension( # noqa: PLR0913
|
||||||
|
profile: str,
|
||||||
replace_if_exists: bool = False,
|
replace_if_exists: bool = False,
|
||||||
remove_after_close: bool = True,
|
|
||||||
) -> typ.Iterator[Path]:
|
) -> typ.Iterator[Path]:
|
||||||
"""Context manager exposing a folder as a (temporary) zip file.
|
"""Context manager exposing a folder as a (temporary) zip file.
|
||||||
|
|
||||||
|
@ -72,81 +184,51 @@ def zipped_addon( # noqa: PLR0913
|
||||||
The .zip file is deleted afterwards, unless `remove_after_close` is specified.
|
The .zip file is deleted afterwards, unless `remove_after_close` is specified.
|
||||||
"""
|
"""
|
||||||
# Delete Existing ZIP (maybe)
|
# Delete Existing ZIP (maybe)
|
||||||
if path_addon_zip.is_file():
|
if info.PATH_ZIP.is_file():
|
||||||
if replace_if_exists:
|
if replace_if_exists:
|
||||||
msg = 'File already exists where ZIP would be made'
|
msg = 'File already exists where extension ZIP would be generated ({info.PATH_ZIP})'
|
||||||
raise ValueError(msg)
|
raise ValueError(msg)
|
||||||
path_addon_zip.unlink()
|
info.PATH_ZIP.unlink()
|
||||||
|
|
||||||
|
init_settings: dict = generate_init_settings_dict(profile)
|
||||||
|
|
||||||
# Create New ZIP file of the addon directory
|
# Create New ZIP file of the addon directory
|
||||||
with zipfile.ZipFile(path_addon_zip, 'w', zipfile.ZIP_DEFLATED) as f_zip:
|
with zipfile.ZipFile(info.PATH_ZIP, 'w', zipfile.ZIP_DEFLATED) as f_zip:
|
||||||
|
# Write Blender Extension Manifest
|
||||||
|
print('Writing Blender Extension Manifest...')
|
||||||
|
f_zip.writestr(BL_EXT__MANIFEST_FILENAME, tomli_w.dumps(BL_EXT_MANIFEST))
|
||||||
|
|
||||||
|
# Write Init Settings
|
||||||
|
print('Writing Init Settings...')
|
||||||
|
f_zip.writestr(
|
||||||
|
info.PROJ_SPEC['tool']['bl_ext']['packaging']['init_settings_filename'],
|
||||||
|
tomli_w.dumps(init_settings),
|
||||||
|
)
|
||||||
|
|
||||||
# Install Addon Files @ /*
|
# Install Addon Files @ /*
|
||||||
for file_to_zip in path_addon_pkg.rglob('*'):
|
print('Writing Addon Files Settings...')
|
||||||
# Dynamically Alter 'bl_info' in __init__.py
|
for file_to_zip in info.PATH_PKG.rglob('*'):
|
||||||
## This is the only way to propagate ex. version information
|
f_zip.write(file_to_zip, file_to_zip.relative_to(info.PATH_PKG.parent))
|
||||||
if str(file_to_zip.relative_to(path_addon_pkg)) == '__init__.py':
|
|
||||||
with (
|
|
||||||
file_to_zip.open('r') as f_init,
|
|
||||||
tempfile.NamedTemporaryFile(mode='w') as f_tmp,
|
|
||||||
):
|
|
||||||
initpy = f_init.read()
|
|
||||||
for (
|
|
||||||
to_replace,
|
|
||||||
replacement,
|
|
||||||
) in BL_INFO_REPLACEMENTS.items():
|
|
||||||
initpy = initpy.replace(to_replace, replacement)
|
|
||||||
f_tmp.write(initpy)
|
|
||||||
|
|
||||||
# Write to ZIP
|
# Install Wheels @ /wheels/*
|
||||||
f_zip.writestr(
|
print('Writing Wheels...')
|
||||||
str(file_to_zip.relative_to(path_addon_pkg.parent)),
|
for wheel_to_zip in info.PATH_WHEELS.rglob('*'):
|
||||||
initpy,
|
f_zip.write(wheel_to_zip, Path('wheels') / wheel_to_zip.name)
|
||||||
)
|
|
||||||
|
|
||||||
# Write File to Zip
|
|
||||||
else:
|
|
||||||
f_zip.write(file_to_zip, file_to_zip.relative_to(path_addon_pkg.parent))
|
|
||||||
|
|
||||||
# Install pyproject.toml @ /pyproject.toml of Addon
|
|
||||||
f_zip.write(
|
|
||||||
path_pyproject_toml,
|
|
||||||
str(Path(path_addon_pkg.name) / Path(path_pyproject_toml.name)),
|
|
||||||
)
|
|
||||||
|
|
||||||
# Install requirements.lock @ /requirements.txt of Addon
|
|
||||||
f_zip.write(
|
|
||||||
path_requirements_lock,
|
|
||||||
str(Path(path_addon_pkg.name) / Path(path_requirements_lock.name)),
|
|
||||||
)
|
|
||||||
|
|
||||||
# Set Initial Log-Level
|
|
||||||
f_zip.writestr(
|
|
||||||
str(Path(path_addon_pkg.name) / info.BOOTSTRAP_LOG_LEVEL_FILENAME),
|
|
||||||
str(initial_log_level),
|
|
||||||
)
|
|
||||||
|
|
||||||
# Delete the ZIP
|
# Delete the ZIP
|
||||||
try:
|
print('Packed Blender Extension!')
|
||||||
yield path_addon_zip
|
|
||||||
finally:
|
|
||||||
if remove_after_close:
|
|
||||||
path_addon_zip.unlink()
|
|
||||||
|
|
||||||
|
|
||||||
####################
|
####################
|
||||||
# - Run Blender w/Clean Addon Reinstall
|
# - Run Blender w/Clean Addon Reinstall
|
||||||
####################
|
####################
|
||||||
def main():
|
|
||||||
with zipped_addon(
|
|
||||||
path_addon_pkg=info.PATH_ADDON_PKG,
|
|
||||||
path_addon_zip=info.PATH_ADDON_ZIP,
|
|
||||||
path_pyproject_toml=info.PATH_ROOT / 'pyproject.toml',
|
|
||||||
path_requirements_lock=info.PATH_ROOT / 'requirements.lock',
|
|
||||||
replace_if_exists=True,
|
|
||||||
remove_after_close=False,
|
|
||||||
):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
main()
|
if not list(info.PATH_WHEELS.iterdir()) or '--download-wheels' in sys.argv:
|
||||||
|
download_wheels()
|
||||||
|
|
||||||
|
profile = sys.argv[1]
|
||||||
|
if sys.argv[1] in ['dev', 'release', 'release-debug']:
|
||||||
|
pack_bl_extension(profile)
|
||||||
|
else:
|
||||||
|
msg = f'Packaging profile "{profile}" is invalid. Refer to source of pack.py for more information'
|
||||||
|
raise ValueError(msg)
|
||||||
|
|
42
uv.lock
42
uv.lock
|
@ -870,7 +870,7 @@ requires-dist = [
|
||||||
{ name = "pydantic-tensor", specifier = ">=0.2" },
|
{ name = "pydantic-tensor", specifier = ">=0.2" },
|
||||||
{ name = "rich", specifier = ">=13.8" },
|
{ name = "rich", specifier = ">=13.8" },
|
||||||
{ name = "rtree", specifier = "==1.3.*" },
|
{ name = "rtree", specifier = "==1.3.*" },
|
||||||
{ name = "scipy", specifier = "==1.13.*" },
|
{ name = "scipy", specifier = "==1.14.*" },
|
||||||
{ name = "seaborn", extras = ["stats"], specifier = ">=0.13" },
|
{ name = "seaborn", extras = ["stats"], specifier = ">=0.13" },
|
||||||
{ name = "sympy", specifier = "==1.13.*" },
|
{ name = "sympy", specifier = "==1.13.*" },
|
||||||
{ name = "tidy3d", specifier = "==2.7.*" },
|
{ name = "tidy3d", specifier = "==2.7.*" },
|
||||||
|
@ -1324,25 +1324,37 @@ wheels = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "scipy"
|
name = "scipy"
|
||||||
version = "1.13.1"
|
version = "1.14.1"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "numpy" },
|
{ name = "numpy" },
|
||||||
]
|
]
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/ae/00/48c2f661e2816ccf2ecd77982f6605b2950afe60f60a52b4cbbc2504aa8f/scipy-1.13.1.tar.gz", hash = "sha256:095a87a0312b08dfd6a6155cbbd310a8c51800fc931b8c0b84003014b874ed3c", size = 57210720 }
|
sdist = { url = "https://files.pythonhosted.org/packages/62/11/4d44a1f274e002784e4dbdb81e0ea96d2de2d1045b2132d5af62cc31fd28/scipy-1.14.1.tar.gz", hash = "sha256:5a275584e726026a5699459aa72f828a610821006228e841b94275c4a7c08417", size = 58620554 }
|
||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/b4/15/4a4bb1b15bbd2cd2786c4f46e76b871b28799b67891f23f455323a0cdcfb/scipy-1.13.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:27e52b09c0d3a1d5b63e1105f24177e544a222b43611aaf5bc44d4a0979e32f9", size = 39333805 },
|
{ url = "https://files.pythonhosted.org/packages/b2/ab/070ccfabe870d9f105b04aee1e2860520460ef7ca0213172abfe871463b9/scipy-1.14.1-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:2da0469a4ef0ecd3693761acbdc20f2fdeafb69e6819cc081308cc978153c675", size = 39076999 },
|
||||||
{ url = "https://files.pythonhosted.org/packages/ba/92/42476de1af309c27710004f5cdebc27bec62c204db42e05b23a302cb0c9a/scipy-1.13.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:54f430b00f0133e2224c3ba42b805bfd0086fe488835effa33fa291561932326", size = 30317687 },
|
{ url = "https://files.pythonhosted.org/packages/a7/c5/02ac82f9bb8f70818099df7e86c3ad28dae64e1347b421d8e3adf26acab6/scipy-1.14.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:c0ee987efa6737242745f347835da2cc5bb9f1b42996a4d97d5c7ff7928cb6f2", size = 29894570 },
|
||||||
{ url = "https://files.pythonhosted.org/packages/80/ba/8be64fe225360a4beb6840f3cbee494c107c0887f33350d0a47d55400b01/scipy-1.13.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e89369d27f9e7b0884ae559a3a956e77c02114cc60a6058b4e5011572eea9299", size = 33694638 },
|
{ url = "https://files.pythonhosted.org/packages/ed/05/7f03e680cc5249c4f96c9e4e845acde08eb1aee5bc216eff8a089baa4ddb/scipy-1.14.1-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:3a1b111fac6baec1c1d92f27e76511c9e7218f1695d61b59e05e0fe04dc59617", size = 23103567 },
|
||||||
{ url = "https://files.pythonhosted.org/packages/36/07/035d22ff9795129c5a847c64cb43c1fa9188826b59344fee28a3ab02e283/scipy-1.13.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a78b4b3345f1b6f68a763c6e25c0c9a23a9fd0f39f5f3d200efe8feda560a5fa", size = 38569931 },
|
{ url = "https://files.pythonhosted.org/packages/5e/fc/9f1413bef53171f379d786aabc104d4abeea48ee84c553a3e3d8c9f96a9c/scipy-1.14.1-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:8475230e55549ab3f207bff11ebfc91c805dc3463ef62eda3ccf593254524ce8", size = 25499102 },
|
||||||
{ url = "https://files.pythonhosted.org/packages/d9/10/f9b43de37e5ed91facc0cfff31d45ed0104f359e4f9a68416cbf4e790241/scipy-1.13.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:45484bee6d65633752c490404513b9ef02475b4284c4cfab0ef946def50b3f59", size = 38838145 },
|
{ url = "https://files.pythonhosted.org/packages/c2/4b/b44bee3c2ddc316b0159b3d87a3d467ef8d7edfd525e6f7364a62cd87d90/scipy-1.14.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:278266012eb69f4a720827bdd2dc54b2271c97d84255b2faaa8f161a158c3b37", size = 35586346 },
|
||||||
{ url = "https://files.pythonhosted.org/packages/4a/48/4513a1a5623a23e95f94abd675ed91cfb19989c58e9f6f7d03990f6caf3d/scipy-1.13.1-cp311-cp311-win_amd64.whl", hash = "sha256:5713f62f781eebd8d597eb3f88b8bf9274e79eeabf63afb4a737abc6c84ad37b", size = 46196227 },
|
{ url = "https://files.pythonhosted.org/packages/93/6b/701776d4bd6bdd9b629c387b5140f006185bd8ddea16788a44434376b98f/scipy-1.14.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fef8c87f8abfb884dac04e97824b61299880c43f4ce675dd2cbeadd3c9b466d2", size = 41165244 },
|
||||||
{ url = "https://files.pythonhosted.org/packages/f2/7b/fb6b46fbee30fc7051913068758414f2721003a89dd9a707ad49174e3843/scipy-1.13.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5d72782f39716b2b3509cd7c33cdc08c96f2f4d2b06d51e52fb45a19ca0c86a1", size = 39357301 },
|
{ url = "https://files.pythonhosted.org/packages/06/57/e6aa6f55729a8f245d8a6984f2855696c5992113a5dc789065020f8be753/scipy-1.14.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b05d43735bb2f07d689f56f7b474788a13ed8adc484a85aa65c0fd931cf9ccd2", size = 42817917 },
|
||||||
{ url = "https://files.pythonhosted.org/packages/dc/5a/2043a3bde1443d94014aaa41e0b50c39d046dda8360abd3b2a1d3f79907d/scipy-1.13.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:017367484ce5498445aade74b1d5ab377acdc65e27095155e448c88497755a5d", size = 30363348 },
|
{ url = "https://files.pythonhosted.org/packages/ea/c2/5ecadc5fcccefaece775feadcd795060adf5c3b29a883bff0e678cfe89af/scipy-1.14.1-cp311-cp311-win_amd64.whl", hash = "sha256:716e389b694c4bb564b4fc0c51bc84d381735e0d39d3f26ec1af2556ec6aad94", size = 44781033 },
|
||||||
{ url = "https://files.pythonhosted.org/packages/e7/cb/26e4a47364bbfdb3b7fb3363be6d8a1c543bcd70a7753ab397350f5f189a/scipy-1.13.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:949ae67db5fa78a86e8fa644b9a6b07252f449dcf74247108c50e1d20d2b4627", size = 33406062 },
|
{ url = "https://files.pythonhosted.org/packages/c0/04/2bdacc8ac6387b15db6faa40295f8bd25eccf33f1f13e68a72dc3c60a99e/scipy-1.14.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:631f07b3734d34aced009aaf6fedfd0eb3498a97e581c3b1e5f14a04164a456d", size = 39128781 },
|
||||||
{ url = "https://files.pythonhosted.org/packages/88/ab/6ecdc526d509d33814835447bbbeedbebdec7cca46ef495a61b00a35b4bf/scipy-1.13.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:de3ade0e53bc1f21358aa74ff4830235d716211d7d077e340c7349bc3542e884", size = 38218311 },
|
{ url = "https://files.pythonhosted.org/packages/c8/53/35b4d41f5fd42f5781dbd0dd6c05d35ba8aa75c84ecddc7d44756cd8da2e/scipy-1.14.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:af29a935803cc707ab2ed7791c44288a682f9c8107bc00f0eccc4f92c08d6e07", size = 29939542 },
|
||||||
{ url = "https://files.pythonhosted.org/packages/0b/00/9f54554f0f8318100a71515122d8f4f503b1a2c4b4cfab3b4b68c0eb08fa/scipy-1.13.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2ac65fb503dad64218c228e2dc2d0a0193f7904747db43014645ae139c8fad16", size = 38442493 },
|
{ url = "https://files.pythonhosted.org/packages/66/67/6ef192e0e4d77b20cc33a01e743b00bc9e68fb83b88e06e636d2619a8767/scipy-1.14.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:2843f2d527d9eebec9a43e6b406fb7266f3af25a751aa91d62ff416f54170bc5", size = 23148375 },
|
||||||
{ url = "https://files.pythonhosted.org/packages/3e/df/963384e90733e08eac978cd103c34df181d1fec424de383cdc443f418dd4/scipy-1.13.1-cp312-cp312-win_amd64.whl", hash = "sha256:cdd7dacfb95fea358916410ec61bbc20440f7860333aee6d882bb8046264e949", size = 45910955 },
|
{ url = "https://files.pythonhosted.org/packages/f6/32/3a6dedd51d68eb7b8e7dc7947d5d841bcb699f1bf4463639554986f4d782/scipy-1.14.1-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:eb58ca0abd96911932f688528977858681a59d61a7ce908ffd355957f7025cfc", size = 25578573 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f0/5a/efa92a58dc3a2898705f1dc9dbaf390ca7d4fba26d6ab8cfffb0c72f656f/scipy-1.14.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:30ac8812c1d2aab7131a79ba62933a2a76f582d5dbbc695192453dae67ad6310", size = 35319299 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/8e/ee/8a26858ca517e9c64f84b4c7734b89bda8e63bec85c3d2f432d225bb1886/scipy-1.14.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f9ea80f2e65bdaa0b7627fb00cbeb2daf163caa015e59b7516395fe3bd1e066", size = 40849331 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a5/cd/06f72bc9187840f1c99e1a8750aad4216fc7dfdd7df46e6280add14b4822/scipy-1.14.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:edaf02b82cd7639db00dbff629995ef185c8df4c3ffa71a5562a595765a06ce1", size = 42544049 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/aa/7d/43ab67228ef98c6b5dd42ab386eae2d7877036970a0d7e3dd3eb47a0d530/scipy-1.14.1-cp312-cp312-win_amd64.whl", hash = "sha256:2ff38e22128e6c03ff73b6bb0f85f897d2362f8c052e3b8ad00532198fbdae3f", size = 44521212 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/50/ef/ac98346db016ff18a6ad7626a35808f37074d25796fd0234c2bb0ed1e054/scipy-1.14.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1729560c906963fc8389f6aac023739ff3983e727b1a4d87696b7bf108316a79", size = 39091068 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b9/cc/70948fe9f393b911b4251e96b55bbdeaa8cca41f37c26fd1df0232933b9e/scipy-1.14.1-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:4079b90df244709e675cdc8b93bfd8a395d59af40b72e339c2287c91860deb8e", size = 29875417 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/3b/2e/35f549b7d231c1c9f9639f9ef49b815d816bf54dd050da5da1c11517a218/scipy-1.14.1-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:e0cf28db0f24a38b2a0ca33a85a54852586e43cf6fd876365c86e0657cfe7d73", size = 23084508 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/3f/d6/b028e3f3e59fae61fb8c0f450db732c43dd1d836223a589a8be9f6377203/scipy-1.14.1-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:0c2f95de3b04e26f5f3ad5bb05e74ba7f68b837133a4492414b3afd79dfe540e", size = 25503364 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a7/2f/6c142b352ac15967744d62b165537a965e95d557085db4beab2a11f7943b/scipy-1.14.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b99722ea48b7ea25e8e015e8341ae74624f72e5f21fc2abd45f3a93266de4c5d", size = 35292639 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/56/46/2449e6e51e0d7c3575f289f6acb7f828938eaab8874dbccfeb0cd2b71a27/scipy-1.14.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5149e3fd2d686e42144a093b206aef01932a0059c2a33ddfa67f5f035bdfe13e", size = 40798288 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/32/cd/9d86f7ed7f4497c9fd3e39f8918dd93d9f647ba80d7e34e4946c0c2d1a7c/scipy-1.14.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e4f5a7c49323533f9103d4dacf4e4f07078f360743dec7f7596949149efeec06", size = 42524647 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f5/1b/6ee032251bf4cdb0cc50059374e86a9f076308c1512b61c4e003e241efb7/scipy-1.14.1-cp313-cp313-win_amd64.whl", hash = "sha256:baff393942b550823bfce952bb62270ee17504d02a1801d7fd0719534dfb9c84", size = 44469524 },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
Loading…
Reference in New Issue