scigraphs-engine 0.1.1__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.
- scigraphs_engine-0.1.1/LICENSE +21 -0
- scigraphs_engine-0.1.1/PKG-INFO +103 -0
- scigraphs_engine-0.1.1/README.md +47 -0
- scigraphs_engine-0.1.1/pyproject.toml +53 -0
- scigraphs_engine-0.1.1/scigraphs_engine/__init__.py +50 -0
- scigraphs_engine-0.1.1/scigraphs_engine/_camera_math.py +85 -0
- scigraphs_engine-0.1.1/scigraphs_engine/adaptive.py +468 -0
- scigraphs_engine-0.1.1/scigraphs_engine/api.py +1284 -0
- scigraphs_engine-0.1.1/scigraphs_engine/backends/__init__.py +4 -0
- scigraphs_engine-0.1.1/scigraphs_engine/backends/wgpu/__init__.py +31 -0
- scigraphs_engine-0.1.1/scigraphs_engine/backends/wgpu/buffers.py +232 -0
- scigraphs_engine-0.1.1/scigraphs_engine/backends/wgpu/camera.py +148 -0
- scigraphs_engine-0.1.1/scigraphs_engine/backends/wgpu/device.py +108 -0
- scigraphs_engine-0.1.1/scigraphs_engine/backends/wgpu/pipelines.py +180 -0
- scigraphs_engine-0.1.1/scigraphs_engine/backends/wgpu/render.py +250 -0
- scigraphs_engine-0.1.1/scigraphs_engine/backends/wgpu/renderer.py +257 -0
- scigraphs_engine-0.1.1/scigraphs_engine/backends/wgpu/shaders.py +381 -0
- scigraphs_engine-0.1.1/scigraphs_engine/backends/wgpu/target.py +115 -0
- scigraphs_engine-0.1.1/scigraphs_engine/backends/wgpu/wgsl/arrow.json +78 -0
- scigraphs_engine-0.1.1/scigraphs_engine/backends/wgpu/wgsl/arrow.wgsl +76 -0
- scigraphs_engine-0.1.1/scigraphs_engine/backends/wgpu/wgsl/arrow_flat.json +48 -0
- scigraphs_engine-0.1.1/scigraphs_engine/backends/wgpu/wgsl/arrow_flat.wgsl +82 -0
- scigraphs_engine-0.1.1/scigraphs_engine/backends/wgpu/wgsl/heb_line.json +81 -0
- scigraphs_engine-0.1.1/scigraphs_engine/backends/wgpu/wgsl/heb_line.wgsl +137 -0
- scigraphs_engine-0.1.1/scigraphs_engine/backends/wgpu/wgsl/line_f.json +108 -0
- scigraphs_engine-0.1.1/scigraphs_engine/backends/wgpu/wgsl/line_f.wgsl +115 -0
- scigraphs_engine-0.1.1/scigraphs_engine/backends/wgpu/wgsl/ribbon.json +93 -0
- scigraphs_engine-0.1.1/scigraphs_engine/backends/wgpu/wgsl/ribbon.wgsl +131 -0
- scigraphs_engine-0.1.1/scigraphs_engine/backends/wgpu/wgsl/ribbon_f.json +162 -0
- scigraphs_engine-0.1.1/scigraphs_engine/backends/wgpu/wgsl/ribbon_f.wgsl +204 -0
- scigraphs_engine-0.1.1/scigraphs_engine/backends/wgpu/wgsl/ribbon_id.json +54 -0
- scigraphs_engine-0.1.1/scigraphs_engine/backends/wgpu/wgsl/ribbon_id.wgsl +103 -0
- scigraphs_engine-0.1.1/scigraphs_engine/backends/wgpu/wgsl/round_point.json +22 -0
- scigraphs_engine-0.1.1/scigraphs_engine/backends/wgpu/wgsl/round_point.wgsl +68 -0
- scigraphs_engine-0.1.1/scigraphs_engine/backends/wgpu/wgsl/round_point_f.json +118 -0
- scigraphs_engine-0.1.1/scigraphs_engine/backends/wgpu/wgsl/round_point_f.wgsl +141 -0
- scigraphs_engine-0.1.1/scigraphs_engine/backends/wgpu/wgsl/smooth_color.json +32 -0
- scigraphs_engine-0.1.1/scigraphs_engine/backends/wgpu/wgsl/smooth_color.wgsl +40 -0
- scigraphs_engine-0.1.1/scigraphs_engine/backends/wgpu/wgsl/sphere.json +83 -0
- scigraphs_engine-0.1.1/scigraphs_engine/backends/wgpu/wgsl/sphere.wgsl +115 -0
- scigraphs_engine-0.1.1/scigraphs_engine/backends/wgpu/wgsl/sphere_f.json +152 -0
- scigraphs_engine-0.1.1/scigraphs_engine/backends/wgpu/wgsl/sphere_f.wgsl +185 -0
- scigraphs_engine-0.1.1/scigraphs_engine/backends/wgpu/wgsl/sphere_id.json +44 -0
- scigraphs_engine-0.1.1/scigraphs_engine/backends/wgpu/wgsl/sphere_id.wgsl +80 -0
- scigraphs_engine-0.1.1/scigraphs_engine/backends/wgpu/wgsl/uniform_color.json +37 -0
- scigraphs_engine-0.1.1/scigraphs_engine/backends/wgpu/wgsl/uniform_color.wgsl +42 -0
- scigraphs_engine-0.1.1/scigraphs_engine/blocks.py +76 -0
- scigraphs_engine-0.1.1/scigraphs_engine/bundling/__init__.py +11 -0
- scigraphs_engine-0.1.1/scigraphs_engine/bundling/fdeb.py +310 -0
- scigraphs_engine-0.1.1/scigraphs_engine/bundling/mingle.py +480 -0
- scigraphs_engine-0.1.1/scigraphs_engine/bundling/routed.py +420 -0
- scigraphs_engine-0.1.1/scigraphs_engine/bundling/sbeb.py +292 -0
- scigraphs_engine-0.1.1/scigraphs_engine/channels.py +91 -0
- scigraphs_engine-0.1.1/scigraphs_engine/communities.py +125 -0
- scigraphs_engine-0.1.1/scigraphs_engine/edge_styles.py +566 -0
- scigraphs_engine-0.1.1/scigraphs_engine/filters.py +754 -0
- scigraphs_engine-0.1.1/scigraphs_engine/gpu_compute.py +17 -0
- scigraphs_engine-0.1.1/scigraphs_engine/lod.py +89 -0
- scigraphs_engine-0.1.1/scigraphs_engine/mesh.py +480 -0
- scigraphs_engine-0.1.1/scigraphs_engine/palette.py +68 -0
- scigraphs_engine-0.1.1/scigraphs_engine/settings.py +151 -0
- scigraphs_engine-0.1.1/scigraphs_engine/simplify.py +352 -0
- scigraphs_engine-0.1.1/scigraphs_engine/source.py +143 -0
- scigraphs_engine-0.1.1/scigraphs_engine.egg-info/PKG-INFO +103 -0
- scigraphs_engine-0.1.1/scigraphs_engine.egg-info/SOURCES.txt +67 -0
- scigraphs_engine-0.1.1/scigraphs_engine.egg-info/dependency_links.txt +1 -0
- scigraphs_engine-0.1.1/scigraphs_engine.egg-info/requires.txt +19 -0
- scigraphs_engine-0.1.1/scigraphs_engine.egg-info/top_level.txt +1 -0
- scigraphs_engine-0.1.1/setup.cfg +4 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 José Marín
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: scigraphs-engine
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: Blender-free graph visualization engine: filters, backbones, bundling, PNG render.
|
|
5
|
+
Author: José Marín
|
|
6
|
+
License: MIT License
|
|
7
|
+
|
|
8
|
+
Copyright (c) 2026 José Marín
|
|
9
|
+
|
|
10
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
11
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
12
|
+
in the Software without restriction, including without limitation the rights
|
|
13
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
14
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
15
|
+
furnished to do so, subject to the following conditions:
|
|
16
|
+
|
|
17
|
+
The above copyright notice and this permission notice shall be included in all
|
|
18
|
+
copies or substantial portions of the Software.
|
|
19
|
+
|
|
20
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
21
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
22
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
23
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
24
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
25
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
26
|
+
SOFTWARE.
|
|
27
|
+
|
|
28
|
+
Project-URL: Homepage, https://github.com/SciBlend/SciGraphs
|
|
29
|
+
Project-URL: Repository, https://github.com/SciBlend/SciGraphs
|
|
30
|
+
Project-URL: Issues, https://github.com/SciBlend/SciGraphs/issues
|
|
31
|
+
Project-URL: Documentation, https://github.com/SciBlend/SciGraphs/tree/main/docs
|
|
32
|
+
Keywords: graph,network,visualization,rendering,layout
|
|
33
|
+
Classifier: Development Status :: 3 - Alpha
|
|
34
|
+
Classifier: Intended Audience :: Science/Research
|
|
35
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
36
|
+
Classifier: Programming Language :: Python :: 3
|
|
37
|
+
Classifier: Topic :: Scientific/Engineering :: Visualization
|
|
38
|
+
Requires-Python: >=3.10
|
|
39
|
+
Description-Content-Type: text/markdown
|
|
40
|
+
License-File: LICENSE
|
|
41
|
+
Requires-Dist: numpy>=1.23
|
|
42
|
+
Provides-Extra: igraph
|
|
43
|
+
Requires-Dist: python-igraph>=0.10; extra == "igraph"
|
|
44
|
+
Provides-Extra: scipy
|
|
45
|
+
Requires-Dist: scipy>=1.9; extra == "scipy"
|
|
46
|
+
Provides-Extra: networkx
|
|
47
|
+
Requires-Dist: networkx>=2.8; extra == "networkx"
|
|
48
|
+
Provides-Extra: all
|
|
49
|
+
Requires-Dist: python-igraph>=0.10; extra == "all"
|
|
50
|
+
Requires-Dist: scipy>=1.9; extra == "all"
|
|
51
|
+
Requires-Dist: networkx>=2.8; extra == "all"
|
|
52
|
+
Provides-Extra: wgpu
|
|
53
|
+
Requires-Dist: wgpu>=0.32; extra == "wgpu"
|
|
54
|
+
Requires-Dist: pillow>=9; extra == "wgpu"
|
|
55
|
+
Dynamic: license-file
|
|
56
|
+
|
|
57
|
+
# scigraphs-engine
|
|
58
|
+
|
|
59
|
+
Graph visualization as a plain Python package. Coordinates, an edge list and
|
|
60
|
+
whatever attributes they carry go in; geometry or pixels come out, with 26
|
|
61
|
+
filter channels, structural sparsification, edge-style tessellation and
|
|
62
|
+
hierarchical bundling in between.
|
|
63
|
+
|
|
64
|
+
```python
|
|
65
|
+
from scigraphs_engine import Graph
|
|
66
|
+
|
|
67
|
+
g = Graph.from_arrays(coords, edges)
|
|
68
|
+
g = g.filter(core__gte=0.5).simplify(backbone="disparity", alpha=0.05)
|
|
69
|
+
g.style(edges="bundled").render(size=(1920, 1080)).save("out.png")
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Nothing in this package imports `bpy`, `gpu`, `bmesh` or `mathutils`. The
|
|
73
|
+
compute half needs only `numpy`, and `Graph.geometry()` runs with no graphics
|
|
74
|
+
library installed at all, returning `MeshSpec`s that name their shader as a
|
|
75
|
+
string, so any renderer can draw them. The GPU half is an optional extra.
|
|
76
|
+
|
|
77
|
+
## Install
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
pip install scigraphs-engine # numpy only
|
|
81
|
+
pip install "scigraphs-engine[igraph]" # coreness, PageRank, betweenness, bridges
|
|
82
|
+
pip install "scigraphs-engine[scipy]" # components, hop distance, crowding
|
|
83
|
+
pip install "scigraphs-engine[wgpu]" # render() to pixels
|
|
84
|
+
pip install "scigraphs-engine[all]" # everything
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Requires Python 3.10 or newer. A missing extra makes individual channels
|
|
88
|
+
unavailable rather than breaking anything: `available_channels()` returns all 26
|
|
89
|
+
with a reason for each one that cannot run.
|
|
90
|
+
|
|
91
|
+
## Documentation
|
|
92
|
+
|
|
93
|
+
https://github.com/SciBlend/SciGraphs/tree/main/docs
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
python engine/scigraphs_engine/tests/run.py # -v for full output
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## License
|
|
100
|
+
|
|
101
|
+
MIT, see `LICENSE`. The SciGraphs Blender add-on is GPLv3 and is one caller of
|
|
102
|
+
this package, not its owner. It reaches the package through a shim that prefers
|
|
103
|
+
an installed `scigraphs_engine` over any copy shipped alongside it.
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# scigraphs-engine
|
|
2
|
+
|
|
3
|
+
Graph visualization as a plain Python package. Coordinates, an edge list and
|
|
4
|
+
whatever attributes they carry go in; geometry or pixels come out, with 26
|
|
5
|
+
filter channels, structural sparsification, edge-style tessellation and
|
|
6
|
+
hierarchical bundling in between.
|
|
7
|
+
|
|
8
|
+
```python
|
|
9
|
+
from scigraphs_engine import Graph
|
|
10
|
+
|
|
11
|
+
g = Graph.from_arrays(coords, edges)
|
|
12
|
+
g = g.filter(core__gte=0.5).simplify(backbone="disparity", alpha=0.05)
|
|
13
|
+
g.style(edges="bundled").render(size=(1920, 1080)).save("out.png")
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Nothing in this package imports `bpy`, `gpu`, `bmesh` or `mathutils`. The
|
|
17
|
+
compute half needs only `numpy`, and `Graph.geometry()` runs with no graphics
|
|
18
|
+
library installed at all, returning `MeshSpec`s that name their shader as a
|
|
19
|
+
string, so any renderer can draw them. The GPU half is an optional extra.
|
|
20
|
+
|
|
21
|
+
## Install
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
pip install scigraphs-engine # numpy only
|
|
25
|
+
pip install "scigraphs-engine[igraph]" # coreness, PageRank, betweenness, bridges
|
|
26
|
+
pip install "scigraphs-engine[scipy]" # components, hop distance, crowding
|
|
27
|
+
pip install "scigraphs-engine[wgpu]" # render() to pixels
|
|
28
|
+
pip install "scigraphs-engine[all]" # everything
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Requires Python 3.10 or newer. A missing extra makes individual channels
|
|
32
|
+
unavailable rather than breaking anything: `available_channels()` returns all 26
|
|
33
|
+
with a reason for each one that cannot run.
|
|
34
|
+
|
|
35
|
+
## Documentation
|
|
36
|
+
|
|
37
|
+
https://github.com/SciBlend/SciGraphs/tree/main/docs
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
python engine/scigraphs_engine/tests/run.py # -v for full output
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## License
|
|
44
|
+
|
|
45
|
+
MIT, see `LICENSE`. The SciGraphs Blender add-on is GPLv3 and is one caller of
|
|
46
|
+
this package, not its owner. It reaches the package through a shim that prefers
|
|
47
|
+
an installed `scigraphs_engine` over any copy shipped alongside it.
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "scigraphs-engine"
|
|
7
|
+
version = "0.1.1"
|
|
8
|
+
description = "Blender-free graph visualization engine: filters, backbones, bundling, PNG render."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
# Floor matches Blender 4.1+ / 5.x Python.
|
|
11
|
+
requires-python = ">=3.10"
|
|
12
|
+
license = { file = "LICENSE" }
|
|
13
|
+
authors = [{ name = "José Marín" }]
|
|
14
|
+
keywords = ["graph", "network", "visualization", "rendering", "layout"]
|
|
15
|
+
classifiers = [
|
|
16
|
+
"Development Status :: 3 - Alpha",
|
|
17
|
+
"Intended Audience :: Science/Research",
|
|
18
|
+
"License :: OSI Approved :: MIT License",
|
|
19
|
+
"Programming Language :: Python :: 3",
|
|
20
|
+
"Topic :: Scientific/Engineering :: Visualization",
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
# Only hard dep. igraph/scipy/networkx are optional and degrade to None.
|
|
24
|
+
dependencies = ["numpy>=1.23"]
|
|
25
|
+
|
|
26
|
+
[project.urls]
|
|
27
|
+
Homepage = "https://github.com/SciBlend/SciGraphs"
|
|
28
|
+
Repository = "https://github.com/SciBlend/SciGraphs"
|
|
29
|
+
Issues = "https://github.com/SciBlend/SciGraphs/issues"
|
|
30
|
+
Documentation = "https://github.com/SciBlend/SciGraphs/tree/main/docs"
|
|
31
|
+
|
|
32
|
+
[project.optional-dependencies]
|
|
33
|
+
igraph = ["python-igraph>=0.10"]
|
|
34
|
+
scipy = ["scipy>=1.9"]
|
|
35
|
+
networkx = ["networkx>=2.8"]
|
|
36
|
+
all = ["python-igraph>=0.10", "scipy>=1.9", "networkx>=2.8"]
|
|
37
|
+
# Only needed for Graph.render(); import stays lazy.
|
|
38
|
+
wgpu = ["wgpu>=0.32", "pillow>=9"]
|
|
39
|
+
|
|
40
|
+
[tool.setuptools]
|
|
41
|
+
# Listed one by one rather than discovered, so a new subpackage has to be added
|
|
42
|
+
# here on purpose. Leaving one out ships a wheel that imports and then fails on
|
|
43
|
+
# the first call into the missing module.
|
|
44
|
+
packages = [
|
|
45
|
+
"scigraphs_engine",
|
|
46
|
+
"scigraphs_engine.backends",
|
|
47
|
+
"scigraphs_engine.backends.wgpu",
|
|
48
|
+
"scigraphs_engine.bundling",
|
|
49
|
+
]
|
|
50
|
+
license-files = ["LICENSE"]
|
|
51
|
+
|
|
52
|
+
[tool.setuptools.package-data]
|
|
53
|
+
"scigraphs_engine.backends.wgpu" = ["wgsl/*.wgsl", "wgsl/*.json"]
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# Nothing in this package may import bpy, gpu, bmesh or mathutils; tests in the
|
|
2
|
+
# SciGraphs repository parse this directory for those names. Everything below
|
|
3
|
+
# numpy is lazy, so a MeshSpec caller never pays for igraph and scipy.
|
|
4
|
+
|
|
5
|
+
import importlib as _importlib
|
|
6
|
+
|
|
7
|
+
__version__ = "0.1.1"
|
|
8
|
+
|
|
9
|
+
from .mesh import ( # noqa: F401
|
|
10
|
+
DrawState, MeshGroup, MeshSpec, ShaderRef, LINES, POINTS, TRIS,
|
|
11
|
+
)
|
|
12
|
+
from .settings import Clause, Settings, PREFIX # noqa: F401
|
|
13
|
+
from .source import ArraySource, GraphSource, EDGE, POINT # noqa: F401
|
|
14
|
+
|
|
15
|
+
_LAZY = (
|
|
16
|
+
"adaptive", "api", "blocks", "channels", "communities", "edge_styles",
|
|
17
|
+
"filters", "gpu_compute", "lod", "mesh", "palette", "settings", "simplify",
|
|
18
|
+
"source",
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
# Lets `from scigraphs_engine import Graph` resolve without importing api.
|
|
22
|
+
_API_NAMES = (
|
|
23
|
+
"BackendUnavailable", "Camera", "ChannelResult", "ChannelSpec",
|
|
24
|
+
"ChannelUnavailable", "Condition", "EngineError", "Geometry", "Graph",
|
|
25
|
+
"Image", "Report", "UnknownChannel", "channel", "channel_names",
|
|
26
|
+
"channel_specs", "colormap_names", "gpu_available",
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
__all__ = [
|
|
30
|
+
"ArraySource", "Clause", "DrawState", "EDGE", "GraphSource", "LINES",
|
|
31
|
+
"MeshGroup", "MeshSpec", "POINT", "POINTS", "PREFIX", "Settings",
|
|
32
|
+
"ShaderRef", "TRIS", "__version__",
|
|
33
|
+
] + list(_API_NAMES) + list(_LAZY)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def __getattr__(name):
|
|
37
|
+
"""Import a submodule or a public API name on first access (PEP 562)."""
|
|
38
|
+
if name in _LAZY:
|
|
39
|
+
module = _importlib.import_module("." + name, __name__)
|
|
40
|
+
globals()[name] = module
|
|
41
|
+
return module
|
|
42
|
+
if name in _API_NAMES:
|
|
43
|
+
value = getattr(_importlib.import_module(".api", __name__), name)
|
|
44
|
+
globals()[name] = value
|
|
45
|
+
return value
|
|
46
|
+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def __dir__():
|
|
50
|
+
return sorted(set(globals()) | set(_LAZY) | set(_API_NAMES))
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# Transcribed from backends/wgpu/camera.py, held float32-exact against it by
|
|
2
|
+
# tests/test_camera.py. Depth is z in [0, 1] (WebGPU), not OpenGL's.
|
|
3
|
+
|
|
4
|
+
import numpy as np
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def _normalize(v):
|
|
8
|
+
v = np.asarray(v, dtype=np.float64)
|
|
9
|
+
n = np.linalg.norm(v)
|
|
10
|
+
return v / n if n > 1e-12 else v
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def look_at(eye, target, up=(0.0, 0.0, 1.0)):
|
|
14
|
+
"""World -> view, right-handed, looking down -z. +Z up like Blender."""
|
|
15
|
+
eye = np.asarray(eye, dtype=np.float64)
|
|
16
|
+
f = _normalize(np.asarray(target, dtype=np.float64) - eye)
|
|
17
|
+
up = _normalize(up)
|
|
18
|
+
if abs(float(np.dot(f, up))) > 0.9999:
|
|
19
|
+
up = np.array([0.0, 1.0, 0.0]) if abs(f[1]) < 0.9 else \
|
|
20
|
+
np.array([1.0, 0.0, 0.0])
|
|
21
|
+
s = _normalize(np.cross(f, up))
|
|
22
|
+
u = np.cross(s, f)
|
|
23
|
+
m = np.eye(4, dtype=np.float64)
|
|
24
|
+
m[0, :3], m[1, :3], m[2, :3] = s, u, -f
|
|
25
|
+
m[0, 3] = -float(np.dot(s, eye))
|
|
26
|
+
m[1, 3] = -float(np.dot(u, eye))
|
|
27
|
+
m[2, 3] = float(np.dot(f, eye))
|
|
28
|
+
return m.astype(np.float32)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def perspective(fov_y_deg, aspect, near, far):
|
|
32
|
+
"""View -> clip with z in [0, 1]."""
|
|
33
|
+
f = 1.0 / np.tan(np.radians(float(fov_y_deg)) * 0.5)
|
|
34
|
+
m = np.zeros((4, 4), dtype=np.float64)
|
|
35
|
+
m[0, 0] = f / float(aspect)
|
|
36
|
+
m[1, 1] = f
|
|
37
|
+
m[2, 2] = far / (near - far)
|
|
38
|
+
m[2, 3] = (far * near) / (near - far)
|
|
39
|
+
m[3, 2] = -1.0
|
|
40
|
+
return m.astype(np.float32)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def bounding_sphere(coords):
|
|
44
|
+
"""(center, radius) of the box's circumsphere. Over-frames a flat layout."""
|
|
45
|
+
coords = np.asarray(coords, dtype=np.float32).reshape(-1, 3)
|
|
46
|
+
if coords.size == 0:
|
|
47
|
+
return np.zeros(3, dtype=np.float32), 1.0
|
|
48
|
+
lo = coords.min(axis=0)
|
|
49
|
+
hi = coords.max(axis=0)
|
|
50
|
+
center = (lo + hi) * 0.5
|
|
51
|
+
radius = float(np.linalg.norm(hi - lo)) * 0.5
|
|
52
|
+
return center, (radius if radius > 1e-6 else 1.0)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def fit_view(coords, size, fov_y_deg=45.0, margin=1.15,
|
|
56
|
+
direction=(0.0, -1.0, 0.35), up=(0.0, 0.0, 1.0)):
|
|
57
|
+
"""A resolved api.Camera framing all of ``coords`` at ``size``."""
|
|
58
|
+
from .api import Camera
|
|
59
|
+
|
|
60
|
+
width, height = size
|
|
61
|
+
center, radius = bounding_sphere(coords)
|
|
62
|
+
aspect = float(width) / float(height)
|
|
63
|
+
# Fit the tighter axis: vertical-only fitting crops a portrait render.
|
|
64
|
+
half_v = np.radians(float(fov_y_deg)) * 0.5
|
|
65
|
+
half_h = np.arctan(np.tan(half_v) * aspect)
|
|
66
|
+
dist = radius * float(margin) / np.sin(min(half_v, half_h))
|
|
67
|
+
|
|
68
|
+
eye = center - _normalize(direction).astype(np.float32) * dist
|
|
69
|
+
near = max(dist - radius * 2.0, dist * 1e-3)
|
|
70
|
+
far = dist + radius * 4.0
|
|
71
|
+
return Camera(look_at(eye, center, up),
|
|
72
|
+
perspective(fov_y_deg, aspect, near, far), near, far)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def look_at_camera(eye, target, up, fov_y_deg, size, near=None, far=None):
|
|
76
|
+
from .api import Camera
|
|
77
|
+
|
|
78
|
+
width, height = size
|
|
79
|
+
eye = np.asarray(eye, dtype=np.float64)
|
|
80
|
+
span = float(np.linalg.norm(np.asarray(target, dtype=np.float64) - eye))
|
|
81
|
+
near = float(near) if near is not None else max(span * 1e-3, 1e-4)
|
|
82
|
+
far = float(far) if far is not None else max(span * 10.0, near * 100.0)
|
|
83
|
+
return Camera(look_at(eye, target, up),
|
|
84
|
+
perspective(fov_y_deg, float(width) / float(height),
|
|
85
|
+
near, far), near, far)
|