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.
@@ -0,0 +1,15 @@
1
+ *.egg-info/
2
+ *.pyc
3
+ __pycache__/
4
+ dist/
5
+
6
+ tmp/
7
+ venv/
8
+
9
+ *.log
10
+ version.py
11
+
12
+ /doc/source/*.rst
13
+ !/doc/source/index.rst
14
+
15
+ /public/
@@ -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
+ [![Hatch](https://img.shields.io/badge/%F0%9F%A5%9A-Hatch-4051b5.svg)](https://github.com/pypa/hatch) [![Latest Release](https://gitlab.inria.fr/jrye/blender-pyutils/-/badges/release.svg)](https://gitlab.inria.fr/jrye/blender-pyutils/-/tags) [![License](https://img.shields.io/badge/license-MIT-9400d3.svg)](https://spdx.org/licenses/MIT.html) [![Pipeline Status](https://gitlab.inria.fr/jrye/blender-pyutils/badges/main/pipeline.svg)](https://gitlab.inria.fr/jrye/blender-pyutils/-/commits/main) [![Pylint](https://gitlab.inria.fr/jrye/blender-pyutils/-/jobs/artifacts/main/raw/pylint/pylint.svg?job=pylint)](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
+ [![Hatch](https://img.shields.io/badge/%F0%9F%A5%9A-Hatch-4051b5.svg)](https://github.com/pypa/hatch) [![Latest Release](https://gitlab.inria.fr/jrye/blender-pyutils/-/badges/release.svg)](https://gitlab.inria.fr/jrye/blender-pyutils/-/tags) [![License](https://img.shields.io/badge/license-MIT-9400d3.svg)](https://spdx.org/licenses/MIT.html) [![Pipeline Status](https://gitlab.inria.fr/jrye/blender-pyutils/badges/main/pipeline.svg)](https://gitlab.inria.fr/jrye/blender-pyutils/-/commits/main) [![Pylint](https://gitlab.inria.fr/jrye/blender-pyutils/-/jobs/artifacts/main/raw/pylint/pylint.svg?job=pylint)](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,3 @@
1
+ myst-parser
2
+ sphinx
3
+ sphinx-rtd-theme
@@ -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,2 @@
1
+ ```{include} ../../README.md
2
+ ```
@@ -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,2 @@
1
+ #!/usr/bin/env python3
2
+ """Package stub."""
@@ -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,5 @@
1
+ #!/usr/bin/env python3
2
+ """Common constants."""
3
+
4
+
5
+ ENCODING = "utf-8"
@@ -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)