├── .github └── FUNDING.yml ├── .gitignore ├── LICENSE ├── README.md ├── img ├── fisher_iris_visualization.mp4 ├── metaballs.png ├── parametric_torus.png ├── phyllotaxis_flower.mp4 ├── rugged_donut.mp4 ├── simple_sphere.png ├── tetrahedron_fractal.png ├── torus_formula.png ├── vornoi_landscape.png └── voronoi_sphere.png ├── run_script.py └── scripts ├── data └── iris │ ├── Index.txt │ ├── iris.data │ └── iris.names ├── fisher_iris_visualization.py ├── metaballs.py ├── parametric_torus.py ├── phyllotaxis_flower.py ├── rugged_donut.py ├── simple_sphere.py ├── tetrahedron_fractal.py ├── utils └── __init__.py ├── voronoi_landscape.py └── voronoi_sphere.py /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: ["https://www.paypal.me/njanakiev"] 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | #frame files 2 | scripts/frames/ 3 | scripts/rendering/ 4 | 5 | # Byte-compiled / optimized / DLL files 6 | __pycache__/ 7 | *.py[cod] 8 | *$py.class 9 | 10 | # C extensions 11 | *.so 12 | 13 | # Distribution / packaging 14 | .Python 15 | env/ 16 | build/ 17 | develop-eggs/ 18 | dist/ 19 | downloads/ 20 | eggs/ 21 | .eggs/ 22 | lib/ 23 | lib64/ 24 | parts/ 25 | sdist/ 26 | var/ 27 | *.egg-info/ 28 | .installed.cfg 29 | *.egg 30 | 31 | # PyInstaller 32 | # Usually these files are written by a python script from a template 33 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 34 | *.manifest 35 | *.spec 36 | 37 | # Installer logs 38 | pip-log.txt 39 | pip-delete-this-directory.txt 40 | 41 | # Unit test / coverage reports 42 | htmlcov/ 43 | .tox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *,cover 50 | .hypothesis/ 51 | 52 | # Translations 53 | *.mo 54 | *.pot 55 | 56 | # Django stuff: 57 | *.log 58 | local_settings.py 59 | 60 | # Flask stuff: 61 | instance/ 62 | .webassets-cache 63 | 64 | # Scrapy stuff: 65 | .scrapy 66 | 67 | # Sphinx documentation 68 | docs/_build/ 69 | 70 | # PyBuilder 71 | target/ 72 | 73 | # IPython Notebook 74 | .ipynb_checkpoints 75 | 76 | # pyenv 77 | .python-version 78 | 79 | # celery beat schedule file 80 | celerybeat-schedule 81 | 82 | # dotenv 83 | .env 84 | 85 | # virtualenv 86 | venv/ 87 | ENV/ 88 | 89 | # Spyder project settings 90 | .spyderproject 91 | 92 | # Rope project settings 93 | .ropeproject 94 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Nikolai Janakiev 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # blender-scripting 2 | This is a collection of simple to more involved examples to scripting in [Blender](https://www.blender.org/) with Python. 3 | 4 | 5 | 6 | ## Table of Contents 7 | - [Requirements](#requirements) 8 | - [Resources](#resources) 9 | - [Utils](#utils) 10 | - [Simple Sphere](#simple-sphere) 11 | - [Parametric Torus](#parametric-torus) 12 | - [Metaballs](#metaballs) 13 | - [Voronoi Landscape](#voronoi-landscape) 14 | - [Tetrahedron Fractal](#tetrahedron-fractal) 15 | - [Phyllotaxis Flower](#phyllotaxis-flower) 16 | - [Rugged Donut](#rugged-donut) 17 | - [Fisher Iris Visualization](#fisher-iris-visualization) 18 | - [Voronoi Sphere](#voronoi-sphere) 19 | 20 | 21 | ## Requirements 22 | 23 | `Blender 2.8+` 24 | 25 | To run the examples, open your favorite console in the example folder. Make sure to edit in [run_script.py](run_script.py) the `scriptFile` variable to the Python script in the [scripts](scripts) folder you want to execute. 26 | 27 | ```bash 28 | blender -b -P run_script.py 29 | ``` 30 | 31 | Another option is to open the script in Blender and run [run_script.py](run_script.py) inside Blender, which is a nice way to test and tweak the files and to see and play with the generated result before rendering. 32 | 33 | To create videos from frames, you can use [ffmpeg](https://ffmpeg.org/) as follows: 34 | 35 | ```bash 36 | ffmpeg \ 37 | -r 15 \ # frame rate 38 | -i frames/phyllotaxis_flower%04d.png \ # input path 39 | -c:v libx264 \ # video codec (H.246) 40 | -c:a aac -ar 44100 \ # audio codec (AAC with 44100 Hz) 41 | -pix_fmt yuv420p \ # pixel format and color sampling 42 | phyllotaxis_flower.mp4 # output path 43 | ``` 44 | 45 | 46 | ## Resources 47 | 48 | - [Blender Cookbook](https://wiki.blender.org/index.php/Dev:Py/Scripts/Cookbook) 49 | - [Blender 3D Python Scripting](https://en.wikibooks.org/wiki/Blender_3D:_Noob_to_Pro/Advanced_Tutorials/Python_Scripting/Introduction) 50 | - [Blender Scripting Blog](http://blenderscripting.blogspot.co.at/) 51 | 52 | 53 | 54 | ## Utils 55 | 56 | [utils](scripts/utils/__init__.py) 57 | 58 | Some frequently used functions in blender, which will be used in most of the scripts. 59 | 60 | 61 | 62 | ## Simple Sphere 63 | 64 | [simple_sphere.py](scripts/simple_sphere.py) 65 | 66 | Simple rendering of a smooth sphere. First an icosphere is added with 67 | 68 | ```python 69 | import bpy 70 | bpy.ops.mesh.primitive_ico_sphere_add(location=(0, 0, 0)) 71 | obj = bpy.context.object 72 | ``` 73 | 74 | Then the subdivision surface modifier is added to the object to increase the resolution of the mesh and afterwards all the faces of the object are set to a smooth shading 75 | 76 | ```python 77 | modifier = obj.modifiers.new('Subsurf', 'SUBSURF') 78 | modifier.levels = 2 79 | modifier.render_levels = 2 80 | 81 | mesh = obj.data 82 | for p in mesh.polygons: 83 | p.use_smooth = True 84 | ``` 85 | 86 | Alternatively the icosphere can be subdivided with the `subdivisions` argument in the function 87 | 88 | ```python 89 | bpy.ops.mesh.primitive_ico_sphere_add(subdivisions=4, location=(0, 0, 0)) 90 | ``` 91 | 92 | ![Simple Sphere](/img/simple_sphere.png) 93 | 94 | 95 | 96 | ## Parametric Torus 97 | 98 | [parametric_torus.py](scripts/parametric_torus.py) 99 | 100 | Parametric generation of a torus. The [torus](https://en.wikipedia.org/wiki/Torus) is created with the following parameterization of a grid of the variables u, v 101 | 102 | ![Torus Formula](/img/torus_formula.png) 103 | 104 | where the values u, v are between 0 and 1 and are then mapped to x, y, z coordinates. In [parametric_torus.py](scripts/parametric_torus.py), the function `torusSurface(r0, r1)` returns the surface parameterization function for a torus which is then used in `createSurface(surface, n, m)` as the first argument, which creates the object from a n by m grid. The function `createSurface(surface, n, m)` can be also used for other parameterizations such as [surfaces of revolution](https://en.wikipedia.org/wiki/Surface_of_revolution) or other [parametric surfaces](https://en.wikipedia.org/wiki/Parametric_surface). 105 | 106 | ![Parametric Torus](/img/parametric_torus.png) 107 | 108 | 109 | 110 | ## Metaballs 111 | 112 | [metaballs.py](scripts/metaballs.py) 113 | 114 | Generate random metaballs in Blender inspired by this [tutorial](http://blenderscripting.blogspot.co.at/2012/09/tripping-metaballs-python.html). 115 | 116 | ![Metaballs](/img/metaballs.png) 117 | 118 | 119 | 120 | ## Voronoi Landscape 121 | 122 | [voronoi_landscape.py](scripts/voronoi_landscape.py) 123 | 124 | This is a more advanced example for using a [Voronoi diagram](https://en.wikipedia.org/wiki/Voronoi_diagram). The Voronoi diagram is implemented with the module `scipy.spatial` which can be added with [Scipy](https://www.scipy.org/), or can be found in the Python distribution [Anaconda](https://www.continuum.io/downloads). The steps to use Anaconda as the Interpreter in Blender 2.77 are shown in this [solution](http://til.janakiev.com/using-anaconda-in-blender/). 125 | 126 | ![Voronoi Landscape](/img/vornoi_landscape.png) 127 | 128 | 129 | 130 | ## Tetrahedron Fractal 131 | 132 | [tetrahedron_fractal.py](scripts/tetrahedron_fractal.py) 133 | 134 | This is an example for a fractal [tetrahedron](http://mathworld.wolfram.com/RegularTetrahedron.html), where each tetrahedron is subdivided into smaller pieces with a recursive function. In order to create a material for the tetrahedron the material is assigned as shown here: 135 | 136 | ```python 137 | color = (0.5, 0.5, 0.5) 138 | mat = bpy.data.materials.new('Material') 139 | 140 | # Diffuse 141 | mat.diffuse_shader = 'LAMBERT' 142 | mat.diffuse_intensity = 0.9 143 | mat.diffuse_color = color 144 | 145 | # Specular 146 | mat.specular_intensity = 0 147 | 148 | obj.data.materials.append(mat) 149 | ``` 150 | 151 | ![Tetrahedron Fractal](/img/tetrahedron_fractal.png) 152 | 153 | 154 | 155 | ## Phyllotaxis Flower 156 | 157 | [phyllotaxis_flower.py](scripts/phyllotaxis_flower.py) 158 | 159 | This script implements a [Phyllotaxis](https://en.wikipedia.org/wiki/Phyllotaxis) Flower which aranges leaves or the petals according to the [golden angle](https://en.wikipedia.org/wiki/Golden_angle). Additionally The flower is animated by appending an [application handler](https://docs.blender.org/api/blender_python_api_current/bpy.app.handlers.html) for frame change by 160 | 161 | ```python 162 | def handler(scene): 163 | frame = scene.frame_current 164 | # Create new geometry for new frame 165 | # ... 166 | 167 | # Append frame change handler on frame change for playback and rendering (before) 168 | bpy.app.handlers.frame_change_pre.append(handler) 169 | ``` 170 | 171 | In order to render all frames you can run 172 | 173 | ```python 174 | bpy.ops.render.render(animation=True) 175 | ``` 176 | 177 | 178 | The animation is inspired by the mesmerizing sculptures by [John Edmark](http://www.johnedmark.com/). 179 | 180 | ![Phyllotaxis Flower](/img/phyllotaxis_flower.mp4) 181 | 182 | 183 | 184 | ## Rugged Donut 185 | 186 | [rugged_donut.py](scripts/rugged_donut.py) 187 | 188 | This script implements a number of different things available in Blender. For one it applies a [Displace modifier](https://docs.blender.org/manual/de/dev/modeling/modifiers/deform/displace.html) to a torus which displaces the object with a texture as follows. 189 | 190 | ```python 191 | # Create musgrave texture 192 | texture = bpy.data.textures.new('Texture', 'MUSGRAVE') 193 | 194 | # Create displace modifier and apply texture 195 | displace = obj.modifiers.new('Displace', 'DISPLACE') 196 | displace.texture = texture 197 | ``` 198 | 199 | Further we can control the texture by an object such as an [Empty object](https://docs.blender.org/manual/ja/dev/modeling/empties.html) 200 | 201 | ```python 202 | # Create Empty to control texture coordinates 203 | empty = bpy.data.objects.new('Empty', None) 204 | bpy.context.scene.objects.link(empty) 205 | 206 | # Take the texture coordinates from empty’s coordinate system 207 | displace.texture_coords = 'OBJECT' 208 | displace.texture_coords_object = empty 209 | ``` 210 | 211 | Additionally we want to add a material with additional bump map to our torus object which is done in the following way. 212 | 213 | ```python 214 | # Create bump map texture 215 | bumptex = bpy.data.textures.new('BumpMapTexture', 'CLOUDS') 216 | 217 | # Create material 218 | mat = bpy.data.materials.new('BumpMapMaterial') 219 | 220 | # Add texture slot for material and add texture to this slot 221 | slot = mat.texture_slots.add() 222 | slot.texture = bumptex 223 | slot.texture_coords = 'GLOBAL' 224 | slot.use_map_color_diffuse = False 225 | slot.use_map_normal = True 226 | 227 | # Append material to object 228 | obj.data.materials.append(mat) 229 | ``` 230 | 231 | Now we want to animate the empty in order to animate the texture. We can achieve this by inserting keyframes for the location of our empty as shown in this quick [tutorial](blenderscripting.blogspot.co.at/2011/05/inspired-by-post-on-ba-it-just-so.html) and in the next snippet. 232 | 233 | ```python 234 | for frame in range(1, num_frames): 235 | t = frame / num_frames 236 | x = 0.7*cos(2*pi*t) 237 | y = 0.7*sin(2*pi*t) 238 | z = 0.4*sin(2*pi*t) 239 | empty.location = (x, y, z) 240 | empty.keyframe_insert(data_path="location", index=-1, frame=frame) 241 | ``` 242 | 243 | ![Rugged Donut](/img/rugged_donut.mp4) 244 | 245 | 246 | 247 | ## Fisher Iris Visualization 248 | 249 | [fisher_iris_visualization.py](scripts/fisher_iris_visualization.py) 250 | 251 | This script implements a visualization of the famous [Fisher's Iris data set](https://en.wikipedia.org/wiki/Iris_flower_data_set). The data set consists of 50 samples for each of three flower species of Iris setosa, Iris virginica and Iris versicolor. Each sample consists of four features (sepal length, sepal width, petal length and petal width). In order to visualize the data set in three dimensions we apply dimensionality reduction by using [Principal Component Analysis](https://en.wikipedia.org/wiki/Principal_component_analysis). The data set and PCA are both included in the [scikit-learn](http://scikit-learn.org/stable/) library for Python. This script works both with or without sklearn which is not part of the Blender Python distribution. You can use sklearn by using [Anaconda](https://anaconda.org/) in Blender which I show in this quick [tutorial](http://til.janakiev.com/using-anaconda-in-blender/). 252 | 253 | ```python 254 | from sklearn import datasets 255 | from sklearn import decomposition 256 | 257 | # Load Dataset 258 | iris = datasets.load_iris() 259 | X = iris.data 260 | y = iris.target 261 | labels = iris.target_names 262 | 263 | # Reduce components by Principal Component Analysis from sklearn 264 | X = decomposition.PCA(n_components=3).fit_transform(X) 265 | ``` 266 | The data set in [/scripts/data/iris/](/scripts/data/iris/) is downloaded from the [UCI Machine Learning Repository](https://archive.ics.uci.edu/ml/datasets/iris) and PCA is implemented manually with the help of the included [Numpy](http://www.numpy.org/) library. If sklearn is not in the current Python distribution the Iris data set is loaded as in the next code snippet. 267 | 268 | ```python 269 | path = os.path.join('data', 'iris', 'iris.data') 270 | iris_data = np.genfromtxt(path, dtype='str', delimiter=',') 271 | X = iris_data[:, :4].astype(dtype=float) 272 | y = np.ndarray((X.shape[0],), dtype=int) 273 | 274 | # Create target vector y and corresponding labels 275 | labels, idx = [], 0 276 | for i, label in enumerate(iris_data[:, 4]): 277 | if label not in labels: 278 | labels.append(label); idx += 1 279 | y[i] = idx - 1 280 | 281 | # Reduce components by implemented Principal Component Analysis 282 | X = PCA(X, 3)[0] 283 | ``` 284 | 285 | The data set is loaded into the scene as a 3D scatter plot with different shape primitives for each class of flower from the [BMesh Operators](https://docs.blender.org/api/blender_python_api_current/bmesh.ops.html). Additionally each collection of shapes in a class has different materials assigned to them. Each class has corresponding labels which are rotated toward the camera by a [Locked Track Constraint](https://docs.blender.org/manual/en/dev/rigging/constraints/tracking/locked_track.html). 286 | 287 | ![Fisher Iris Visualization](/img/fisher_iris_visualization.mp4) 288 | 289 | 290 | 291 | ## Voronoi Sphere 292 | 293 | [voronoi_sphere.py](scripts/voronoi_sphere.py) 294 | 295 | This is another example using the [Voronoi diagram](https://en.wikipedia.org/wiki/Voronoi_diagram), but this time in the 3rd dimension. It is implemented as well with the module `scipy.spatial` which can be added with [Scipy](https://www.scipy.org/) and it is even used in a similar way as the previous Voronoi example in 2D. 296 | 297 | ![Voronoi Sphere](/img/voronoi_sphere.png) 298 | -------------------------------------------------------------------------------- /img/fisher_iris_visualization.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/njanakiev/blender-scripting/477ba73c8578901066cc69be4e0d52fb31cf6530/img/fisher_iris_visualization.mp4 -------------------------------------------------------------------------------- /img/metaballs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/njanakiev/blender-scripting/477ba73c8578901066cc69be4e0d52fb31cf6530/img/metaballs.png -------------------------------------------------------------------------------- /img/parametric_torus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/njanakiev/blender-scripting/477ba73c8578901066cc69be4e0d52fb31cf6530/img/parametric_torus.png -------------------------------------------------------------------------------- /img/phyllotaxis_flower.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/njanakiev/blender-scripting/477ba73c8578901066cc69be4e0d52fb31cf6530/img/phyllotaxis_flower.mp4 -------------------------------------------------------------------------------- /img/rugged_donut.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/njanakiev/blender-scripting/477ba73c8578901066cc69be4e0d52fb31cf6530/img/rugged_donut.mp4 -------------------------------------------------------------------------------- /img/simple_sphere.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/njanakiev/blender-scripting/477ba73c8578901066cc69be4e0d52fb31cf6530/img/simple_sphere.png -------------------------------------------------------------------------------- /img/tetrahedron_fractal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/njanakiev/blender-scripting/477ba73c8578901066cc69be4e0d52fb31cf6530/img/tetrahedron_fractal.png -------------------------------------------------------------------------------- /img/torus_formula.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/njanakiev/blender-scripting/477ba73c8578901066cc69be4e0d52fb31cf6530/img/torus_formula.png -------------------------------------------------------------------------------- /img/vornoi_landscape.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/njanakiev/blender-scripting/477ba73c8578901066cc69be4e0d52fb31cf6530/img/vornoi_landscape.png -------------------------------------------------------------------------------- /img/voronoi_sphere.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/njanakiev/blender-scripting/477ba73c8578901066cc69be4e0d52fb31cf6530/img/voronoi_sphere.png -------------------------------------------------------------------------------- /run_script.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | import os 3 | import sys 4 | 5 | # Specify the script to be executed 6 | scriptFile = "fisher_iris_visualization.py" 7 | 8 | # Check if script is executed in Blender and get absolute path of current folder 9 | if bpy.context.space_data is not None: 10 | filesDir = os.path.dirname(bpy.context.space_data.text.filepath) 11 | else: 12 | filesDir = os.path.dirname(os.path.abspath(__file__)) 13 | 14 | # Get scripts folder and add it to the search path for modules 15 | cwd = os.path.join(filesDir, "scripts") 16 | sys.path.append(cwd) 17 | # Change current working directory to scripts folder 18 | os.chdir(cwd) 19 | 20 | # Compile and execute script file 21 | file = os.path.join(cwd, scriptFile) 22 | exec(compile(open(file).read(), scriptFile, 'exec')) 23 | -------------------------------------------------------------------------------- /scripts/data/iris/Index.txt: -------------------------------------------------------------------------------- 1 | Index of iris 2 | 3 | 02 Dec 1996 105 Index 4 | 08 Mar 1993 4551 iris.data 5 | 30 May 1989 2604 iris.names 6 | -------------------------------------------------------------------------------- /scripts/data/iris/iris.data: -------------------------------------------------------------------------------- 1 | 5.1,3.5,1.4,0.2,Iris-setosa 2 | 4.9,3.0,1.4,0.2,Iris-setosa 3 | 4.7,3.2,1.3,0.2,Iris-setosa 4 | 4.6,3.1,1.5,0.2,Iris-setosa 5 | 5.0,3.6,1.4,0.2,Iris-setosa 6 | 5.4,3.9,1.7,0.4,Iris-setosa 7 | 4.6,3.4,1.4,0.3,Iris-setosa 8 | 5.0,3.4,1.5,0.2,Iris-setosa 9 | 4.4,2.9,1.4,0.2,Iris-setosa 10 | 4.9,3.1,1.5,0.1,Iris-setosa 11 | 5.4,3.7,1.5,0.2,Iris-setosa 12 | 4.8,3.4,1.6,0.2,Iris-setosa 13 | 4.8,3.0,1.4,0.1,Iris-setosa 14 | 4.3,3.0,1.1,0.1,Iris-setosa 15 | 5.8,4.0,1.2,0.2,Iris-setosa 16 | 5.7,4.4,1.5,0.4,Iris-setosa 17 | 5.4,3.9,1.3,0.4,Iris-setosa 18 | 5.1,3.5,1.4,0.3,Iris-setosa 19 | 5.7,3.8,1.7,0.3,Iris-setosa 20 | 5.1,3.8,1.5,0.3,Iris-setosa 21 | 5.4,3.4,1.7,0.2,Iris-setosa 22 | 5.1,3.7,1.5,0.4,Iris-setosa 23 | 4.6,3.6,1.0,0.2,Iris-setosa 24 | 5.1,3.3,1.7,0.5,Iris-setosa 25 | 4.8,3.4,1.9,0.2,Iris-setosa 26 | 5.0,3.0,1.6,0.2,Iris-setosa 27 | 5.0,3.4,1.6,0.4,Iris-setosa 28 | 5.2,3.5,1.5,0.2,Iris-setosa 29 | 5.2,3.4,1.4,0.2,Iris-setosa 30 | 4.7,3.2,1.6,0.2,Iris-setosa 31 | 4.8,3.1,1.6,0.2,Iris-setosa 32 | 5.4,3.4,1.5,0.4,Iris-setosa 33 | 5.2,4.1,1.5,0.1,Iris-setosa 34 | 5.5,4.2,1.4,0.2,Iris-setosa 35 | 4.9,3.1,1.5,0.1,Iris-setosa 36 | 5.0,3.2,1.2,0.2,Iris-setosa 37 | 5.5,3.5,1.3,0.2,Iris-setosa 38 | 4.9,3.1,1.5,0.1,Iris-setosa 39 | 4.4,3.0,1.3,0.2,Iris-setosa 40 | 5.1,3.4,1.5,0.2,Iris-setosa 41 | 5.0,3.5,1.3,0.3,Iris-setosa 42 | 4.5,2.3,1.3,0.3,Iris-setosa 43 | 4.4,3.2,1.3,0.2,Iris-setosa 44 | 5.0,3.5,1.6,0.6,Iris-setosa 45 | 5.1,3.8,1.9,0.4,Iris-setosa 46 | 4.8,3.0,1.4,0.3,Iris-setosa 47 | 5.1,3.8,1.6,0.2,Iris-setosa 48 | 4.6,3.2,1.4,0.2,Iris-setosa 49 | 5.3,3.7,1.5,0.2,Iris-setosa 50 | 5.0,3.3,1.4,0.2,Iris-setosa 51 | 7.0,3.2,4.7,1.4,Iris-versicolor 52 | 6.4,3.2,4.5,1.5,Iris-versicolor 53 | 6.9,3.1,4.9,1.5,Iris-versicolor 54 | 5.5,2.3,4.0,1.3,Iris-versicolor 55 | 6.5,2.8,4.6,1.5,Iris-versicolor 56 | 5.7,2.8,4.5,1.3,Iris-versicolor 57 | 6.3,3.3,4.7,1.6,Iris-versicolor 58 | 4.9,2.4,3.3,1.0,Iris-versicolor 59 | 6.6,2.9,4.6,1.3,Iris-versicolor 60 | 5.2,2.7,3.9,1.4,Iris-versicolor 61 | 5.0,2.0,3.5,1.0,Iris-versicolor 62 | 5.9,3.0,4.2,1.5,Iris-versicolor 63 | 6.0,2.2,4.0,1.0,Iris-versicolor 64 | 6.1,2.9,4.7,1.4,Iris-versicolor 65 | 5.6,2.9,3.6,1.3,Iris-versicolor 66 | 6.7,3.1,4.4,1.4,Iris-versicolor 67 | 5.6,3.0,4.5,1.5,Iris-versicolor 68 | 5.8,2.7,4.1,1.0,Iris-versicolor 69 | 6.2,2.2,4.5,1.5,Iris-versicolor 70 | 5.6,2.5,3.9,1.1,Iris-versicolor 71 | 5.9,3.2,4.8,1.8,Iris-versicolor 72 | 6.1,2.8,4.0,1.3,Iris-versicolor 73 | 6.3,2.5,4.9,1.5,Iris-versicolor 74 | 6.1,2.8,4.7,1.2,Iris-versicolor 75 | 6.4,2.9,4.3,1.3,Iris-versicolor 76 | 6.6,3.0,4.4,1.4,Iris-versicolor 77 | 6.8,2.8,4.8,1.4,Iris-versicolor 78 | 6.7,3.0,5.0,1.7,Iris-versicolor 79 | 6.0,2.9,4.5,1.5,Iris-versicolor 80 | 5.7,2.6,3.5,1.0,Iris-versicolor 81 | 5.5,2.4,3.8,1.1,Iris-versicolor 82 | 5.5,2.4,3.7,1.0,Iris-versicolor 83 | 5.8,2.7,3.9,1.2,Iris-versicolor 84 | 6.0,2.7,5.1,1.6,Iris-versicolor 85 | 5.4,3.0,4.5,1.5,Iris-versicolor 86 | 6.0,3.4,4.5,1.6,Iris-versicolor 87 | 6.7,3.1,4.7,1.5,Iris-versicolor 88 | 6.3,2.3,4.4,1.3,Iris-versicolor 89 | 5.6,3.0,4.1,1.3,Iris-versicolor 90 | 5.5,2.5,4.0,1.3,Iris-versicolor 91 | 5.5,2.6,4.4,1.2,Iris-versicolor 92 | 6.1,3.0,4.6,1.4,Iris-versicolor 93 | 5.8,2.6,4.0,1.2,Iris-versicolor 94 | 5.0,2.3,3.3,1.0,Iris-versicolor 95 | 5.6,2.7,4.2,1.3,Iris-versicolor 96 | 5.7,3.0,4.2,1.2,Iris-versicolor 97 | 5.7,2.9,4.2,1.3,Iris-versicolor 98 | 6.2,2.9,4.3,1.3,Iris-versicolor 99 | 5.1,2.5,3.0,1.1,Iris-versicolor 100 | 5.7,2.8,4.1,1.3,Iris-versicolor 101 | 6.3,3.3,6.0,2.5,Iris-virginica 102 | 5.8,2.7,5.1,1.9,Iris-virginica 103 | 7.1,3.0,5.9,2.1,Iris-virginica 104 | 6.3,2.9,5.6,1.8,Iris-virginica 105 | 6.5,3.0,5.8,2.2,Iris-virginica 106 | 7.6,3.0,6.6,2.1,Iris-virginica 107 | 4.9,2.5,4.5,1.7,Iris-virginica 108 | 7.3,2.9,6.3,1.8,Iris-virginica 109 | 6.7,2.5,5.8,1.8,Iris-virginica 110 | 7.2,3.6,6.1,2.5,Iris-virginica 111 | 6.5,3.2,5.1,2.0,Iris-virginica 112 | 6.4,2.7,5.3,1.9,Iris-virginica 113 | 6.8,3.0,5.5,2.1,Iris-virginica 114 | 5.7,2.5,5.0,2.0,Iris-virginica 115 | 5.8,2.8,5.1,2.4,Iris-virginica 116 | 6.4,3.2,5.3,2.3,Iris-virginica 117 | 6.5,3.0,5.5,1.8,Iris-virginica 118 | 7.7,3.8,6.7,2.2,Iris-virginica 119 | 7.7,2.6,6.9,2.3,Iris-virginica 120 | 6.0,2.2,5.0,1.5,Iris-virginica 121 | 6.9,3.2,5.7,2.3,Iris-virginica 122 | 5.6,2.8,4.9,2.0,Iris-virginica 123 | 7.7,2.8,6.7,2.0,Iris-virginica 124 | 6.3,2.7,4.9,1.8,Iris-virginica 125 | 6.7,3.3,5.7,2.1,Iris-virginica 126 | 7.2,3.2,6.0,1.8,Iris-virginica 127 | 6.2,2.8,4.8,1.8,Iris-virginica 128 | 6.1,3.0,4.9,1.8,Iris-virginica 129 | 6.4,2.8,5.6,2.1,Iris-virginica 130 | 7.2,3.0,5.8,1.6,Iris-virginica 131 | 7.4,2.8,6.1,1.9,Iris-virginica 132 | 7.9,3.8,6.4,2.0,Iris-virginica 133 | 6.4,2.8,5.6,2.2,Iris-virginica 134 | 6.3,2.8,5.1,1.5,Iris-virginica 135 | 6.1,2.6,5.6,1.4,Iris-virginica 136 | 7.7,3.0,6.1,2.3,Iris-virginica 137 | 6.3,3.4,5.6,2.4,Iris-virginica 138 | 6.4,3.1,5.5,1.8,Iris-virginica 139 | 6.0,3.0,4.8,1.8,Iris-virginica 140 | 6.9,3.1,5.4,2.1,Iris-virginica 141 | 6.7,3.1,5.6,2.4,Iris-virginica 142 | 6.9,3.1,5.1,2.3,Iris-virginica 143 | 5.8,2.7,5.1,1.9,Iris-virginica 144 | 6.8,3.2,5.9,2.3,Iris-virginica 145 | 6.7,3.3,5.7,2.5,Iris-virginica 146 | 6.7,3.0,5.2,2.3,Iris-virginica 147 | 6.3,2.5,5.0,1.9,Iris-virginica 148 | 6.5,3.0,5.2,2.0,Iris-virginica 149 | 6.2,3.4,5.4,2.3,Iris-virginica 150 | 5.9,3.0,5.1,1.8,Iris-virginica 151 | 152 | -------------------------------------------------------------------------------- /scripts/data/iris/iris.names: -------------------------------------------------------------------------------- 1 | 1. Title: Iris Plants Database 2 | Updated Sept 21 by C.Blake - Added discrepency information 3 | 4 | 2. Sources: 5 | (a) Creator: R.A. Fisher 6 | (b) Donor: Michael Marshall (MARSHALL%PLU@io.arc.nasa.gov) 7 | (c) Date: July, 1988 8 | 9 | 3. Past Usage: 10 | - Publications: too many to mention!!! Here are a few. 11 | 1. Fisher,R.A. "The use of multiple measurements in taxonomic problems" 12 | Annual Eugenics, 7, Part II, 179-188 (1936); also in "Contributions 13 | to Mathematical Statistics" (John Wiley, NY, 1950). 14 | 2. Duda,R.O., & Hart,P.E. (1973) Pattern Classification and Scene Analysis. 15 | (Q327.D83) John Wiley & Sons. ISBN 0-471-22361-1. See page 218. 16 | 3. Dasarathy, B.V. (1980) "Nosing Around the Neighborhood: A New System 17 | Structure and Classification Rule for Recognition in Partially Exposed 18 | Environments". IEEE Transactions on Pattern Analysis and Machine 19 | Intelligence, Vol. PAMI-2, No. 1, 67-71. 20 | -- Results: 21 | -- very low misclassification rates (0% for the setosa class) 22 | 4. Gates, G.W. (1972) "The Reduced Nearest Neighbor Rule". IEEE 23 | Transactions on Information Theory, May 1972, 431-433. 24 | -- Results: 25 | -- very low misclassification rates again 26 | 5. See also: 1988 MLC Proceedings, 54-64. Cheeseman et al's AUTOCLASS II 27 | conceptual clustering system finds 3 classes in the data. 28 | 29 | 4. Relevant Information: 30 | --- This is perhaps the best known database to be found in the pattern 31 | recognition literature. Fisher's paper is a classic in the field 32 | and is referenced frequently to this day. (See Duda & Hart, for 33 | example.) The data set contains 3 classes of 50 instances each, 34 | where each class refers to a type of iris plant. One class is 35 | linearly separable from the other 2; the latter are NOT linearly 36 | separable from each other. 37 | --- Predicted attribute: class of iris plant. 38 | --- This is an exceedingly simple domain. 39 | --- This data differs from the data presented in Fishers article 40 | (identified by Steve Chadwick, spchadwick@espeedaz.net ) 41 | The 35th sample should be: 4.9,3.1,1.5,0.2,"Iris-setosa" 42 | where the error is in the fourth feature. 43 | The 38th sample: 4.9,3.6,1.4,0.1,"Iris-setosa" 44 | where the errors are in the second and third features. 45 | 46 | 5. Number of Instances: 150 (50 in each of three classes) 47 | 48 | 6. Number of Attributes: 4 numeric, predictive attributes and the class 49 | 50 | 7. Attribute Information: 51 | 1. sepal length in cm 52 | 2. sepal width in cm 53 | 3. petal length in cm 54 | 4. petal width in cm 55 | 5. class: 56 | -- Iris Setosa 57 | -- Iris Versicolour 58 | -- Iris Virginica 59 | 60 | 8. Missing Attribute Values: None 61 | 62 | Summary Statistics: 63 | Min Max Mean SD Class Correlation 64 | sepal length: 4.3 7.9 5.84 0.83 0.7826 65 | sepal width: 2.0 4.4 3.05 0.43 -0.4194 66 | petal length: 1.0 6.9 3.76 1.76 0.9490 (high!) 67 | petal width: 0.1 2.5 1.20 0.76 0.9565 (high!) 68 | 69 | 9. Class Distribution: 33.3% for each of 3 classes. 70 | -------------------------------------------------------------------------------- /scripts/fisher_iris_visualization.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | import bmesh 3 | import numpy as np 4 | import utils 5 | from mathutils import Vector, Matrix 6 | from math import pi 7 | import os 8 | 9 | def PCA(data, num_components=None): 10 | # mean center the data 11 | data -= data.mean(axis=0) 12 | # calculate the covariance matrix 13 | R = np.cov(data, rowvar=False) 14 | # calculate eigenvectors & eigenvalues of the covariance matrix 15 | # use 'eigh' rather than 'eig' since R is symmetric, 16 | # the performance gain is substantial 17 | V, E = np.linalg.eigh(R) 18 | # sort eigenvalue in decreasing order 19 | idx = np.argsort(V)[::-1] 20 | E = E[:,idx] 21 | # sort eigenvectors according to same index 22 | V = V[idx] 23 | # select the first n eigenvectors (n is desired dimension 24 | # of rescaled data array, or dims_rescaled_data) 25 | E = E[:, :num_components] 26 | # carry out the transformation on the data using eigenvectors 27 | # and return the re-scaled data, eigenvalues, and eigenvectors 28 | return np.dot(E.T, data.T).T, V, E 29 | 30 | 31 | def load_iris(): 32 | try: 33 | # Load Iris dataset from the sklearn.datasets package 34 | from sklearn import datasets 35 | from sklearn import decomposition 36 | 37 | # Load Dataset 38 | iris = datasets.load_iris() 39 | X = iris.data 40 | y = iris.target 41 | labels = iris.target_names 42 | 43 | # Reduce components by Principal Component Analysis from sklearn 44 | X = decomposition.PCA(n_components=3).fit_transform(X) 45 | except ImportError: 46 | # Load Iris dataset manually 47 | path = os.path.join('data', 'iris', 'iris.data') 48 | iris_data = np.genfromtxt(path, dtype='str', delimiter=',') 49 | X = iris_data[:, :4].astype(dtype=float) 50 | y = np.ndarray((X.shape[0],), dtype=int) 51 | 52 | # Create target vector y and corresponding labels 53 | labels, idx = [], 0 54 | for i, label in enumerate(iris_data[:, 4]): 55 | label = label.split('-')[1] 56 | if label not in labels: 57 | labels.append(label); idx += 1 58 | y[i] = idx - 1 59 | 60 | # Reduce components by implemented Principal Component Analysis 61 | X = PCA(X, 3)[0] 62 | 63 | return X, y, labels 64 | 65 | 66 | def create_scatter(X, y, size=0.25): 67 | labelIndices = set(y) 68 | colors = [(1, 0, 0, 1), (0, 1, 0, 1), (0, 0, 1, 1), \ 69 | (1, 1, 0, 1), (1, 0, 1, 1), (0, 1, 1, 1)] 70 | 71 | # Create a bmesh for each label 72 | bmList = [] 73 | for labelIdx in labelIndices: 74 | bmList.append(bmesh.new()) 75 | 76 | # Iterate through all the vectors and targets 77 | for x, labelIdx in zip(X, y): 78 | # Use the vector as translation for each point 79 | T = Matrix.Translation(x) 80 | 81 | if labelIdx % 3 == 0: 82 | bmesh.ops.create_cube(bmList[labelIdx], 83 | size=size, matrix=T) 84 | elif labelIdx % 3 == 1: 85 | bmesh.ops.create_icosphere(bmList[labelIdx], 86 | diameter=size/2, matrix=T) 87 | else: 88 | bmesh.ops.create_cone(bmList[labelIdx], 89 | segments=6, cap_ends=True, 90 | diameter1=size/2, diameter2=0, 91 | depth=size, matrix=T) 92 | 93 | objects = [] 94 | for labelIdx, color in zip(labelIndices, colors): 95 | # Create a mesh from the existing bmesh 96 | mesh = bpy.data.meshes.new('ScatterMesh {}'.format(labelIdx)) 97 | bmList[labelIdx].to_mesh(mesh) 98 | bmList[labelIdx].free() 99 | 100 | # Create a object with the mesh and link it to the scene 101 | obj = bpy.data.objects.new('ScatterObject {}'.format(labelIdx), mesh) 102 | bpy.context.collection.objects.link(obj) 103 | 104 | # Create materials for each bmesh 105 | mat = bpy.data.materials.new('ScatterMaterial {}'.format(labelIdx)) 106 | mat.diffuse_color = color 107 | # mat.diffuse_intensity = 0.5 108 | mat.specular_intensity = 0.0 109 | obj.data.materials.append(mat) 110 | 111 | objects.append(obj) 112 | 113 | return objects 114 | 115 | 116 | def create_labels(X, y, labels, camera=None): 117 | label_indices = set(y) 118 | objects = [] 119 | 120 | # Draw labels 121 | for label_idx in label_indices: 122 | center = np.sum([x for x, idx in zip(X, y) \ 123 | if idx == label_idx], axis=0) 124 | counts = (y == label_idx).sum() 125 | center = Vector(center) / counts 126 | 127 | label = labels[label_idx] 128 | font_curve = bpy.data.curves.new(type="FONT", name=label) 129 | font_curve.body = label 130 | font_curve.align_x = 'CENTER' 131 | font_curve.align_y = 'BOTTOM' 132 | font_curve.size = 0.6 133 | 134 | obj = bpy.data.objects.new("Label {}".format(label), font_curve) 135 | obj.location = center + Vector((0, 0, 0.8)) 136 | obj.rotation_mode = 'AXIS_ANGLE' 137 | obj.rotation_axis_angle = (pi/2, 1, 0, 0) 138 | bpy.context.collection.objects.link(obj) 139 | 140 | if camera is not None: 141 | constraint = obj.constraints.new('LOCKED_TRACK') 142 | constraint.target = camera 143 | constraint.track_axis = 'TRACK_Z' 144 | constraint.lock_axis = 'LOCK_Y' 145 | 146 | objects.append(obj) 147 | bpy.context.scene.collection.objects.link(obj) 148 | 149 | return objects 150 | 151 | 152 | if __name__ == '__main__': 153 | # Remove all elements 154 | utils.remove_all() 155 | 156 | # Create camera and lamp 157 | target, camera, light = utils.simple_scene( 158 | (0, 0, 0), (6, 6, 3.5), (-5, 5, 10)) 159 | 160 | # Make target as parent of camera 161 | camera.parent = target 162 | 163 | # Set number of frames 164 | bpy.context.scene.frame_end = 50 165 | 166 | # Animate rotation of target by keyframe animation 167 | target.rotation_mode = 'AXIS_ANGLE' 168 | target.rotation_axis_angle = (0, 0, 0, 1) 169 | target.keyframe_insert(data_path='rotation_axis_angle', index=-1, 170 | frame=bpy.context.scene.frame_start) 171 | target.rotation_axis_angle = (2*pi, 0, 0, 1) 172 | # Set last frame to one frame further to have an animation loop 173 | target.keyframe_insert(data_path='rotation_axis_angle', index=-1, 174 | frame=bpy.context.scene.frame_end + 1) 175 | 176 | # Change each created keyframe point to linear interpolation 177 | for fcurve in target.animation_data.action.fcurves: 178 | for keyframe in fcurve.keyframe_points: 179 | keyframe.interpolation = 'LINEAR' 180 | 181 | X, y, labels = load_iris() 182 | create_scatter(X, y) 183 | label_objects = create_labels(X, y, labels, camera) 184 | 185 | # Create a grid 186 | bpy.ops.mesh.primitive_grid_add( 187 | size=5, 188 | location=(0, 0, 0), 189 | x_subdivisions=15, 190 | y_subdivisions=15) 191 | grid_obj = bpy.context.active_object 192 | 193 | # Add wireframe modifier 194 | modifier = grid_obj.modifiers.new("Wireframe", "WIREFRAME") 195 | modifier.thickness = 0.05 196 | 197 | # Create grid material 198 | mat = utils.create_material() 199 | grid_obj.data.materials.append(mat) 200 | 201 | utils.render( 202 | 'frames', 'fisher_iris_visualization', 512, 512, 203 | render_engine='BLENDER_EEVEE', animation=True) 204 | -------------------------------------------------------------------------------- /scripts/metaballs.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | import random 3 | from mathutils import Vector 4 | import utils 5 | 6 | 7 | def createMetaball(origin=(0, 0, 0), n=30, r0=4, r1=2.5): 8 | metaball = bpy.data.metaballs.new('MetaBall') 9 | obj = bpy.data.objects.new('MetaBallObject', metaball) 10 | bpy.context.collection.objects.link(obj) 11 | 12 | metaball.resolution = 0.2 13 | metaball.render_resolution = 0.05 14 | 15 | for i in range(n): 16 | location = Vector(origin) + Vector(random.uniform(-r0, r0) for i in range(3)) 17 | 18 | element = metaball.elements.new() 19 | element.co = location 20 | element.radius = r1 21 | 22 | return obj 23 | 24 | 25 | if __name__ == '__main__': 26 | # Remove all elements 27 | utils.remove_all() 28 | 29 | # Create camera 30 | target = utils.create_target() 31 | camera = utils.create_camera((-10, -10, 10), target) 32 | 33 | # Create lights 34 | utils.rainbowLights(10, 100, 3, energy=100) 35 | 36 | # Create metaball 37 | obj = createMetaball() 38 | 39 | # Create material 40 | mat = utils.create_material(metalic=0.5) 41 | obj.data.materials.append(mat) 42 | 43 | # Render scene 44 | utils.render('rendering', 'metaballs', 512, 512) 45 | -------------------------------------------------------------------------------- /scripts/parametric_torus.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | from math import sin, cos, pi 3 | TAU = 2*pi 4 | import utils 5 | 6 | 7 | # Create a function for the u, v surface parameterization from r0 and r1 8 | def torus_surface(r0, r1): 9 | def surface(u, v): 10 | point = ((r0 + r1*cos(TAU*v))*cos(TAU*u), \ 11 | (r0 + r1*cos(TAU*v))*sin(TAU*u), \ 12 | r1*sin(TAU*v)) 13 | return point 14 | return surface 15 | 16 | 17 | # Create an object from a surface parameterization 18 | def create_surface(surface, n=10, m=10, origin=(0,0,0), name='Surface'): 19 | verts = list() 20 | faces = list() 21 | 22 | # Create uniform n by m grid 23 | for col in range(m): 24 | for row in range(n): 25 | u = row / n 26 | v = col / m 27 | 28 | # Surface parameterization 29 | point = surface(u, v) 30 | verts.append(point) 31 | 32 | # Connect first and last vertices on the u and v axis 33 | row_next = (row + 1) % n 34 | col_next = (col + 1) % m 35 | # Indices for each qued 36 | faces.append(( 37 | (col*n) + row_next, 38 | (col_next*n) + row_next, 39 | (col_next*n) + row, 40 | (col*n) + row 41 | )) 42 | 43 | print('verts : ' + str(len(verts))) 44 | print('faces : ' + str(len(faces))) 45 | 46 | # Create mesh and object 47 | mesh = bpy.data.meshes.new(name+'Mesh') 48 | obj = bpy.data.objects.new(name, mesh) 49 | obj.location = origin 50 | # Link object to scene 51 | bpy.context.collection.objects.link(obj) 52 | # Create mesh from given verts and faces 53 | mesh.from_pydata(verts, [], faces) 54 | #Update mesh with new data 55 | mesh.update(calc_edges=True) 56 | return obj 57 | 58 | 59 | if __name__ == '__main__': 60 | # Remove all elements 61 | utils.remove_all() 62 | 63 | # Create camera 64 | target = utils.create_target() 65 | camera = utils.create_camera((-12, -12, 12), target, lens=50) 66 | 67 | # Set cursor to (0, 0, 0) 68 | bpy.context.scene.cursor.location = (0, 0, 0) 69 | 70 | # Create lamps 71 | utils.rainbowLights(10, 100, 3, energy=300) 72 | 73 | # Create object 74 | obj = create_surface(torus_surface(4, 2), 20, 20) 75 | 76 | # Create material 77 | mat = utils.create_material(metalic=0.5) 78 | obj.data.materials.append(mat) 79 | 80 | # Smooth surface and add subsurf modifier 81 | utils.set_smooth(obj, 2) 82 | 83 | # Render scene 84 | utils.render( 85 | 'rendering', 'parametric_torus', 500, 500, 86 | render_engine='CYCLES') 87 | -------------------------------------------------------------------------------- /scripts/phyllotaxis_flower.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | import bmesh 3 | import numpy as np 4 | from mathutils import Vector, Matrix 5 | from math import sqrt, pi, sin, cos 6 | import utils 7 | 8 | TAU = 2*pi 9 | # https://en.wikipedia.org/wiki/Golden_angle 10 | GOLDEN_ANGLE = pi*(3 - sqrt(5)) 11 | 12 | 13 | # Get a frame of a vector (tangent, normal and binormal vectors) 14 | # https://en.wikipedia.org/wiki/Frenet%E2%80%93Serret_formulas 15 | def getTNBfromVector(v): 16 | v = Vector(v) 17 | N = v.normalized() 18 | B = N.cross((0, 0, -1)) 19 | if(B.length == 0): 20 | B, T = Vector((1, 0, 0)), Vector((0, 1, 0)) 21 | else: 22 | B.normalize() 23 | T = N.cross(B).normalized() 24 | 25 | return T, N, B 26 | 27 | 28 | class PhyllotaxisFlower(): 29 | def __init__(self, scene): 30 | self.n, self.m = 40, 30 31 | self.r0, self.r1, self.r2 = 10, 2, 2 32 | self.h0, self.h1 = 10, 3 33 | self.frames = scene.frame_end - scene.frame_start + 1 34 | 35 | # Calculate and compensate for angle offset for infinite animation 36 | self.offset = (self.frames * GOLDEN_ANGLE) % TAU 37 | if self.offset > pi: self.offset -= TAU 38 | 39 | # Create object 40 | mesh = bpy.data.meshes.new('PhyllotaxisFlower') 41 | self.obj = bpy.data.objects.new('PhyllotaxisFlower', mesh) 42 | 43 | # Create mesh 44 | bm = self.geometry() 45 | bm.to_mesh(mesh) 46 | mesh.update() 47 | bm.free() 48 | 49 | # Link object to scene 50 | scene.collection.objects.link(self.obj) 51 | 52 | # Append new frame change handler to redraw geometry for each frame 53 | bpy.app.handlers.frame_change_pre.append(self.__frame_change_handler) 54 | 55 | 56 | def __frame_change_handler(self, scene, value): 57 | frame = scene.frame_current 58 | # Constrain to frame range 59 | if(frame < 1): frame = 1 60 | if(frame >= self.frames): frame = self.frames + 1 61 | 62 | mesh = self.obj.data 63 | bm = self.geometry(frame - 1) 64 | bm.to_mesh(mesh) 65 | mesh.update() 66 | bm.free() 67 | 68 | 69 | def geometry(self, frame=0): 70 | t = frame / self.frames 71 | Rot = Matrix.Rotation(0.5*pi, 4, 'Y') 72 | bm = bmesh.new() 73 | 74 | for i in range(self.n): 75 | t0 = i / self.n 76 | r0, theta = t0*self.r0, i*GOLDEN_ANGLE - frame*GOLDEN_ANGLE + t*self.offset 77 | 78 | x = r0*cos(theta) 79 | y = r0*sin(theta) 80 | z = self.h0/2 - (self.h0 / (self.r0*self.r0))*r0*r0 81 | p0 = Vector((x, y, z)) 82 | 83 | T0, N0, B0 = getTNBfromVector(p0) 84 | M0 = Matrix([T0, B0, N0]).to_4x4().transposed() 85 | 86 | for j in range(self.m): 87 | t1 = j / self.m 88 | t2 = 0.4 + 0.6*t0 89 | r1, theta = t2*t1*self.r1, j*GOLDEN_ANGLE #- frame*goldenAngle + t*self.offset 90 | 91 | x = r1*cos(theta) 92 | y = r1*sin(theta) 93 | z = self.h1 - (self.h1 / (self.r1*self.r1))*r1*r1 94 | p1 = Vector((x, y, z)) 95 | T1, N1, B1 = getTNBfromVector(p1) 96 | M1 = Matrix([T1, B1, N1]).to_4x4().transposed() 97 | 98 | p = p0 + (M0 @ p1) 99 | r2 = t2*t1*self.r2 100 | 101 | T = Matrix.Translation(p) 102 | bmesh.ops.create_cone(bm, 103 | cap_ends=True, segments=6, 104 | diameter1=r2, diameter2=r2, 105 | depth=0.1*r2, matrix=T @ M0 @ M1 @ Rot) 106 | return bm 107 | 108 | 109 | if __name__ == '__main__': 110 | # Remove all elements 111 | bpy.ops.object.select_all(action="SELECT") 112 | bpy.ops.object.delete(use_global=False) 113 | 114 | # Creata phyllotaxis flower 115 | flower = PhyllotaxisFlower(bpy.context.scene) 116 | 117 | # Create camera and lamp 118 | target = utils.create_target((0, 0, -1.5)) 119 | camera = utils.create_camera((-21.0, -21.0, 12.5), target, 35) 120 | sun = utils.create_light((-5, 5, 10), 'SUN', target=target) 121 | 122 | # Select colors 123 | palette = [(3,101,100), (205,179,128)] 124 | # Convert color and apply gamma correction 125 | palette = [tuple(pow(float(c)/255, 2.2) for c in color) 126 | for color in palette] 127 | 128 | # Smooth surface and add subsurf modifier 129 | utils.set_smooth(flower.obj, 2) 130 | 131 | # Set background color of scene 132 | bpy.context.scene.world.use_nodes = False 133 | bpy.context.scene.world.color = palette[0] 134 | 135 | # Set material for object 136 | mat = utils.create_material(palette[1], roughness=0.8) 137 | flower.obj.data.materials.append(mat) 138 | 139 | # Render scene 140 | utils.render( 141 | 'frames_02', 'phyllotaxis_flower', 512, 512, 142 | render_engine='CYCLES', 143 | animation=True, 144 | frame_end=50) 145 | -------------------------------------------------------------------------------- /scripts/rugged_donut.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | from math import pi, sin, cos 3 | import utils 4 | import os 5 | 6 | 7 | if __name__ == '__main__': 8 | # Remove all elements 9 | utils.remove_all() 10 | 11 | # Create camera and lamp 12 | utils.simple_scene((0, 0, -0.3), (4.2, 4.2, 5), (-5, 5, 10)) 13 | 14 | # Set number of frames 15 | num_frames = 100 16 | 17 | # Create Torus 18 | bpy.ops.mesh.primitive_torus_add( 19 | location=(0, 0, 0), 20 | major_segments=120, minor_segments=50, 21 | major_radius=1.8, minor_radius=0.5) 22 | obj = bpy.context.active_object 23 | bpy.ops.object.shade_smooth() 24 | 25 | # Create Empty to control texture coordinates 26 | empty = bpy.data.objects.new('Empty', None) 27 | bpy.context.scene.collection.objects.link(empty) 28 | 29 | # Animate empty with keyframe animation 30 | # keyframe approach adapted from: http://blenderscripting.blogspot.co.at/2011/05/inspired-by-post-on-ba-it-just-so.html 31 | for frame in range(1, num_frames): 32 | t = frame / num_frames 33 | x = 0.7*cos(2*pi*t) + 1 34 | y = 0.7*sin(2*pi*t) 35 | z = 0.4*sin(2*pi*t) 36 | empty.location = (x, y, z) 37 | empty.keyframe_insert(data_path="location", index=-1, frame=frame) 38 | 39 | # Change each created keyframe point to linear interpolation 40 | #for fcurve in empty.animation_data.action.fcurves: 41 | # for keyframe in fcurve.keyframe_points: 42 | # keyframe.interpolation = 'LINEAR' 43 | 44 | # Apply subsurf modifier 45 | subsurf = obj.modifiers.new('Subsurf', 'SUBSURF') 46 | subsurf.levels = 2 47 | subsurf.render_levels = 3 48 | 49 | # Create marble texture for the displace modifier 50 | displace_texture = bpy.data.textures.new('Texture', 'MUSGRAVE') 51 | displace_texture.musgrave_type = 'RIDGED_MULTIFRACTAL' 52 | displace_texture.octaves = 1.5 53 | displace_texture.noise_scale = 1.1 54 | 55 | # Create and apply displace modifier 56 | displace = obj.modifiers.new('Displace', 'DISPLACE') 57 | displace.texture = displace_texture 58 | displace.texture_coords = 'OBJECT' 59 | displace.texture_coords_object = empty 60 | displace.mid_level = 0 61 | displace.strength = 0.6 62 | 63 | # Use some colors 64 | palette = [(131,175,155,255), (250,105,10,255)] 65 | # Convert color and apply gamma correction 66 | palette = [tuple(pow(float(c)/255.0, 2.2) for c in color) 67 | for color in palette] 68 | 69 | # Set background color of scene 70 | bpy.context.scene.world.use_nodes = False 71 | bpy.context.scene.world.color = palette[0][:3] 72 | #bpy.context.scene.world.node_tree.nodes["Background"] \ 73 | # .inputs[0].default_value = palette[0] 74 | 75 | # Create material for the object 76 | mat = bpy.data.materials.new('BumpMapMaterial') 77 | mat.use_nodes = True 78 | material_node = mat.node_tree.nodes[0] 79 | material_node.inputs['Base Color'].default_value = palette[1] 80 | material_node.inputs['Metallic'].default_value = 0.0 81 | material_node.inputs['Roughness'].default_value = 0.5 82 | material_node.inputs['Specular'].default_value = 0.5 83 | 84 | # Add bump texture 85 | bump_texture_node = mat.node_tree.nodes.new('ShaderNodeTexNoise') 86 | bump_texture_node.noise_dimensions = '3D' 87 | bump_texture_node.inputs['Scale'].default_value = 20.0 88 | bump_texture_node.inputs['Detail'].default_value = 4.0 89 | bump_texture_node.inputs['Roughness'].default_value = 5.0 90 | 91 | # Add bump node 92 | bump_node = mat.node_tree.nodes.new('ShaderNodeBump') 93 | bump_node.inputs['Strength'].default_value = 0.4 94 | 95 | # Link nodes 96 | mat.node_tree.links.new( 97 | bump_node.inputs['Height'], bump_texture_node.outputs[0]) 98 | mat.node_tree.links.new( 99 | material_node.inputs['Normal'], bump_node.outputs[0]) 100 | 101 | # Append material to object 102 | obj.data.materials.append(mat) 103 | 104 | # Render scene 105 | utils.render('frames_01', 'rugged_donut', 512, 512, 106 | animation=True, 107 | render_engine='BLENDER_EEVEE', 108 | frame_end=num_frames) 109 | -------------------------------------------------------------------------------- /scripts/simple_sphere.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | import colorsys 3 | from math import sin, cos, pi 4 | from mathutils import Euler 5 | TAU = 2*pi 6 | 7 | 8 | def rainbow_lights(r=5, n=100, freq=2, energy=100): 9 | for i in range(n): 10 | t = float(i)/float(n) 11 | pos = (r*sin(TAU*t), r*cos(TAU*t), r*sin(freq*TAU*t)) 12 | 13 | # Create lamp 14 | bpy.ops.object.add(type='LIGHT', location=pos) 15 | obj = bpy.context.object 16 | obj.data.type = 'POINT' 17 | 18 | # Apply gamma correction for Blender 19 | color = tuple(pow(c, 2.2) for c in colorsys.hsv_to_rgb(t, 0.6, 1)) 20 | 21 | # Set HSV color and lamp energy 22 | obj.data.color = color 23 | obj.data.energy = energy 24 | 25 | 26 | if __name__ == '__main__': 27 | # Remove all elements 28 | bpy.ops.object.select_all(action="SELECT") 29 | bpy.ops.object.delete(use_global=False) 30 | 31 | # Set cursor to (0, 0, 0) 32 | bpy.context.scene.cursor.location = (0, 0, 0) 33 | 34 | # Create camera 35 | bpy.ops.object.add(type='CAMERA', location=(0, -3.0, 0)) 36 | camera = bpy.context.object 37 | camera.data.lens = 35 38 | camera.rotation_euler = Euler((pi/2, 0, 0), 'XYZ') 39 | 40 | # Make this the current camera 41 | bpy.context.scene.camera = camera 42 | 43 | # Create lamps 44 | rainbow_lights(5, 100, 2, energy=100) 45 | 46 | # Create object 47 | bpy.ops.mesh.primitive_ico_sphere_add( 48 | location=(0,0,0), 49 | subdivisions=3, 50 | radius=1) 51 | obj = bpy.context.object 52 | 53 | # Add subsurf modifier 54 | modifier = obj.modifiers.new('Subsurf', 'SUBSURF') 55 | modifier.levels = 2 56 | modifier.render_levels = 2 57 | 58 | # Smooth surface 59 | for p in obj.data.polygons: 60 | p.use_smooth = True 61 | 62 | # Add Glossy BSDF material 63 | mat = bpy.data.materials.new('Material') 64 | mat.use_nodes = True 65 | node = mat.node_tree.nodes[0] 66 | node.inputs[0].default_value = (0.8, 0.8, 0.8, 1) # Base color 67 | node.inputs[4].default_value = 0.5 # Metalic 68 | node.inputs[7].default_value = 0.5 # Roughness 69 | obj.data.materials.append(mat) 70 | 71 | # Render image 72 | scene = bpy.context.scene 73 | scene.render.resolution_x = 512 74 | scene.render.resolution_y = 512 75 | scene.render.resolution_percentage = 100 76 | scene.render.engine = 'CYCLES' 77 | #scene.render.engine = 'BLENDER_EEVEE' 78 | scene.render.filepath = 'rendering/simple_sphere.png' 79 | bpy.ops.render.render(write_still=True) 80 | -------------------------------------------------------------------------------- /scripts/tetrahedron_fractal.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | import bmesh 3 | import numpy as np 4 | from mathutils import Vector, Matrix 5 | from math import sqrt 6 | import itertools as it 7 | import utils 8 | 9 | 10 | def tetrahedron_points(r=1, origin=(0, 0, 0)): 11 | origin = Vector(origin) 12 | 13 | # Formulas from http://mathworld.wolfram.com/RegularTetrahedron.html 14 | a = 4*r/sqrt(6) 15 | points = [( sqrt(3)*a/3, 0, -r/3), \ 16 | (-sqrt(3)*a/6, -0.5*a, -r/3), \ 17 | (-sqrt(3)*a/6, 0.5*a, -r/3), \ 18 | (0, 0, sqrt(6)*a/3 - r/3)] 19 | 20 | points = [Vector(p) + origin for p in points] 21 | return points 22 | 23 | 24 | def recursive_tetrahedron(bm, points, level=0): 25 | sub_tetras = [] 26 | for i in range(len(points)): 27 | p0 = points[i] 28 | pK = points[:i] + points[i + 1:] 29 | sub_tetras.append([p0] + [(p0 + p)/2 for p in pK]) 30 | 31 | if 0 < level: 32 | for subTetra in sub_tetras: 33 | recursive_tetrahedron(bm, subTetra, level-1) 34 | else: 35 | for subTetra in sub_tetras: 36 | verts = [bm.verts.new(p) for p in subTetra] 37 | faces = [bm.faces.new(face) for face in it.combinations(verts, 3)] 38 | bmesh.ops.recalc_face_normals(bm, faces=faces) 39 | 40 | 41 | if __name__ == '__main__': 42 | # Remove all elements 43 | utils.remove_all() 44 | 45 | # Creata fractal tetrahedron 46 | bm = bmesh.new() 47 | tetrahedron_base_points = tetrahedron_points(5) 48 | recursive_tetrahedron(bm, tetrahedron_base_points, level=4) 49 | 50 | # Create obj and mesh from bmesh object 51 | mesh = bpy.data.meshes.new("TetrahedronMesh") 52 | bm.to_mesh(mesh) 53 | bm.free() 54 | obj = bpy.data.objects.new("Tetrahedron", mesh) 55 | bpy.context.collection.objects.link(obj) 56 | 57 | # Create camera and lamp 58 | target = utils.create_target((0, 0, 1)) 59 | utils.create_camera((-8, 10, 5), target, type='ORTHO', ortho_scale=10) 60 | utils.create_light((10, -10, 10), target=target, type='SUN', energy=100) 61 | 62 | # Select colors 63 | palette = [(181, 221, 201, 255), (218, 122, 61, 255)] 64 | palette = [utils.colorRGB_256(color) for color in palette] # Adjust color to Blender 65 | 66 | # Set background color of scene 67 | bpy.context.scene.world.node_tree.nodes["Background"] \ 68 | .inputs[0].default_value = palette[0] 69 | 70 | # Set material for object 71 | mat = utils.create_material(palette[1]) 72 | obj.data.materials.append(mat) 73 | 74 | # Render scene 75 | utils.render( 76 | 'rendering', 'tetrahedron_fractal', 500, 500, 77 | render_engine='CYCLES') 78 | -------------------------------------------------------------------------------- /scripts/utils/__init__.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | import bmesh 3 | from math import sin, cos, pi 4 | TAU = 2*pi 5 | import colorsys 6 | import os 7 | 8 | 9 | def remove_object(obj): 10 | if obj.type == 'MESH': 11 | if obj.data.name in bpy.data.meshes: 12 | bpy.data.meshes.remove(obj.data) 13 | if obj.name in bpy.context.scene.objects: 14 | bpy.context.scene.objects.unlink(obj) 15 | bpy.data.objects.remove(obj) 16 | else: 17 | raise NotImplementedError('Other types not implemented yet besides \'MESH\'') 18 | 19 | 20 | def track_to_constraint(obj, target): 21 | constraint = obj.constraints.new('TRACK_TO') 22 | constraint.target = target 23 | constraint.track_axis = 'TRACK_NEGATIVE_Z' 24 | #constraint.track_axis = 'TRACK_Z' 25 | constraint.up_axis = 'UP_Y' 26 | #constraint.owner_space = 'LOCAL' 27 | #constraint.target_space = 'LOCAL' 28 | 29 | return constraint 30 | 31 | 32 | def create_target(origin=(0,0,0)): 33 | target = bpy.data.objects.new('Target', None) 34 | bpy.context.collection.objects.link(target) 35 | target.location = origin 36 | return target 37 | 38 | 39 | def create_camera(origin, target=None, lens=35, clip_start=0.1, clip_end=200, type='PERSP', ortho_scale=6): 40 | # Create object and camera 41 | camera = bpy.data.cameras.new("Camera") 42 | camera.lens = lens 43 | camera.clip_start = clip_start 44 | camera.clip_end = clip_end 45 | camera.type = type # 'PERSP', 'ORTHO', 'PANO' 46 | if type == 'ORTHO': 47 | camera.ortho_scale = ortho_scale 48 | 49 | # Link object to scene 50 | obj = bpy.data.objects.new("CameraObj", camera) 51 | obj.location = origin 52 | bpy.context.collection.objects.link(obj) 53 | bpy.context.scene.camera = obj # Make this the current camera 54 | 55 | if target: 56 | track_to_constraint(obj, target) 57 | return obj 58 | 59 | 60 | def create_light(origin, type='POINT', energy=1, color=(1,1,1), target=None): 61 | # Light types: 'POINT', 'SUN', 'SPOT', 'HEMI', 'AREA' 62 | bpy.ops.object.add(type='LIGHT', location=origin) 63 | obj = bpy.context.object 64 | obj.data.type = type 65 | obj.data.energy = energy 66 | obj.data.color = color 67 | 68 | if target: 69 | track_to_constraint(obj, target) 70 | return obj 71 | 72 | 73 | def simple_scene(target_coords, camera_coords, sun_coords, lens=35): 74 | target = create_target(target_coords) 75 | camera = create_camera(camera_coords, target, lens) 76 | sun = create_light(sun_coords, 'SUN', target=target) 77 | 78 | return target, camera, sun 79 | 80 | 81 | def set_smooth(obj, level=None, smooth=True): 82 | if level: 83 | # Add subsurf modifier 84 | modifier = obj.modifiers.new('Subsurf', 'SUBSURF') 85 | modifier.levels = level 86 | modifier.render_levels = level 87 | 88 | # Smooth surface 89 | mesh = obj.data 90 | for p in mesh.polygons: 91 | p.use_smooth = smooth 92 | 93 | 94 | def rainbow_lights(r=5, n=100, freq=2, energy=0.1): 95 | for i in range(n): 96 | t = float(i)/float(n) 97 | pos = (r*sin(TAU*t), r*cos(TAU*t), r*sin(freq*TAU*t)) 98 | 99 | # Create lamp 100 | bpy.ops.object.add(type='LIGHT', location=pos) 101 | obj = bpy.context.object 102 | obj.data.type = 'POINT' 103 | 104 | # Apply gamma correction for Blender 105 | color = tuple(pow(c, 2.2) for c in colorsys.hsv_to_rgb(t, 0.6, 1)) 106 | 107 | # Set HSV color and lamp energy 108 | obj.data.color = color 109 | obj.data.energy = energy 110 | 111 | 112 | def remove_all(type=None): 113 | # Possible type: 114 | # "MESH", "CURVE", "SURFACE", "META", "FONT", "ARMATURE", 115 | # "LATTICE", "EMPTY", "CAMERA", "LIGHT" 116 | if type: 117 | bpy.ops.object.select_all(action='DESELECT') 118 | bpy.ops.object.select_by_type(type=type) 119 | bpy.ops.object.delete() 120 | else: 121 | # Remove all elements in scene 122 | bpy.ops.object.select_all(action="SELECT") 123 | bpy.ops.object.delete(use_global=False) 124 | 125 | 126 | def create_material(base_color=(1, 1, 1, 1), metalic=0.0, roughness=0.5): 127 | mat = bpy.data.materials.new('Material') 128 | 129 | if len(base_color) == 3: 130 | base_color = list(base_color) 131 | base_color.append(1) 132 | 133 | mat.use_nodes = True 134 | node = mat.node_tree.nodes[0] 135 | node.inputs[0].default_value = base_color 136 | node.inputs[4].default_value = metalic 137 | node.inputs[7].default_value = roughness 138 | 139 | return mat 140 | 141 | 142 | def colorRGB_256(color): 143 | return tuple(pow(float(c)/255.0, 2.2) for c in color) 144 | 145 | 146 | def render( 147 | render_folder='rendering', 148 | render_name='render', 149 | resolution_x=800, 150 | resolution_y=800, 151 | resolution_percentage=100, 152 | animation=False, 153 | frame_end=None, 154 | render_engine='CYCLES' 155 | ): 156 | scene = bpy.context.scene 157 | scene.render.resolution_x = resolution_x 158 | scene.render.resolution_y = resolution_y 159 | scene.render.resolution_percentage = resolution_percentage 160 | scene.render.engine = render_engine 161 | if frame_end: 162 | scene.frame_end = frame_end 163 | 164 | # Check if script is executed inside Blender 165 | if bpy.context.space_data is None: 166 | # Specify folder to save rendering and check if it exists 167 | render_folder = os.path.join(os.getcwd(), render_folder) 168 | if(not os.path.exists(render_folder)): 169 | os.mkdir(render_folder) 170 | 171 | if animation: 172 | # Render animation 173 | scene.render.filepath = os.path.join( 174 | render_folder, 175 | render_name) 176 | bpy.ops.render.render(animation=True) 177 | else: 178 | # Render still frame 179 | scene.render.filepath = os.path.join( 180 | render_folder, 181 | render_name + '.png') 182 | bpy.ops.render.render(write_still=True) 183 | 184 | 185 | def bmesh_to_object(bm, name='Object'): 186 | mesh = bpy.data.meshes.new(name + 'Mesh') 187 | bm.to_mesh(mesh) 188 | bm.free() 189 | 190 | obj = bpy.data.objects.new(name, mesh) 191 | bpy.context.scene.collection.objects.link(obj) 192 | 193 | return obj 194 | -------------------------------------------------------------------------------- /scripts/voronoi_landscape.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | import bmesh 3 | import numpy as np 4 | import scipy.spatial as spatial 5 | from random import random 6 | from mathutils import Vector, Matrix 7 | import colorsys 8 | import utils 9 | 10 | 11 | # Convert hsv values to gamma corrected rgb values 12 | # Based on: http://stackoverflow.com/questions/17910543/convert-gamma-rgb-curves-to-rgb-curves-with-no-gamma/ 13 | def convert_hsv(hsv): 14 | color = [pow(val, 2.2) for val in colorsys.hsv_to_rgb(*hsv)] 15 | color.append(1) 16 | return color 17 | 18 | 19 | def voronoi_landscape(n=1000, w=10, h=5): 20 | # Create voronoi structure 21 | points = np.random.normal(size=(n, 2))/4 22 | vor = spatial.Voronoi(points) 23 | verts, regions = vor.vertices, vor.regions 24 | 25 | # Filter unused voronoi regions 26 | regions = [region for region in regions 27 | if not -1 in region and len(region) > 0] 28 | regions = [region for region in regions 29 | if np.all([np.linalg.norm(verts[i]) < 1.2 for i in region])] 30 | 31 | # Create faces from voronoi regions 32 | bm = bmesh.new() 33 | vDict, faces = {}, [] 34 | for region in regions: 35 | for idx in region: 36 | if not idx in vDict: 37 | x, y, z = verts[idx, 0]*w, verts[idx, 1]*w, 0 38 | vert = bm.verts.new((x, y, z)) 39 | vDict[idx] = vert 40 | 41 | face = bm.faces.new(tuple(vDict[i] for i in region)) 42 | faces.append(face) 43 | 44 | bmesh.ops.recalc_face_normals(bm, faces=faces) 45 | 46 | # Extrude faces randomly 47 | top_faces = [] 48 | for face in faces: 49 | r = bmesh.ops.extrude_discrete_faces(bm, faces=[face]) 50 | f = r['faces'][0] 51 | top_faces.append(f) 52 | bmesh.ops.translate(bm, vec=Vector((0, 0, random()*h)), verts=f.verts) 53 | center = f.calc_center_bounds() 54 | bmesh.ops.scale(bm, vec=Vector((0.8, 0.8, 0.8)), verts=f.verts, space=Matrix.Translation(-center)) 55 | 56 | # Create list of random colors based on a range for each channel 57 | #color_range = [[0.7, 0.9], [0.7, 0.8], [0.8, 0.9]] # Pink 58 | color_range = [[0.5, 0.7], [0.7, 0.8], [0.8, 0.9]] # Blue 59 | #color_range = [[0.05, 0.15], [0.7, 0.8], [0.8, 0.9]] # Yellow 60 | n_colors = 20 61 | colors = np.random.random((n_colors, 3)) 62 | for i, r in zip(range(n_colors), color_range): 63 | print(r) 64 | colors[:, i] = (r[1] - r[0])*colors[:, i] + r[0] 65 | 66 | # Assign material index to each bar 67 | for face in top_faces: 68 | idx = np.random.randint(len(colors)) 69 | face.material_index = idx 70 | for edge in face.edges: 71 | for f in edge.link_faces: 72 | f.material_index = idx 73 | 74 | # Create obj and mesh from bmesh object 75 | me = bpy.data.meshes.new("VornoiMesh") 76 | bm.to_mesh(me) 77 | bm.free() 78 | obj = bpy.data.objects.new("Voronoi", me) 79 | bpy.context.scene.collection.objects.link(obj) 80 | 81 | # Create and assign materials to object 82 | for color in colors: 83 | mat = utils.create_material(convert_hsv(color)) 84 | obj.data.materials.append(mat) 85 | 86 | 87 | if __name__ == '__main__': 88 | print(__file__) 89 | 90 | # Remove all elements 91 | utils.remove_all() 92 | 93 | # Create object 94 | voronoi_landscape() 95 | 96 | # Create camera and lamp 97 | target = utils.create_target((0, 0, 3)) 98 | utils.create_camera((-8, -12, 11), target, type='ORTHO', ortho_scale=5) 99 | utils.create_light((5, -5, 10), target=target, type='SUN') 100 | 101 | # Render scene 102 | utils.render( 103 | 'rendering', 'vornoi_landscape', 512, 512, 104 | render_engine='CYCLES') 105 | -------------------------------------------------------------------------------- /scripts/voronoi_sphere.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | import bmesh 3 | import numpy as np 4 | import scipy.spatial as spatial 5 | from mathutils import Vector, Matrix 6 | import utils 7 | 8 | 9 | def VoronoiSphere(bm, points, r=2, offset=0.02, num_materials=1): 10 | # Calculate 3D Voronoi diagram 11 | vor = spatial.Voronoi(points) 12 | 13 | faces_dict = {} 14 | for (idx_p0, idx_p1), ridge_vertices in zip(vor.ridge_points, vor.ridge_vertices): 15 | if -1 in ridge_vertices: continue 16 | if idx_p0 not in faces_dict: 17 | faces_dict[idx_p0] = [] 18 | if idx_p1 not in faces_dict: 19 | faces_dict[idx_p1] = [] 20 | 21 | faces_dict[idx_p0].append(ridge_vertices) 22 | faces_dict[idx_p1].append(ridge_vertices) 23 | 24 | for idx_point in faces_dict: 25 | region = faces_dict[idx_point] 26 | center = Vector(vor.points[idx_point]) 27 | if len(region) <= 1: continue 28 | 29 | # Skip all Voronoi regions outside of radius r 30 | skip = False 31 | for faces in region: 32 | for idx in faces: 33 | p = vor.vertices[idx] 34 | if np.linalg.norm(p) > r: 35 | skip = True 36 | break 37 | 38 | if not skip: 39 | vertsDict = {} 40 | material_index = np.random.randint(num_materials) 41 | 42 | for faces in region: 43 | verts = [] 44 | for idx in faces: 45 | p = Vector(vor.vertices[idx]) 46 | if idx not in vertsDict: 47 | v = center - p 48 | v.normalize() 49 | vert = bm.verts.new(p + offset*v) 50 | verts.append(vert) 51 | vertsDict[idx] = vert 52 | else: 53 | verts.append(vertsDict[idx]) 54 | 55 | face = bm.faces.new(verts) 56 | face.material_index = material_index 57 | 58 | bmesh.ops.recalc_face_normals(bm, faces=bm.faces) 59 | 60 | 61 | if __name__ == '__main__': 62 | print(__file__) 63 | 64 | # Remove all elements 65 | utils.remove_all() 66 | 67 | # Create camera and lamp 68 | utils.simple_scene((0, 0, 0), (5.5, 0, 0), (-5, 5, 10)) 69 | 70 | # Color palette 71 | # http://www.colourlovers.com/palette/1189317/Rock_Mint_Splash 72 | palette = [(89, 91, 90), (20, 195, 162), (13, 229, 168), 73 | (124, 244, 154), (184, 253, 153)] 74 | palette = [utils.colorRGB_256(color) for color in palette] 75 | 76 | # Set background color of scene 77 | bpy.context.scene.world.use_nodes = False 78 | bpy.context.scene.world.color = palette[0] 79 | 80 | # Create Voronoi Sphere 81 | n, r = 2000, 2 82 | points = (np.random.random((n, 3)) - 0.5)*2*r 83 | bm = bmesh.new() 84 | VoronoiSphere(bm, points, r, num_materials=len(palette)-1) 85 | obj = utils.bmesh_to_object(bm) 86 | 87 | # Apply materials to object 88 | for color in palette[1:]: 89 | mat = utils.create_material(color) 90 | obj.data.materials.append(mat) 91 | 92 | # Render scene 93 | utils.render( 94 | 'rendering', 'voronoi_sphere', 512, 512, 95 | render_engine='CYCLES') 96 | --------------------------------------------------------------------------------