├── .flake8
├── .gitignore
├── .travis.yml
├── LICENSE
├── MANIFEST.in
├── README.md
├── demo
├── README.md
├── __init__.py
├── example_window.py
├── shaders
│ ├── demo.ComputeShader.comp
│ ├── demo.FieldFragmentShader.frag
│ ├── demo.QuiverFragmentShader.frag
│ ├── demo.VertexShader.vert
│ ├── shader.AddParticle.comp
│ ├── shader.AdvectParticle.comp
│ └── varying.def.sc
├── simulation_demo.py
├── smooth_particles_area.py
└── utils
│ ├── __init__.py
│ ├── imgui_utils.py
│ └── matrix_utils.py
├── media
└── screenshot.png
├── natrix
├── __init__.py
└── core
│ ├── __init__.py
│ ├── common
│ ├── __init__.py
│ └── constants.py
│ ├── fluid_simulator.py
│ ├── shaders
│ └── originals
│ │ ├── common.sh
│ │ ├── constants.sh
│ │ ├── shader.AddCircleObstacle.comp
│ │ ├── shader.AddTriangleObstacle.comp
│ │ ├── shader.AddVelocity.comp
│ │ ├── shader.AdvectVelocity.comp
│ │ ├── shader.ApplyVorticity.comp
│ │ ├── shader.CalcVorticity.comp
│ │ ├── shader.ClearBuffer.comp
│ │ ├── shader.Divergence.comp
│ │ ├── shader.InitBoundaries.comp
│ │ ├── shader.Poisson.comp
│ │ ├── shader.SubtractGradient.comp
│ │ └── shader.Viscosity.comp
│ └── utils
│ ├── __init__.py
│ ├── bgfx_utils.py
│ └── shaders_utils.py
├── noxfile.py
├── poetry.lock
├── pyproject.toml
├── setup.py
└── tests
└── __init__.py
/.flake8:
--------------------------------------------------------------------------------
1 | [flake8]
2 | select = B,B9,BLK,C,E,F,I,S,W
3 | ignore = E203,E501,W503,E731,F405,F403,F401
4 | max-line-length = 128
5 | per-file-ignores = tests/*:S101
6 | import-order-style = google
7 | application-import-names = src,tests
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 | *$py.class
5 |
6 | # C extensions
7 | *.so
8 |
9 | # Distribution / packaging
10 | .Python
11 | build/
12 | develop-eggs/
13 | dist/
14 | downloads/
15 | eggs/
16 | .eggs/
17 | lib/
18 | lib64/
19 | parts/
20 | sdist/
21 | var/
22 | wheels/
23 | *.egg-info/
24 | .installed.cfg
25 | *.egg
26 | MANIFEST
27 |
28 | # PyInstaller
29 | # Usually these files are written by a python script from a template
30 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
31 | *.manifest
32 | *.spec
33 |
34 | # Installer logs
35 | pip-log.txt
36 | pip-delete-this-directory.txt
37 |
38 | # Unit test / coverage reports
39 | htmlcov/
40 | .tox/
41 | .coverage
42 | .coverage.*
43 | .cache
44 | nosetests.xml
45 | coverage.xml
46 | *.cover
47 | .hypothesis/
48 | .pytest_cache/
49 |
50 | # Translations
51 | *.mo
52 | *.pot
53 |
54 | # Django stuff:
55 | *.log
56 | local_settings.py
57 | db.sqlite3
58 |
59 | # Flask stuff:
60 | instance/
61 | .webassets-cache
62 |
63 | # Scrapy stuff:
64 | .scrapy
65 |
66 | # Sphinx documentation
67 | docs/_build/
68 |
69 | # PyBuilder
70 | target/
71 |
72 | # Jupyter Notebook
73 | .ipynb_checkpoints
74 |
75 | # pyenv
76 | .python-version
77 |
78 | # celery beat schedule file
79 | celerybeat-schedule
80 |
81 | # SageMath parsed files
82 | *.sage.py
83 |
84 | # Environments
85 | .env
86 | .venv
87 | env/
88 | venv/
89 | ENV/
90 | env.bak/
91 | venv.bak/
92 |
93 | # Spyder project settings
94 | .spyderproject
95 | .spyproject
96 |
97 | # Rope project settings
98 | .ropeproject
99 |
100 | # mkdocs documentation
101 | /site
102 |
103 | # mypy
104 | .mypy_cache/
105 |
106 | # PyCharm
107 | .idea/
108 |
109 | */shaders_cache
110 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: python
2 | python:
3 | - "3.6"
4 | - "3.7"
5 | - "3.8"
6 |
7 | before_install:
8 | - pip3 install poetry
9 |
10 | install:
11 | - poetry install
12 |
13 | script:
14 | - true
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Federico Bertola
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.
22 |
--------------------------------------------------------------------------------
/MANIFEST.in:
--------------------------------------------------------------------------------
1 | include LICENSE README.md CONTRIBUTING.md CODE_OF_CONDUCT.md CHANGELOG.rst NOTICES HISTORY.txt
2 | exclude .gitmodules
3 | exclude .editorconfig .travis.yml .env appveyor.yml tox.ini pytest.ini
4 | exclude Pipfile* CHANGELOG.draft.rst
5 |
6 | recursive-include docs Makefile *.rst *.py *.bat
7 | recursive-include docs/_templates *.html
8 | recursive-include docs/_static *.js *.css *.png
9 | recursive-include natrix/core/shaders *.comp
10 | recursive-exclude docs requirements*.txt
11 | recursive-exclude natrix *.pyi
12 | recursive-exclude natrix *.typed
13 |
14 | prune .github
15 | prune docs/build
16 | prune news
17 | prune tasks
18 | prune tests
19 | prune demo
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | Natrix
4 |
5 |
6 |
7 | Fast fluid simultation in Python.
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 | Key Features •
20 | How To Use •
21 | Credits •
22 | License
23 |
24 |
25 |
26 |
27 |
28 |
29 | ## Key Features
30 |
31 | * Fast - leverages Compute Shaders to offload most of the calculations to the GPU.
32 | * Built with BGFX rendering engine, supporting OpenGL, Vulkan, Metal and DirectX backends.
33 | * Vorticity confinement.
34 | * Fluid obstacles.
35 | * Poisson kernel.
36 |
37 | ## How To Use
38 |
39 | To use this library, you'll need [Poetry](https://github.com/python-poetry/poetry):
40 |
41 | ```bash
42 | # Clone this repository
43 | $ git clone https://github.com/fbertola/Natrix .
44 |
45 | # Go into the repository
46 | $ cd Natrix
47 |
48 | # Install dependencies
49 | $ poetry install
50 | ```
51 |
52 | ## Examples
53 |
54 | In the [demo](https://github.com/fbertola/Natrix/tree/master/demo) folder you will find a complete example, be sure to check it out.
55 |
56 | ## Credits
57 |
58 | This software uses the following open source packages:
59 |
60 | - [BGFX](https://github.com/bkaradzic/bgfx)
61 | - [BGFX Python Wrapper](https://github.com/fbertola/bgfx-python)
62 |
63 | [License (BSD 2-clause)](https://raw.githubusercontent.com/fbertola/bgfx-python/master/LICENSE)
64 | -----------------------------------------------------------------------
65 |
66 |
67 |
68 |
69 |
70 | BSD 2-Clause License
71 |
72 | Copyright (c) 2021, Federico Bertola
73 | All rights reserved.
74 |
75 | Redistribution and use in source and binary forms, with or without
76 | modification, are permitted provided that the following conditions are met:
77 |
78 | 1. Redistributions of source code must retain the above copyright notice, this
79 | list of conditions and the following disclaimer.
80 |
81 | 2. Redistributions in binary form must reproduce the above copyright notice,
82 | this list of conditions and the following disclaimer in the documentation
83 | and/or other materials provided with the distribution.
84 |
85 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
86 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
87 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
88 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
89 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
90 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
91 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
92 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
93 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
94 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
95 |
96 |
97 | ---
98 |
99 | > GitHub [@fbertola](https://github.com/fbertola)
100 |
--------------------------------------------------------------------------------
/demo/README.md:
--------------------------------------------------------------------------------
1 | # Natrix Demo
2 |
3 | This folder contains a complete example of how to user Natrix.
4 |
5 | It demonstrates how to:
6 | * use the velocity field to animate a simple smooth particles area.
7 | * use different resolutions for the velocity field.
8 | * use [ImGui](https://github.com/ocornut/imgui) to create a simple GUI for controlling the simulation.
9 | * support HiDPI screens.
--------------------------------------------------------------------------------
/demo/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fbertola/Natrix/403fed55cab582563615c71ef11b40b11cdb7777/demo/__init__.py
--------------------------------------------------------------------------------
/demo/example_window.py:
--------------------------------------------------------------------------------
1 | import ctypes
2 | import cppyy
3 | import sys
4 | import os
5 | import time
6 |
7 | # noinspection PyPackageRequirements
8 | import glfw
9 |
10 | from pybgfx import bgfx
11 |
12 | # noinspection PyPackageRequirements,PyProtectedMember
13 | from glfw import _glfw as glfw_native
14 | from pybgfx.utils import as_void_ptr
15 |
16 |
17 | class ExampleWindow(object):
18 | def __init__(self, width, height, title):
19 | self.title = title
20 | self.height = height
21 | self.width = width
22 | self.ctx = None
23 | self.window = None
24 | self.fb_width = width
25 | self.fb_height = height
26 | self.hidpi = False
27 |
28 | def init(self, platform_data):
29 | pass
30 |
31 | def shutdown(self):
32 | pass
33 |
34 | def update(self, dt):
35 | pass
36 |
37 | def resize(self):
38 | pass
39 |
40 | def get_mouse_state(self):
41 | mouse_x, mouse_y = glfw.get_cursor_pos(self.window)
42 |
43 | if self.hidpi:
44 | mouse_x, mouse_y = mouse_x * 2, mouse_y * 2
45 | state_mbl = glfw.get_mouse_button(self.window, glfw.MOUSE_BUTTON_LEFT)
46 | state_mbm = glfw.get_mouse_button(self.window, glfw.MOUSE_BUTTON_MIDDLE)
47 | state_mbr = glfw.get_mouse_button(self.window, glfw.MOUSE_BUTTON_RIGHT)
48 |
49 | return (
50 | mouse_x,
51 | mouse_y,
52 | 1
53 | if state_mbl == glfw.PRESS
54 | else 0 | 2
55 | if state_mbm == glfw.PRESS
56 | else 0 | 4
57 | if state_mbr == glfw.PRESS
58 | else 0,
59 | )
60 |
61 | # noinspection PyProtectedMember
62 | def run(self):
63 | glfw_native.glfwCreateWindow.argtypes = [
64 | ctypes.c_int,
65 | ctypes.c_int,
66 | ctypes.c_char_p,
67 | ]
68 | glfw_native.glfwCreateWindow.restype = ctypes.POINTER(glfw._GLFWwindow)
69 | glfw_native.glfwMakeContextCurrent.argtypes = [ctypes.POINTER(glfw._GLFWwindow)]
70 | glfw_native.glfwWindowShouldClose.argtypes = [ctypes.POINTER(glfw._GLFWwindow)]
71 | glfw_native.glfwWindowShouldClose.restype = ctypes.c_int
72 |
73 | glfw.init()
74 |
75 | glfw.window_hint(glfw.CLIENT_API, glfw.NO_API)
76 | glfw.window_hint(glfw.COCOA_RETINA_FRAMEBUFFER, glfw.TRUE)
77 |
78 | self.window = glfw.create_window(
79 | self.width, self.height, self.title, None, None
80 | )
81 |
82 | self.fb_width, self.fb_height = glfw.get_framebuffer_size(self.window)
83 |
84 | self.hidpi = self.fb_width != self.width or self.fb_height != self.height
85 |
86 | glfw.set_window_size_callback(self.window, self._handle_window_resize)
87 |
88 | handle, display = None, None
89 |
90 | if sys.platform == "darwin":
91 | handle = glfw_native.glfwGetCocoaWindow(self.window)
92 | elif sys.platform == "win32":
93 | handle = glfw_native.glfwGetWin32Window(self.window)
94 | elif sys.platform == "linux" and "WAYLAND_DISPLAY" not in os.environ:
95 | handle = glfw_native.glfwGetX11Window(self.window)
96 | display = glfw_native.glfwGetX11Display()
97 | elif sys.platform == "linux" and "WAYLAND_DISPLAY" in os.environ:
98 | handle = glfw_native.glfwGetWaylandWindow(self.window)
99 | display = glfw_native.glfwGetWaylandDisplay()
100 |
101 | data = bgfx.PlatformData()
102 | data.ndt = as_void_ptr(display) if display else cppyy.nullptr
103 | data.nwh = as_void_ptr(handle)
104 | data.context = cppyy.nullptr
105 | data.backBuffer = cppyy.nullptr
106 | data.backBufferDS = cppyy.nullptr
107 |
108 | self.init(data)
109 |
110 | last_time = None
111 |
112 | while not glfw.window_should_close(self.window):
113 | glfw.poll_events()
114 |
115 | now = time.perf_counter()
116 | if not last_time:
117 | last_time = now
118 |
119 | frame_time = now - last_time
120 | last_time = now
121 |
122 | self.update(frame_time)
123 |
124 | self.shutdown()
125 | glfw.terminate()
126 |
127 | def _handle_window_resize(self, window, width, height):
128 | self.width = width
129 | self.height = height
130 |
131 | self.fb_width, self.fb_height = glfw.get_framebuffer_size(window)
132 |
133 | self.resize()
134 |
135 | def _get_normalized_mouse_coords(self, mouse_x, mouse_y):
136 | return 1.0 - mouse_x / self.fb_width, 1.0 - mouse_y / self.fb_height
137 |
--------------------------------------------------------------------------------
/demo/shaders/demo.ComputeShader.comp:
--------------------------------------------------------------------------------
1 |
2 | #include "bgfx_compute.sh"
3 |
4 | IMAGE2D_WR(InputTexture, rgba8, 0);
5 | BUFFER_RO(_Particles, float, 9);
6 |
7 | uniform vec2 _ParticleSize;
8 |
9 | NUM_THREADS(16, 16, 1)
10 | void main()
11 | {
12 | if (gl_GlobalInvocationID.x >= _ParticleSize.x || gl_GlobalInvocationID.y >= _ParticleSize.y)
13 | {
14 | return;
15 | }
16 |
17 | ivec2 coord = ivec2(gl_GlobalInvocationID.xy);
18 | uint pos = gl_GlobalInvocationID.y * _ParticleSize.x + gl_GlobalInvocationID.x;
19 | float particle_value = _Particles[pos];
20 | imageStore(InputTexture, coord, vec4(particle_value, particle_value, particle_value, particle_value));
21 | }
--------------------------------------------------------------------------------
/demo/shaders/demo.FieldFragmentShader.frag:
--------------------------------------------------------------------------------
1 | $input v_texcoord0
2 |
3 | #include
4 |
5 | SAMPLER2D(s_texColor, 0);
6 |
7 | vec3 viridis(float t) {
8 |
9 | const vec3 c0 = vec3(0.2777273272234177, 0.005407344544966578, 0.3340998053353061);
10 | const vec3 c1 = vec3(0.1050930431085774, 1.404613529898575, 1.384590162594685);
11 | const vec3 c2 = vec3(-0.3308618287255563, 0.214847559468213, 0.09509516302823659);
12 | const vec3 c3 = vec3(-4.634230498983486, -5.799100973351585, -19.33244095627987);
13 | const vec3 c4 = vec3(6.228269936347081, 14.17993336680509, 56.69055260068105);
14 | const vec3 c5 = vec3(4.776384997670288, -13.74514537774601, -65.35303263337234);
15 | const vec3 c6 = vec3(-5.435455855934631, 4.645852612178535, 26.3124352495832);
16 |
17 | return c0+t*(c1+t*(c2+t*(c3+t*(c4+t*(c5+t*c6)))));
18 |
19 | }
20 |
21 | vec3 plasma(float t) {
22 |
23 | const vec3 c0 = vec3(0.05873234392399702, 0.02333670892565664, 0.5433401826748754);
24 | const vec3 c1 = vec3(2.176514634195958, 0.2383834171260182, 0.7539604599784036);
25 | const vec3 c2 = vec3(-2.689460476458034, -7.455851135738909, 3.110799939717086);
26 | const vec3 c3 = vec3(6.130348345893603, 42.3461881477227, -28.51885465332158);
27 | const vec3 c4 = vec3(-11.10743619062271, -82.66631109428045, 60.13984767418263);
28 | const vec3 c5 = vec3(10.02306557647065, 71.41361770095349, -54.07218655560067);
29 | const vec3 c6 = vec3(-3.658713842777788, -22.93153465461149, 18.19190778539828);
30 |
31 | return c0+t*(c1+t*(c2+t*(c3+t*(c4+t*(c5+t*c6)))));
32 |
33 | }
34 |
35 | vec3 magma(float t) {
36 |
37 | const vec3 c0 = vec3(-0.002136485053939582, -0.000749655052795221, -0.005386127855323933);
38 | const vec3 c1 = vec3(0.2516605407371642, 0.6775232436837668, 2.494026599312351);
39 | const vec3 c2 = vec3(8.353717279216625, -3.577719514958484, 0.3144679030132573);
40 | const vec3 c3 = vec3(-27.66873308576866, 14.26473078096533, -13.64921318813922);
41 | const vec3 c4 = vec3(52.17613981234068, -27.94360607168351, 12.94416944238394);
42 | const vec3 c5 = vec3(-50.76852536473588, 29.04658282127291, 4.23415299384598);
43 | const vec3 c6 = vec3(18.65570506591883, -11.48977351997711, -5.601961508734096);
44 |
45 | return c0+t*(c1+t*(c2+t*(c3+t*(c4+t*(c5+t*c6)))));
46 |
47 | }
48 |
49 | vec3 inferno(float t) {
50 |
51 | const vec3 c0 = vec3(0.0002189403691192265, 0.001651004631001012, -0.01948089843709184);
52 | const vec3 c1 = vec3(0.1065134194856116, 0.5639564367884091, 3.932712388889277);
53 | const vec3 c2 = vec3(11.60249308247187, -3.972853965665698, -15.9423941062914);
54 | const vec3 c3 = vec3(-41.70399613139459, 17.43639888205313, 44.35414519872813);
55 | const vec3 c4 = vec3(77.162935699427, -33.40235894210092, -81.80730925738993);
56 | const vec3 c5 = vec3(-71.31942824499214, 32.62606426397723, 73.20951985803202);
57 | const vec3 c6 = vec3(25.13112622477341, -12.24266895238567, -23.07032500287172);
58 |
59 | return c0+t*(c1+t*(c2+t*(c3+t*(c4+t*(c5+t*c6)))));
60 |
61 | }
62 |
63 | vec3 posterize(vec3 _rgb, float _numColors)
64 | {
65 | return floor(_rgb*_numColors) / _numColors;
66 | }
67 |
68 | vec4 posterize(vec4 _rgba, float _numColors)
69 | {
70 | return vec4(posterize(_rgba.xyz, _numColors), _rgba.w);
71 | }
72 |
73 | #define NUM_OCTAVES 5
74 |
75 | float rand(float n){return fract(sin(n) * 43758.5453123);}
76 |
77 | float noise(float p){
78 | float fl = floor(p);
79 | float fc = fract(p);
80 | return mix(rand(fl), rand(fl + 1.0), fc);
81 | }
82 |
83 | float fbm(float x) {
84 | float v = 0.0;
85 | float a = 0.5;
86 | float shift = float(100);
87 | for (int i = 0; i < NUM_OCTAVES; ++i) {
88 | v += a * noise(x);
89 | x = x * 2.0 + shift;
90 | a *= 0.5;
91 | }
92 | return v;
93 | }
94 |
95 | void main()
96 | {
97 | vec4 col = texture2D(s_texColor, v_texcoord0.xy*0.5 + 0.5);
98 | gl_FragColor = vec4(plasma(fbm(col.x)), col.x);
99 | }
--------------------------------------------------------------------------------
/demo/shaders/demo.QuiverFragmentShader.frag:
--------------------------------------------------------------------------------
1 | $input v_texcoord0
2 |
3 | #include
4 | #include "bgfx_compute.sh"
5 |
6 | SAMPLER2D(s_texColor, 0);
7 | BUFFER_RO(_VelocityIn, vec2, 1);
8 |
9 | uniform float ArrowTileSize;
10 | uniform vec2 VelocitySize;
11 | uniform vec2 WindowSize;
12 |
13 |
14 | vec2 _arrow_tile_center_coord(vec2 pos) {
15 | return (floor(pos / ArrowTileSize) + 0.5) * ArrowTileSize;
16 | }
17 |
18 | float _line(vec2 p, vec2 p1, vec2 p2) {
19 | vec2 center = (p1 + p2) * 0.5;
20 | float len = length(p2 - p1);
21 | vec2 dir = (p2 - p1) / len;
22 | vec2 rel_p = p - center;
23 | float dist1 = abs(dot(rel_p, vec2(dir.y, -dir.x)));
24 | float dist2 = abs(dot(rel_p, dir)) - 0.5*len;
25 | return max(dist1, dist2);
26 | }
27 |
28 | float _vector(vec2 p, vec2 v) {
29 | p -= _arrow_tile_center_coord(p);
30 | float mag_v = length(v), mag_p = length(p);
31 |
32 | if (mag_v > 0.001) {
33 | vec2 dir_v = v / mag_v;
34 |
35 | mag_v = clamp(mag_v, 0.0, ArrowTileSize * 0.5);
36 |
37 | v = dir_v * mag_v;
38 |
39 | float shaft = _line(p, v, -v);
40 | float head = min(_line(p, v, 0.4*v + 0.2*vec2(-v.y, v.x)),
41 | _line(p, v, 0.4*v + 0.2*vec2(v.y, -v.x)));
42 |
43 | return min(shaft, head);
44 | } else {
45 | return 1.0;
46 | }
47 | }
48 |
49 | vec2 _field(vec2 pos) {
50 | ivec2 zero = ivec2(0, 0);
51 | vec2 one = vec2(1.0, 1.0);
52 | vec2 fPos = (one - (pos / WindowSize)) * VelocitySize;
53 | ivec2 size_bounds = ivec2(VelocitySize.x - 1u, VelocitySize.y - 1u);
54 | ivec2 top_right = ivec2(clamp(ceil(fPos), vec2(zero), vec2(size_bounds)));
55 | ivec2 bottom_left = ivec2(clamp(floor(fPos), vec2(zero), vec2(size_bounds)));
56 | vec2 delta = fPos - vec2(bottom_left);
57 | vec2 lt = _VelocityIn[uint(top_right.y) * VelocitySize.x + uint(bottom_left.x)];
58 | vec2 rt = _VelocityIn[uint(top_right.y) * VelocitySize.x + uint(top_right.x)];
59 | vec2 lb = _VelocityIn[uint(bottom_left.y) * VelocitySize.x + uint(bottom_left.x)];
60 | vec2 rb = _VelocityIn[uint(bottom_left.y) * VelocitySize.x + uint(top_right.x)];
61 | vec2 h1 = mix(lt, rt, vec2(delta.x, 0));
62 | vec2 h2 = mix(lb, rb, vec2(delta.x, 0));
63 | return -1.0 * mix(h2, h1, vec2(delta.y, 0)) * (WindowSize / VelocitySize);
64 | }
65 |
66 | void main()
67 | {
68 | float arrow_dist = _vector(gl_FragCoord.xy, _field(_arrow_tile_center_coord(gl_FragCoord.xy)) * ArrowTileSize * 0.4);
69 | gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0 - clamp(arrow_dist, 0.0, 1.0));
70 | }
--------------------------------------------------------------------------------
/demo/shaders/demo.VertexShader.vert:
--------------------------------------------------------------------------------
1 | $input a_position, a_texcoord0
2 | $output v_texcoord0
3 |
4 | #include
5 |
6 | void main()
7 | {
8 | gl_Position = mul(u_modelViewProj, vec4(a_position, 1.0) );
9 | v_texcoord0 = a_texcoord0;
10 | }
--------------------------------------------------------------------------------
/demo/shaders/shader.AddParticle.comp:
--------------------------------------------------------------------------------
1 |
2 | #include "bgfx_compute.sh"
3 |
4 | BUFFER_RO(_ParticlesIn, float, 9);
5 |
6 | BUFFER_WR(_ParticlesOut, float, 10);
7 |
8 | BUFFER_RO(_Obstacles, vec2, 7);
9 |
10 | uniform float _Radius;
11 |
12 | uniform vec2 _Position;
13 |
14 | uniform float _Value;
15 |
16 | uniform vec2 _ParticleSize;
17 |
18 | NUM_THREADS(16, 16, 1)
19 | void main()
20 | {
21 | if (gl_GlobalInvocationID.x >= _ParticleSize.x || gl_GlobalInvocationID.y >= _ParticleSize.y)
22 | {
23 | return;
24 | }
25 | uint pos = gl_GlobalInvocationID.y * _ParticleSize.x + gl_GlobalInvocationID.x ;
26 | vec2 splat_pos = _Position * _ParticleSize;
27 | float val = _ParticlesIn[pos];
28 | float result = val;
29 | float len = distance(splat_pos, gl_GlobalInvocationID.xy);
30 | if (len <= _Radius)
31 | {
32 | result = clamp(val + _Value * (_Radius - len) / _Radius, 0.0f, 255.0f);
33 | }
34 | _ParticlesOut[pos] = result;
35 |
36 | }
37 |
38 |
--------------------------------------------------------------------------------
/demo/shaders/shader.AdvectParticle.comp:
--------------------------------------------------------------------------------
1 | #include "bgfx_compute.sh"
2 |
3 | BUFFER_RO(_ParticlesIn, float, 9);
4 |
5 | BUFFER_WR(_ParticlesOut, float, 10);
6 |
7 | BUFFER_RO(_Velocity, vec2, 1);
8 |
9 | BUFFER_RO(_Obstacles, vec2, 7);
10 |
11 | uniform vec2 _ParticleSize;
12 |
13 | uniform vec2 _VelocitySize;
14 |
15 | uniform float _Dissipation;
16 |
17 | uniform float _ElapsedTime;
18 |
19 | uniform float _Speed;
20 |
21 | vec2 GetVelocity(vec2 fPos)
22 | {
23 | ivec2 zero = ivec2(0, 0);
24 | ivec2 size_bounds = ivec2(_VelocitySize.x - 1u, _VelocitySize.y - 1u);
25 | ivec2 top_right = ivec2(clamp(ceil(fPos), vec2(zero), vec2(size_bounds)));
26 | ivec2 bottom_left = ivec2(clamp(floor(fPos), vec2(zero), vec2(size_bounds)));
27 | vec2 delta = fPos - vec2(bottom_left);
28 | vec2 lt = _Velocity[uint(top_right.y) * _VelocitySize.x + uint(bottom_left.x)];
29 | vec2 rt = _Velocity[uint(top_right.y) * _VelocitySize.x + uint(top_right.x)];
30 | vec2 lb = _Velocity[uint(bottom_left.y) * _VelocitySize.x + uint(bottom_left.x)];
31 | vec2 rb = _Velocity[uint(bottom_left.y) * _VelocitySize.x + uint(top_right.x)];
32 | vec2 h1 = mix(lt, rt, vec2(delta.x));
33 | vec2 h2 = mix(lb, rb, vec2(delta.x));
34 | return mix(h2, h1, vec2(delta.y)) * (_ParticleSize / _VelocitySize);
35 | }
36 |
37 | NUM_THREADS(16, 16, 1)
38 | void main()
39 | {
40 | if (gl_GlobalInvocationID.x >= _ParticleSize.x || gl_GlobalInvocationID.y >= _ParticleSize.y)
41 | {
42 | return;
43 | }
44 |
45 | uint particle_pos = gl_GlobalInvocationID.y * _ParticleSize.x + gl_GlobalInvocationID.x;
46 | vec2 fNormalisedPos = vec2(float(gl_GlobalInvocationID.x) / float(_ParticleSize.x), float(gl_GlobalInvocationID.y) / float(_ParticleSize.y)) * vec2(_VelocitySize);
47 | uint obstacle_pos = (uint(fNormalisedPos.y)) * _VelocitySize.x + (uint(fNormalisedPos.x));
48 | vec2 obstacle = _Obstacles[obstacle_pos];
49 |
50 | if (obstacle.x > 0.0f || obstacle.y > 0.0f)
51 | {
52 | _ParticlesOut[particle_pos] = 0.0f;
53 | }
54 | else
55 | {
56 | vec2 vel = GetVelocity(fNormalisedPos);
57 | vec2 final_pos = vec2(float(gl_GlobalInvocationID.x) - vel.x * _ElapsedTime * _Speed, float(gl_GlobalInvocationID.y) - vel.y * _ElapsedTime * _Speed);
58 | ivec2 zero = ivec2(0, 0);
59 | ivec2 size_bounds = ivec2(_ParticleSize.x - 1u, _ParticleSize.y - 1u);
60 | ivec2 top_right = ivec2(clamp(ceil(final_pos), vec2(zero), vec2(size_bounds)));
61 | ivec2 bottom_left = ivec2(clamp(floor(final_pos), vec2(zero), vec2(size_bounds)));
62 | vec2 delta = final_pos - vec2(bottom_left);
63 | float lt = _ParticlesIn[uint(top_right.y) * _ParticleSize.x + uint(bottom_left.x)];
64 | float rt = _ParticlesIn[uint(top_right.y) * _ParticleSize.x + uint(top_right.x)];
65 | float lb = _ParticlesIn[uint(bottom_left.y) * _ParticleSize.x + uint(bottom_left.x)];
66 | float rb = _ParticlesIn[uint(bottom_left.y) * _ParticleSize.x + uint(top_right.x)];
67 | float h1 = mix(lt, rt, vec2(delta.x));
68 | float h2 = mix(lb, rb, vec2(delta.x));
69 | _ParticlesOut[particle_pos] = mix(h2, h1, vec2(delta.y)) * _Dissipation;
70 | }
71 | }
--------------------------------------------------------------------------------
/demo/shaders/varying.def.sc:
--------------------------------------------------------------------------------
1 | vec4 v_color0 : COLOR0;
2 | vec2 v_texcoord0 : TEXCOORD0;
3 |
4 | vec3 a_position : POSITION;
5 | vec2 a_texcoord0 : TEXCOORD0;
6 | vec4 a_color0 : COLOR0;
--------------------------------------------------------------------------------
/demo/simulation_demo.py:
--------------------------------------------------------------------------------
1 | import ctypes
2 | from ctypes import c_float, sizeof, Structure
3 | from pathlib import Path
4 |
5 | import glfw
6 | import numpy as np
7 | from pybgfx import (
8 | bgfx,
9 | ImGui,
10 | )
11 | from pybgfx.utils.imgui_utils import ImGuiExtra
12 | from pybgfx.constants import (
13 | BGFX_CLEAR_COLOR,
14 | BGFX_CLEAR_DEPTH,
15 | BGFX_RESET_VSYNC,
16 | BGFX_DEBUG_TEXT,
17 | BGFX_STATE_WRITE_RGB,
18 | BGFX_STATE_WRITE_A,
19 | BGFX_STATE_DEFAULT,
20 | BGFX_TEXTURE_COMPUTE_WRITE,
21 | BGFX_RESET_HIDPI,
22 | BGFX_STATE_BLEND_ALPHA,
23 | )
24 | from pybgfx.utils import as_void_ptr
25 | from pybgfx.utils.shaders_utils import ShaderType, load_shader
26 | from loguru import logger
27 |
28 | from demo.example_window import ExampleWindow
29 | from demo.smooth_particles_area import SmoothParticlesArea
30 | # from demo.utils.imgui_utils import show_properties_dialog
31 | from demo.utils.imgui_utils import show_properties_dialog
32 | from demo.utils.matrix_utils import look_at, proj
33 | from natrix.core.fluid_simulator import FluidSimulator
34 |
35 | logger.enable("bgfx")
36 |
37 |
38 | class PosColorVertex(Structure):
39 | _fields_ = [
40 | ("m_x", c_float),
41 | ("m_y", c_float),
42 | ("m_z", c_float),
43 | ("m_u", c_float),
44 | ("m_v", c_float),
45 | ("m_w", c_float),
46 | ]
47 |
48 |
49 | cube_vertices = (PosColorVertex * 4)(
50 | PosColorVertex(-1.0, 1.0, 0.0, -1.0, 1.0, 1.0),
51 | PosColorVertex(1.0, 1.0, 0.0, 1.0, 1.0, 1.0),
52 | PosColorVertex(-1.0, -1.0, 0.0, -1.0, -1.0, 1.0),
53 | PosColorVertex(1.0, -1.0, 0.0, 1.0, -1.0, 1.0),
54 | )
55 |
56 | cube_indices = np.array([0, 1, 2, 1, 2, 3], dtype=np.uint16)
57 |
58 | root_path = Path(__file__).parent / "shaders"
59 |
60 |
61 | class SimulationDemo(ExampleWindow):
62 | def __init__(self, width, height, title):
63 | super().__init__(width, height, title)
64 |
65 | self.init_conf = bgfx.Init()
66 | self.init_conf.debug = True
67 | self.init_conf.resolution.width = self.fb_width
68 | self.init_conf.resolution.height = self.fb_height
69 | self.init_conf.resolution.reset = BGFX_RESET_VSYNC | BGFX_RESET_HIDPI
70 | self.old_mouse_x = -1
71 | self.old_mouse_y = -1
72 |
73 | self.particles_strength = 0.04
74 | self.particles_diameter = 250.0
75 |
76 | self.show_quiver_plot_overlay = ctypes.c_bool(False)
77 | self.quiver_plot_resolution = ctypes.c_int(2)
78 | self.quiver_plot_resolutions = (8.0, 16.0, 32.0, 64.0)
79 |
80 | def init(self, platform_data):
81 | bgfx.renderFrame()
82 | bgfx.setPlatformData(platform_data)
83 | bgfx.init(self.init_conf)
84 | bgfx.reset(
85 | self.fb_width,
86 | self.fb_height,
87 | BGFX_RESET_VSYNC | BGFX_RESET_HIDPI,
88 | self.init_conf.resolution.format,
89 | )
90 |
91 | bgfx.setDebug(BGFX_DEBUG_TEXT)
92 | # bgfx.setViewClear(0, BGFX_CLEAR_COLOR | BGFX_CLEAR_DEPTH, 0xFFFFFFFF, 1.0, 0)
93 | bgfx.setViewClear(0, BGFX_CLEAR_COLOR | BGFX_CLEAR_DEPTH, 0x1a0427FF, 1.0, 0)
94 |
95 | self.vertex_layout = bgfx.VertexLayout()
96 | self.vertex_layout.begin().add(
97 | bgfx.Attrib.Position, 3, bgfx.AttribType.Float
98 | ).add(bgfx.Attrib.TexCoord0, 3, bgfx.AttribType.Float).end()
99 |
100 | self.fluid_simulator = FluidSimulator(
101 | self.width // 2, self.height // 2, self.vertex_layout
102 | )
103 | self.fluid_simulator.vorticity = 1.0
104 | self.fluid_simulator.viscosity = 0.5
105 | self.fluid_simulator.iterations = 50
106 |
107 | self.particle_area = SmoothParticlesArea(
108 | self.fb_width, self.fb_height, self.fluid_simulator, self.vertex_layout
109 | )
110 | self.particle_area.dissipation = 0.980
111 |
112 | # Create static vertex buffer
113 | vb_memory = bgfx.copy(as_void_ptr(cube_vertices), sizeof(PosColorVertex) * 4)
114 | self.vertex_buffer = bgfx.createVertexBuffer(vb_memory, self.vertex_layout)
115 |
116 | ib_memory = bgfx.copy(as_void_ptr(cube_indices), cube_indices.nbytes)
117 | self.index_buffer = bgfx.createIndexBuffer(ib_memory)
118 |
119 | self.output_texture = bgfx.createTexture2D(
120 | self.fb_width,
121 | self.fb_height,
122 | False,
123 | 1,
124 | bgfx.TextureFormat.RGBA8,
125 | BGFX_TEXTURE_COMPUTE_WRITE,
126 | )
127 |
128 | self.texture_uniform = bgfx.createUniform(
129 | "InputTexture", bgfx.UniformType.Sampler
130 | )
131 | self.window_size_uniform = bgfx.createUniform(
132 | "WindowSize", bgfx.UniformType.Vec4
133 | )
134 | self.velocity_size_uniform = bgfx.createUniform(
135 | "VelocitySize", bgfx.UniformType.Vec4
136 | )
137 | self.arrow_tile_size_uniform = bgfx.createUniform(
138 | "ArrowTileSize", bgfx.UniformType.Vec4
139 | )
140 |
141 | # Create program from shaders.
142 | self.main_program = bgfx.createProgram(
143 | load_shader(
144 | "demo.VertexShader.vert", ShaderType.VERTEX, root_path=root_path
145 | ),
146 | load_shader(
147 | "demo.FieldFragmentShader.frag",
148 | ShaderType.FRAGMENT,
149 | root_path=root_path,
150 | ),
151 | True,
152 | )
153 | self.quiver_program = bgfx.createProgram(
154 | load_shader(
155 | "demo.VertexShader.vert", ShaderType.VERTEX, root_path=root_path
156 | ),
157 | load_shader(
158 | "demo.QuiverFragmentShader.frag",
159 | ShaderType.FRAGMENT,
160 | root_path=root_path,
161 | ),
162 | True,
163 | )
164 | self.cs_program = bgfx.createProgram(
165 | load_shader(
166 | "demo.ComputeShader.comp", ShaderType.COMPUTE, root_path=root_path
167 | ),
168 | True,
169 | )
170 |
171 | ImGuiExtra.create(36.0)
172 |
173 | def shutdown(self):
174 | self.fluid_simulator.destroy()
175 | self.particle_area.destroy()
176 |
177 | ImGuiExtra.destroy()
178 |
179 | bgfx.destroy(self.index_buffer)
180 | bgfx.destroy(self.vertex_buffer)
181 | bgfx.destroy(self.output_texture)
182 | bgfx.destroy(self.texture_uniform)
183 | bgfx.destroy(self.window_size_uniform)
184 | bgfx.destroy(self.velocity_size_uniform)
185 | bgfx.destroy(self.arrow_tile_size_uniform)
186 | bgfx.destroy(self.main_program)
187 | bgfx.destroy(self.quiver_program)
188 | bgfx.destroy(self.cs_program)
189 |
190 | bgfx.shutdown()
191 |
192 | def update(self, dt):
193 | mouse_x, mouse_y, buttons_states = self.get_mouse_state()
194 |
195 | ImGuiExtra.begin_frame(
196 | int(mouse_x), int(mouse_y), buttons_states, 0, self.fb_width, self.fb_height
197 | )
198 | show_properties_dialog(self.fb_width, self.fb_height, self.hidpi)
199 | self._create_imgui_config_dialog()
200 |
201 | ImGuiExtra.end_frame()
202 |
203 | at = (c_float * 3)(*[0.0, 0.0, 0.0])
204 | eye = (c_float * 3)(*[0.0, 0.0, 10.0])
205 | up = (c_float * 3)(*[0.0, 1.0, 0.0])
206 |
207 | view = look_at(eye, at, up)
208 | projection = proj(11.4, 1, 0.1, 100.0)
209 |
210 | bgfx.setViewRect(0, 0, 0, self.fb_width, self.fb_height)
211 |
212 | bgfx.setViewTransform(0, as_void_ptr(view), as_void_ptr(projection))
213 |
214 | bgfx.setVertexBuffer(0, self.vertex_buffer, 0, 4)
215 | bgfx.setIndexBuffer(self.index_buffer, 0, cube_indices.size)
216 |
217 | bgfx.setState(BGFX_STATE_DEFAULT)
218 | bgfx.setImage(0, self.output_texture, 0, bgfx.Access.Write)
219 |
220 | self.fluid_simulator.add_circle_obstacle((0.5, 0.5), 40.0)
221 |
222 | self.fluid_simulator.update(dt)
223 | self.particle_area.update(dt)
224 |
225 | if glfw.get_mouse_button(self.window, glfw.MOUSE_BUTTON_LEFT) == glfw.PRESS\
226 | and not ImGui.GetIO().WantCaptureMouse:
227 | n_mouse_x, n_mouse_y = self._get_normalized_mouse_coords(mouse_x, mouse_y)
228 | n_old_mouse_x, n_old_mouse_y = self._get_normalized_mouse_coords(
229 | self.old_mouse_x, self.old_mouse_y
230 | )
231 | vel_x = n_mouse_x - n_old_mouse_x
232 | vel_y = n_mouse_y - n_old_mouse_y
233 | # if vel_x != 0 and vel_y != 0:
234 | self.fluid_simulator.add_velocity(
235 | (n_mouse_x, n_mouse_y), (vel_x * 10, vel_y * 10), 32.0
236 | )
237 | self.particle_area.add_particles((n_mouse_x, n_mouse_y), self.particles_diameter, self.particles_strength)
238 |
239 | self.old_mouse_x = mouse_x
240 | self.old_mouse_y = mouse_y
241 |
242 | bgfx.setState(
243 | 0 | BGFX_STATE_WRITE_RGB | BGFX_STATE_WRITE_A | BGFX_STATE_BLEND_ALPHA
244 | )
245 |
246 | bgfx.dispatch(0, self.cs_program, self.fb_width // 16, self.fb_height // 16)
247 | bgfx.setTexture(0, self.texture_uniform, self.output_texture)
248 | bgfx.submit(0, self.main_program, 0, False)
249 |
250 | if self.show_quiver_plot_overlay.value:
251 | bgfx.setBuffer(
252 | 1, self.fluid_simulator.get_velocity_buffer(), bgfx.Access.Read
253 | )
254 | bgfx.setUniform(
255 | self.window_size_uniform,
256 | as_void_ptr((c_float * 2)(self.fb_width, self.fb_height)),
257 | )
258 | bgfx.setUniform(
259 | self.velocity_size_uniform,
260 | as_void_ptr(
261 | (c_float * 2)(
262 | self.fluid_simulator.width, self.fluid_simulator.height
263 | )
264 | ),
265 | )
266 | bgfx.setUniform(
267 | self.arrow_tile_size_uniform,
268 | as_void_ptr(
269 | (c_float * 1)(
270 | self.quiver_plot_resolutions[self.quiver_plot_resolution.value]
271 | )
272 | ),
273 | )
274 | bgfx.submit(0, self.quiver_program, 0, False)
275 |
276 | bgfx.frame()
277 |
278 | def resize(self):
279 | bgfx.reset(
280 | self.fb_width,
281 | self.fb_height,
282 | BGFX_RESET_VSYNC | BGFX_RESET_HIDPI,
283 | self.init_conf.resolution.format,
284 | )
285 |
286 | def _create_imgui_config_dialog(self):
287 | res_multiplier = 2 if self.hidpi else 1
288 |
289 | ImGui.SetNextWindowPos(
290 | ImGui.ImVec2(
291 | self.fb_width - self.fb_width / 4.1 - 20.0 * res_multiplier,
292 | 40.0 * res_multiplier,
293 | ),
294 | ImGui.ImGuiCond_FirstUseEver,
295 | )
296 | ImGui.SetNextWindowSize(
297 | ImGui.ImVec2(self.fb_width / 4.1, self.fb_height / 2.1),
298 | ImGui.ImGuiCond_FirstUseEver,
299 | )
300 |
301 | ImGui.Begin("\uf013 Settings")
302 |
303 | vorticity = ctypes.c_float(self.fluid_simulator.vorticity)
304 | viscosity = ctypes.c_float(self.fluid_simulator.viscosity)
305 | speed = ctypes.c_float(self.fluid_simulator.speed)
306 | iterations = ctypes.c_int(self.fluid_simulator.iterations)
307 | borders = ctypes.c_bool(self.fluid_simulator.has_borders)
308 | fluid_dissipation = ctypes.c_float(self.fluid_simulator.dissipation)
309 | part_strength = ctypes.c_float(self.particles_strength)
310 | part_diameter = ctypes.c_float(self.particles_diameter)
311 |
312 | ImGui.Text("Fluid simulation parameters")
313 |
314 | if ImGui.SliderFloat("Vorticity", vorticity, 0.0, 10.0):
315 | self.fluid_simulator.vorticity = vorticity.value
316 |
317 | if ImGui.SliderFloat("Viscosity", viscosity, 0.0, 10.0):
318 | self.fluid_simulator.viscosity = viscosity.value
319 |
320 | if ImGui.SliderFloat("Speed", speed, 1.0, 1000.0):
321 | self.fluid_simulator.speed = speed.value
322 | self.particle_area.speed = speed.value
323 |
324 | if ImGui.SliderFloat("V_Dissipation", fluid_dissipation, 0.500, 1.0):
325 | self.fluid_simulator.dissipation = fluid_dissipation.value
326 |
327 | if ImGui.SliderInt("Iteration", iterations, 10, 100):
328 | self.fluid_simulator.iterations = iterations.value
329 |
330 | if ImGui.Checkbox("Borders", borders):
331 | self.fluid_simulator.has_borders = borders.value
332 |
333 | ImGui.Separator()
334 |
335 | particles_dissipation = ctypes.c_float(self.particle_area.dissipation)
336 |
337 | ImGui.Text("Particles area parameters")
338 |
339 | if ImGui.SliderFloat("P_Dissipation", particles_dissipation, 0.900, 1.0):
340 | self.particle_area.dissipation = particles_dissipation.value
341 |
342 | if ImGui.SliderFloat("Strength", part_strength, 0.0, 1.0):
343 | self.particles_strength = part_strength.value
344 |
345 | if ImGui.SliderFloat("Diameter", part_diameter, 100.0, 1000.0):
346 | self.particles_diameter = part_diameter.value
347 |
348 | ImGui.Separator()
349 |
350 | stop = ctypes.c_bool(not self.fluid_simulator.simulate)
351 |
352 | if ImGui.Checkbox("Stop simulation", stop):
353 | self.fluid_simulator.simulate = not stop.value
354 | self.particle_area.simulate = not stop.value
355 |
356 | ImGui.Checkbox("Show velocity field", self.show_quiver_plot_overlay)
357 | ImGui.SameLine()
358 | ImGui.PushItemWidth(200.0)
359 | ImGui.Combo(
360 | "", self.quiver_plot_resolution, ["Very Small", "Small", "Medium", "Large"], 4,
361 | )
362 | ImGui.PopItemWidth()
363 | ImGui.End()
364 |
365 |
366 | if __name__ == "__main__":
367 | demo = SimulationDemo(1280, 720, "Fluid Simulation Demo")
368 | demo.run()
369 |
--------------------------------------------------------------------------------
/demo/smooth_particles_area.py:
--------------------------------------------------------------------------------
1 | from ctypes import c_float
2 | from math import ceil
3 | from pathlib import Path
4 |
5 | from pybgfx import bgfx
6 | from pybgfx.utils import as_void_ptr
7 | from pybgfx.utils.shaders_utils import ShaderType, load_shader
8 | from natrix.core.common.constants import TemplateConstants
9 | from natrix.core.fluid_simulator import FluidSimulator
10 | from natrix.core.utils.shaders_utils import create_buffer
11 |
12 | root_path = Path(__file__).parent / "shaders"
13 |
14 |
15 | class SmoothParticlesArea:
16 | PARTICLES_IN = 0
17 | PARTICLES_OUT = 1
18 |
19 | _width = 512
20 | _height = 512
21 | _particles_buffer = None
22 |
23 | _speed = 500.0
24 | _dissipation = 1.0
25 |
26 | simulate = True
27 |
28 | def __init__(
29 | self,
30 | width: int,
31 | height: int,
32 | fluid_simulation: FluidSimulator,
33 | vertex_layout: bgfx.VertexLayout,
34 | ):
35 | self.fluid_simulation = fluid_simulation
36 | self.vertex_layout = vertex_layout
37 |
38 | self._width = width
39 | self._height = height
40 |
41 | self._create_uniforms()
42 |
43 | self._load_compute_kernels()
44 | self._set_size()
45 | self._create_buffers()
46 | self._init_compute_kernels()
47 |
48 | @property
49 | def speed(self):
50 | return self._speed
51 |
52 | @speed.setter
53 | def speed(self, value):
54 | if value > 0:
55 | self._speed = value
56 | else:
57 | raise ValueError("'Speed' should be greater than zero")
58 |
59 | @property
60 | def dissipation(self):
61 | return self._dissipation
62 |
63 | @dissipation.setter
64 | def dissipation(self, value):
65 | if value > 0:
66 | self._dissipation = value
67 | else:
68 | raise ValueError("'Dissipation' should be grater than zero")
69 |
70 | def add_particles(self, position: tuple, radius: float, strength: float):
71 | if self.simulate:
72 | self._init_compute_kernels()
73 | bgfx.setUniform(
74 | self.position_uniform,
75 | as_void_ptr((c_float * 2)(position[0], position[1])),
76 | )
77 | bgfx.setUniform(self.value_uniform, as_void_ptr((c_float * 1)(strength)))
78 | bgfx.setUniform(self.radius_uniform, as_void_ptr((c_float * 1)(radius)))
79 |
80 | bgfx.dispatch(
81 | 0, self._add_particles_kernel, self._num_groups_x, self._num_groups_x, 1
82 | )
83 | self._flip_buffer()
84 |
85 | def update(self, time_delta: float):
86 | self._init_compute_kernels()
87 |
88 | if self.simulate:
89 | bgfx.setUniform(
90 | self.dissipation_uniform, as_void_ptr((c_float * 1)(self.dissipation))
91 | )
92 | bgfx.setUniform(
93 | self.elapsed_time_uniform, as_void_ptr((c_float * 1)(time_delta))
94 | )
95 | bgfx.setUniform(self.speed_uniform, as_void_ptr((c_float * 1)(self.speed)))
96 |
97 | bgfx.dispatch(
98 | 0,
99 | self._advect_particles_kernel,
100 | self._num_groups_x,
101 | self._num_groups_y,
102 | 1,
103 | )
104 | self._flip_buffer()
105 |
106 | def _set_size(self):
107 | self._particle_size = (self._width, self._height)
108 | self._velocity_size = (
109 | self.fluid_simulation.width,
110 | self.fluid_simulation.height,
111 | )
112 | group_size_x = TemplateConstants.NUM_THREADS.value
113 | group_size_y = TemplateConstants.NUM_THREADS.value
114 |
115 | self._num_cells = self._width * self._height
116 | self._num_groups_x = int(ceil(float(self._width) / float(group_size_x)))
117 | self._num_groups_y = int(ceil(float(self._height) / float(group_size_y)))
118 |
119 | def _create_uniforms(self):
120 | self.particle_size_uniform = bgfx.createUniform(
121 | "_ParticleSize", bgfx.UniformType.Vec4
122 | )
123 | self.position_uniform = bgfx.createUniform("_Position", bgfx.UniformType.Vec4)
124 | self.value_uniform = bgfx.createUniform("_Value", bgfx.UniformType.Vec4)
125 | self.radius_uniform = bgfx.createUniform("_Radius", bgfx.UniformType.Vec4)
126 | self.dissipation_uniform = bgfx.createUniform(
127 | "_Dissipation", bgfx.UniformType.Vec4
128 | )
129 | self.elapsed_time_uniform = bgfx.createUniform(
130 | "_ElapsedTime", bgfx.UniformType.Vec4
131 | )
132 | self.speed_uniform = bgfx.createUniform("_Speed", bgfx.UniformType.Vec4)
133 | self.velocity_size_uniform = bgfx.createUniform(
134 | "_VelocitySize", bgfx.UniformType.Vec4
135 | )
136 |
137 | def _init_compute_kernels(self):
138 | bgfx.setUniform(
139 | self.particle_size_uniform,
140 | as_void_ptr((c_float * 2)(self._particle_size[0], self._particle_size[1])),
141 | )
142 | bgfx.setUniform(
143 | self.velocity_size_uniform,
144 | as_void_ptr((c_float * 2)(self._velocity_size[0], self._velocity_size[1])),
145 | )
146 |
147 | bgfx.setBuffer(
148 | TemplateConstants.PARTICLES_IN.value,
149 | self._particles_buffer[self.PARTICLES_IN],
150 | bgfx.Access.Write,
151 | )
152 | bgfx.setBuffer(
153 | TemplateConstants.PARTICLES_OUT.value,
154 | self._particles_buffer[self.PARTICLES_OUT],
155 | bgfx.Access.Write,
156 | )
157 |
158 | def _create_buffers(self):
159 | self._particles_buffer = [
160 | create_buffer(self._num_cells, 1, self.vertex_layout),
161 | create_buffer(self._num_cells, 1, self.vertex_layout),
162 | ]
163 |
164 | def _load_compute_kernels(self):
165 | self._add_particles_kernel = bgfx.createProgram(
166 | load_shader(
167 | "shader.AddParticle.comp", ShaderType.COMPUTE, root_path=root_path
168 | ),
169 | True,
170 | )
171 | self._advect_particles_kernel = bgfx.createProgram(
172 | load_shader(
173 | "shader.AdvectParticle.comp", ShaderType.COMPUTE, root_path=root_path
174 | ),
175 | True,
176 | )
177 |
178 | def _flip_buffer(self):
179 | tmp = self.PARTICLES_IN
180 | self.PARTICLES_IN = self.PARTICLES_OUT
181 | self.PARTICLES_OUT = tmp
182 |
183 | bgfx.setBuffer(
184 | TemplateConstants.PARTICLES_IN.value,
185 | self._particles_buffer[self.PARTICLES_IN],
186 | bgfx.Access.Read,
187 | )
188 | bgfx.setBuffer(
189 | TemplateConstants.PARTICLES_OUT.value,
190 | self._particles_buffer[self.PARTICLES_OUT],
191 | bgfx.Access.Write,
192 | )
193 |
194 | def destroy(self):
195 | # Destroy uniforms
196 | bgfx.destroy(self.particle_size_uniform)
197 | bgfx.destroy(self.position_uniform)
198 | bgfx.destroy(self.value_uniform)
199 | bgfx.destroy(self.radius_uniform)
200 | bgfx.destroy(self.dissipation_uniform)
201 | bgfx.destroy(self.elapsed_time_uniform)
202 | bgfx.destroy(self.speed_uniform)
203 | bgfx.destroy(self.velocity_size_uniform)
204 |
205 | # Destroy buffers
206 | bgfx.destroy(self._particles_buffer[0])
207 | bgfx.destroy(self._particles_buffer[1])
208 |
209 | # Destroy compute shaders
210 | bgfx.destroy(self._add_particles_kernel)
211 | bgfx.destroy(self._advect_particles_kernel)
212 |
--------------------------------------------------------------------------------
/demo/utils/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fbertola/Natrix/403fed55cab582563615c71ef11b40b11cdb7777/demo/utils/__init__.py
--------------------------------------------------------------------------------
/demo/utils/imgui_utils.py:
--------------------------------------------------------------------------------
1 | from array import array
2 |
3 | # noinspection PyUnresolvedReferences
4 | from pybgfx import bgfx, ImGui
5 |
6 |
7 | class SampleData:
8 | m_values = []
9 | m_offset = 0
10 | m_min = 0.0
11 | m_max = 0.0
12 | m_avg = 0.0
13 |
14 | def __init__(self):
15 | self.reset()
16 |
17 | def reset(self):
18 | self.m_values = [0.0] * 100
19 | self.m_offset = 0
20 | self.m_min = 0.0
21 | self.m_max = 0.0
22 | self.m_avg = 0.0
23 |
24 | def push_sample(self, value: float):
25 | self.m_values[self.m_offset] = value
26 | self.m_offset = (self.m_offset + 1) % 100
27 |
28 | min_val = float("inf")
29 | max_val = float("-inf")
30 | avg_val = 0.0
31 |
32 | for val in self.m_values:
33 | min_val = min(min_val, val)
34 | max_val = max(max_val, val)
35 | avg_val += val
36 |
37 | self.m_min = min_val
38 | self.m_max = max_val
39 | self.m_avg = avg_val / 100.0
40 |
41 |
42 | def bar(width, max_width, height, color):
43 | style = ImGui.GetStyle()
44 |
45 | hovered_color = ImGui.ImVec4(
46 | color.x + color.x * 0.1,
47 | color.y + color.y * 0.1,
48 | color.z + color.z * 0.1,
49 | color.w + color.w * 0.1,
50 | )
51 |
52 | ImGui.PushStyleColor(ImGui.ImGuiCol_Button, color)
53 | ImGui.PushStyleColor(ImGui.ImGuiCol_ButtonHovered, hovered_color)
54 | ImGui.PushStyleColor(ImGui.ImGuiCol_ButtonActive, color)
55 | ImGui.PushStyleVar(ImGui.ImGuiStyleVar_FrameRounding, 0.0)
56 | ImGui.PushStyleVar(
57 | ImGui.ImGuiStyleVar_ItemSpacing, ImGui.ImVec2(0.0, style.ItemSpacing.y)
58 | )
59 |
60 | item_hovered = False
61 |
62 | ImGui.Button("", ImGui.ImVec2(width, height))
63 | item_hovered |= ImGui.IsItemHovered(0)
64 |
65 | ImGui.SameLine()
66 | ImGui.InvisibleButton("", ImGui.ImVec2(max(1.0, max_width - width), height), 0)
67 | item_hovered |= ImGui.IsItemHovered(0)
68 |
69 | ImGui.PopStyleVar(2)
70 | ImGui.PopStyleColor(3)
71 |
72 | return item_hovered
73 |
74 |
75 | s_resourceColor = ImGui.ImVec4(0.5, 0.5, 0.5, 1.0)
76 | s_frame_time = SampleData()
77 |
78 |
79 | def resource_bar(name, tooltip, num, _max, max_width, height):
80 | item_hovered = False
81 |
82 | ImGui.Text(f"{name}: {num:4d} / {_max:4d}")
83 | item_hovered |= ImGui.IsItemHovered(0)
84 | ImGui.SameLine()
85 |
86 | percentage = float(num) / float(_max)
87 |
88 | item_hovered |= bar(
89 | max(1.0, percentage * max_width), max_width, height, s_resourceColor
90 | )
91 | ImGui.SameLine()
92 |
93 | ImGui.Text(f"{(percentage * 100.0):5.2f}%")
94 |
95 | if item_hovered:
96 | ImGui.SetTooltip(f"{tooltip} {(percentage * 100.0):5.2f}%")
97 |
98 |
99 | def show_properties_dialog(fb_width, fb_height, hidpi: bool):
100 | res_multiplier = 2 if hidpi else 1
101 |
102 | ImGui.SetNextWindowPos(ImGui.ImVec2(
103 | 20.0 * res_multiplier, fb_height - fb_height / 1.7 - 20.0 * res_multiplier
104 | ), ImGui.ImGuiCond_FirstUseEver)
105 | ImGui.SetNextWindowSize(ImGui.ImVec2(
106 | fb_width / 4.3, fb_height / 1.7
107 | ), ImGui.ImGuiCond_FirstUseEver)
108 |
109 | ImGui.Begin("\uf080 Statistics")
110 | renderer_name = bgfx.getRendererName(bgfx.getRendererType())
111 | ImGui.TextWrapped("Current renderer: {}".format(renderer_name))
112 | ImGui.Separator()
113 |
114 | stats = bgfx.getStats()
115 | to_ms_cpu = 1000.0 / stats.cpuTimerFreq
116 | to_ms_gpu = 1000.0 / stats.gpuTimerFreq
117 | frame_ms = float(stats.cpuTimeFrame) * to_ms_cpu
118 |
119 | s_frame_time.push_sample(frame_ms)
120 | fps = 1000.0 / s_frame_time.m_avg
121 |
122 | frame_text_overlay = f"\uf063{s_frame_time.m_min:7.3f}ms, \uf062{s_frame_time.m_max:7.3f}ms\nAvg: {s_frame_time.m_avg:7.3f}ms, {fps:6.2f} FPS"
123 | ImGui.PushStyleColor(
124 | ImGui.ImGuiCol_PlotHistogram, ImGui.ImVec4(0.0, 0.5, 0.15, 1.0)
125 | )
126 | ImGui.PushItemWidth(-1)
127 | ImGui.PlotHistogram(
128 | "",
129 | array("f", s_frame_time.m_values),
130 | 100,
131 | s_frame_time.m_offset,
132 | frame_text_overlay,
133 | 0.0,
134 | 60.0,
135 | ImGui.ImVec2(0.0, 45.0 * res_multiplier),
136 | )
137 | ImGui.PopItemWidth()
138 | ImGui.PopStyleColor()
139 |
140 | ImGui.Text(
141 | f"Submit CPU {(stats.cpuTimeEnd - stats.cpuTimeBegin) * to_ms_cpu:.3f}, GPU {(stats.gpuTimeEnd - stats.gpuTimeBegin) * to_ms_gpu:.3f} (L: {stats.maxGpuLatency})"
142 | )
143 |
144 | if stats.gpuMemoryMax > 0:
145 | ImGui.Text(f"GPU mem: {stats.gpuMemoryUsed} / {stats.gpuMemoryMax}")
146 |
147 | if ImGui.CollapsingHeader("\uf12e Resources", ImGui.ImGuiTreeNodeFlags_DefaultOpen):
148 | caps = bgfx.getCaps()
149 | item_height = ImGui.GetTextLineHeightWithSpacing()
150 | max_width = 90.0
151 | ImGui.PushFont(ImGui.Font.Mono)
152 | ImGui.Text("Res: Num / Max")
153 | resource_bar(
154 | "DIB",
155 | "Dynamic index buffers",
156 | stats.numDynamicIndexBuffers,
157 | caps.limits.maxDynamicIndexBuffers,
158 | max_width,
159 | item_height,
160 | )
161 | resource_bar(
162 | "DVB",
163 | "Dynamic vertex buffers",
164 | stats.numDynamicVertexBuffers,
165 | caps.limits.maxDynamicVertexBuffers,
166 | max_width,
167 | item_height,
168 | )
169 | resource_bar(
170 | " FB",
171 | "Frame buffers",
172 | stats.numFrameBuffers,
173 | caps.limits.maxFrameBuffers,
174 | max_width,
175 | item_height,
176 | )
177 | resource_bar(
178 | " IB",
179 | "Index buffers",
180 | stats.numIndexBuffers,
181 | caps.limits.maxIndexBuffers,
182 | max_width,
183 | item_height,
184 | )
185 | resource_bar(
186 | " OQ",
187 | "Occlusion queries",
188 | stats.numOcclusionQueries,
189 | caps.limits.maxOcclusionQueries,
190 | max_width,
191 | item_height,
192 | )
193 | resource_bar(
194 | " P",
195 | "Programs",
196 | stats.numPrograms,
197 | caps.limits.maxPrograms,
198 | max_width,
199 | item_height,
200 | )
201 | resource_bar(
202 | " S",
203 | "Shaders",
204 | stats.numShaders,
205 | caps.limits.maxShaders,
206 | max_width,
207 | item_height,
208 | )
209 | resource_bar(
210 | " T",
211 | "Textures",
212 | stats.numTextures,
213 | caps.limits.maxTextures,
214 | max_width,
215 | item_height,
216 | )
217 | resource_bar(
218 | " U",
219 | "Uniforms",
220 | stats.numUniforms,
221 | caps.limits.maxUniforms,
222 | max_width,
223 | item_height,
224 | )
225 | resource_bar(
226 | " VB",
227 | "Vertex buffers",
228 | stats.numVertexBuffers,
229 | caps.limits.maxVertexBuffers,
230 | max_width,
231 | item_height,
232 | )
233 | resource_bar(
234 | " VL",
235 | "Vertex layouts",
236 | stats.numVertexLayouts,
237 | caps.limits.maxVertexLayouts,
238 | max_width,
239 | item_height,
240 | )
241 | ImGui.PopFont()
242 |
243 | ImGui.End()
244 |
--------------------------------------------------------------------------------
/demo/utils/matrix_utils.py:
--------------------------------------------------------------------------------
1 | import ctypes
2 | import math
3 |
4 | import numpy as np
5 |
6 |
7 | def sub(v1, v2):
8 | return [v1[0] - v2[0], v1[1] - v2[1], v1[2] - v2[2]]
9 |
10 |
11 | def cross(v1, v2):
12 | v1x = v1[0]
13 | v1y = v1[1]
14 | v1z = v1[2]
15 | v2x = v2[0]
16 | v2y = v2[1]
17 | v2z = v2[2]
18 | return [
19 | (v1y * v2z) - (v1z * v2y),
20 | (v1z * v2x) - (v1x * v2z),
21 | (v1x * v2y) - (v1y * v2x),
22 | ]
23 |
24 |
25 | def length(vec3):
26 | return math.sqrt(vec3[0] * vec3[0] + vec3[1] * vec3[1] + vec3[2] * vec3[2])
27 |
28 |
29 | def normalize(vec3):
30 | l = length(vec3)
31 | return [vec3[0] / l, vec3[1] / l, vec3[2] / l]
32 |
33 |
34 | def look_at(eye, at, up):
35 | """Constructs look-at matrix for right handed coordinate system."""
36 | view = normalize(sub(at, eye))
37 | right = normalize(np.cross(up, view))
38 |
39 | up = np.cross(view, right)
40 |
41 | view = (ctypes.c_float * 16)(
42 | *[
43 | right[0],
44 | up[0],
45 | view[0],
46 | 0.0,
47 | right[1],
48 | up[1],
49 | view[1],
50 | 0.0,
51 | right[2],
52 | up[2],
53 | view[2],
54 | 0.0,
55 | -np.dot(right, eye),
56 | -np.dot(up, eye),
57 | -np.dot(view, eye),
58 | 1.0,
59 | ]
60 | )
61 |
62 | return view
63 |
64 |
65 | def proj(fov_y, aspect, near, far):
66 | height = 1.0 / math.tan(math.radians(fov_y) * 0.5)
67 | width = height * 1.0 / aspect
68 | diff = far - near
69 | aa = far / diff
70 | bb = near * aa
71 |
72 | return (ctypes.c_float * 16)(
73 | *[
74 | width,
75 | 0.0,
76 | 0.0,
77 | 0.0,
78 | 0.0,
79 | height,
80 | 0.0,
81 | 0.0,
82 | 0.0,
83 | 0.0,
84 | aa,
85 | 1.0,
86 | 0.0,
87 | 0.0,
88 | -bb,
89 | 0.0,
90 | ]
91 | )
92 |
93 |
94 | def ortho(left, right, bottom, top, near, far):
95 | aa = 2.0 / (right - left)
96 | bb = 2.0 / (top - bottom)
97 | cc = 1.0 / (far - near)
98 | dd = (left + right) / (left - right)
99 | ee = (top + bottom) / (bottom - top)
100 | ff = near / (near - far)
101 |
102 | return (ctypes.c_float * 16)(
103 | *[aa, 0.0, 0.0, 0.0, 0.0, bb, 0.0, 0.0, 0.0, 0.0, cc, 0.0, dd, ee, ff, 1.0]
104 | )
105 |
106 |
107 | def rotate_xy(rot_x, rot_y):
108 | sx, cx = np.sin(rot_x), np.cos(rot_x)
109 | sy, cy = np.sin(rot_y), np.cos(rot_y)
110 |
111 | return np.array(
112 | (
113 | (cy, 0.0, sy, 0.0),
114 | (sx * sy, cx, -sx * cy, 0.0),
115 | (-cx * sy, sx, cx * cy, 0.0),
116 | (0.0, 0.0, 0.0, 1.0),
117 | ),
118 | dtype=np.float32,
119 | )
120 |
--------------------------------------------------------------------------------
/media/screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fbertola/Natrix/403fed55cab582563615c71ef11b40b11cdb7777/media/screenshot.png
--------------------------------------------------------------------------------
/natrix/__init__.py:
--------------------------------------------------------------------------------
1 | from .core import *
2 |
--------------------------------------------------------------------------------
/natrix/core/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fbertola/Natrix/403fed55cab582563615c71ef11b40b11cdb7777/natrix/core/__init__.py
--------------------------------------------------------------------------------
/natrix/core/common/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fbertola/Natrix/403fed55cab582563615c71ef11b40b11cdb7777/natrix/core/common/__init__.py
--------------------------------------------------------------------------------
/natrix/core/common/constants.py:
--------------------------------------------------------------------------------
1 | from enum import IntEnum
2 |
3 | from decouple import config
4 |
5 |
6 | class TemplateConstants(IntEnum):
7 | NUM_THREADS = config("NATRIX__NUM_THREADS", cast=int, default=16)
8 | VELOCITY_IN = 1
9 | VELOCITY_OUT = 2
10 | PRESSURE_IN = 3
11 | PRESSURE_OUT = 4
12 | VORTICITY = 5
13 | DIVERGENCE = 6
14 | OBSTACLES = 7
15 | GENERIC = 8
16 | PARTICLES_IN = 9
17 | PARTICLES_OUT = 10
18 |
--------------------------------------------------------------------------------
/natrix/core/fluid_simulator.py:
--------------------------------------------------------------------------------
1 | from ctypes import c_float
2 | from math import ceil
3 | from pathlib import Path
4 |
5 | from pybgfx import bgfx
6 | from pybgfx.utils import as_void_ptr
7 | from pybgfx.utils.shaders_utils import ShaderType, load_shader
8 |
9 | from natrix.core.common.constants import TemplateConstants
10 | from natrix.core.utils.shaders_utils import create_buffer
11 |
12 | root_path = Path(__file__).parent / "shaders" / "originals"
13 |
14 |
15 | class FluidSimulator:
16 | VELOCITY_READ = 0
17 | VELOCITY_WRITE = 1
18 |
19 | PRESSURE_READ = 0
20 | PRESSURE_WRITE = 1
21 |
22 | _num_cells = 0
23 | _num_groups_x = 0
24 | _num_groups_y = 0
25 | _width = 512
26 | _height = 512
27 |
28 | _speed = 500.0
29 | _iterations = 50
30 | _dissipation = 1.0
31 | _vorticity = 0.0
32 | _viscosity = 0.1
33 |
34 | has_borders = True
35 | simulate = True
36 |
37 | def __init__(self, width: int, height: int, vertex_layout: bgfx.VertexLayout):
38 | self._width = width
39 | self._height = height
40 |
41 | self.vertex_layout = vertex_layout
42 |
43 | self._create_uniforms()
44 |
45 | self._load_compute_kernels()
46 | self._set_size(width, height)
47 | self._create_buffers()
48 | self._init_compute_kernels()
49 |
50 | @property
51 | def width(self):
52 | return self._width
53 |
54 | @property
55 | def height(self):
56 | return self._height
57 |
58 | @property
59 | def speed(self):
60 | return self._speed
61 |
62 | @speed.setter
63 | def speed(self, value):
64 | if value > 0:
65 | self._speed = value
66 | else:
67 | raise ValueError("'Speed' should be greater than zero")
68 |
69 | @property
70 | def iterations(self):
71 | return self._iterations
72 |
73 | @iterations.setter
74 | def iterations(self, value):
75 | if value > 0:
76 | self._iterations = value
77 | else:
78 | raise ValueError("'Iterations' should be grater than zero")
79 |
80 | @property
81 | def dissipation(self):
82 | return self._dissipation
83 |
84 | @dissipation.setter
85 | def dissipation(self, value):
86 | if value > 0:
87 | self._dissipation = value
88 | else:
89 | raise ValueError("'Dissipation' should be grater than zero")
90 |
91 | @property
92 | def vorticity(self):
93 | return self._vorticity
94 |
95 | @vorticity.setter
96 | def vorticity(self, value):
97 | if value >= 0:
98 | self._vorticity = value
99 | else:
100 | raise ValueError("'Vorticity' should be grater or equal than zero")
101 |
102 | @property
103 | def viscosity(self):
104 | return self._viscosity
105 |
106 | @viscosity.setter
107 | def viscosity(self, value):
108 | if value >= 0.0:
109 | self._viscosity = value
110 | else:
111 | raise ValueError("'Viscosity' should be greater or equal than zero")
112 |
113 | def get_velocity_buffer(self):
114 | return self._velocity_buffer[self.VELOCITY_READ]
115 |
116 | def add_velocity(self, position: tuple, velocity: tuple, radius: float):
117 | if self.simulate:
118 | self._init_compute_kernels()
119 | bgfx.setUniform(
120 | self.position_uniform,
121 | as_void_ptr((c_float * 2)(position[0], position[1])),
122 | )
123 | bgfx.setUniform(
124 | self.value_uniform, as_void_ptr((c_float * 2)(velocity[0], velocity[1]))
125 | )
126 | bgfx.setUniform(self.radius_uniform, as_void_ptr((c_float * 1)(radius)))
127 |
128 | bgfx.dispatch(
129 | 0, self._add_velocity_kernel, self._num_groups_x, self._num_groups_y, 1
130 | )
131 | self._flip_velocity_buffer()
132 |
133 | # position in normalised local space
134 | # radius in world space
135 | def add_circle_obstacle(self, position: tuple, radius: float, static=False):
136 | if self.simulate:
137 | self._init_compute_kernels()
138 | bgfx.setUniform(
139 | self.position_uniform,
140 | as_void_ptr((c_float * 2)(position[0], position[1])),
141 | )
142 | bgfx.setUniform(self.radius_uniform, as_void_ptr((c_float * 1)(radius)))
143 | bgfx.setUniform(
144 | self.static_uniform, as_void_ptr((c_float * 1)(1.0 if static else 0.0))
145 | )
146 |
147 | bgfx.dispatch(
148 | 0,
149 | self._add_circle_obstacle_kernel,
150 | self._num_groups_x,
151 | self._num_groups_y,
152 | 1,
153 | )
154 |
155 | # points in normalised local space
156 | def add_triangle_obstacle(self, p1: tuple, p2: tuple, p3: tuple, static=False):
157 | if self.simulate:
158 | self._init_compute_kernels()
159 | bgfx.setUniform(self.p1_uniform, as_void_ptr((c_float * 2)(p1[0], p1[1])))
160 | bgfx.setUniform(self.p2_uniform, as_void_ptr((c_float * 2)(p2[0], p2[1])))
161 | bgfx.setUniform(self.p3_uniform, as_void_ptr((c_float * 2)(p3[0], p3[1])))
162 | bgfx.setUniform(
163 | self.static_uniform, as_void_ptr((c_float * 1)(1.0 if static else 0.0))
164 | )
165 |
166 | bgfx.dispatch(
167 | 0,
168 | self._add_triangle_obstacle_kernel,
169 | self._num_groups_x,
170 | self._num_groups_y,
171 | 1,
172 | )
173 |
174 | def update(self, time_delta: float):
175 | if self.simulate:
176 |
177 | self._init_compute_kernels()
178 | self._update_params(time_delta)
179 |
180 | # Init boundaries
181 | if self.has_borders:
182 | bgfx.dispatch(
183 | 0,
184 | self._init_boundaries_kernel,
185 | self._num_groups_x,
186 | self._num_groups_y,
187 | 1,
188 | )
189 |
190 | # Advect
191 | bgfx.dispatch(
192 | 0,
193 | self._advect_velocity_kernel,
194 | self._num_groups_x,
195 | self._num_groups_y,
196 | 1,
197 | )
198 | self._flip_velocity_buffer()
199 |
200 | # Vorticity confinement 1 - Calculate vorticity
201 | bgfx.dispatch(
202 | 0,
203 | self._calc_vorticity_kernel,
204 | self._num_groups_x,
205 | self._num_groups_y,
206 | 1,
207 | )
208 |
209 | # Vorticity confinement 2 - Apply vorticity force
210 | bgfx.dispatch(
211 | 0,
212 | self._apply_vorticity_kernel,
213 | self._num_groups_x,
214 | self._num_groups_y,
215 | 1,
216 | )
217 | self._flip_velocity_buffer()
218 |
219 | # Viscosity
220 | if self.viscosity > 0.0:
221 | bgfx.dispatch(
222 | 0,
223 | self._viscosity_kernel,
224 | self._num_groups_x,
225 | self._num_groups_y,
226 | 1,
227 | )
228 | self._flip_velocity_buffer()
229 |
230 | # Divergence
231 | bgfx.dispatch(
232 | 0, self._divergence_kernel, self._num_groups_x, self._num_groups_y, 1
233 | )
234 |
235 | # Clear pressure
236 | bgfx.setBuffer(
237 | TemplateConstants.GENERIC.value,
238 | self._pressure_buffer[self.PRESSURE_READ],
239 | bgfx.Access.ReadWrite,
240 | )
241 | bgfx.dispatch(
242 | 0, self._clear_buffer_kernel, self._num_groups_x, self._num_groups_y, 1
243 | )
244 | bgfx.setBuffer(
245 | TemplateConstants.PRESSURE_IN.value,
246 | self._pressure_buffer[self.PRESSURE_READ],
247 | bgfx.Access.Read,
248 | )
249 |
250 | # Poisson
251 | for _ in range(self.iterations):
252 | bgfx.dispatch(
253 | 0, self._poisson_kernel, self._num_groups_x, self._num_groups_y, 1
254 | )
255 | self._flip_pressure_buffer()
256 |
257 | # Subtract gradient
258 | bgfx.dispatch(
259 | 0,
260 | self._subtract_gradient_kernel,
261 | self._num_groups_x,
262 | self._num_groups_y,
263 | 1,
264 | )
265 | self._flip_velocity_buffer()
266 |
267 | # Clear obstacles
268 | bgfx.setBuffer(
269 | TemplateConstants.GENERIC.value,
270 | self._obstacles_buffer,
271 | bgfx.Access.ReadWrite,
272 | )
273 | bgfx.dispatch(
274 | 0, self._clear_buffer_kernel, self._num_groups_x, self._num_groups_y, 1
275 | )
276 | bgfx.setBuffer(
277 | TemplateConstants.OBSTACLES.value,
278 | self._obstacles_buffer,
279 | bgfx.Access.ReadWrite,
280 | )
281 |
282 | def _set_size(self, width: int, height: int):
283 | group_size_x = TemplateConstants.NUM_THREADS.value
284 | group_size_y = TemplateConstants.NUM_THREADS.value
285 |
286 | self._width = width
287 | self._height = height
288 | self._num_cells = width * height
289 | self._num_groups_x = int(ceil(float(width) / float(group_size_x)))
290 | self._num_groups_y = int(ceil(float(height) / float(group_size_y)))
291 |
292 | def _create_uniforms(self):
293 | self.size_uniform = bgfx.createUniform("_Size", bgfx.UniformType.Vec4)
294 | self.position_uniform = bgfx.createUniform("_Position", bgfx.UniformType.Vec4)
295 | self.radius_uniform = bgfx.createUniform("_Radius", bgfx.UniformType.Vec4)
296 | self.value_uniform = bgfx.createUniform("_Value", bgfx.UniformType.Vec4)
297 | self.static_uniform = bgfx.createUniform("_Static", bgfx.UniformType.Vec4)
298 | self.p1_uniform = bgfx.createUniform("_P1", bgfx.UniformType.Vec4)
299 | self.p2_uniform = bgfx.createUniform("_P2", bgfx.UniformType.Vec4)
300 | self.p3_uniform = bgfx.createUniform("_P3", bgfx.UniformType.Vec4)
301 | self.elapsed_time_uniform = bgfx.createUniform(
302 | "_ElapsedTime", bgfx.UniformType.Vec4
303 | )
304 | self.speed_uniform = bgfx.createUniform("_Speed", bgfx.UniformType.Vec4)
305 | self.dissipation_uniform = bgfx.createUniform(
306 | "_Dissipation", bgfx.UniformType.Vec4
307 | )
308 | self.velocity_uniform = bgfx.createUniform("_Velocity", bgfx.UniformType.Vec4)
309 | self.vorticity_scale_uniform = bgfx.createUniform(
310 | "_VorticityScale", bgfx.UniformType.Vec4
311 | )
312 | self.alpha_uniform = bgfx.createUniform("_Alpha", bgfx.UniformType.Vec4)
313 | self.rbeta_uniform = bgfx.createUniform("_rBeta", bgfx.UniformType.Vec4)
314 |
315 | def _update_params(self, time_delta: float):
316 | bgfx.setUniform(
317 | self.elapsed_time_uniform, as_void_ptr((c_float * 1)(time_delta))
318 | )
319 | bgfx.setUniform(self.speed_uniform, as_void_ptr((c_float * 1)(self.speed)))
320 | bgfx.setUniform(
321 | self.dissipation_uniform, as_void_ptr((c_float * 1)(self.dissipation))
322 | )
323 | bgfx.setUniform(
324 | self.vorticity_scale_uniform, as_void_ptr((c_float * 1)(self.vorticity))
325 | )
326 |
327 | if self._viscosity > 0.0:
328 | centre_factor = 1.0 / self.viscosity
329 | stencil_factor = 1.0 / (4.0 + centre_factor)
330 |
331 | bgfx.setUniform(
332 | self.alpha_uniform, as_void_ptr((c_float * 1)(centre_factor))
333 | )
334 | bgfx.setUniform(
335 | self.rbeta_uniform, as_void_ptr((c_float * 1)(stencil_factor))
336 | )
337 |
338 | def _init_compute_kernels(self):
339 | bgfx.setUniform(
340 | self.size_uniform, as_void_ptr((c_float * 2)(self._width, self._height))
341 | )
342 |
343 | bgfx.setBuffer(1, self._velocity_buffer[self.VELOCITY_READ], bgfx.Access.Read)
344 | bgfx.setBuffer(
345 | 2, self._velocity_buffer[self.VELOCITY_WRITE], bgfx.Access.Write
346 | )
347 |
348 | bgfx.setBuffer(3, self._pressure_buffer[self.PRESSURE_READ], bgfx.Access.Read)
349 | bgfx.setBuffer(
350 | 4, self._pressure_buffer[self.PRESSURE_WRITE], bgfx.Access.Write
351 | )
352 |
353 | bgfx.setBuffer(5, self._divergence_buffer, bgfx.Access.ReadWrite)
354 | bgfx.setBuffer(6, self._vorticity_buffer, bgfx.Access.ReadWrite)
355 | bgfx.setBuffer(7, self._obstacles_buffer, bgfx.Access.ReadWrite)
356 |
357 | def _create_buffers(self):
358 | self._velocity_buffer = [
359 | create_buffer(self._num_cells, 2, self.vertex_layout),
360 | create_buffer(self._num_cells, 2, self.vertex_layout),
361 | ]
362 | self._pressure_buffer = [
363 | create_buffer(self._num_cells, 1, self.vertex_layout),
364 | create_buffer(self._num_cells, 1, self.vertex_layout),
365 | ]
366 | self._divergence_buffer = create_buffer(self._num_cells, 1, self.vertex_layout)
367 | self._vorticity_buffer = create_buffer(self._num_cells, 1, self.vertex_layout)
368 | self._obstacles_buffer = create_buffer(self._num_cells, 2, self.vertex_layout)
369 |
370 | def _load_compute_kernels(self):
371 | self._add_velocity_kernel = bgfx.createProgram(
372 | load_shader(
373 | "shader.AddVelocity.comp", ShaderType.COMPUTE, root_path=root_path
374 | ),
375 | True,
376 | )
377 | self._init_boundaries_kernel = bgfx.createProgram(
378 | load_shader(
379 | "shader.InitBoundaries.comp", ShaderType.COMPUTE, root_path=root_path
380 | ),
381 | True,
382 | )
383 | self._advect_velocity_kernel = bgfx.createProgram(
384 | load_shader(
385 | "shader.AdvectVelocity.comp", ShaderType.COMPUTE, root_path=root_path
386 | ),
387 | True,
388 | )
389 | self._divergence_kernel = bgfx.createProgram(
390 | load_shader(
391 | "shader.Divergence.comp", ShaderType.COMPUTE, root_path=root_path
392 | ),
393 | True,
394 | )
395 | self._poisson_kernel = bgfx.createProgram(
396 | load_shader("shader.Poisson.comp", ShaderType.COMPUTE, root_path=root_path),
397 | True,
398 | )
399 | self._subtract_gradient_kernel = bgfx.createProgram(
400 | load_shader(
401 | "shader.SubtractGradient.comp", ShaderType.COMPUTE, root_path=root_path
402 | ),
403 | True,
404 | )
405 | self._calc_vorticity_kernel = bgfx.createProgram(
406 | load_shader(
407 | "shader.CalcVorticity.comp", ShaderType.COMPUTE, root_path=root_path
408 | ),
409 | True,
410 | )
411 | self._apply_vorticity_kernel = bgfx.createProgram(
412 | load_shader(
413 | "shader.ApplyVorticity.comp", ShaderType.COMPUTE, root_path=root_path
414 | ),
415 | True,
416 | )
417 | self._add_circle_obstacle_kernel = bgfx.createProgram(
418 | load_shader(
419 | "shader.AddCircleObstacle.comp", ShaderType.COMPUTE, root_path=root_path
420 | ),
421 | True,
422 | )
423 | self._add_triangle_obstacle_kernel = bgfx.createProgram(
424 | load_shader(
425 | "shader.AddTriangleObstacle.comp",
426 | ShaderType.COMPUTE,
427 | root_path=root_path,
428 | ),
429 | True,
430 | )
431 | self._clear_buffer_kernel = bgfx.createProgram(
432 | load_shader(
433 | "shader.ClearBuffer.comp", ShaderType.COMPUTE, root_path=root_path
434 | ),
435 | True,
436 | )
437 | self._viscosity_kernel = bgfx.createProgram(
438 | load_shader(
439 | "shader.Viscosity.comp", ShaderType.COMPUTE, root_path=root_path
440 | ),
441 | True,
442 | )
443 |
444 | def _flip_velocity_buffer(self):
445 | tmp = self.VELOCITY_READ
446 | self.VELOCITY_READ = self.VELOCITY_WRITE
447 | self.VELOCITY_WRITE = tmp
448 |
449 | bgfx.setBuffer(
450 | TemplateConstants.VELOCITY_IN.value,
451 | self._velocity_buffer[self.VELOCITY_READ],
452 | bgfx.Access.Read,
453 | )
454 | bgfx.setBuffer(
455 | TemplateConstants.VELOCITY_OUT.value,
456 | self._velocity_buffer[self.VELOCITY_WRITE],
457 | bgfx.Access.Write,
458 | )
459 |
460 | def _flip_pressure_buffer(self):
461 | tmp = self.PRESSURE_READ
462 | self.PRESSURE_READ = self.PRESSURE_WRITE
463 | self.PRESSURE_WRITE = tmp
464 |
465 | bgfx.setBuffer(
466 | TemplateConstants.PRESSURE_IN.value,
467 | self._pressure_buffer[self.PRESSURE_READ],
468 | bgfx.Access.Read,
469 | )
470 | bgfx.setBuffer(
471 | TemplateConstants.PRESSURE_OUT.value,
472 | self._pressure_buffer[self.PRESSURE_WRITE],
473 | bgfx.Access.Write,
474 | )
475 |
476 | def destroy(self):
477 | # Destroy uniforms
478 | bgfx.destroy(self.size_uniform)
479 | bgfx.destroy(self.position_uniform)
480 | bgfx.destroy(self.radius_uniform)
481 | bgfx.destroy(self.value_uniform)
482 | bgfx.destroy(self.static_uniform)
483 | bgfx.destroy(self.velocity_uniform)
484 | bgfx.destroy(self.p1_uniform)
485 | bgfx.destroy(self.p2_uniform)
486 | bgfx.destroy(self.p3_uniform)
487 | bgfx.destroy(self.elapsed_time_uniform)
488 | bgfx.destroy(self.speed_uniform)
489 | bgfx.destroy(self.dissipation_uniform)
490 | bgfx.destroy(self.vorticity_scale_uniform)
491 | bgfx.destroy(self.alpha_uniform)
492 | bgfx.destroy(self.rbeta_uniform)
493 |
494 | # Destroy buffers
495 | bgfx.destroy(self._velocity_buffer[0])
496 | bgfx.destroy(self._velocity_buffer[1])
497 | bgfx.destroy(self._pressure_buffer[0])
498 | bgfx.destroy(self._pressure_buffer[1])
499 | bgfx.destroy(self._divergence_buffer)
500 | bgfx.destroy(self._vorticity_buffer)
501 | bgfx.destroy(self._obstacles_buffer)
502 |
503 | # Destroy compute shaders
504 | bgfx.destroy(self._add_velocity_kernel)
505 | bgfx.destroy(self._init_boundaries_kernel)
506 | bgfx.destroy(self._advect_velocity_kernel)
507 | bgfx.destroy(self._divergence_kernel)
508 | bgfx.destroy(self._poisson_kernel)
509 | bgfx.destroy(self._subtract_gradient_kernel)
510 | bgfx.destroy(self._calc_vorticity_kernel)
511 | bgfx.destroy(self._apply_vorticity_kernel)
512 | bgfx.destroy(self._add_circle_obstacle_kernel)
513 | bgfx.destroy(self._add_triangle_obstacle_kernel)
514 | bgfx.destroy(self._clear_buffer_kernel)
515 | bgfx.destroy(self._viscosity_kernel)
516 |
--------------------------------------------------------------------------------
/natrix/core/shaders/originals/common.sh:
--------------------------------------------------------------------------------
1 | #ifndef COMMON_SH_HEADER_GUARD
2 | #define COMMON_SH_HEADER_GUARD
3 |
4 | #if BGFX_SHADER_LANGUAGE_SPIRV
5 | #undef BGFX_SHADER_LANGUAGE_HLSL
6 | #define BGFX_SHADER_LANGUAGE_HLSL 5
7 | #endif
8 |
9 | uvec4 GetNeighbours(ivec2 pos, ivec2 size)
10 | {
11 | uvec4 result;
12 | int maxX = size.x - 1;
13 | int maxY = size.y - 1;
14 | result.x = uint(pos.y) * _Size.x + uint(clamp(pos.x - 1, 0, maxX));
15 | result.y = uint(pos.y) * _Size.x + uint(clamp(pos.x + 1, 0, maxX));
16 | result.z = uint(clamp(pos.y - 1, 0, maxY) * size.x + pos.x);
17 | result.w = uint(clamp(pos.y + 1, 0, maxY) * size.x + pos.x);
18 | return result;
19 | }
20 |
21 | #endif // COMMON_SH_HEADER_GUARD
--------------------------------------------------------------------------------
/natrix/core/shaders/originals/constants.sh:
--------------------------------------------------------------------------------
1 | #ifndef CONSTANTS_SH_HEADER_GUARD
2 | #define CONSTANTS_SH_HEADER_GUARD
3 |
4 | // workgroup size of the culling compute shader
5 | // D3D compute shaders only allow up to 1024 threads per workgroup
6 | // GL_MAX_COMPUTE_WORK_GROUP_INVOCATIONS also only guarantees 1024
7 | #define GROUP_SIZE 16
8 |
9 | #define VELOCITY_IN 1
10 | #define VELOCITY_OUT 2
11 | #define PRESSURE_IN 3
12 | #define PRESSURE_OUT 4
13 | #define VORTICITY 5
14 | #define DIVERGENCE 6
15 | #define OBSTACLES 7
16 | #define GENERIC 8
17 | #define PARTICLES_IN 9
18 | #define PARTICLES_OUT 10
19 |
20 | #endif // CONSTANTS_SH_HEADER_GUARD
--------------------------------------------------------------------------------
/natrix/core/shaders/originals/shader.AddCircleObstacle.comp:
--------------------------------------------------------------------------------
1 |
2 | #include "bgfx_compute.sh"
3 | #include "constants.sh"
4 |
5 | uniform vec2 _Size;
6 |
7 | BUFFER_WR(_Obstacles, vec2, 7);
8 |
9 | uniform float _Radius;
10 |
11 | uniform vec2 _Position;
12 |
13 | uniform float _Static;
14 |
15 | #include "common.sh"
16 |
17 | NUM_THREADS(GROUP_SIZE, GROUP_SIZE, 1)
18 | void main()
19 | {
20 | if (gl_GlobalInvocationID.x >= _Size.x || gl_GlobalInvocationID.y >= _Size.y)
21 | {
22 | return;
23 | }
24 | uint pos = gl_GlobalInvocationID.y * _Size.x + gl_GlobalInvocationID.x;
25 | vec2 splat_pos = _Position * vec2(_Size);
26 | if (distance(splat_pos, vec2(gl_GlobalInvocationID)) <= _Radius)
27 | {
28 | if (_Static > 0)
29 | {
30 | _Obstacles[pos] = vec2(1.0f, 0);
31 | }
32 | else
33 | {
34 | _Obstacles[pos] = vec2(1.0f, 0);
35 | }
36 | }
37 | }
38 |
39 |
--------------------------------------------------------------------------------
/natrix/core/shaders/originals/shader.AddTriangleObstacle.comp:
--------------------------------------------------------------------------------
1 |
2 | #include "bgfx_compute.sh"
3 | #include "constants.sh"
4 |
5 | uniform vec2 _Size;
6 |
7 | BUFFER_WR(_Obstacles, vec2, 7);
8 |
9 | uniform float _Static;
10 |
11 | uniform vec2 _P1;
12 |
13 | uniform vec2 _P2;
14 |
15 | uniform vec2 _P3;
16 |
17 | #include "common.sh"
18 |
19 | float Sign(vec2 p1, vec2 p2, vec2 p3)
20 | {
21 | return ((p1.x - p3.x) * (p2.y - p3.y)) - ((p2.x - p3.x) * (p1.y - p3.y));
22 | }
23 |
24 | bool IsPointInTriangle(vec2 pt, vec2 v1, vec2 v2, vec2 v3)
25 | {
26 | bool b1 = Sign(pt, v1, v2) < 0.0f;
27 | bool b2 = Sign(pt, v2, v3) < 0.0f;
28 | bool b3 = Sign(pt, v3, v1) < 0.0f;
29 | return (b1 == b2) && (b2 == b3);
30 | }
31 |
32 | NUM_THREADS(GROUP_SIZE, GROUP_SIZE, 1)
33 | void main()
34 | {
35 | if (gl_GlobalInvocationID.x >= _Size.x || gl_GlobalInvocationID.y >= _Size.y)
36 | {
37 | return;
38 | }
39 | vec2 pt = vec2(gl_GlobalInvocationID.x, gl_GlobalInvocationID.y) / vec2(_Size);
40 | if (IsPointInTriangle(pt, _P1, _P2, _P3))
41 | {
42 | uint pos = gl_GlobalInvocationID.y * _Size.x + gl_GlobalInvocationID.x;
43 | if (_Static > 0)
44 | {
45 | _Obstacles[pos] = vec2(0, 1.0f);
46 | }
47 | else
48 | {
49 | _Obstacles[pos] = vec2(1.0f, 0);
50 | }
51 | }
52 | }
53 |
54 |
--------------------------------------------------------------------------------
/natrix/core/shaders/originals/shader.AddVelocity.comp:
--------------------------------------------------------------------------------
1 |
2 | #include "bgfx_compute.sh"
3 | #include "constants.sh"
4 |
5 | uniform vec2 _Size;
6 |
7 | BUFFER_RO(_VelocityIn, vec2, 1);
8 |
9 | BUFFER_WR(_VelocityOut, vec2, 2);
10 |
11 | uniform float _Radius;
12 |
13 | uniform vec2 _Position;
14 |
15 | uniform vec2 _Value;
16 |
17 | #include "common.sh"
18 |
19 | NUM_THREADS(GROUP_SIZE, GROUP_SIZE, 1)
20 | void main()
21 | {
22 | if (gl_GlobalInvocationID.x >= _Size.x || gl_GlobalInvocationID.y >= _Size.y)
23 | {
24 | return;
25 | }
26 | uint pos = gl_GlobalInvocationID.y * _Size.x + gl_GlobalInvocationID.x;
27 | vec2 splat_pos = _Position * vec2(_Size);
28 | vec2 val = _VelocityIn[pos];
29 | vec2 result = val;
30 | float len = distance(splat_pos, vec2(gl_GlobalInvocationID.xy));
31 | if (len <= _Radius)
32 | {
33 | result = val + _Value * (_Radius - len) / _Radius;
34 | }
35 | _VelocityOut[pos] = clamp(result, vec2(-1.0, -1.0), vec2(1.0, 1.0));
36 | }
37 |
38 |
--------------------------------------------------------------------------------
/natrix/core/shaders/originals/shader.AdvectVelocity.comp:
--------------------------------------------------------------------------------
1 |
2 | #include "bgfx_compute.sh"
3 | #include "constants.sh"
4 |
5 | uniform vec2 _Size;
6 |
7 | BUFFER_RO(_VelocityIn, vec2, 1);
8 |
9 | BUFFER_WR(_VelocityOut, vec2, 2);
10 |
11 | BUFFER_RO(_Obstacles, vec2, 7);
12 |
13 | uniform float _ElapsedTime;
14 |
15 | uniform float _Speed;
16 |
17 | uniform float _Dissipation;
18 |
19 | NUM_THREADS(GROUP_SIZE, GROUP_SIZE, 1)
20 | void main()
21 | {
22 | if (gl_GlobalInvocationID.x >= _Size.x || gl_GlobalInvocationID.y >= _Size.y)
23 | {
24 | return;
25 | }
26 |
27 | uint pos = gl_GlobalInvocationID.y * _Size.x + gl_GlobalInvocationID.x;
28 | vec2 obstacle = _Obstacles[pos];
29 |
30 | if (obstacle.x > 0.0f || obstacle.y > 0.0f)
31 | {
32 | _VelocityOut[pos] = vec2(0, 0);
33 | }
34 | else
35 | {
36 | vec2 vel = _VelocityIn[pos];
37 | vec2 final_pos = vec2(float(gl_GlobalInvocationID.x) - vel.x * _ElapsedTime * _Speed, float(gl_GlobalInvocationID.y) - vel.y * _ElapsedTime * _Speed);
38 | ivec2 zero = ivec2(0, 0);
39 | ivec2 size_bounds = ivec2(_Size.x - 1u, _Size.y - 1u);
40 | ivec2 top_right = ivec2(clamp(ceil(final_pos), vec2(zero), vec2(size_bounds)));
41 | ivec2 bottom_left = ivec2(clamp(floor(final_pos), vec2(zero), vec2(size_bounds)));
42 | vec2 delta = final_pos - vec2(bottom_left);
43 | vec2 lt = _VelocityIn[uint(top_right.y) * _Size.x + uint(bottom_left.x)];
44 | vec2 rt = _VelocityIn[uint(top_right.y) * _Size.x + uint(top_right.x)];
45 | vec2 lb = _VelocityIn[uint(bottom_left.y) * _Size.x + uint(bottom_left.x)];
46 | vec2 rb = _VelocityIn[uint(bottom_left.y) * _Size.x + uint(top_right.x)];
47 | vec2 h1 = mix(lt, rt, vec2(delta.x));
48 | vec2 h2 = mix(lb, rb, vec2(delta.x));
49 | _VelocityOut[pos] = clamp(mix(h2, h1, vec2(delta.y)) * _Dissipation, vec2(-1.0, -1.0), vec2(1.0, 1.0));
50 | }
51 | }
52 |
53 |
--------------------------------------------------------------------------------
/natrix/core/shaders/originals/shader.ApplyVorticity.comp:
--------------------------------------------------------------------------------
1 |
2 | #include "bgfx_compute.sh"
3 | #include "constants.sh"
4 |
5 | uniform vec2 _Size;
6 |
7 | BUFFER_RO(_VelocityIn, vec2, 1);
8 |
9 | BUFFER_WR(_VelocityOut, vec2, 2);
10 |
11 | BUFFER_RO(_Vorticity, float, 5);
12 |
13 | uniform float _ElapsedTime;
14 |
15 | uniform float _VorticityScale;
16 |
17 | #include "common.sh"
18 |
19 | NUM_THREADS(GROUP_SIZE, GROUP_SIZE, 1)
20 | void main()
21 | {
22 | if (gl_GlobalInvocationID.x >= _Size.x || gl_GlobalInvocationID.y >= _Size.y)
23 | {
24 | return;
25 | }
26 | uint pos = gl_GlobalInvocationID.y * _Size.x + gl_GlobalInvocationID.x;
27 | uvec4 n = GetNeighbours(ivec2(gl_GlobalInvocationID.xy), ivec2(_Size));
28 | float vL = _Vorticity[n.x];
29 | float vR = _Vorticity[n.y];
30 | float vB = _Vorticity[n.z];
31 | float vT = _Vorticity[n.w];
32 | float vC = _Vorticity[pos];
33 | vec2 force = 0.5f * vec2(abs(vT) - abs(vB), abs(vR) - abs(vL));
34 | float EPSILON = 2.4414e-4f;
35 | float magSqr = max(EPSILON, dot(force, force));
36 | force = force * inversesqrt(magSqr);
37 | force *= _VorticityScale * vC * vec2(1, -1);
38 | vec2 final_force = force * _ElapsedTime;
39 | _VelocityOut[pos] = _VelocityIn[pos] + vec2(final_force.x, final_force.y);
40 | }
41 |
42 |
--------------------------------------------------------------------------------
/natrix/core/shaders/originals/shader.CalcVorticity.comp:
--------------------------------------------------------------------------------
1 |
2 | #include "bgfx_compute.sh"
3 | #include "constants.sh"
4 |
5 | uniform vec2 _Size;
6 |
7 | BUFFER_RO(_VelocityIn, vec2, 1);
8 |
9 | BUFFER_WR(_Vorticity, float, 5);
10 |
11 | #include "common.sh"
12 |
13 | NUM_THREADS(GROUP_SIZE, GROUP_SIZE, 1)
14 | void main()
15 | {
16 | if (gl_GlobalInvocationID.x >= _Size.x || gl_GlobalInvocationID.y >= _Size.y)
17 | {
18 | return;
19 | }
20 | uint pos = gl_GlobalInvocationID.y * _Size.x + gl_GlobalInvocationID.x;
21 | uvec4 n = GetNeighbours(ivec2(gl_GlobalInvocationID.xy), ivec2(_Size));
22 | vec2 vL = _VelocityIn[n.x];
23 | vec2 vR = _VelocityIn[n.y];
24 | vec2 vB = _VelocityIn[n.z];
25 | vec2 vT = _VelocityIn[n.w];
26 | _Vorticity[pos] = 0.5f * ((vR.y - vL.y) - (vT.x - vB.x));
27 | }
28 |
29 |
--------------------------------------------------------------------------------
/natrix/core/shaders/originals/shader.ClearBuffer.comp:
--------------------------------------------------------------------------------
1 |
2 | #include "bgfx_compute.sh"
3 | #include "constants.sh"
4 |
5 | uniform vec2 _Size;
6 |
7 | BUFFER_WR(_Buffer, vec2, 8);
8 |
9 | NUM_THREADS(GROUP_SIZE, GROUP_SIZE, 1)
10 | void main()
11 | {
12 | if (gl_GlobalInvocationID.x >= _Size.x || gl_GlobalInvocationID.y >= _Size.y)
13 | {
14 | return;
15 | }
16 | uint pos = gl_GlobalInvocationID.y * _Size.x + gl_GlobalInvocationID.x;
17 | _Buffer[pos] = 0.0f;
18 | }
19 |
20 |
--------------------------------------------------------------------------------
/natrix/core/shaders/originals/shader.Divergence.comp:
--------------------------------------------------------------------------------
1 |
2 | #include "bgfx_compute.sh"
3 | #include "constants.sh"
4 |
5 | uniform vec2 _Size;
6 |
7 | BUFFER_RO(_VelocityIn, vec2, 1);
8 |
9 | BUFFER_RO(_Obstacles, vec2, 7);
10 |
11 | BUFFER_WR(_Divergence, float, 6);
12 |
13 | #include "common.sh"
14 |
15 | NUM_THREADS(GROUP_SIZE, GROUP_SIZE, 1)
16 | void main()
17 | {
18 | if (gl_GlobalInvocationID.x >= _Size.x || gl_GlobalInvocationID.y >= _Size.y)
19 | {
20 | return;
21 | }
22 | uint pos = gl_GlobalInvocationID.y * _Size.x + gl_GlobalInvocationID.x;
23 | uvec4 n = GetNeighbours(ivec2(gl_GlobalInvocationID.xy), ivec2(_Size));
24 | float x1 = _VelocityIn[n.x].x;
25 | float x2 = _VelocityIn[n.y].x;
26 | float y1 = _VelocityIn[n.z].y;
27 | float y2 = _VelocityIn[n.w].y;
28 | vec2 obsL = _Obstacles[n.x];
29 | vec2 obsR = _Obstacles[n.y];
30 | vec2 obsB = _Obstacles[n.z];
31 | vec2 obsT = _Obstacles[n.w];
32 | if (obsL.x > 0.0f || obsL.y > 0.0f)
33 | x1 = 0.0f;
34 | if (obsR.x > 0.0f || obsR.y > 0.0f)
35 | x2 = 0.0f;
36 | if (obsB.x > 0.0f || obsB.y > 0.0f)
37 | y1 = 0.0f;
38 | if (obsT.x > 0.0f || obsT.y > 0.0f)
39 | y2 = 0.0f;
40 | _Divergence[pos] = 0.5f * ((x2 - x1) + (y2 - y1));
41 | }
42 |
43 |
--------------------------------------------------------------------------------
/natrix/core/shaders/originals/shader.InitBoundaries.comp:
--------------------------------------------------------------------------------
1 |
2 | #include "bgfx_compute.sh"
3 | #include "constants.sh"
4 |
5 | uniform vec2 _Size;
6 |
7 | BUFFER_WR(_VelocityIn, vec2, 1);
8 |
9 | #include "common.sh"
10 |
11 | NUM_THREADS(GROUP_SIZE, GROUP_SIZE, 1)
12 | void main()
13 | {
14 | if (gl_GlobalInvocationID.x >= _Size.x || gl_GlobalInvocationID.y >= _Size.y)
15 | {
16 | return;
17 | }
18 | uint pos = gl_GlobalInvocationID.y * _Size.x + gl_GlobalInvocationID.x;
19 | if (gl_GlobalInvocationID.x == 0u)
20 | {
21 | _VelocityIn[pos] = vec2(0.0, 0.0);
22 | }
23 | else if (gl_GlobalInvocationID.x == _Size.x - 1u)
24 | {
25 | _VelocityIn[pos] = vec2(0.0, 0.0);
26 | }
27 | else if (gl_GlobalInvocationID.y == 0u)
28 | {
29 | _VelocityIn[pos] = vec2(0.0, 0.0);
30 | }
31 | else if (gl_GlobalInvocationID.y == _Size.y - 1u)
32 | {
33 | _VelocityIn[pos] = vec2(0.0, 0.0);
34 | }
35 | }
36 |
37 |
--------------------------------------------------------------------------------
/natrix/core/shaders/originals/shader.Poisson.comp:
--------------------------------------------------------------------------------
1 |
2 | #include "bgfx_compute.sh"
3 | #include "constants.sh"
4 |
5 | uniform vec2 _Size;
6 |
7 | BUFFER_RO(_Obstacles, vec2, 7);
8 |
9 | BUFFER_RO(_Divergence, float, 6);
10 |
11 | BUFFER_RO(_PressureIn, vec2, 3);
12 |
13 | BUFFER_WR(_PressureOut, vec2, 4);
14 |
15 | #include "common.sh"
16 |
17 | NUM_THREADS(GROUP_SIZE, GROUP_SIZE, 1)
18 | void main()
19 | {
20 | if (gl_GlobalInvocationID.x >= _Size.x || gl_GlobalInvocationID.y >= _Size.y)
21 | {
22 | return;
23 | }
24 | uint pos = gl_GlobalInvocationID.y * _Size.x + gl_GlobalInvocationID.x;
25 | float rbeta = 0.25f;
26 | uvec4 n = GetNeighbours(ivec2(gl_GlobalInvocationID.xy), ivec2(_Size));
27 | float p = _PressureIn[pos];
28 | vec2 obsL = _Obstacles[n.x];
29 | vec2 obsR = _Obstacles[n.y];
30 | vec2 obsB = _Obstacles[n.z];
31 | vec2 obsT = _Obstacles[n.w];
32 | float x1 = (obsL.x > 0.0f || obsL.y > 0.0f) ? p : _PressureIn[n.x];
33 | float x2 = (obsR.x > 0.0f || obsR.y > 0.0f) ? p : _PressureIn[n.y];
34 | float y1 = (obsB.x > 0.0f || obsB.y > 0.0f) ? p : _PressureIn[n.z];
35 | float y2 = (obsT.x > 0.0f || obsT.y > 0.0f) ? p : _PressureIn[n.w];
36 | float b = _Divergence[pos];
37 | _PressureOut[pos] = (x1 + x2 + y1 + y2 - b) * rbeta;
38 | }
39 |
40 |
--------------------------------------------------------------------------------
/natrix/core/shaders/originals/shader.SubtractGradient.comp:
--------------------------------------------------------------------------------
1 |
2 | #include "bgfx_compute.sh"
3 | #include "constants.sh"
4 |
5 | uniform vec2 _Size;
6 |
7 | BUFFER_RO(_VelocityIn, vec2, 1);
8 |
9 | BUFFER_WR(_VelocityOut, vec2, 2);
10 |
11 | BUFFER_RO(_Obstacles, vec2, 7);
12 |
13 | BUFFER_RO(_PressureIn, vec2, 3);
14 |
15 | #include "common.sh"
16 |
17 | NUM_THREADS(GROUP_SIZE, GROUP_SIZE, 1)
18 | void main()
19 | {
20 | if (gl_GlobalInvocationID.x >= _Size.x || gl_GlobalInvocationID.y >= _Size.y)
21 | {
22 | return;
23 | }
24 | uvec4 n = GetNeighbours(ivec2(gl_GlobalInvocationID.xy), ivec2(_Size));
25 | uint pos = gl_GlobalInvocationID.y * _Size.x + gl_GlobalInvocationID.x;
26 | float x1 = _PressureIn[n.x];
27 | float x2 = _PressureIn[n.y];
28 | float y1 = _PressureIn[n.z];
29 | float y2 = _PressureIn[n.w];
30 | float p = _PressureIn[pos];
31 | vec2 obsL = _Obstacles[n.x];
32 | vec2 obsR = _Obstacles[n.y];
33 | vec2 obsB = _Obstacles[n.z];
34 | vec2 obsT = _Obstacles[n.w];
35 | if (obsL.x > 0.0f || obsL.y > 0.0f)
36 | x1 = p;
37 | if (obsR.x > 0.0f || obsR.y > 0.0f)
38 | x2 = p;
39 | if (obsB.x > 0.0f || obsB.y > 0.0f)
40 | y1 = p;
41 | if (obsT.x > 0.0f || obsT.y > 0.0f)
42 | y2 = p;
43 | vec2 velocity = _VelocityIn[pos];
44 | velocity.x -= 0.5f * (x2 - x1);
45 | velocity.y -= 0.5f * (y2 - y1);
46 | _VelocityOut[pos] = velocity;
47 | }
48 |
49 |
--------------------------------------------------------------------------------
/natrix/core/shaders/originals/shader.Viscosity.comp:
--------------------------------------------------------------------------------
1 |
2 | #include "bgfx_compute.sh"
3 | #include "constants.sh"
4 |
5 | uniform vec2 _Size;
6 |
7 | BUFFER_RO(_VelocityIn, vec2, 1);
8 |
9 | BUFFER_WR(_VelocityOut, vec2, 2);
10 |
11 | uniform float _Alpha;
12 |
13 | uniform float _rBeta;
14 |
15 | #include "common.sh"
16 |
17 | NUM_THREADS(GROUP_SIZE, GROUP_SIZE, 1)
18 | void main()
19 | {
20 | if (gl_GlobalInvocationID.x >= _Size.x || gl_GlobalInvocationID.y >= _Size.y)
21 | {
22 | return;
23 | }
24 | uint pos = gl_GlobalInvocationID.y * _Size.x + gl_GlobalInvocationID.x;
25 | uvec4 n = GetNeighbours(ivec2(gl_GlobalInvocationID.xy), ivec2(_Size));
26 | vec2 x1 = _VelocityIn[n.x];
27 | vec2 x2 = _VelocityIn[n.y];
28 | vec2 y1 = _VelocityIn[n.z];
29 | vec2 y2 = _VelocityIn[n.w];
30 | vec2 b = _VelocityIn[pos];
31 | _VelocityOut[pos] = (x1 + x2 + y1 + y2 + b * _Alpha) * _rBeta;
32 | }
33 |
34 |
--------------------------------------------------------------------------------
/natrix/core/utils/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fbertola/Natrix/403fed55cab582563615c71ef11b40b11cdb7777/natrix/core/utils/__init__.py
--------------------------------------------------------------------------------
/natrix/core/utils/bgfx_utils.py:
--------------------------------------------------------------------------------
1 | import platform
2 | from _ctypes import Structure
3 | from ctypes import c_float, c_uint32
4 |
5 |
6 | class PosVertex(Structure):
7 | _fields_ = [
8 | ("m_x", c_float),
9 | ("m_y", c_float),
10 | ("m_z", c_float),
11 | ("m_abgr", c_uint32),
12 | ("m_u", c_float),
13 | ("m_v", c_float),
14 | ]
15 |
16 |
17 | def screen_space_quad(
18 | texture_width: int,
19 | texture_height: int,
20 | origin_bottom_left=False,
21 | width=1.0,
22 | height=1.0,
23 | ):
24 | if platform.system() == "Windows":
25 | s_texel_half = 0.5
26 | else:
27 | s_texel_half = 0.0
28 |
29 | zz = 0.0
30 | minx = -width
31 | maxx = width
32 | miny = 0.0
33 | maxy = height * 2.0
34 | texel_half_w = s_texel_half / texture_width
35 | texel_half_h = s_texel_half / texture_height
36 | minu = -1.0 + texel_half_w
37 | maxu = 1.0 + texel_half_w
38 |
39 | minv = texel_half_h
40 | maxv = 2.0 + texel_half_h
41 |
42 | if origin_bottom_left:
43 | temp = minv
44 | minv = maxv
45 | maxv = temp
46 |
47 | minv -= 1.0
48 | maxv -= 1.0
49 |
50 | vertices = (PosVertex * 3)(
51 | PosVertex(minx, miny, zz, 0xFFFFFFFF, minu, minv),
52 | PosVertex(maxx, miny, zz, 0xFFFFFFFF, maxu, minv),
53 | PosVertex(maxx, maxy, zz, 0xFFFFFFFF, maxu, maxv),
54 | )
55 |
56 | return vertices
57 |
--------------------------------------------------------------------------------
/natrix/core/utils/shaders_utils.py:
--------------------------------------------------------------------------------
1 | from ctypes import sizeof, c_float
2 |
3 | from pybgfx import bgfx
4 | from pybgfx.constants import BGFX_BUFFER_COMPUTE_READ_WRITE
5 |
6 |
7 | def create_buffer(length: int, dimensions: int, vertex_layout: bgfx.VertexLayout):
8 | return bgfx.createDynamicVertexBuffer(
9 | sizeof(c_float) * length * dimensions,
10 | vertex_layout,
11 | BGFX_BUFFER_COMPUTE_READ_WRITE,
12 | )
13 |
--------------------------------------------------------------------------------
/noxfile.py:
--------------------------------------------------------------------------------
1 | import tempfile
2 |
3 | import nox
4 |
5 | locations = "demo", "natrix", "tests", "noxfile.py"
6 |
7 |
8 | def install_with_constraints(session, *args, **kwargs):
9 | with tempfile.NamedTemporaryFile() as requirements:
10 | session.run(
11 | "poetry",
12 | "export",
13 | "--dev",
14 | "--format=requirements.txt",
15 | "--without-hashes",
16 | f"--output={requirements.name}",
17 | external=True,
18 | )
19 | session.install(f"--constraint={requirements.name}", *args, **kwargs)
20 |
21 |
22 | @nox.session(python="3.7")
23 | def black(session):
24 | args = session.posargs or locations
25 | install_with_constraints(session, "black")
26 | session.run("black", *args)
27 |
28 |
29 | @nox.session(python="3.7")
30 | def lint(session):
31 | args = session.posargs or locations
32 | install_with_constraints(
33 | session,
34 | "flake8",
35 | "flake8-bandit",
36 | "flake8-black",
37 | "flake8-bugbear",
38 | "flake8-import-order",
39 | )
40 | session.run("flake8", *args)
41 |
42 |
43 | @nox.session(python="3.7")
44 | def safety(session):
45 | with tempfile.NamedTemporaryFile() as requirements:
46 | session.run(
47 | "poetry",
48 | "export",
49 | "--dev",
50 | "--format=requirements.txt",
51 | "--without-hashes",
52 | f"--output={requirements.name}",
53 | external=True,
54 | )
55 | install_with_constraints(session, "safety")
56 | session.run("safety", "check", f"--file={requirements.name}", "--full-report")
57 |
58 |
59 | @nox.session(python="3.7")
60 | def tests(session):
61 | args = session.posargs or ["--cov", "-m", "not e2e"]
62 | session.run("poetry", "install", "--no-dev", external=True)
63 | install_with_constraints(session, "pytest", "pytest-mock")
64 | session.run("pytest", *args)
65 |
--------------------------------------------------------------------------------
/poetry.lock:
--------------------------------------------------------------------------------
1 | [[package]]
2 | name = "appdirs"
3 | version = "1.4.4"
4 | description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
5 | category = "dev"
6 | optional = false
7 | python-versions = "*"
8 |
9 | [[package]]
10 | name = "argcomplete"
11 | version = "1.12.3"
12 | description = "Bash tab completion for argparse"
13 | category = "dev"
14 | optional = false
15 | python-versions = "*"
16 |
17 | [package.dependencies]
18 | importlib-metadata = {version = ">=0.23,<5", markers = "python_version == \"3.7\""}
19 |
20 | [package.extras]
21 | test = ["coverage", "flake8", "pexpect", "wheel"]
22 |
23 | [[package]]
24 | name = "atomicwrites"
25 | version = "1.4.0"
26 | description = "Atomic file writes."
27 | category = "dev"
28 | optional = false
29 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
30 |
31 | [[package]]
32 | name = "attrs"
33 | version = "21.2.0"
34 | description = "Classes Without Boilerplate"
35 | category = "dev"
36 | optional = false
37 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
38 |
39 | [package.extras]
40 | dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "furo", "sphinx", "sphinx-notfound-page", "pre-commit"]
41 | docs = ["furo", "sphinx", "zope.interface", "sphinx-notfound-page"]
42 | tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface"]
43 | tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins"]
44 |
45 | [[package]]
46 | name = "backports.entry-points-selectable"
47 | version = "1.1.0"
48 | description = "Compatibility shim providing selectable entry points for older implementations"
49 | category = "dev"
50 | optional = false
51 | python-versions = ">=2.7"
52 |
53 | [package.dependencies]
54 | importlib-metadata = {version = "*", markers = "python_version < \"3.8\""}
55 |
56 | [package.extras]
57 | docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"]
58 | testing = ["pytest (>=4.6)", "pytest-flake8", "pytest-cov", "pytest-black (>=0.3.7)", "pytest-mypy", "pytest-checkdocs (>=2.4)", "pytest-enabler (>=1.0.1)"]
59 |
60 | [[package]]
61 | name = "bandit"
62 | version = "1.7.0"
63 | description = "Security oriented static analyser for python code."
64 | category = "dev"
65 | optional = false
66 | python-versions = ">=3.5"
67 |
68 | [package.dependencies]
69 | colorama = {version = ">=0.3.9", markers = "platform_system == \"Windows\""}
70 | GitPython = ">=1.0.1"
71 | PyYAML = ">=5.3.1"
72 | six = ">=1.10.0"
73 | stevedore = ">=1.20.0"
74 |
75 | [[package]]
76 | name = "bgfx-python"
77 | version = "2.0.1"
78 | description = "Python wrapper for BGFX Library"
79 | category = "main"
80 | optional = false
81 | python-versions = ">=3.7.0"
82 |
83 | [package.dependencies]
84 | clang = "*"
85 | cppyy = "*"
86 | loguru = "*"
87 |
88 | [[package]]
89 | name = "black"
90 | version = "20.8b1"
91 | description = "The uncompromising code formatter."
92 | category = "dev"
93 | optional = false
94 | python-versions = ">=3.6"
95 |
96 | [package.dependencies]
97 | appdirs = "*"
98 | click = ">=7.1.2"
99 | mypy-extensions = ">=0.4.3"
100 | pathspec = ">=0.6,<1"
101 | regex = ">=2020.1.8"
102 | toml = ">=0.10.1"
103 | typed-ast = ">=1.4.0"
104 | typing-extensions = ">=3.7.4"
105 |
106 | [package.extras]
107 | colorama = ["colorama (>=0.4.3)"]
108 | d = ["aiohttp (>=3.3.2)", "aiohttp-cors"]
109 |
110 | [[package]]
111 | name = "certifi"
112 | version = "2021.10.8"
113 | description = "Python package for providing Mozilla's CA Bundle."
114 | category = "dev"
115 | optional = false
116 | python-versions = "*"
117 |
118 | [[package]]
119 | name = "charset-normalizer"
120 | version = "2.0.6"
121 | description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet."
122 | category = "dev"
123 | optional = false
124 | python-versions = ">=3.5.0"
125 |
126 | [package.extras]
127 | unicode_backport = ["unicodedata2"]
128 |
129 | [[package]]
130 | name = "clang"
131 | version = "11.0"
132 | description = "libclang python bindings"
133 | category = "main"
134 | optional = false
135 | python-versions = "*"
136 |
137 | [[package]]
138 | name = "click"
139 | version = "8.0.3"
140 | description = "Composable command line interface toolkit"
141 | category = "dev"
142 | optional = false
143 | python-versions = ">=3.6"
144 |
145 | [package.dependencies]
146 | colorama = {version = "*", markers = "platform_system == \"Windows\""}
147 | importlib-metadata = {version = "*", markers = "python_version < \"3.8\""}
148 |
149 | [[package]]
150 | name = "colorama"
151 | version = "0.4.4"
152 | description = "Cross-platform colored terminal text."
153 | category = "main"
154 | optional = false
155 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
156 |
157 | [[package]]
158 | name = "colorlog"
159 | version = "4.8.0"
160 | description = "Log formatting with colors!"
161 | category = "dev"
162 | optional = false
163 | python-versions = "*"
164 |
165 | [package.dependencies]
166 | colorama = {version = "*", markers = "sys_platform == \"win32\""}
167 |
168 | [[package]]
169 | name = "cppyy"
170 | version = "2.1.0"
171 | description = "Cling-based Python-C++ bindings"
172 | category = "main"
173 | optional = false
174 | python-versions = "*"
175 |
176 | [package.dependencies]
177 | cppyy-backend = "1.14.6"
178 | cppyy-cling = "6.25.1"
179 | CPyCppyy = "1.12.7"
180 |
181 | [[package]]
182 | name = "cppyy-backend"
183 | version = "1.14.6"
184 | description = "C/C++ wrapper for Cling"
185 | category = "main"
186 | optional = false
187 | python-versions = "*"
188 |
189 | [package.dependencies]
190 | cppyy-cling = ">=6.25.1"
191 |
192 | [[package]]
193 | name = "cppyy-cling"
194 | version = "6.25.1"
195 | description = "Re-packaged Cling, as backend for cppyy"
196 | category = "main"
197 | optional = false
198 | python-versions = "*"
199 |
200 | [[package]]
201 | name = "cpycppyy"
202 | version = "1.12.7"
203 | description = "Cling-based Python-C++ bindings for CPython"
204 | category = "main"
205 | optional = false
206 | python-versions = "*"
207 |
208 | [package.dependencies]
209 | cppyy-backend = ">=1.14.6"
210 | cppyy-cling = ">=6.25.1"
211 |
212 | [[package]]
213 | name = "distlib"
214 | version = "0.3.3"
215 | description = "Distribution utilities"
216 | category = "dev"
217 | optional = false
218 | python-versions = "*"
219 |
220 | [[package]]
221 | name = "dparse"
222 | version = "0.5.1"
223 | description = "A parser for Python dependency files"
224 | category = "dev"
225 | optional = false
226 | python-versions = ">=3.5"
227 |
228 | [package.dependencies]
229 | packaging = "*"
230 | pyyaml = "*"
231 | toml = "*"
232 |
233 | [package.extras]
234 | pipenv = ["pipenv"]
235 |
236 | [[package]]
237 | name = "filelock"
238 | version = "3.3.0"
239 | description = "A platform independent file lock."
240 | category = "dev"
241 | optional = false
242 | python-versions = ">=3.6"
243 |
244 | [package.extras]
245 | docs = ["furo (>=2021.8.17b43)", "sphinx (>=4.1)", "sphinx-autodoc-typehints (>=1.12)"]
246 | testing = ["covdefaults (>=1.2.0)", "coverage (>=4)", "pytest (>=4)", "pytest-cov", "pytest-timeout (>=1.4.2)"]
247 |
248 | [[package]]
249 | name = "flake8"
250 | version = "3.9.2"
251 | description = "the modular source code checker: pep8 pyflakes and co"
252 | category = "dev"
253 | optional = false
254 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7"
255 |
256 | [package.dependencies]
257 | importlib-metadata = {version = "*", markers = "python_version < \"3.8\""}
258 | mccabe = ">=0.6.0,<0.7.0"
259 | pycodestyle = ">=2.7.0,<2.8.0"
260 | pyflakes = ">=2.3.0,<2.4.0"
261 |
262 | [[package]]
263 | name = "flake8-bandit"
264 | version = "2.1.2"
265 | description = "Automated security testing with bandit and flake8."
266 | category = "dev"
267 | optional = false
268 | python-versions = "*"
269 |
270 | [package.dependencies]
271 | bandit = "*"
272 | flake8 = "*"
273 | flake8-polyfill = "*"
274 | pycodestyle = "*"
275 |
276 | [[package]]
277 | name = "flake8-black"
278 | version = "0.2.3"
279 | description = "flake8 plugin to call black as a code style validator"
280 | category = "dev"
281 | optional = false
282 | python-versions = "*"
283 |
284 | [package.dependencies]
285 | black = "*"
286 | flake8 = ">=3.0.0"
287 | toml = "*"
288 |
289 | [[package]]
290 | name = "flake8-bugbear"
291 | version = "20.11.1"
292 | description = "A plugin for flake8 finding likely bugs and design problems in your program. Contains warnings that don't belong in pyflakes and pycodestyle."
293 | category = "dev"
294 | optional = false
295 | python-versions = ">=3.6"
296 |
297 | [package.dependencies]
298 | attrs = ">=19.2.0"
299 | flake8 = ">=3.0.0"
300 |
301 | [package.extras]
302 | dev = ["coverage", "black", "hypothesis", "hypothesmith"]
303 |
304 | [[package]]
305 | name = "flake8-import-order"
306 | version = "0.18.1"
307 | description = "Flake8 and pylama plugin that checks the ordering of import statements."
308 | category = "dev"
309 | optional = false
310 | python-versions = "*"
311 |
312 | [package.dependencies]
313 | pycodestyle = "*"
314 |
315 | [[package]]
316 | name = "flake8-polyfill"
317 | version = "1.0.2"
318 | description = "Polyfill package for Flake8 plugins"
319 | category = "dev"
320 | optional = false
321 | python-versions = "*"
322 |
323 | [package.dependencies]
324 | flake8 = "*"
325 |
326 | [[package]]
327 | name = "gitdb"
328 | version = "4.0.7"
329 | description = "Git Object Database"
330 | category = "dev"
331 | optional = false
332 | python-versions = ">=3.4"
333 |
334 | [package.dependencies]
335 | smmap = ">=3.0.1,<5"
336 |
337 | [[package]]
338 | name = "gitpython"
339 | version = "3.1.24"
340 | description = "GitPython is a python library used to interact with Git repositories"
341 | category = "dev"
342 | optional = false
343 | python-versions = ">=3.7"
344 |
345 | [package.dependencies]
346 | gitdb = ">=4.0.1,<5"
347 | typing-extensions = {version = ">=3.7.4.3", markers = "python_version < \"3.10\""}
348 |
349 | [[package]]
350 | name = "glfw"
351 | version = "1.12.0"
352 | description = "A ctypes-based wrapper for GLFW3."
353 | category = "main"
354 | optional = false
355 | python-versions = "*"
356 |
357 | [[package]]
358 | name = "idna"
359 | version = "3.2"
360 | description = "Internationalized Domain Names in Applications (IDNA)"
361 | category = "dev"
362 | optional = false
363 | python-versions = ">=3.5"
364 |
365 | [[package]]
366 | name = "importlib-metadata"
367 | version = "4.8.1"
368 | description = "Read metadata from Python packages"
369 | category = "dev"
370 | optional = false
371 | python-versions = ">=3.6"
372 |
373 | [package.dependencies]
374 | typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""}
375 | zipp = ">=0.5"
376 |
377 | [package.extras]
378 | docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"]
379 | perf = ["ipython"]
380 | testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "packaging", "pep517", "pyfakefs", "flufl.flake8", "pytest-perf (>=0.9.2)", "pytest-black (>=0.3.7)", "pytest-mypy", "importlib-resources (>=1.3)"]
381 |
382 | [[package]]
383 | name = "loguru"
384 | version = "0.5.3"
385 | description = "Python logging made (stupidly) simple"
386 | category = "main"
387 | optional = false
388 | python-versions = ">=3.5"
389 |
390 | [package.dependencies]
391 | colorama = {version = ">=0.3.4", markers = "sys_platform == \"win32\""}
392 | win32-setctime = {version = ">=1.0.0", markers = "sys_platform == \"win32\""}
393 |
394 | [package.extras]
395 | dev = ["codecov (>=2.0.15)", "colorama (>=0.3.4)", "flake8 (>=3.7.7)", "tox (>=3.9.0)", "tox-travis (>=0.12)", "pytest (>=4.6.2)", "pytest-cov (>=2.7.1)", "Sphinx (>=2.2.1)", "sphinx-autobuild (>=0.7.1)", "sphinx-rtd-theme (>=0.4.3)", "black (>=19.10b0)", "isort (>=5.1.1)"]
396 |
397 | [[package]]
398 | name = "mccabe"
399 | version = "0.6.1"
400 | description = "McCabe checker, plugin for flake8"
401 | category = "dev"
402 | optional = false
403 | python-versions = "*"
404 |
405 | [[package]]
406 | name = "more-itertools"
407 | version = "8.10.0"
408 | description = "More routines for operating on iterables, beyond itertools"
409 | category = "dev"
410 | optional = false
411 | python-versions = ">=3.5"
412 |
413 | [[package]]
414 | name = "mypy-extensions"
415 | version = "0.4.3"
416 | description = "Experimental type system extensions for programs checked with the mypy typechecker."
417 | category = "dev"
418 | optional = false
419 | python-versions = "*"
420 |
421 | [[package]]
422 | name = "nox"
423 | version = "2020.12.31"
424 | description = "Flexible test automation."
425 | category = "dev"
426 | optional = false
427 | python-versions = ">=3.6"
428 |
429 | [package.dependencies]
430 | argcomplete = ">=1.9.4,<2.0"
431 | colorlog = ">=2.6.1,<5.0.0"
432 | importlib-metadata = {version = "*", markers = "python_version < \"3.8\""}
433 | py = ">=1.4.0,<2.0.0"
434 | virtualenv = ">=14.0.0"
435 |
436 | [package.extras]
437 | tox_to_nox = ["jinja2", "tox"]
438 |
439 | [[package]]
440 | name = "numpy"
441 | version = "1.21.1"
442 | description = "NumPy is the fundamental package for array computing with Python."
443 | category = "dev"
444 | optional = false
445 | python-versions = ">=3.7"
446 |
447 | [[package]]
448 | name = "packaging"
449 | version = "21.0"
450 | description = "Core utilities for Python packages"
451 | category = "dev"
452 | optional = false
453 | python-versions = ">=3.6"
454 |
455 | [package.dependencies]
456 | pyparsing = ">=2.0.2"
457 |
458 | [[package]]
459 | name = "pathspec"
460 | version = "0.9.0"
461 | description = "Utility library for gitignore style pattern matching of file paths."
462 | category = "dev"
463 | optional = false
464 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7"
465 |
466 | [[package]]
467 | name = "pbr"
468 | version = "5.6.0"
469 | description = "Python Build Reasonableness"
470 | category = "dev"
471 | optional = false
472 | python-versions = ">=2.6"
473 |
474 | [[package]]
475 | name = "platformdirs"
476 | version = "2.4.0"
477 | description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
478 | category = "dev"
479 | optional = false
480 | python-versions = ">=3.6"
481 |
482 | [package.extras]
483 | docs = ["Sphinx (>=4)", "furo (>=2021.7.5b38)", "proselint (>=0.10.2)", "sphinx-autodoc-typehints (>=1.12)"]
484 | test = ["appdirs (==1.4.4)", "pytest (>=6)", "pytest-cov (>=2.7)", "pytest-mock (>=3.6)"]
485 |
486 | [[package]]
487 | name = "pluggy"
488 | version = "0.13.1"
489 | description = "plugin and hook calling mechanisms for python"
490 | category = "dev"
491 | optional = false
492 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
493 |
494 | [package.dependencies]
495 | importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""}
496 |
497 | [package.extras]
498 | dev = ["pre-commit", "tox"]
499 |
500 | [[package]]
501 | name = "py"
502 | version = "1.10.0"
503 | description = "library with cross-python path, ini-parsing, io, code, log facilities"
504 | category = "dev"
505 | optional = false
506 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
507 |
508 | [[package]]
509 | name = "pycodestyle"
510 | version = "2.7.0"
511 | description = "Python style guide checker"
512 | category = "dev"
513 | optional = false
514 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
515 |
516 | [[package]]
517 | name = "pyflakes"
518 | version = "2.3.1"
519 | description = "passive checker of Python programs"
520 | category = "dev"
521 | optional = false
522 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
523 |
524 | [[package]]
525 | name = "pyparsing"
526 | version = "2.4.7"
527 | description = "Python parsing module"
528 | category = "dev"
529 | optional = false
530 | python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
531 |
532 | [[package]]
533 | name = "pytest"
534 | version = "5.4.3"
535 | description = "pytest: simple powerful testing with Python"
536 | category = "dev"
537 | optional = false
538 | python-versions = ">=3.5"
539 |
540 | [package.dependencies]
541 | atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""}
542 | attrs = ">=17.4.0"
543 | colorama = {version = "*", markers = "sys_platform == \"win32\""}
544 | importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""}
545 | more-itertools = ">=4.0.0"
546 | packaging = "*"
547 | pluggy = ">=0.12,<1.0"
548 | py = ">=1.5.0"
549 | wcwidth = "*"
550 |
551 | [package.extras]
552 | checkqa-mypy = ["mypy (==v0.761)"]
553 | testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"]
554 |
555 | [[package]]
556 | name = "pytest-mock"
557 | version = "1.13.0"
558 | description = "Thin-wrapper around the mock package for easier use with py.test"
559 | category = "dev"
560 | optional = false
561 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
562 |
563 | [package.dependencies]
564 | pytest = ">=2.7"
565 |
566 | [package.extras]
567 | dev = ["pre-commit", "tox"]
568 |
569 | [[package]]
570 | name = "pytest-sugar"
571 | version = "0.9.4"
572 | description = "pytest-sugar is a plugin for pytest that changes the default look and feel of pytest (e.g. progressbar, show tests that fail instantly)."
573 | category = "dev"
574 | optional = false
575 | python-versions = "*"
576 |
577 | [package.dependencies]
578 | packaging = ">=14.1"
579 | pytest = ">=2.9"
580 | termcolor = ">=1.1.0"
581 |
582 | [[package]]
583 | name = "python-decouple"
584 | version = "3.5"
585 | description = "Strict separation of settings from code."
586 | category = "main"
587 | optional = false
588 | python-versions = "*"
589 |
590 | [[package]]
591 | name = "pyyaml"
592 | version = "5.4.1"
593 | description = "YAML parser and emitter for Python"
594 | category = "dev"
595 | optional = false
596 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*"
597 |
598 | [[package]]
599 | name = "regex"
600 | version = "2021.10.8"
601 | description = "Alternative regular expression module, to replace re."
602 | category = "dev"
603 | optional = false
604 | python-versions = "*"
605 |
606 | [[package]]
607 | name = "requests"
608 | version = "2.26.0"
609 | description = "Python HTTP for Humans."
610 | category = "dev"
611 | optional = false
612 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*"
613 |
614 | [package.dependencies]
615 | certifi = ">=2017.4.17"
616 | charset-normalizer = {version = ">=2.0.0,<2.1.0", markers = "python_version >= \"3\""}
617 | idna = {version = ">=2.5,<4", markers = "python_version >= \"3\""}
618 | urllib3 = ">=1.21.1,<1.27"
619 |
620 | [package.extras]
621 | socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"]
622 | use_chardet_on_py3 = ["chardet (>=3.0.2,<5)"]
623 |
624 | [[package]]
625 | name = "safety"
626 | version = "1.10.3"
627 | description = "Checks installed dependencies for known vulnerabilities."
628 | category = "dev"
629 | optional = false
630 | python-versions = ">=3.5"
631 |
632 | [package.dependencies]
633 | Click = ">=6.0"
634 | dparse = ">=0.5.1"
635 | packaging = "*"
636 | requests = "*"
637 |
638 | [[package]]
639 | name = "six"
640 | version = "1.16.0"
641 | description = "Python 2 and 3 compatibility utilities"
642 | category = "dev"
643 | optional = false
644 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
645 |
646 | [[package]]
647 | name = "smmap"
648 | version = "4.0.0"
649 | description = "A pure Python implementation of a sliding window memory map manager"
650 | category = "dev"
651 | optional = false
652 | python-versions = ">=3.5"
653 |
654 | [[package]]
655 | name = "stevedore"
656 | version = "3.4.0"
657 | description = "Manage dynamic plugins for Python applications"
658 | category = "dev"
659 | optional = false
660 | python-versions = ">=3.6"
661 |
662 | [package.dependencies]
663 | importlib-metadata = {version = ">=1.7.0", markers = "python_version < \"3.8\""}
664 | pbr = ">=2.0.0,<2.1.0 || >2.1.0"
665 |
666 | [[package]]
667 | name = "termcolor"
668 | version = "1.1.0"
669 | description = "ANSII Color formatting for output in terminal."
670 | category = "dev"
671 | optional = false
672 | python-versions = "*"
673 |
674 | [[package]]
675 | name = "toml"
676 | version = "0.10.2"
677 | description = "Python Library for Tom's Obvious, Minimal Language"
678 | category = "dev"
679 | optional = false
680 | python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
681 |
682 | [[package]]
683 | name = "typed-ast"
684 | version = "1.4.3"
685 | description = "a fork of Python 2 and 3 ast modules with type comment support"
686 | category = "dev"
687 | optional = false
688 | python-versions = "*"
689 |
690 | [[package]]
691 | name = "typing-extensions"
692 | version = "3.10.0.2"
693 | description = "Backported and Experimental Type Hints for Python 3.5+"
694 | category = "dev"
695 | optional = false
696 | python-versions = "*"
697 |
698 | [[package]]
699 | name = "urllib3"
700 | version = "1.26.7"
701 | description = "HTTP library with thread-safe connection pooling, file post, and more."
702 | category = "dev"
703 | optional = false
704 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4"
705 |
706 | [package.extras]
707 | brotli = ["brotlipy (>=0.6.0)"]
708 | secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"]
709 | socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"]
710 |
711 | [[package]]
712 | name = "virtualenv"
713 | version = "20.8.1"
714 | description = "Virtual Python Environment builder"
715 | category = "dev"
716 | optional = false
717 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7"
718 |
719 | [package.dependencies]
720 | "backports.entry-points-selectable" = ">=1.0.4"
721 | distlib = ">=0.3.1,<1"
722 | filelock = ">=3.0.0,<4"
723 | importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""}
724 | platformdirs = ">=2,<3"
725 | six = ">=1.9.0,<2"
726 |
727 | [package.extras]
728 | docs = ["proselint (>=0.10.2)", "sphinx (>=3)", "sphinx-argparse (>=0.2.5)", "sphinx-rtd-theme (>=0.4.3)", "towncrier (>=19.9.0rc1)"]
729 | testing = ["coverage (>=4)", "coverage-enable-subprocess (>=1)", "flaky (>=3)", "pytest (>=4)", "pytest-env (>=0.6.2)", "pytest-freezegun (>=0.4.1)", "pytest-mock (>=2)", "pytest-randomly (>=1)", "pytest-timeout (>=1)", "packaging (>=20.0)"]
730 |
731 | [[package]]
732 | name = "wcwidth"
733 | version = "0.2.5"
734 | description = "Measures the displayed width of unicode strings in a terminal"
735 | category = "dev"
736 | optional = false
737 | python-versions = "*"
738 |
739 | [[package]]
740 | name = "win32-setctime"
741 | version = "1.0.3"
742 | description = "A small Python utility to set file creation time on Windows"
743 | category = "main"
744 | optional = false
745 | python-versions = ">=3.5"
746 |
747 | [package.extras]
748 | dev = ["pytest (>=4.6.2)", "black (>=19.3b0)"]
749 |
750 | [[package]]
751 | name = "zipp"
752 | version = "3.6.0"
753 | description = "Backport of pathlib-compatible object wrapper for zip files"
754 | category = "dev"
755 | optional = false
756 | python-versions = ">=3.6"
757 |
758 | [package.extras]
759 | docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"]
760 | testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy"]
761 |
762 | [metadata]
763 | lock-version = "1.1"
764 | python-versions = "^3.7"
765 | content-hash = "dea7b78c5cb774d28b386950419497b41cd7cc94f97955b560dcaf61f7941f46"
766 |
767 | [metadata.files]
768 | appdirs = [
769 | {file = "appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"},
770 | {file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"},
771 | ]
772 | argcomplete = [
773 | {file = "argcomplete-1.12.3-py2.py3-none-any.whl", hash = "sha256:291f0beca7fd49ce285d2f10e4c1c77e9460cf823eef2de54df0c0fec88b0d81"},
774 | {file = "argcomplete-1.12.3.tar.gz", hash = "sha256:2c7dbffd8c045ea534921e63b0be6fe65e88599990d8dc408ac8c542b72a5445"},
775 | ]
776 | atomicwrites = [
777 | {file = "atomicwrites-1.4.0-py2.py3-none-any.whl", hash = "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197"},
778 | {file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"},
779 | ]
780 | attrs = [
781 | {file = "attrs-21.2.0-py2.py3-none-any.whl", hash = "sha256:149e90d6d8ac20db7a955ad60cf0e6881a3f20d37096140088356da6c716b0b1"},
782 | {file = "attrs-21.2.0.tar.gz", hash = "sha256:ef6aaac3ca6cd92904cdd0d83f629a15f18053ec84e6432106f7a4d04ae4f5fb"},
783 | ]
784 | "backports.entry-points-selectable" = [
785 | {file = "backports.entry_points_selectable-1.1.0-py2.py3-none-any.whl", hash = "sha256:a6d9a871cde5e15b4c4a53e3d43ba890cc6861ec1332c9c2428c92f977192acc"},
786 | {file = "backports.entry_points_selectable-1.1.0.tar.gz", hash = "sha256:988468260ec1c196dab6ae1149260e2f5472c9110334e5d51adcb77867361f6a"},
787 | ]
788 | bandit = [
789 | {file = "bandit-1.7.0-py3-none-any.whl", hash = "sha256:216be4d044209fa06cf2a3e51b319769a51be8318140659719aa7a115c35ed07"},
790 | {file = "bandit-1.7.0.tar.gz", hash = "sha256:8a4c7415254d75df8ff3c3b15cfe9042ecee628a1e40b44c15a98890fbfc2608"},
791 | ]
792 | bgfx-python = [
793 | {file = "bgfx-python-2.0.1.tar.gz", hash = "sha256:cfc99ab01b60c3254f580a51a945345c6c311656051230de8183c9045539b62e"},
794 | {file = "bgfx_python-2.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:fd400488d7ceaa77c91b9f79b804c7ac97918aa119f5f34337751bb0ada43a51"},
795 | {file = "bgfx_python-2.0.1-cp37-cp37m-manylinux_2_24_x86_64.whl", hash = "sha256:8433b37b3a3d85f6ad30bddde7a95ff8911cbd7fd50983bbfe927917b4bd447a"},
796 | {file = "bgfx_python-2.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:cd8195d46e780f4678a73d92629bb1d59b233a9cba0e0a09b9988b66b2e07fce"},
797 | {file = "bgfx_python-2.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f790b874f1b9d29842920fc99902a89d732d46719d0ef4799b8f7c3dd4ed4c0e"},
798 | {file = "bgfx_python-2.0.1-cp38-cp38-manylinux_2_24_x86_64.whl", hash = "sha256:0bcfcfbc64fe4a47c10db953ce688b6037e0ec8f4e6c240449308858b207d570"},
799 | {file = "bgfx_python-2.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:80d17fd07108b9f9cbb56b4c966689485ba0c138d5845975a514e1b9d9bbbd88"},
800 | {file = "bgfx_python-2.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4cc51193502bb7c8ad23b57153336a34a73e9869f11ac7901ac82af9d8f60b1b"},
801 | {file = "bgfx_python-2.0.1-cp39-cp39-manylinux_2_24_x86_64.whl", hash = "sha256:ea66bc03875bf17cb7ecd44280a756faa816df360b4fe0862e2a35f228e44ebd"},
802 | {file = "bgfx_python-2.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:31b4be4b791d28bfd75ed3ee92ddfadfa5b74f631bbf5202d325cf2bd1575d24"},
803 | ]
804 | black = [
805 | {file = "black-20.8b1.tar.gz", hash = "sha256:1c02557aa099101b9d21496f8a914e9ed2222ef70336404eeeac8edba836fbea"},
806 | ]
807 | certifi = [
808 | {file = "certifi-2021.10.8-py2.py3-none-any.whl", hash = "sha256:d62a0163eb4c2344ac042ab2bdf75399a71a2d8c7d47eac2e2ee91b9d6339569"},
809 | {file = "certifi-2021.10.8.tar.gz", hash = "sha256:78884e7c1d4b00ce3cea67b44566851c4343c120abd683433ce934a68ea58872"},
810 | ]
811 | charset-normalizer = [
812 | {file = "charset-normalizer-2.0.6.tar.gz", hash = "sha256:5ec46d183433dcbd0ab716f2d7f29d8dee50505b3fdb40c6b985c7c4f5a3591f"},
813 | {file = "charset_normalizer-2.0.6-py3-none-any.whl", hash = "sha256:5d209c0a931f215cee683b6445e2d77677e7e75e159f78def0db09d68fafcaa6"},
814 | ]
815 | clang = [
816 | {file = "clang-11.0-py3-none-any.whl", hash = "sha256:de1b1e7defe3b499e82cc9851cdc968790575c510f62c2a000c724e1943ca8bb"},
817 | {file = "clang-11.0.tar.gz", hash = "sha256:f838e6475b1fe5c91efb97e80ae19420c39483fd5aa7ef10f03ffb51edc6f8c5"},
818 | ]
819 | click = [
820 | {file = "click-8.0.3-py3-none-any.whl", hash = "sha256:353f466495adaeb40b6b5f592f9f91cb22372351c84caeb068132442a4518ef3"},
821 | {file = "click-8.0.3.tar.gz", hash = "sha256:410e932b050f5eed773c4cda94de75971c89cdb3155a72a0831139a79e5ecb5b"},
822 | ]
823 | colorama = [
824 | {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"},
825 | {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"},
826 | ]
827 | colorlog = [
828 | {file = "colorlog-4.8.0-py2.py3-none-any.whl", hash = "sha256:3dd15cb27e8119a24c1a7b5c93f9f3b455855e0f73993b1c25921b2f646f1dcd"},
829 | {file = "colorlog-4.8.0.tar.gz", hash = "sha256:59b53160c60902c405cdec28d38356e09d40686659048893e026ecbd589516b1"},
830 | ]
831 | cppyy = [
832 | {file = "cppyy-2.1.0.tar.gz", hash = "sha256:e5feb67fe6af2314da8ced934b2966ec3dd55a4649462b21607a2f51de1d0b27"},
833 | ]
834 | cppyy-backend = [
835 | {file = "cppyy-backend-1.14.6.tar.gz", hash = "sha256:42598fc1f578ccd389a8c5a5410f57809726dce758185585c3a2ab1d72bd4ed3"},
836 | ]
837 | cppyy-cling = [
838 | {file = "cppyy-cling-6.25.1.tar.gz", hash = "sha256:bedb63a5b47a2bcb482f793fbd992f4b4a9318dd0dbd24e6dd57de07c30b66fd"},
839 | {file = "cppyy_cling-6.25.1-py2.py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b3dd95a96989d2d552f78de92feaf2b7052be6d7e6b64d617ee377874cf3fbcf"},
840 | {file = "cppyy_cling-6.25.1-py2.py3-none-manylinux1_x86_64.whl", hash = "sha256:4a88b88fd53b91d09000f545edcdc09dee37a63635b7166077c399d769361d2c"},
841 | {file = "cppyy_cling-6.25.1-py2.py3-none-manylinux2014_x86_64.whl", hash = "sha256:9db039a3f68eb8a62eca0e419e7a8a145120147c41cb8b45844a51828387daa7"},
842 | {file = "cppyy_cling-6.25.1-py2.py3-none-win32.whl", hash = "sha256:6aef281e0e897e52955091a69f06ef06a0429292a70ce6500c3b981b22ae2359"},
843 | {file = "cppyy_cling-6.25.1-py2.py3-none-win_amd64.whl", hash = "sha256:da5fd43598bf643fbfed9bbc2f8b044058c30517e556eabf418159deb86c8884"},
844 | ]
845 | cpycppyy = [
846 | {file = "CPyCppyy-1.12.7.tar.gz", hash = "sha256:2c55a5c0d3ce13c6c120288d78fff09447dc4cb29b337f4a90fae6fb83fd9e3c"},
847 | ]
848 | distlib = [
849 | {file = "distlib-0.3.3-py2.py3-none-any.whl", hash = "sha256:c8b54e8454e5bf6237cc84c20e8264c3e991e824ef27e8f1e81049867d861e31"},
850 | {file = "distlib-0.3.3.zip", hash = "sha256:d982d0751ff6eaaab5e2ec8e691d949ee80eddf01a62eaa96ddb11531fe16b05"},
851 | ]
852 | dparse = [
853 | {file = "dparse-0.5.1-py3-none-any.whl", hash = "sha256:e953a25e44ebb60a5c6efc2add4420c177f1d8404509da88da9729202f306994"},
854 | {file = "dparse-0.5.1.tar.gz", hash = "sha256:a1b5f169102e1c894f9a7d5ccf6f9402a836a5d24be80a986c7ce9eaed78f367"},
855 | ]
856 | filelock = [
857 | {file = "filelock-3.3.0-py3-none-any.whl", hash = "sha256:bbc6a0382fe8ec4744ecdf6683a2e07f65eb10ff1aff53fc02a202565446cde0"},
858 | {file = "filelock-3.3.0.tar.gz", hash = "sha256:8c7eab13dc442dc249e95158bcc12dec724465919bdc9831fdbf0660f03d1785"},
859 | ]
860 | flake8 = [
861 | {file = "flake8-3.9.2-py2.py3-none-any.whl", hash = "sha256:bf8fd333346d844f616e8d47905ef3a3384edae6b4e9beb0c5101e25e3110907"},
862 | {file = "flake8-3.9.2.tar.gz", hash = "sha256:07528381786f2a6237b061f6e96610a4167b226cb926e2aa2b6b1d78057c576b"},
863 | ]
864 | flake8-bandit = [
865 | {file = "flake8_bandit-2.1.2.tar.gz", hash = "sha256:687fc8da2e4a239b206af2e54a90093572a60d0954f3054e23690739b0b0de3b"},
866 | ]
867 | flake8-black = [
868 | {file = "flake8-black-0.2.3.tar.gz", hash = "sha256:c199844bc1b559d91195ebe8620216f21ed67f2cc1ff6884294c91a0d2492684"},
869 | {file = "flake8_black-0.2.3-py3-none-any.whl", hash = "sha256:cc080ba5b3773b69ba102b6617a00cc4ecbad8914109690cfda4d565ea435d96"},
870 | ]
871 | flake8-bugbear = [
872 | {file = "flake8-bugbear-20.11.1.tar.gz", hash = "sha256:528020129fea2dea33a466b9d64ab650aa3e5f9ffc788b70ea4bc6cf18283538"},
873 | {file = "flake8_bugbear-20.11.1-py36.py37.py38-none-any.whl", hash = "sha256:f35b8135ece7a014bc0aee5b5d485334ac30a6da48494998cc1fabf7ec70d703"},
874 | ]
875 | flake8-import-order = [
876 | {file = "flake8-import-order-0.18.1.tar.gz", hash = "sha256:a28dc39545ea4606c1ac3c24e9d05c849c6e5444a50fb7e9cdd430fc94de6e92"},
877 | {file = "flake8_import_order-0.18.1-py2.py3-none-any.whl", hash = "sha256:90a80e46886259b9c396b578d75c749801a41ee969a235e163cfe1be7afd2543"},
878 | ]
879 | flake8-polyfill = [
880 | {file = "flake8-polyfill-1.0.2.tar.gz", hash = "sha256:e44b087597f6da52ec6393a709e7108b2905317d0c0b744cdca6208e670d8eda"},
881 | {file = "flake8_polyfill-1.0.2-py2.py3-none-any.whl", hash = "sha256:12be6a34ee3ab795b19ca73505e7b55826d5f6ad7230d31b18e106400169b9e9"},
882 | ]
883 | gitdb = [
884 | {file = "gitdb-4.0.7-py3-none-any.whl", hash = "sha256:6c4cc71933456991da20917998acbe6cf4fb41eeaab7d6d67fbc05ecd4c865b0"},
885 | {file = "gitdb-4.0.7.tar.gz", hash = "sha256:96bf5c08b157a666fec41129e6d327235284cca4c81e92109260f353ba138005"},
886 | ]
887 | gitpython = [
888 | {file = "GitPython-3.1.24-py3-none-any.whl", hash = "sha256:dc0a7f2f697657acc8d7f89033e8b1ea94dd90356b2983bca89dc8d2ab3cc647"},
889 | {file = "GitPython-3.1.24.tar.gz", hash = "sha256:df83fdf5e684fef7c6ee2c02fc68a5ceb7e7e759d08b694088d0cacb4eba59e5"},
890 | ]
891 | glfw = [
892 | {file = "glfw-1.12.0-py2.py27.py3.py30.py31.py32.py33.py34.py35.py36.py37.py38-none-macosx_10_6_intel.whl", hash = "sha256:88bd1cd2ace036d275e9af8312993068d94b3ac19248421eedc35a4baf53bb8c"},
893 | {file = "glfw-1.12.0-py2.py27.py3.py30.py31.py32.py33.py34.py35.py36.py37.py38-none-manylinux2010_i686.whl", hash = "sha256:bb63f6121c40f5f17cd78328c040b40aeaca9ed748f440c40d2fcad824107a74"},
894 | {file = "glfw-1.12.0-py2.py27.py3.py30.py31.py32.py33.py34.py35.py36.py37.py38-none-manylinux2010_x86_64.whl", hash = "sha256:6324fed2c4fd1762ae580277c534b5dab6f360b8fb9aed3e1547cf33f63d207c"},
895 | {file = "glfw-1.12.0-py2.py27.py3.py30.py31.py32.py33.py34.py35.py36.py37.py38-none-manylinux2014_x86_64.whl", hash = "sha256:20c918a1ae413a009f7559be9559bc2ec1d6251888f588ffa7a174d6db69a942"},
896 | {file = "glfw-1.12.0-py2.py27.py3.py30.py31.py32.py33.py34.py35.py36.py37.py38-none-win32.whl", hash = "sha256:fe9622a48ec9dc436e67de0d0bb4c9443996b5bd5564df1734d3a4280b728d38"},
897 | {file = "glfw-1.12.0-py2.py27.py3.py30.py31.py32.py33.py34.py35.py36.py37.py38-none-win_amd64.whl", hash = "sha256:3723db5e5628b2cd8729421fb6fc5fed3e5d25c7d1631f72f13301b218ee1600"},
898 | {file = "glfw-1.12.0.tar.gz", hash = "sha256:f195ed7a43475e4f1603903d6999f3a6b470fda88bd1749ff10adc520abe8fb1"},
899 | ]
900 | idna = [
901 | {file = "idna-3.2-py3-none-any.whl", hash = "sha256:14475042e284991034cb48e06f6851428fb14c4dc953acd9be9a5e95c7b6dd7a"},
902 | {file = "idna-3.2.tar.gz", hash = "sha256:467fbad99067910785144ce333826c71fb0e63a425657295239737f7ecd125f3"},
903 | ]
904 | importlib-metadata = [
905 | {file = "importlib_metadata-4.8.1-py3-none-any.whl", hash = "sha256:b618b6d2d5ffa2f16add5697cf57a46c76a56229b0ed1c438322e4e95645bd15"},
906 | {file = "importlib_metadata-4.8.1.tar.gz", hash = "sha256:f284b3e11256ad1e5d03ab86bb2ccd6f5339688ff17a4d797a0fe7df326f23b1"},
907 | ]
908 | loguru = [
909 | {file = "loguru-0.5.3-py3-none-any.whl", hash = "sha256:f8087ac396b5ee5f67c963b495d615ebbceac2796379599820e324419d53667c"},
910 | {file = "loguru-0.5.3.tar.gz", hash = "sha256:b28e72ac7a98be3d28ad28570299a393dfcd32e5e3f6a353dec94675767b6319"},
911 | ]
912 | mccabe = [
913 | {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"},
914 | {file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"},
915 | ]
916 | more-itertools = [
917 | {file = "more-itertools-8.10.0.tar.gz", hash = "sha256:1debcabeb1df793814859d64a81ad7cb10504c24349368ccf214c664c474f41f"},
918 | {file = "more_itertools-8.10.0-py3-none-any.whl", hash = "sha256:56ddac45541718ba332db05f464bebfb0768110111affd27f66e0051f276fa43"},
919 | ]
920 | mypy-extensions = [
921 | {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"},
922 | {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"},
923 | ]
924 | nox = [
925 | {file = "nox-2020.12.31-py3-none-any.whl", hash = "sha256:f179d6990f7a0a9cebad01b9ecea34556518b8d3340dfcafdc1d85f2c1a37ea0"},
926 | {file = "nox-2020.12.31.tar.gz", hash = "sha256:58a662070767ed4786beb46ce3a789fca6f1e689ed3ac15c73c4d0094e4f9dc4"},
927 | ]
928 | numpy = [
929 | {file = "numpy-1.21.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:38e8648f9449a549a7dfe8d8755a5979b45b3538520d1e735637ef28e8c2dc50"},
930 | {file = "numpy-1.21.1-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:fd7d7409fa643a91d0a05c7554dd68aa9c9bb16e186f6ccfe40d6e003156e33a"},
931 | {file = "numpy-1.21.1-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:a75b4498b1e93d8b700282dc8e655b8bd559c0904b3910b144646dbbbc03e062"},
932 | {file = "numpy-1.21.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1412aa0aec3e00bc23fbb8664d76552b4efde98fb71f60737c83efbac24112f1"},
933 | {file = "numpy-1.21.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e46ceaff65609b5399163de5893d8f2a82d3c77d5e56d976c8b5fb01faa6b671"},
934 | {file = "numpy-1.21.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:c6a2324085dd52f96498419ba95b5777e40b6bcbc20088fddb9e8cbb58885e8e"},
935 | {file = "numpy-1.21.1-cp37-cp37m-win32.whl", hash = "sha256:73101b2a1fef16602696d133db402a7e7586654682244344b8329cdcbbb82172"},
936 | {file = "numpy-1.21.1-cp37-cp37m-win_amd64.whl", hash = "sha256:7a708a79c9a9d26904d1cca8d383bf869edf6f8e7650d85dbc77b041e8c5a0f8"},
937 | {file = "numpy-1.21.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:95b995d0c413f5d0428b3f880e8fe1660ff9396dcd1f9eedbc311f37b5652e16"},
938 | {file = "numpy-1.21.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:635e6bd31c9fb3d475c8f44a089569070d10a9ef18ed13738b03049280281267"},
939 | {file = "numpy-1.21.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4a3d5fb89bfe21be2ef47c0614b9c9c707b7362386c9a3ff1feae63e0267ccb6"},
940 | {file = "numpy-1.21.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:8a326af80e86d0e9ce92bcc1e65c8ff88297de4fa14ee936cb2293d414c9ec63"},
941 | {file = "numpy-1.21.1-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:791492091744b0fe390a6ce85cc1bf5149968ac7d5f0477288f78c89b385d9af"},
942 | {file = "numpy-1.21.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0318c465786c1f63ac05d7c4dbcecd4d2d7e13f0959b01b534ea1e92202235c5"},
943 | {file = "numpy-1.21.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9a513bd9c1551894ee3d31369f9b07460ef223694098cf27d399513415855b68"},
944 | {file = "numpy-1.21.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:91c6f5fc58df1e0a3cc0c3a717bb3308ff850abdaa6d2d802573ee2b11f674a8"},
945 | {file = "numpy-1.21.1-cp38-cp38-win32.whl", hash = "sha256:978010b68e17150db8765355d1ccdd450f9fc916824e8c4e35ee620590e234cd"},
946 | {file = "numpy-1.21.1-cp38-cp38-win_amd64.whl", hash = "sha256:9749a40a5b22333467f02fe11edc98f022133ee1bfa8ab99bda5e5437b831214"},
947 | {file = "numpy-1.21.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:d7a4aeac3b94af92a9373d6e77b37691b86411f9745190d2c351f410ab3a791f"},
948 | {file = "numpy-1.21.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d9e7912a56108aba9b31df688a4c4f5cb0d9d3787386b87d504762b6754fbb1b"},
949 | {file = "numpy-1.21.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:25b40b98ebdd272bc3020935427a4530b7d60dfbe1ab9381a39147834e985eac"},
950 | {file = "numpy-1.21.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:8a92c5aea763d14ba9d6475803fc7904bda7decc2a0a68153f587ad82941fec1"},
951 | {file = "numpy-1.21.1-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:05a0f648eb28bae4bcb204e6fd14603de2908de982e761a2fc78efe0f19e96e1"},
952 | {file = "numpy-1.21.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f01f28075a92eede918b965e86e8f0ba7b7797a95aa8d35e1cc8821f5fc3ad6a"},
953 | {file = "numpy-1.21.1-cp39-cp39-win32.whl", hash = "sha256:88c0b89ad1cc24a5efbb99ff9ab5db0f9a86e9cc50240177a571fbe9c2860ac2"},
954 | {file = "numpy-1.21.1-cp39-cp39-win_amd64.whl", hash = "sha256:01721eefe70544d548425a07c80be8377096a54118070b8a62476866d5208e33"},
955 | {file = "numpy-1.21.1-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:2d4d1de6e6fb3d28781c73fbde702ac97f03d79e4ffd6598b880b2d95d62ead4"},
956 | {file = "numpy-1.21.1.zip", hash = "sha256:dff4af63638afcc57a3dfb9e4b26d434a7a602d225b42d746ea7fe2edf1342fd"},
957 | ]
958 | packaging = [
959 | {file = "packaging-21.0-py3-none-any.whl", hash = "sha256:c86254f9220d55e31cc94d69bade760f0847da8000def4dfe1c6b872fd14ff14"},
960 | {file = "packaging-21.0.tar.gz", hash = "sha256:7dc96269f53a4ccec5c0670940a4281106dd0bb343f47b7471f779df49c2fbe7"},
961 | ]
962 | pathspec = [
963 | {file = "pathspec-0.9.0-py2.py3-none-any.whl", hash = "sha256:7d15c4ddb0b5c802d161efc417ec1a2558ea2653c2e8ad9c19098201dc1c993a"},
964 | {file = "pathspec-0.9.0.tar.gz", hash = "sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1"},
965 | ]
966 | pbr = [
967 | {file = "pbr-5.6.0-py2.py3-none-any.whl", hash = "sha256:c68c661ac5cc81058ac94247278eeda6d2e6aecb3e227b0387c30d277e7ef8d4"},
968 | {file = "pbr-5.6.0.tar.gz", hash = "sha256:42df03e7797b796625b1029c0400279c7c34fd7df24a7d7818a1abb5b38710dd"},
969 | ]
970 | platformdirs = [
971 | {file = "platformdirs-2.4.0-py3-none-any.whl", hash = "sha256:8868bbe3c3c80d42f20156f22e7131d2fb321f5bc86a2a345375c6481a67021d"},
972 | {file = "platformdirs-2.4.0.tar.gz", hash = "sha256:367a5e80b3d04d2428ffa76d33f124cf11e8fff2acdaa9b43d545f5c7d661ef2"},
973 | ]
974 | pluggy = [
975 | {file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"},
976 | {file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"},
977 | ]
978 | py = [
979 | {file = "py-1.10.0-py2.py3-none-any.whl", hash = "sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a"},
980 | {file = "py-1.10.0.tar.gz", hash = "sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3"},
981 | ]
982 | pycodestyle = [
983 | {file = "pycodestyle-2.7.0-py2.py3-none-any.whl", hash = "sha256:514f76d918fcc0b55c6680472f0a37970994e07bbb80725808c17089be302068"},
984 | {file = "pycodestyle-2.7.0.tar.gz", hash = "sha256:c389c1d06bf7904078ca03399a4816f974a1d590090fecea0c63ec26ebaf1cef"},
985 | ]
986 | pyflakes = [
987 | {file = "pyflakes-2.3.1-py2.py3-none-any.whl", hash = "sha256:7893783d01b8a89811dd72d7dfd4d84ff098e5eed95cfa8905b22bbffe52efc3"},
988 | {file = "pyflakes-2.3.1.tar.gz", hash = "sha256:f5bc8ecabc05bb9d291eb5203d6810b49040f6ff446a756326104746cc00c1db"},
989 | ]
990 | pyparsing = [
991 | {file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"},
992 | {file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"},
993 | ]
994 | pytest = [
995 | {file = "pytest-5.4.3-py3-none-any.whl", hash = "sha256:5c0db86b698e8f170ba4582a492248919255fcd4c79b1ee64ace34301fb589a1"},
996 | {file = "pytest-5.4.3.tar.gz", hash = "sha256:7979331bfcba207414f5e1263b5a0f8f521d0f457318836a7355531ed1a4c7d8"},
997 | ]
998 | pytest-mock = [
999 | {file = "pytest-mock-1.13.0.tar.gz", hash = "sha256:e24a911ec96773022ebcc7030059b57cd3480b56d4f5d19b7c370ec635e6aed5"},
1000 | {file = "pytest_mock-1.13.0-py2.py3-none-any.whl", hash = "sha256:67e414b3caef7bff6fc6bd83b22b5bc39147e4493f483c2679bc9d4dc485a94d"},
1001 | ]
1002 | pytest-sugar = [
1003 | {file = "pytest-sugar-0.9.4.tar.gz", hash = "sha256:b1b2186b0a72aada6859bea2a5764145e3aaa2c1cfbb23c3a19b5f7b697563d3"},
1004 | ]
1005 | python-decouple = [
1006 | {file = "python-decouple-3.5.tar.gz", hash = "sha256:68e4b3fcc97e24bc90eecc514852d0bf970f4ff031f5f7a6728ddafa9afefcaf"},
1007 | ]
1008 | pyyaml = [
1009 | {file = "PyYAML-5.4.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:3b2b1824fe7112845700f815ff6a489360226a5609b96ec2190a45e62a9fc922"},
1010 | {file = "PyYAML-5.4.1-cp27-cp27m-win32.whl", hash = "sha256:129def1b7c1bf22faffd67b8f3724645203b79d8f4cc81f674654d9902cb4393"},
1011 | {file = "PyYAML-5.4.1-cp27-cp27m-win_amd64.whl", hash = "sha256:4465124ef1b18d9ace298060f4eccc64b0850899ac4ac53294547536533800c8"},
1012 | {file = "PyYAML-5.4.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:bb4191dfc9306777bc594117aee052446b3fa88737cd13b7188d0e7aa8162185"},
1013 | {file = "PyYAML-5.4.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:6c78645d400265a062508ae399b60b8c167bf003db364ecb26dcab2bda048253"},
1014 | {file = "PyYAML-5.4.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:4e0583d24c881e14342eaf4ec5fbc97f934b999a6828693a99157fde912540cc"},
1015 | {file = "PyYAML-5.4.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:72a01f726a9c7851ca9bfad6fd09ca4e090a023c00945ea05ba1638c09dc3347"},
1016 | {file = "PyYAML-5.4.1-cp36-cp36m-manylinux2014_s390x.whl", hash = "sha256:895f61ef02e8fed38159bb70f7e100e00f471eae2bc838cd0f4ebb21e28f8541"},
1017 | {file = "PyYAML-5.4.1-cp36-cp36m-win32.whl", hash = "sha256:3bd0e463264cf257d1ffd2e40223b197271046d09dadf73a0fe82b9c1fc385a5"},
1018 | {file = "PyYAML-5.4.1-cp36-cp36m-win_amd64.whl", hash = "sha256:e4fac90784481d221a8e4b1162afa7c47ed953be40d31ab4629ae917510051df"},
1019 | {file = "PyYAML-5.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:5accb17103e43963b80e6f837831f38d314a0495500067cb25afab2e8d7a4018"},
1020 | {file = "PyYAML-5.4.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:e1d4970ea66be07ae37a3c2e48b5ec63f7ba6804bdddfdbd3cfd954d25a82e63"},
1021 | {file = "PyYAML-5.4.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:cb333c16912324fd5f769fff6bc5de372e9e7a202247b48870bc251ed40239aa"},
1022 | {file = "PyYAML-5.4.1-cp37-cp37m-manylinux2014_s390x.whl", hash = "sha256:fe69978f3f768926cfa37b867e3843918e012cf83f680806599ddce33c2c68b0"},
1023 | {file = "PyYAML-5.4.1-cp37-cp37m-win32.whl", hash = "sha256:dd5de0646207f053eb0d6c74ae45ba98c3395a571a2891858e87df7c9b9bd51b"},
1024 | {file = "PyYAML-5.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:08682f6b72c722394747bddaf0aa62277e02557c0fd1c42cb853016a38f8dedf"},
1025 | {file = "PyYAML-5.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d2d9808ea7b4af864f35ea216be506ecec180628aced0704e34aca0b040ffe46"},
1026 | {file = "PyYAML-5.4.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:8c1be557ee92a20f184922c7b6424e8ab6691788e6d86137c5d93c1a6ec1b8fb"},
1027 | {file = "PyYAML-5.4.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:fd7f6999a8070df521b6384004ef42833b9bd62cfee11a09bda1079b4b704247"},
1028 | {file = "PyYAML-5.4.1-cp38-cp38-manylinux2014_s390x.whl", hash = "sha256:bfb51918d4ff3d77c1c856a9699f8492c612cde32fd3bcd344af9be34999bfdc"},
1029 | {file = "PyYAML-5.4.1-cp38-cp38-win32.whl", hash = "sha256:fa5ae20527d8e831e8230cbffd9f8fe952815b2b7dae6ffec25318803a7528fc"},
1030 | {file = "PyYAML-5.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:0f5f5786c0e09baddcd8b4b45f20a7b5d61a7e7e99846e3c799b05c7c53fa696"},
1031 | {file = "PyYAML-5.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:294db365efa064d00b8d1ef65d8ea2c3426ac366c0c4368d930bf1c5fb497f77"},
1032 | {file = "PyYAML-5.4.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:74c1485f7707cf707a7aef42ef6322b8f97921bd89be2ab6317fd782c2d53183"},
1033 | {file = "PyYAML-5.4.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:d483ad4e639292c90170eb6f7783ad19490e7a8defb3e46f97dfe4bacae89122"},
1034 | {file = "PyYAML-5.4.1-cp39-cp39-manylinux2014_s390x.whl", hash = "sha256:fdc842473cd33f45ff6bce46aea678a54e3d21f1b61a7750ce3c498eedfe25d6"},
1035 | {file = "PyYAML-5.4.1-cp39-cp39-win32.whl", hash = "sha256:49d4cdd9065b9b6e206d0595fee27a96b5dd22618e7520c33204a4a3239d5b10"},
1036 | {file = "PyYAML-5.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:c20cfa2d49991c8b4147af39859b167664f2ad4561704ee74c1de03318e898db"},
1037 | {file = "PyYAML-5.4.1.tar.gz", hash = "sha256:607774cbba28732bfa802b54baa7484215f530991055bb562efbed5b2f20a45e"},
1038 | ]
1039 | regex = [
1040 | {file = "regex-2021.10.8-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:981c786293a3115bc14c103086ae54e5ee50ca57f4c02ce7cf1b60318d1e8072"},
1041 | {file = "regex-2021.10.8-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:51feefd58ac38eb91a21921b047da8644155e5678e9066af7bcb30ee0dca7361"},
1042 | {file = "regex-2021.10.8-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ea8de658d7db5987b11097445f2b1f134400e2232cb40e614e5f7b6f5428710e"},
1043 | {file = "regex-2021.10.8-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:1ce02f420a7ec3b2480fe6746d756530f69769292eca363218c2291d0b116a01"},
1044 | {file = "regex-2021.10.8-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:39079ebf54156be6e6902f5c70c078f453350616cfe7bfd2dd15bdb3eac20ccc"},
1045 | {file = "regex-2021.10.8-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ff24897f6b2001c38a805d53b6ae72267025878d35ea225aa24675fbff2dba7f"},
1046 | {file = "regex-2021.10.8-cp310-cp310-win32.whl", hash = "sha256:c6569ba7b948c3d61d27f04e2b08ebee24fec9ff8e9ea154d8d1e975b175bfa7"},
1047 | {file = "regex-2021.10.8-cp310-cp310-win_amd64.whl", hash = "sha256:45cb0f7ff782ef51bc79e227a87e4e8f24bc68192f8de4f18aae60b1d60bc152"},
1048 | {file = "regex-2021.10.8-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:fab3ab8aedfb443abb36729410403f0fe7f60ad860c19a979d47fb3eb98ef820"},
1049 | {file = "regex-2021.10.8-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:74e55f8d66f1b41d44bc44c891bcf2c7fad252f8f323ee86fba99d71fd1ad5e3"},
1050 | {file = "regex-2021.10.8-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d52c5e089edbdb6083391faffbe70329b804652a53c2fdca3533e99ab0580d9"},
1051 | {file = "regex-2021.10.8-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:1abbd95cbe9e2467cac65c77b6abd9223df717c7ae91a628502de67c73bf6838"},
1052 | {file = "regex-2021.10.8-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b9b5c215f3870aa9b011c00daeb7be7e1ae4ecd628e9beb6d7e6107e07d81287"},
1053 | {file = "regex-2021.10.8-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f540f153c4f5617bc4ba6433534f8916d96366a08797cbbe4132c37b70403e92"},
1054 | {file = "regex-2021.10.8-cp36-cp36m-win32.whl", hash = "sha256:1f51926db492440e66c89cd2be042f2396cf91e5b05383acd7372b8cb7da373f"},
1055 | {file = "regex-2021.10.8-cp36-cp36m-win_amd64.whl", hash = "sha256:5f55c4804797ef7381518e683249310f7f9646da271b71cb6b3552416c7894ee"},
1056 | {file = "regex-2021.10.8-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:fb2baff66b7d2267e07ef71e17d01283b55b3cc51a81b54cc385e721ae172ba4"},
1057 | {file = "regex-2021.10.8-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9e527ab1c4c7cf2643d93406c04e1d289a9d12966529381ce8163c4d2abe4faf"},
1058 | {file = "regex-2021.10.8-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:36c98b013273e9da5790ff6002ab326e3f81072b4616fd95f06c8fa733d2745f"},
1059 | {file = "regex-2021.10.8-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:55ef044899706c10bc0aa052f2fc2e58551e2510694d6aae13f37c50f3f6ff61"},
1060 | {file = "regex-2021.10.8-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aa0ab3530a279a3b7f50f852f1bab41bc304f098350b03e30a3876b7dd89840e"},
1061 | {file = "regex-2021.10.8-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:a37305eb3199d8f0d8125ec2fb143ba94ff6d6d92554c4b8d4a8435795a6eccd"},
1062 | {file = "regex-2021.10.8-cp37-cp37m-win32.whl", hash = "sha256:2efd47704bbb016136fe34dfb74c805b1ef5c7313aef3ce6dcb5ff844299f432"},
1063 | {file = "regex-2021.10.8-cp37-cp37m-win_amd64.whl", hash = "sha256:924079d5590979c0e961681507eb1773a142553564ccae18d36f1de7324e71ca"},
1064 | {file = "regex-2021.10.8-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b09d3904bf312d11308d9a2867427479d277365b1617e48ad09696fa7dfcdf59"},
1065 | {file = "regex-2021.10.8-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7f125fce0a0ae4fd5c3388d369d7a7d78f185f904c90dd235f7ecf8fe13fa741"},
1066 | {file = "regex-2021.10.8-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5f199419a81c1016e0560c39773c12f0bd924c37715bffc64b97140d2c314354"},
1067 | {file = "regex-2021.10.8-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:09e1031e2059abd91177c302da392a7b6859ceda038be9e015b522a182c89e4f"},
1068 | {file = "regex-2021.10.8-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9c070d5895ac6aeb665bd3cd79f673775caf8d33a0b569e98ac434617ecea57d"},
1069 | {file = "regex-2021.10.8-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:176796cb7f82a7098b0c436d6daac82f57b9101bb17b8e8119c36eecf06a60a3"},
1070 | {file = "regex-2021.10.8-cp38-cp38-win32.whl", hash = "sha256:5e5796d2f36d3c48875514c5cd9e4325a1ca172fc6c78b469faa8ddd3d770593"},
1071 | {file = "regex-2021.10.8-cp38-cp38-win_amd64.whl", hash = "sha256:e4204708fa116dd03436a337e8e84261bc8051d058221ec63535c9403a1582a1"},
1072 | {file = "regex-2021.10.8-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b8b6ee6555b6fbae578f1468b3f685cdfe7940a65675611365a7ea1f8d724991"},
1073 | {file = "regex-2021.10.8-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:973499dac63625a5ef9dfa4c791aa33a502ddb7615d992bdc89cf2cc2285daa3"},
1074 | {file = "regex-2021.10.8-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88dc3c1acd3f0ecfde5f95c32fcb9beda709dbdf5012acdcf66acbc4794468eb"},
1075 | {file = "regex-2021.10.8-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:4786dae85c1f0624ac77cb3813ed99267c9adb72e59fdc7297e1cf4d6036d493"},
1076 | {file = "regex-2021.10.8-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fe6ce4f3d3c48f9f402da1ceb571548133d3322003ce01b20d960a82251695d2"},
1077 | {file = "regex-2021.10.8-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:9e3e2cea8f1993f476a6833ef157f5d9e8c75a59a8d8b0395a9a6887a097243b"},
1078 | {file = "regex-2021.10.8-cp39-cp39-win32.whl", hash = "sha256:82cfb97a36b1a53de32b642482c6c46b6ce80803854445e19bc49993655ebf3b"},
1079 | {file = "regex-2021.10.8-cp39-cp39-win_amd64.whl", hash = "sha256:b04e512eb628ea82ed86eb31c0f7fc6842b46bf2601b66b1356a7008327f7700"},
1080 | {file = "regex-2021.10.8.tar.gz", hash = "sha256:26895d7c9bbda5c52b3635ce5991caa90fbb1ddfac9c9ff1c7ce505e2282fb2a"},
1081 | ]
1082 | requests = [
1083 | {file = "requests-2.26.0-py2.py3-none-any.whl", hash = "sha256:6c1246513ecd5ecd4528a0906f910e8f0f9c6b8ec72030dc9fd154dc1a6efd24"},
1084 | {file = "requests-2.26.0.tar.gz", hash = "sha256:b8aa58f8cf793ffd8782d3d8cb19e66ef36f7aba4353eec859e74678b01b07a7"},
1085 | ]
1086 | safety = [
1087 | {file = "safety-1.10.3-py2.py3-none-any.whl", hash = "sha256:5f802ad5df5614f9622d8d71fedec2757099705c2356f862847c58c6dfe13e84"},
1088 | {file = "safety-1.10.3.tar.gz", hash = "sha256:30e394d02a20ac49b7f65292d19d38fa927a8f9582cdfd3ad1adbbc66c641ad5"},
1089 | ]
1090 | six = [
1091 | {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"},
1092 | {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"},
1093 | ]
1094 | smmap = [
1095 | {file = "smmap-4.0.0-py2.py3-none-any.whl", hash = "sha256:a9a7479e4c572e2e775c404dcd3080c8dc49f39918c2cf74913d30c4c478e3c2"},
1096 | {file = "smmap-4.0.0.tar.gz", hash = "sha256:7e65386bd122d45405ddf795637b7f7d2b532e7e401d46bbe3fb49b9986d5182"},
1097 | ]
1098 | stevedore = [
1099 | {file = "stevedore-3.4.0-py3-none-any.whl", hash = "sha256:920ce6259f0b2498aaa4545989536a27e4e4607b8318802d7ddc3a533d3d069e"},
1100 | {file = "stevedore-3.4.0.tar.gz", hash = "sha256:59b58edb7f57b11897f150475e7bc0c39c5381f0b8e3fa9f5c20ce6c89ec4aa1"},
1101 | ]
1102 | termcolor = [
1103 | {file = "termcolor-1.1.0.tar.gz", hash = "sha256:1d6d69ce66211143803fbc56652b41d73b4a400a2891d7bf7a1cdf4c02de613b"},
1104 | ]
1105 | toml = [
1106 | {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"},
1107 | {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"},
1108 | ]
1109 | typed-ast = [
1110 | {file = "typed_ast-1.4.3-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:2068531575a125b87a41802130fa7e29f26c09a2833fea68d9a40cf33902eba6"},
1111 | {file = "typed_ast-1.4.3-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:c907f561b1e83e93fad565bac5ba9c22d96a54e7ea0267c708bffe863cbe4075"},
1112 | {file = "typed_ast-1.4.3-cp35-cp35m-manylinux2014_aarch64.whl", hash = "sha256:1b3ead4a96c9101bef08f9f7d1217c096f31667617b58de957f690c92378b528"},
1113 | {file = "typed_ast-1.4.3-cp35-cp35m-win32.whl", hash = "sha256:dde816ca9dac1d9c01dd504ea5967821606f02e510438120091b84e852367428"},
1114 | {file = "typed_ast-1.4.3-cp35-cp35m-win_amd64.whl", hash = "sha256:777a26c84bea6cd934422ac2e3b78863a37017618b6e5c08f92ef69853e765d3"},
1115 | {file = "typed_ast-1.4.3-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f8afcf15cc511ada719a88e013cec87c11aff7b91f019295eb4530f96fe5ef2f"},
1116 | {file = "typed_ast-1.4.3-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:52b1eb8c83f178ab787f3a4283f68258525f8d70f778a2f6dd54d3b5e5fb4341"},
1117 | {file = "typed_ast-1.4.3-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:01ae5f73431d21eead5015997ab41afa53aa1fbe252f9da060be5dad2c730ace"},
1118 | {file = "typed_ast-1.4.3-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:c190f0899e9f9f8b6b7863debfb739abcb21a5c054f911ca3596d12b8a4c4c7f"},
1119 | {file = "typed_ast-1.4.3-cp36-cp36m-win32.whl", hash = "sha256:398e44cd480f4d2b7ee8d98385ca104e35c81525dd98c519acff1b79bdaac363"},
1120 | {file = "typed_ast-1.4.3-cp36-cp36m-win_amd64.whl", hash = "sha256:bff6ad71c81b3bba8fa35f0f1921fb24ff4476235a6e94a26ada2e54370e6da7"},
1121 | {file = "typed_ast-1.4.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0fb71b8c643187d7492c1f8352f2c15b4c4af3f6338f21681d3681b3dc31a266"},
1122 | {file = "typed_ast-1.4.3-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:760ad187b1041a154f0e4d0f6aae3e40fdb51d6de16e5c99aedadd9246450e9e"},
1123 | {file = "typed_ast-1.4.3-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:5feca99c17af94057417d744607b82dd0a664fd5e4ca98061480fd8b14b18d04"},
1124 | {file = "typed_ast-1.4.3-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:95431a26309a21874005845c21118c83991c63ea800dd44843e42a916aec5899"},
1125 | {file = "typed_ast-1.4.3-cp37-cp37m-win32.whl", hash = "sha256:aee0c1256be6c07bd3e1263ff920c325b59849dc95392a05f258bb9b259cf39c"},
1126 | {file = "typed_ast-1.4.3-cp37-cp37m-win_amd64.whl", hash = "sha256:9ad2c92ec681e02baf81fdfa056fe0d818645efa9af1f1cd5fd6f1bd2bdfd805"},
1127 | {file = "typed_ast-1.4.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b36b4f3920103a25e1d5d024d155c504080959582b928e91cb608a65c3a49e1a"},
1128 | {file = "typed_ast-1.4.3-cp38-cp38-manylinux1_i686.whl", hash = "sha256:067a74454df670dcaa4e59349a2e5c81e567d8d65458d480a5b3dfecec08c5ff"},
1129 | {file = "typed_ast-1.4.3-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7538e495704e2ccda9b234b82423a4038f324f3a10c43bc088a1636180f11a41"},
1130 | {file = "typed_ast-1.4.3-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:af3d4a73793725138d6b334d9d247ce7e5f084d96284ed23f22ee626a7b88e39"},
1131 | {file = "typed_ast-1.4.3-cp38-cp38-win32.whl", hash = "sha256:f2362f3cb0f3172c42938946dbc5b7843c2a28aec307c49100c8b38764eb6927"},
1132 | {file = "typed_ast-1.4.3-cp38-cp38-win_amd64.whl", hash = "sha256:dd4a21253f42b8d2b48410cb31fe501d32f8b9fbeb1f55063ad102fe9c425e40"},
1133 | {file = "typed_ast-1.4.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f328adcfebed9f11301eaedfa48e15bdece9b519fb27e6a8c01aa52a17ec31b3"},
1134 | {file = "typed_ast-1.4.3-cp39-cp39-manylinux1_i686.whl", hash = "sha256:2c726c276d09fc5c414693a2de063f521052d9ea7c240ce553316f70656c84d4"},
1135 | {file = "typed_ast-1.4.3-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:cae53c389825d3b46fb37538441f75d6aecc4174f615d048321b716df2757fb0"},
1136 | {file = "typed_ast-1.4.3-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:b9574c6f03f685070d859e75c7f9eeca02d6933273b5e69572e5ff9d5e3931c3"},
1137 | {file = "typed_ast-1.4.3-cp39-cp39-win32.whl", hash = "sha256:209596a4ec71d990d71d5e0d312ac935d86930e6eecff6ccc7007fe54d703808"},
1138 | {file = "typed_ast-1.4.3-cp39-cp39-win_amd64.whl", hash = "sha256:9c6d1a54552b5330bc657b7ef0eae25d00ba7ffe85d9ea8ae6540d2197a3788c"},
1139 | {file = "typed_ast-1.4.3.tar.gz", hash = "sha256:fb1bbeac803adea29cedd70781399c99138358c26d05fcbd23c13016b7f5ec65"},
1140 | ]
1141 | typing-extensions = [
1142 | {file = "typing_extensions-3.10.0.2-py2-none-any.whl", hash = "sha256:d8226d10bc02a29bcc81df19a26e56a9647f8b0a6d4a83924139f4a8b01f17b7"},
1143 | {file = "typing_extensions-3.10.0.2-py3-none-any.whl", hash = "sha256:f1d25edafde516b146ecd0613dabcc61409817af4766fbbcfb8d1ad4ec441a34"},
1144 | {file = "typing_extensions-3.10.0.2.tar.gz", hash = "sha256:49f75d16ff11f1cd258e1b988ccff82a3ca5570217d7ad8c5f48205dd99a677e"},
1145 | ]
1146 | urllib3 = [
1147 | {file = "urllib3-1.26.7-py2.py3-none-any.whl", hash = "sha256:c4fdf4019605b6e5423637e01bc9fe4daef873709a7973e195ceba0a62bbc844"},
1148 | {file = "urllib3-1.26.7.tar.gz", hash = "sha256:4987c65554f7a2dbf30c18fd48778ef124af6fab771a377103da0585e2336ece"},
1149 | ]
1150 | virtualenv = [
1151 | {file = "virtualenv-20.8.1-py2.py3-none-any.whl", hash = "sha256:10062e34c204b5e4ec5f62e6ef2473f8ba76513a9a617e873f1f8fb4a519d300"},
1152 | {file = "virtualenv-20.8.1.tar.gz", hash = "sha256:bcc17f0b3a29670dd777d6f0755a4c04f28815395bca279cdcb213b97199a6b8"},
1153 | ]
1154 | wcwidth = [
1155 | {file = "wcwidth-0.2.5-py2.py3-none-any.whl", hash = "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784"},
1156 | {file = "wcwidth-0.2.5.tar.gz", hash = "sha256:c4d647b99872929fdb7bdcaa4fbe7f01413ed3d98077df798530e5b04f116c83"},
1157 | ]
1158 | win32-setctime = [
1159 | {file = "win32_setctime-1.0.3-py3-none-any.whl", hash = "sha256:dc925662de0a6eb987f0b01f599c01a8236cb8c62831c22d9cada09ad958243e"},
1160 | {file = "win32_setctime-1.0.3.tar.gz", hash = "sha256:4e88556c32fdf47f64165a2180ba4552f8bb32c1103a2fafd05723a0bd42bd4b"},
1161 | ]
1162 | zipp = [
1163 | {file = "zipp-3.6.0-py3-none-any.whl", hash = "sha256:9fe5ea21568a0a70e50f273397638d39b03353731e6cbbb3fd8502a33fec40bc"},
1164 | {file = "zipp-3.6.0.tar.gz", hash = "sha256:71c644c5369f4a6e07636f0aa966270449561fcea2e3d6747b8d23efaa9d7832"},
1165 | ]
1166 |
--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------
1 | [tool.poetry]
2 | name = "Natrix"
3 | version = "1.0.1"
4 | description = "Fast fluid simulation in Python"
5 | authors = [
6 | "Federico Bertola"
7 | ]
8 | license = "MIT"
9 |
10 | readme = "README.md"
11 |
12 | repository = "https://github.com/fbertola/natrix"
13 |
14 | keywords = ["fluid-simulation", "navier-stokes", "bgfx"]
15 |
16 | classifiers = [
17 | "License :: OSI Approved :: BSD License",
18 | "Intended Audience :: Developers",
19 | "Operating System :: OS Independent",
20 | "Programming Language :: Python",
21 | "Programming Language :: Python :: 3",
22 | "Programming Language :: Python :: 3.7",
23 | "Programming Language :: Python :: 3.8",
24 | "Programming Language :: Python :: Implementation :: CPython",
25 | "Programming Language :: Python :: Implementation :: PyPy",
26 | "Topic :: Multimedia :: Graphics"
27 | ]
28 |
29 | [tool.poetry.dependencies]
30 | python = "^3.7"
31 | bgfx-python = "^2.0.1"
32 | glfw = "^1.12.0"
33 | python-decouple = "^3.3"
34 |
35 | [tool.poetry.dev-dependencies]
36 | pytest = "^5.4.3"
37 | pytest-mock = "^1.9"
38 | black = "^20.8b1"
39 | pytest-sugar = "^0.9.2"
40 | flake8 = "^3.8.4"
41 | flake8-bandit = "^2.1.2"
42 | flake8-black = "^0.2.0"
43 | flake8-bugbear = "^20.1.4"
44 | flake8-import-order = "^0.18.1"
45 | safety = "^1.9.0"
46 | nox = "^2020.5.24"
47 | glfw = "^1.12.0"
48 | numpy = "^1.19.0"
49 |
50 | [tool.black]
51 | line-length = 88
52 | include = '\.pyi?$'
53 | exclude = '''
54 | /(
55 | \.eggs
56 | | \.git
57 | | \.hg
58 | | \.mypy_cache
59 | | \.tox
60 | | \.venv
61 | | _build
62 | | buck-out
63 | | build
64 | | dist
65 | | tests/.*/setup.py
66 | )/
67 | '''
68 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 |
4 | import io
5 | import os
6 |
7 | from setuptools import find_packages, setup
8 |
9 | NAME = 'Natrix'
10 | DESCRIPTION = 'Fast fluid simulation for python.'
11 | URL = 'https://github.com/fbertola/Natrix'
12 | AUTHOR = 'Federico Bertola'
13 | REQUIRES_PYTHON = '>=3.6.0'
14 | VERSION = "1.0.1"
15 |
16 | REQUIRED = [
17 | "bgfx-python",
18 | "numpy",
19 | "python-decouple",
20 | ]
21 |
22 | here = os.path.abspath(os.path.dirname(__file__))
23 |
24 | try:
25 | with io.open(os.path.join(here, 'README.md'), encoding='utf-8') as f:
26 | long_description = '\n' + f.read()
27 | except FileNotFoundError:
28 | long_description = DESCRIPTION
29 |
30 | setup(
31 | name=NAME,
32 | version=VERSION,
33 | description=DESCRIPTION,
34 | long_description=long_description,
35 | long_description_content_type='text/markdown',
36 | author=AUTHOR,
37 | python_requires=REQUIRES_PYTHON,
38 | url=URL,
39 | packages=find_packages(exclude=('tests',)),
40 | install_requires=REQUIRED,
41 | include_package_data=True,
42 | license='MIT',
43 | classifiers=[
44 | 'License :: OSI Approved :: MIT License',
45 | 'Programming Language :: Python',
46 | 'Programming Language :: Python :: 3',
47 | 'Programming Language :: Python :: 3.6',
48 | 'Programming Language :: Python :: 3.7',
49 | 'Programming Language :: Python :: Implementation :: CPython',
50 | 'Programming Language :: Python :: Implementation :: PyPy'
51 | ],
52 | )
53 |
--------------------------------------------------------------------------------
/tests/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fbertola/Natrix/403fed55cab582563615c71ef11b40b11cdb7777/tests/__init__.py
--------------------------------------------------------------------------------