blender-pyutils 2025.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- blender_pyutils-2025.2/.gitignore +15 -0
- blender_pyutils-2025.2/.gitlab-ci.yml +63 -0
- blender_pyutils-2025.2/LICENSE.txt +9 -0
- blender_pyutils-2025.2/PKG-INFO +191 -0
- blender_pyutils-2025.2/README.md +164 -0
- blender_pyutils-2025.2/codemeta.json +69 -0
- blender_pyutils-2025.2/doc/Makefile +20 -0
- blender_pyutils-2025.2/doc/make.bat +35 -0
- blender_pyutils-2025.2/doc/requirements.txt +3 -0
- blender_pyutils-2025.2/doc/source/conf.py +56 -0
- blender_pyutils-2025.2/doc/source/index.rst +16 -0
- blender_pyutils-2025.2/doc/source/readme.md +2 -0
- blender_pyutils-2025.2/pyproject.toml +64 -0
- blender_pyutils-2025.2/scripts/blender-pip_install.sh +95 -0
- blender_pyutils-2025.2/scripts/build_doc.sh +72 -0
- blender_pyutils-2025.2/src/blender_pyutils/__init__.py +2 -0
- blender_pyutils-2025.2/src/blender_pyutils/blender_wrapper.py +229 -0
- blender_pyutils-2025.2/src/blender_pyutils/common.py +5 -0
- blender_pyutils-2025.2/src/blender_pyutils/exceptions.py +18 -0
- blender_pyutils-2025.2/src/blender_pyutils/main.py +142 -0
- blender_pyutils-2025.2/src/blender_pyutils/utils.py +29 -0
- blender_pyutils-2025.2/src/blender_pyutils/version.py +9 -0
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
pages:
|
|
2
|
+
artifacts:
|
|
3
|
+
paths:
|
|
4
|
+
- public
|
|
5
|
+
image: sphinxdoc/sphinx
|
|
6
|
+
only:
|
|
7
|
+
- main
|
|
8
|
+
script:
|
|
9
|
+
- bash ./scripts/build_doc.sh
|
|
10
|
+
stage: deploy
|
|
11
|
+
tags: &id001
|
|
12
|
+
- ci.inria.fr
|
|
13
|
+
- linux
|
|
14
|
+
- small
|
|
15
|
+
pylint:
|
|
16
|
+
artifacts:
|
|
17
|
+
paths:
|
|
18
|
+
- ./pylint/
|
|
19
|
+
before_script:
|
|
20
|
+
- pip install -U pip
|
|
21
|
+
- pip install -U uv
|
|
22
|
+
- uv venv venv
|
|
23
|
+
- source venv/bin/activate
|
|
24
|
+
- uv pip install -U pylint anybadge .
|
|
25
|
+
image: python:latest
|
|
26
|
+
only:
|
|
27
|
+
- main
|
|
28
|
+
script:
|
|
29
|
+
- mkdir pylint
|
|
30
|
+
- source venv/bin/activate
|
|
31
|
+
- (LC_ALL=C pylint --output-format=text src | tee ./pylint/pylint.txt) || true
|
|
32
|
+
- pylint_score=$(sed -n "s@Your code has been rated at \([^/]\+\)/.*@\1@p" "./pylint/pylint.txt")
|
|
33
|
+
- anybadge -o -l pylint -v "$pylint_score" -f ./pylint/pylint.svg 2=red 4=orange
|
|
34
|
+
8=yellow 10=green
|
|
35
|
+
stage: test
|
|
36
|
+
tags: *id001
|
|
37
|
+
register_python_package:
|
|
38
|
+
image: python:latest
|
|
39
|
+
only:
|
|
40
|
+
- main
|
|
41
|
+
script:
|
|
42
|
+
- pip install build twine
|
|
43
|
+
- python -m build
|
|
44
|
+
- TWINE_PASSWORD=${CI_JOB_TOKEN} TWINE_USERNAME=gitlab-ci-token python -m twine
|
|
45
|
+
upload --repository-url ${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/pypi
|
|
46
|
+
dist/*
|
|
47
|
+
stage: deploy
|
|
48
|
+
tags: *id001
|
|
49
|
+
release_job:
|
|
50
|
+
image: registry.gitlab.com/gitlab-org/release-cli:latest
|
|
51
|
+
release:
|
|
52
|
+
description: Release $CI_COMMIT_TAG
|
|
53
|
+
tag_name: $CI_COMMIT_TAG
|
|
54
|
+
rules:
|
|
55
|
+
- if: $CI_COMMIT_TAG =~ /^v./
|
|
56
|
+
script:
|
|
57
|
+
- echo "Running release_job"
|
|
58
|
+
stage: release
|
|
59
|
+
tags: *id001
|
|
60
|
+
stages:
|
|
61
|
+
- test
|
|
62
|
+
- release
|
|
63
|
+
- deploy
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025, Inria
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
6
|
+
|
|
7
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
8
|
+
|
|
9
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: blender-pyutils
|
|
3
|
+
Version: 2025.2
|
|
4
|
+
Summary: Simple utilities for building and installing Python extensions for Blender.
|
|
5
|
+
Project-URL: Homepage, https://gitlab.inria.fr/jrye/blender-pyutils
|
|
6
|
+
Project-URL: Source, https://gitlab.inria.fr/jrye/blender-pyutils.git
|
|
7
|
+
Project-URL: Documentation, https://jrye.gitlabpages.inria.fr/blender-pyutils
|
|
8
|
+
Project-URL: Issues, https://gitlab.inria.fr/jrye/blender-pyutils/issues
|
|
9
|
+
Author-email: Jan-Michael Rye <jan-michael.rye@inria.fr>
|
|
10
|
+
License: MIT License
|
|
11
|
+
|
|
12
|
+
Copyright (c) 2025, Inria
|
|
13
|
+
|
|
14
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
15
|
+
|
|
16
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
17
|
+
|
|
18
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
19
|
+
License-File: LICENSE.txt
|
|
20
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
21
|
+
Classifier: Operating System :: OS Independent
|
|
22
|
+
Classifier: Programming Language :: Python :: 3
|
|
23
|
+
Requires-Python: >=3.7
|
|
24
|
+
Requires-Dist: pyxdg
|
|
25
|
+
Requires-Dist: tomli-w
|
|
26
|
+
Description-Content-Type: text/markdown
|
|
27
|
+
|
|
28
|
+
[insert: badges gitlab]: #
|
|
29
|
+
|
|
30
|
+
[](https://github.com/pypa/hatch) [](https://gitlab.inria.fr/jrye/blender-pyutils/-/tags) [](https://spdx.org/licenses/MIT.html) [](https://gitlab.inria.fr/jrye/blender-pyutils/-/commits/main) [](https://gitlab.inria.fr/jrye/blender-pyutils/-/jobs/artifacts/main/raw/pylint/pylint.txt?job=pylint)
|
|
31
|
+
|
|
32
|
+
[/insert: badges gitlab]: #
|
|
33
|
+
|
|
34
|
+
# Synopsis
|
|
35
|
+
|
|
36
|
+
This is a small Python package that installs a command-line utility to facilitate extension installation in [Blender](https://www.blender.org/). It currently provides the following functionality:
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
* Print information about Blender and its internal Python environment.
|
|
40
|
+
* Validate and build Blender extensions, including their dependency wheels.
|
|
41
|
+
* Install Python packages directly into Blender's modules directory.
|
|
42
|
+
|
|
43
|
+
## Links
|
|
44
|
+
|
|
45
|
+
[insert: links 2]: #
|
|
46
|
+
|
|
47
|
+
### GitLab
|
|
48
|
+
|
|
49
|
+
* [Homepage](https://gitlab.inria.fr/jrye/blender-pyutils)
|
|
50
|
+
* [Source](https://gitlab.inria.fr/jrye/blender-pyutils.git)
|
|
51
|
+
* [Issues](https://gitlab.inria.fr/jrye/blender-pyutils/issues)
|
|
52
|
+
* [Documentation](https://jrye.gitlabpages.inria.fr/blender-pyutils)
|
|
53
|
+
* [GitLab package registry](https://gitlab.inria.fr/jrye/blender-pyutils/-/packages)
|
|
54
|
+
|
|
55
|
+
### Other Repositories
|
|
56
|
+
|
|
57
|
+
* [Software Heritage](https://archive.softwareheritage.org/browse/origin/?origin_url=https%3A//gitlab.inria.fr/jrye/blender-pyutils.git)
|
|
58
|
+
|
|
59
|
+
[/insert: links 2]: #
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
# Usage
|
|
64
|
+
|
|
65
|
+
Install this package, preferably in a Python virtual environment.
|
|
66
|
+
|
|
67
|
+
~~~sh
|
|
68
|
+
# Create the virtual environment.
|
|
69
|
+
python3 -m venv
|
|
70
|
+
|
|
71
|
+
# Activate it.
|
|
72
|
+
source venv/bin/activate
|
|
73
|
+
|
|
74
|
+
# Ensure that pip is installed and up-to-date.
|
|
75
|
+
python3 -m ensure pip
|
|
76
|
+
pip install -U pip
|
|
77
|
+
|
|
78
|
+
# Install this package.
|
|
79
|
+
pip install -U Blender Python Utils
|
|
80
|
+
~~~
|
|
81
|
+
|
|
82
|
+
## blender-pytuils Executable
|
|
83
|
+
|
|
84
|
+
The package will install the `blender-pyutils` command that accepts different subcommands as arguments.
|
|
85
|
+
|
|
86
|
+
[insert: command_output: blender-pyutils --help]: #
|
|
87
|
+
|
|
88
|
+
~~~
|
|
89
|
+
usage: blender-pyutils [-h] {info,build,pip} ...
|
|
90
|
+
|
|
91
|
+
Utility script for validating and packaging Blender extensions.
|
|
92
|
+
|
|
93
|
+
positional arguments:
|
|
94
|
+
{info,build,pip}
|
|
95
|
+
|
|
96
|
+
options:
|
|
97
|
+
-h, --help show this help message and exit
|
|
98
|
+
|
|
99
|
+
~~~
|
|
100
|
+
|
|
101
|
+
[/insert: command_output: blender-pyutils --help]: #
|
|
102
|
+
|
|
103
|
+
### `info` Subcommand
|
|
104
|
+
|
|
105
|
+
The `info` subcommand will print information about Blender's version, module directory path and configured Python executable.
|
|
106
|
+
|
|
107
|
+
[insert: command_output: blender-pyutils info --help]: #
|
|
108
|
+
|
|
109
|
+
~~~
|
|
110
|
+
usage: blender-pyutils info [-h]
|
|
111
|
+
|
|
112
|
+
Print Blender information.
|
|
113
|
+
|
|
114
|
+
options:
|
|
115
|
+
-h, --help show this help message and exit
|
|
116
|
+
|
|
117
|
+
~~~
|
|
118
|
+
|
|
119
|
+
[/insert: command_output: blender-pyutils info --help]: #
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
### `build` Subcommand
|
|
123
|
+
|
|
124
|
+
The `build` subommand will validate and build a Blender extension. If a `requirements.txt` file is found in the extension directory then it will also download the dependency wheels to the `wheels` subdirectory and add them to the `blender_manifest.toml`.
|
|
125
|
+
|
|
126
|
+
[insert: command_output: blender-pyutils build --help]: #
|
|
127
|
+
|
|
128
|
+
~~~
|
|
129
|
+
usage: blender-pyutils build [-h] [-p PATH]
|
|
130
|
+
|
|
131
|
+
Validate and build an extension. If a requirements.txt file is found in the
|
|
132
|
+
extension directory then the wheels for its dependencies will be downloaded to
|
|
133
|
+
the wheels directory and the manifest will be updated to include them.
|
|
134
|
+
|
|
135
|
+
options:
|
|
136
|
+
-h, --help show this help message and exit
|
|
137
|
+
-p, --path PATH The path to the extension's root directory. If not given,
|
|
138
|
+
the current working directory is assumed.
|
|
139
|
+
|
|
140
|
+
~~~
|
|
141
|
+
|
|
142
|
+
[/insert: command_output: blender-pyutils build --help]: #
|
|
143
|
+
|
|
144
|
+
### `pip` Subcommand
|
|
145
|
+
|
|
146
|
+
The `pip` subcommand will install Python packages directly to Blender's modules directory. It accepts the same commands as `pip`.
|
|
147
|
+
|
|
148
|
+
~~~sh
|
|
149
|
+
# Example: install scipy using pip
|
|
150
|
+
blender-pyutils pip install scipy
|
|
151
|
+
|
|
152
|
+
# Example: install scipy using uv
|
|
153
|
+
blender-pyutils pip --uv install scipy
|
|
154
|
+
|
|
155
|
+
# Example: use "--" to pass through options to pip, such as a requirements.txt file
|
|
156
|
+
blender-pyutils pip -- install -U -r requirements.txt
|
|
157
|
+
~~~
|
|
158
|
+
|
|
159
|
+
[insert: command_output: blender-pyutils pip --help]: #
|
|
160
|
+
|
|
161
|
+
~~~
|
|
162
|
+
usage: blender-pyutils pip [-h] [--path PATH] [--uv] <PIP ARG> [<PIP ARG> ...]
|
|
163
|
+
|
|
164
|
+
Install Python packages to Blender's module directory. Additional arguments as
|
|
165
|
+
passed through to pip.
|
|
166
|
+
|
|
167
|
+
positional arguments:
|
|
168
|
+
<PIP ARG> Arguments to pass through to pip. Precede these arguments with
|
|
169
|
+
"--" if any of them begin with "-".
|
|
170
|
+
|
|
171
|
+
options:
|
|
172
|
+
-h, --help show this help message and exit
|
|
173
|
+
--path PATH Installation directory for Python packages. If not given, the
|
|
174
|
+
default Blender module directory will be used. Use the "info"
|
|
175
|
+
command to show the path to the module directory.
|
|
176
|
+
--uv Use "uv pip" instead of pip.
|
|
177
|
+
|
|
178
|
+
~~~
|
|
179
|
+
|
|
180
|
+
[/insert: command_output: blender-pyutils pip --help]: #
|
|
181
|
+
|
|
182
|
+
#### Caveats
|
|
183
|
+
|
|
184
|
+
* At the time or writing, Blender does not recognize packages installed with [pip's editable option (-e/--editiable)](https://pip.pypa.io/en/stable/cli/pip_install/).
|
|
185
|
+
* Packages must be installed before launching Blender.
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
#### Legacy Script
|
|
189
|
+
|
|
190
|
+
This repository originally only provided [blender-pip_install.sh](blender-pip_install.sh) as a wrapper around `pip install` and `uv pip install`. It has been superceded by `blender-pyutils pip` and users should migrate accordingly.
|
|
191
|
+
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
[insert: badges gitlab]: #
|
|
2
|
+
|
|
3
|
+
[](https://github.com/pypa/hatch) [](https://gitlab.inria.fr/jrye/blender-pyutils/-/tags) [](https://spdx.org/licenses/MIT.html) [](https://gitlab.inria.fr/jrye/blender-pyutils/-/commits/main) [](https://gitlab.inria.fr/jrye/blender-pyutils/-/jobs/artifacts/main/raw/pylint/pylint.txt?job=pylint)
|
|
4
|
+
|
|
5
|
+
[/insert: badges gitlab]: #
|
|
6
|
+
|
|
7
|
+
# Synopsis
|
|
8
|
+
|
|
9
|
+
This is a small Python package that installs a command-line utility to facilitate extension installation in [Blender](https://www.blender.org/). It currently provides the following functionality:
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
* Print information about Blender and its internal Python environment.
|
|
13
|
+
* Validate and build Blender extensions, including their dependency wheels.
|
|
14
|
+
* Install Python packages directly into Blender's modules directory.
|
|
15
|
+
|
|
16
|
+
## Links
|
|
17
|
+
|
|
18
|
+
[insert: links 2]: #
|
|
19
|
+
|
|
20
|
+
### GitLab
|
|
21
|
+
|
|
22
|
+
* [Homepage](https://gitlab.inria.fr/jrye/blender-pyutils)
|
|
23
|
+
* [Source](https://gitlab.inria.fr/jrye/blender-pyutils.git)
|
|
24
|
+
* [Issues](https://gitlab.inria.fr/jrye/blender-pyutils/issues)
|
|
25
|
+
* [Documentation](https://jrye.gitlabpages.inria.fr/blender-pyutils)
|
|
26
|
+
* [GitLab package registry](https://gitlab.inria.fr/jrye/blender-pyutils/-/packages)
|
|
27
|
+
|
|
28
|
+
### Other Repositories
|
|
29
|
+
|
|
30
|
+
* [Software Heritage](https://archive.softwareheritage.org/browse/origin/?origin_url=https%3A//gitlab.inria.fr/jrye/blender-pyutils.git)
|
|
31
|
+
|
|
32
|
+
[/insert: links 2]: #
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
# Usage
|
|
37
|
+
|
|
38
|
+
Install this package, preferably in a Python virtual environment.
|
|
39
|
+
|
|
40
|
+
~~~sh
|
|
41
|
+
# Create the virtual environment.
|
|
42
|
+
python3 -m venv
|
|
43
|
+
|
|
44
|
+
# Activate it.
|
|
45
|
+
source venv/bin/activate
|
|
46
|
+
|
|
47
|
+
# Ensure that pip is installed and up-to-date.
|
|
48
|
+
python3 -m ensure pip
|
|
49
|
+
pip install -U pip
|
|
50
|
+
|
|
51
|
+
# Install this package.
|
|
52
|
+
pip install -U Blender Python Utils
|
|
53
|
+
~~~
|
|
54
|
+
|
|
55
|
+
## blender-pytuils Executable
|
|
56
|
+
|
|
57
|
+
The package will install the `blender-pyutils` command that accepts different subcommands as arguments.
|
|
58
|
+
|
|
59
|
+
[insert: command_output: blender-pyutils --help]: #
|
|
60
|
+
|
|
61
|
+
~~~
|
|
62
|
+
usage: blender-pyutils [-h] {info,build,pip} ...
|
|
63
|
+
|
|
64
|
+
Utility script for validating and packaging Blender extensions.
|
|
65
|
+
|
|
66
|
+
positional arguments:
|
|
67
|
+
{info,build,pip}
|
|
68
|
+
|
|
69
|
+
options:
|
|
70
|
+
-h, --help show this help message and exit
|
|
71
|
+
|
|
72
|
+
~~~
|
|
73
|
+
|
|
74
|
+
[/insert: command_output: blender-pyutils --help]: #
|
|
75
|
+
|
|
76
|
+
### `info` Subcommand
|
|
77
|
+
|
|
78
|
+
The `info` subcommand will print information about Blender's version, module directory path and configured Python executable.
|
|
79
|
+
|
|
80
|
+
[insert: command_output: blender-pyutils info --help]: #
|
|
81
|
+
|
|
82
|
+
~~~
|
|
83
|
+
usage: blender-pyutils info [-h]
|
|
84
|
+
|
|
85
|
+
Print Blender information.
|
|
86
|
+
|
|
87
|
+
options:
|
|
88
|
+
-h, --help show this help message and exit
|
|
89
|
+
|
|
90
|
+
~~~
|
|
91
|
+
|
|
92
|
+
[/insert: command_output: blender-pyutils info --help]: #
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
### `build` Subcommand
|
|
96
|
+
|
|
97
|
+
The `build` subommand will validate and build a Blender extension. If a `requirements.txt` file is found in the extension directory then it will also download the dependency wheels to the `wheels` subdirectory and add them to the `blender_manifest.toml`.
|
|
98
|
+
|
|
99
|
+
[insert: command_output: blender-pyutils build --help]: #
|
|
100
|
+
|
|
101
|
+
~~~
|
|
102
|
+
usage: blender-pyutils build [-h] [-p PATH]
|
|
103
|
+
|
|
104
|
+
Validate and build an extension. If a requirements.txt file is found in the
|
|
105
|
+
extension directory then the wheels for its dependencies will be downloaded to
|
|
106
|
+
the wheels directory and the manifest will be updated to include them.
|
|
107
|
+
|
|
108
|
+
options:
|
|
109
|
+
-h, --help show this help message and exit
|
|
110
|
+
-p, --path PATH The path to the extension's root directory. If not given,
|
|
111
|
+
the current working directory is assumed.
|
|
112
|
+
|
|
113
|
+
~~~
|
|
114
|
+
|
|
115
|
+
[/insert: command_output: blender-pyutils build --help]: #
|
|
116
|
+
|
|
117
|
+
### `pip` Subcommand
|
|
118
|
+
|
|
119
|
+
The `pip` subcommand will install Python packages directly to Blender's modules directory. It accepts the same commands as `pip`.
|
|
120
|
+
|
|
121
|
+
~~~sh
|
|
122
|
+
# Example: install scipy using pip
|
|
123
|
+
blender-pyutils pip install scipy
|
|
124
|
+
|
|
125
|
+
# Example: install scipy using uv
|
|
126
|
+
blender-pyutils pip --uv install scipy
|
|
127
|
+
|
|
128
|
+
# Example: use "--" to pass through options to pip, such as a requirements.txt file
|
|
129
|
+
blender-pyutils pip -- install -U -r requirements.txt
|
|
130
|
+
~~~
|
|
131
|
+
|
|
132
|
+
[insert: command_output: blender-pyutils pip --help]: #
|
|
133
|
+
|
|
134
|
+
~~~
|
|
135
|
+
usage: blender-pyutils pip [-h] [--path PATH] [--uv] <PIP ARG> [<PIP ARG> ...]
|
|
136
|
+
|
|
137
|
+
Install Python packages to Blender's module directory. Additional arguments as
|
|
138
|
+
passed through to pip.
|
|
139
|
+
|
|
140
|
+
positional arguments:
|
|
141
|
+
<PIP ARG> Arguments to pass through to pip. Precede these arguments with
|
|
142
|
+
"--" if any of them begin with "-".
|
|
143
|
+
|
|
144
|
+
options:
|
|
145
|
+
-h, --help show this help message and exit
|
|
146
|
+
--path PATH Installation directory for Python packages. If not given, the
|
|
147
|
+
default Blender module directory will be used. Use the "info"
|
|
148
|
+
command to show the path to the module directory.
|
|
149
|
+
--uv Use "uv pip" instead of pip.
|
|
150
|
+
|
|
151
|
+
~~~
|
|
152
|
+
|
|
153
|
+
[/insert: command_output: blender-pyutils pip --help]: #
|
|
154
|
+
|
|
155
|
+
#### Caveats
|
|
156
|
+
|
|
157
|
+
* At the time or writing, Blender does not recognize packages installed with [pip's editable option (-e/--editiable)](https://pip.pypa.io/en/stable/cli/pip_install/).
|
|
158
|
+
* Packages must be installed before launching Blender.
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
#### Legacy Script
|
|
162
|
+
|
|
163
|
+
This repository originally only provided [blender-pip_install.sh](blender-pip_install.sh) as a wrapper around `pip install` and `uv pip install`. It has been superceded by `blender-pyutils pip` and users should migrate accordingly.
|
|
164
|
+
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
{
|
|
2
|
+
"@context": [
|
|
3
|
+
"https://w3id.org/codemeta/3.0",
|
|
4
|
+
"https://w3id.org/software-iodata",
|
|
5
|
+
"https://raw.githubusercontent.com/jantman/repostatus.org/master/badges/latest/ontology.jsonld",
|
|
6
|
+
"https://schema.org",
|
|
7
|
+
"https://w3id.org/software-types"
|
|
8
|
+
],
|
|
9
|
+
"@type": "SoftwareSourceCode",
|
|
10
|
+
"author": [
|
|
11
|
+
{
|
|
12
|
+
"@id": "https://orcid.org/0009-0005-0109-6598",
|
|
13
|
+
"@type": "Person",
|
|
14
|
+
"email": "jan-michael.rye@inria.fr",
|
|
15
|
+
"familyName": "Rye",
|
|
16
|
+
"givenName": "Jan-Michael"
|
|
17
|
+
}
|
|
18
|
+
],
|
|
19
|
+
"codeRepository": "https://gitlab.inria.fr/jrye/blender-pyutils.git",
|
|
20
|
+
"contributor": [
|
|
21
|
+
{
|
|
22
|
+
"@id": "https://orcid.org/0009-0005-0109-6598",
|
|
23
|
+
"@type": "Person",
|
|
24
|
+
"email": "jan-michael.rye@inria.fr",
|
|
25
|
+
"familyName": "Rye",
|
|
26
|
+
"givenName": "Jan-Michael"
|
|
27
|
+
}
|
|
28
|
+
],
|
|
29
|
+
"description": "Simple utilities for building and installing Python extensions for Blender.",
|
|
30
|
+
"identifier": "blender-pyutils",
|
|
31
|
+
"isSourceCodeOf": {
|
|
32
|
+
"@type": "CommandLineApplication",
|
|
33
|
+
"description": "Utility script for validating and packaging Blender extensions.",
|
|
34
|
+
"executableName": "blender-pyutils",
|
|
35
|
+
"name": "blender-pyutils",
|
|
36
|
+
"runtimePlatform": "Python 3"
|
|
37
|
+
},
|
|
38
|
+
"issueTracker": "https://gitlab.inria.fr/jrye/blender-pyutils/issues",
|
|
39
|
+
"license": "http://spdx.org/licenses/MIT",
|
|
40
|
+
"maintainer": {
|
|
41
|
+
"@id": "https://orcid.org/0009-0005-0109-6598",
|
|
42
|
+
"@type": "Person",
|
|
43
|
+
"email": "jan-michael.rye@inria.fr",
|
|
44
|
+
"familyName": "Rye",
|
|
45
|
+
"givenName": "Jan-Michael"
|
|
46
|
+
},
|
|
47
|
+
"name": "blender-pyutils",
|
|
48
|
+
"operatingSystem": "OS Independent",
|
|
49
|
+
"programmingLanguage": "Python",
|
|
50
|
+
"readme": "https://gitlab.inria.fr/jrye/blender-pyutils/-/blob/main/README.md",
|
|
51
|
+
"runtimePlatform": "Python 3",
|
|
52
|
+
"softwareHelp": "https://jrye.gitlabpages.inria.fr/blender-pyutils",
|
|
53
|
+
"softwareRequirements": [
|
|
54
|
+
{
|
|
55
|
+
"@type": "SoftwareApplication",
|
|
56
|
+
"identifier": "pyxdg",
|
|
57
|
+
"name": "pyxdg",
|
|
58
|
+
"runtimePlatform": "Python 3"
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
"@type": "SoftwareApplication",
|
|
62
|
+
"identifier": "tomli-w",
|
|
63
|
+
"name": "tomli-w",
|
|
64
|
+
"runtimePlatform": "Python 3"
|
|
65
|
+
}
|
|
66
|
+
],
|
|
67
|
+
"url": "https://gitlab.inria.fr/jrye/blender-pyutils",
|
|
68
|
+
"version": "2025.2"
|
|
69
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# Minimal makefile for Sphinx documentation
|
|
2
|
+
#
|
|
3
|
+
|
|
4
|
+
# You can set these variables from the command line, and also
|
|
5
|
+
# from the environment for the first two.
|
|
6
|
+
SPHINXOPTS ?=
|
|
7
|
+
SPHINXBUILD ?= sphinx-build
|
|
8
|
+
SOURCEDIR = source
|
|
9
|
+
BUILDDIR = build
|
|
10
|
+
|
|
11
|
+
# Put it first so that "make" without argument is like "make help".
|
|
12
|
+
help:
|
|
13
|
+
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
|
14
|
+
|
|
15
|
+
.PHONY: help Makefile
|
|
16
|
+
|
|
17
|
+
# Catch-all target: route all unknown targets to Sphinx using the new
|
|
18
|
+
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
|
|
19
|
+
%: Makefile
|
|
20
|
+
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
@ECHO OFF
|
|
2
|
+
|
|
3
|
+
pushd %~dp0
|
|
4
|
+
|
|
5
|
+
REM Command file for Sphinx documentation
|
|
6
|
+
|
|
7
|
+
if "%SPHINXBUILD%" == "" (
|
|
8
|
+
set SPHINXBUILD=sphinx-build
|
|
9
|
+
)
|
|
10
|
+
set SOURCEDIR=source
|
|
11
|
+
set BUILDDIR=build
|
|
12
|
+
|
|
13
|
+
%SPHINXBUILD% >NUL 2>NUL
|
|
14
|
+
if errorlevel 9009 (
|
|
15
|
+
echo.
|
|
16
|
+
echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
|
|
17
|
+
echo.installed, then set the SPHINXBUILD environment variable to point
|
|
18
|
+
echo.to the full path of the 'sphinx-build' executable. Alternatively you
|
|
19
|
+
echo.may add the Sphinx directory to PATH.
|
|
20
|
+
echo.
|
|
21
|
+
echo.If you don't have Sphinx installed, grab it from
|
|
22
|
+
echo.https://www.sphinx-doc.org/
|
|
23
|
+
exit /b 1
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
if "%1" == "" goto help
|
|
27
|
+
|
|
28
|
+
%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
|
|
29
|
+
goto end
|
|
30
|
+
|
|
31
|
+
:help
|
|
32
|
+
%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
|
|
33
|
+
|
|
34
|
+
:end
|
|
35
|
+
popd
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Sphinx configuration file."""
|
|
3
|
+
|
|
4
|
+
# pylint: disable=invalid-name,redefined-builtin
|
|
5
|
+
|
|
6
|
+
import importlib
|
|
7
|
+
import pathlib
|
|
8
|
+
import sys
|
|
9
|
+
|
|
10
|
+
source_code = "../../src"
|
|
11
|
+
git_url = "https://gitlab.inria.fr/jrye/blender-pyutils"
|
|
12
|
+
|
|
13
|
+
this_path = pathlib.Path(__file__).resolve()
|
|
14
|
+
sys.path.insert(0, str((this_path.parent / source_code).resolve()))
|
|
15
|
+
|
|
16
|
+
author = "Jan-Michael Rye"
|
|
17
|
+
copyright = "2025, Inria"
|
|
18
|
+
project = "blender-pyutils"
|
|
19
|
+
html_theme = "sphinx_rtd_theme"
|
|
20
|
+
|
|
21
|
+
autodoc_mock_imports = []
|
|
22
|
+
extensions = [
|
|
23
|
+
"myst_parser",
|
|
24
|
+
"sphinx.ext.autodoc",
|
|
25
|
+
"sphinx.ext.linkcode",
|
|
26
|
+
"sphinx.ext.napoleon",
|
|
27
|
+
"sphinx.ext.todo",
|
|
28
|
+
]
|
|
29
|
+
index_entries = []
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def skip(
|
|
33
|
+
_app, _what, name, _obj, would_skip, _options
|
|
34
|
+
): # pylint: disable=too-many-arguments
|
|
35
|
+
"""Customize autodoc member skipping."""
|
|
36
|
+
if name == "__init__":
|
|
37
|
+
return False
|
|
38
|
+
return would_skip
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def setup(app):
|
|
42
|
+
"""Connect the skip function."""
|
|
43
|
+
app.connect("autodoc-skip-member", skip)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def linkcode_resolve(domain, info):
|
|
47
|
+
"""Get source links for the linkcode extension."""
|
|
48
|
+
module = info["module"]
|
|
49
|
+
if domain != "py" or not module:
|
|
50
|
+
return None
|
|
51
|
+
top_mod = importlib.import_module(module.split(".")[0])
|
|
52
|
+
mod = importlib.import_module(module)
|
|
53
|
+
top_mod_path = pathlib.Path(top_mod.__file__)
|
|
54
|
+
mod_path = pathlib.Path(mod.__file__)
|
|
55
|
+
subpath = str(mod_path.relative_to(top_mod_path.parent.parent))
|
|
56
|
+
return f"{git_url}/-/blob/main/src/{subpath}"
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
`blender-pyutils <https://gitlab.inria.fr/jrye/blender-pyutils>`_ Documentation
|
|
2
|
+
===============================================================================
|
|
3
|
+
|
|
4
|
+
.. toctree::
|
|
5
|
+
:maxdepth: 2
|
|
6
|
+
:caption: Contents:
|
|
7
|
+
|
|
8
|
+
readme
|
|
9
|
+
modules
|
|
10
|
+
|
|
11
|
+
Indices and tables
|
|
12
|
+
==================
|
|
13
|
+
|
|
14
|
+
* :ref:`genindex`
|
|
15
|
+
* :ref:`modindex`
|
|
16
|
+
* :ref:`search`
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = [
|
|
3
|
+
"hatchling",
|
|
4
|
+
"hatch-vcs",
|
|
5
|
+
"wheel",
|
|
6
|
+
]
|
|
7
|
+
build-backend = "hatchling.build"
|
|
8
|
+
|
|
9
|
+
[project]
|
|
10
|
+
name = "blender-pyutils"
|
|
11
|
+
description = "Simple utilities for building and installing Python extensions for Blender."
|
|
12
|
+
dependencies = [
|
|
13
|
+
"pyxdg",
|
|
14
|
+
"tomli_w",
|
|
15
|
+
]
|
|
16
|
+
requires-python = ">=3.7"
|
|
17
|
+
classifiers = [
|
|
18
|
+
"License :: OSI Approved :: MIT License",
|
|
19
|
+
"Operating System :: OS Independent",
|
|
20
|
+
"Programming Language :: Python :: 3",
|
|
21
|
+
]
|
|
22
|
+
readme = "README.md"
|
|
23
|
+
dynamic = [
|
|
24
|
+
"version",
|
|
25
|
+
]
|
|
26
|
+
authors = [
|
|
27
|
+
{ name = "Jan-Michael Rye", email = "jan-michael.rye@inria.fr" },
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
[project.license]
|
|
31
|
+
file = "LICENSE.txt"
|
|
32
|
+
|
|
33
|
+
[project.urls]
|
|
34
|
+
Homepage = "https://gitlab.inria.fr/jrye/blender-pyutils"
|
|
35
|
+
Source = "https://gitlab.inria.fr/jrye/blender-pyutils.git"
|
|
36
|
+
Documentation = "https://jrye.gitlabpages.inria.fr/blender-pyutils"
|
|
37
|
+
Issues = "https://gitlab.inria.fr/jrye/blender-pyutils/issues"
|
|
38
|
+
|
|
39
|
+
[project.scripts]
|
|
40
|
+
blender-pyutils = "blender_pyutils.main:run_main"
|
|
41
|
+
|
|
42
|
+
[tool.hatch.version]
|
|
43
|
+
source = "vcs"
|
|
44
|
+
|
|
45
|
+
[tool.hatch.version.raw-options]
|
|
46
|
+
version_scheme = "no-guess-dev"
|
|
47
|
+
local_scheme = "no-local-version"
|
|
48
|
+
|
|
49
|
+
[tool.hatch.build.hooks.vcs]
|
|
50
|
+
version-file = "src/blender_pyutils/version.py"
|
|
51
|
+
template = """
|
|
52
|
+
#!/usr/bin/env python3
|
|
53
|
+
'''
|
|
54
|
+
Version file automatically generated by setuptools-scm via hatch-vcs.
|
|
55
|
+
https://pypi.org/project/setuptools-scm/
|
|
56
|
+
https://github.com/ofek/hatch-vcs
|
|
57
|
+
Do not track this file and do not edit it manually.
|
|
58
|
+
'''
|
|
59
|
+
VERSION = {version!r}
|
|
60
|
+
VERSION_TUPLE = {version_tuple!r}
|
|
61
|
+
"""
|
|
62
|
+
|
|
63
|
+
[tool.pylint."MESSAGES CONTROL"]
|
|
64
|
+
max-line-length = 100
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
# This is the original version of the code that only served to install packages
|
|
5
|
+
# in Blender's module directory. Use "blender-pyutils pip" instead.
|
|
6
|
+
|
|
7
|
+
# Usage examples
|
|
8
|
+
#
|
|
9
|
+
# Install the tqdm package.
|
|
10
|
+
# blender-pip_install.sh tqdm
|
|
11
|
+
#
|
|
12
|
+
# Install a package from source in the current directory.
|
|
13
|
+
# blender-pip_install.sh .
|
|
14
|
+
#
|
|
15
|
+
# Install all packages in a requirements.txt file.
|
|
16
|
+
# blender-pip_install.sh -r requirements.txt
|
|
17
|
+
~~~
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
tmp_dir=$(mktemp -d)
|
|
21
|
+
# shellcheck disable=SC2064
|
|
22
|
+
trap "rm -fr ${tmp_dir@Q}" EXIT
|
|
23
|
+
cat > "$tmp_dir/get_version.py" <<'SCRIPT'
|
|
24
|
+
#!/usr/bin/env python3
|
|
25
|
+
"""Print the Python version."""
|
|
26
|
+
import sys
|
|
27
|
+
import bpy
|
|
28
|
+
vinfo = sys.version_info
|
|
29
|
+
print(f"{vinfo.major}.{vinfo.minor}.{vinfo.micro}")
|
|
30
|
+
bpy.ops.wm.quit_blender()
|
|
31
|
+
SCRIPT
|
|
32
|
+
|
|
33
|
+
# Installed version of blender.
|
|
34
|
+
blender_version=$(blender -v | sed -n 's/^Blender //p')
|
|
35
|
+
|
|
36
|
+
# Blender version of Python.
|
|
37
|
+
blender_pyversion=$(blender --background --python "$tmp_dir/get_version.py" | \
|
|
38
|
+
grep -E '^[0-9]+\.[0-9]+\.[0-9]+$')
|
|
39
|
+
|
|
40
|
+
# Blender configuration directory.
|
|
41
|
+
blender_dir=${XDG_CONFIG_HOME:-$HOME/.config}/blender/${blender_version%.*}
|
|
42
|
+
|
|
43
|
+
# Module directory recognized by blender.
|
|
44
|
+
module_dir=${blender_dir}/scripts/modules
|
|
45
|
+
|
|
46
|
+
log_path=${0##*/}
|
|
47
|
+
log_path=${log_path%.*}.log
|
|
48
|
+
|
|
49
|
+
echo "Blender version: $blender_version"
|
|
50
|
+
echo "Python version in Blender: $blender_pyversion"
|
|
51
|
+
echo "Detected module directory: $module_dir"
|
|
52
|
+
echo "Installation log file: $log_path"
|
|
53
|
+
|
|
54
|
+
# Install uv within a virtual environment.
|
|
55
|
+
function install_uv()
|
|
56
|
+
{
|
|
57
|
+
python3 -m ensurepip
|
|
58
|
+
python3 -m pip install -U pip
|
|
59
|
+
python3 -m pip install -U uv
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
# Create the bootstrap virtual environment with uv if necessary.
|
|
63
|
+
install_uv=false
|
|
64
|
+
if ! command -v uv >/dev/null 2>&1
|
|
65
|
+
then
|
|
66
|
+
cat << 'UV_MSG'
|
|
67
|
+
It is recommended to install uv for virtual environment management:
|
|
68
|
+
|
|
69
|
+
https://docs.astral.sh/uv/getting-started/installation/
|
|
70
|
+
|
|
71
|
+
For this operation, uv will be installed in a temporary virtual environment.
|
|
72
|
+
UV_MSG
|
|
73
|
+
install_uv=true
|
|
74
|
+
python3 -m venv "$tmp_dir/bootstrap_venv"
|
|
75
|
+
# shellcheck source=/dev/null
|
|
76
|
+
source "$tmp_dir/bootstrap_venv/bin/activate"
|
|
77
|
+
install_uv
|
|
78
|
+
fi
|
|
79
|
+
|
|
80
|
+
# Create the virtual environment with Blender's version of Python.
|
|
81
|
+
uv venv --python "$blender_pyversion" "$tmp_dir/venv"
|
|
82
|
+
# shellcheck source=/dev/null
|
|
83
|
+
source "$tmp_dir/venv/bin/activate"
|
|
84
|
+
# Install UV in this environment too if necessary.
|
|
85
|
+
if "$install_uv"
|
|
86
|
+
then
|
|
87
|
+
install_uv
|
|
88
|
+
fi
|
|
89
|
+
|
|
90
|
+
# Create the module directory if missing.
|
|
91
|
+
mkdir -p "$module_dir"
|
|
92
|
+
|
|
93
|
+
# Install the packages to the target directory from the virtual environment with
|
|
94
|
+
# the required version of Python.
|
|
95
|
+
uv pip install -U --target "$module_dir" "$@"
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
SELF=$(readlink -f "${BASH_SOURCE[0]}")
|
|
5
|
+
DIR=${SELF%/*/*}
|
|
6
|
+
|
|
7
|
+
cd -- "$DIR"
|
|
8
|
+
|
|
9
|
+
function show_help()
|
|
10
|
+
{
|
|
11
|
+
cat << HELP
|
|
12
|
+
USAGE
|
|
13
|
+
|
|
14
|
+
${0##*/} [-h] [-v]
|
|
15
|
+
|
|
16
|
+
OPTIONS
|
|
17
|
+
|
|
18
|
+
-h
|
|
19
|
+
Show this help message and exit.
|
|
20
|
+
|
|
21
|
+
-v
|
|
22
|
+
Use a Python virtual environment to build the documentation.
|
|
23
|
+
|
|
24
|
+
HELP
|
|
25
|
+
exit "$1"
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function ensure_venv()
|
|
29
|
+
{
|
|
30
|
+
local venv_dir=$1
|
|
31
|
+
if [[ ! -e "$venv_dir/bin/activate" ]]
|
|
32
|
+
then
|
|
33
|
+
if command -v uv >/dev/null 2>&1
|
|
34
|
+
then
|
|
35
|
+
uv venv "$venv_dir"
|
|
36
|
+
else
|
|
37
|
+
python3 -m venv "$venv_dir"
|
|
38
|
+
fi
|
|
39
|
+
fi
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function ensure_uv()
|
|
43
|
+
{
|
|
44
|
+
if ! command -v uv >/dev/null 2>&1
|
|
45
|
+
then
|
|
46
|
+
python3 -m ensurepip
|
|
47
|
+
pip install -U pip
|
|
48
|
+
pip install -U uv
|
|
49
|
+
fi
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
venv_dir=venv
|
|
53
|
+
while getopts "hv:" opt
|
|
54
|
+
do
|
|
55
|
+
case "$opt" in
|
|
56
|
+
h) show_help 0 ;;
|
|
57
|
+
v) venv_dir=$OPTARG ;;
|
|
58
|
+
*) show_help 1 ;;
|
|
59
|
+
esac
|
|
60
|
+
done
|
|
61
|
+
shift $((OPTIND - 1))
|
|
62
|
+
|
|
63
|
+
ensure_venv "$venv_dir"
|
|
64
|
+
# shellcheck source=/dev/null
|
|
65
|
+
source "$venv_dir/bin/activate"
|
|
66
|
+
ensure_uv
|
|
67
|
+
|
|
68
|
+
uv pip install -U -r doc/requirements.txt
|
|
69
|
+
sphinx-apidoc -o doc/source -f -H "API Documentation" ./src
|
|
70
|
+
sphinx-build -b html doc/source public
|
|
71
|
+
# Run again to fix cross-references.
|
|
72
|
+
sphinx-build -b html doc/source public
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Invoke the Blender executable."""
|
|
3
|
+
|
|
4
|
+
import functools
|
|
5
|
+
import logging
|
|
6
|
+
import pathlib
|
|
7
|
+
import shutil
|
|
8
|
+
import subprocess
|
|
9
|
+
import tempfile
|
|
10
|
+
import textwrap
|
|
11
|
+
import tomllib
|
|
12
|
+
|
|
13
|
+
import tomli_w
|
|
14
|
+
from xdg.BaseDirectory import xdg_config_home
|
|
15
|
+
|
|
16
|
+
from .common import ENCODING
|
|
17
|
+
from .exceptions import BlenderError, PipError
|
|
18
|
+
from .utils import run
|
|
19
|
+
|
|
20
|
+
LOGGER = logging.getLogger(__name__)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class BlenderWrapper:
|
|
24
|
+
"""
|
|
25
|
+
Simple wrapper around the Blender command-line executable.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
def __init__(self, exe="blender", ext_dir=None):
|
|
29
|
+
"""
|
|
30
|
+
Args:
|
|
31
|
+
exe:
|
|
32
|
+
The blender command to execute.
|
|
33
|
+
|
|
34
|
+
ext_dir:
|
|
35
|
+
The path to the directory with the extension to validate and
|
|
36
|
+
build.
|
|
37
|
+
"""
|
|
38
|
+
self.exe = str(exe)
|
|
39
|
+
if ext_dir is None:
|
|
40
|
+
ext_dir = pathlib.Path.cwd()
|
|
41
|
+
else:
|
|
42
|
+
ext_dir = pathlib.Path(ext_dir).resolve()
|
|
43
|
+
self.ext_dir = ext_dir
|
|
44
|
+
|
|
45
|
+
def run_blender(self, args, **kwargs):
|
|
46
|
+
"""
|
|
47
|
+
Invoke blender.
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
Arguments to pass to the blender command.
|
|
51
|
+
|
|
52
|
+
**kwargs:
|
|
53
|
+
Keyword arguments passed through to subprocess.run().
|
|
54
|
+
|
|
55
|
+
Returns:
|
|
56
|
+
The return value of subprocess.run()
|
|
57
|
+
"""
|
|
58
|
+
return run((self.exe, *args), **kwargs)
|
|
59
|
+
|
|
60
|
+
@functools.cached_property
|
|
61
|
+
def info(self):
|
|
62
|
+
"""
|
|
63
|
+
A dict with information about Blender and its embedded Python
|
|
64
|
+
environment. It contains the following keys:
|
|
65
|
+
Blender version:
|
|
66
|
+
The Blender version.
|
|
67
|
+
|
|
68
|
+
Python version:
|
|
69
|
+
The Python version.
|
|
70
|
+
|
|
71
|
+
Python executable:
|
|
72
|
+
The path to the Python executable.
|
|
73
|
+
"""
|
|
74
|
+
delim = "###"
|
|
75
|
+
blver = "Blender version"
|
|
76
|
+
blmoddir = "Blender module directory"
|
|
77
|
+
pyver = "Python version"
|
|
78
|
+
pyexe = "Python executable"
|
|
79
|
+
code = textwrap.dedent(
|
|
80
|
+
f"""\
|
|
81
|
+
import sys
|
|
82
|
+
import bpy
|
|
83
|
+
print(f'{blver}{delim}{{bpy.app.version_string}}')
|
|
84
|
+
ver = sys.version_info
|
|
85
|
+
print(f'{pyver}{delim}{{ver.major}}.{{ver.minor}}.{{ver.micro}}')
|
|
86
|
+
print(f'{pyexe}{delim}{{sys.executable}}')
|
|
87
|
+
bpy.ops.wm.quit_blender()
|
|
88
|
+
"""
|
|
89
|
+
)
|
|
90
|
+
cmd = ["--background", "--python-expr", code]
|
|
91
|
+
try:
|
|
92
|
+
response = self.run_blender(cmd, stdout=subprocess.PIPE)
|
|
93
|
+
except subprocess.CalledProcessError as err:
|
|
94
|
+
raise BlenderError(
|
|
95
|
+
f"Failed to determine Blender's Python version: {err}"
|
|
96
|
+
) from err
|
|
97
|
+
info = {}
|
|
98
|
+
for line in response.stdout.decode(ENCODING).splitlines():
|
|
99
|
+
try:
|
|
100
|
+
key, value = line.strip().split(delim, 1)
|
|
101
|
+
except ValueError:
|
|
102
|
+
continue
|
|
103
|
+
value = value.strip()
|
|
104
|
+
if key == pyexe:
|
|
105
|
+
value = pathlib.Path(value).resolve()
|
|
106
|
+
info[key] = value
|
|
107
|
+
|
|
108
|
+
bldir = ".".join(info[blver].split(".", 2)[:2])
|
|
109
|
+
info[blmoddir] = (
|
|
110
|
+
pathlib.Path(xdg_config_home) / "blender" / bldir / "scripts/modules"
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
return info
|
|
114
|
+
|
|
115
|
+
def run_python(self, args, **kwargs):
|
|
116
|
+
"""
|
|
117
|
+
Invoke Blender's Python interpreter.
|
|
118
|
+
|
|
119
|
+
Args:
|
|
120
|
+
Arguments to pass to the python command.
|
|
121
|
+
|
|
122
|
+
**kwargs:
|
|
123
|
+
Keyword arguments passed through to subprocess.run().
|
|
124
|
+
|
|
125
|
+
Returns:
|
|
126
|
+
The return value of subprocess.run()
|
|
127
|
+
"""
|
|
128
|
+
return run((self.info["Python executable"], *args), **kwargs)
|
|
129
|
+
|
|
130
|
+
def validate(self):
|
|
131
|
+
"""
|
|
132
|
+
Validate the extension.
|
|
133
|
+
"""
|
|
134
|
+
try:
|
|
135
|
+
self.run_blender(["--command", "extension", "validate"])
|
|
136
|
+
except subprocess.CalledProcessError as err:
|
|
137
|
+
raise BlenderError(f"Failed to validate the extension: {err}") from err
|
|
138
|
+
LOGGER.info("The extension has been validated.")
|
|
139
|
+
|
|
140
|
+
def download_wheel_deps(self, wheels_dir="wheels"):
|
|
141
|
+
"""
|
|
142
|
+
Download the wheels of all external dependencies to the local wheel
|
|
143
|
+
directory and update the manifest to point to them.
|
|
144
|
+
"""
|
|
145
|
+
wheels_dir = self.ext_dir / wheels_dir
|
|
146
|
+
wheels_dir.mkdir(parents=True, exist_ok=True)
|
|
147
|
+
req_file = self.ext_dir / "requirements.txt"
|
|
148
|
+
LOGGER.info("Downloading wheels...")
|
|
149
|
+
try:
|
|
150
|
+
self.run_python(
|
|
151
|
+
(
|
|
152
|
+
"-m",
|
|
153
|
+
"pip",
|
|
154
|
+
"wheel",
|
|
155
|
+
"-w",
|
|
156
|
+
wheels_dir,
|
|
157
|
+
"-r",
|
|
158
|
+
req_file,
|
|
159
|
+
)
|
|
160
|
+
)
|
|
161
|
+
except subprocess.CalledProcessError as err:
|
|
162
|
+
raise PipError(f"Failed to download dependency wheels: {err}") from err
|
|
163
|
+
|
|
164
|
+
manifest_file = self.ext_dir / "blender_manifest.toml"
|
|
165
|
+
LOGGER.info("Loading %s", manifest_file)
|
|
166
|
+
with manifest_file.open("rb") as handle:
|
|
167
|
+
manifest = tomllib.load(handle)
|
|
168
|
+
manifest["wheels"] = sorted(
|
|
169
|
+
str(p.relative_to(manifest_file.parent)) for p in wheels_dir.glob("*.whl")
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
# Write the updated manifest to a temporary file to avoid truncating the
|
|
173
|
+
# original on error. The temporary file is moved into place on
|
|
174
|
+
# success.
|
|
175
|
+
manifest_tmp_file = manifest_file.with_suffix(".tmp.toml")
|
|
176
|
+
with manifest_tmp_file.open("wb") as handle:
|
|
177
|
+
tomli_w.dump(manifest, handle)
|
|
178
|
+
LOGGER.info("Saving updated wheels to %s", manifest_file)
|
|
179
|
+
shutil.move(manifest_tmp_file, manifest_file)
|
|
180
|
+
|
|
181
|
+
def build(self):
|
|
182
|
+
"""
|
|
183
|
+
Build the extension (i.e. the .zip file).
|
|
184
|
+
"""
|
|
185
|
+
try:
|
|
186
|
+
self.run_blender(["--command", "extension", "build"])
|
|
187
|
+
except subprocess.CalledProcessError as err:
|
|
188
|
+
raise BlenderError(f"Failed to build the extension: {err}") from err
|
|
189
|
+
LOGGER.info("The extension has been built.")
|
|
190
|
+
|
|
191
|
+
def pip(self, args, path=None, uv=False):
|
|
192
|
+
"""
|
|
193
|
+
Manage Python packages in Blender's module directory with pip or uv.
|
|
194
|
+
|
|
195
|
+
Args:
|
|
196
|
+
args:
|
|
197
|
+
Arguments to pass through to the pip command.
|
|
198
|
+
|
|
199
|
+
path:
|
|
200
|
+
The target directory. If None, the default location will be
|
|
201
|
+
used.
|
|
202
|
+
|
|
203
|
+
uv:
|
|
204
|
+
If True, use "uv pip" instead of pip.
|
|
205
|
+
"""
|
|
206
|
+
if path is None:
|
|
207
|
+
path = self.info["Blender module directory"]
|
|
208
|
+
else:
|
|
209
|
+
path = pathlib.Path(path).resolve()
|
|
210
|
+
LOGGER.info("Pip target directory: %s", path)
|
|
211
|
+
LOGGER.info("Creating temporary Python virtual environment.")
|
|
212
|
+
with tempfile.TemporaryDirectory() as tmp_dir:
|
|
213
|
+
tmp_dir = pathlib.Path(tmp_dir)
|
|
214
|
+
venv_dir = tmp_dir / "venv"
|
|
215
|
+
self.run_python(("-m", "venv", venv_dir))
|
|
216
|
+
py_exe = venv_dir / "bin/python"
|
|
217
|
+
LOGGER.info("Ensuring pip in the virtual environment.")
|
|
218
|
+
run([py_exe, "-m", "ensurepip", "-U"])
|
|
219
|
+
run([py_exe, "-m", "pip", "install", "-U", "pip"])
|
|
220
|
+
if uv:
|
|
221
|
+
LOGGER.info("Installing uv in virtual environment.")
|
|
222
|
+
run((py_exe, "-m", "pip", "install", "-U", "uv"))
|
|
223
|
+
cmd = ("uv", "pip")
|
|
224
|
+
else:
|
|
225
|
+
cmd = ("pip",)
|
|
226
|
+
try:
|
|
227
|
+
run((py_exe, "-m", *cmd, *args, "--target", path))
|
|
228
|
+
except subprocess.CalledProcessError as err:
|
|
229
|
+
raise PipError(f"Failed to run pip command: {err}") from err
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
"""Custom exceptions."""
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class BlenderPythonUtilsError(Exception):
|
|
6
|
+
"""Base class for custom exceptions raised by this module."""
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class SubprocessError(BlenderPythonUtilsError):
|
|
10
|
+
"""Errors raised by subprocess calls."""
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class BlenderError(SubprocessError):
|
|
14
|
+
"""Errors raised when invoking the Blender executable."""
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class PipError(SubprocessError):
|
|
18
|
+
"""Errors raised when invoking pip."""
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Utility script for validating and packaging Blender extensions."""
|
|
3
|
+
|
|
4
|
+
import argparse
|
|
5
|
+
import logging
|
|
6
|
+
import pathlib
|
|
7
|
+
import sys
|
|
8
|
+
|
|
9
|
+
from .blender_wrapper import BlenderWrapper
|
|
10
|
+
from .exceptions import BlenderPythonUtilsError
|
|
11
|
+
|
|
12
|
+
LOGGER = logging.getLogger(__name__)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def configure_logging(level=logging.INFO):
|
|
16
|
+
"""
|
|
17
|
+
Configure logging.
|
|
18
|
+
|
|
19
|
+
Args:
|
|
20
|
+
level:
|
|
21
|
+
The logging level.
|
|
22
|
+
"""
|
|
23
|
+
logging.basicConfig(
|
|
24
|
+
style="{",
|
|
25
|
+
format="[{asctime}] {levelname} {message}",
|
|
26
|
+
datefmt="%Y-%m-%d %H:%M:%S",
|
|
27
|
+
level=level,
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class CommandRunner:
|
|
32
|
+
"""
|
|
33
|
+
Run selected commands. The only real purpose of this class is to avoid
|
|
34
|
+
redundant subprocess calls to retrieve Blender information.
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
def __init__(self):
|
|
38
|
+
self.blender = BlenderWrapper()
|
|
39
|
+
|
|
40
|
+
def info(self, _pargs):
|
|
41
|
+
"""
|
|
42
|
+
Print Blender information.
|
|
43
|
+
"""
|
|
44
|
+
for key, value in sorted(self.blender.info.items()):
|
|
45
|
+
print(f"{key}: {value}")
|
|
46
|
+
|
|
47
|
+
def build(self, pargs):
|
|
48
|
+
"""
|
|
49
|
+
Validate and build an extension. If a requirements.txt file is found in the
|
|
50
|
+
extension directory then the wheels for its dependencies will be downloaded
|
|
51
|
+
to the wheels directory and the manifest will be updated to include them.
|
|
52
|
+
"""
|
|
53
|
+
bld = self.blender
|
|
54
|
+
bld.ext_dir = pargs.path
|
|
55
|
+
bld.download_wheel_deps()
|
|
56
|
+
bld.validate()
|
|
57
|
+
bld.build()
|
|
58
|
+
|
|
59
|
+
def pip(self, pargs):
|
|
60
|
+
"""
|
|
61
|
+
Install Python packages to Blender's module directory. Additional
|
|
62
|
+
arguments as passed through to pip.
|
|
63
|
+
"""
|
|
64
|
+
self.blender.pip(pargs.pip_args, path=pargs.path, uv=pargs.uv)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def main(args=None):
|
|
68
|
+
"""
|
|
69
|
+
Main function.
|
|
70
|
+
|
|
71
|
+
Args:
|
|
72
|
+
args:
|
|
73
|
+
Passed through to ArgumentParser.parse_args().
|
|
74
|
+
"""
|
|
75
|
+
cmd_runner = CommandRunner()
|
|
76
|
+
|
|
77
|
+
parser = argparse.ArgumentParser(description=__doc__)
|
|
78
|
+
subparsers = parser.add_subparsers(required=True)
|
|
79
|
+
|
|
80
|
+
parser_info = subparsers.add_parser("info", description=cmd_runner.info.__doc__)
|
|
81
|
+
parser_info.set_defaults(func=cmd_runner.info)
|
|
82
|
+
|
|
83
|
+
parser_build = subparsers.add_parser("build", description=cmd_runner.build.__doc__)
|
|
84
|
+
parser_build.add_argument(
|
|
85
|
+
"-p",
|
|
86
|
+
"--path",
|
|
87
|
+
type=pathlib.Path,
|
|
88
|
+
help=(
|
|
89
|
+
"The path to the extension's root directory. "
|
|
90
|
+
"If not given, the current working directory is assumed."
|
|
91
|
+
),
|
|
92
|
+
)
|
|
93
|
+
parser_build.set_defaults(func=cmd_runner.build)
|
|
94
|
+
|
|
95
|
+
parser_pip = subparsers.add_parser("pip", description=cmd_runner.pip.__doc__)
|
|
96
|
+
parser_pip.add_argument(
|
|
97
|
+
"--path",
|
|
98
|
+
type=pathlib.Path,
|
|
99
|
+
help=(
|
|
100
|
+
"Installation directory for Python packages. "
|
|
101
|
+
"If not given, the default Blender module directory will be used. "
|
|
102
|
+
'Use the "info" command to show the path to the module directory.'
|
|
103
|
+
),
|
|
104
|
+
)
|
|
105
|
+
parser_pip.add_argument(
|
|
106
|
+
"--uv", action="store_true", help='Use "uv pip" instead of pip.'
|
|
107
|
+
)
|
|
108
|
+
parser_pip.add_argument(
|
|
109
|
+
"pip_args",
|
|
110
|
+
nargs="+",
|
|
111
|
+
help=(
|
|
112
|
+
"Arguments to pass through to pip. "
|
|
113
|
+
'Precede these arguments with "--" if any of them begin with "-".'
|
|
114
|
+
),
|
|
115
|
+
metavar="<PIP ARG>",
|
|
116
|
+
)
|
|
117
|
+
parser_pip.set_defaults(func=cmd_runner.pip)
|
|
118
|
+
|
|
119
|
+
pargs = parser.parse_args(args)
|
|
120
|
+
pargs.func(pargs)
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def run_main(*args, **kwargs):
|
|
124
|
+
"""
|
|
125
|
+
Run the main function with exception handling.
|
|
126
|
+
|
|
127
|
+
Args:
|
|
128
|
+
*args:
|
|
129
|
+
Positional arguments passed through to main().
|
|
130
|
+
|
|
131
|
+
**kwargs:
|
|
132
|
+
Keyword arguments passed through to main().
|
|
133
|
+
"""
|
|
134
|
+
configure_logging()
|
|
135
|
+
try:
|
|
136
|
+
main(*args, **kwargs)
|
|
137
|
+
except BlenderPythonUtilsError as err:
|
|
138
|
+
sys.exit(err)
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
if __name__ == "__main__":
|
|
142
|
+
run_main()
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Utility functions."""
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
import subprocess
|
|
7
|
+
|
|
8
|
+
LOGGER = logging.getLogger(__name__)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def run(cmd, **kwargs):
|
|
12
|
+
"""
|
|
13
|
+
Run a command with subprocess.run.
|
|
14
|
+
|
|
15
|
+
Args:
|
|
16
|
+
cmd:
|
|
17
|
+
The command to run.
|
|
18
|
+
|
|
19
|
+
**kwargs:
|
|
20
|
+
Keyword arguments passed through to subprocess.run().
|
|
21
|
+
|
|
22
|
+
Returns:
|
|
23
|
+
The return value of subprocess.run().
|
|
24
|
+
"""
|
|
25
|
+
cmd = [str(w) for w in cmd]
|
|
26
|
+
LOGGER.debug("Running command: %s", cmd)
|
|
27
|
+
kwargs["check"] = kwargs.get("check", True)
|
|
28
|
+
# pylint: disable=subprocess-run-check
|
|
29
|
+
return subprocess.run(cmd, **kwargs)
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
'''
|
|
3
|
+
Version file automatically generated by setuptools-scm via hatch-vcs.
|
|
4
|
+
https://pypi.org/project/setuptools-scm/
|
|
5
|
+
https://github.com/ofek/hatch-vcs
|
|
6
|
+
Do not track this file and do not edit it manually.
|
|
7
|
+
'''
|
|
8
|
+
VERSION = '2025.2'
|
|
9
|
+
VERSION_TUPLE = (2025, 2)
|