├── .gitignore ├── LICENSE ├── README.md ├── install.sh ├── media ├── collage.sh ├── collage_huge.png ├── collage_large.jpg ├── collage_medium.jpg ├── collage_small.jpg ├── example.png ├── examples │ ├── snowflake_01.png │ ├── snowflake_02.png │ ├── snowflake_03.png │ ├── snowflake_04.png │ ├── snowflake_05.png │ ├── snowflake_06.png │ ├── snowflake_07.png │ ├── snowflake_08.png │ ├── snowflake_09.png │ ├── snowflake_10.png │ ├── snowflake_11.png │ ├── snowflake_12.png │ ├── snowflake_13.png │ ├── snowflake_14.png │ ├── snowflake_15.png │ ├── snowflake_16.png │ ├── snowflake_17.png │ ├── snowflake_18.png │ ├── snowflake_19.png │ ├── snowflake_20.png │ ├── snowflake_21.png │ ├── snowflake_22.png │ ├── snowflake_23.png │ ├── snowflake_24.png │ ├── snowflake_25.png │ ├── snowflake_26.png │ ├── snowflake_27.png │ ├── snowflake_28.png │ ├── snowflake_29.png │ ├── snowflake_30.png │ ├── snowflake_31.png │ ├── snowflake_32.png │ ├── snowflake_33.png │ ├── snowflake_34.png │ ├── snowflake_35.png │ ├── snowflake_36.png │ ├── snowflake_37.png │ ├── snowflake_38.png │ ├── snowflake_39.png │ ├── snowflake_40.png │ ├── snowflake_41.png │ ├── snowflake_42.png │ ├── snowflake_43.png │ ├── snowflake_44.png │ ├── snowflake_45.png │ ├── snowflake_46.png │ ├── snowflake_47.png │ ├── snowflake_48.png │ ├── snowflake_49.png │ ├── snowflake_50.png │ ├── snowflake_51.png │ ├── snowflake_52.png │ ├── snowflake_53.png │ ├── snowflake_54.png │ ├── snowflake_55.png │ ├── snowflake_56.png │ ├── snowflake_57.png │ ├── snowflake_58.png │ ├── snowflake_59.png │ ├── snowflake_60.png │ ├── snowflake_61.png │ ├── snowflake_62.png │ ├── snowflake_63.png │ ├── snowflake_64.png │ ├── snowflake_65.png │ ├── snowflake_66.png │ ├── snowflake_67.png │ ├── snowflake_68.png │ ├── snowflake_69.png │ ├── snowflake_70.png │ ├── snowflake_71.png │ ├── snowflake_72.png │ ├── snowflake_73.png │ ├── snowflake_74.png │ ├── snowflake_75.png │ ├── snowflake_76.png │ ├── snowflake_77.png │ ├── snowflake_78.png │ ├── snowflake_79.png │ ├── snowflake_80.png │ └── snowflake_81.png ├── make_collage.py └── slides │ ├── Slide03.png │ ├── Slide04.png │ ├── Slide05.png │ ├── Slide06.png │ ├── Slide07.png │ ├── Slide08.png │ ├── Slide09.png │ ├── Slide10.png │ ├── Slide11.png │ ├── Slide12.png │ ├── Slide13.png │ ├── Slide14.png │ ├── Slide15.png │ ├── Slide16.png │ ├── Slide17.png │ ├── Slide18.png │ ├── Slide20.png │ └── Slide21.png ├── papers └── h2l.pdf ├── scripts └── snowflake.py ├── setup.py ├── src ├── __init__.py ├── curves.py ├── engine.py ├── etc │ ├── hist.gplot │ └── snowflake.ini ├── graphics.py ├── movie.py ├── render.py ├── runner.py └── splines.py └── utils ├── aws.py ├── deploy.sh ├── htmlgen.py ├── instance.sh ├── name_to_flake.py ├── plotdata.py ├── run_names.py ├── sfgen.py ├── snowflake_walk.py └── submit.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.png 2 | *.bmp 3 | *.dill 4 | *.dxf 5 | *.eps 6 | *.gcode 7 | *.pdf 8 | *.pgm 9 | *.pickle 10 | *.pyc 11 | *.stl 12 | *.ttf 13 | *.txt 14 | *.zip 15 | *egg-info* 16 | .*.sw? 17 | .*DS* 18 | ._* 19 | build 20 | build/ 21 | dist/ 22 | env 23 | env/ 24 | ez_setup.py 25 | media/snowflake_laser.mov 26 | sctk.egg-info/ 27 | snowflakes.html 28 | src/etc 29 | tags 30 | tarpit/ 31 | web 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (C) 2012 Giles Hall, Rachael Holmes 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 7 | of the Software, and to permit persons to whom the Software is furnished to do 8 | so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Example Snowflakes](https://raw.githubusercontent.com/vishnubob/snowflake/master/media/collage_medium.jpg) 2 | 3 | This is a snowflake simulator written in python by Rachael Holmes and Giles 4 | Hall. It builds realistic looking snowflakes by modeling the phase 5 | transistions between water molecules at a mesoscopic scale. The model is 6 | lifted (verbatim) from "MODELING SNOW CRYSTAL GROWTH II: A mesoscopic lattice 7 | map with plausible dynamics" by Janko Gravner and David Griffeath. 8 | 9 | Requirements: 10 | - PyPy (for fast execution of the simulations) 11 | - PIL for exporting graphics (PyPy accesible) 12 | 13 | Requirements for Laser Cutting: 14 | - Python only (no PyPy support) 15 | - PIL (Python accessible) 16 | - potrace (For translating SVG) 17 | - scipy/numpy (For clustering) 18 | 19 | Requirements for 3D Printing: 20 | - PyPy or Python 21 | - PIL (PyPy/Python accessible) 22 | - potrace (For translating SVG) 23 | 24 | ![Example Snowflake](https://raw.githubusercontent.com/vishnubob/snowflake/master/media/example.png) 25 | -------------------------------------------------------------------------------- /install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | python setup.py install 3 | pypy setup.py install 4 | -------------------------------------------------------------------------------- /media/collage.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | python make_collage.py -g 9x9 examples/*.png 3 | -------------------------------------------------------------------------------- /media/collage_huge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vishnubob/snowflake/009481f57246c8d9cb686487c57ed9e84ee2660b/media/collage_huge.png -------------------------------------------------------------------------------- /media/collage_large.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vishnubob/snowflake/009481f57246c8d9cb686487c57ed9e84ee2660b/media/collage_large.jpg -------------------------------------------------------------------------------- /media/collage_medium.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vishnubob/snowflake/009481f57246c8d9cb686487c57ed9e84ee2660b/media/collage_medium.jpg -------------------------------------------------------------------------------- /media/collage_small.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vishnubob/snowflake/009481f57246c8d9cb686487c57ed9e84ee2660b/media/collage_small.jpg -------------------------------------------------------------------------------- /media/example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vishnubob/snowflake/009481f57246c8d9cb686487c57ed9e84ee2660b/media/example.png -------------------------------------------------------------------------------- /media/examples/snowflake_01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vishnubob/snowflake/009481f57246c8d9cb686487c57ed9e84ee2660b/media/examples/snowflake_01.png -------------------------------------------------------------------------------- /media/examples/snowflake_02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vishnubob/snowflake/009481f57246c8d9cb686487c57ed9e84ee2660b/media/examples/snowflake_02.png -------------------------------------------------------------------------------- /media/examples/snowflake_03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vishnubob/snowflake/009481f57246c8d9cb686487c57ed9e84ee2660b/media/examples/snowflake_03.png -------------------------------------------------------------------------------- /media/examples/snowflake_04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vishnubob/snowflake/009481f57246c8d9cb686487c57ed9e84ee2660b/media/examples/snowflake_04.png -------------------------------------------------------------------------------- /media/examples/snowflake_05.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vishnubob/snowflake/009481f57246c8d9cb686487c57ed9e84ee2660b/media/examples/snowflake_05.png -------------------------------------------------------------------------------- /media/examples/snowflake_06.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vishnubob/snowflake/009481f57246c8d9cb686487c57ed9e84ee2660b/media/examples/snowflake_06.png -------------------------------------------------------------------------------- /media/examples/snowflake_07.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vishnubob/snowflake/009481f57246c8d9cb686487c57ed9e84ee2660b/media/examples/snowflake_07.png -------------------------------------------------------------------------------- /media/examples/snowflake_08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vishnubob/snowflake/009481f57246c8d9cb686487c57ed9e84ee2660b/media/examples/snowflake_08.png -------------------------------------------------------------------------------- /media/examples/snowflake_09.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vishnubob/snowflake/009481f57246c8d9cb686487c57ed9e84ee2660b/media/examples/snowflake_09.png -------------------------------------------------------------------------------- /media/examples/snowflake_10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vishnubob/snowflake/009481f57246c8d9cb686487c57ed9e84ee2660b/media/examples/snowflake_10.png -------------------------------------------------------------------------------- /media/examples/snowflake_11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vishnubob/snowflake/009481f57246c8d9cb686487c57ed9e84ee2660b/media/examples/snowflake_11.png -------------------------------------------------------------------------------- /media/examples/snowflake_12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vishnubob/snowflake/009481f57246c8d9cb686487c57ed9e84ee2660b/media/examples/snowflake_12.png -------------------------------------------------------------------------------- /media/examples/snowflake_13.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vishnubob/snowflake/009481f57246c8d9cb686487c57ed9e84ee2660b/media/examples/snowflake_13.png -------------------------------------------------------------------------------- /media/examples/snowflake_14.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vishnubob/snowflake/009481f57246c8d9cb686487c57ed9e84ee2660b/media/examples/snowflake_14.png -------------------------------------------------------------------------------- /media/examples/snowflake_15.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vishnubob/snowflake/009481f57246c8d9cb686487c57ed9e84ee2660b/media/examples/snowflake_15.png -------------------------------------------------------------------------------- /media/examples/snowflake_16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vishnubob/snowflake/009481f57246c8d9cb686487c57ed9e84ee2660b/media/examples/snowflake_16.png -------------------------------------------------------------------------------- /media/examples/snowflake_17.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vishnubob/snowflake/009481f57246c8d9cb686487c57ed9e84ee2660b/media/examples/snowflake_17.png -------------------------------------------------------------------------------- /media/examples/snowflake_18.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vishnubob/snowflake/009481f57246c8d9cb686487c57ed9e84ee2660b/media/examples/snowflake_18.png -------------------------------------------------------------------------------- /media/examples/snowflake_19.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vishnubob/snowflake/009481f57246c8d9cb686487c57ed9e84ee2660b/media/examples/snowflake_19.png -------------------------------------------------------------------------------- /media/examples/snowflake_20.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vishnubob/snowflake/009481f57246c8d9cb686487c57ed9e84ee2660b/media/examples/snowflake_20.png -------------------------------------------------------------------------------- /media/examples/snowflake_21.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vishnubob/snowflake/009481f57246c8d9cb686487c57ed9e84ee2660b/media/examples/snowflake_21.png -------------------------------------------------------------------------------- /media/examples/snowflake_22.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vishnubob/snowflake/009481f57246c8d9cb686487c57ed9e84ee2660b/media/examples/snowflake_22.png -------------------------------------------------------------------------------- /media/examples/snowflake_23.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vishnubob/snowflake/009481f57246c8d9cb686487c57ed9e84ee2660b/media/examples/snowflake_23.png -------------------------------------------------------------------------------- /media/examples/snowflake_24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vishnubob/snowflake/009481f57246c8d9cb686487c57ed9e84ee2660b/media/examples/snowflake_24.png -------------------------------------------------------------------------------- /media/examples/snowflake_25.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vishnubob/snowflake/009481f57246c8d9cb686487c57ed9e84ee2660b/media/examples/snowflake_25.png -------------------------------------------------------------------------------- /media/examples/snowflake_26.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vishnubob/snowflake/009481f57246c8d9cb686487c57ed9e84ee2660b/media/examples/snowflake_26.png -------------------------------------------------------------------------------- /media/examples/snowflake_27.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vishnubob/snowflake/009481f57246c8d9cb686487c57ed9e84ee2660b/media/examples/snowflake_27.png -------------------------------------------------------------------------------- /media/examples/snowflake_28.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vishnubob/snowflake/009481f57246c8d9cb686487c57ed9e84ee2660b/media/examples/snowflake_28.png -------------------------------------------------------------------------------- /media/examples/snowflake_29.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vishnubob/snowflake/009481f57246c8d9cb686487c57ed9e84ee2660b/media/examples/snowflake_29.png -------------------------------------------------------------------------------- /media/examples/snowflake_30.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vishnubob/snowflake/009481f57246c8d9cb686487c57ed9e84ee2660b/media/examples/snowflake_30.png -------------------------------------------------------------------------------- /media/examples/snowflake_31.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vishnubob/snowflake/009481f57246c8d9cb686487c57ed9e84ee2660b/media/examples/snowflake_31.png -------------------------------------------------------------------------------- /media/examples/snowflake_32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vishnubob/snowflake/009481f57246c8d9cb686487c57ed9e84ee2660b/media/examples/snowflake_32.png -------------------------------------------------------------------------------- /media/examples/snowflake_33.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vishnubob/snowflake/009481f57246c8d9cb686487c57ed9e84ee2660b/media/examples/snowflake_33.png -------------------------------------------------------------------------------- /media/examples/snowflake_34.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vishnubob/snowflake/009481f57246c8d9cb686487c57ed9e84ee2660b/media/examples/snowflake_34.png -------------------------------------------------------------------------------- /media/examples/snowflake_35.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vishnubob/snowflake/009481f57246c8d9cb686487c57ed9e84ee2660b/media/examples/snowflake_35.png -------------------------------------------------------------------------------- /media/examples/snowflake_36.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vishnubob/snowflake/009481f57246c8d9cb686487c57ed9e84ee2660b/media/examples/snowflake_36.png -------------------------------------------------------------------------------- /media/examples/snowflake_37.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vishnubob/snowflake/009481f57246c8d9cb686487c57ed9e84ee2660b/media/examples/snowflake_37.png -------------------------------------------------------------------------------- /media/examples/snowflake_38.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vishnubob/snowflake/009481f57246c8d9cb686487c57ed9e84ee2660b/media/examples/snowflake_38.png -------------------------------------------------------------------------------- /media/examples/snowflake_39.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vishnubob/snowflake/009481f57246c8d9cb686487c57ed9e84ee2660b/media/examples/snowflake_39.png -------------------------------------------------------------------------------- /media/examples/snowflake_40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vishnubob/snowflake/009481f57246c8d9cb686487c57ed9e84ee2660b/media/examples/snowflake_40.png -------------------------------------------------------------------------------- /media/examples/snowflake_41.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vishnubob/snowflake/009481f57246c8d9cb686487c57ed9e84ee2660b/media/examples/snowflake_41.png -------------------------------------------------------------------------------- /media/examples/snowflake_42.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vishnubob/snowflake/009481f57246c8d9cb686487c57ed9e84ee2660b/media/examples/snowflake_42.png -------------------------------------------------------------------------------- /media/examples/snowflake_43.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vishnubob/snowflake/009481f57246c8d9cb686487c57ed9e84ee2660b/media/examples/snowflake_43.png -------------------------------------------------------------------------------- /media/examples/snowflake_44.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vishnubob/snowflake/009481f57246c8d9cb686487c57ed9e84ee2660b/media/examples/snowflake_44.png -------------------------------------------------------------------------------- /media/examples/snowflake_45.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vishnubob/snowflake/009481f57246c8d9cb686487c57ed9e84ee2660b/media/examples/snowflake_45.png -------------------------------------------------------------------------------- /media/examples/snowflake_46.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vishnubob/snowflake/009481f57246c8d9cb686487c57ed9e84ee2660b/media/examples/snowflake_46.png -------------------------------------------------------------------------------- /media/examples/snowflake_47.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vishnubob/snowflake/009481f57246c8d9cb686487c57ed9e84ee2660b/media/examples/snowflake_47.png -------------------------------------------------------------------------------- /media/examples/snowflake_48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vishnubob/snowflake/009481f57246c8d9cb686487c57ed9e84ee2660b/media/examples/snowflake_48.png -------------------------------------------------------------------------------- /media/examples/snowflake_49.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vishnubob/snowflake/009481f57246c8d9cb686487c57ed9e84ee2660b/media/examples/snowflake_49.png -------------------------------------------------------------------------------- /media/examples/snowflake_50.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vishnubob/snowflake/009481f57246c8d9cb686487c57ed9e84ee2660b/media/examples/snowflake_50.png -------------------------------------------------------------------------------- /media/examples/snowflake_51.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vishnubob/snowflake/009481f57246c8d9cb686487c57ed9e84ee2660b/media/examples/snowflake_51.png -------------------------------------------------------------------------------- /media/examples/snowflake_52.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vishnubob/snowflake/009481f57246c8d9cb686487c57ed9e84ee2660b/media/examples/snowflake_52.png -------------------------------------------------------------------------------- /media/examples/snowflake_53.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vishnubob/snowflake/009481f57246c8d9cb686487c57ed9e84ee2660b/media/examples/snowflake_53.png -------------------------------------------------------------------------------- /media/examples/snowflake_54.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vishnubob/snowflake/009481f57246c8d9cb686487c57ed9e84ee2660b/media/examples/snowflake_54.png -------------------------------------------------------------------------------- /media/examples/snowflake_55.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vishnubob/snowflake/009481f57246c8d9cb686487c57ed9e84ee2660b/media/examples/snowflake_55.png -------------------------------------------------------------------------------- /media/examples/snowflake_56.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vishnubob/snowflake/009481f57246c8d9cb686487c57ed9e84ee2660b/media/examples/snowflake_56.png -------------------------------------------------------------------------------- /media/examples/snowflake_57.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vishnubob/snowflake/009481f57246c8d9cb686487c57ed9e84ee2660b/media/examples/snowflake_57.png -------------------------------------------------------------------------------- /media/examples/snowflake_58.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vishnubob/snowflake/009481f57246c8d9cb686487c57ed9e84ee2660b/media/examples/snowflake_58.png -------------------------------------------------------------------------------- /media/examples/snowflake_59.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vishnubob/snowflake/009481f57246c8d9cb686487c57ed9e84ee2660b/media/examples/snowflake_59.png -------------------------------------------------------------------------------- /media/examples/snowflake_60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vishnubob/snowflake/009481f57246c8d9cb686487c57ed9e84ee2660b/media/examples/snowflake_60.png -------------------------------------------------------------------------------- /media/examples/snowflake_61.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vishnubob/snowflake/009481f57246c8d9cb686487c57ed9e84ee2660b/media/examples/snowflake_61.png -------------------------------------------------------------------------------- /media/examples/snowflake_62.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vishnubob/snowflake/009481f57246c8d9cb686487c57ed9e84ee2660b/media/examples/snowflake_62.png -------------------------------------------------------------------------------- /media/examples/snowflake_63.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vishnubob/snowflake/009481f57246c8d9cb686487c57ed9e84ee2660b/media/examples/snowflake_63.png -------------------------------------------------------------------------------- /media/examples/snowflake_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vishnubob/snowflake/009481f57246c8d9cb686487c57ed9e84ee2660b/media/examples/snowflake_64.png -------------------------------------------------------------------------------- /media/examples/snowflake_65.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vishnubob/snowflake/009481f57246c8d9cb686487c57ed9e84ee2660b/media/examples/snowflake_65.png -------------------------------------------------------------------------------- /media/examples/snowflake_66.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vishnubob/snowflake/009481f57246c8d9cb686487c57ed9e84ee2660b/media/examples/snowflake_66.png -------------------------------------------------------------------------------- /media/examples/snowflake_67.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vishnubob/snowflake/009481f57246c8d9cb686487c57ed9e84ee2660b/media/examples/snowflake_67.png -------------------------------------------------------------------------------- /media/examples/snowflake_68.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vishnubob/snowflake/009481f57246c8d9cb686487c57ed9e84ee2660b/media/examples/snowflake_68.png -------------------------------------------------------------------------------- /media/examples/snowflake_69.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vishnubob/snowflake/009481f57246c8d9cb686487c57ed9e84ee2660b/media/examples/snowflake_69.png -------------------------------------------------------------------------------- /media/examples/snowflake_70.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vishnubob/snowflake/009481f57246c8d9cb686487c57ed9e84ee2660b/media/examples/snowflake_70.png -------------------------------------------------------------------------------- /media/examples/snowflake_71.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vishnubob/snowflake/009481f57246c8d9cb686487c57ed9e84ee2660b/media/examples/snowflake_71.png -------------------------------------------------------------------------------- /media/examples/snowflake_72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vishnubob/snowflake/009481f57246c8d9cb686487c57ed9e84ee2660b/media/examples/snowflake_72.png -------------------------------------------------------------------------------- /media/examples/snowflake_73.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vishnubob/snowflake/009481f57246c8d9cb686487c57ed9e84ee2660b/media/examples/snowflake_73.png -------------------------------------------------------------------------------- /media/examples/snowflake_74.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vishnubob/snowflake/009481f57246c8d9cb686487c57ed9e84ee2660b/media/examples/snowflake_74.png -------------------------------------------------------------------------------- /media/examples/snowflake_75.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vishnubob/snowflake/009481f57246c8d9cb686487c57ed9e84ee2660b/media/examples/snowflake_75.png -------------------------------------------------------------------------------- /media/examples/snowflake_76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vishnubob/snowflake/009481f57246c8d9cb686487c57ed9e84ee2660b/media/examples/snowflake_76.png -------------------------------------------------------------------------------- /media/examples/snowflake_77.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vishnubob/snowflake/009481f57246c8d9cb686487c57ed9e84ee2660b/media/examples/snowflake_77.png -------------------------------------------------------------------------------- /media/examples/snowflake_78.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vishnubob/snowflake/009481f57246c8d9cb686487c57ed9e84ee2660b/media/examples/snowflake_78.png -------------------------------------------------------------------------------- /media/examples/snowflake_79.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vishnubob/snowflake/009481f57246c8d9cb686487c57ed9e84ee2660b/media/examples/snowflake_79.png -------------------------------------------------------------------------------- /media/examples/snowflake_80.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vishnubob/snowflake/009481f57246c8d9cb686487c57ed9e84ee2660b/media/examples/snowflake_80.png -------------------------------------------------------------------------------- /media/examples/snowflake_81.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vishnubob/snowflake/009481f57246c8d9cb686487c57ed9e84ee2660b/media/examples/snowflake_81.png -------------------------------------------------------------------------------- /media/make_collage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import random 4 | import argparse 5 | from PIL import Image 6 | 7 | random.seed("i am a big purple potato") 8 | 9 | parser = argparse.ArgumentParser() 10 | 11 | parser.add_argument('-g', '--grid', default=None) 12 | parser.add_argument('-o', '--output', default='collage.jpg') 13 | parser.add_argument('images', nargs='+') 14 | 15 | args = parser.parse_args() 16 | 17 | n = len(args.images) 18 | print 'reading %d images: %r' % (n, args.images) 19 | 20 | if args.grid: 21 | (grid_width, grid_height) = map(int, args.grid.split('x')) 22 | else: 23 | (grid_width, grid_height) = n, 1 24 | 25 | images = [] 26 | target_size = 1000 27 | 28 | random.shuffle(args.images) 29 | 30 | for path in args.images: 31 | image = Image.open(path) 32 | (width, height) = image.size 33 | ratio = width / float(height) 34 | new_width = int(target_size * ratio) 35 | new_height = int(target_size / ratio) 36 | if new_width > target_size: 37 | image = image.resize((new_width, target_size)) 38 | left_pad = (new_width - target_size) / 2 39 | image = image.crop((left_pad, 0, left_pad + target_size, target_size)) 40 | else: 41 | image = image.resize((target_size, new_height)) 42 | upper_pad = (new_width - target_size) / 2 43 | image = image.crop((0, upper_pad, target_size, upper_pad + target_size)) 44 | (new_width, new_height) = image.size 45 | print "(%s x %s) -> (%s x %s)" % (width, height, new_width, new_height) 46 | images.append(image) 47 | 48 | collage = Image.new('RGB', (target_size * grid_width, target_size * grid_height)) 49 | 50 | for (idx, image) in enumerate(images): 51 | x = idx % grid_width 52 | y = idx / grid_width 53 | if y > grid_height: 54 | break 55 | collage.paste(image.copy(), (x * target_size, y * target_size)) 56 | 57 | collage.save(open("collage_huge.png", 'wb')) 58 | # large 59 | large = collage.resize((4096, 4096)) 60 | large.save(open("collage_large.jpg", 'wb')) 61 | # medium 62 | medium = collage.resize((1024, 1024)) 63 | medium.save(open("collage_medium.jpg", 'wb')) 64 | # small 65 | medium = collage.resize((640, 640)) 66 | medium.save(open("collage_medium.jpg", 'wb')) 67 | # small 68 | small = collage.resize((200, 200)) 69 | small.save(open("collage_small.jpg", 'wb')) 70 | -------------------------------------------------------------------------------- /media/slides/Slide03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vishnubob/snowflake/009481f57246c8d9cb686487c57ed9e84ee2660b/media/slides/Slide03.png -------------------------------------------------------------------------------- /media/slides/Slide04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vishnubob/snowflake/009481f57246c8d9cb686487c57ed9e84ee2660b/media/slides/Slide04.png -------------------------------------------------------------------------------- /media/slides/Slide05.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vishnubob/snowflake/009481f57246c8d9cb686487c57ed9e84ee2660b/media/slides/Slide05.png -------------------------------------------------------------------------------- /media/slides/Slide06.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vishnubob/snowflake/009481f57246c8d9cb686487c57ed9e84ee2660b/media/slides/Slide06.png -------------------------------------------------------------------------------- /media/slides/Slide07.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vishnubob/snowflake/009481f57246c8d9cb686487c57ed9e84ee2660b/media/slides/Slide07.png -------------------------------------------------------------------------------- /media/slides/Slide08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vishnubob/snowflake/009481f57246c8d9cb686487c57ed9e84ee2660b/media/slides/Slide08.png -------------------------------------------------------------------------------- /media/slides/Slide09.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vishnubob/snowflake/009481f57246c8d9cb686487c57ed9e84ee2660b/media/slides/Slide09.png -------------------------------------------------------------------------------- /media/slides/Slide10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vishnubob/snowflake/009481f57246c8d9cb686487c57ed9e84ee2660b/media/slides/Slide10.png -------------------------------------------------------------------------------- /media/slides/Slide11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vishnubob/snowflake/009481f57246c8d9cb686487c57ed9e84ee2660b/media/slides/Slide11.png -------------------------------------------------------------------------------- /media/slides/Slide12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vishnubob/snowflake/009481f57246c8d9cb686487c57ed9e84ee2660b/media/slides/Slide12.png -------------------------------------------------------------------------------- /media/slides/Slide13.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vishnubob/snowflake/009481f57246c8d9cb686487c57ed9e84ee2660b/media/slides/Slide13.png -------------------------------------------------------------------------------- /media/slides/Slide14.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vishnubob/snowflake/009481f57246c8d9cb686487c57ed9e84ee2660b/media/slides/Slide14.png -------------------------------------------------------------------------------- /media/slides/Slide15.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vishnubob/snowflake/009481f57246c8d9cb686487c57ed9e84ee2660b/media/slides/Slide15.png -------------------------------------------------------------------------------- /media/slides/Slide16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vishnubob/snowflake/009481f57246c8d9cb686487c57ed9e84ee2660b/media/slides/Slide16.png -------------------------------------------------------------------------------- /media/slides/Slide17.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vishnubob/snowflake/009481f57246c8d9cb686487c57ed9e84ee2660b/media/slides/Slide17.png -------------------------------------------------------------------------------- /media/slides/Slide18.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vishnubob/snowflake/009481f57246c8d9cb686487c57ed9e84ee2660b/media/slides/Slide18.png -------------------------------------------------------------------------------- /media/slides/Slide20.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vishnubob/snowflake/009481f57246c8d9cb686487c57ed9e84ee2660b/media/slides/Slide20.png -------------------------------------------------------------------------------- /media/slides/Slide21.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vishnubob/snowflake/009481f57246c8d9cb686487c57ed9e84ee2660b/media/slides/Slide21.png -------------------------------------------------------------------------------- /papers/h2l.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vishnubob/snowflake/009481f57246c8d9cb686487c57ed9e84ee2660b/papers/h2l.pdf -------------------------------------------------------------------------------- /scripts/snowflake.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env pypy 2 | 3 | import os 4 | import sys 5 | from sfgen import * 6 | 7 | def ensure_python(): 8 | # pylab doesn't play well with pypy 9 | # so this will cause us to re-exec if 10 | # we are in pypy ... do not use if you want pypy 11 | if sys.subversion[0] == "PyPy": 12 | msg = "Restarting within CPython environment to accomdate scipy/numpy" 13 | logging.warning(msg) 14 | args = ["/usr/local/bin/python", "python"] + sys.argv 15 | #os.execlp("/usr/bin/env", *args) 16 | print args 17 | os.execlp(*args) 18 | 19 | 20 | def get_cli(): 21 | parser = argparse.ArgumentParser(description='Snowflake Generator.') 22 | parser.add_argument(dest="name", nargs='+', help="The name of the snowflake.") 23 | parser.add_argument('-s', '--size', dest="size", type=int, help="The size of the snowflake.") 24 | parser.add_argument('-e', '--env', dest='env', help='comma seperated key=val env overrides') 25 | parser.add_argument('-b', '--bw', dest='bw', action='store_true', help='write out the image in black and white') 26 | parser.add_argument('-r', '--randomize', dest='randomize', action='store_true', help='randomize environment.') 27 | parser.add_argument('-x', '--extrude', dest='pipeline_3d', action='store_true', help='Enable 3d pipeline.') 28 | parser.add_argument('-l', '--laser', dest='pipeline_lasercutter', action='store_true', help='Enable Laser Cutter pipeline.') 29 | parser.add_argument('-M', '--max-steps', dest='max_steps', type=int, help='Maximum number of iterations.') 30 | parser.add_argument('-m', '--margin', dest='margin', type=float, help='When to stop snowflake growth (between 0 and 1)') 31 | parser.add_argument('-c', '--curves', dest='curves', action='store_true', help='run name as curves') 32 | parser.add_argument('-L', '--datalog', dest='datalog', action='store_true', help='Enable step wise data logging.') 33 | parser.add_argument('-D', '--debug', dest='debug', action='store_true', help='Show every step.') 34 | parser.add_argument('-V', '--movie', dest='movie', action='store_true', help='Render a movie.') 35 | parser.add_argument('-W', '--width', dest='width', type=float, help="Width of target render.") 36 | parser.add_argument('-H', '--height', dest='height', type=float, help="Height of target render.") 37 | 38 | parser.set_defaults(**SNOWFLAKE_DEFAULTS) 39 | args = parser.parse_args() 40 | args.name = str.join('', map(str.lower, args.name)) 41 | args.target_size = None 42 | if args.width and args.height: 43 | args.target_size = (args.width, args.height) 44 | if args.name[-1] == '/': 45 | # wart from the shell 46 | args.name = args.name[:-1] 47 | if not os.path.exists(args.name): 48 | os.mkdir(args.name) 49 | if args.pipeline_lasercutter: 50 | ensure_python() 51 | if args.pipeline_3d: 52 | args.bw = True 53 | if args.pipeline_3d or args.pipeline_lasercutter: 54 | ensure_python() 55 | return args 56 | 57 | if __name__ == "__main__": 58 | args = get_cli() 59 | os.chdir(args.name) 60 | try: 61 | run(args) 62 | finally: 63 | os.chdir('..') 64 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from setuptools import setup 4 | 5 | snowflake = { 6 | "name":"snowflake", 7 | "description":"snowflake generator", 8 | "author":"Giles Hall / Rachael Holmes", 9 | "packages": ["sfgen"], 10 | "package_dir": { 11 | "sfgen": "src", 12 | }, 13 | "py_modules":[ 14 | "sfgen.__init__", 15 | "sfgen.curves", 16 | "sfgen.splines", 17 | "sfgen.engine", 18 | "sfgen.graphics", 19 | ], 20 | "install_requires": [ 21 | "pillow", 22 | ], 23 | "package_data": {"sfgen": ["etc/*.ini"]}, 24 | "scripts":[ 25 | "scripts/snowflake.py", 26 | ], 27 | "version": "0.3", 28 | } 29 | 30 | if __name__ == "__main__": 31 | setup(**snowflake) 32 | -------------------------------------------------------------------------------- /src/__init__.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import math 3 | import os 4 | 5 | SNOWFLAKE_INI = os.path.join(os.path.split(__file__)[0], "etc", "snowflake.ini") 6 | logging.basicConfig(format="%(asctime)s (%(process)d): %(message)s", level=logging.DEBUG, datefmt='%d/%m/%y %H:%M:%S') 7 | log = logging.info 8 | 9 | X_SCALE_FACTOR = (1.0 / math.sqrt(3)) 10 | 11 | def log_output(name): 12 | logfn = "%s.log" % name 13 | foh = logging.FileHandler(logfn) 14 | foh.setLevel(logging.DEBUG) 15 | formatter = logging.Formatter('%(asctime)s (%(process)d): %(message)s', datefmt='%d/%m/%y %H:%M:%S') 16 | foh.setFormatter(formatter) 17 | logger = logging.getLogger() 18 | logger.addHandler(foh) 19 | 20 | from curves import * 21 | from graphics import * 22 | from engine import * 23 | -------------------------------------------------------------------------------- /src/curves.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import re 4 | import os 5 | from splines import * 6 | from bisect import bisect_left 7 | import random 8 | import math 9 | import sys 10 | 11 | # matplotlib 12 | PLOTS_ENABLED = True 13 | try: 14 | import matplotlib 15 | matplotlib.use('AGG') 16 | import numpy as np 17 | import matplotlib.mlab as mlab 18 | import matplotlib.pyplot as plt 19 | import matplotlib.ticker as ticker 20 | import pylab 21 | except ImportError: 22 | PLOTS_ENABLED = False 23 | 24 | class Interpolate(object): 25 | def __init__(self, x_list, y_list, squelch=0): 26 | self.squelch = squelch 27 | new_x = [x_list[0]] 28 | new_y = [y_list[0]] 29 | last_x = x_list[0] 30 | for (x, y) in zip(x_list[1:], y_list[1:]): 31 | if x - last_x < 0: 32 | continue 33 | last_x = x 34 | new_x.append(x) 35 | new_y.append(y) 36 | x_list = new_x 37 | y_list = new_y 38 | if any(y - x <= 0 for x, y in zip(x_list, x_list[1:])): 39 | raise ValueError("x_list must be in strictly ascending order!") 40 | x_list = self.x_list = map(float, x_list) 41 | y_list = self.y_list = map(float, y_list) 42 | intervals = zip(x_list, x_list[1:], y_list, y_list[1:]) 43 | self.slopes = [(y2 - y1)/(x2 - x1) for x1, x2, y1, y2 in intervals] 44 | 45 | def __getitem__(self, x): 46 | i = bisect_left(self.x_list, x) - 1 47 | return self.y_list[i] + self.slopes[i] * (x - self.x_list[i]) 48 | 49 | class Curve(object): 50 | def __init__(self, steps, minval, maxval): 51 | def sumup(lst): 52 | if len(lst) <= 1: 53 | return lst 54 | return [lst[0]] + sumup([lst[0] + lst[1]] + lst[2:]) 55 | self.minval = minval 56 | self.maxval = maxval 57 | self.steps = steps 58 | self.intervals = random.randint(5, 50) 59 | weights = [random.random() for x in range(self.intervals)] 60 | wsum = sum(weights) 61 | self.xlist = [0] + [int(x / wsum * self.steps) for x in sumup(weights)] 62 | self.ylist = [(random.random() * (maxval - minval) + minval) for x in self.xlist] 63 | self.process() 64 | 65 | def build_spline(self, knots): 66 | ncs = NaturalCubicSpline(tuples2points(knots)) 67 | points = [] 68 | u = 0.0 69 | du = 0.1 70 | lim = len(ncs) + du 71 | while (u < lim): 72 | p = ncs(u) 73 | points.append(tuple(p)) 74 | u = u + du 75 | return points 76 | 77 | def process(self): 78 | knots = [] 79 | for xy in zip(self.xlist, self.ylist): 80 | knots.append(xy) 81 | self.curve = Interpolate(*zip(*self.build_spline(knots))) 82 | 83 | def __getitem__(self, x): 84 | x %= (self.steps - 1) 85 | x += 1 86 | return self.curve[x] 87 | 88 | class CurveSet(dict): 89 | def __init__(self, name, steps, curves): 90 | self.name = name 91 | random.seed(name) 92 | self.steps = steps 93 | self.curves = curves 94 | self.process() 95 | 96 | def process(self): 97 | for cname in self.curves: 98 | args = self.curves[cname] 99 | curve = Curve(self.steps, *args) 100 | self[cname] = curve 101 | 102 | def run_graph(self): 103 | mod = os.path.splitext(__file__)[0] + '.py' 104 | cmd = "python %s %s \"%s\" %s" % (mod, self.steps, self.curves, self.name) 105 | os.system(cmd) 106 | 107 | def plot(self): 108 | assert PLOTS_ENABLED, "you do not currently have plots enabled." 109 | fig, axs = plt.subplots(nrows=len(self), ncols=1, sharex=True) 110 | fig.set_size_inches(10, 25) 111 | for (idx, key) in enumerate(self): 112 | ax = axs[idx] 113 | data = [self[key][x] for x in range(self.steps)] 114 | ax.plot(data) 115 | ax.set_xlabel("time (simulation steps)") 116 | ax.set_ylabel(key) 117 | ax.set_ylim(self[key].minval, self[key].maxval) 118 | fn = "%s_runtime.png" % self.name 119 | plt.savefig(fn) 120 | 121 | # for backwards compatability 122 | class NameCurve(object): 123 | Vowels = ['a', 'e', 'i', 'o', 'u'] 124 | 125 | def __init__(self, name, steps=5000, pause=None): 126 | # first thing first, set the random seed based on the name 127 | # and lock us into determinism and repeatability. 128 | random.seed(name) 129 | self.steps = steps 130 | if pause == None: 131 | self.pause = self.steps * .15 132 | self.name = str.join('', re.split("\s+", name.lower())) 133 | self.name_consonants = str.join('', [ch for ch in self.name if ch not in self.Vowels]) 134 | self.name_vowels = str.join('', [ch for ch in self.name if ch in self.Vowels]) 135 | assert self.name_consonants and self.name_vowels 136 | # XXX: "cheating" 137 | # if there is no final point, we add a synthetic 0 via 'a' 138 | if len(self.name_consonants) < 2: 139 | self.name_consonants += 'a' 140 | if len(self.name_vowels) < 2: 141 | self.name_vowels += 'a' 142 | self.coefs_consonants = [((ord(ch) - ord('a')) / 12.5) for ch in self.name_consonants] 143 | self.coefs_vowels = [((ord(ch) - ord('a')) / 25.0 - .5) for ch in self.name_vowels] 144 | self.step_vowels = (self.steps - self.pause) / max(1, (len(self.name_vowels) - 1)) 145 | self.step_consonants = (self.steps - self.pause) / max(1, (len(self.name_consonants) - 1)) 146 | self.process() 147 | 148 | def get_temperature(self, step): 149 | return self.temp_curve[step] 150 | 151 | def get_humidity(self, step): 152 | return self.hum_curve[step] 153 | 154 | def build_spline(self, knots): 155 | ncs = NaturalCubicSpline(tuples2points(knots)) 156 | points = [] 157 | u = 0.0 158 | du = 0.1 159 | lim = len(ncs) + du 160 | while (u < lim): 161 | p = ncs(u) 162 | points.append(tuple(p)) 163 | u = u + du 164 | return points 165 | 166 | def process(self): 167 | kv = [(0, 0)] 168 | kc = [(0, 0)] 169 | vsteps = self.pause 170 | csteps = self.pause 171 | for coef in self.coefs_vowels: 172 | kv.append((vsteps, coef)) 173 | vsteps += self.step_vowels 174 | for coef in self.coefs_consonants: 175 | kc.append((csteps, coef)) 176 | csteps += self.step_consonants 177 | self.hum_curve = Interpolate(*zip(*self.build_spline(kc)), squelch=5) 178 | self.temp_curve = Interpolate(*zip(*self.build_spline(kv)), squelch=5) 179 | 180 | def run_graph(self): 181 | mod = os.path.splitext(__file__)[0] + '.py' 182 | cmd = "python %s %s" % (mod, self.name) 183 | os.system(cmd) 184 | 185 | def graph(self, fn): 186 | assert PLOTS_ENABLED, "You do not currently have plots enabled." 187 | fig = plt.figure() 188 | ax1 = fig.add_subplot(1, 1, 1) 189 | ax1.plot([self.hum_curve[x] for x in range(self.steps)], 'r') 190 | ax1.set_ylim(0, 2) 191 | ax1.set_xlabel("Time (simulation steps)") 192 | ax1.set_ylabel("Relative Humidity", color='r') 193 | for tl in ax1.get_yticklabels(): 194 | tl.set_color('r') 195 | ax2 = ax1.twinx() 196 | ax2.plot([(20.0 * self.temp_curve[x]) - 40 for x in range(self.steps)], 'b') 197 | ax2.set_ylabel("Temperature (C)", color='b') 198 | #ax2.set_ylim(-1, 1) 199 | for tl in ax2.get_yticklabels(): 200 | tl.set_color('b') 201 | plt.title(self.name) 202 | plt.savefig(fn) 203 | 204 | 205 | 206 | if __name__ == "__main__": 207 | name = str.join('', sys.argv[3:]) 208 | curves = eval(sys.argv[2]) 209 | steps = int(sys.argv[1]) 210 | nc = CurveSet(name, steps, curves) 211 | nc.plot() 212 | -------------------------------------------------------------------------------- /src/engine.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env pypy 2 | 3 | import random 4 | import math 5 | import argparse 6 | import cPickle as pickle 7 | import logging 8 | import os 9 | import sys 10 | import re 11 | import colorsys 12 | import bisect 13 | import operator 14 | from xml.dom.minidom import parse 15 | 16 | InkscapePath = "/Applications/Inkscape.app/Contents/Resources/bin/inkscape" 17 | 18 | try: 19 | import Image 20 | import ImageDraw 21 | except ImportError: 22 | from PIL import Image 23 | from PIL import ImageDraw 24 | 25 | # local 26 | from sfgen import * 27 | sys.modules["curves"] = curves 28 | 29 | def avg_stdev(data): 30 | avg = sum(data) / float(len(data)) 31 | stdev = math.sqrt(sum((x - avg) ** 2 for x in data) / float(len(data))) 32 | return (avg, stdev) 33 | 34 | class CrystalEnvironment(dict): 35 | def __init__(self, curves=None, **kw): 36 | self.curves = curves 37 | self._init_defaults() 38 | self.update(**kw) 39 | self.set_factory_settings() 40 | 41 | def set_factory_settings(self): 42 | self.factory_settings = self.copy() 43 | 44 | def __getattr__(self, name): 45 | if name not in self: 46 | return AttributeError, "no such thing brah: %s" % name 47 | return self[name] 48 | 49 | def __getnewargs__(self): 50 | return () 51 | 52 | def __getstate__(self): 53 | return (self.curves, self.factory_settings, dict(self)) 54 | 55 | def __setstate__(self, state): 56 | if type(state) == dict: 57 | self.update(state) 58 | self.curves = None 59 | self.set_factory_settings() 60 | else: 61 | self.curves = state[0] 62 | self.factory_settings = state[1] 63 | self.update(state[2]) 64 | 65 | def step(self, x): 66 | if self.curves == None: 67 | return 68 | for key in self.curves: 69 | self[key] = self.curves[key][x] 70 | 71 | @classmethod 72 | def build_env(self, name, steps, min_gamma=0.45, max_gamma=0.85): 73 | curves = { 74 | "beta": (1.3, 2), 75 | "theta": (0.01, 0.04), 76 | "alpha": (0.02, 0.1), 77 | "kappa": (0.001, 0.01), 78 | "mu": (0.01, 0.1), 79 | "upilson": (0.00001, 0.0001), 80 | "sigma": (0.00001, 0.000001), 81 | } 82 | cs = CurveSet(name, steps, curves) 83 | cs.run_graph() 84 | env = {key: cs[key][0] for key in curves} 85 | env["gamma"] = random.random() * (max_gamma - min_gamma) + min_gamma 86 | return CrystalEnvironment(curves=cs, **env) 87 | 88 | def get_default(self, key): 89 | return self.factory_settings[key] 90 | 91 | def randomize(self): 92 | for key in self: 93 | if key == "sigma": 94 | continue 95 | if key == "gamma": 96 | self[key] += 1.0 / random.randint(100, 1000) 97 | else: 98 | self[key] += random.choice([1.0, -1.0]) / random.randint(100, 1000) 99 | self.set_factory_settings() 100 | 101 | def _init_defaults(self): 102 | # (3a) 103 | # "A boundary site with 1 or 2 attached neighbors needs boundary mass at least beta to join the crystal 104 | # This is the case when the local mesoscopic geometry near x corresponds to a tip or flat spot of the crystal. 105 | # (Distinguishing the two cases turns out to be of minor significance.) In our simulations, beta is typically 106 | # between about 1.05 and 3. We assume beta > 1 since 1 is the basic threshold of the case to follow next. 107 | self["beta"] = 1.3 108 | 109 | # (3b) 110 | # "A boundary site with 3 attached neighbors joins the crystal if either it has boundary mass >= 1, 111 | # or it has diffusive mass < theta in its neighborhood and it has boundary mass >= alpha" 112 | self["theta"] = 0.025 113 | self["alpha"] = 0.08 114 | 115 | # (2) 116 | # "Proportion kappa of the diffusive mass at each boundary site crystallizes. 117 | # The remainder (proportion 1 - kappa) becomes boundary mass." 118 | self["kappa"] = 0.003 119 | 120 | # (4) 121 | # "Proportion mu of the boundary mass and proportion upsilon of the crystal mass at each boundary site become diffusive mass. 122 | # Melting represents mass flow at the boundary from ice and quasi-liquid back to vapor, reverse 123 | # effects from the freezing of step ii. Typically mu is small and upsilon extremely small." 124 | self["mu"] = 0.07 125 | self["upsilon"] = 0.00005 126 | 127 | # (5) 128 | # "The diffusive mass at each site undergoes an independent random perturbation of proportion sigma" 129 | self["sigma"] = 0.00001 130 | 131 | # initial diffusion 132 | self["gamma"] = 0.5 133 | 134 | def _init_special(self): 135 | pass 136 | 137 | class RenderMovie(object): 138 | def __init__(self, name): 139 | self.name = name 140 | self.replay = LatticeReplay(name) 141 | 142 | def run(self): 143 | if not os.path.exists("frames"): 144 | os.mkdir("frames") 145 | x = iter(self.replay) 146 | for (idx, frame) in enumerate(self.replay): 147 | fn = "frames/%s_%09d.png" % (self.name, idx + 1) 148 | frame.save_image(fn) 149 | 150 | class LatticeReplay(object): 151 | class ReplayIterator(object): 152 | def __init__(self, replay): 153 | self.replay = replay 154 | self.idx = 0 155 | 156 | def next(self): 157 | try: 158 | lattice = self.replay.get_lattice(self.idx) 159 | self.idx += 1 160 | return lattice 161 | except IndexError: 162 | raise StopIteration 163 | 164 | def __init__(self, name): 165 | self.name = name 166 | self.current_frame = None 167 | self.current_replay = None 168 | pfn = "%s.pickle" % self.name 169 | self.lattice = CrystalLattice.load_lattice(pfn) 170 | self.scan_replays() 171 | 172 | def __iter__(self): 173 | return self.ReplayIterator(self) 174 | 175 | def get_lattice(self, step): 176 | (step, dm, cm) = self.get_step(step) 177 | for (idx, cell) in enumerate(zip(dm, cm)): 178 | self.lattice.cells[idx].diffusive_mass = cell[0] 179 | self.lattice.cells[idx].crystal_mass = cell[1] 180 | self.lattice.cells[idx].attached = bool(cell[1]) 181 | for cell in self.lattice.cells: 182 | cell.update_boundary() 183 | return self.lattice 184 | 185 | def get_step(self, step): 186 | idx = bisect.bisect_left(self.replay_map, step + 1) 187 | if self.current_frame != idx or not self.current_replay: 188 | self.current_frame = idx 189 | fn = self.replays[self.current_frame] 190 | print "loading", fn 191 | f = open(fn) 192 | self.current_replay = pickle.load(f) 193 | offset = self.current_replay[0][0] 194 | return self.current_replay[step - offset] 195 | 196 | def scan_replays(self): 197 | replays = [] 198 | fn_re = re.compile("cell_log_(\d+).pickle") 199 | for fn in os.listdir('.'): 200 | m = fn_re.search(fn) 201 | if m: 202 | step = int(m.group(1)) 203 | replays.append((fn, step)) 204 | replays.sort(key=operator.itemgetter(1)) 205 | self.replays = [rp[0] for rp in replays] 206 | self.replay_map = [rp[1] for rp in replays] 207 | 208 | class CrystalLattice(object): 209 | LogHeader = ["dm", "cm", "bm", "acnt", "bcnt", "width", "beta", "theta", "alpha", "kappa", "mu", "upsilon"] 210 | 211 | def __init__(self, size, environment=None, celltype=None, max_steps=0, margin=None, curves=None, datalog=False, debug=False): 212 | self.size = size 213 | if environment == None: 214 | environment = CrystalEnvironment() 215 | self.environment = environment 216 | self.datalog = None 217 | self.celllog = None 218 | if datalog: 219 | self.datalog = [] 220 | self.celllog = [] 221 | if celltype == None: 222 | celltype = SnowflakeCell 223 | self.debug = debug 224 | self.celltype = celltype 225 | self.iteration = 1 226 | assert margin > 0 and margin <= 1.0 227 | self.margin = margin 228 | self.curves = curves 229 | self.max_steps = max_steps 230 | self._init_cells() 231 | 232 | def __setstate__(self, state): 233 | # 0.1->0.2 format changes 234 | if "radius" in state: 235 | state["size"] = state["radius"] 236 | del state["radius"] 237 | if "angle" in state: 238 | del state["angle"] 239 | self.__dict__.update(state) 240 | 241 | def save_lattice(self, fn): 242 | msg = "Saving %s..." % fn 243 | log(msg) 244 | f = open(fn, 'wb') 245 | pickle.dump(self, f, protocol=-1) 246 | 247 | @classmethod 248 | def load_lattice(cls, fn): 249 | msg = "Loading %s..." % fn 250 | log(msg) 251 | f = open(fn, 'rb') 252 | obj = pickle.load(f) 253 | for cell in obj.cells: 254 | cell.lattice = obj 255 | cell.env = obj.environment 256 | cell.update_boundary() 257 | return obj 258 | 259 | def get_neighbors(self, xy): 260 | (x, y) = xy 261 | nlist = [(x, y + 1), (x, y - 1), (x - 1, y), (x + 1, y), (x - 1, y - 1), (x + 1, y + 1)] 262 | nlist = map(self._cell_index, filter(self._xy_ok, nlist)) 263 | res = tuple([self.cells[nidx] for nidx in nlist if self.cells[nidx] != None]) 264 | return res 265 | 266 | def reality_check(self): 267 | for cell in self.cells: 268 | cell.reality_check() 269 | 270 | def _init_cells(self): 271 | self.cells = [None] * (self.size * self.size) 272 | for x in range(self.size): 273 | for y in range(self.size): 274 | xy = (x, y) 275 | cell = self.celltype(xy, self) 276 | idx = self._cell_index(xy) 277 | self.cells[idx] = cell 278 | self.reality_check() 279 | center_pt = self._cell_index((self.size / 2, self.size / 2)) 280 | self.cells[center_pt].attach(1) 281 | # fun experiments 282 | #self.cells[center_pt+4].attach(1) 283 | #self.cells[center_pt-4].attach(1) 284 | 285 | def _xy_ok(self, xy): 286 | (x, y) = xy 287 | return (x >= 0 and x < self.size and y >= 0 and y < self.size) 288 | 289 | def _cell_index(self, xy): 290 | (x, y) = xy 291 | return int(round(y * self.size + x)) 292 | 293 | def _cell_xy(self, idx): 294 | y = idx / self.size 295 | x = idx % self.size 296 | return (x, y) 297 | 298 | def adjust_humidity(self, val): 299 | val = abs(val) 300 | for cell in self.cells: 301 | if cell.attached or cell.boundary: 302 | continue 303 | cell.diffusive_mass += val * self.environment.sigma 304 | # only mutate the cells outside our margin 305 | #if self.xy_to_polar(cell.xy)[1] > (self.size * self.margin): 306 | # we use the same coef as the noise coef 307 | #cell.diffusive_mass += val * self.environment.sigma 308 | 309 | def log_status(self): 310 | if self.datalog == None: 311 | return 312 | row = [] 313 | #row.append(self.iteration) 314 | dm = [cell.diffusive_mass for cell in self.cells if cell] 315 | row.append(sum(dm)) 316 | cm = [cell.crystal_mass for cell in self.cells if cell] 317 | row.append(sum(cm)) 318 | bm = [cell.boundary_mass for cell in self.cells if cell] 319 | row.append(sum(bm)) 320 | acnt = len([cell for cell in self.cells if cell and cell.attached]) 321 | row.append(acnt) 322 | bcnt = len([cell for cell in self.cells if cell and cell.boundary]) 323 | row.append(bcnt) 324 | d = self.snowflake_radius() 325 | row.append(d) 326 | row.append(self.environment.beta) 327 | row.append(self.environment.theta) 328 | row.append(self.environment.alpha) 329 | row.append(self.environment.kappa) 330 | row.append(self.environment.mu) 331 | row.append(self.environment.upsilon) 332 | #row.append(self.environment.sigma) 333 | #row.append(self.environment.gamma) 334 | self.datalog.append(row) 335 | # log the cells 336 | self.celllog.append((self.iteration, dm, cm)) 337 | 338 | def write_log(self): 339 | self.write_datalog() 340 | self.write_celllog() 341 | 342 | def write_datalog(self): 343 | if self.datalog == None: 344 | return 345 | logfn = "datalog.csv" 346 | msg = "Saving runtime data to %s" % logfn 347 | log(msg) 348 | f = open(logfn, 'w') 349 | txt = '' 350 | txt += str.join(',', self.LogHeader) + '\n' 351 | for row in self.datalog: 352 | txt += str.join(',', map(str, row)) + '\n' 353 | f.write(txt) 354 | 355 | def write_celllog(self): 356 | if not self.celllog: 357 | return 358 | logfn = "cell_log_%d.pickle" % self.iteration 359 | f = open(logfn, 'wb') 360 | pickle.dump(self.celllog, f, protocol=-1) 361 | self.celllog = [] 362 | 363 | def print_status(self): 364 | dm = sum([cell.diffusive_mass for cell in self.cells if cell]) 365 | cm = sum([cell.crystal_mass for cell in self.cells if cell]) 366 | bm = sum([cell.boundary_mass for cell in self.cells if cell]) 367 | acnt = len([cell for cell in self.cells if cell and cell.attached]) 368 | bcnt = len([cell for cell in self.cells if cell and cell.boundary]) 369 | #msg = "Step #%d, %d attached, %d boundary, %.2f dM, %.2f bM, %.2f cM, tot %.2f M" % (self.iteration, acnt, bcnt, dm, bm, cm, dm + cm + bm) 370 | d = self.snowflake_radius() 371 | msg = "Step #%d/%dp (%.2f%% scl), %d/%d (%.2f%%), %.2f dM, %.2f bM, %.2f cM, tot %.2f M" % (self.iteration, d, (float(d * 2 * X_SCALE_FACTOR) / self.iteration) * 100, acnt, bcnt, (float(bcnt) / acnt) * 100, dm, bm, cm, dm + cm + bm) 372 | log(msg) 373 | 374 | def step(self): 375 | self.log_status() 376 | for cell in self.cells: 377 | if cell == None or cell.attached: 378 | continue 379 | cell.step_one() 380 | for cell in self.cells: 381 | if cell == None or cell.attached: 382 | continue 383 | cell.step_two() 384 | for cell in self.cells: 385 | if cell == None or cell.attached: 386 | continue 387 | cell.step_three() 388 | # run curves 389 | self.iteration += 1 390 | self.environment.step(self.iteration) 391 | 392 | def translate_xy(self, xy): 393 | (x, y) = xy 394 | x = int(round(x * X_SCALE_FACTOR)) 395 | return (x, y) 396 | 397 | def polar_to_xy(self, args): 398 | (angle, distance) = args 399 | half = self.size / 2.0 400 | angle = math.radians(angle) 401 | y = int(round(half - (math.sin(angle) * distance))) 402 | x = int(round(half + (math.cos(angle) * distance))) 403 | return (x, y) 404 | 405 | def xy_to_polar(self, args): 406 | (x, y) = args 407 | half = self.size / 2.0 408 | x -= half 409 | y += half 410 | angle = math.degrees(math.atan2(y, x)) 411 | distance = math.hypot(x, y) 412 | return (angle, distance) 413 | 414 | def snowflake_radius(self, angle=135): 415 | # we cast a ray on the 135 degeree axis 416 | radius = 0 417 | half = self.size / 2.0 418 | while radius < half: 419 | radius += 1 420 | xy = self.polar_to_xy((angle, radius)) 421 | cell = self.cells[self._cell_index(xy)] 422 | if cell.attached or cell.boundary: 423 | continue 424 | return radius 425 | # uhh 426 | return int(round(half)) 427 | 428 | def crop_snowflake(self, margin=None): 429 | def scale(val): 430 | return int(round(X_SCALE_FACTOR * val)) 431 | if margin == None: 432 | margin = 15 433 | half = self.size / 2 434 | radius = scale(self.snowflake_radius()) 435 | distance = min(radius + margin, half) 436 | half_s = scale(half) 437 | distance_s = scale(distance) 438 | box = (half_s - distance, half - distance, half_s + distance, half + distance) 439 | return box 440 | 441 | def headroom(self, margin=None): 442 | if self.max_steps and self.iteration >= self.max_steps: 443 | return False 444 | if margin == None: 445 | margin = self.margin 446 | assert margin > 0 and margin <= 1 447 | cutoff = int(round(margin * (self.size / 2.0))) 448 | radius = self.snowflake_radius() 449 | if radius > cutoff: 450 | return False 451 | return True 452 | 453 | def grow(self): 454 | while True: 455 | if self.debug: 456 | self.print_status() 457 | self.step() 458 | if self.iteration % 50 == 0: 459 | self.write_celllog() 460 | if not self.debug: 461 | self.print_status() 462 | if not self.headroom(): 463 | break 464 | if self.debug: 465 | self.print_status() 466 | 467 | def save_image(self, fn, **kw): 468 | import sfgen 469 | r = sfgen.RenderSnowflake(self) 470 | r.save_image(fn, **kw) 471 | 472 | class SnowflakeCell(object): 473 | def __init__(self, xy, lattice): 474 | self.xy = xy 475 | self.lattice = lattice 476 | self.env = lattice.environment 477 | self.diffusive_mass = self.env.gamma 478 | self.boundary_mass = 0.0 479 | self.crystal_mass = 0.0 480 | self.attached = False 481 | self.age = 0 482 | self.boundary = 0 483 | self.attached_neighbors = [] 484 | self.__neighbors = None 485 | 486 | def __getstate__(self): 487 | return (self.xy, self.diffusive_mass, self.boundary_mass, self.crystal_mass, self.attached, self.age) 488 | 489 | def __setstate__(self, state): 490 | self.xy = state[0] 491 | self.diffusive_mass = state[1] 492 | self.boundary_mass = state[2] 493 | self.crystal_mass = state[3] 494 | self.attached = state[4] 495 | # 0.2 -> 0.3 496 | try: 497 | self.age = state[5] 498 | except IndexError: 499 | self.age = 0 500 | self.__neighbors = None 501 | self.lattice = None 502 | self.env = None 503 | 504 | def reality_check(self): 505 | assert len(self.neighbors) 506 | for neighbor in self.neighbors: 507 | assert self in neighbor.neighbors, "%s not in %s" % (str(self), str(neighbor.neighbors)) 508 | 509 | def __repr__(self): 510 | return "(%d,%d)" % self.xy 511 | 512 | @property 513 | def neighbors(self): 514 | if self.__neighbors == None: 515 | self.__neighbors = self.lattice.get_neighbors(self.xy) 516 | return self.__neighbors 517 | 518 | #@property 519 | #def attached_neighbors(self): 520 | # return [cell for cell in self.neighbors if cell.attached] 521 | 522 | #@property 523 | #def boundary(self): 524 | # return (not self.attached) and any([cell.attached for cell in self.neighbors]) 525 | 526 | def update_boundary(self): 527 | self.boundary = (not self.attached) and any([cell.attached for cell in self.neighbors]) 528 | 529 | def step_one(self): 530 | self.update_boundary() 531 | if self.boundary: 532 | self.attached_neighbors = [cell for cell in self.neighbors if cell.attached] 533 | self._next_dm = self.diffusion_calc() 534 | 535 | def step_two(self): 536 | self.diffusive_mass = self._next_dm 537 | self.attachment_flag = self.attached 538 | self.freezing_step() 539 | self.attachment_flag = self.attachment_step() 540 | self.melting_step() 541 | 542 | def step_three(self): 543 | if self.boundary and self.attachment_flag: 544 | self.attach() 545 | self.noise_step() 546 | 547 | def diffusion_calc(self): 548 | next_dm = self.diffusive_mass 549 | if self.attached: 550 | return next_dm 551 | self.age += 1 552 | for cell in self.neighbors: 553 | if cell.attached: 554 | next_dm += self.diffusive_mass 555 | else: 556 | next_dm += cell.diffusive_mass 557 | return float(next_dm) / (len(self.neighbors) + 1) 558 | 559 | def attach(self, offset=0.0): 560 | self.crystal_mass = self.boundary_mass + self.crystal_mass + offset 561 | self.boundary_mass = 0 562 | self.attached = True 563 | 564 | def freezing_step(self): 565 | if not self.boundary: 566 | return 567 | self.boundary_mass += (1 - self.env.kappa) * self.diffusive_mass 568 | self.crystal_mass += (self.env.kappa * self.diffusive_mass) 569 | self.diffusive_mass = 0 570 | 571 | def attachment_step(self): 572 | if not self.boundary: 573 | return False 574 | attach_count = len(self.attached_neighbors) 575 | if attach_count <= 2: 576 | if self.boundary_mass > self.env.beta: 577 | return True 578 | elif attach_count == 3: 579 | if self.boundary_mass >= 1: 580 | return True 581 | else: 582 | summed_diffusion = self.diffusive_mass 583 | for cell in self.neighbors: 584 | summed_diffusion += cell.diffusive_mass 585 | if summed_diffusion < self.env.theta and self.boundary_mass >= self.env.alpha: 586 | return True 587 | elif attach_count >= 4: 588 | return True 589 | return False 590 | 591 | def melting_step(self): 592 | if not self.boundary: 593 | return 594 | self.diffusive_mass += self.env.mu * self.boundary_mass + self.env.upsilon * self.crystal_mass 595 | self.boundary_mass = (1 - self.env.mu) * self.boundary_mass 596 | self.crystal_mass = (1 - self.env.upsilon) * self.crystal_mass 597 | 598 | def noise_step(self): 599 | if (self.boundary or self.attached): 600 | return 601 | if random.random() >= .5: 602 | self.diffusive_mass = (1 - self.env.sigma) * self.diffusive_mass 603 | else: 604 | self.diffusive_mass = (1 + self.env.sigma) * self.diffusive_mass 605 | 606 | def check_basecut(svgfn): 607 | # ensure there is only one path 608 | svg = parse(svgfn) 609 | for (cnt, node) in enumerate(svg.getElementsByTagName("path")): 610 | if cnt > 0: 611 | return False 612 | return True 613 | 614 | def merge_svg(file_list, color_list, outfn): 615 | first = None 616 | idx = 0 617 | for (svgfn, color) in zip(file_list, color_list): 618 | svg = parse(svgfn) 619 | for node in svg.getElementsByTagName("g"): 620 | if idx == 0: 621 | # cut layer 622 | # write a new group 623 | container = svg.createElement("g") 624 | container.setAttribute("transform", node.attributes["transform"].nodeValue) 625 | node.parentNode.replaceChild(container, node) 626 | container.appendChild(node) 627 | node.attributes["fill"] = "none" 628 | node.attributes["stroke"] = "rgb(0, 0, 255)" 629 | node.attributes["stroke-opacity"] = "1" 630 | node.attributes["stroke-width"] = ".01mm" 631 | else: 632 | node.attributes["fill"] = color 633 | del node.attributes["transform"] 634 | idx += 1 635 | import_nodes = svg.importNode(node, True) 636 | container.appendChild(import_nodes) 637 | if first == None: 638 | first = svg 639 | f = open(outfn, 'w') 640 | f.write(first.toxml()) 641 | 642 | def potrace(svgfn, fn, turd=None, size=None): 643 | cmd = ["potrace", "-i", "-b", "svg"] 644 | if turd != None: 645 | cmd.extend(["-t", str(turd)]) 646 | if size != None: 647 | sz = map(str, size) 648 | cmd.extend(["-W", sz[0], "-H", sz[1]]) 649 | cmd.extend(["-o", svgfn, fn]) 650 | cmd = str.join(' ', cmd) 651 | msg = "Running '%s'" % cmd 652 | log(msg) 653 | os.system(cmd) 654 | 655 | # laser cutter pipeline 656 | def pipeline_lasercutter(args, lattice, inches=3, dpi=96, turd=10): 657 | # layers 658 | rs = RenderSnowflake(lattice) 659 | name = str.join('', [c for c in args.name if c.islower()]) 660 | size = args.target_size 661 | layerfn = "%s_layer_%%d.bmp" % name 662 | resize = inches * dpi 663 | fnlist = rs.save_layers(layerfn, 2, resize=resize, margin=1) 664 | # we want to drop the heaviest layer 665 | del fnlist[0] 666 | # try to save o'natural 667 | imgfn = "%s_bw.bmp" % name 668 | svgfn = "%s_bw.svg" % name 669 | lattice.save_image(imgfn, scheme=BlackWhite(lattice), resize=resize, margin=1) 670 | potrace(svgfn, imgfn, turd=2000) 671 | if not check_basecut(svgfn): 672 | msg = "There are disconnected elements in the base cut, turning on boundary layer." 673 | log(msg) 674 | lattice.save_image(imgfn, scheme=BlackWhite(lattice, boundary=True), resize=resize, margin=1) 675 | potrace(svgfn, imgfn, turd=2000) 676 | assert check_basecut(svgfn), "Despite best efforts, base cut is still non-contiguous." 677 | os.unlink(svgfn) 678 | fnlist.insert(0, imgfn) 679 | # adjusted for ponoko 680 | # cut layer is blue 681 | # etch layer are black, or shades of grey 682 | colors = ["#000000", "#111111", "#222222", "#333333", "#444444", "#555555"] 683 | svgs = [] 684 | for (idx, fn) in enumerate(fnlist): 685 | svgfn = os.path.splitext(fn)[0] 686 | svgfn = "%s_laser.svg" % svgfn 687 | svgs.append(svgfn) 688 | if idx == 0: 689 | potrace(svgfn, fn, turd=turd, size=size) 690 | else: 691 | potrace(svgfn, fn, size=size) 692 | svgfn = "%s_laser_merged.svg" % name 693 | epsfn = "%s_laser_merged.eps" % name 694 | merge_svg(svgs, colors, svgfn) 695 | """ 696 | # move to eps 697 | cmd = "%s %s -E %s" % (InkscapePath, svgfn, epsfn) 698 | msg = "Running '%s'" % cmd 699 | log(msg) 700 | os.system(cmd) 701 | """ 702 | 703 | # 3d pipeline 704 | def pipeline_3d(args, lattice, inches=3, dpi=96, turd=10): 705 | resize = inches * dpi 706 | # try to save o'natural 707 | imgfn = "%s_bw.bmp" % args.name 708 | svgfn = "%s_bw.svg" % args.name 709 | lattice.save_image(imgfn, scheme=BlackWhite(lattice), resize=resize, margin=1) 710 | potrace(svgfn, imgfn, turd=2000) 711 | if not check_basecut(svgfn): 712 | msg = "There are disconnected elements in the base cut, turning on boundary layer." 713 | log(msg) 714 | lattice.save_image(imgfn, bw=True, boundary=True) 715 | potrace(svgfn, imgfn, turd=2000) 716 | assert check_basecut(svgfn), "Despite best efforts, base cut is still non-contiguous." 717 | # 718 | epsfn = "%s_3d.eps" % args.name 719 | dxffn = "%s_3d.dxf" % args.name 720 | cmd = "potrace -M .1 --tight -i -b eps -o %s %s" % (epsfn, imgfn) 721 | msg = "Running '%s'" % cmd 722 | log(msg) 723 | os.system(cmd) 724 | # 725 | cmd = "pstoedit -dt -f dxf:-polyaslines %s %s" % (epsfn, dxffn) 726 | msg = "Running '%s'" % cmd 727 | log(msg) 728 | os.system(cmd) 729 | # 730 | scad_fn = "%s_3d.scad" % args.name 731 | stlfn = "%s_3d.stl" % args.name 732 | f = open(scad_fn, 'w') 733 | scad_txt = 'scale([30, 30, 30]) linear_extrude(height=.18, layer="0") import("%s");\n' % dxffn 734 | f.write(scad_txt) 735 | f.close() 736 | cmd = "/Applications/OpenSCAD.app/Contents/MacOS/OpenSCAD -o %s %s" % (stlfn, scad_fn) 737 | msg = "Running '%s'" % cmd 738 | log(msg) 739 | os.system(cmd) 740 | # 741 | cmd = "python /Applications/Cura/Cura.app/Contents/Resources/Cura/cura.py -s %s -i %s" % (stlfn, SNOWFLAKE_INI) 742 | msg = "Running '%s'" % cmd 743 | log(msg) 744 | os.system(cmd) 745 | 746 | SNOWFLAKE_DEFAULTS = { 747 | "size": 200, 748 | "name": "snowflake", 749 | "bw": False, 750 | "env": '', 751 | "pipeline_3d": False, 752 | "pipeline_lasercutter": False, 753 | "randomize": False, 754 | "max_steps": 0, 755 | "margin": .85, 756 | "curves": False, 757 | "datalog": False, 758 | "debug": False, 759 | "movie": False, 760 | } 761 | 762 | def run(args): 763 | log_output(args.name) 764 | msg = "Snowflake Generator v0.3" 765 | log(msg) 766 | pfn = "%s.pickle" % args.name 767 | ifn = "%s.png" % args.name 768 | if os.path.exists(pfn): 769 | cl = CrystalLattice.load_lattice(pfn) 770 | #cl.save_image(ifn, bw=args.bw) 771 | #cl.save_image(ifn) 772 | else: 773 | kw = {} 774 | if args.env: 775 | mods = {key: float(val) for (key, val) in [keyval.split('=') for keyval in args.env.split(',')]} 776 | env = CrystalEnvironment(mods) 777 | kw["environment"] = env 778 | elif args.randomize: 779 | env = CrystalEnvironment() 780 | env.randomize() 781 | msg = str.join(', ', ["%s=%.6f" % (key, env[key]) for key in env]) 782 | log(msg) 783 | kw["environment"] = env 784 | elif args.curves: 785 | env = CrystalEnvironment.build_env(args.name, 50000) 786 | kw["environment"] = env 787 | kw["max_steps"] = args.max_steps 788 | kw["margin"] = args.margin 789 | kw["datalog"] = args.datalog 790 | kw["debug"] = args.debug 791 | cl = CrystalLattice(args.size, **kw) 792 | try: 793 | cl.grow() 794 | finally: 795 | cl.write_log() 796 | cl.save_lattice(pfn) 797 | #cl.save_image(ifn, bw=args.bw) 798 | cl.save_image(ifn) 799 | if args.pipeline_3d: 800 | pipeline_3d(args, cl) 801 | if args.pipeline_lasercutter: 802 | pipeline_lasercutter(args, cl) 803 | if args.movie: 804 | movie = RenderMovie(args.name) 805 | movie.run() 806 | -------------------------------------------------------------------------------- /src/etc/hist.gplot: -------------------------------------------------------------------------------- 1 | reset 2 | n=400 #number of intervals 3 | max=3. #max value 4 | min=-3. #min value 5 | width=(max-min)/n #interval width 6 | #function used to map a value to the intervals 7 | hist(x,width)=width*floor(x/width)+width/2.0 8 | set term png #output terminal and file 9 | set output "histogram.png" 10 | set xrange [min:max] 11 | set yrange [0:] 12 | #to put an empty boundary around the 13 | #data inside an autoscaled graph. 14 | set offset graph 0.05,0.05,0.05,0.0 15 | set xtics min,(max-min)/5,max 16 | set boxwidth width*0.9 17 | set style fill solid 0.5 #fillstyle 18 | set tics out nomirror 19 | set xlabel "x" 20 | set ylabel "Frequency" 21 | #count and plot 22 | plot "data.dat" u (hist($1,width)):(1.0) smooth freq w boxes lc rgb"green" notitle 23 | -------------------------------------------------------------------------------- /src/etc/snowflake.ini: -------------------------------------------------------------------------------- 1 | [profile] 2 | filament_diameter = 2.89 3 | machine_center_x = 100 4 | machine_center_y = 100 5 | flip_x = False 6 | flip_y = False 7 | flip_z = False 8 | swap_xz = False 9 | swap_yz = False 10 | model_scale = 1 11 | model_rotate_base = 0 12 | layer_height = 0.1 13 | wall_thickness = 1.2 14 | retraction_enable = False 15 | solid_layer_thickness = 0.6 16 | fill_density = 100 17 | skirt_line_count = 5 18 | skirt_gap = 3.0 19 | print_speed = 50 20 | print_temperature = 190 21 | support = None 22 | enable_raft = False 23 | filament_density = 1.00 24 | nozzle_size = 0.4 25 | retraction_min_travel = 5.0 26 | retraction_speed = 40.0 27 | retraction_amount = 4.5 28 | retraction_extra = 0.0 29 | travel_speed = 150 30 | max_z_speed = 3.0 31 | bottom_layer_speed = 15 32 | cool_min_layer_time = 10 33 | fan_enabled = True 34 | bottom_thickness = 0.3 35 | enable_skin = False 36 | plugin_config = 37 | 38 | [alterations] 39 | start.gcode = ;Sliced {filename} at: {day} {date} {time} 40 | ;Basic settings: Layer height: {layer_height} Walls: {wall_thickness} Fill: {fill_density} 41 | ;Print time: {print_time} 42 | ;Filament used: {filament_amount}m {filament_weight}g 43 | ;Filament cost: {filament_cost} 44 | G21 ;metric values 45 | G90 ;absolute positioning 46 | M107 ;start with the fan off 47 | 48 | G28 X0 Y0 ;move X/Y to min endstops 49 | G28 Z0 ;move Z to min endstops 50 | G92 X0 Y0 Z0 E0 ;reset software position to front/left/z=0.0 51 | 52 | G1 Z15.0 F{max_z_speed} ;move the platform down 15mm 53 | 54 | G92 E0 ;zero the extruded length 55 | G1 F200 E3 ;extrude 3mm of feed stock 56 | G92 E0 ;zero the extruded length again 57 | 58 | ;go to the middle of the platform (disabled, as there is no need to go to the center) 59 | ;G1 X{machine_center_x} Y{machine_center_y} F{travel_speed} 60 | G1 F{travel_speed} 61 | 62 | 63 | -------------------------------------------------------------------------------- /src/graphics.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env pypy 2 | 3 | import random 4 | import math 5 | import argparse 6 | import cPickle as pickle 7 | import logging 8 | import os 9 | import sys 10 | import re 11 | import colorsys 12 | from xml.dom.minidom import parse 13 | 14 | try: 15 | import Image 16 | import ImageDraw 17 | except ImportError: 18 | from PIL import Image 19 | from PIL import ImageDraw 20 | 21 | from sfgen import * 22 | 23 | class ColorScheme(object): 24 | Name = "__ColorScheme__" 25 | 26 | def __init__(self, lattice): 27 | self.lattice = lattice 28 | self.cells = self.lattice.cells 29 | 30 | def __call__(self, cell, **kw): 31 | pass 32 | 33 | class Grayscale(ColorScheme): 34 | Name = "grayscale" 35 | 36 | def __init__(self, lattice, boundary=False): 37 | self.boundary = boundary 38 | super(Grayscale, self).__init__(lattice) 39 | 40 | def __call__(self, cell, **kw): 41 | mass = cell.diffusive_mass 42 | if cell.attached: 43 | mass = cell.crystal_mass 44 | color = 200 * mass 45 | color = min(255, int(color)) 46 | color = (color, color, color) 47 | return color 48 | 49 | class BlackWhite(ColorScheme): 50 | Name = "blackwhite" 51 | 52 | def __init__(self, lattice, boundary=False): 53 | self.boundary = boundary 54 | super(BlackWhite, self).__init__(lattice) 55 | 56 | def __call__(self, cell): 57 | color = (0, 0, 0) 58 | if self.boundary and cell.boundary or cell.attached: 59 | color = (0xFF, 0xFF, 0xFF) 60 | return color 61 | 62 | class Colorful(ColorScheme): 63 | Name = "colorful" 64 | 65 | def __call__(self, cell, **kw): 66 | return tuple([int(round(x * 0xff)) for x in colorsys.hsv_to_rgb(cell.age / float(self.lattice.iteration), 1, 1)]) 67 | 68 | class LaserScheme(ColorScheme): 69 | Name = "laser" 70 | 71 | def __init__(self, lattice, layers, scheme=None): 72 | self.layers = layers 73 | self.layer = 0 74 | super(LaserScheme, self).__init__(lattice) 75 | self._init_clusters() 76 | self.scheme = scheme 77 | if self.scheme == None: 78 | self.scheme = BlackWhite(self.lattice) 79 | 80 | def select_layer(self, layer): 81 | self.layer = layer 82 | 83 | def _init_clusters(self): 84 | import scipy.cluster.vq 85 | import numpy 86 | acells = [cell for cell in self.cells if cell and cell.attached] 87 | fm = [cell.crystal_mass for cell in acells] 88 | clusters = scipy.cluster.vq.kmeans2(numpy.array(fm), self.layers) 89 | cluster_map = clusters[1] 90 | self._layer_cache = {cell.xy: cluster for (cell, cluster) in zip(acells, cluster_map)} 91 | 92 | def __call__(self, cell, layer=None, **kw): 93 | if layer == None: 94 | layer = self.layer 95 | if cell.xy in self._layer_cache and self._layer_cache[cell.xy] == layer: 96 | return self.scheme(cell, **kw) 97 | return (0, 0, 0) 98 | 99 | class RenderSnowflake(object): 100 | ColorSchemes = {cls.Name: cls for cls in globals().values() if type(cls) == type and issubclass(cls, ColorScheme) and cls != ColorScheme} 101 | 102 | def __init__(self, lattice): 103 | self.lattice = lattice 104 | self.cells = self.lattice.cells 105 | 106 | def save_layer(self, fn, scheme, layer, **kw): 107 | scheme.select_layer(layer) 108 | self.save_image(fn, scheme=scheme, **kw) 109 | 110 | def save_layers(self, fn, layers, scheme=None, **kw): 111 | if scheme == None: 112 | scheme = LaserScheme(self.lattice, layers) 113 | fnlist = [] 114 | for layer in range(layers): 115 | _fn = fn % layer 116 | fnlist.append(_fn) 117 | self.save_layer(_fn, scheme, layer, **kw) 118 | return fnlist 119 | 120 | def save_image(self, fn, scheme=None, overwrite=True, rotate=True, scale=True, crop=True, resize=None, margin=None): 121 | if not overwrite and os.path.exists(fn): 122 | return 123 | if scheme == None: 124 | scheme = Grayscale(self.lattice) 125 | #scheme = Colorful(self.lattice) 126 | msg = "Saving %s..." % fn 127 | log(msg) 128 | content = str.join('', [str.join('', map(chr, scheme(cell))) for cell in self.cells]) 129 | img = Image.new("RGB", (self.lattice.size, self.lattice.size)) 130 | img.frombytes(content) 131 | del content 132 | 133 | # post-process 134 | if rotate: 135 | img = img.rotate(45) 136 | if scale: 137 | img = img.resize((int(round(self.lattice.size * X_SCALE_FACTOR)), int(self.lattice.size))) 138 | if crop: 139 | img = img.crop(self.lattice.crop_snowflake(margin=margin)) 140 | if resize: 141 | y_sz = int(round((resize / float(img.size[0])) * img.size[1])) 142 | if y_sz != resize: 143 | print "WARNING: image after resize is not square." 144 | img = img.resize((resize, resize)) 145 | img.save(fn) 146 | -------------------------------------------------------------------------------- /src/movie.py: -------------------------------------------------------------------------------- 1 | class RenderMovie(object): 2 | def __init__(self, name): 3 | self.name = name 4 | self.replay = LatticeReplay(name) 5 | 6 | def run(self): 7 | if not os.path.exists("frames"): 8 | os.mkdir("frames") 9 | x = iter(self.replay) 10 | for (idx, frame) in enumerate(self.replay): 11 | fn = "frames/%s_%09d.png" % (self.name, idx + 1) 12 | frame.save_image(fn) 13 | 14 | class LatticeReplay(object): 15 | class ReplayIterator(object): 16 | def __init__(self, replay): 17 | self.replay = replay 18 | self.idx = 0 19 | 20 | def next(self): 21 | try: 22 | lattice = self.replay.get_lattice(self.idx) 23 | self.idx += 1 24 | return lattice 25 | except IndexError: 26 | raise StopIteration 27 | 28 | def __init__(self, name): 29 | self.name = name 30 | self.current_frame = None 31 | self.current_replay = None 32 | pfn = "%s.pickle" % self.name 33 | self.lattice = CrystalLattice.load_lattice(pfn) 34 | self.scan_replays() 35 | 36 | def __iter__(self): 37 | return self.ReplayIterator(self) 38 | 39 | def get_lattice(self, step): 40 | (step, dm, cm) = self.get_step(step) 41 | for (idx, cell) in enumerate(zip(dm, cm)): 42 | self.lattice.cells[idx].diffusive_mass = cell[0] 43 | self.lattice.cells[idx].crystal_mass = cell[1] 44 | self.lattice.cells[idx].attached = bool(cell[1]) 45 | for cell in self.lattice.cells: 46 | cell.update_boundary() 47 | return self.lattice 48 | 49 | def get_step(self, step): 50 | idx = bisect.bisect_left(self.replay_map, step + 1) 51 | if self.current_frame != idx or not self.current_replay: 52 | self.current_frame = idx 53 | fn = self.replays[self.current_frame] 54 | print "loading", fn 55 | f = open(fn) 56 | self.current_replay = pickle.load(f) 57 | offset = self.current_replay[0][0] 58 | return self.current_replay[step - offset] 59 | 60 | def scan_replays(self): 61 | replays = [] 62 | fn_re = re.compile("cell_log_(\d+).pickle") 63 | for fn in os.listdir('.'): 64 | m = fn_re.search(fn) 65 | if m: 66 | step = int(m.group(1)) 67 | replays.append((fn, step)) 68 | replays.sort(key=operator.itemgetter(1)) 69 | self.replays = [rp[0] for rp in replays] 70 | self.replay_map = [rp[1] for rp in replays] 71 | 72 | 73 | -------------------------------------------------------------------------------- /src/render.py: -------------------------------------------------------------------------------- 1 | def check_basecut(svgfn): 2 | # ensure there is only one path 3 | svg = parse(svgfn) 4 | for (cnt, node) in enumerate(svg.getElementsByTagName("path")): 5 | if cnt > 0: 6 | return False 7 | return True 8 | 9 | def merge_svg(file_list, color_list, outfn): 10 | first = None 11 | idx = 0 12 | for (svgfn, color) in zip(file_list, color_list): 13 | svg = parse(svgfn) 14 | for node in svg.getElementsByTagName("g"): 15 | if idx == 0: 16 | # cut layer 17 | # write a new group 18 | container = svg.createElement("g") 19 | container.setAttribute("transform", node.attributes["transform"].nodeValue) 20 | node.parentNode.replaceChild(container, node) 21 | container.appendChild(node) 22 | node.attributes["fill"] = "none" 23 | node.attributes["stroke"] = "rgb(0, 0, 255)" 24 | node.attributes["stroke-opacity"] = "1" 25 | node.attributes["stroke-width"] = ".01mm" 26 | else: 27 | node.attributes["fill"] = color 28 | del node.attributes["transform"] 29 | idx += 1 30 | import_nodes = svg.importNode(node, True) 31 | container.appendChild(import_nodes) 32 | if first == None: 33 | first = svg 34 | f = open(outfn, 'w') 35 | f.write(first.toxml()) 36 | 37 | def potrace(svgfn, fn, turd=None, size=None): 38 | cmd = ["potrace", "-i", "-b", "svg"] 39 | if turd != None: 40 | cmd.extend(["-t", str(turd)]) 41 | if size != None: 42 | sz = map(str, size) 43 | cmd.extend(["-W", sz[0], "-H", sz[1]]) 44 | cmd.extend(["-o", svgfn, fn]) 45 | cmd = str.join(' ', cmd) 46 | msg = "Running '%s'" % cmd 47 | log(msg) 48 | os.system(cmd) 49 | 50 | # laser cutter pipeline 51 | def pipeline_lasercutter(args, lattice, inches=3, dpi=96, turd=10): 52 | # layers 53 | rs = RenderSnowflake(lattice) 54 | name = str.join('', [c for c in args.name if c.islower()]) 55 | size = args.target_size 56 | layerfn = "%s_layer_%%d.bmp" % name 57 | resize = inches * dpi 58 | fnlist = rs.save_layers(layerfn, 2, resize=resize, margin=1) 59 | # we want to drop the heaviest layer 60 | del fnlist[0] 61 | # try to save o'natural 62 | imgfn = "%s_bw.bmp" % name 63 | svgfn = "%s_bw.svg" % name 64 | lattice.save_image(imgfn, scheme=BlackWhite(lattice), resize=resize, margin=1) 65 | potrace(svgfn, imgfn, turd=2000) 66 | if not check_basecut(svgfn): 67 | msg = "There are disconnected elements in the base cut, turning on boundary layer." 68 | log(msg) 69 | lattice.save_image(imgfn, scheme=BlackWhite(lattice, boundary=True), resize=resize, margin=1) 70 | potrace(svgfn, imgfn, turd=2000) 71 | assert check_basecut(svgfn), "Despite best efforts, base cut is still non-contiguous." 72 | os.unlink(svgfn) 73 | fnlist.insert(0, imgfn) 74 | # adjusted for ponoko 75 | # cut layer is blue 76 | # etch layer are black, or shades of grey 77 | colors = ["#000000", "#111111", "#222222", "#333333", "#444444", "#555555"] 78 | svgs = [] 79 | for (idx, fn) in enumerate(fnlist): 80 | svgfn = os.path.splitext(fn)[0] 81 | svgfn = "%s_laser.svg" % svgfn 82 | svgs.append(svgfn) 83 | if idx == 0: 84 | potrace(svgfn, fn, turd=turd, size=size) 85 | else: 86 | potrace(svgfn, fn, size=size) 87 | svgfn = "%s_laser_merged.svg" % name 88 | epsfn = "%s_laser_merged.eps" % name 89 | merge_svg(svgs, colors, svgfn) 90 | """ 91 | # move to eps 92 | cmd = "%s %s -E %s" % (InkscapePath, svgfn, epsfn) 93 | msg = "Running '%s'" % cmd 94 | log(msg) 95 | os.system(cmd) 96 | """ 97 | 98 | # 3d pipeline 99 | def pipeline_3d(args, lattice, inches=3, dpi=96, turd=10): 100 | resize = inches * dpi 101 | # try to save o'natural 102 | imgfn = "%s_bw.bmp" % args.name 103 | svgfn = "%s_bw.svg" % args.name 104 | lattice.save_image(imgfn, scheme=BlackWhite(lattice), resize=resize, margin=1) 105 | potrace(svgfn, imgfn, turd=2000) 106 | if not check_basecut(svgfn): 107 | msg = "There are disconnected elements in the base cut, turning on boundary layer." 108 | log(msg) 109 | lattice.save_image(imgfn, bw=True, boundary=True) 110 | potrace(svgfn, imgfn, turd=2000) 111 | assert check_basecut(svgfn), "Despite best efforts, base cut is still non-contiguous." 112 | # 113 | epsfn = "%s_3d.eps" % args.name 114 | dxffn = "%s_3d.dxf" % args.name 115 | cmd = "potrace -M .1 --tight -i -b eps -o %s %s" % (epsfn, imgfn) 116 | msg = "Running '%s'" % cmd 117 | log(msg) 118 | os.system(cmd) 119 | # 120 | cmd = "pstoedit -dt -f dxf:-polyaslines %s %s" % (epsfn, dxffn) 121 | msg = "Running '%s'" % cmd 122 | log(msg) 123 | os.system(cmd) 124 | # 125 | scad_fn = "%s_3d.scad" % args.name 126 | stlfn = "%s_3d.stl" % args.name 127 | f = open(scad_fn, 'w') 128 | scad_txt = 'scale([30, 30, 30]) linear_extrude(height=.18, layer="0") import("%s");\n' % dxffn 129 | f.write(scad_txt) 130 | f.close() 131 | cmd = "/Applications/OpenSCAD.app/Contents/MacOS/OpenSCAD -o %s %s" % (stlfn, scad_fn) 132 | msg = "Running '%s'" % cmd 133 | log(msg) 134 | os.system(cmd) 135 | # 136 | cmd = "python /Applications/Cura/Cura.app/Contents/Resources/Cura/cura.py -s %s -i %s" % (stlfn, SNOWFLAKE_INI) 137 | msg = "Running '%s'" % cmd 138 | log(msg) 139 | os.system(cmd) 140 | 141 | SNOWFLAKE_DEFAULTS = { 142 | "size": 200, 143 | "name": "snowflake", 144 | "bw": False, 145 | "env": '', 146 | "pipeline_3d": False, 147 | "pipeline_lasercutter": False, 148 | "randomize": False, 149 | "max_steps": 0, 150 | "margin": .85, 151 | "curves": False, 152 | "datalog": False, 153 | "debug": False, 154 | "movie": False, 155 | } 156 | 157 | 158 | -------------------------------------------------------------------------------- /src/runner.py: -------------------------------------------------------------------------------- 1 | def run(args): 2 | log_output(args.name) 3 | msg = "Snowflake Generator v0.3" 4 | log(msg) 5 | pfn = "%s.pickle" % args.name 6 | ifn = "%s.png" % args.name 7 | if os.path.exists(pfn): 8 | cl = CrystalLattice.load_lattice(pfn) 9 | #cl.save_image(ifn, bw=args.bw) 10 | #cl.save_image(ifn) 11 | else: 12 | kw = {} 13 | if args.env: 14 | mods = {key: float(val) for (key, val) in [keyval.split('=') for keyval in args.env.split(',')]} 15 | env = CrystalEnvironment(mods) 16 | kw["environment"] = env 17 | elif args.randomize: 18 | env = CrystalEnvironment() 19 | env.randomize() 20 | msg = str.join(', ', ["%s=%.6f" % (key, env[key]) for key in env]) 21 | log(msg) 22 | kw["environment"] = env 23 | elif args.curves: 24 | env = CrystalEnvironment.build_env(args.name, 50000) 25 | kw["environment"] = env 26 | kw["max_steps"] = args.max_steps 27 | kw["margin"] = args.margin 28 | kw["datalog"] = args.datalog 29 | kw["debug"] = args.debug 30 | cl = CrystalLattice(args.size, **kw) 31 | try: 32 | cl.grow() 33 | finally: 34 | cl.write_log() 35 | cl.save_lattice(pfn) 36 | #cl.save_image(ifn, bw=args.bw) 37 | cl.save_image(ifn) 38 | if args.pipeline_3d: 39 | pipeline_3d(args, cl) 40 | if args.pipeline_lasercutter: 41 | pipeline_lasercutter(args, cl) 42 | if args.movie: 43 | movie = RenderMovie(args.name) 44 | movie.run() 45 | 46 | -------------------------------------------------------------------------------- /src/splines.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | 3 | """A simple splines package. 4 | 5 | This package aims to be simple to use, but also simple to read - it's as much an exegesis on splines as it is a package. It's also developed to support somewhat more general exploratory programming with curves, so it supports slightly peripheral things like the ability to fit low-order polynomials to sequences of points. 6 | 7 | Note that the only code in the package which knows the dimensionality of the space being worked in is the Point class. This code is two-dimensional, but a few changes to Point would make it three-dimensional. 8 | 9 | Performance is almost certainly execrable. 10 | 11 | (c) 2005 Tom Anderson - all rights reserved 12 | 13 | Redistribution and use in source and binary forms, with or without modification, are permitted. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 16 | 17 | """ 18 | 19 | """todo: 20 | 21 | - make calculation of multiple points more efficient; support a form of call which evaluates over a range or an iteratable of u values 22 | - allow subclasses to handle this themselves, so they can cache intermediate results 23 | - support slices in the spline subclasses, maybe - do this by supporting slice objects as parameters to __getitem__ 24 | - does list() use slices? in 2.2, it appears to use getitem with scalar indices - and not to use len, but to rely on getting an IndexError! 25 | 26 | """ 27 | 28 | # excuse this - i'm on 2.2 here 29 | try: 30 | True 31 | except NameError: 32 | True = 1 == 1 33 | False = 1 == 0 34 | 35 | def tuples2points(ts): 36 | return map(lambda t: Point(*t), ts) 37 | 38 | class Point(object): 39 | "A point. Actually a somewhat general vector, but never mind. Implements more behaviour than it strictly needs to." 40 | __slots__ = ("x", "y") 41 | def __init__(self, x, y): 42 | self.x = float(x) 43 | self.y = float(y) 44 | def __add__(self, other): 45 | return Point((self.x + other.x), (self.y + other.y)) 46 | def __sub__(self, other): 47 | return Point((self.x - other.x), (self.y - other.y)) 48 | def __mul__(self, factor): 49 | return Point((self.x * factor), (self.y * factor)) 50 | def __div__(self, factor): 51 | return Point((self.x / factor), (self.y / factor)) 52 | def __neg__(self): 53 | return Point(-(self.x), -(self.y)) 54 | def __abs__(self): 55 | "Computes the magnitude of the vector." 56 | return (~self) ** 0.5 57 | def __xor__(self, other): 58 | "Computes the dot product. If you look at '^' long enough, it looks like '.'. You don't think so? Keep looking." 59 | return (self.x * other.x) + (self.y * other.y) 60 | def __invert__(self): 61 | "Computes the dot-square, ie the dot product of the point with itself. Hey, if the xor operator can mean dot, the complement operator can mean this!" 62 | return (self.x ** 2) + (self.y ** 2) 63 | def __hash__(self): 64 | return hash(self.x) ^ hash(self.y) 65 | def __cmp__(self, other): 66 | "This is a pretty arbitrary order, but i'm told i have to have one." 67 | d = cmp(self.x, other.x) 68 | if (d == 0): d = cmp(self.y, other.y) 69 | return d 70 | def __iter__(self): 71 | "A bit of a funny method, this; the idea is to make it possible to do tuple(p)." 72 | # if you have a python which supports generators (unlike me), uncomment these lines and comment out or delete the last line 73 | # yield self.x 74 | # yield self.y 75 | return iter((self.x, self.y)) 76 | def __nonzero__(self): 77 | return (self.x != 0.0) and (self.y != 0.0) 78 | def __str__(self): 79 | return "(" + str(self.x) + "," + str(self.y) + ")" 80 | def __repr__(self): 81 | return "splines.Point(" + str(self.x) + ", " + str(self.y) + ")" 82 | 83 | class Curve(object): 84 | "A curve describes a continuous sequence of points. Points are discriminated by their distance along the curve, which is measured in a vague, dimensionless way, using a coordinate called 'u'; a given value of u corresponds to a particular point p on the curve. Individual coordinates of p do not necessarily vary monotonically with u, and multiple values of u may map to the same value of p, if the curve crosses itself. I think this is called a 'parametric' curve. In this package, curves are maps from u to p, not from x to y. This is very important!" 85 | def __call__(self, u): 86 | "Finds the position of the curve at coordinate u." 87 | raise NotImplementedError 88 | 89 | class Line(Curve): 90 | "A straight line." 91 | def __init__(self, a, b): 92 | "a and b are the 0th and 1st order coefficients, respectively; the equation of the curve is p = a + ub. The coefficients are all points." 93 | self.a = a 94 | self.b = b 95 | def __call__(self, u): 96 | return self.a + (self.b * u) 97 | def __str__(self): 98 | return str(self.a) + " + " + str(self.b) + "u" 99 | def __repr__(self): 100 | return "splines.Line(" + repr(self.a) + ", " + repr(self.b) + ")" 101 | def fit(p, q): 102 | "Fits a line to two points, so that at u = 0, it passes through p, and at u = 1, it passes through q." 103 | a = p 104 | b = (q - p) 105 | return Line(a, b) 106 | fit = staticmethod(fit) 107 | 108 | class Quadratic(Curve): 109 | "A quadratic curve." 110 | def __init__(self, a, b, c): 111 | "a, b and c are the 0th, 1st and 2nd order coefficients, respectively; the equation of the curve is p = a + ub + (u**2)c. The coefficients are all points." 112 | self.a = a 113 | self.b = b 114 | self.c = c 115 | def __call__(self, u): 116 | return self.a + (self.b * u) + (self.c * (u ** 2)) 117 | def __str__(self): 118 | return str(self.a) + " + " + str(self.b) + "u + " + str(self.c) + "u**2" 119 | def __repr__(self): 120 | return "splines.Quadratic(" + repr(self.a) + ", " + repr(self.b) + ", " + repr(self.c) + ")" 121 | def fit(o, p, q): 122 | "Fits a quadratic to three points, so that it passes through o, p and q at u = -1, 0 and 1, respectively." 123 | a = p 124 | b = (q - o) / 2.0 125 | c = ((q + o) / 2.0) - p 126 | return Quadratic(a, b, c) 127 | fit = staticmethod(fit) 128 | 129 | class Cubic(Curve): 130 | "A cubic curve." 131 | def __init__(self, a, b, c, d): 132 | "a, b, c and d are the 0th, 1st, 2nd and 3rd order coefficients, respectively; the equation of the curve is p = a + ub + (u**2)c + (u**3)d. The coefficients are all points." 133 | self.a = a 134 | self.b = b 135 | self.c = c 136 | self.d = d 137 | def __call__(self, u): 138 | return self.a + (self.b * u) + (self.c * (u ** 2)) + (self.d * (u ** 3)) 139 | def __str__(self): 140 | return str(self.a) + " + " + str(self.b) + "u + " + str(self.c) + "u**2 + " + str(self.d) + "u**3" 141 | def __repr__(self): 142 | return "splines.Cubic(" + repr(self.a) + ", " + repr(self.b) + ", " + repr(self.c) + ", " + repr(self.d) + ")" 143 | def fit(o, p, q, r): 144 | "Fits a cubic to four points, so that it passes through o, p, q and r at u = -1, 0, 1 and 2, respectively." 145 | # there's probably a slightly simpler way of working this out ... 146 | a = p 147 | c = ((o + q) / 2.0) - a 148 | d = (((r - (q * 2.0)) + a) - (c * 2.0)) / 6.0 149 | b = ((q - o) / 2.0) - d 150 | return Cubic(a, b, c, d) 151 | fit = staticmethod(fit) 152 | 153 | class Spline(Curve): 154 | "A spline is a curve defined be a sequence of knots, each knot being a point, and a way of drawing a curve between these points. Some splines pass exactly through their knots; others do not. The spline may not reach all the knots at its ends: these are known as loose knots (the knots which are reached being tight knots), and the number at each end is fixed for splines of a given type; if a spline has loose knots, it has a property called 'looseKnots' giving the number at either end. The parametric coordinate for splines is defined to be 0 at their first knot, increasing by 1 at each knot. The sequence of knots is exposed as the member variable 'knots', which can be manipulated directly. Note that splines generally require at least three tight knots to work; don't expect sensible behaviour from smaller splines." 155 | def __init__(self, knots=None): 156 | if (knots != None): 157 | self.knots = list(knots) 158 | else: 159 | self.knots = [] 160 | def __repr__(self): 161 | return type(self).__name__ + "([" + ", ".join(self.knots) + "])" 162 | 163 | class PiecewiseSpline(Spline): 164 | "A piecewise spline is one in which the curve is drawn by constructing 'pieces', curves which connect adjacent knots. The spline is simply the concatenation of the pieces." 165 | def __getitem__(self, i): 166 | "Get the ith piece of the spline (the piece valid from i <= u < (i + 1)). A position u on the spline corresponds to a position v = (u - i) on the piece. An index i is valid iff 0 <= i < len(self)." 167 | raise NotImplementedError 168 | def __call__(self, u): 169 | if (u < 0.0): 170 | i = 0 171 | elif (u >= len(self)): 172 | i = len(self) - 1 173 | else: 174 | i = int(u) 175 | v = u - i 176 | return self[i](v) 177 | def __len__(self): 178 | "The length of a spline is the number of pieces in it, which is one less than the number of tight knots." 179 | return len(self.knots) - ((2 * getattr(self, "looseKnots", 0)) + 1) 180 | 181 | class Polyline(PiecewiseSpline): 182 | "The simplest possible spline!" 183 | def __getitem__(self, i): 184 | return Line.fit(self.knots[i], self.knots[(i + 1)]) 185 | 186 | class NaturalCubicSpline(PiecewiseSpline): 187 | "The daddy! Since the calculation of the pieces is fairly heavy work, we cache the results." 188 | def __init__(self, knots=None): 189 | Spline.__init__(self, knots) 190 | self.cachedknots = None 191 | def __getitem__(self, i): 192 | if (self.knots != self.cachedknots): 193 | self.calculate() 194 | return self.pieces[i] 195 | def calculate(self): 196 | "This code is ultimately derived from some written by Tim Lambert. Cheers Tim." 197 | # you are not expected to understand this 198 | # i certainly don't 199 | p = self.knots 200 | self.cachedknots = list(p) 201 | g = _gamma(len(p)) 202 | e = _epsilon(g, _delta(p, g)) 203 | self.pieces = [] 204 | for i in range((len(p) - 1)): 205 | a = p[i] 206 | b = e[i] 207 | c = ((p[(i + 1)] - p[i]) * 3.0) - ((e[i] * 2.0) + e[(i + 1)]) 208 | d = ((p[i] - p[(i + 1)]) * 2.0) + e[i] + e[(i + 1)] 209 | self.pieces.append(Cubic(a, b, c, d)) 210 | # i am always absolutely outraged that this voodoo works! 211 | 212 | # don't ask what happened to alpha and beta 213 | 214 | def _gamma(n): 215 | g = [0.5] 216 | for i in range(1, (n - 1)): 217 | g.append((1.0 / (4.0 - g[(i - 1)]))) 218 | g.append((1.0 / (2.0 - g[-1]))) 219 | return g 220 | 221 | def _delta(p, g): 222 | d = [((p[1] - p[0]) * (g[0] * 3.0))] 223 | for i in range(1, (len(p) - 1)): 224 | d.append(((((p[(i + 1)] - p[(i - 1)]) * 3.0) - d[(i - 1)]) * g[i])) 225 | d.append(((((p[-1] - p[-2]) * 3.0) - d[-1]) * g[-1])) 226 | return d 227 | 228 | def _epsilon(g, d): 229 | # todo: would be nice to find a way to do this without the reverse 230 | # the original code built an empty length-len(g) list, then filled it backwards 231 | e = [d[-1]] 232 | for i in range((len(d) - 2), -1, -1): 233 | e.append((d[i] - (e[-1] * g[i]))) 234 | e.reverse() 235 | return e 236 | 237 | class BlendedSpline(Spline): 238 | "A blended spline is composed of a sequence of tangent curves which sit at the knots; the overall shape of the curve is found by blending together the tangents. The blending is such that, where the tangent to a knot k is given by tan(k), between two knots k and l, at a distance v from k, the curve is (1 - v) * tan(k) + v * tan(l); note that it follows from this that at a knot k, the curve is exactly equal to tan(k)." 239 | def __getitem__(self, i): 240 | "Get the ith tangent of the spline (the tangent which applies from (i - 1) < u < (i + 1)). A position u on the spline corresponds to a position v = (u - i) on the piece. An index i is valid iff 0 <= i < len(self)." 241 | raise NotImplementedError 242 | def __call__(self, u): 243 | if (u < 0.0): 244 | return self[0](u) 245 | elif (u >= (len(self) - 1)): 246 | l = len(self) - 1 247 | return self[l]((u - l)) 248 | else: 249 | i = int(u) 250 | v = u - i 251 | w = 1 - v 252 | return (self[i](v) * w) + (self[(i + 1)](-w) * v) 253 | def __len__(self): 254 | "The length of a blended spline is the number of tangents in it, which is equal to the number of tight knots." 255 | return len(self.knots) - (2 * getattr(self, "looseKnots", 0)) 256 | 257 | class BlendedQuadraticSpline(BlendedSpline): 258 | "Note that tangents are not cached. They probably should be." 259 | def __getitem__(self, i): 260 | if (i == 0): 261 | return Line.fit(self.knots[0], self.knots[1]) 262 | elif (i == (len(self) - 1)): 263 | end = self.knots[-1] 264 | prev = self.knots[-2] 265 | proj = (end * 2.0) - prev 266 | return Line.fit(end, proj) 267 | else: 268 | return Quadratic.fit(self.knots[(i - 1)], self.knots[i], self.knots[(i + 1)]) 269 | 270 | class BlendedQuarticSpline(BlendedSpline): 271 | "I'm not going to implement this, but it would be interesting - hopefully, it would be smoother than the quadratic, which, compared to the natural cubic, is a bit angular." 272 | def __init__(self, knots=None): 273 | raise NotImplementedError 274 | 275 | class BlendedPiecewiseSpline(PiecewiseSpline): 276 | "I'm not going to implement this. This would be another general kind of spline; the way it would work is that, rather than demanding that the pieces fit together smoothly, it only requires they pass through the knots at each end, and then blends adjacent pieces to produce the final curve. A given interval would be derived by blending the piece for that interval with the piece for the nearest adjacent interval; at knots, the curve would be an even mix of the pieces on each side, and halfway between two knots, it would be purely one piece. The obvious piece curve would be a line, but a cubic (fitted to the adjacent knots and the knots on either side) would also be good, and much smoother. Note that such curves would be local, like other blended splines." 277 | def __init__(self, knots=None): 278 | raise NotImplementedError 279 | 280 | def test_spline(splinetype=NaturalCubicSpline, trim=False): 281 | "Fits a spline of some sort through a simple spiral-shaped sequence of knots, and returns a trace along it as a list of 2-tuples. Note that blended splines will have a bit of a tail; set trim to True to trim it off." 282 | knots = [(0, 0), (0, 1), (1, 0), (0, -2), (-3, 0)] # a spiral 283 | points = [] 284 | c = splinetype(tuples2points(knots)) 285 | u = 0.0 286 | du = 0.1 287 | if (trim): 288 | lim = (len(c) - 1) + du 289 | else: 290 | lim = len(c) + du 291 | while (u < lim): 292 | p = c(u) 293 | points.append(tuple(p)) 294 | u = u + du 295 | return points 296 | -------------------------------------------------------------------------------- /utils/aws.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | import boto 4 | from boto.sqs.message import Message 5 | import boto.ec2 6 | import os 7 | import cPickle as pickle 8 | import subprocess 9 | import shlex 10 | 11 | import time 12 | import logging 13 | import socket 14 | 15 | HOSTNAME = str.join('_', socket.gethostname().split('.')) 16 | FUSE_MOUNT = "/tmp/snowflake" 17 | SERVER_DIR = "/tmp/snowflake_server" 18 | 19 | logging.getLogger('boto').setLevel(logging.CRITICAL) 20 | logging.basicConfig(format="%(asctime)s (%(process)d): %(message)s", level=logging.DEBUG, datefmt='%d/%m/%y %H:%M:%S') 21 | logger = logging.getLogger() 22 | 23 | def enable_file_logging(): 24 | logfn = "server_%s.log" % HOSTNAME 25 | logfn = os.path.join(SERVER_DIR, logfn) 26 | foh = logging.FileHandler(logfn) 27 | foh.setLevel(logging.DEBUG) 28 | formatter = logging.Formatter('%(asctime)s (%(process)d): %(message)s', datefmt='%d/%m/%y %H:%M:%S') 29 | foh.setFormatter(formatter) 30 | logger = logging.getLogger() 31 | logger.addHandler(foh) 32 | 33 | def mount_s3fs(mnt): 34 | cmd = "s3fs vishnubob_snowflakes %s -ouse_cache=/tmp" % mnt 35 | os.system(cmd) 36 | 37 | def unmount_s3fs(mnt): 38 | cmd = "fusermount -u %s" % mnt 39 | os.system(cmd) 40 | 41 | def rsync(): 42 | cmd = "rsync -r %s/ %s" % (SERVER_DIR, FUSE_MOUNT) 43 | os.system(cmd) 44 | 45 | class Job(object): 46 | SleepCycle = 60 47 | 48 | def __init__(self, cmd): 49 | self.cmd = cmd 50 | self.proc = None 51 | 52 | def execute(self): 53 | self.proc = subprocess.Popen(self.cmd) 54 | 55 | def kill_job(self): 56 | retry = 10 57 | self.proc.send_signal(signal.SIGINT) 58 | # wait a few minues 59 | while (self.proc.poll() == None) and retry: 60 | time.sleep(self.SleepCycle) 61 | retry -= 1 62 | if not retry: 63 | self.proc.kill() 64 | 65 | def finished(self): 66 | if self.proc == None: 67 | return True 68 | if self.proc.poll() != None: 69 | # XXX: log? 70 | # XXX: clean up? 71 | self.proc = None 72 | return True 73 | return False 74 | 75 | class UpdateSource(Job): 76 | def execute(self): 77 | cwd = os.getcwd() 78 | try: 79 | os.chdir("/tmp") 80 | cmd = "git pull https://github.com/vishnubob/snowflake.git" 81 | os.system(cmd) 82 | cmd = "/tmp/snowflake/utils/deploy.sh" 83 | os.system(cmd) 84 | finally: 85 | os.chdir(cwd) 86 | 87 | def post_process(self): 88 | msg = "Restarting SnowflakeServer" 89 | logging.warning(msg) 90 | args = ["python", "python"] + sys.argv 91 | os.execlp("/usr/bin/env", *args) 92 | rsync(self.name, FUSE_MOUNT) 93 | 94 | class SnowflakeJob(Job): 95 | Command = "snowflake.py -s %(size)s -c %(name)s -L" 96 | 97 | def __init__(self, name, size=200): 98 | self.name = name 99 | self.size = size 100 | self.proc = None 101 | 102 | def execute(self): 103 | kw = {"size": self.size, "name": self.name} 104 | cmd = self.Command % kw 105 | msg = "Executing %s" % cmd 106 | logging.debug(msg) 107 | cmd = shlex.split(cmd) 108 | self.proc = subprocess.Popen(cmd) 109 | 110 | def post_process(self): 111 | rsync(self.name, FUSE_MOUNT) 112 | 113 | class SnowflakeMaster(object): 114 | SleepCycle = 60 115 | 116 | def __init__(self): 117 | self.service = SnowflakeServices() 118 | self.service.add_queue("snowflake") 119 | 120 | def add_work(self, name): 121 | job = SnowflakeJob(name) 122 | self.service.queue_push("snowflake", job) 123 | 124 | class SnowflakeServer(object): 125 | SleepCycle = 10 126 | 127 | def __init__(self): 128 | self.service = SnowflakeServices() 129 | self.service.add_queue("snowflake") 130 | hq = "snowflakehost_%s" % HOSTNAME 131 | self.service.add_queue(hq) 132 | self.service.add_queue("snowflake") 133 | self.running = True 134 | self.current_job = None 135 | #self.service.update_state("booting") 136 | msg = "SnowflakeServer started." 137 | logger.info(msg) 138 | self.logger = logger 139 | 140 | def __del__(self): 141 | hq = "snowflakehost_%s" % HOSTNAME 142 | try: 143 | self.service.delete_queue(hq) 144 | except: 145 | import traceback 146 | traceback.print_exc() 147 | 148 | def do_work(self, work): 149 | msg = "Executing %s" % work 150 | logger.info(msg) 151 | self.current_job = work 152 | self.current_job.execute() 153 | 154 | def check_work(self): 155 | if not self.current_job: 156 | return 157 | if self.current_job.finished(): 158 | msg = "Work finished." 159 | logger.info(msg) 160 | self.current_job.post_process() 161 | self.current_job = None 162 | 163 | def loop(self): 164 | mount_s3fs(FUSE_MOUNT) 165 | try: 166 | while self.running: 167 | if self.current_job: 168 | self.check_work() 169 | else: 170 | msg = "Checking for work." 171 | logger.info(msg) 172 | work = self.service.queue_pull("snowflake") 173 | if work: 174 | self.do_work(work) 175 | time.sleep(self.SleepCycle) 176 | finally: 177 | unmount_s3fs(FUSE_MOUNT) 178 | 179 | class SnowflakeServices(object): 180 | SimpleDB_Domain = "snowflake_db" 181 | 182 | def __init__(self): 183 | self.boto = boto 184 | self.sqs = boto.connect_sqs() 185 | self.queues = {} 186 | self.logger = logger 187 | 188 | def ensure(self, func, *args, **kw): 189 | trycnt = 1 190 | while not func(*args, **kw): 191 | msg = "Try #%d to %s failed, retrying in %s seconds.." % (trycnt, func, self.RetryPause) 192 | logger.debug(msg) 193 | time.sleep(self.RetryPause) 194 | trycnt += 1 195 | 196 | def get_tags(self): 197 | return self.get_this_instance().tags 198 | 199 | def send_message(self, name, obj): 200 | queue = self.queues[name] 201 | m = Message() 202 | m.set_body(pickle.dumps(obj, protocol=-1)) 203 | self.ensure(queue.write, *(m,)) 204 | 205 | def recv_message_filter(self, name, func): 206 | queue = self.queues[name] 207 | rs = queue.get_messages() 208 | if len(rs) == 0: 209 | return 210 | m = rs[0] 211 | # XXX: no ensure? 212 | if delete: 213 | self.delete_message(name, m) 214 | return pickle.loads(m.get_body()) 215 | 216 | def recv_message(self, name, delete=True): 217 | queue = self.queues[name] 218 | rs = queue.get_messages() 219 | if len(rs) == 0: 220 | return 221 | m = rs[0] 222 | # XXX: no ensure? 223 | if delete: 224 | self.delete_message(name, m) 225 | return pickle.loads(m.get_body()) 226 | 227 | def delete_message(self, name, message): 228 | queue = self.queues[name] 229 | self.ensure(queue.delete_message, (message,)) 230 | 231 | def add_queue(self, name): 232 | self.update_queue_cache() 233 | if name not in self.queues: 234 | self.create_queue(name) 235 | return self.queues[name] 236 | 237 | def create_queue(self, name): 238 | self.update_queue_cache() 239 | if name in self.queues: 240 | return self.queues[name] 241 | msg = "Creating SimpleQS queue: %s" % name 242 | logger.info(msg) 243 | self.queues[name] = self.sqs.create_queue(name) 244 | return self.queues[name] 245 | 246 | def delete_queue(self, name): 247 | if name not in self.queues: 248 | return 249 | queue = self.queues[name] 250 | queue.clear() 251 | self.ensure(self.sqs.delete_queue, (queue,)) 252 | 253 | def update_queue_cache(self): 254 | queues = self.sqs.get_all_queues() 255 | self.queues = {q.name: q for q in queues} 256 | 257 | if __name__ == "__main__": 258 | cwd = os.getcwd() 259 | if not os.path.exists(SERVER_DIR): 260 | os.mkdir(SERVER_DIR) 261 | try: 262 | enable_file_logging() 263 | ss = SnowflakeServer() 264 | ss.loop() 265 | finally: 266 | os.chdir(cwd) 267 | -------------------------------------------------------------------------------- /utils/deploy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | $HOME/local/pypy/bin/pypy setup.py install 4 | $HOME/local/python/bin/python setup.py install 5 | -------------------------------------------------------------------------------- /utils/htmlgen.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env pypy 2 | import cPickle as pickle 3 | import os 4 | import Image 5 | from snowflake import * 6 | 7 | class ExampleTable(object): 8 | def __init__(self, pics_per_row=15): 9 | self.ppr = pics_per_row 10 | self.table = [] 11 | self.row = [] 12 | self.load_cache() 13 | 14 | def load_cache(self): 15 | fn = "htmlgen_cache.dill" 16 | try: 17 | f = open(fn) 18 | self._cache = pickle.load(f) 19 | except: 20 | self._cache = {} 21 | 22 | def save_cache(self): 23 | fn = "htmlgen_cache.dill" 24 | f = open(fn, 'w') 25 | pickle.dump(self._cache, f) 26 | 27 | def get(self, name): 28 | if name not in self._cache: 29 | pfn = "%s.pickle" % name 30 | f = open(pfn) 31 | sf = pickle.load(f) 32 | env = dict(sf.environment) 33 | self._cache[name] = env 34 | return self._cache[name] 35 | 36 | def add_example(self, name): 37 | print "Processing %s..." % name 38 | env = self.get(name) 39 | env_keys = env.keys() 40 | env_keys.sort() 41 | #caption = "%s=%s %s=%s" % (env_keys[1], env[env_keys[1]],env_keys[2], env[env_keys[2]]) 42 | caption = ["%s = %s" % (key, env[key]) for key in env_keys] 43 | caption = str.join('\n', caption) 44 | content = '' 45 | content += "%s\n" % name 46 | content += "
\n" 47 | content += "\n" % name 48 | content += "
\n" 49 | content += "

%s\n" % caption 50 | self.add_cell(content) 51 | 52 | def add_cell(self, content): 53 | if len(self.row) >= self.ppr: 54 | self.table.append(self.row) 55 | self.row = [] 56 | self.row.append(content) 57 | 58 | def render(self): 59 | self.save_cache() 60 | if self.row: 61 | self.table.append(self.row) 62 | table = '' 63 | for row in self.table: 64 | html_row = '' 65 | for cell in row: 66 | html_row += "%s\n" % cell 67 | table += "%s\n" % html_row 68 | return "%s 72 | 73 | 77 | Mah Sn0wFl4K3z!@!##@ 78 | %s 79 | 80 | """ 81 | 82 | if __name__ == "__main__": 83 | body = '' 84 | table = ExampleTable() 85 | for fn in os.listdir("."): 86 | if fn.endswith(".pickle"): 87 | table.add_example(fn.split(".")[0]) 88 | html = HTML % table.render() 89 | fn = "snowflakes.html" 90 | f = open(fn, 'w') 91 | f.write(html) 92 | -------------------------------------------------------------------------------- /utils/instance.sh: -------------------------------------------------------------------------------- 1 | export EC2_INSTANCE_ID="`wget -q -O - http://169.254.169.254/latest/meta-data/instance-id || die \"wget instance-id has failed: $?\"`" 2 | test -n "$EC2_INSTANCE_ID" || die 'cannot obtain instance-id' 3 | export EC2_AVAIL_ZONE="`wget -q -O - http://169.254.169.254/latest/meta-data/placement/availability-zone || die \"wget availability-zone has failed: $?\"`" 4 | test -n "$EC2_AVAIL_ZONE" || die 'cannot obtain availability-zone' 5 | export EC2_REGION="`echo \"$EC2_AVAIL_ZONE\" | sed -e 's:\\([0-9][0-9]*\\)[a-z]*\\$:\\\\1:'`" 6 | -------------------------------------------------------------------------------- /utils/name_to_flake.py: -------------------------------------------------------------------------------- 1 | import os 2 | import string 3 | from snowflake import * 4 | 5 | friend_name = raw_input('What is your name? Enter it here: ') 6 | print "Great, thanks. I will make you a snowflake." 7 | #flake_size = raw_input('But first, how big do you want it (heh, heh)? Smaller sizes are faster output. Integers only please. 300 is a good start.: ') 8 | 9 | base_fn = "second_snowflake_for_%s" % friend_name 10 | 11 | def sanitize_text(friend_name): 12 | for char in friend_name: 13 | if char not in string.letters: 14 | friend_name = string.replace(friend_name, char, '') 15 | return str.lower(friend_name) 16 | 17 | def name_val(name): 18 | nameval = 0 19 | for letter in name: 20 | nameval += ord(letter)-ord('a') + 1 21 | return nameval 22 | 23 | archetype_list = [SpikeEndFlake(), FernFlake(), ClassicFlake(), DelicateFlake(), RandomBeautyFlake()] 24 | 25 | def archetype_chooser(name): 26 | print name[0] 27 | if name[0] in 'abcde': 28 | print 'Caution: You are a Spike End Flake (sharp!)' 29 | flake_type = SpikeEndFlake() 30 | elif name[0] in 'fghij': 31 | print 'Looks like you\'re what I call a Fern Flake (many fingered and soft to the touch, rawr)' 32 | flake_type = FernFlake() 33 | elif name[0] in 'klmno': 34 | print 'You are a Classic Flake (you\'ve got excellent form)' 35 | flake_type = ClassicFlake() 36 | elif name[0] in 'pqrst': 37 | print 'I\'ve determined that you are a Delicate Snowflake (har)' 38 | flake_type = DelicateFlake() 39 | else: 40 | print 'Your etheral beauty cannot be captured except to say that you are a Random Beauty of a Snowflake' 41 | flake_type = RandomBeautyFlake() 42 | return flake_type 43 | 44 | def archetype_chooser_2(nameval): 45 | flake_type = archetype_list[nameval % len(archetype_list)] 46 | return flake_type 47 | 48 | 49 | def makeflake(flake): 50 | ifn = (base_fn) + ".bmp" 51 | lfn = (base_fn) + ".pickle" 52 | if os.path.exists(lfn): 53 | print "Found %s, skipping..." % ifn 54 | print flake 55 | cl = CrystalLattice(500, environment=flake) 56 | cl.grow() 57 | cl.save_image(ifn) 58 | cl.save_lattice(lfn) 59 | 60 | sanitized_name = sanitize_text(friend_name) 61 | 62 | namevalue = name_val(sanitized_name) 63 | print 'Your namevalue is ', namevalue 64 | 65 | #flakity = archetype_chooser(sanitized_name) 66 | flakity = archetype_chooser_2(namevalue) 67 | print 'the flake values are',flakity 68 | print type(flakity) 69 | 70 | #for key in flakity: 71 | # flakity[key] = flakity[key] - (flakity[key])* (1/float(len(sanitized_name))) 72 | 73 | 74 | for key in flakity: 75 | if flakity[key] == 0: 76 | continue 77 | if int(1/flakity[key]) % 2: 78 | print 'make ', key, 'a little bit bigger' 79 | flakity[key] = 1.0 / ((1.0/flakity[key]) * (1.0 - 1.0/float(namevalue))) 80 | else: 81 | print 'make ',key,'a little bit smaller' 82 | flakity[key] = 1.0 / ((1.0/flakity[key]) * (1.0 + 1.0/float(len(sanitized_name)))) 83 | 84 | print 'the customized flake values are', flakity 85 | 86 | makeflake(flakity) 87 | 88 | 89 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /utils/plotdata.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import argparse 4 | import os 5 | 6 | # matplotlib 7 | PLOTS_ENABLED = True 8 | try: 9 | import matplotlib 10 | matplotlib.use('AGG') 11 | import numpy as np 12 | import matplotlib.mlab as mlab 13 | import matplotlib.pyplot as plt 14 | import matplotlib.ticker as ticker 15 | import pylab 16 | except ImportError: 17 | PLOTS_ENABLED = False 18 | 19 | class DataPlotter(list): 20 | def __init__(self, args): 21 | self.name = args.name 22 | self.load() 23 | 24 | def load(self): 25 | fn = "datalog.csv" 26 | f = open(fn) 27 | hdr = f.readline() 28 | hdr = hdr.strip() 29 | self.hdr = hdr.split(',') 30 | for line in f: 31 | line = line.strip() 32 | row = line.split(',') 33 | self.append(row) 34 | 35 | def plot(self): 36 | assert PLOTS_ENABLED, "You do not currently have plots enabled." 37 | fig, axs = plt.subplots(nrows=len(self.hdr), ncols=1, sharex=True) 38 | fig.set_size_inches(10, 25) 39 | #plt.title(self.name) 40 | for (idx, key) in enumerate(self.hdr): 41 | ax = axs[idx] 42 | data = [row[idx] for row in self] 43 | ax.plot(data) 44 | ax.set_xlabel("Time (simulation steps)") 45 | ax.set_ylabel(key) 46 | fn = "%s_rundata.png" % self.name 47 | plt.savefig(fn) 48 | 49 | 50 | DEFAULTS = { 51 | "name": "snowflake", 52 | } 53 | 54 | def get_cli(): 55 | parser = argparse.ArgumentParser(description='Snowflake Generator.') 56 | parser.add_argument(dest="name", nargs='+', help="The name of the snowflake.") 57 | 58 | parser.set_defaults(**DEFAULTS) 59 | args = parser.parse_args() 60 | args.name = str.join('', map(str.lower, args.name)) 61 | return args 62 | 63 | if __name__ == "__main__": 64 | args = get_cli() 65 | os.chdir(args.name) 66 | try: 67 | dp = DataPlotter(args) 68 | dp.plot() 69 | finally: 70 | os.chdir('..') 71 | -------------------------------------------------------------------------------- /utils/run_names.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import sys 4 | from multiprocessing import Pool 5 | 6 | CMD = "snowflake.py -c -s 600 -M 50000 %s" 7 | def name_snowflake(name): 8 | cmd = CMD % name 9 | print cmd 10 | os.system(cmd) 11 | 12 | workers = Pool(3) 13 | f = open(sys.argv[1]) 14 | names = [name.strip() for name in f if name.strip()] 15 | workers.map(name_snowflake, names) 16 | -------------------------------------------------------------------------------- /utils/sfgen.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import time 4 | import uuid 5 | 6 | CMD = "./snowflake.py -s 600 -M 5000 -r %s" 7 | while 1: 8 | _id = str(uuid.uuid1()).split('-')[0] 9 | fn = "snowflake_%s" % _id 10 | cmd = CMD % fn 11 | os.system(cmd) 12 | time.sleep(1) 13 | -------------------------------------------------------------------------------- /utils/snowflake_walk.py: -------------------------------------------------------------------------------- 1 | import os 2 | from snowflake import * 3 | 4 | def steps(low, high, step): 5 | val = low 6 | while val < high: 7 | yield val 8 | val += step 9 | 10 | 11 | cnt = 0 12 | base_fn = "snowflake_beta_%03d" 13 | 14 | for beta_val in steps(0.8, 1.2, 0.05): 15 | for gamma_val in steps(0.3, 0.6, 0.02): 16 | ifn = (base_fn % cnt) + ".bmp" 17 | lfn = (base_fn % cnt) + ".pickle" 18 | if os.path.exists(lfn): 19 | print "Found %s, skipping..." % ifn 20 | cnt += 1 21 | continue 22 | print beta_val 23 | env = CrystalEnvironment() 24 | env["beta"] = beta_val 25 | env["gamma"] = gamma_val 26 | cl = CrystalLattice(300, environment=env) 27 | cl.grow() 28 | cnt += 1 29 | cl.save_image(ifn) 30 | cl.save_lattice(lfn) 31 | -------------------------------------------------------------------------------- /utils/submit.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import aws 4 | import sys 5 | 6 | ss = aws.SnowflakeServices() 7 | job = aws.SnowflakeJob(sys.argv[1]) 8 | ss.send_work(job) 9 | --------------------------------------------------------------------------------