notso-glb 0.1.0__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.
- notso_glb-0.1.0/PKG-INFO +150 -0
- notso_glb-0.1.0/README.md +124 -0
- notso_glb-0.1.0/pyproject.toml +90 -0
- notso_glb-0.1.0/src/notso_glb/__init__.py +38 -0
- notso_glb-0.1.0/src/notso_glb/__main__.py +6 -0
- notso_glb-0.1.0/src/notso_glb/analyzers/__init__.py +20 -0
- notso_glb-0.1.0/src/notso_glb/analyzers/bloat.py +117 -0
- notso_glb-0.1.0/src/notso_glb/analyzers/bones.py +100 -0
- notso_glb-0.1.0/src/notso_glb/analyzers/duplicates.py +71 -0
- notso_glb-0.1.0/src/notso_glb/analyzers/skinned_mesh.py +47 -0
- notso_glb-0.1.0/src/notso_glb/analyzers/uv_maps.py +59 -0
- notso_glb-0.1.0/src/notso_glb/cleaners/__init__.py +23 -0
- notso_glb-0.1.0/src/notso_glb/cleaners/bones.py +49 -0
- notso_glb-0.1.0/src/notso_glb/cleaners/duplicates.py +110 -0
- notso_glb-0.1.0/src/notso_glb/cleaners/mesh.py +183 -0
- notso_glb-0.1.0/src/notso_glb/cleaners/textures.py +116 -0
- notso_glb-0.1.0/src/notso_glb/cleaners/uv_maps.py +29 -0
- notso_glb-0.1.0/src/notso_glb/cleaners/vertex_groups.py +34 -0
- notso_glb-0.1.0/src/notso_glb/cli.py +330 -0
- notso_glb-0.1.0/src/notso_glb/exporters/__init__.py +8 -0
- notso_glb-0.1.0/src/notso_glb/exporters/gltf.py +647 -0
- notso_glb-0.1.0/src/notso_glb/utils/__init__.py +20 -0
- notso_glb-0.1.0/src/notso_glb/utils/blender.py +49 -0
- notso_glb-0.1.0/src/notso_glb/utils/constants.py +41 -0
- notso_glb-0.1.0/src/notso_glb/utils/gltfpack.py +273 -0
- notso_glb-0.1.0/src/notso_glb/utils/logging.py +421 -0
- notso_glb-0.1.0/src/notso_glb/utils/naming.py +24 -0
- notso_glb-0.1.0/src/notso_glb/wasm/__init__.py +32 -0
- notso_glb-0.1.0/src/notso_glb/wasm/constants.py +8 -0
- notso_glb-0.1.0/src/notso_glb/wasm/gltfpack.version +1 -0
- notso_glb-0.1.0/src/notso_glb/wasm/gltfpack.wasm +0 -0
- notso_glb-0.1.0/src/notso_glb/wasm/py.typed +0 -0
- notso_glb-0.1.0/src/notso_glb/wasm/runner.py +137 -0
- notso_glb-0.1.0/src/notso_glb/wasm/runtime.py +244 -0
- notso_glb-0.1.0/src/notso_glb/wasm/wasi.py +347 -0
notso_glb-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: notso-glb
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: GLB Export Optimizer for Mascot Models
|
|
5
|
+
Keywords: 3d,blender,compression,draco,glb,gltf,mascot,optimization,webgl
|
|
6
|
+
Author: Kaj Kowalski
|
|
7
|
+
Author-email: Kaj Kowalski <info@kajkowalski.nl>
|
|
8
|
+
Classifier: Development Status :: 4 - Beta
|
|
9
|
+
Classifier: Environment :: Console
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: License :: OSI Approved :: GNU General Public License v3 (GPLv3)
|
|
12
|
+
Classifier: Operating System :: OS Independent
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Topic :: Multimedia :: Graphics :: 3D Modeling
|
|
16
|
+
Classifier: Topic :: Multimedia :: Graphics :: Graphics Conversion
|
|
17
|
+
Requires-Dist: bpy>=5,<6 ; python_full_version == '3.11.*'
|
|
18
|
+
Requires-Dist: rich>=14.2.0
|
|
19
|
+
Requires-Dist: typer>=0.20.1
|
|
20
|
+
Requires-Dist: wasmtime>=41.0.0
|
|
21
|
+
Requires-Python: ~=3.11.0
|
|
22
|
+
Project-URL: Homepage, https://github.com/kjanat/notso-glb
|
|
23
|
+
Project-URL: Issues, https://github.com/kjanat/notso-glb/issues
|
|
24
|
+
Project-URL: Repository, https://github.com/kjanat/notso-glb.git
|
|
25
|
+
Description-Content-Type: text/markdown
|
|
26
|
+
|
|
27
|
+
# GLB Export Optimizer for Mascot Models
|
|
28
|
+
|
|
29
|
+
> Cleans up Blender files and exports optimized GLB for web delivery.
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
uvx notso-glb [OPTIONS] FILE
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
<p align="center">
|
|
36
|
+
<a href="https://github.com/kjanat/notso-glb/blob/master/CLI.md" target="_blank" rel="noopener" title="View CLI Options">
|
|
37
|
+
<img alt="Screenshot with cli options" width="100%" src="https://raw.githubusercontent.com/kjanat/notso-glb/refs/heads/master/screenshot.webp">
|
|
38
|
+
</a>
|
|
39
|
+
</p>
|
|
40
|
+
|
|
41
|
+
<p align="center">
|
|
42
|
+
<a href="https://pypi.org/project/notso-glb/"><img src="https://img.shields.io/pypi/v/notso-glb" alt="PyPI"></a>
|
|
43
|
+
<a href="https://pypi.org/project/notso-glb/"><img src="https://img.shields.io/pypi/dm/notso-glb" alt="Downloads"></a> <!--<a href="https://github.com/kjanat/notso-glb/actions/workflows/ci.yml"><img src="https://img.shields.io/github/actions/workflow/status/kjanat/notso-glb/ci.yml?branch=master" alt="CI"></a>-->
|
|
44
|
+
<a href="https://github.com/kjanat/notso-glb/blob/master/LICENSE"><img src="https://img.shields.io/github/license/kjanat/notso-glb" alt="License"></a> <!--<a href="https://notso-glb.kjanat.com"><img src="https://img.shields.io/badge/docs-mkdocs-blue" alt="Docs"></a>-->
|
|
45
|
+
<img src="https://img.shields.io/badge/python-3.11-blue" alt="Python 3.11">
|
|
46
|
+
</p>
|
|
47
|
+
|
|
48
|
+
## Install
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
uv tool install notso-glb
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
# Or install directly from GitHub:
|
|
56
|
+
uv tool install -p3.11 git+https://github.com/kjanat/notso-glb
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
then just run `notso-glb` from the command line.
|
|
60
|
+
|
|
61
|
+
### Upgrade
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
uv tool upgrade notso-glb
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Features
|
|
68
|
+
|
|
69
|
+
Optimizations:
|
|
70
|
+
|
|
71
|
+
- Detects bloated props (high-vert non-skinned meshes, repetitive geometry)
|
|
72
|
+
- Detects skinned meshes with non-root parents (glTF spec issue)
|
|
73
|
+
- Detects unused UV maps (`TEXCOORD` bloat)
|
|
74
|
+
- Detects duplicate names and sanitization collisions
|
|
75
|
+
- Removes unused vertex groups (bone weight bloat)
|
|
76
|
+
- Marks static bones as non-deform (animation bloat)
|
|
77
|
+
- Removes bone shape objects (Icosphere artifacts)
|
|
78
|
+
- Resizes textures to max 1024px (optional `POT` enforcement)
|
|
79
|
+
- Exports with Draco mesh compression
|
|
80
|
+
- Exports with WebP textures
|
|
81
|
+
|
|
82
|
+
Bloat Detection:
|
|
83
|
+
|
|
84
|
+
- CRITICAL: Props >2000 verts, repetitive detail (many islands with high verts)
|
|
85
|
+
- WARNING: Props >1000 verts, scene total >15000 verts, non-root skinned meshes
|
|
86
|
+
|
|
87
|
+
Experimental Auto-fix (`--autofix`):
|
|
88
|
+
|
|
89
|
+
- BMesh cleanup (remove doubles, degenerate geometry, loose verts)
|
|
90
|
+
- Decimate bloated props to ~1600 verts
|
|
91
|
+
- Auto-rename duplicate objects/meshes/materials/actions (using pointer ID)
|
|
92
|
+
- Remove unused UV maps
|
|
93
|
+
|
|
94
|
+
## Usage
|
|
95
|
+
|
|
96
|
+
See [CLI.md]
|
|
97
|
+
|
|
98
|
+
## Requirements
|
|
99
|
+
|
|
100
|
+
- Blender 5.0+
|
|
101
|
+
- Python 3.11 (same as bundled with Blender)
|
|
102
|
+
- uv (optional, for easy install/upgrade)
|
|
103
|
+
- gltfpack (optional, for extra compression - WASM fallback included)
|
|
104
|
+
|
|
105
|
+
## Development Setup
|
|
106
|
+
|
|
107
|
+
For local development, download the gltfpack WASM binary:
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
# Download latest WASM from npm
|
|
111
|
+
uv run scripts/update_wasm.py
|
|
112
|
+
|
|
113
|
+
# Or check current version
|
|
114
|
+
uv run scripts/update_wasm.py --show-version
|
|
115
|
+
|
|
116
|
+
# Download specific version
|
|
117
|
+
uv run scripts/update_wasm.py --version 1.0.0
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
The WASM binary (`src/notso_glb/wasm/gltfpack.wasm`) is not committed to git and
|
|
121
|
+
must be downloaded locally. CI/CD pipelines handle this automatically.
|
|
122
|
+
|
|
123
|
+
## Useful Links
|
|
124
|
+
|
|
125
|
+
- [glTF 2.0 Specification]
|
|
126
|
+
- [glTF 2.0 API Reference Guide]
|
|
127
|
+
- [Khronos Resources]
|
|
128
|
+
- [Blender 5.0 glTF 2.0]
|
|
129
|
+
- [Blender 5.0 Python API Documentation]
|
|
130
|
+
- [Blender 5.0 Reference Manual]
|
|
131
|
+
|
|
132
|
+
## License
|
|
133
|
+
|
|
134
|
+
This project is licensed under the GNU General Public License v3.0 - see the
|
|
135
|
+
[LICENSE] file for details.
|
|
136
|
+
|
|
137
|
+
This project uses [Blender] as a Python module (bpy), which is also GPL-3.0
|
|
138
|
+
licensed.
|
|
139
|
+
|
|
140
|
+
[Khronos Resources]: https://github.khronos.org/
|
|
141
|
+
[glTF 2.0 Specification]: https://www.khronos.org/gltf/#gltf-spec
|
|
142
|
+
[glTF 2.0 API Reference Guide]: https://www.khronos.org/files/gltf20-reference-guide.pdf
|
|
143
|
+
[Blender 5.0 Reference Manual]: https://docs.blender.org/manual/en/latest
|
|
144
|
+
[Blender 5.0 glTF 2.0]: https://docs.blender.org/manual/en/5.0/addons/import_export/scene_gltf2.html
|
|
145
|
+
[Blender 5.0 Python API Documentation]: https://docs.blender.org/api/current/index.html
|
|
146
|
+
[Blender]: https://www.blender.org/
|
|
147
|
+
[CLI.md]: https://github.com/kjanat/notso-glb/blob/master/CLI.md
|
|
148
|
+
[LICENSE]: https://github.com/kjanat/notso-glb/blob/master/LICENSE
|
|
149
|
+
|
|
150
|
+
<!-- markdownlint-disable-file MD033 -->
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# GLB Export Optimizer for Mascot Models
|
|
2
|
+
|
|
3
|
+
> Cleans up Blender files and exports optimized GLB for web delivery.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
uvx notso-glb [OPTIONS] FILE
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
<p align="center">
|
|
10
|
+
<a href="https://github.com/kjanat/notso-glb/blob/master/CLI.md" target="_blank" rel="noopener" title="View CLI Options">
|
|
11
|
+
<img alt="Screenshot with cli options" width="100%" src="https://raw.githubusercontent.com/kjanat/notso-glb/refs/heads/master/screenshot.webp">
|
|
12
|
+
</a>
|
|
13
|
+
</p>
|
|
14
|
+
|
|
15
|
+
<p align="center">
|
|
16
|
+
<a href="https://pypi.org/project/notso-glb/"><img src="https://img.shields.io/pypi/v/notso-glb" alt="PyPI"></a>
|
|
17
|
+
<a href="https://pypi.org/project/notso-glb/"><img src="https://img.shields.io/pypi/dm/notso-glb" alt="Downloads"></a> <!--<a href="https://github.com/kjanat/notso-glb/actions/workflows/ci.yml"><img src="https://img.shields.io/github/actions/workflow/status/kjanat/notso-glb/ci.yml?branch=master" alt="CI"></a>-->
|
|
18
|
+
<a href="https://github.com/kjanat/notso-glb/blob/master/LICENSE"><img src="https://img.shields.io/github/license/kjanat/notso-glb" alt="License"></a> <!--<a href="https://notso-glb.kjanat.com"><img src="https://img.shields.io/badge/docs-mkdocs-blue" alt="Docs"></a>-->
|
|
19
|
+
<img src="https://img.shields.io/badge/python-3.11-blue" alt="Python 3.11">
|
|
20
|
+
</p>
|
|
21
|
+
|
|
22
|
+
## Install
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
uv tool install notso-glb
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
# Or install directly from GitHub:
|
|
30
|
+
uv tool install -p3.11 git+https://github.com/kjanat/notso-glb
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
then just run `notso-glb` from the command line.
|
|
34
|
+
|
|
35
|
+
### Upgrade
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
uv tool upgrade notso-glb
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Features
|
|
42
|
+
|
|
43
|
+
Optimizations:
|
|
44
|
+
|
|
45
|
+
- Detects bloated props (high-vert non-skinned meshes, repetitive geometry)
|
|
46
|
+
- Detects skinned meshes with non-root parents (glTF spec issue)
|
|
47
|
+
- Detects unused UV maps (`TEXCOORD` bloat)
|
|
48
|
+
- Detects duplicate names and sanitization collisions
|
|
49
|
+
- Removes unused vertex groups (bone weight bloat)
|
|
50
|
+
- Marks static bones as non-deform (animation bloat)
|
|
51
|
+
- Removes bone shape objects (Icosphere artifacts)
|
|
52
|
+
- Resizes textures to max 1024px (optional `POT` enforcement)
|
|
53
|
+
- Exports with Draco mesh compression
|
|
54
|
+
- Exports with WebP textures
|
|
55
|
+
|
|
56
|
+
Bloat Detection:
|
|
57
|
+
|
|
58
|
+
- CRITICAL: Props >2000 verts, repetitive detail (many islands with high verts)
|
|
59
|
+
- WARNING: Props >1000 verts, scene total >15000 verts, non-root skinned meshes
|
|
60
|
+
|
|
61
|
+
Experimental Auto-fix (`--autofix`):
|
|
62
|
+
|
|
63
|
+
- BMesh cleanup (remove doubles, degenerate geometry, loose verts)
|
|
64
|
+
- Decimate bloated props to ~1600 verts
|
|
65
|
+
- Auto-rename duplicate objects/meshes/materials/actions (using pointer ID)
|
|
66
|
+
- Remove unused UV maps
|
|
67
|
+
|
|
68
|
+
## Usage
|
|
69
|
+
|
|
70
|
+
See [CLI.md]
|
|
71
|
+
|
|
72
|
+
## Requirements
|
|
73
|
+
|
|
74
|
+
- Blender 5.0+
|
|
75
|
+
- Python 3.11 (same as bundled with Blender)
|
|
76
|
+
- uv (optional, for easy install/upgrade)
|
|
77
|
+
- gltfpack (optional, for extra compression - WASM fallback included)
|
|
78
|
+
|
|
79
|
+
## Development Setup
|
|
80
|
+
|
|
81
|
+
For local development, download the gltfpack WASM binary:
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
# Download latest WASM from npm
|
|
85
|
+
uv run scripts/update_wasm.py
|
|
86
|
+
|
|
87
|
+
# Or check current version
|
|
88
|
+
uv run scripts/update_wasm.py --show-version
|
|
89
|
+
|
|
90
|
+
# Download specific version
|
|
91
|
+
uv run scripts/update_wasm.py --version 1.0.0
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
The WASM binary (`src/notso_glb/wasm/gltfpack.wasm`) is not committed to git and
|
|
95
|
+
must be downloaded locally. CI/CD pipelines handle this automatically.
|
|
96
|
+
|
|
97
|
+
## Useful Links
|
|
98
|
+
|
|
99
|
+
- [glTF 2.0 Specification]
|
|
100
|
+
- [glTF 2.0 API Reference Guide]
|
|
101
|
+
- [Khronos Resources]
|
|
102
|
+
- [Blender 5.0 glTF 2.0]
|
|
103
|
+
- [Blender 5.0 Python API Documentation]
|
|
104
|
+
- [Blender 5.0 Reference Manual]
|
|
105
|
+
|
|
106
|
+
## License
|
|
107
|
+
|
|
108
|
+
This project is licensed under the GNU General Public License v3.0 - see the
|
|
109
|
+
[LICENSE] file for details.
|
|
110
|
+
|
|
111
|
+
This project uses [Blender] as a Python module (bpy), which is also GPL-3.0
|
|
112
|
+
licensed.
|
|
113
|
+
|
|
114
|
+
[Khronos Resources]: https://github.khronos.org/
|
|
115
|
+
[glTF 2.0 Specification]: https://www.khronos.org/gltf/#gltf-spec
|
|
116
|
+
[glTF 2.0 API Reference Guide]: https://www.khronos.org/files/gltf20-reference-guide.pdf
|
|
117
|
+
[Blender 5.0 Reference Manual]: https://docs.blender.org/manual/en/latest
|
|
118
|
+
[Blender 5.0 glTF 2.0]: https://docs.blender.org/manual/en/5.0/addons/import_export/scene_gltf2.html
|
|
119
|
+
[Blender 5.0 Python API Documentation]: https://docs.blender.org/api/current/index.html
|
|
120
|
+
[Blender]: https://www.blender.org/
|
|
121
|
+
[CLI.md]: https://github.com/kjanat/notso-glb/blob/master/CLI.md
|
|
122
|
+
[LICENSE]: https://github.com/kjanat/notso-glb/blob/master/LICENSE
|
|
123
|
+
|
|
124
|
+
<!-- markdownlint-disable-file MD033 -->
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "notso-glb"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "GLB Export Optimizer for Mascot Models"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = "~=3.11.0"
|
|
7
|
+
authors = [{ name = "Kaj Kowalski", email = "info@kajkowalski.nl" }]
|
|
8
|
+
keywords = ["3d", "blender", "compression", "draco", "glb", "gltf", "mascot", "optimization", "webgl"]
|
|
9
|
+
classifiers = [
|
|
10
|
+
"Development Status :: 4 - Beta",
|
|
11
|
+
"Environment :: Console",
|
|
12
|
+
"Intended Audience :: Developers",
|
|
13
|
+
"License :: OSI Approved :: GNU General Public License v3 (GPLv3)",
|
|
14
|
+
"Operating System :: OS Independent",
|
|
15
|
+
"Programming Language :: Python :: 3",
|
|
16
|
+
"Programming Language :: Python :: 3.11",
|
|
17
|
+
"Topic :: Multimedia :: Graphics :: 3D Modeling",
|
|
18
|
+
"Topic :: Multimedia :: Graphics :: Graphics Conversion"
|
|
19
|
+
]
|
|
20
|
+
dependencies = [
|
|
21
|
+
"bpy>=5,<6; python_version ~= '3.11.0'",
|
|
22
|
+
"rich>=14.2.0",
|
|
23
|
+
"typer>=0.20.1",
|
|
24
|
+
"wasmtime>=41.0.0",
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
[project.urls]
|
|
28
|
+
Homepage = "https://github.com/kjanat/notso-glb"
|
|
29
|
+
Issues = "https://github.com/kjanat/notso-glb/issues"
|
|
30
|
+
Repository = "https://github.com/kjanat/notso-glb.git"
|
|
31
|
+
|
|
32
|
+
[project.scripts]
|
|
33
|
+
notso-glb = "notso_glb:main"
|
|
34
|
+
|
|
35
|
+
[dependency-groups]
|
|
36
|
+
dev = [
|
|
37
|
+
{ include-group = "lint" },
|
|
38
|
+
{ include-group = "test" },
|
|
39
|
+
{ include-group = "types" },
|
|
40
|
+
]
|
|
41
|
+
lint = [
|
|
42
|
+
"ruff>=0.14.10",
|
|
43
|
+
"tombi>=0.7.26",
|
|
44
|
+
"ty>=0.0.5",
|
|
45
|
+
]
|
|
46
|
+
test = [
|
|
47
|
+
"pytest>=9.0.2",
|
|
48
|
+
"pytest-cov>=7.0.0",
|
|
49
|
+
]
|
|
50
|
+
types = [
|
|
51
|
+
"fake-bpy-module>=20251003",
|
|
52
|
+
"ty>=0.0.5",
|
|
53
|
+
]
|
|
54
|
+
|
|
55
|
+
[build-system]
|
|
56
|
+
requires = ["uv_build>=0.9.18,<0.10.0"]
|
|
57
|
+
build-backend = "uv_build"
|
|
58
|
+
|
|
59
|
+
[tool.pytest]
|
|
60
|
+
addopts = ["--cov", "--import-mode=importlib", "-ra"]
|
|
61
|
+
minversion = "9.0"
|
|
62
|
+
strict = true
|
|
63
|
+
testpaths = ["tests"]
|
|
64
|
+
|
|
65
|
+
[tool.ruff]
|
|
66
|
+
line-length = 88
|
|
67
|
+
indent-width = 4
|
|
68
|
+
target-version = "py311"
|
|
69
|
+
|
|
70
|
+
[tool.ruff.lint]
|
|
71
|
+
preview = true
|
|
72
|
+
select = ["E4", "E7", "E9", "F", "F", "B", "UP"]
|
|
73
|
+
ignore = []
|
|
74
|
+
fixable = ["ALL"]
|
|
75
|
+
unfixable = []
|
|
76
|
+
|
|
77
|
+
[tool.ruff.format]
|
|
78
|
+
preview = true
|
|
79
|
+
quote-style = "double"
|
|
80
|
+
indent-style = "space"
|
|
81
|
+
skip-magic-trailing-comma = false
|
|
82
|
+
line-ending = "lf"
|
|
83
|
+
docstring-code-format = true
|
|
84
|
+
docstring-code-line-length = "dynamic"
|
|
85
|
+
|
|
86
|
+
[tool.uv]
|
|
87
|
+
default-groups = "all"
|
|
88
|
+
|
|
89
|
+
[tool.uv.build-backend]
|
|
90
|
+
source-include = ["src/notso_glb/wasm/**"]
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"""
|
|
2
|
+
GLB Export Optimizer for Mascot Models
|
|
3
|
+
======================================
|
|
4
|
+
Cleans up Blender files and exports optimized GLB for web delivery.
|
|
5
|
+
|
|
6
|
+
Optimizations:
|
|
7
|
+
- Detects bloated props (high-vert non-skinned meshes, repetitive geometry)
|
|
8
|
+
- Detects skinned meshes with non-root parents (glTF spec issue)
|
|
9
|
+
- Detects unused UV maps (TEXCOORD bloat)
|
|
10
|
+
- Detects duplicate names and sanitization collisions
|
|
11
|
+
- Removes unused vertex groups (bone weight bloat)
|
|
12
|
+
- Marks static bones as non-deform (animation bloat)
|
|
13
|
+
- Removes bone shape objects (Icosphere artifacts)
|
|
14
|
+
- Resizes textures to max 1024px (optional POT enforcement)
|
|
15
|
+
- Exports with Draco mesh compression
|
|
16
|
+
- Exports with WebP textures
|
|
17
|
+
|
|
18
|
+
Usage:
|
|
19
|
+
CLI:
|
|
20
|
+
notso-glb model.glb -o output.glb
|
|
21
|
+
notso-glb model.blend --format gltf-embedded
|
|
22
|
+
notso-glb model.gltf --no-draco --max-texture 2048
|
|
23
|
+
|
|
24
|
+
Python:
|
|
25
|
+
from notso_glb import main
|
|
26
|
+
main()
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
from importlib.metadata import PackageNotFoundError, version
|
|
30
|
+
|
|
31
|
+
from notso_glb.cli import main
|
|
32
|
+
|
|
33
|
+
try:
|
|
34
|
+
__version__ = version("notso-glb")
|
|
35
|
+
except PackageNotFoundError:
|
|
36
|
+
__version__ = "unknown"
|
|
37
|
+
|
|
38
|
+
__all__ = ["main"]
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""Analyzers for mesh bloat, bones, duplicates, and UV maps."""
|
|
2
|
+
|
|
3
|
+
from notso_glb.analyzers.bloat import analyze_mesh_bloat, count_mesh_islands
|
|
4
|
+
from notso_glb.analyzers.bones import (
|
|
5
|
+
analyze_bone_animation,
|
|
6
|
+
get_bones_used_for_skinning,
|
|
7
|
+
)
|
|
8
|
+
from notso_glb.analyzers.duplicates import analyze_duplicate_names
|
|
9
|
+
from notso_glb.analyzers.skinned_mesh import analyze_skinned_mesh_parents
|
|
10
|
+
from notso_glb.analyzers.uv_maps import analyze_unused_uv_maps
|
|
11
|
+
|
|
12
|
+
__all__ = [
|
|
13
|
+
"analyze_bone_animation",
|
|
14
|
+
"analyze_duplicate_names",
|
|
15
|
+
"analyze_mesh_bloat",
|
|
16
|
+
"analyze_skinned_mesh_parents",
|
|
17
|
+
"analyze_unused_uv_maps",
|
|
18
|
+
"count_mesh_islands",
|
|
19
|
+
"get_bones_used_for_skinning",
|
|
20
|
+
]
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
"""Mesh bloat analysis for detecting overly complex geometry."""
|
|
2
|
+
|
|
3
|
+
import bpy
|
|
4
|
+
|
|
5
|
+
from notso_glb.utils import get_mesh_data
|
|
6
|
+
from notso_glb.utils.constants import BLOAT_THRESHOLDS
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def count_mesh_islands(obj) -> int:
|
|
10
|
+
"""Count disconnected mesh parts (islands) using BFS."""
|
|
11
|
+
import bmesh
|
|
12
|
+
|
|
13
|
+
bm = bmesh.new()
|
|
14
|
+
bm.from_mesh(obj.data)
|
|
15
|
+
bm.verts.ensure_lookup_table()
|
|
16
|
+
|
|
17
|
+
visited: set[int] = set()
|
|
18
|
+
islands = 0
|
|
19
|
+
|
|
20
|
+
for v in bm.verts:
|
|
21
|
+
if v.index in visited:
|
|
22
|
+
continue
|
|
23
|
+
islands += 1
|
|
24
|
+
stack = [v]
|
|
25
|
+
while stack:
|
|
26
|
+
current = stack.pop()
|
|
27
|
+
if current.index in visited:
|
|
28
|
+
continue
|
|
29
|
+
visited.add(current.index)
|
|
30
|
+
for edge in current.link_edges:
|
|
31
|
+
other = edge.other_vert(current)
|
|
32
|
+
if other.index not in visited:
|
|
33
|
+
stack.append(other)
|
|
34
|
+
|
|
35
|
+
bm.free()
|
|
36
|
+
return islands
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def analyze_mesh_bloat() -> list[dict[str, object]]:
|
|
40
|
+
"""
|
|
41
|
+
Detect unreasonably complex meshes for web delivery.
|
|
42
|
+
|
|
43
|
+
Returns list of warnings with severity levels:
|
|
44
|
+
- CRITICAL: Must fix before web deployment
|
|
45
|
+
- WARNING: Should review, likely bloated
|
|
46
|
+
- INFO: Notable but may be intentional
|
|
47
|
+
"""
|
|
48
|
+
warnings: list[dict[str, object]] = []
|
|
49
|
+
|
|
50
|
+
total_verts = 0
|
|
51
|
+
for obj in bpy.data.objects:
|
|
52
|
+
if obj.type != "MESH":
|
|
53
|
+
continue
|
|
54
|
+
|
|
55
|
+
mesh = get_mesh_data(obj)
|
|
56
|
+
verts = len(mesh.vertices)
|
|
57
|
+
total_verts += verts
|
|
58
|
+
|
|
59
|
+
if verts < 100:
|
|
60
|
+
continue
|
|
61
|
+
|
|
62
|
+
# Check if skinned (character mesh vs prop)
|
|
63
|
+
is_skinned = any(mod.type == "ARMATURE" for mod in obj.modifiers)
|
|
64
|
+
|
|
65
|
+
# Count islands for non-skinned meshes (expensive operation)
|
|
66
|
+
islands = 1
|
|
67
|
+
if not is_skinned and verts < 20000:
|
|
68
|
+
islands = count_mesh_islands(obj)
|
|
69
|
+
|
|
70
|
+
verts_per_island = verts / max(islands, 1)
|
|
71
|
+
|
|
72
|
+
# Bloat detection rules
|
|
73
|
+
if not is_skinned:
|
|
74
|
+
if verts > BLOAT_THRESHOLDS["prop_critical"]:
|
|
75
|
+
warnings.append({
|
|
76
|
+
"severity": "CRITICAL",
|
|
77
|
+
"object": obj.name,
|
|
78
|
+
"issue": "BLOATED_PROP",
|
|
79
|
+
"detail": f"{verts:,} verts (limit: {BLOAT_THRESHOLDS['prop_critical']:,})",
|
|
80
|
+
"suggestion": "Decimate or replace with baked texture",
|
|
81
|
+
})
|
|
82
|
+
elif verts > BLOAT_THRESHOLDS["prop_warning"]:
|
|
83
|
+
warnings.append({
|
|
84
|
+
"severity": "WARNING",
|
|
85
|
+
"object": obj.name,
|
|
86
|
+
"issue": "HIGH_VERT_PROP",
|
|
87
|
+
"detail": f"{verts:,} verts",
|
|
88
|
+
"suggestion": "Consider simplifying",
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
if (
|
|
92
|
+
islands > BLOAT_THRESHOLDS["repetitive_islands"]
|
|
93
|
+
and verts_per_island > BLOAT_THRESHOLDS["repetitive_verts"]
|
|
94
|
+
):
|
|
95
|
+
warnings.append({
|
|
96
|
+
"severity": "CRITICAL",
|
|
97
|
+
"object": obj.name,
|
|
98
|
+
"issue": "REPETITIVE_DETAIL",
|
|
99
|
+
"detail": f"{islands} islands x {verts_per_island:.0f} verts each",
|
|
100
|
+
"suggestion": "Merge islands or use instancing/texture",
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
# Scene-level check
|
|
104
|
+
if total_verts > BLOAT_THRESHOLDS["scene_total"]:
|
|
105
|
+
warnings.append({
|
|
106
|
+
"severity": "WARNING",
|
|
107
|
+
"object": "SCENE",
|
|
108
|
+
"issue": "HIGH_TOTAL_VERTS",
|
|
109
|
+
"detail": f"{total_verts:,} verts (target: <{BLOAT_THRESHOLDS['scene_total']:,})",
|
|
110
|
+
"suggestion": "Review all meshes for optimization opportunities",
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
# Sort by severity
|
|
114
|
+
severity_order = {"CRITICAL": 0, "WARNING": 1, "INFO": 2}
|
|
115
|
+
warnings.sort(key=lambda w: severity_order.get(str(w["severity"]), 99))
|
|
116
|
+
|
|
117
|
+
return warnings
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
"""Bone animation analysis for detecting static bones."""
|
|
2
|
+
|
|
3
|
+
import bpy
|
|
4
|
+
from bpy.types import Object
|
|
5
|
+
|
|
6
|
+
from notso_glb.utils import get_scene, get_view_layer
|
|
7
|
+
from notso_glb.utils.logging import log_debug
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def get_bones_used_for_skinning() -> set[str]:
|
|
11
|
+
"""Find all bones that have vertex weights on skinned meshes."""
|
|
12
|
+
used_bones: set[str] = set()
|
|
13
|
+
|
|
14
|
+
for obj in bpy.data.objects:
|
|
15
|
+
if obj.type != "MESH":
|
|
16
|
+
continue
|
|
17
|
+
|
|
18
|
+
# Check if mesh is skinned (has armature modifier)
|
|
19
|
+
has_armature = any(mod.type == "ARMATURE" for mod in obj.modifiers)
|
|
20
|
+
if not has_armature:
|
|
21
|
+
continue
|
|
22
|
+
|
|
23
|
+
# All vertex groups on skinned meshes are bone references
|
|
24
|
+
for vg in obj.vertex_groups:
|
|
25
|
+
used_bones.add(vg.name)
|
|
26
|
+
|
|
27
|
+
return used_bones
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def analyze_bone_animation() -> set[str]:
|
|
31
|
+
"""Find bones that never animate across all actions.
|
|
32
|
+
|
|
33
|
+
Optimized to batch frame evaluations - evaluates all bones at once per frame
|
|
34
|
+
instead of switching frames per-bone, reducing scene updates from O(bones*actions)
|
|
35
|
+
to O(actions).
|
|
36
|
+
"""
|
|
37
|
+
armature: Object | None = None
|
|
38
|
+
for obj in bpy.data.objects:
|
|
39
|
+
if obj.type == "ARMATURE":
|
|
40
|
+
armature = obj
|
|
41
|
+
break
|
|
42
|
+
|
|
43
|
+
if not armature or not armature.animation_data or not armature.pose:
|
|
44
|
+
log_debug("No armature with animation data found")
|
|
45
|
+
return set()
|
|
46
|
+
|
|
47
|
+
scene = get_scene()
|
|
48
|
+
view_layer = get_view_layer()
|
|
49
|
+
bone_movement: dict[str, float] = {b.name: 0.0 for b in armature.pose.bones}
|
|
50
|
+
num_bones = len(armature.pose.bones)
|
|
51
|
+
num_actions = len(bpy.data.actions)
|
|
52
|
+
|
|
53
|
+
log_debug(f"Analyzing {num_bones} bones across {num_actions} actions")
|
|
54
|
+
|
|
55
|
+
orig_action = armature.animation_data.action
|
|
56
|
+
orig_frame = scene.frame_current
|
|
57
|
+
|
|
58
|
+
for action in bpy.data.actions:
|
|
59
|
+
armature.animation_data.action = action
|
|
60
|
+
frame_start = int(action.frame_range[0])
|
|
61
|
+
frame_end = int(action.frame_range[1])
|
|
62
|
+
|
|
63
|
+
# Evaluate start frame ONCE for all bones
|
|
64
|
+
scene.frame_set(frame_start)
|
|
65
|
+
view_layer.update()
|
|
66
|
+
start_poses: dict[str, tuple] = {}
|
|
67
|
+
for bone in armature.pose.bones:
|
|
68
|
+
start_poses[bone.name] = (
|
|
69
|
+
bone.location.copy(),
|
|
70
|
+
bone.rotation_quaternion.copy(),
|
|
71
|
+
bone.rotation_euler.copy(),
|
|
72
|
+
bone.rotation_mode,
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
# Evaluate end frame ONCE for all bones
|
|
76
|
+
scene.frame_set(frame_end)
|
|
77
|
+
view_layer.update()
|
|
78
|
+
|
|
79
|
+
# Now calculate diffs without any frame switching
|
|
80
|
+
for bone in armature.pose.bones:
|
|
81
|
+
start_loc, start_rot_q, start_rot_e, rot_mode = start_poses[bone.name]
|
|
82
|
+
end_loc = bone.location.copy()
|
|
83
|
+
end_rot_q = bone.rotation_quaternion.copy()
|
|
84
|
+
end_rot_e = bone.rotation_euler.copy()
|
|
85
|
+
|
|
86
|
+
loc_diff = (end_loc - start_loc).length
|
|
87
|
+
if rot_mode == "QUATERNION":
|
|
88
|
+
rot_diff = (end_rot_q - start_rot_q).magnitude
|
|
89
|
+
else:
|
|
90
|
+
rot_diff = (
|
|
91
|
+
end_rot_e.to_quaternion() - start_rot_e.to_quaternion()
|
|
92
|
+
).magnitude
|
|
93
|
+
|
|
94
|
+
bone_movement[bone.name] += loc_diff + rot_diff
|
|
95
|
+
|
|
96
|
+
if orig_action:
|
|
97
|
+
armature.animation_data.action = orig_action
|
|
98
|
+
scene.frame_set(orig_frame)
|
|
99
|
+
|
|
100
|
+
return {name for name, movement in bone_movement.items() if movement < 0.01}
|