├── .gitignore ├── README.md ├── blender_scripts ├── clear_blender_data.py ├── constants.py ├── draw_scenes.py ├── import.py ├── render.py ├── tools │ ├── blobject.py │ ├── bobject.py │ ├── centipede.py │ ├── creature.py │ ├── drawn_contest_world.py │ ├── drawn_market.py │ ├── drawn_world.py │ ├── gesture.py │ ├── graph_bobject.py │ ├── hamilton_basic.py │ ├── hawk_dove.py │ ├── hawk_dove_basic.py │ ├── helpers.py │ ├── market_sim.py │ ├── natural_sim.py │ ├── population.py │ ├── scene.py │ ├── svg_bobject.py │ ├── table_bobject.py │ ├── tex_bobject.py │ ├── tex_complex.py │ └── two_d_world.py └── video_scenes │ ├── aggression.py │ ├── bppv.py │ ├── bvl.py │ ├── fecal_transplant.py │ ├── final_vestibular_animations_anatomy.py │ ├── final_vestibular_animations_text.py │ ├── inclusive_fitness.py │ ├── logistic_growth.py │ ├── meniere.py │ ├── mutations.py │ ├── natural_selection.py │ ├── recurring_assets.py │ ├── replication_only.py │ ├── scds.py │ ├── selfish_gene.py │ ├── supply_and_demand.py │ ├── vn.py │ └── why_things_exist.py ├── draw_molecules ├── __init__.py ├── atoms.json ├── draw.py ├── parse.py └── run.py └── files ├── template.tex ├── template_arial.tex └── template_garamond.tex /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | __pycache__/ 3 | files/animations 4 | files/blend 5 | files/raster 6 | files/sims 7 | files/svg 8 | files/tex 9 | test.py 10 | channel_assets/ 11 | draw_molecules/mol/ 12 | draw_molecules/json/ 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # This repo is archived. Helpsypoo/primerunity is what I'm using now. If you decide to move forward with trying to get this code running, know that it's extremely likely to waste your time, and nobody can help you. 2 | 3 | # primer 4 | Code that makes videos for this: youtube.com/c/primerlearning 5 | 6 | This is going to be a pretty minimal readme for now, and the code itself is likely very confusing. I intend to clean things up and improve documentation, though I don't know when that will be. If you'd like to receive an email when that update happens, you can submit your email here: https://forms.gle/GHTgKMUaMxqxGUu39 7 | 8 | ## Overview 9 | This is a library of tools that lets you write high-level functions to build and animate objects in Blender. It's not set up to run from the command line. Instead, it uses the Script Runner addon for Blender, which lets you run scripts by pushing buttons within Blender. This makes it easy to see the results of your scripts and use the Blender interface to experiment with parameters before iterating on your script. 10 | 11 | The primary script run by Script Runner in my workflow is draw_scenes.py, whose main() function can be used to call test scripts or run a script from another file. 12 | 13 | Much of the structure comes from [manim](https://github.com/3b1b/manim), 3blue1brown's animation engine. 14 | 15 | ## Using primer 16 | The root object class used for defining and manipulating objects in Blender is called Bobject (blender object). It initializes an object in Blender and defines simple functions for changing the parameters and adding keyframes. 17 | 18 | The best scene to try when getting started is probably tex_test(), defined in draw_scenes.py. It creates a TexBobject and morphs through a few strings. 19 | 20 | There isn't a best practice for creating a series of scenes in a new file. As you'll see, the files from past primer videos are all structured fairly differently. I haven't landed on a steady-state workflow here. So if you're confused, that might be why. 21 | 22 | ## Requirements 23 | Blender 2.79 (Not 2.8+, the api has changed) 24 | - [Download](https://www.blender.org/) 25 | - [API](https://docs.blender.org/api/2.79/) 26 | 27 | Script Runner addon for Blender 28 | - [Download](http://goodspiritgraphics.com/software/products/script-runner-addon/) 29 | 30 | TeX 31 | 32 | dvisvgm 33 | - https://dvisvgm.de/ 34 | 35 | OpenBabel if you want to do the molecule stuff http://openbabel.org/wiki/Main_Page 36 | - Chem stuff adapted from here: https://github.com/patrickfuller/blender-chemicals 37 | - Also helpful: https://patrickfuller.github.io/molecules-from-smiles-molfiles-in-blender/ 38 | 39 | ## Gotchas 40 | Blender comes packaged with its own version of python, so your local python installation and your favorite packages won't be there unless you install them to Blender's version. 41 | 42 | Many of the scenes from my videos depend on specific blend files that are imported for use as common objects or components of them. For example, the blob creatures and components of the graphs. If at some point I try to turn this into a more broadly accessible tool, I'll have to include those files in the repo or procedurally generate them. 43 | -------------------------------------------------------------------------------- /blender_scripts/clear_blender_data.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | import collections 3 | import sys 4 | sys.path.append('C:\\Users\\justi\\Documents\\CodeProjects\\Primer\\blender_scripts') 5 | sys.path.append('C:\\Users\\justi\\Documents\\CodeProjects\\Primer\\blender_scripts\\tools') 6 | import helpers 7 | import imp 8 | imp.reload(helpers) 9 | from helpers import print_time_report 10 | 11 | def clear_blender_data(): 12 | print('Clearing Blender data') 13 | for bpy_data_iter in ( 14 | bpy.data.objects, 15 | bpy.data.meshes, 16 | bpy.data.lamps, 17 | bpy.data.cameras, 18 | bpy.data.curves, 19 | bpy.data.materials, 20 | bpy.data.particles, 21 | bpy.data.worlds 22 | ): 23 | 24 | for id_data in bpy_data_iter: 25 | bpy_data_iter.remove(id_data) 26 | 27 | #bpy.ops.wm.read_homefile(use_empty=True) 28 | #bpy.ops.wm.read_homefile(use_empty=True) 29 | #bpy.context.scene.update() 30 | 31 | if __name__ == '__main__': 32 | clear_blender() 33 | print_time_report() 34 | -------------------------------------------------------------------------------- /blender_scripts/constants.py: -------------------------------------------------------------------------------- 1 | import os 2 | import math 3 | import datetime 4 | from copy import deepcopy 5 | 6 | ''' 7 | Quality 8 | ''' 9 | RENDER_QUALITY = 'final' 10 | #'medium' or higher turns on expression morphing 11 | #which takes a few seconds per run 12 | if RENDER_QUALITY == 'final': 13 | RESOLUTION_PERCENTAGE = 100 14 | LIGHT_SAMPLING_THRESHOLD = 0.01 15 | SAMPLE_COUNT = 64 16 | RENDER_QUALITY = 'high' 17 | RENDER_TILE_SIZE = 256 18 | #The 'final' stuff is over, so just setting to 'high' for rest of code 19 | else: 20 | RESOLUTION_PERCENTAGE = 30 21 | LIGHT_SAMPLING_THRESHOLD = 0.0 #For my scenes, it seems there's so little 22 | #ray bouncing that the cutoff test actually 23 | #takes longer than letting the light keep going 24 | SAMPLE_COUNT = 64 25 | RENDER_TILE_SIZE = 256 26 | 27 | if RENDER_QUALITY == 'high': 28 | LAMPS_TO_A_SIDE = 1 29 | LAMP_TYPE = 'SUN' 30 | ICO_SUBDIVISIONS = 6 31 | CONTROL_POINTS_PER_SPLINE = 40 #TODO: figure out the threshold for noticing a difference 32 | PARTICLES_PER_MESH = 300000 #Could be smaller if morphing smaller objects 33 | #Could even be a function of scale 34 | #Or number of other objects 35 | 36 | else: 37 | LAMPS_TO_A_SIDE = 1 38 | LAMP_TYPE = 'SUN' 39 | ICO_SUBDIVISIONS = 2 40 | CONTROL_POINTS_PER_SPLINE = 30 41 | PARTICLES_PER_MESH = 1000 42 | 43 | RESOLUTION_PERCENTAGE = 30 44 | LIGHT_SAMPLING_THRESHOLD = 0.1 45 | SAMPLE_COUNT = 64 46 | 47 | ''' 48 | Colorssssss 49 | ''' 50 | color_scheme = 2 51 | if color_scheme == 1: 52 | #Coolors Exported Palette - https://coolors.co/393e41-f3f2f0-3e7ea0-ff9400-e7e247 53 | COLORS = [ 54 | [57, 62, 65, 1], 55 | #[211, 208, 203, 1], 56 | [243, 242, 240, 1], 57 | [62, 126, 160, 1], 58 | [255, 148, 0, 1], 59 | #[232, 225, 34, 1], 60 | [231, 226, 71, 1], 61 | #[106, 141, 115, 1] 62 | [215, 38, 61, 1] 63 | #[255, 0, 0, 1] 64 | ] 65 | elif color_scheme == 2: #Main. Why isn't #1 main? Because your face. 66 | COLORS = [ 67 | #[42, 46, 48, 1], #Three darker than first video 68 | [47, 51, 54, 1], #Two darker than first video 69 | #[211, 208, 203, 1], 70 | [243, 242, 240, 1], 71 | [62, 126, 160, 1], 72 | [255, 148, 0, 1], 73 | #[232, 225, 34, 1], 74 | [231, 226, 71, 1], 75 | #[106, 141, 115, 1] 76 | [214, 59, 80, 1], 77 | #[255, 0, 0, 1] 78 | [105, 143, 63, 1], 79 | [219, 90, 186, 1], 80 | [145, 146.5, 147, 1], #Gray from averaging 1 and 2 81 | [0, 0, 0, 1] 82 | ] 83 | 84 | elif color_scheme == 3: 85 | #Coolors Exported Palette - coolors.co/191308-bbd8b3-f3b61f-48a0c9-72120d 86 | COLORS = [ 87 | [1, 33, 31, 1], 88 | [255, 237, 225, 1], 89 | [32, 164, 243, 1], 90 | [255, 51, 102, 1], 91 | [234, 196, 53, 1], 92 | [215, 38, 61, 1] 93 | ] 94 | 95 | elif color_scheme == 4: #UCSF 96 | #https://identity.ucsf.edu/print-digital/digital-colors 97 | COLORS = [ 98 | [5, 32, 73, 1], #Dark blue 99 | [255, 255, 255, 1], #White 100 | [255, 221, 0, 1], #Yellow 101 | [80, 99, 128, 1], #Light Navy 102 | [47, 51, 54, 1], #Two darker than first video 103 | [0, 0, 0, 1], #Black 104 | [113, 111, 178, 1], #Light purple 105 | [180, 185, 191, 1], #Dark gray 106 | [209, 211, 211, 1], #Light gray 107 | ] 108 | 109 | COLORS_SCALED = [] 110 | for color in COLORS: 111 | color_scaled = deepcopy(color) 112 | for i in range(3): 113 | color_scaled[i] /= 255 114 | #color_scaled[i] = color_scaled[i] ** 2.2 115 | COLORS_SCALED.append(color_scaled) 116 | 117 | ''' 118 | File and directory constants 119 | ''' 120 | MEDIA_DIR = os.path.join(os.path.expanduser('~'), "Documents\\CodeProjects\\Primer") 121 | 122 | ### 123 | THIS_DIR = os.path.dirname(os.path.realpath(__file__)) 124 | MAIN_DIR = os.path.join(THIS_DIR, "..") 125 | FILE_DIR = os.path.join(MAIN_DIR, "files") 126 | TEX_DIR = os.path.join(FILE_DIR, "tex") 127 | #SIM_DIR = os.path.join(FILE_DIR, "sims") 128 | SIM_DIR = 'D:\\primer_overflow\\sims' 129 | SVG_DIR = os.path.join(FILE_DIR, "svg") 130 | IMG_DIR = os.path.join(FILE_DIR, "raster") 131 | BLEND_DIR = os.path.join(FILE_DIR, "blend") 132 | 133 | for folder in [FILE_DIR, TEX_DIR, SIM_DIR, SVG_DIR, BLEND_DIR]: 134 | if not os.path.exists(folder): 135 | os.makedirs(folder) 136 | 137 | TEX_TEXT_TO_REPLACE = "YourTextHere" 138 | TEMPLATE_TEX_FILE = os.path.join(FILE_DIR, "template.tex") 139 | 140 | ''' 141 | Birth and death constants 142 | ''' 143 | #Equilibrium number = 144 | #BIRTH_CHANCE / (DEATH_CHANCE - REPLICATION_CHANCE) 145 | #Different if there's mutation to/from the species 146 | 147 | BASE_BIRTH_CHANCE = 0.001 148 | BASE_DEATH_CHANCE = 0.001 149 | BASE_REPLICATION_CHANCE = 0.001 #If this is higher than DEATH_CHANCE, 150 | #it can get cray 151 | DEFAULT_MUTATION_CHANCE = 0.5 152 | 153 | INITIAL_CREATURES = 10 154 | DEFAULT_POP_CAP = 3000 155 | 156 | ''' 157 | Sim motion constants 158 | ''' 159 | CREATURE_BUBBLE_WIDTH = 0.1 160 | BOUNCE_DAMP_FACTOR = 0.8 161 | FLOOR_PADDING = 1 162 | BLINK_CHANCE = 0.005 163 | BLINK_LENGTH = 11 164 | BLINK_CYCLE_LENGTH = 1200 165 | #Could make a constant for the velocity parameters 166 | 167 | ''' 168 | Blob constants 169 | ''' 170 | BLOB_VOLUME_DENSITY = 0.04 171 | 172 | ''' 173 | World constants 174 | ''' 175 | DEFAULT_WORLD_DURATION = 100 176 | WORLD_RADIUS = 10 177 | 178 | ''' 179 | Camera and lighting constants 180 | ''' 181 | CAMERA_LOCATION = (0, 0, 32.8) 182 | CAMERA_ANGLE = (0, 0, 0) 183 | 184 | ''' 185 | Text constants 186 | ''' 187 | SPACE_BETWEEN_EXPRESSIONS = 0.45 #0.1 For text svgs #0.45 For actual tex_bobjects 188 | TEX_LOCAL_SCALE_UP = 260 #Value that makes line height about 1 Blender Unit 189 | 190 | ''' 191 | Animation constants 192 | ''' 193 | FRAME_RATE = 60 194 | 195 | MATURATION_TIME = 5 #time for a new creature to grow to full scale. 196 | OBJECT_APPEARANCE_TIME = 30 197 | PARTICLE_APPEARANCE_TIME = 1 198 | DEFAULT_MORPH_TIME = OBJECT_APPEARANCE_TIME 199 | MORPH_PARTICLE_SIZE = 0.03 200 | FLOOR_APPEARANCE_TIME = OBJECT_APPEARANCE_TIME #time for floor to appear in a new world 201 | TEXT_APPEARANCE_TIME = OBJECT_APPEARANCE_TIME 202 | DEFAULT_SCENE_DURATION = 150 203 | DEFAULT_SCENE_BUFFER = 0 #This was used when multiple scenes were in blender at 204 | #once, which was basically never, and will never be. 205 | #Could just delete. 206 | 207 | ''' 208 | Graph constants 209 | ''' 210 | AXIS_WIDTH = 0.05 211 | AXIS_DEPTH = 0.01 212 | ARROW_SCALE = [0.3, 0.3, 0.4] 213 | GRAPH_PADDING = 1 214 | CURVE_WIDTH = 0.04 215 | if RENDER_QUALITY == 'high': 216 | PLOTTED_POINT_DENSITY = 30 217 | else: 218 | PLOTTED_POINT_DENSITY = 20 219 | CURVE_Z_OFFSET = 0.01 220 | AUTO_TICK_SPACING_TARGET = 2 #Blender units 221 | HIGHLIGHT_POINT_UPDATE_TIME = OBJECT_APPEARANCE_TIME 222 | -------------------------------------------------------------------------------- /blender_scripts/draw_scenes.py: -------------------------------------------------------------------------------- 1 | import imp 2 | import bpy 3 | import mathutils 4 | import math 5 | import statistics 6 | import pickle 7 | import inspect 8 | from copy import deepcopy 9 | from random import random, uniform 10 | 11 | import sys 12 | sys.path.append('C:\\Users\\justi\\Documents\\CodeProjects\\Primer\\blender_scripts') 13 | sys.path.append('C:\\Users\\justi\\Documents\\CodeProjects\\Primer\\blender_scripts\\tools') 14 | 15 | import bobject 16 | import drawn_world 17 | import tex_bobject 18 | import constants 19 | 20 | 21 | import clear_blender_data 22 | #import alone doesn't check for changes in cached files 23 | imp.reload(drawn_world) 24 | imp.reload(tex_bobject) 25 | 26 | imp.reload(constants) 27 | from constants import * 28 | 29 | import svg_bobject 30 | imp.reload(svg_bobject) 31 | from svg_bobject import * 32 | 33 | import graph_bobject 34 | imp.reload(graph_bobject) 35 | from graph_bobject import * 36 | 37 | import helpers 38 | imp.reload(helpers) 39 | from helpers import * 40 | 41 | import table_bobject 42 | imp.reload(table_bobject) 43 | 44 | import natural_sim 45 | imp.reload(natural_sim) 46 | from natural_sim import * 47 | 48 | sys.path.append('C:\\Users\\justi\\Documents\\CodeProjects\\Primer\\blender_scripts\\video_scenes') 49 | 50 | import recurring_assets 51 | imp.reload(recurring_assets) 52 | from recurring_assets import * 53 | 54 | import population 55 | imp.reload(population) 56 | from population import * 57 | 58 | import gesture 59 | imp.reload(gesture) 60 | from gesture import * 61 | 62 | import tex_complex 63 | imp.reload(tex_complex) 64 | from tex_complex import TexComplex 65 | 66 | import blobject 67 | imp.reload(blobject) 68 | from blobject import Blobject 69 | 70 | import hawk_dove 71 | imp.reload(hawk_dove) 72 | import drawn_contest_world 73 | imp.reload(drawn_contest_world) 74 | 75 | import aggression 76 | imp.reload(aggression) 77 | 78 | import final_vestibular_animations_text 79 | imp.reload(final_vestibular_animations_text) 80 | 81 | import recurring_assets 82 | imp.reload(recurring_assets) 83 | 84 | def initialize_blender(total_duration = DEFAULT_SCENE_DURATION, clear_blender = True, vertical = False): 85 | #clear objects and materials 86 | #Reading the homefile would likely by faster, but it 87 | #sets the context to None, which breaks a bunch of 88 | #other stuff down the line. I don't know how to make the context not None. 89 | #bpy.ops.wm.read_homefile() 90 | if clear_blender == True: 91 | clear_blender_data.clear_blender_data() 92 | 93 | scn = bpy.context.scene 94 | scn.render.engine = 'CYCLES' 95 | scn.cycles.device = 'GPU' 96 | scn.cycles.samples = SAMPLE_COUNT 97 | scn.cycles.preview_samples = SAMPLE_COUNT 98 | scn.cycles.light_sampling_threshold = LIGHT_SAMPLING_THRESHOLD 99 | scn.cycles.transparent_max_bounces = 40 100 | scn.render.resolution_percentage = RESOLUTION_PERCENTAGE 101 | scn.render.use_compositing = False 102 | scn.render.use_sequencer = False 103 | scn.render.image_settings.file_format = 'PNG' 104 | scn.render.tile_x = RENDER_TILE_SIZE 105 | scn.render.tile_y = RENDER_TILE_SIZE 106 | scn.render.resolution_x = 1920 107 | scn.render.resolution_y = 1080 108 | if vertical == True: 109 | scn.render.resolution_x = 1080 110 | scn.render.resolution_y = 1920 111 | #Apparentlly 16-bit color depth pngs don't convert well to mp4 in Blender. 112 | #It gets all dark. 8-bit it is. 113 | #BUT WAIT. I can put stacks of pngs straight into premiere. 114 | scn.render.image_settings.color_depth = '16' 115 | scn.render.image_settings.color_mode = 'RGBA' 116 | scn.cycles.film_transparent = True 117 | 118 | #Set to 60 fps 119 | bpy.ops.script.execute_preset( 120 | filepath="C:\\Program Files\\Blender Foundation\\Blender\\2.79\\scripts\\presets\\framerate\\60.py", 121 | menu_idname="RENDER_MT_framerate_presets" 122 | ) 123 | 124 | #Idfk how to do manipulate the context 125 | '''for area in bpy.context.screen.areas: 126 | if area.type == 'TIMELINE': 127 | bpy.context.area = area 128 | bpy.context.space_data.show_seconds = True''' 129 | 130 | #The line below makes it so Blender doesn't apply gamma correction. For some 131 | #reason, Blender handles colors differently from how every other program 132 | #does, and it's terrible. Why. 133 | #But this fixes it. Also, the RGB values you see in Blender 134 | #will be wrong, because the gamma correction is still applied when the color 135 | #is defined, but setting view_transform to 'Raw' undoes the correction in 136 | #render. 137 | scn.view_settings.view_transform = 'Raw' 138 | 139 | scn.gravity = (0, 0, -9.81) 140 | 141 | bpy.ops.world.new() 142 | world = bpy.data.worlds[-1] 143 | scn.world = world 144 | nodes = world.node_tree.nodes 145 | nodes.new(type = 'ShaderNodeMixRGB') 146 | nodes.new(type = 'ShaderNodeLightPath') 147 | nodes.new(type = 'ShaderNodeRGB') 148 | world.node_tree.links.new(nodes[2].outputs[0], nodes[1].inputs[0]) 149 | world.node_tree.links.new(nodes[3].outputs[0], nodes[2].inputs[0]) 150 | world.node_tree.links.new(nodes[4].outputs[0], nodes[2].inputs[2]) 151 | nodes[4].outputs[0].default_value = COLORS_SCALED[0] 152 | 153 | define_materials() 154 | 155 | #set up timeline 156 | bpy.data.scenes["Scene"].frame_start = 0 157 | bpy.data.scenes["Scene"].frame_end = total_duration * FRAME_RATE - 1 158 | bpy.context.scene.frame_set(0) 159 | 160 | #create camera and light 161 | bpy.ops.object.camera_add(location = CAMERA_LOCATION, rotation = CAMERA_ANGLE) 162 | cam = bpy.data.cameras[0] 163 | #cam.type = 'ORTHO' 164 | cam.type = 'PERSP' 165 | cam.ortho_scale = 30 166 | 167 | bpy.ops.object.empty_add(type = 'PLAIN_AXES', location = (0, 0, 100)) 168 | lamp_parent = bpy.context.object 169 | lamp_parent.name = 'Lamps' 170 | 171 | lamp_height = 35 172 | bpy.ops.object.lamp_add(type = LAMP_TYPE, location = (0, 0, lamp_height)) 173 | lamp = bpy.context.object 174 | lamp.parent = lamp_parent 175 | lamp.matrix_parent_inverse = lamp.parent.matrix_world.inverted() 176 | lamp.data.node_tree.nodes[1].inputs[1].default_value = 1.57 177 | #lamp.data.shadow_soft_size = 3 178 | 179 | #Sets view to look through camera. 180 | for area in bpy.context.screen.areas: 181 | if area.type == 'VIEW_3D': 182 | override = bpy.context.copy() 183 | override['area'] = area 184 | bpy.ops.view3d.viewnumpad(override, type = 'CAMERA') 185 | break 186 | 187 | def is_scene(obj): 188 | #print('checking scene') 189 | #if "TextScene" in str(obj): 190 | if not inspect.isclass(obj): 191 | #print(' not class') 192 | return False 193 | if not issubclass(obj, Scene): 194 | print(obj) 195 | #print(' not subclass of scene') 196 | return False 197 | if obj == Scene: 198 | #print(obj) 199 | #print(' is scene class itself') 200 | return False 201 | return True 202 | 203 | def get_total_duration(scenes): 204 | #scenes is a list of (name, object) pairs 205 | duration = 0 206 | for scene in scenes: 207 | duration += scene[1].duration + DEFAULT_SCENE_BUFFER 208 | return duration 209 | 210 | def get_scene_object_list(script_file): 211 | pairs = inspect.getmembers(script_file, is_scene) 212 | #The output of inspect.getmembers is a list of (name, class) pairs. 213 | #This turns that list into a list of (name, object) pairs 214 | objects = [] 215 | for pair in pairs: 216 | objects.append([pair[0], pair[1]()]) 217 | return objects 218 | 219 | def tex_test(): 220 | initialize_blender(total_duration = 100) 221 | 222 | message = tex_bobject.TexBobject( 223 | '\\text{Always}', 224 | '\\text{Pursue}', 225 | '\\text{Beauty}', 226 | '\\text{Always}', 227 | centered = True, 228 | scale = 8, 229 | color = 'color10', 230 | typeface = 'garamond' 231 | ) 232 | message.add_to_blender(appear_time = 0) 233 | message.morph_figure('next', start_time = 1) 234 | message.morph_figure('next', start_time = 2) 235 | message.morph_figure('next', start_time = 3) 236 | 237 | #message.disappear(disappear_time = 3.5) 238 | 239 | '''t = tex_bobject.TexBobject( 240 | '\\curvearrowleft' 241 | ) 242 | t.add_to_blender(appear_time = 0) 243 | 244 | print_time_report()''' 245 | 246 | def marketing(): 247 | scene_end = 12 248 | initialize_blender(total_duration = scene_end) 249 | 250 | x = 7.64349 251 | y = -8.71545 252 | 253 | b_blob = import_object( 254 | 'boerd_blob_stern', 'creatures', 255 | location = [-x, y, 0], 256 | rotation_euler = [0, 57.4 * math.pi / 180, 0], 257 | scale = 12, 258 | ) 259 | b_blob.ref_obj.children[0].children[0].data.resolution = 0.2 260 | apply_material(b_blob.ref_obj.children[0].children[0], 'creature_color3') 261 | b_blob.add_to_blender(appear_time = 0) 262 | 263 | y_blob = import_object( 264 | 'boerd_blob_stern', 'creatures', 265 | rotation_euler = [0, -57.4 * math.pi / 180, 0], 266 | location = [x, y, 0], 267 | scale = 12, 268 | ) 269 | y_blob.ref_obj.children[0].children[0].data.resolution = 0.2 270 | apply_material(y_blob.ref_obj.children[0].children[0], 'creature_color4') 271 | y_blob.add_to_blender(appear_time = 0) 272 | 273 | y_blob.blob_wave( 274 | start_time = 0, 275 | duration = 12 276 | ) 277 | 278 | comp = tex_bobject.TexBobject( 279 | '\\text{COMPETITION} \\phantom{blargh}', 280 | centered = True, 281 | scale = 4.5, 282 | location = [0, 5.5, 0], 283 | color = 'color2' 284 | ) 285 | comp.add_to_blender(appear_time = 0) 286 | 287 | def sample_scene(): 288 | initialize_blender() 289 | 290 | num_colors = 8 291 | for i in range(num_colors): 292 | ghost = blobject.Blobject( 293 | location = [3.5 * i - 12.25, 5, 0], 294 | mat = 'trans_color' + str(i + 1), 295 | scale = 2, 296 | wiggle = True 297 | ) 298 | mat = ghost.ref_obj.children[0].children[0].material_slots[0].material 299 | node_tree = mat.node_tree 300 | node_tree.nodes['Volume Absorption'].inputs[1].default_value = 0.2 301 | node_tree.nodes['Emission'].inputs[1].default_value = 0.2 302 | node_tree.nodes['Volume Scatter'].inputs[1].default_value = 0.2 303 | 304 | solid = blobject.Blobject( 305 | location = [3.5 * i - 12.25, -1, 0], 306 | mat = 'creature_color' + str(i + 1), 307 | scale = 2, 308 | wiggle = True 309 | ) 310 | 311 | text = tex_bobject.TexBobject( 312 | '\\text{Text!}', 313 | location = [3.5 * i - 12.25, -6, 0], 314 | centered = True, 315 | color = 'color' + str(i + 1), 316 | scale = 1.5 317 | ) 318 | 319 | ghost.add_to_blender(appear_time = 0) 320 | solid.add_to_blender(appear_time = 0) 321 | text.add_to_blender(appear_time = 0) 322 | 323 | def draw_scenes_from_file(script_file, clear = True): 324 | #This function is meant to process many scenes at once. 325 | #Most scenes end up being large enough where it doesn't make sense to have 326 | #more than one in blender at once, so this is obsolete and will 327 | #break if you try to process more than one scene at a time. 328 | scenes = get_scene_object_list(script_file) 329 | print(scenes) 330 | duration = get_total_duration(scenes) 331 | initialize_blender(total_duration = duration, clear_blender = clear) 332 | 333 | frame = 0 334 | for scene in scenes: 335 | execute_and_time( 336 | scene[0], #This is just a string 337 | scene[1].play() 338 | ) 339 | #frame += scene[1].duration + DEFAULT_SCENE_BUFFER 340 | 341 | #Hide empty objects from render, for speed 342 | for obj in bpy.data.objects: 343 | if obj.type == 'EMPTY': 344 | obj.hide_render = True 345 | #Doesn't change much, since most empty objects are keyframed handles for 346 | #other objects. 347 | 348 | print_time_report() 349 | 350 | def make_blob_with_actions_for_unity(): 351 | #initialize_blender() 352 | 353 | actual_blob = bpy.data.objects['blob'] 354 | 355 | blobj = blobject.Blobject( 356 | upright = True, 357 | name = 'blobject_ref_obj', 358 | #wiggle = True 359 | ) 360 | 361 | #trash = blobj.objects[0] 362 | 363 | blobj.objects[0] = actual_blob 364 | actual_blob.parent = blobj.ref_obj 365 | 366 | #print() 367 | #print(blob.__dict__) 368 | #print() 369 | 370 | '''blob.add_to_blender( 371 | animate = False, 372 | unhide = False 373 | )''' 374 | 375 | '''blobj.evil_pose( 376 | start_time = 0, 377 | end_time = 4 378 | )''' 379 | blobj.hold_object( 380 | start_time = 0, 381 | end_time = 2 382 | ) 383 | 384 | def test(): 385 | #initialize_blender() 386 | 387 | def sim_test(): 388 | print('Initial bimodal distribution') 389 | 390 | food_counts_to_try = [ 391 | #37, 392 | #37, 393 | #37, 394 | #37, 395 | #37, 396 | #37, 397 | #37, 398 | #37, 399 | #37, 400 | #50, 401 | 61, 402 | #61, 403 | #61, 404 | #61, 405 | #61, 406 | #61, 407 | #61, 408 | #1, 409 | #61, 410 | #61, 411 | #61, 412 | #100, 413 | #150, 414 | #200, 415 | #300, 416 | #400, 417 | #500, 418 | #600, 419 | #700, 420 | #800, 421 | #900, 422 | #1999 423 | ] 424 | nums_days_to_try = [ 425 | #2, 426 | #10, 427 | #11, 428 | #12, 429 | #13, 430 | #14, 431 | #15, 432 | #16, 433 | #17, 434 | #18, 435 | #19, 436 | #20, 437 | #30, 438 | #40, 439 | #50, 440 | #60, 441 | #70, 442 | #80, 443 | #90, 444 | 10000, 445 | #150, 446 | #200, 447 | #300, 448 | #400, 449 | #500, 450 | #600, 451 | #700, 452 | #800, 453 | #900, 454 | #999, 455 | #1999 456 | ] 457 | num_samples = 1 458 | quantile_count = 10 459 | 460 | 461 | #table_type = 'avgs_with_food_count_and_time' 462 | table_type = 'show_distribution' 463 | 464 | #Table where rows are food counts and columns are day counts 465 | #Each cell show mean and stddev of samples 466 | if table_type == 'avgs_with_food_count_and_time': 467 | table_headings = '{0: <4}'.format('') + ' | ' 468 | for num in nums_days_to_try: 469 | table_headings += '{0: ^13}'.format(num) + ' | ' 470 | print('Food v, days >') 471 | print(table_headings) 472 | print('-' * len(table_headings)) 473 | elif table_type == 'show_distribution': 474 | table_headings = '{0: <4}'.format('') + ' | ' 475 | for i in range(quantile_count): 476 | table_headings += '{0: ^5}'.format(i) + ' | ' 477 | print('Sample v, Quantiles >') 478 | print(table_headings) 479 | print('-' * len(table_headings)) 480 | 481 | 482 | for food_count in food_counts_to_try: 483 | samples = [] 484 | for j in range(num_samples): 485 | sample_results = [] 486 | 487 | num_creatures = 2 * food_count 488 | initial_creatures = [] 489 | for k in range(num_creatures): 490 | if k == num_creatures - 1: #% 2 == 0: 491 | cre = hawk_dove.Creature( 492 | fight_chance = 1 493 | ) 494 | else: 495 | cre = hawk_dove.Creature( 496 | fight_chance = 0 497 | ) 498 | '''cre = hawk_dove.Creature( 499 | #fight_chance = 0.2 500 | fight_chance = round(k / num_creatures, 1) 501 | )''' 502 | initial_creatures.append(cre) 503 | 504 | world = hawk_dove_basic.World()#food_count = food_count, initial_creatures = initial_creatures) 505 | 506 | num_days = nums_days_to_try[-1] 507 | for i in range(num_days): 508 | world.new_day() 509 | #print('Done simming day ' + str(i)) 510 | 511 | #print() 512 | for day in world.calendar: 513 | #print('f_hawks: ' + str(len([x for x in day.creatures if x.fight_chance == 1]) / \ 514 | # (len([x for x in day.creatures if x.fight_chance == 0]) + len([x for x in day.creatures if x.fight_chance == 1]))) 515 | #) 516 | #print('Num hawks: ' + str(len([x for x in day.creatures if x.fight_chance == 1]))) 517 | #print('num : ' + str(len(day.creatures))) 518 | #print() 519 | 520 | if table_type == 'avgs_with_food_count_and_time': 521 | avg = statistics.mean([x.fight_chance for x in day.creatures]) 522 | if day.date + 1 in nums_days_to_try: 523 | sample_results.append(avg) 524 | elif table_type == 'show_distribution': 525 | if day.date + 1 in nums_days_to_try: 526 | sample_results = ( 527 | [x.fight_chance for x in day.creatures] 528 | ) 529 | 530 | #print("Average fight chance: " + str(avg)) 531 | 532 | #print('Num shares: ' + str(len(day.morning_contests) + len(day.afternoon_contests))) 533 | #contests = day.morning_contests + day.afternoon_contests 534 | #shares = [x for x in contests if x.outcome == 'share'] 535 | #takes = [x for x in contests if x.outcome == 'take'] 536 | #fights = [x for x in contests if x.outcome == 'fight'] 537 | #print('Num shares: ' + str(len(shares))) 538 | #print('Num takes: ' + str(len(takes))) 539 | #print('Num fights: ' + str(len(fights))) 540 | #for food in day.food_objects: 541 | # print('Eaten = ' + str(food.eaten_time) + ' Creatures = ' + str(food.interested_creatures)) 542 | #print() 543 | 544 | samples.append(sample_results) 545 | 546 | '''print() 547 | for sample in samples: 548 | to_print = ["{0:.2f}".format(round(x, 2)) for x in sample] 549 | print(to_print) 550 | print()''' 551 | 552 | if table_type == 'avgs_with_food_count_and_time': 553 | results_str = '{0: <4}'.format(food_count) + ' | ' 554 | for i in range(len(nums_days_to_try)): 555 | samples_at_day = [] 556 | for sample in samples: 557 | samples_at_day.append(sample[i]) 558 | 559 | results_str += "{0:.2f}".format(round(statistics.mean(samples_at_day),2)) 560 | if len(samples_at_day) > 1: 561 | results_str += ' +/- ' + \ 562 | "{0:.2f}".format(round(statistics.stdev(samples_at_day),2)) 563 | 564 | results_str += ' | ' 565 | 566 | elif table_type == 'show_distribution': 567 | #print(samples) 568 | for sample in samples: 569 | results_str = '{0: <4}'.format(food_count) + ' | ' 570 | for i in range(quantile_count + 1): #1 to catch 100% 571 | num_in_quantile = 0 572 | for cre_fight_chance in sample: #Good naming! ... 573 | if cre_fight_chance < (i + 1) / quantile_count and \ 574 | cre_fight_chance >= i / quantile_count: 575 | num_in_quantile += 1 576 | 577 | results_str += '{0: ^5}'.format(num_in_quantile) + \ 578 | ' | ' 579 | 580 | print(results_str) 581 | 582 | if table_type == 'avgs_with_food_count_and_time': 583 | print(results_str) 584 | 585 | #sim_test() 586 | 587 | def animation_test(): 588 | world = 'hawks_only_10' 589 | 590 | drawn_world = drawn_contest_world.DrawnWorld( 591 | sim = world, 592 | loud = True 593 | ) 594 | 595 | #world = hawk_dove.World(food_count = 61) 596 | world = drawn_world.sim 597 | #sys.setrecursionlimit(10000) 598 | '''num_days = 70 599 | 600 | for i in range(num_days): 601 | save = False 602 | if i == num_days - 1: 603 | save = True 604 | world.new_day(save = save)''' 605 | 606 | drawn_world.add_to_blender(appear_time = 1) 607 | drawn_world.animate_days( 608 | start_time = 2, 609 | first_animated_day = 0, 610 | last_animated_day = 5 611 | ) 612 | 613 | #animation_test() 614 | 615 | def walk_to_test(): 616 | bleb = blobject.Blobject( 617 | scale = 4, 618 | location = [-8, 0, 0], 619 | rotation_euler = [math.pi / 2, 0, 0] 620 | ) 621 | 622 | bleb.add_to_blender() 623 | 624 | bleb.walk_to( 625 | new_location = [8, 0, 0], 626 | new_angle = [math.pi / 2, 0, - math.pi / 2], 627 | start_time = 3, 628 | end_time = 5 629 | ) 630 | 631 | #walk_to_test() 632 | 633 | 634 | initialize_blender() 635 | 636 | def func0(x): 637 | return 4 + x / 5 + 3 * math.sin(x) 638 | 639 | def func1(x): 640 | return 4 + x / 5 + 3 * math.sin(2 * x) 641 | 642 | graph = graph_bobject.GraphBobject( 643 | func0, 644 | func1, 645 | centered = True 646 | ) 647 | 648 | #Times in seconds 649 | graph.add_to_blender(appear_time = 0) #Animate appearance of axes and labels 650 | graph.animate_function_curve(start_time = 1, end_time = 2) #Animate drawing of curve 651 | graph.morph_curve(1, start_time = 3) #Change curve to different function. First arg is destination function index 652 | graph.change_window( #Animate change of window with same function. 653 | new_x_range = [0, 15], 654 | new_y_range = [0, 20], 655 | new_tick_step = [5, 5], 656 | start_time = 5 657 | ) 658 | 659 | def main(): 660 | """Use this as a test scene""" 661 | #tex_test() 662 | """""" 663 | #initialize_blender() 664 | 665 | #circle_grid() 666 | #test() 667 | make_blob_with_actions_for_unity() 668 | #draw_scenes_from_file(final_vestibular_animations_anatomy, clear = False) 669 | #draw_scenes_from_file(final_vestibular_animations_text) 670 | 671 | print_time_report() 672 | finish_noise() 673 | 674 | if __name__ == "__main__": 675 | try: 676 | main() 677 | except: 678 | print_time_report() 679 | finish_noise(error = True) 680 | raise() 681 | -------------------------------------------------------------------------------- /blender_scripts/import.py: -------------------------------------------------------------------------------- 1 | #This script is a place for me to run random code with the script runner addon 2 | #https://wiki.blender.org/index.php/Extensions:2.6/Py/Scripts/Import-Export/Script_Runner 3 | 4 | import sys 5 | sys.path.append('C:\\Users\\justi\\Documents\\CodeProjects\\Primer\\blender_scripts') 6 | sys.path.append('C:\\Users\\justi\\Documents\\CodeProjects\\Primer\\blender_scripts\\tools') 7 | 8 | import imp 9 | 10 | import helpers 11 | imp.reload(helpers) 12 | from helpers import * 13 | from constants import * 14 | import tex_bobject 15 | imp.reload(tex_bobject) 16 | import gesture 17 | imp.reload(gesture) 18 | import svg_bobject 19 | imp.reload(svg_bobject) 20 | 21 | #reddit = import_object('logo') 22 | 23 | #initialize_blender 24 | 25 | bracket = gesture.Gesture( 26 | gesture_series = [ 27 | { 28 | 'type': 'arrow', 29 | 'points': { 30 | 'tail': (0, -2, 0), 31 | 'head': (2, 0, 0) 32 | } 33 | } 34 | ] 35 | ) 36 | bracket.add_to_blender(appear_frame = 0) 37 | 38 | reddit = import_object( 39 | 'reddit', 'svgblend', 40 | scale = 1, 41 | location = (0, 0, 0) 42 | ) 43 | 44 | #define_materials() 45 | 46 | #yt = tex_bobject.TexBobject( 47 | # '\\xcancel{B} + N \\times R = N \\times D' 48 | #) 49 | #yt.add_to_blender(appear_frame = 0) 50 | -------------------------------------------------------------------------------- /blender_scripts/render.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | import subprocess 3 | import os 4 | import shutil 5 | 6 | import sys 7 | sys.path.append('C:\\Users\\justi\\Documents\\CodeProjects\\Primer\\blender_scripts') 8 | sys.path.append('C:\\Users\\justi\\Documents\\CodeProjects\\Primer\\blender_scripts\\tools') 9 | import helpers 10 | 11 | overwrite = True 12 | 13 | def render_with_skips(start, stop): 14 | """ 15 | Take start and stop, and render animation only for animated 16 | frames. Still frames, are substituted into the output folder 17 | as copies of their equivalents. 18 | """ 19 | 20 | render_range = list(range(start, stop + 1)) 21 | #+1 because range should for frames should be inclusive 22 | 23 | # create JSON like dictionary to store each 24 | # animated object's fcurve data at each frame. 25 | all_obj_fcurves = {} 26 | for obj in bpy.data.objects: 27 | obj_fcurves = {} 28 | 29 | try: 30 | obj.animation_data.action.fcurves 31 | except AttributeError: 32 | #print("--|'%s' is not animated" % obj.name) 33 | continue 34 | 35 | #print("\n--> '%s' is animated at frames:" % obj.name) 36 | 37 | for fr in list(range(start,stop+1)): 38 | fc_evals = [c.evaluate(fr) for c in obj.animation_data.action.fcurves] 39 | obj_fcurves.update({int(fr): fc_evals}) 40 | #print(fr, end=", ") 41 | #print() 42 | 43 | all_obj_fcurves.update({obj.name: obj_fcurves}) 44 | 45 | 46 | # loop through each animated object and find its 47 | # animated frames. then remove those frames from 48 | # a set containing all frames, to get still frames. 49 | still_frames = set(render_range) 50 | for obj in all_obj_fcurves.keys(): 51 | obj_animated_frames = [] 52 | for i, fr in enumerate(sorted(all_obj_fcurves[obj].keys())): 53 | if i != 0: 54 | if all_obj_fcurves[obj][fr] != all_obj_fcurves[obj][fr_prev]: 55 | obj_animated_frames.append(fr) 56 | fr_prev = fr 57 | 58 | still_frames = still_frames - set(obj_animated_frames) 59 | 60 | print("\nFound %d still frames" % len(still_frames)) 61 | print(sorted(still_frames), end="\n\n") 62 | 63 | 64 | # render animation, skipping the still frames and 65 | # filling them in as copies of their equivalents 66 | filepath = bpy.context.scene.render.filepath 67 | 68 | for fr in render_range: 69 | exists = False 70 | if overwrite == False: 71 | directory = os.fsencode(filepath) 72 | for file in os.listdir(directory): 73 | filename = os.fsdecode(file) 74 | if ('%04d' % fr) in filename: 75 | exists = True 76 | if exists == False: 77 | if fr not in still_frames or fr == render_range[0]: 78 | bpy.context.scene.frame_set(fr) 79 | bpy.context.scene.render.filepath = filepath + '%04d' % fr 80 | bpy.context.scene.render.use_overwrite = False 81 | bpy.ops.render.render(write_still=True) 82 | else: 83 | scene = bpy.context.scene 84 | abs_filepath = scene.render.frame_path(scene.frame_current) 85 | #abs_path = '/'.join(abs_filepath.split('/')[:-1]) + '/' 86 | print("Frame %d is still, copying from equivalent" % fr) 87 | scn = bpy.context.scene 88 | shutil.copyfile(filepath + '%04d.png' % (fr-1), filepath + '%04d.png' % fr) 89 | 90 | bpy.context.scene.render.filepath = filepath 91 | 92 | start = bpy.data.scenes['Scene'].frame_start 93 | end = bpy.data.scenes['Scene'].frame_end 94 | render_with_skips(start,end) 95 | helpers.finish_noise() 96 | -------------------------------------------------------------------------------- /blender_scripts/tools/centipede.py: -------------------------------------------------------------------------------- 1 | import math 2 | import statistics 3 | import random 4 | 5 | class Player(object): 6 | def __init__( 7 | self, 8 | first_num_passes = 0, 9 | second_num_passes = 0 10 | ): 11 | self.score = 0 12 | self.first_num_passes = first_num_passes 13 | self.second_num_passes = second_num_passes 14 | 15 | 16 | class Tournament(object): 17 | def __init__( 18 | self, 19 | max_game_length = 10, 20 | initial_players = None, 21 | num_player_target = 1000, 22 | mutation_chance = 0 23 | ): 24 | self.max_game_length = max_game_length 25 | self.mutation_chance = mutation_chance 26 | 27 | #Default initial player set is one of each possible strategy 28 | if initial_players == None or initial_players == 'spread': 29 | 30 | 31 | num_kinds = round(math.sqrt(num_player_target)) 32 | spacing = self.max_game_length / (num_kinds - 1) 33 | initial_players = [] 34 | for i in range(num_kinds): 35 | for j in range(num_kinds): 36 | player = Player( 37 | first_num_passes = round(i * spacing), 38 | second_num_passes = round(j * spacing) 39 | ) 40 | initial_players.append(player) 41 | 42 | elif initial_players == 'trusters': 43 | initial_players = [] 44 | for i in range(num_player_target): 45 | player = Player( 46 | first_num_passes = self.max_game_length, 47 | second_num_passes = self.max_game_length 48 | ) 49 | initial_players.append(player) 50 | elif initial_players == 'untrusters': 51 | initial_players = [] 52 | for i in range(num_player_target): 53 | player = Player( 54 | first_num_passes = 0, 55 | second_num_passes = 0 56 | ) 57 | initial_players.append(player) 58 | 59 | if not isinstance(initial_players, list): 60 | raise Warning('Initial player set must be a list') 61 | 62 | #Ensure num_player_target is set. Sometimes redundant. 63 | self.num_player_target = len(initial_players) 64 | print(self.num_player_target) 65 | 66 | self.round_log = [initial_players] 67 | 68 | 69 | def play_round(self): 70 | print('Playing round ' + str(len(self.round_log))) 71 | #Play games 72 | players = self.round_log[-1] 73 | #print(len(players)) 74 | #Let each pair of players play twice, swapping first move 75 | for player in players: 76 | for other_player in players: 77 | if player != other_player: 78 | self.play_game(player, other_player) 79 | 80 | #Create next player set 81 | tot_score = sum([x.score for x in players]) 82 | #print(tot_score) 83 | 84 | next_players = [] 85 | 86 | #For each possible strategy, award offspring number proportional to 87 | #total score of that strategy 88 | #Doesn't take care of mutations below zero or above the max. 89 | #Just kills them. Eh. 90 | for i in range(self.max_game_length + 1): 91 | for j in range(self.max_game_length + 1): 92 | pws = [x for x in players if \ 93 | x.first_num_passes == i and x.second_num_passes == j] 94 | 95 | s_score = sum([x.score for x in pws]) 96 | num_offspring = round(s_score / tot_score * self.num_player_target) 97 | 98 | 99 | for k in range(num_offspring): 100 | f_var = 0 101 | s_var = 0 102 | mutation_roll = random.random() 103 | if mutation_roll < self.mutation_chance / 4: 104 | f_var = 1 105 | elif mutation_roll < self.mutation_chance / 2: 106 | f_var = -1 107 | elif mutation_roll < 3 * self.mutation_chance / 4: 108 | s_var = 1 109 | elif mutation_roll < self.mutation_chance: 110 | s_var = -1 111 | 112 | new_player = Player( 113 | first_num_passes = i + f_var, 114 | second_num_passes = j + s_var 115 | ) 116 | next_players.append(new_player) 117 | 118 | #print(len(next_players)) 119 | 120 | self.round_log.append(next_players) 121 | 122 | def play_game(self, player1, player2): 123 | if player1.first_num_passes >= self.max_game_length / 2 and \ 124 | player2.second_num_passes >= self.max_game_length / 2: 125 | player1.score += 2 * self.max_game_length 126 | player2.score += 2 * self.max_game_length 127 | elif player1.first_num_passes <= player2.second_num_passes: 128 | player1.score += 2 + 2 * player1.first_num_passes 129 | player2.score += 2 * player1.first_num_passes 130 | elif player1.first_num_passes > player2.second_num_passes: 131 | player1.score += 1 + 2 * player2.second_num_passes 132 | player2.score += 3 + 2 * player2.second_num_passes 133 | 134 | def print_stats(self): 135 | print() 136 | for rou in self.round_log: 137 | fnps = [x.first_num_passes for x in rou] 138 | avg_fnp = statistics.mean(fnps) 139 | std_dev_fnp = statistics.pstdev(fnps) 140 | 141 | snps = [x.second_num_passes for x in rou] 142 | avg_snp = statistics.mean(snps) 143 | std_dev_snp = statistics.pstdev(snps) 144 | 145 | print( 146 | str(round(avg_fnp, 1)) + ', ' + \ 147 | str(round(std_dev_fnp, 1)) + ', ' + \ 148 | str(round(avg_snp, 1)) + ', ' + \ 149 | str(round(std_dev_snp, 1)) + ', ' + \ 150 | str(len(rou)) 151 | ) 152 | #print() 153 | -------------------------------------------------------------------------------- /blender_scripts/tools/creature.py: -------------------------------------------------------------------------------- 1 | class Creature(object): 2 | def __init__(self, size = '1', color = 'creature_color_1', shape = 'shape1'): 3 | self.alleles = { 4 | "size" : size, 5 | "color" : color, 6 | "shape" : shape 7 | } 8 | self.birthday = None 9 | self.deathday = None 10 | self.name = None 11 | self.parent = None 12 | self.children = [] 13 | 14 | self.locations = [] 15 | self.velocities = [] 16 | 17 | self.bobject = None 18 | -------------------------------------------------------------------------------- /blender_scripts/tools/drawn_world.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | import mathutils 3 | 4 | import imp 5 | import pickle 6 | import math 7 | 8 | import two_d_world 9 | import tex_bobject 10 | import constants 11 | 12 | #import alone doesn't check for changes in cached files 13 | imp.reload(two_d_world) 14 | from two_d_world import TwoDWorld 15 | imp.reload(tex_bobject) 16 | imp.reload(constants) 17 | from constants import * 18 | 19 | import helpers 20 | imp.reload(helpers) 21 | from helpers import * 22 | 23 | import bobject 24 | imp.reload(bobject) 25 | from bobject import * 26 | 27 | class DrawnWorld(TwoDWorld, Bobject): 28 | """docstring for .DrawnWorld""" 29 | def __init__( 30 | self, 31 | name = '', 32 | location = (0, 0, 0), 33 | scale = 0, 34 | appear_frame = 0, 35 | start_delay = 0, 36 | sim_duration = None, 37 | sim_duration_seconds = None, 38 | frames_per_time_step = 5, 39 | load = None, 40 | save = False, 41 | spin_creatures = False, 42 | pauses = [[0, 0]], #[pause_at, pause_duration] 43 | overlap_okay = False, 44 | initial_creatures = 10, 45 | gene_updates = [], 46 | #motion_vars = None, 47 | counter_alignment = 'right_top', 48 | creature_model = ['boerd_blob', 'creatures', 'boerd_blob_squat', 'creatures'], 49 | world_bound_points = [], 50 | bound_mode = 'rect', 51 | show_world = True 52 | ): 53 | if sim_duration_seconds != None: 54 | if sim_duration != None: 55 | raise Warning("You defined duration in both frames and seconds. " +\ 56 | "Just do one, ya dick.") 57 | sim_duration = int(sim_duration_seconds * FRAME_RATE / frames_per_time_step) 58 | if sim_duration == None: 59 | sim_duration = DEFAULT_WORLD_DURATION 60 | #This is slightly wonky because we want to save instances of 61 | #TwoDWorld (the parent class), but not instances of DrawnWorld 62 | #itself. 63 | #TwoDWorld has data that's randomly generated and independent from 64 | #exactly how objects appear when rendered in Blender, so it's useful for 65 | #saving a nice-looking sim. 66 | #DrawnWorld's additional attributes aren't generated randomly, and 67 | #They will be manually changed while working on a video, so saving 68 | #isn't helpful here. 69 | if load != None: 70 | self.get_saved_world(load) 71 | else: 72 | world = TwoDWorld( 73 | sim_duration = sim_duration, 74 | overlap_okay = overlap_okay, 75 | initial_creatures = initial_creatures, 76 | gene_updates = gene_updates, 77 | frames_per_time_step = frames_per_time_step, 78 | world_bound_points = world_bound_points, 79 | bound_mode = bound_mode, 80 | ) 81 | self.__dict__ = world.__dict__ 82 | if save == True: 83 | world.save_sim_result() 84 | self.show_world = show_world 85 | 86 | #It is a world. It is a Bobject. 87 | Bobject.__init__(self, 88 | location = location, 89 | scale = scale, 90 | #appear_frame = appear_frame 91 | ) 92 | 93 | self.sim_duration = sim_duration # - start_delay 94 | 95 | #self.frames_per_time_step = 1 96 | #print('There are ' + str(self.frames_per_time_step) +' frames per time step') 97 | #self.sim_duration_in_frames = self.sim_duration * self.frames_per_time_step 98 | 99 | #Could make radius more than 1D, but for now, just uses the 100 | #x component of scale 101 | #self.world_radius = self.scale[0] * self.radius #attribute of TwoDWorld 102 | self.start_delay = int(start_delay * FRAME_RATE) 103 | #self.disappear_frame = self.start_frame + self.sim_duration 104 | 105 | #Pauses are given in sim time. Translate to frame time because that's 106 | #how they're used. Should rethink what to define with which units. 107 | for pause in pauses: 108 | pause[0] *= frames_per_time_step 109 | pause[1] *= frames_per_time_step 110 | self.pauses = pauses 111 | 112 | self.info = [] 113 | self.counter_times_lists = [] 114 | #I kind of hate the above line, but it was the easiest way to separate 115 | #The creation of the python counter object and the morphing of the 116 | #characters after adding to blender 117 | self.counter_alignment = counter_alignment 118 | self.creature_model = creature_model 119 | 120 | self.spin_creatures = spin_creatures 121 | 122 | def get_saved_world(self, world_file): 123 | result = os.path.join( 124 | SIM_DIR, 125 | world_file 126 | ) + ".pkl" 127 | with open(result, 'rb') as input: 128 | print(input) 129 | world = pickle.load(input) 130 | self.__dict__ = world.__dict__ 131 | print("Loaded the world") 132 | 133 | def add_to_blender(self, **kwargs): 134 | #Create floor 135 | if self.show_world == True: 136 | plane = import_object( 137 | 'xyplane', 'primitives', 138 | scale = self.radius * 1.1, 139 | location = (0, 0, -0.7), 140 | name = 'sim_plane' 141 | ) 142 | #print(plane.ref_obj.scale) 143 | apply_material(plane.ref_obj.children[0], 'color2') 144 | self.add_subbobject(plane) 145 | 146 | super().add_to_blender(**kwargs) 147 | #self.appear_frame = kwargs['appear_frame'] 148 | self.start_frame = self.appear_frame + self.start_delay 149 | self.align_info() 150 | self.morph_counters() 151 | self.add_creatures_to_blender() 152 | self.set_world_keyframes() 153 | 154 | def add_creatures_to_blender(self): 155 | 156 | #plane.ref_obj.parent = self.ref_obj 157 | #print(plane.ref_obj.scale) 158 | #plane.superbobject = self 159 | #self.subbobjects.append(plane) 160 | #plane.add_to_blender(appear_frame = self.appear_frame) 161 | 162 | print("Adding " + str(len(self.creatures)) + " creatures to Blender") 163 | #creature_cache = [] 164 | for cre in self.creatures: 165 | cre.reused = False 166 | cre_needs_bobject = True 167 | #Assumes creatures are in order of birthframe by assuming 168 | #other_cre.bobject and other_cre.reused exist. 169 | for other_cre in self.creatures: 170 | #2 * MATURATION_TIME because that's the minimum length of time 171 | #for a creature to be animated as it's born and then dies 172 | #EDIT: I haven't dug into this, but for some reason, 2 times 173 | #maturation time doesn't cut it. I just made it 3x. This means 174 | #more unique creatures will be made than absolutely necessary, 175 | #but it still puts a manageable cap on longer sims, so ¯\_(ツ)_/¯. 176 | if other_cre != cre and \ 177 | other_cre.deathframe + \ 178 | 3 * MATURATION_TIME < cre.birthframe and \ 179 | other_cre.alleles == cre.alleles and \ 180 | other_cre.reused == False: 181 | cre.bobject = other_cre.bobject 182 | delay = 0 183 | for pause in self.pauses: 184 | if cre.birthframe > pause[0]: 185 | delay += pause[1] 186 | cre.bobject.add_to_blender( 187 | appear_frame = self.start_frame + \ 188 | cre.birthframe + delay, 189 | is_creature = True 190 | ) 191 | other_cre.reused = True 192 | cre_needs_bobject = False 193 | print('Hand-me-down for ' + str(cre.name)) 194 | break 195 | if cre_needs_bobject == True: 196 | self.add_creature_to_blender(cre) 197 | 198 | bpy.ops.object.select_all(action='DESELECT') 199 | 200 | def add_creature_to_blender(self, cre): 201 | size = float(cre.alleles['size']) #* self.scale[0] 202 | type_req = None #For applying material to only objects of certain type 203 | old_mats = [x for x in bpy.data.materials] #copy doesn't work on this object 204 | 205 | if RENDER_QUALITY != 'high': 206 | if cre.alleles['shape'] == "shape1": 207 | bobj = import_object('icosphere', 'primitives') 208 | if cre.alleles['shape'] == "shape2": 209 | bobj = import_object('torus', 'primitives') 210 | else: 211 | model = self.creature_model 212 | if model == ['boerd_blob', 'creatures', 'boerd_blob_squat', 'creatures']: 213 | type_req = 'META' 214 | if cre.alleles['shape'] == "shape1": 215 | bobj = import_object(model[0], model[1]) 216 | if cre.alleles['shape'] == "shape2": 217 | bobj = import_object(model[2], model[3]) 218 | 219 | delay = 0 220 | for pause in self.pauses: 221 | if cre.birthframe > pause[0]: 222 | delay += pause[1] 223 | bobj.add_to_blender( 224 | appear_frame = self.start_frame + cre.birthframe + delay, 225 | is_creature = True 226 | ) 227 | cre.bobject = bobj 228 | obj = bobj.ref_obj 229 | 230 | if cre.alleles['color'] == 'creature_color_1': 231 | col = 'creature_color3' 232 | elif cre.alleles['color'] == 'creature_color_2': 233 | col = 'creature_color7' 234 | elif cre.alleles['color'] == 'creature_color_3': 235 | col = 'creature_color6' 236 | elif cre.alleles['color'] == 'creature_color_4': 237 | col = 'creature_color4' 238 | 239 | recursive = True 240 | if 'stanford_bunny' in self.creature_model: 241 | recursive = False 242 | 243 | apply_material(obj.children[0], col, recursive = recursive, type_req = type_req) 244 | #Remove creature's previous materials since this can take up a lot of 245 | #memory if we duplicate complex materials many times 246 | new_mats = [x for x in bpy.data.materials if x not in old_mats] 247 | for mat in new_mats: 248 | bpy.data.materials.remove(mat) 249 | 250 | #obj.parent = self.ref_obj 251 | self.add_subbobject(cre.bobject) 252 | #obj.matrix_parent_inverse = obj.parent.matrix_world.inverted() 253 | print("Added " + str(cre.name)) 254 | 255 | def set_world_keyframes(self): 256 | for cre in self.creatures: 257 | #print("Setting keyframes for " + str(cre.name)) 258 | bobj = cre.bobject 259 | obj = bobj.ref_obj 260 | 261 | #Set scale to 1 in case cre.bobject is being reused. 262 | obj.scale = (1, 1, 1) 263 | 264 | delay = 0 265 | for pause in self.pauses: 266 | if cre.deathframe > pause[0]: 267 | delay += pause[1] 268 | 269 | #If creature dies right away, animate as if it lasted long enough to 270 | #finish appearing before dying. 2 * maturation time because creatures 271 | #start appearing on their birthframe and finish disappearing on their 272 | #deathframe. 273 | effective_deathframe = cre.deathframe 274 | #MATURATION_TIME is defined in frames, but we want it in sim time 275 | #steps 276 | #mat_time_in_sim_time = MATURATION_TIME / self.frames_per_time_step 277 | if effective_deathframe - cre.birthframe < 2 * MATURATION_TIME: 278 | effective_deathframe = cre.birthframe + 2 * MATURATION_TIME 279 | disappear_time = effective_deathframe + MATURATION_TIME + delay 280 | #Ensure creature disappears in place. 281 | try: 282 | obj.location = cre.locations[cre.deathframe] 283 | except: 284 | obj.location = cre.locations[-1] 285 | obj.keyframe_insert( 286 | data_path = 'location', 287 | frame = self.start_frame + disappear_time 288 | ) 289 | 290 | #Make creatures disappear if they died before the sim ended. 291 | #(All creatures are given a numerical deathframe even if they last 292 | #the whole time) 293 | if cre.deathframe < self.animated_duration: 294 | bobj.disappear( 295 | disappear_frame = self.start_frame + disappear_time, 296 | is_creature = True 297 | ) 298 | 299 | for t in range(self.animated_duration + 1): 300 | if cre.locations and cre.locations[t] != None: 301 | obj.location = cre.locations[t] 302 | 303 | delay = 0 304 | for pause in self.pauses: 305 | if t > pause[0]: 306 | delay += pause[1] 307 | 308 | key_time = t + delay 309 | 310 | obj.keyframe_insert( 311 | data_path = "location", 312 | frame = key_time + self.start_frame 313 | ) 314 | 315 | if RENDER_QUALITY == 'high': 316 | if self.creature_model == \ 317 | ['boerd_blob', 'creatures', 'boerd_blob_squat', 'creatures']: 318 | if cre.head_angle and cre.head_angle[t] != None: 319 | bone = obj.children[0].pose.bones[3] 320 | bone.rotation_quaternion = cre.head_angle[t] 321 | bone.keyframe_insert( 322 | data_path="rotation_quaternion", 323 | frame = key_time + self.start_frame 324 | ) 325 | if self.spin_creatures and \ 326 | cre.rotation and cre.rotation[t] != None: 327 | obj.rotation_euler = [0, 0, cre.rotation[t]] 328 | 329 | delay = 0 330 | for pause in self.pauses: 331 | if t > pause[0]: 332 | delay += pause[1] 333 | 334 | key_time = t + delay 335 | 336 | obj.keyframe_insert( 337 | data_path = "rotation_euler", 338 | frame = key_time + self.start_frame 339 | ) 340 | 341 | 342 | def add_counter( 343 | self, 344 | location = 'right_top', 345 | size = 'any', 346 | shape = 'any', 347 | color = 'any', 348 | average = False, 349 | label = '' 350 | ): 351 | 352 | #Counting business 353 | count_by_time = self.get_creature_count_by_t(size, shape, color) 354 | if average == True: 355 | average_by_time = [] 356 | for i in range(len(count_by_time)): 357 | total = 0 358 | for j in range(i + 1): 359 | total += count_by_time[j] 360 | avg = total / (i + 1) 361 | avg = round(avg, 1) 362 | average_by_time.append(avg) 363 | count_by_time = average_by_time 364 | 365 | 366 | #print("Creating expressions for " + label) 367 | expressions = ['0'] 368 | expression_times = [0] 369 | #Make list of expressions for each time the count changes 370 | count = -math.inf 371 | for t in range(self.animated_duration): 372 | if count_by_time[t] != count: 373 | count = count_by_time[t] 374 | expressions.append(str(count)) 375 | expression_times.append(t) 376 | self.counter_times_lists.append(expression_times) 377 | #Everything above here could perhaps be moved to morph_counters, since 378 | #It's all preparing for that. 379 | 380 | label_tex = tex_bobject.TexBobject(label) 381 | count_tex = tex_bobject.TexBobject(*expressions, transition_type = 'instant') 382 | #Lazy morphing is too intensive for a counter that switches to many 383 | #values, and the counting happens too fast for it to be appeciated 384 | #anyway. 385 | counter = tex_complex.TexComplex( 386 | label_tex, count_tex, 387 | name = 'sim_counter ' + label 388 | ) 389 | 390 | self.info.append(counter) 391 | self.subbobjects.append(counter) 392 | counter.ref_obj.parent = self.ref_obj 393 | counter.superbobject = self 394 | 395 | def add_info(self, info): 396 | #Could maybe override the info.append() to prevent it from being done 397 | #the wrong way. 398 | self.info.append(info) 399 | self.subbobjects.append(info) 400 | info.ref_obj.parent = self.ref_obj 401 | info.superbobject = self 402 | 403 | def align_info(self, frame = None): 404 | align = self.counter_alignment 405 | #Sort out the location 406 | #num_prev_counters = len(self.info) 407 | #Will add cases as needed 408 | locs = [] 409 | for i in range(len(self.info)): 410 | locs.append([0, 0, 0]) 411 | for i, loc in enumerate(locs): 412 | if align == 'right_top': 413 | loc[0] = 8.5 414 | loc[1] = 7 - i * 1.5 #Could make this sensitive to counter 415 | #scale, but may not be necessary 416 | elif align == 'top_left': 417 | loc[0] = -8 418 | loc[1] = 9 419 | #bump the previous ones up, since we're aligning to bottom here 420 | for j in range(i): 421 | locs[j][1] += 1.5 422 | 423 | #frame is used when updating info after the drawn_world has been added 424 | #to blender 425 | if frame == None: 426 | for loc, info in zip(locs, self.info): 427 | info.ref_obj.location = loc 428 | else: 429 | for loc, info in zip(locs, self.info): 430 | if info.ref_obj.name not in bpy.context.scene.objects: 431 | info.ref_obj.location = loc 432 | info.add_to_blender(appear_frame = frame, animate = True) 433 | else: 434 | info.move_to( 435 | new_location = loc, 436 | start_frame = frame, 437 | end_frame = frame + OBJECT_APPEARANCE_TIME 438 | ) 439 | 440 | def morph_counters(self): 441 | for counter, frames in zip(self.info, self.counter_times_lists): 442 | count_tex = counter.subbobjects[1] #The second tex_bobject is the counter 443 | for i in range(1, len(frames)): 444 | frame = frames[i] 445 | #morph quickly if there's another animation coming up, otherwise 446 | #morph over three frames. 447 | try: 448 | morph_duration = frames[i + 1] - frames[i] 449 | if morph_duration > 6: #6 is an arbitrary max duration 450 | morph_duration = 6 451 | except: #should only run for the last expression 452 | morph_duration = self.animated_duration - frames[i] 453 | if morph_duration > 6: #6 is an arbitrary max duration 454 | morph_duration = 6 455 | 456 | #"Morphed expression " + str(i) 457 | #print(count_tex.expressions[i], frame + self.start_frame, frame + self.start_frame + morph_duration) 458 | 459 | #2 * frame because of change from 30 to 60 fps 460 | count_tex.morph_figure( 461 | i, 462 | start_frame = frame + self.start_frame, 463 | duration = morph_duration 464 | ) 465 | 466 | def move_to(self, new_counter_alignment = None, **kwargs): 467 | super().move_to(**kwargs) 468 | 469 | #Convert start_time to start_frame (and/or end) 470 | if 'start_time' in kwargs: 471 | if 'start_frame' in kwargs: 472 | raise Warning("You defined both start frame and start time." + \ 473 | "Just do one, ya dick.") 474 | kwargs['start_frame'] = kwargs['start_time'] * FRAME_RATE 475 | if 'end_time' in kwargs: 476 | if 'end_frame' in kwargs: 477 | raise Warning("You defined both end frame and end time." + \ 478 | "Just do one, ya dick.") 479 | kwargs['end_frame'] = kwargs['end_time'] * FRAME_RATE 480 | 481 | #Ensure start_frame and end_frame are set 482 | if 'start_frame' in kwargs: 483 | start_frame = kwargs['start_frame'] 484 | else: 485 | if 'end_frame' in kwargs: 486 | start_frame = kwargs['end_frame'] - OBJECT_APPEARANCE_TIME 487 | else: 488 | raise Warning('Need to specify start frame or end frame for move_to') 489 | 490 | if 'end_frame' in kwargs: 491 | end_frame = kwargs['end_frame'] 492 | else: 493 | end_frame = start_frame + OBJECT_APPEARANCE_TIME 494 | 495 | 496 | if new_counter_alignment != None: 497 | for counter in self.info: 498 | counter.ref_obj.keyframe_insert(data_path = 'location', frame = start_frame) 499 | self.counter_alignment = new_counter_alignment 500 | self.align_info() 501 | for counter in self.info: 502 | counter.ref_obj.keyframe_insert(data_path = 'location', frame = end_frame) 503 | -------------------------------------------------------------------------------- /blender_scripts/tools/gesture.py: -------------------------------------------------------------------------------- 1 | import math 2 | import svg_bobject 3 | from svg_bobject import SVGFromBlend 4 | from helpers import * 5 | 6 | class Gesture(SVGFromBlend): 7 | """This is convoluted and everything is named horribly. My bad.""" 8 | def __init__( 9 | self, 10 | gesture_series = None, 11 | reindex_points_before_morph = False, 12 | **kwargs 13 | ): 14 | 15 | self.gesture_series = gesture_series 16 | if self.gesture_series == None: 17 | raise Warning('Hey, you need to define the gesture objects') 18 | 19 | paths = [] 20 | #Assign paths to pass to super. The paths should be made unique because 21 | #the gesture curves will be modified and be different, even though they 22 | #might come from the same file. Also, they aren't really paths, but 23 | #svg bobject takes paths by default, so we're sort of pretending here. 24 | for i, gesture in enumerate(self.gesture_series): 25 | if gesture['type'] == 'bracket': 26 | paths.append('bracket ' + str(i)) 27 | if gesture['type'] == 'arrow': 28 | paths.append('arrow' + str(i)) 29 | if gesture['type'] == None: 30 | paths.append(None) 31 | 32 | kwargs['reindex_points_before_morph'] = reindex_points_before_morph 33 | 34 | #TODO: Fix the fact that passing a name to an SVGFromBlend object 35 | #breaks things. 36 | #if 'name' not in kwargs: 37 | # kwargs['name'] = 'bracket' 38 | super().__init__(*paths, **kwargs) 39 | 40 | self.curve = self.ref_obj 41 | #print(self.ref_obj.name) 42 | 43 | def align_figures(self): 44 | pass #Avoid using the method from svg_bobject 45 | 46 | def process_points(self, gesture): 47 | if gesture['type'] == 'bracket': 48 | #Vectors and lengths from points 49 | width_vec = add_lists_by_element(gesture['points']['right_point'], 50 | gesture['points']['left_point'], 51 | subtract = True) 52 | #print(width_vec) 53 | width_unit_vec = get_unit_vec(width_vec) 54 | width = vec_len(width_vec) 55 | #print("Width unit vec length, which should be 1, is " + str(vec_len(width_unit_vec))) 56 | left_to_top_vec = add_lists_by_element(gesture['points']['annotation_point'], 57 | gesture['points']['left_point'], 58 | subtract = True) 59 | left_to_top = vec_len(left_to_top_vec) 60 | 61 | #Rotation 62 | angle = math.atan2(width_vec[1], width_vec[0]) 63 | #self.ref_obj.rotation_euler = [0, 0, angle] 64 | 65 | #Extend arms/stem 66 | default_left_length = 1.3689 #Distance in bracket.blend 67 | left_length = dot_product(width_unit_vec, left_to_top_vec) 68 | left_extension = left_length - default_left_length 69 | #print("Left extension: " + str(self.left_extention)) 70 | #Might warp the bracket if arms are too short. 71 | 72 | default_right_length = 1.3688 73 | right_length = width - left_length 74 | right_extension = right_length - default_right_length 75 | 76 | default_height = 1 77 | height = math.sqrt(left_to_top ** 2 - left_length ** 2) 78 | height_extension = height - default_height 79 | 80 | return { 81 | 'angle': angle, 82 | 'left_extension': left_extension, 83 | 'right_extension': right_extension, 84 | 'height_extension': height_extension 85 | } 86 | 87 | elif gesture['type'] == 'arrow': 88 | #Vectors and lengths from points 89 | length_vec = add_lists_by_element(gesture['points']['head'], 90 | gesture['points']['tail'], 91 | subtract = True) 92 | length = vec_len(length_vec) 93 | 94 | angle = math.atan2(length_vec[1], length_vec[0]) + math.pi / 2 95 | 96 | default_length = 2 97 | extension = length - default_length 98 | return { 99 | 'angle': angle, 100 | 'extension': extension 101 | } 102 | 103 | elif gesture['type'] == None: 104 | return { 105 | 'location': gesture['points']['location'], 106 | 'angle': gesture['points']['rotation'][2] 107 | } 108 | 109 | def deform(self, curve, gesture): 110 | #curve = self.ref_obj.children[0].children[0] 111 | #curve = self.imported_svg_data[self.paths[0]]['curves'][0].ref_obj.children[0] 112 | #curve = self.morph_chains[0][0].ref_obj.children[0] 113 | 114 | points = curve.data.splines[0].bezier_points 115 | params = self.process_points(gesture) 116 | 117 | if gesture['type'] == 'bracket': 118 | for i in range(4, 11): #Happens to be the points on the left arm 119 | point = points[i] 120 | point.co[0] -= params['left_extension'] 121 | point.handle_left[0] -= params['left_extension'] 122 | point.handle_right[0] -= params['left_extension'] 123 | 124 | for i in range(15, 20): #Happens to be the points on the right arm 125 | point = points[i] 126 | point.co[0] += params['right_extension'] 127 | point.handle_left[0] += params['right_extension'] 128 | point.handle_right[0] += params['right_extension'] 129 | 130 | for i in range(1, 23): #Happens to be the points on the top 131 | point = points[i] 132 | point.co[1] -= params['height_extension'] 133 | point.handle_left[1] -= params['height_extension'] 134 | point.handle_right[1] -= params['height_extension'] 135 | 136 | elif gesture['type'] == 'arrow': 137 | for i in range(2, 9): #Happens to be the points on left side 138 | point = points[i] 139 | point.co[1] -= params['extension'] 140 | point.handle_left[1] -= params['extension'] 141 | point.handle_right[1] -= params['extension'] 142 | 143 | '''elif gesture['type'] == None: 144 | for i in range(2, 9): #Happens to be the points on left side 145 | point = points[i] 146 | point.co[1] -= params['extension'] 147 | point.handle_left[1] -= params['extension'] 148 | point.handle_right[1] -= params['extension']''' 149 | 150 | curve.rotation_euler = (0, 0, params['angle']) 151 | 152 | def import_and_modify_curve(self, index, path): 153 | #path is purposely not used by this method, which overrides a method 154 | #from the super class 155 | gesture = self.gesture_series[index] 156 | 157 | if gesture['type'] == 'bracket': 158 | path = ['bracket', 'svgblend'] 159 | new_curve_bobj = import_object(*path) 160 | ref = new_curve_bobj.ref_obj.children[0] 161 | curve = ref.children[0] 162 | self.deform(curve, gesture) 163 | curve.location = gesture['points']['annotation_point'] 164 | 165 | elif gesture['type'] == 'arrow': 166 | path = ['arrow', 'svgblend'] 167 | new_curve_bobj = import_object(*path) 168 | ref = new_curve_bobj.ref_obj.children[0] 169 | curve = ref.children[0] 170 | self.deform(curve, gesture) 171 | curve.location = gesture['points']['tail'] 172 | 173 | elif gesture['type'] == None: 174 | null = svg_bobject.new_null_curve() 175 | curve = null.ref_obj.children[0] 176 | '''self.deform(curve, gesture)''' 177 | curve.location = gesture['points']['location'] 178 | curve.rotation_euler = gesture['points']['rotation'] 179 | svg_bobject.equalize_spline_count(curve, 1) 180 | new_curve_bobj = bobject.Bobject() 181 | null.ref_obj.parent = new_curve_bobj.ref_obj 182 | 183 | return new_curve_bobj 184 | -------------------------------------------------------------------------------- /blender_scripts/tools/hamilton_basic.py: -------------------------------------------------------------------------------- 1 | from random import random, choice, shuffle 2 | 3 | class Creature(object): 4 | """docstring for Creature.""" 5 | def __init__( 6 | self, 7 | altruist = None, 8 | parents = None, 9 | mate_chance = None 10 | ): 11 | super().__init__() 12 | self.altruist = altruist 13 | if self.altruist == None: 14 | raise Warning("Altruism genotype not set") 15 | 16 | self.parents = parents 17 | self.mate_chance = mate_chance 18 | self.mating = False 19 | 20 | class World(object): 21 | """docstring for World.""" 22 | 23 | def __init__( 24 | self, 25 | num_initial_creatures = 10000, 26 | initial_frac_altruists = 0.1, 27 | base_mate_chance = 2/3, 28 | offspring = 3, #Per mating pair 29 | mate_chance_cost = 1/12, 30 | mate_chance_benefit = 1/3, 31 | completed_days = 0, 32 | ): 33 | super().__init__() 34 | self.num_initial_creatures = num_initial_creatures 35 | self.base_mate_chance = base_mate_chance 36 | self.offspring = offspring 37 | self.mate_chance_cost = mate_chance_cost 38 | self.mate_chance_benefit = mate_chance_benefit 39 | 40 | self.creatures = [] 41 | for i in range(num_initial_creatures): 42 | alt = False 43 | if random() < initial_frac_altruists: 44 | alt = True 45 | self.creatures.append( 46 | Creature( 47 | altruist = alt, 48 | mate_chance = self.base_mate_chance 49 | ) 50 | ) 51 | self.completed_days = completed_days 52 | print_summary(self.completed_days, self.creatures) 53 | 54 | def new_generation(self): 55 | altruists = [x for x in self.creatures if x.altruist == True] 56 | for cre in altruists: 57 | #Each altruist helps one sibling at a cost to itself 58 | siblings = [x for x in self.creatures if x.parents == cre.parents] 59 | if len(siblings) > 0: 60 | recipient = choice(siblings) 61 | recipient.mate_chance += self.mate_chance_benefit 62 | cre.mate_chance -= self.mate_chance_cost 63 | 64 | for cre in self.creatures: 65 | #Decide who mates 66 | if random() < cre.mate_chance: 67 | cre.mating = True 68 | 69 | maters = [x for x in self.creatures if x.mating == True] 70 | shuffle(maters) 71 | next_creatures = [] 72 | while len(maters) > 2: 73 | p1 = maters.pop(0) 74 | p2 = maters.pop(0) 75 | for j in range(3): 76 | next_creatures.append( 77 | Creature( 78 | altruist = choice([p1.altruist, p2.altruist]), 79 | mate_chance = self.base_mate_chance, 80 | parents = [p1, p2] 81 | ) 82 | ) 83 | 84 | self.creatures = next_creatures 85 | self.completed_days += 1 86 | print_summary(self.completed_days, self.creatures) 87 | 88 | def print_summary(days, creatures): 89 | string = 'Gen ' + str(days) + ': ' 90 | 91 | num_creatures = len(creatures) 92 | altruists = [x for x in creatures if x.altruist == True] 93 | frac_altruists = len(altruists) / num_creatures 94 | 95 | 96 | string += str(num_creatures) + ' total, ' 97 | string += str(round(frac_altruists * 100)) + '% altruists' 98 | 99 | print(string) 100 | 101 | 102 | def main(): 103 | world = World() 104 | 105 | num_gens = 30 106 | for i in range(num_gens): 107 | world.new_generation() 108 | 109 | main() 110 | -------------------------------------------------------------------------------- /blender_scripts/tools/hawk_dove.py: -------------------------------------------------------------------------------- 1 | from random import random, choice, shuffle 2 | from helpers import save_sim_result 3 | 4 | DEFAULT_NUM_CREATURES = 121 5 | MUTATION_CHANCE = 0.0 6 | MUTATION_VARIATION = 0.1 7 | TRAIT_MODE = 'binary' #float or binary 8 | FOOD_VALUE = 2 9 | BASE_FOOD = 0 10 | 11 | SHARE_FRACTION = 1/2 12 | HAWK_TAKE_FRACTION = 3/4 13 | SUCKER_FRACTION = 1/4 14 | FIGHT_FRACTION = 1/2 15 | FIGHT_COST = 16/16 16 | 17 | 18 | class Creature(object): 19 | """docstring for Creature.""" 20 | def __init__(self, fight_chance = None, parent = None): 21 | super().__init__() 22 | 23 | self.fight_chance = fight_chance 24 | self.parent = parent 25 | 26 | self.days_log = [] 27 | 28 | class Food(object): 29 | """docstring for Food.""" 30 | 31 | def __init__(self, index = None): 32 | super().__init__() 33 | if index == None: 34 | raise Warning('Food object needs index') 35 | self.index = index 36 | self.interested_creatures = [] 37 | self.eaten = False 38 | 39 | class Day(object): 40 | """docstring for Day.""" 41 | 42 | def __init__(self, date = None, food_count = None, creatures = None): 43 | super().__init__() 44 | self.date = date 45 | 46 | self.food_objects = [] 47 | for i in range(food_count): 48 | self.food_objects.append(Food(index = i)) 49 | self.creatures = creatures 50 | self.contests = [] 51 | 52 | def simulate_day(self): 53 | shuffled_cres = self.creatures.copy() 54 | shuffle(shuffled_cres) 55 | for cre in shuffled_cres: 56 | cre.days_log.append( 57 | { 58 | 'date' : self.date, 59 | 'food' : None, 60 | 'score' : 0 61 | } 62 | ) 63 | 64 | uneaten = [x for x in self.food_objects if x.eaten == False] 65 | if len(uneaten) > 0: 66 | target_food = choice(uneaten) 67 | target_food.interested_creatures.append(cre) 68 | cre.days_log[-1]['food'] = target_food 69 | if len(target_food.interested_creatures) == 2: 70 | #Food is marked as eaten during the contest 71 | self.contests.append( 72 | Contest( 73 | food = target_food, 74 | contestants = target_food.interested_creatures, 75 | date = self.date 76 | ) 77 | ) 78 | if len(target_food.interested_creatures) == 3: 79 | raise Warning("Too many blobs on the dance floor") 80 | 81 | #Lone creatures eat their food 82 | for food in [x for x in self.food_objects if x.eaten == False]: 83 | num = len(food.interested_creatures) 84 | if num == 1: 85 | food.eaten = True 86 | food.interested_creatures[0].days_log[-1]['score'] += FOOD_VALUE 87 | elif num > 1: 88 | raise Warning('Food has ' + str(num) + ' interested creatures. Too many.') 89 | 90 | 91 | '''#Afternoon 92 | for cre in self.creatures: 93 | uneaten = [x for x in self.food_objects if x.eaten == False] 94 | if len(uneaten) > 0: 95 | target_food = choice(uneaten) 96 | target_food.interested_creatures.append(cre) 97 | cre.days_log[-1]['afternoon_food'] = target_food 98 | if len(target_food.interested_creatures) == 2: 99 | self.afternoon_contests.append( 100 | Contest( 101 | food = target_food, 102 | contestants = target_food.interested_creatures, 103 | time = 'afternoon' 104 | ) 105 | ) 106 | 107 | #Lone creatures eat their food 108 | for food in [x for x in self.food_objects if x.eaten == False]: 109 | num = len(food.interested_creatures) 110 | if num == 1: 111 | food.eaten = True 112 | food.eaten_time = 'afternoon' 113 | food.interested_creatures[0].days_log[-1]['score'] += 1 114 | elif num > 1: 115 | raise Warning('Food has ' + str(num) + ' interested creatures. Too many.')''' 116 | 117 | self.update_creatures() 118 | 119 | def update_creatures(self): 120 | next = [] 121 | 122 | for cre in self.creatures: 123 | score = cre.days_log[-1]['score'] 124 | survival_roll = random() 125 | if score > survival_roll: 126 | next.append(cre) 127 | 128 | #if score > 1: 129 | reproduce_roll = random() 130 | if score > 1 + reproduce_roll: 131 | baby_fight_chance = cre.fight_chance 132 | mutation_roll = random() 133 | if MUTATION_CHANCE > mutation_roll: 134 | if TRAIT_MODE == 'binary': 135 | if baby_fight_chance == 1: 136 | baby_fight_chance = 0 137 | elif baby_fight_chance == 0: 138 | baby_fight_chance = 1 139 | else: 140 | raise Warning('Baby fight chance should be 0 or 1 but is not') 141 | elif TRAIT_MODE == 'float': 142 | nudge = choice([MUTATION_VARIATION, - MUTATION_VARIATION]) 143 | baby_fight_chance += nudge 144 | baby_fight_chance = max(baby_fight_chance, 0) 145 | baby_fight_chance = min(baby_fight_chance, 1) 146 | else: 147 | raise Warning('Nonbinary fight chance not implemented') 148 | 149 | next.append( 150 | Creature( 151 | fight_chance = baby_fight_chance, 152 | parent = cre 153 | ) 154 | ) 155 | 156 | self.next_creatures = next 157 | 158 | class Contest(object): 159 | """docstring for Contest.""" 160 | 161 | def __init__(self, contestants = None, food = None, date = None): 162 | super().__init__() 163 | self.contestants = contestants 164 | self.food = food 165 | self.date = date 166 | 167 | self.outcome = None 168 | self.winner = None 169 | 170 | #Determine strategies 171 | strat_0 = strat_1 = 'share' 172 | if self.contestants[0].fight_chance > random(): 173 | strat_0 = 'fight' 174 | if self.contestants[1].fight_chance > random(): 175 | strat_1 = 'fight' 176 | 177 | #Determine payouts 178 | if strat_0 == 'fight' and strat_1 == 'fight': 179 | #winner_index = choice([0, 1]) 180 | #self.contestants[winner_index].days_log[-1]['score'] += 1 181 | self.contestants[0].days_log[-1]['score'] += BASE_FOOD + FOOD_VALUE * FIGHT_FRACTION - FIGHT_COST 182 | self.contestants[1].days_log[-1]['score'] += BASE_FOOD + FOOD_VALUE * FIGHT_FRACTION - FIGHT_COST 183 | self.outcome = 'fight' 184 | if strat_0 == 'fight' and strat_1 == 'share': 185 | self.contestants[0].days_log[-1]['score'] += BASE_FOOD + FOOD_VALUE * HAWK_TAKE_FRACTION 186 | self.contestants[1].days_log[-1]['score'] += BASE_FOOD + FOOD_VALUE * SUCKER_FRACTION 187 | self.outcome = 'take' 188 | self.winner = self.contestants[0] 189 | if strat_0 == 'share' and strat_1 == 'fight': 190 | self.contestants[1].days_log[-1]['score'] += BASE_FOOD + FOOD_VALUE * HAWK_TAKE_FRACTION 191 | self.contestants[0].days_log[-1]['score'] += BASE_FOOD + FOOD_VALUE * SUCKER_FRACTION 192 | self.outcome = 'take' 193 | self.winner = self.contestants[1] 194 | if strat_0 == 'share' and strat_1 == 'share': 195 | self.contestants[0].days_log[-1]['score'] += BASE_FOOD + FOOD_VALUE * SHARE_FRACTION 196 | self.contestants[1].days_log[-1]['score'] += BASE_FOOD + FOOD_VALUE * SHARE_FRACTION 197 | self.outcome = 'share' 198 | 199 | self.food.eaten = True 200 | 201 | class World(object): 202 | """docstring for World.""" 203 | 204 | def __init__(self, initial_creatures = None, food_count = 100): 205 | super().__init__() 206 | 207 | if isinstance(initial_creatures, int) or initial_creatures == None: 208 | self.generate_creatures(num = initial_creatures) 209 | elif isinstance(initial_creatures, list): 210 | self.initial_creatures = initial_creatures 211 | else: 212 | raise Warning('Cannot parse initial_creatures') 213 | 214 | self.food_count = food_count 215 | 216 | self.calendar = [] 217 | 218 | def new_day( 219 | self, 220 | food_count = None, 221 | save = False, 222 | filename = None, 223 | filename_seed = None 224 | ): 225 | if len(self.calendar) == 0: 226 | creatures = self.initial_creatures 227 | else: 228 | creatures = self.calendar[-1].next_creatures 229 | 230 | if food_count == None: 231 | food_count = self.food_count 232 | 233 | day = Day( 234 | creatures = creatures, 235 | date = len(self.calendar), 236 | food_count = food_count 237 | ) 238 | 239 | day.simulate_day() 240 | self.calendar.append(day) 241 | 242 | if save == True: 243 | save_sim_result(self, filename, filename_seed, type = 'HAWKDOVE') 244 | 245 | 246 | def generate_creatures(self, num = None): 247 | if num == None: 248 | num = DEFAULT_NUM_CREATURES 249 | self.initial_creatures = [] 250 | for i in range(num): 251 | cre = Creature( 252 | fight_chance = i % 2 #(i % 11) / 10 253 | ) 254 | 255 | self.initial_creatures.append(cre) 256 | 257 | shuffle(self.initial_creatures) 258 | -------------------------------------------------------------------------------- /blender_scripts/tools/hawk_dove_basic.py: -------------------------------------------------------------------------------- 1 | import math 2 | from random import random, choice, shuffle 3 | from helpers import save_sim_result 4 | 5 | DEFAULT_NUM_CREATURES = 11000 6 | MUTATION_CHANCE = 0.01 7 | MUTATION_VARIATION = 0.1 8 | TRAIT_MODE = 'float' #float or binary 9 | FOOD_VALUE = 1 10 | BASE_FOOD = 0 11 | 12 | 13 | SHARE_FRACTION = 1/2 14 | HAWK_TAKE_FRACTION = 3/4 15 | SUCKER_FRACTION = 1/4 16 | FIGHT_FRACTION = 0 17 | 18 | 19 | class Creature(object): 20 | """docstring for Creature.""" 21 | def __init__(self, fight_chance = None, parent = None): 22 | super().__init__() 23 | 24 | self.fight_chance = fight_chance 25 | self.parent = parent 26 | 27 | self.days_log = [] 28 | 29 | class Food(object): 30 | """docstring for Food.""" 31 | 32 | def __init__(self, index = None): 33 | super().__init__() 34 | if index == None: 35 | raise Warning('Food object needs index') 36 | self.index = index 37 | self.interested_creatures = [] 38 | self.eaten = False 39 | 40 | class Day(object): 41 | """docstring for Day.""" 42 | 43 | def __init__(self, date = None, food_count = None, creatures = None): 44 | super().__init__() 45 | self.date = date 46 | 47 | self.creatures = creatures 48 | 49 | 50 | food_count = int(math.floor(len(creatures)) / 2) #Make sure there's a food for every 51 | #two creatures. Except there will 52 | #be an odd one out. But they 53 | #survive anyway 54 | 55 | 56 | self.food_objects = [] 57 | for i in range(food_count): 58 | self.food_objects.append(Food(index = i)) 59 | self.contests = [] 60 | 61 | def simulate_day(self): 62 | #Morning 63 | 64 | shuffled_cres = self.creatures.copy() 65 | shuffle(shuffled_cres) 66 | 67 | for cre in shuffled_cres: 68 | cre.days_log.append( 69 | { 70 | 'date' : self.date, 71 | 'food' : None, 72 | 'score' : 0 73 | } 74 | ) 75 | 76 | uneaten = [x for x in self.food_objects if x.eaten == False] 77 | if len(uneaten) > 0: 78 | target_food = choice(uneaten) 79 | target_food.interested_creatures.append(cre) 80 | cre.days_log[-1]['food'] = target_food 81 | if len(target_food.interested_creatures) == 2: 82 | #Food is marked as eaten during the contest 83 | self.contests.append( 84 | Contest( 85 | food = target_food, 86 | contestants = target_food.interested_creatures, 87 | date = self.date 88 | ) 89 | ) 90 | if len(target_food.interested_creatures) == 3: 91 | raise Warning("Too many blobs on the dance floor") 92 | 93 | #Lone creatures eat their food 94 | for food in [x for x in self.food_objects if x.eaten == False]: 95 | raise Warning('Should be node of these') 96 | num = len(food.interested_creatures) 97 | if num == 1: 98 | food.eaten = True 99 | food.interested_creatures[0].days_log[-1]['score'] += FOOD_VALUE 100 | elif num > 1: 101 | raise Warning('Food has ' + str(num) + ' interested creatures. Too many.') 102 | 103 | 104 | '''#Afternoon 105 | for cre in self.creatures: 106 | uneaten = [x for x in self.food_objects if x.eaten == False] 107 | if len(uneaten) > 0: 108 | target_food = choice(uneaten) 109 | target_food.interested_creatures.append(cre) 110 | cre.days_log[-1]['afternoon_food'] = target_food 111 | if len(target_food.interested_creatures) == 2: 112 | self.afternoon_contests.append( 113 | Contest( 114 | food = target_food, 115 | contestants = target_food.interested_creatures, 116 | time = 'afternoon' 117 | ) 118 | ) 119 | 120 | #Lone creatures eat their food 121 | for food in [x for x in self.food_objects if x.eaten == False]: 122 | num = len(food.interested_creatures) 123 | if num == 1: 124 | food.eaten = True 125 | food.eaten_time = 'afternoon' 126 | food.interested_creatures[0].days_log[-1]['score'] += 1 127 | elif num > 1: 128 | raise Warning('Food has ' + str(num) + ' interested creatures. Too many.')''' 129 | 130 | self.update_creatures() 131 | 132 | def update_creatures(self): 133 | 134 | scores = [] 135 | nums = [] 136 | for i in range(11): 137 | score = 0 138 | num = 0 139 | for cre in self.creatures: 140 | if int(cre.fight_chance * 10) == i: 141 | score += cre.days_log[-1]['score'] 142 | num += 1 143 | scores.append(score) 144 | nums.append(num) 145 | 146 | total = sum(scores) 147 | 148 | nums_offspring = [] 149 | for score in scores: 150 | frac = score / total 151 | num_offspring = round(frac * DEFAULT_NUM_CREATURES) 152 | nums_offspring.append(num_offspring) 153 | 154 | '''avgs = [] 155 | for num, score in zip(nums, scores): 156 | if num > 0: 157 | avgs.append(score/num) 158 | else: 159 | avgs.append(0) 160 | print(avgs) 161 | offsprings_per = [] 162 | for num, num_offspring in zip(nums, nums_offspring): 163 | if num > 0: 164 | offsprings_per.append(num_offspring/num) 165 | else: 166 | offsprings_per.append(0) 167 | print(offsprings_per) 168 | print()''' 169 | 170 | next = [] 171 | for i, num_offspring in enumerate(nums_offspring): 172 | baby_fight_chance = i / 10 173 | for i in range(num_offspring): 174 | mutation_roll = random() 175 | if MUTATION_CHANCE > mutation_roll: 176 | if TRAIT_MODE == 'binary': 177 | raise Warning('Check baby fight chance') 178 | if baby_fight_chance == 1: 179 | baby_fight_chance = 0 180 | elif baby_fight_chance == 0: 181 | baby_fight_chance = 1 182 | else: 183 | raise Warning('Baby fight chance should be 0 or 1 but is not') 184 | elif TRAIT_MODE == 'float': 185 | nudge = choice([MUTATION_VARIATION, - MUTATION_VARIATION]) 186 | baby_fight_chance += nudge 187 | baby_fight_chance = max(baby_fight_chance, 0) 188 | baby_fight_chance = min(baby_fight_chance, 1) 189 | else: 190 | raise Warning('Nonbinary fight chance not implemented') 191 | 192 | next.append( 193 | Creature( 194 | fight_chance = baby_fight_chance, 195 | parent = cre 196 | ) 197 | ) 198 | 199 | self.next_creatures = next 200 | 201 | class Contest(object): 202 | """docstring for Contest.""" 203 | 204 | def __init__(self, contestants = None, food = None, date = None): 205 | super().__init__() 206 | self.contestants = contestants 207 | self.food = food 208 | self.date = date 209 | 210 | self.outcome = None 211 | self.winner = None 212 | 213 | #Determine strategies 214 | strat_0 = strat_1 = 'share' 215 | if self.contestants[0].fight_chance > random(): 216 | strat_0 = 'fight' 217 | if self.contestants[1].fight_chance > random(): 218 | strat_1 = 'fight' 219 | 220 | #Determine payouts 221 | if strat_0 == 'fight' and strat_1 == 'fight': 222 | #winner_index = choice([0, 1]) 223 | #self.contestants[winner_index].days_log[-1]['score'] += 1 224 | self.contestants[0].days_log[-1]['score'] += BASE_FOOD + FOOD_VALUE * FIGHT_FRACTION 225 | self.contestants[1].days_log[-1]['score'] += BASE_FOOD + FOOD_VALUE * FIGHT_FRACTION 226 | self.outcome = 'fight' 227 | if strat_0 == 'fight' and strat_1 == 'share': 228 | self.contestants[0].days_log[-1]['score'] += BASE_FOOD + FOOD_VALUE * HAWK_TAKE_FRACTION 229 | self.contestants[1].days_log[-1]['score'] += BASE_FOOD + FOOD_VALUE * SUCKER_FRACTION 230 | self.outcome = 'take' 231 | self.winner = self.contestants[0] 232 | if strat_0 == 'share' and strat_1 == 'fight': 233 | self.contestants[1].days_log[-1]['score'] += BASE_FOOD + FOOD_VALUE * HAWK_TAKE_FRACTION 234 | self.contestants[0].days_log[-1]['score'] += BASE_FOOD + FOOD_VALUE * SUCKER_FRACTION 235 | self.outcome = 'take' 236 | self.winner = self.contestants[1] 237 | if strat_0 == 'share' and strat_1 == 'share': 238 | self.contestants[0].days_log[-1]['score'] += BASE_FOOD + FOOD_VALUE * SHARE_FRACTION 239 | self.contestants[1].days_log[-1]['score'] += BASE_FOOD + FOOD_VALUE * SHARE_FRACTION 240 | self.outcome = 'share' 241 | 242 | self.food.eaten = True 243 | 244 | class World(object): 245 | """docstring for World.""" 246 | 247 | def __init__(self, initial_creatures = None, food_count = 100): 248 | super().__init__() 249 | 250 | self.initial_creatures = initial_creatures 251 | if self.initial_creatures == None: 252 | self.generate_creatures() 253 | self.food_count = food_count 254 | 255 | self.calendar = [] 256 | 257 | def new_day( 258 | self, 259 | food_count = None, 260 | save = False, 261 | filename = None, 262 | filename_seed = None 263 | ): 264 | if len(self.calendar) == 0: 265 | creatures = self.initial_creatures 266 | else: 267 | creatures = self.calendar[-1].next_creatures 268 | 269 | if food_count == None: 270 | food_count = self.food_count 271 | 272 | day = Day( 273 | creatures = creatures, 274 | date = len(self.calendar), 275 | food_count = food_count 276 | ) 277 | 278 | day.simulate_day() 279 | self.calendar.append(day) 280 | 281 | if save == True: 282 | save_sim_result(self, filename, filename_seed, type = 'HAWKDOVE') 283 | 284 | def generate_creatures(self): 285 | self.initial_creatures = [] 286 | for i in range(DEFAULT_NUM_CREATURES): 287 | cre = Creature( 288 | fight_chance = (i % 11) / 10 289 | ) 290 | '''if i % 2 == 0: 291 | cre = Creature( 292 | fight_chance = 0 293 | ) 294 | else: 295 | cre = Creature( 296 | fight_chance = 1 297 | )''' 298 | 299 | self.initial_creatures.append(cre) 300 | 301 | shuffle(self.initial_creatures) 302 | -------------------------------------------------------------------------------- /blender_scripts/tools/population.py: -------------------------------------------------------------------------------- 1 | from random import random, choice 2 | from copy import deepcopy 3 | import imp 4 | import creature 5 | import constants 6 | import math 7 | import collections 8 | #import alone doesn't check for changes in cached files 9 | imp.reload(creature) 10 | #from creature import Creature 11 | imp.reload(constants) 12 | from constants import * 13 | 14 | ''' 15 | A collection of creatures over time. 16 | ''' 17 | 18 | class Population(object): 19 | genes = { 20 | 'color' : collections.OrderedDict([ 21 | ("creature_color_1" , { 22 | "birth_modifier" : 0, 23 | "replication_modifier" : 1, 24 | "mutation_chance" : DEFAULT_MUTATION_CHANCE, 25 | "death_modifier" : 1 26 | }), 27 | ("creature_color_2" , { 28 | "birth_modifier" : 0, 29 | "replication_modifier" : 1, 30 | "mutation_chance" : DEFAULT_MUTATION_CHANCE, 31 | "death_modifier" : 1 32 | }), 33 | ("creature_color_3" , { 34 | "birth_modifier" : 0, 35 | "replication_modifier" : 1, 36 | "mutation_chance" : DEFAULT_MUTATION_CHANCE, 37 | "death_modifier" : 1 38 | }), 39 | ("creature_color_4" , { 40 | "birth_modifier" : 0, 41 | "replication_modifier" : 1, 42 | "mutation_chance" : DEFAULT_MUTATION_CHANCE, 43 | "death_modifier" : 1 44 | }) 45 | ]), 46 | 'shape' : collections.OrderedDict([ 47 | ("shape1" , { 48 | "birth_modifier" : 0, 49 | "replication_modifier" : 1, 50 | "mutation_chance" : DEFAULT_MUTATION_CHANCE, 51 | "death_modifier" : 1 52 | }), 53 | ("shape2" , { 54 | "birth_modifier" : 0, 55 | "replication_modifier" : 1, 56 | "mutation_chance" : DEFAULT_MUTATION_CHANCE, 57 | "death_modifier" : 1 58 | }) 59 | ]), 60 | 'size' : collections.OrderedDict([ 61 | ("1" , { 62 | "birth_modifier" : 0, 63 | "replication_modifier" : 1, 64 | "mutation_chance" : DEFAULT_MUTATION_CHANCE, 65 | "death_modifier" : 1 66 | }), 67 | ("0.5" , { 68 | "birth_modifier" : 0, 69 | "replication_modifier" : 1, 70 | "mutation_chance" : DEFAULT_MUTATION_CHANCE, 71 | "death_modifier" : 1 72 | }) 73 | ]) 74 | } 75 | 76 | def __init__(self, **kwargs): 77 | #super().__init__() 78 | self.creatures = [] 79 | 80 | #Using class attribute Population.genes as a default 81 | self.genes = deepcopy(Population.genes) 82 | if 'sim_duration' in kwargs: 83 | self.duration = kwargs['sim_duration'] 84 | else: 85 | self.duration = DEFAULT_WORLD_DURATION 86 | if 'gene_updates' in kwargs: 87 | self.updates = kwargs['gene_updates'] 88 | else: 89 | self.updates = [] 90 | if 'initial_creatures' in kwargs: 91 | self.initial_creatures = kwargs['initial_creatures'] 92 | else: 93 | self.initial_creatures = 10 94 | 95 | if 'pop_cap' in kwargs: 96 | self.pop_cap = kwargs['pop_cap'] 97 | else: 98 | self.pop_cap = DEFAULT_POP_CAP 99 | 100 | if 'name' in kwargs: 101 | self.name = kwargs['name'] 102 | else: 103 | self.name = 'population' 104 | 105 | #Each update arg should be of the form 106 | #update = [gene, allele, property, value, frame] 107 | # e.g., ['shape', 'shape1', 'replication_modifier', 2, 20] 108 | '''if gene_updates != None: 109 | self.updates = gene_updates 110 | else: 111 | self.updates = []''' 112 | 113 | def simulate(self): 114 | self.creatures = [] #clear creatures each time so one population 115 | #object can be used to generate multiple sets of 116 | #sim data 117 | print('Simulating ' + self.name) 118 | num = 1 119 | if isinstance(self.initial_creatures, int): 120 | for i in range(self.initial_creatures): 121 | cre = creature.Creature() #Creature with default alleles 122 | cre.birthday = 0 123 | cre.name = cre.alleles['color'] + " " + \ 124 | cre.alleles['shape'] + " " + str(num) 125 | self.creatures.append(cre) 126 | num += 1 127 | elif isinstance(self.initial_creatures, list): 128 | for cre in self.initial_creatures: 129 | cre.birthday = 0 130 | cre.name = cre.alleles['color'] + " " + \ 131 | cre.alleles['shape'] + " " + str(num) 132 | self.creatures.append(cre) 133 | num += 1 134 | 135 | #self.apply_updates(0) #Catches any gene property changes at time 0 136 | self.duration = int(self.duration) #Might be a float if calculated, idk. 137 | for t in range(0, self.duration): 138 | self.apply_updates(t) 139 | 140 | self.death(t) 141 | self.replicate(t) 142 | self.spontaneous_birth(t) 143 | 144 | #Make sure all creatures die at the end. :( 145 | #This makes things smoother elsewhere by ensuring deathday is a number 146 | for cre in self.creatures: 147 | if cre.deathday == None: 148 | cre.deathday = self.duration + 1 149 | 150 | print("There are " + str(len(self.creatures)) + " creatures total in " + \ 151 | self.name) 152 | 153 | def list_possible_genotypes(self): 154 | #Actually returns a list of creatures, one with each possible genotype 155 | possible_creatures = [creature.Creature()] 156 | for gene in self.genes: 157 | new_possible_creatures = [] 158 | for allele in self.genes[gene]: 159 | for cre in possible_creatures: 160 | creature_copy_plus_allele = deepcopy(cre) 161 | creature_copy_plus_allele.alleles[gene] = allele 162 | new_possible_creatures.append(creature_copy_plus_allele) 163 | possible_creatures = new_possible_creatures 164 | 165 | return possible_creatures 166 | 167 | def apply_updates(self, t): 168 | for update in self.updates: 169 | if update[4] == t: 170 | self.genes[update[0]][update[1]][update[2]] = update[3] 171 | 172 | def spontaneous_birth(self, t): 173 | candidates = self.list_possible_genotypes() 174 | for candidate in candidates: 175 | birth_chance = BASE_BIRTH_CHANCE 176 | for gene in candidate.alleles: 177 | birth_chance *= \ 178 | self.genes[gene][candidate.alleles[gene]]['birth_modifier'] 179 | 180 | #If birth chance is greater than one, interpret whole number part 181 | #as an additional sure birth 182 | #print("Birth chance: " + str(birth_chance)) 183 | while birth_chance > 1: 184 | sibling = deepcopy(candidate) 185 | self.birth(sibling, t + 1) 186 | birth_chance -= 1 187 | 188 | birth_roll = random() 189 | if birth_roll < birth_chance: 190 | self.birth(candidate, t + 1) 191 | 192 | def birth(self, baby, t): 193 | self.creatures.append(baby) 194 | baby.birthday = t 195 | 196 | #Give creature a color+shape name with unique number 197 | #TODO Makes names more meaningful, e.g., by species, if needed. 198 | num = 1 199 | name_list = self.get_creature_names() 200 | for creature in self.creatures: 201 | if baby.alleles['color'] + " " + baby.alleles['shape'] + \ 202 | " " + str(num) in name_list: 203 | num += 1 204 | else: 205 | baby.name = baby.alleles['color'] + " " + \ 206 | baby.alleles['shape'] + " " + str(num) 207 | break 208 | 209 | def replicate(self, t): 210 | alive = [cre for cre in self.creatures if \ 211 | cre.deathday == None or cre.deathday > t] 212 | for cre in alive: 213 | replication_chance = BASE_REPLICATION_CHANCE 214 | #Death chance is here because I decided to share the crowding 215 | #effect evenly between replication and death. We need the death 216 | #chance to calculate the overall effect. 217 | death_chance = BASE_DEATH_CHANCE 218 | 219 | for gene in cre.alleles: 220 | replication_chance *= \ 221 | self.genes[gene][cre.alleles[gene]]\ 222 | ['replication_modifier'] 223 | death_chance *= \ 224 | self.genes[gene][cre.alleles[gene]]['death_modifier'] 225 | crowding_rep_mod = (replication_chance - death_chance) / self.pop_cap / 2 226 | pop_size = self.count_creatures_at_t(t) 227 | 228 | replicate_roll = random() 229 | if replicate_roll < replication_chance - crowding_rep_mod * pop_size: 230 | baby = creature.Creature() 231 | self.creatures.append(baby) 232 | #Assign genes, checking for mutations 233 | for gene in self.genes: 234 | mutation_roll = random() 235 | chances = self.genes[gene][cre.alleles[gene]]['mutation_chance'] 236 | if isinstance(chances, (float, int)): 237 | if mutation_roll < chances: 238 | other_options = deepcopy(self.genes[gene]) 239 | other_options.pop(cre.alleles[gene]) 240 | baby.alleles[gene] = choice(list(other_options.keys())) 241 | else: 242 | baby.alleles[gene] = cre.alleles[gene] 243 | #Allow for different odds of mutating to different alleles 244 | elif isinstance(chances, list): 245 | cumulative_chance = 0 246 | for chance, allele in zip(chances, self.genes[gene]): 247 | cumulative_chance += chance 248 | if mutation_roll < cumulative_chance: 249 | baby.alleles[gene] = allele 250 | break 251 | baby.alleles[gene] = cre.alleles[gene] 252 | 253 | else: 254 | raise Warning('Mutation chance must be number or list') 255 | 256 | 257 | baby.birthday = t + 1 258 | baby.parent = cre 259 | 260 | #Give creature a color+shape name with unique number 261 | num = 1 262 | name_list = self.get_creature_names() 263 | for cre in self.creatures: 264 | if baby.alleles['color'] + " " + baby.alleles['shape'] + \ 265 | " " + str(num) in name_list: 266 | num += 1 267 | else: 268 | baby.name = baby.alleles['color'] + " " + \ 269 | baby.alleles['shape'] + " " + str(num) 270 | cre.children.append(baby.name) 271 | break 272 | 273 | def death(self, t): 274 | alive = [cre for cre in self.creatures if \ 275 | cre.deathday == None and cre.birthday <= t] 276 | 277 | pop_size = self.count_creatures_at_t(t) 278 | #Old 279 | #Simple function that ramps death chance up around the population cap 280 | #Default cap is 3000, so it mostly functions to stop crashes/freezes 281 | #when I put in the wrong parameters. 282 | #crowding_death_mod = 1 + (pop_size / self.pop_cap) ** 10 283 | 284 | for creature in alive: 285 | death_chance = BASE_DEATH_CHANCE 286 | #Replication chance is here because I decided to share the crowding 287 | #effect evenly between replication and death. We need the replication 288 | #chance to calculate the overall effect. 289 | replication_chance = BASE_REPLICATION_CHANCE 290 | for gene in creature.alleles: 291 | death_chance *= \ 292 | self.genes[gene][creature.alleles[gene]]['death_modifier'] 293 | replication_chance *= \ 294 | self.genes[gene][creature.alleles[gene]]['replication_modifier'] 295 | crowding_death_mod = (replication_chance - death_chance) / self.pop_cap / 2 296 | death_roll = random() 297 | #print('Death chance: ' + str(death_chance)) 298 | if death_roll < death_chance + crowding_death_mod * pop_size: 299 | creature.deathday = t + 1 300 | 301 | def get_creature_names(self): 302 | name_list = [creature.name for creature in self.creatures] 303 | return name_list 304 | 305 | def count_creatures_at_t(self, t, creatures = None): 306 | if creatures == None: 307 | creatures = self.creatures 308 | count = 0 309 | for creature in creatures: 310 | if creature.birthday <= t: 311 | count += 1 312 | #print(creature.deathday) 313 | if creature.deathday != None and creature.deathday <= t: 314 | count -= 1 315 | 316 | return count 317 | 318 | def get_creature_count_by_t( 319 | self, 320 | size = 'any', 321 | shape = 'any', 322 | color = 'any' 323 | ): 324 | #Get list of creatures according to requirements 325 | creatures_to_count = [] 326 | for creature in self.creatures: 327 | add = True 328 | if size != 'any': 329 | if creature.alleles['size'] != size: 330 | add = False 331 | if shape != 'any': 332 | if creature.alleles['shape'] != shape: 333 | add = False 334 | if color != 'any': 335 | if creature.alleles['color'] != color: 336 | add = False 337 | if add: 338 | creatures_to_count.append(creature) 339 | 340 | creature_count_by_t = [] 341 | count = 0 342 | #print(self.duration) 343 | for t in range(self.duration + 1): 344 | count = self.count_creatures_at_t(t, creatures = creatures_to_count) 345 | creature_count_by_t.append(count) 346 | 347 | return creature_count_by_t 348 | 349 | #For testing 350 | def main(): 351 | pop = Population() 352 | pop.simulate(self.duration) 353 | count = pop.get_creature_count_by_t() 354 | for i in range(self.duration): 355 | print(count[i]) 356 | print("population.py successfully run") 357 | 358 | if __name__ == "__main__": 359 | main() 360 | -------------------------------------------------------------------------------- /blender_scripts/tools/scene.py: -------------------------------------------------------------------------------- 1 | import imp 2 | import constants 3 | imp.reload(constants) 4 | from constants import * 5 | 6 | class Scene(object): 7 | def __init__(self): 8 | try: 9 | total_duration = 0 10 | for sub, attrs in self.subscenes.items(): 11 | total_duration += attrs['duration'] 12 | 13 | self.duration = total_duration 14 | except: 15 | raise Warning('Must define self.subscenes in subclass of Scene') 16 | 17 | self.set_subscene_timing() 18 | 19 | def set_subscene_timing(self): 20 | start = 0 21 | for sub, attrs in self.subscenes.items(): 22 | attrs['start'] = start 23 | attrs['end'] = start + attrs['duration'] 24 | start = attrs['end'] 25 | 26 | def play(self): 27 | pass 28 | #To be extended by subclass 29 | -------------------------------------------------------------------------------- /blender_scripts/tools/table_bobject.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | 3 | import imp 4 | #from copy import deepcopy 5 | import math 6 | 7 | import bobject 8 | imp.reload(bobject) 9 | from bobject import * 10 | 11 | import tex_bobject 12 | imp.reload(tex_bobject) 13 | import svg_bobject 14 | imp.reload(svg_bobject) 15 | 16 | class TableBobject(Bobject): 17 | """docstring for TableBobject.""" 18 | def __init__(self, 19 | width = 10, 20 | height = 10, 21 | cell_padding = 0.75, 22 | centered = False, 23 | element_matrix = None, 24 | style = 'two_lines', 25 | **kwargs 26 | ): 27 | print('Initializing table bobject') 28 | if 'name' not in kwargs: 29 | kwargs['name'] = 'table' 30 | super().__init__(**kwargs) 31 | 32 | self.width = width 33 | self.height = height 34 | self.style = style 35 | #This doesn't actually do anything right now. 36 | self.centered = centered 37 | 38 | if element_matrix == None: 39 | element_matrix = self.default_element_matrix() 40 | self.element_matrix = element_matrix 41 | 42 | for row in self.element_matrix: 43 | for element in row: 44 | self.add_subbobject(element) 45 | 46 | #Get row and column dimensions 47 | self.cell_padding = cell_padding 48 | self.row_heights = [] 49 | self.column_widths = [] 50 | for i, row in enumerate(self.element_matrix): 51 | self.row_heights.append(0) 52 | for j, cell in enumerate(row): 53 | width, height = self.get_element_dimensions(self.element_matrix[i][j]) 54 | 55 | width += 2 * self.cell_padding 56 | height += 2 * self.cell_padding 57 | 58 | if self.row_heights[i] < height: 59 | self.row_heights[i] = height 60 | 61 | if len(self.column_widths) < j + 1: 62 | self.column_widths.append(0) 63 | if self.column_widths[j] < width: 64 | self.column_widths[j] = width 65 | 66 | 67 | self.fill_dimensions() 68 | self.place_elements() 69 | self.draw_grid() 70 | 71 | def get_element_dimensions(self, element): 72 | if not isinstance(element, svg_bobject.SVGBobject) \ 73 | and not isinstance(element, tex_bobject.TexBobject): 74 | print(element) 75 | raise Warning("Tables can't yet hold things that aren't svg bobjects") 76 | 77 | width = 0 78 | height = 0 79 | #Just size to the max width out of all possible forms 80 | for key in element.imported_svg_data: 81 | fig = element.imported_svg_data[key] 82 | if fig['length'] * element.ref_obj.scale[0] > width: 83 | width = fig['length'] 84 | if fig['height'] * element.ref_obj.scale[1] > height: 85 | height = fig['height'] 86 | 87 | return width, height 88 | 89 | def place_elements(self): 90 | y = 0 91 | for i, row in enumerate(self.element_matrix): 92 | x = 0 93 | for j, element in enumerate(row): 94 | x_disp = 0 95 | if element.centered == True: 96 | x_disp = self.column_widths[j] / 2 97 | else: 98 | x_disp = self.cell_padding 99 | element.ref_obj.location[0] = x + x_disp 100 | x += self.column_widths[j] 101 | 102 | #SVG bobjects are currently vertically centered 103 | #(or maybe not purposefully unaligned) 104 | y_disp = self.row_heights[i] / 2 105 | element.ref_obj.location[1] = y - y_disp 106 | y -= self.row_heights[i] 107 | 108 | def fill_dimensions(self): 109 | width_factor = self.width / sum(self.column_widths) 110 | height_factor = self.height / sum(self.row_heights) 111 | 112 | self.scale_factor = min(width_factor, height_factor) 113 | 114 | for i in range(len(self.row_heights)): 115 | self.row_heights[i] = self.row_heights[i] * self.scale_factor 116 | for j in range(len(self.column_widths)): 117 | self.column_widths[j] = self.column_widths[j] * self.scale_factor 118 | 119 | 120 | for i, row in enumerate(self.element_matrix): 121 | for j, element in enumerate(row): 122 | element.ref_obj.scale = [ 123 | element.ref_obj.scale[0] * self.scale_factor, 124 | element.ref_obj.scale[1] * self.scale_factor, 125 | element.ref_obj.scale[2] * self.scale_factor, 126 | ] 127 | 128 | def draw_grid(self): 129 | #Just going to draw two lines here, since that's what I need right now. 130 | if self.style == 'two_lines': 131 | vert_line = import_object( 132 | 'one_side_cylinder', 'primitives', 133 | location = [self.column_widths[0], 0, 0], 134 | scale = [0.05, 0.05, sum(self.row_heights)], 135 | rotation_euler = [math.pi / 2, 0, 0] 136 | ) 137 | hor_line = import_object( 138 | 'one_side_cylinder', 'primitives', 139 | location = [0, -self.row_heights[0], 0], 140 | scale = [0.05, 0.05, sum(self.column_widths)], 141 | rotation_euler = [0, math.pi / 2, 0] 142 | ) 143 | for line in [vert_line, hor_line]: 144 | self.add_subbobject(line) 145 | 146 | if self.style == 'full_grid': 147 | hor_pos = 0 148 | for i in range(len(self.column_widths) + 1): 149 | vert_line = import_object( 150 | 'one_side_cylinder', 'primitives', 151 | location = [hor_pos, 0, 0], 152 | scale = [0.05, 0.05, sum(self.row_heights)], 153 | rotation_euler = [math.pi / 2, 0, 0] 154 | ) 155 | self.add_subbobject(vert_line) 156 | if i < len(self.column_widths): 157 | hor_pos += self.column_widths[i] 158 | 159 | vert_pos = 0 160 | for i in range(len(self.row_heights) + 1): 161 | hor_line = import_object( 162 | 'one_side_cylinder', 'primitives', 163 | location = [0, -vert_pos, 0], 164 | scale = [0.05, 0.05, sum(self.column_widths)], 165 | rotation_euler = [0, math.pi / 2, 0] 166 | ) 167 | self.add_subbobject(hor_line) 168 | if i < len(self.row_heights): 169 | vert_pos += self.row_heights[i] 170 | 171 | 172 | 173 | 174 | 175 | def default_element_matrix(self): 176 | strings = [['This', 'is', 'a'],['wide', 'table', 'dawg']] 177 | element_matrix = [] 178 | 179 | for row in strings: 180 | element_matrix.append([]) 181 | for element in row: 182 | element_matrix[-1].append( 183 | tex_bobject.TexBobject( 184 | '\\text{' + element + '}', 185 | centered = True 186 | ) 187 | ) 188 | 189 | return element_matrix 190 | -------------------------------------------------------------------------------- /blender_scripts/tools/tex_bobject.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | import mathutils 3 | 4 | import collections 5 | import sys 6 | import os 7 | import imp 8 | 9 | import sys 10 | sys.path.append('C:\\Users\\justi\\Documents\\CodeProjects\\Primer\\blender_scripts') 11 | 12 | import svg_bobject 13 | imp.reload(svg_bobject) 14 | from svg_bobject import * 15 | 16 | import constants 17 | imp.reload(constants) 18 | from constants import * 19 | 20 | import helpers 21 | imp.reload(helpers) 22 | from helpers import * 23 | 24 | import tex_complex 25 | imp.reload(tex_complex) 26 | 27 | import draw_scenes 28 | 29 | #import clear 30 | 31 | class TexBobject(SVGBobject): 32 | def __init__(self, *expressions, typeface = 'default', **kwargs): 33 | self.kwargs = kwargs 34 | self.centered = self.get_from_kwargs('centered', False) 35 | 36 | self.typeface = typeface 37 | 38 | if 'vert_align_centers' not in kwargs: 39 | kwargs['vert_align_centers'] = True 40 | 41 | if 'name' not in kwargs: 42 | kwargs['name'] = 'tex' 43 | 44 | #paths = get_svg_file_paths(expressions) 45 | super().__init__(*expressions, **kwargs) 46 | #self.active_expression_path = self.paths[0] 47 | self.annotations = [] 48 | #self.align() 49 | 50 | def get_figure_curves(self, fig): 51 | if fig == None: 52 | return self.imported_svg_data[fig]['curves'] 53 | else: 54 | return self.imported_svg_data[fig]['curves'][1:] 55 | 56 | def align_figure(self, fig): 57 | curve_list = super().align_figure(fig) 58 | #Get rid of reference H after alignment is done 59 | if fig == None: 60 | self.imported_svg_data[fig]['curves'] = curve_list 61 | else: 62 | self.imported_svg_data[fig]['curves'] = curve_list[1:] 63 | 64 | def morph_figure( 65 | self, 66 | final_index, 67 | start_time = None, 68 | start_frame = None, 69 | duration = DEFAULT_MORPH_TIME, 70 | transition_type = None, 71 | arrange_super = True 72 | ): 73 | if start_time != None: 74 | if start_frame != None: 75 | raise Warning("You defined both start frame and start time. " +\ 76 | "Just do one, ya dick.") 77 | start_frame = int(start_time * FRAME_RATE) 78 | 79 | if final_index == 'next': 80 | final_index = self.paths.index(self.active_path) + 1 81 | #self.active_expression_path = self.paths[final_index] 82 | 83 | super().morph_figure( 84 | final_index, 85 | start_frame = start_frame, 86 | duration = duration, 87 | transition_type = transition_type 88 | ) 89 | 90 | #This code messes up the counters, making the numbers all wobbly. 91 | #I don't fully get why, but it has something to do with the fact that 92 | #the counters morph over 3 frames rather than the standard 10. 93 | #For now, this just checks whether the superbobject TexComplex is a 94 | #counter, and then skips if so. 95 | if isinstance(self.superbobject, tex_complex.TexComplex) \ 96 | and 'sim_counter' not in self.superbobject.name and \ 97 | arrange_super == True: 98 | self.superbobject.arrange_tex_bobjects( 99 | start_frame = start_frame, 100 | end_frame = start_frame + duration 101 | ) 102 | 103 | for i, annotation in enumerate(self.annotations): 104 | gesture = annotation[0].subbobjects[0] 105 | label = gesture.subbobjects[0].subbobjects[0] 106 | for j, target in enumerate(annotation[1]): 107 | if target[0] == final_index and j > 0: 108 | #old_loc = deepcopy(gesture.subbobjects[0].ref_obj.location) 109 | gesture.morph_figure( 110 | j, 111 | start_frame = start_frame, 112 | duration = duration 113 | ) 114 | #new_loc = deepcopy(gesture.subbobjects[0].ref_obj.location) 115 | d_loc = [0, 0, 0]#new_loc - old_loc 116 | 117 | for t_bobj in label.tex_bobjects: 118 | t_bobj.morph_figure( 119 | j, 120 | start_frame = start_frame, 121 | duration = duration 122 | ) 123 | #If a t_bobj morphs to an empty expression, adjust d_loc 124 | if t_bobj.paths[j] == None and \ 125 | annotation[2] == 'top' and \ 126 | j > 0 and \ 127 | t_bobj.paths[j-1] != None: 128 | d_loc[1] -= 0.8 #line height as scale = 0.67 129 | #Such hack. 130 | #Should make a vertical alignment 131 | #function for TexComplex TODO 132 | if t_bobj.paths[j] != None and \ 133 | annotation[2] == 'top' and \ 134 | j > 0 and \ 135 | t_bobj.paths[j-1] == None: 136 | d_loc[1] += 0.8 137 | 138 | label.move_to( 139 | start_frame = start_frame, 140 | displacement = d_loc, 141 | new_angle = [0, 0, -label.ref_obj.parent.rotation_euler[2]] 142 | ) 143 | break 144 | 145 | def get_file_paths(self, expressions): 146 | #Replaces an svg_bobject method 147 | self.paths = [] 148 | template = deepcopy(TEMPLATE_TEX_FILE) 149 | if self.typeface != 'default': 150 | template = template[:-4] #chop off the .tex 151 | template += '_' + self.typeface + '.tex' 152 | if not os.path.exists(template): 153 | raise Warning('Can\'t find template tex file for that font.') 154 | for expr in expressions: 155 | if expr == None: 156 | self.paths.append(expr) 157 | else: self.paths.append(tex_to_svg_file(expr, template, self.typeface)) 158 | 159 | def tex_to_svg_file(expression, template_tex_file, typeface): 160 | path = os.path.join( 161 | TEX_DIR, 162 | tex_title(expression, typeface) 163 | ) + ".svg" 164 | if os.path.exists(path): 165 | return path 166 | 167 | tex_file = generate_tex_file(expression, template_tex_file, typeface) 168 | dvi_file = tex_to_dvi(tex_file) 169 | return dvi_to_svg(dvi_file) 170 | 171 | def tex_title(expression, typeface): 172 | name = expression 173 | to_delete = ['/', '\\', '{', '}', ' ', '~', '\'', '\"', '^'] 174 | #Replace these rather than deleting them. These are characters that I've 175 | #wanted as lone expressions. (Which are also off limits in file names) 176 | to_replace = { 177 | '<' : 'lessthan', 178 | '>' : 'greaterthan', 179 | '?' : 'questionmark', 180 | '.' : 'point', 181 | ':' : 'colon', 182 | '%' : 'percent', 183 | '|' : 'vbar' 184 | } 185 | for char in name: 186 | if char in to_delete: 187 | name = name.replace(char, "") 188 | for char in name: 189 | if char in to_replace.keys(): 190 | name = name.replace(char, to_replace[char]) 191 | #name = str(name) + '_' 192 | if typeface != 'default': 193 | name += '_' + typeface 194 | 195 | return str(name) 196 | 197 | def generate_tex_file(expression, template_tex_file, typeface): 198 | result = os.path.join( 199 | TEX_DIR, 200 | tex_title(expression, typeface) 201 | ) + ".tex" 202 | if not os.path.exists(result): 203 | print("Writing \"%s\" to %s"%( 204 | "".join(expression), result 205 | )) 206 | with open(template_tex_file, "r") as infile: 207 | body = infile.read() 208 | #I add an H to every expression to give a common reference point 209 | #for all expressions, then hide the H character. This is necessary 210 | #for consistent alignment of tex curves in blender, because 211 | #blender's import svg function sets the object's origin depending 212 | #on the expression itself, not according to a typesetting reference 213 | #frame. 214 | expression = '\\text{H} ' + expression 215 | body = body.replace(TEX_TEXT_TO_REPLACE, expression) 216 | with open(result, "w") as outfile: 217 | outfile.write(body) 218 | return result 219 | 220 | def tex_to_dvi(tex_file): 221 | result = tex_file.replace(".tex", ".dvi") 222 | if not os.path.exists(result): 223 | commands = [ 224 | "latex", 225 | "-interaction=batchmode", 226 | "-halt-on-error", 227 | "-output-directory=" + TEX_DIR, 228 | tex_file#, 229 | #">", 230 | #get_null() 231 | ] 232 | exit_code = os.system(" ".join(commands)) 233 | if exit_code != 0: 234 | latex_output = '' 235 | log_file = tex_file.replace(".tex", ".log") 236 | if os.path.exists(log_file): 237 | with open(log_file, 'r') as f: 238 | latex_output = f.read() 239 | raise Exception( 240 | "Latex error converting to dvi. " 241 | "See log output above or the log file: %s" % log_file) 242 | return result 243 | 244 | def get_null(): 245 | if os.name == "nt": 246 | return "NUL" 247 | return "/dev/null" 248 | 249 | def dvi_to_svg(dvi_file): 250 | """ 251 | Converts a dvi, which potentially has multiple slides, into a 252 | directory full of enumerated pngs corresponding with these slides. 253 | Returns a list of PIL Image objects for these images sorted as they 254 | where in the dvi 255 | """ 256 | result = dvi_file.replace(".dvi", ".svg") 257 | if not os.path.exists(result): 258 | commands = [ 259 | "dvisvgm", 260 | dvi_file, 261 | "-n", 262 | "-v", 263 | "3", 264 | "-o", 265 | result 266 | #Not sure what these are for, and it seems to work without them 267 | #so commenting out for now 268 | #, 269 | #">", 270 | #get_null() 271 | ] 272 | os.system(" ".join(commands)) 273 | return result 274 | 275 | #For testing 276 | def main(): 277 | for i in range(1000, 1200): 278 | tex_to_svg_file(str(i/10), TEMPLATE_TEX_FILE) 279 | 280 | if __name__ == "__main__": 281 | main() 282 | -------------------------------------------------------------------------------- /blender_scripts/tools/tex_complex.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | import mathutils 3 | 4 | import inspect 5 | import imp 6 | from random import random 7 | import math 8 | import time 9 | from copy import deepcopy 10 | 11 | import constants 12 | imp.reload(constants) 13 | from constants import * 14 | 15 | import helpers 16 | imp.reload(helpers) 17 | from helpers import * 18 | 19 | import bobject 20 | imp.reload(bobject) 21 | from bobject import Bobject 22 | 23 | import gesture 24 | imp.reload(gesture) 25 | 26 | import tex_bobject 27 | imp.reload(tex_bobject) 28 | 29 | class TexComplex(Bobject): 30 | """docstring for TexComplex.""" 31 | def __init__( 32 | self, 33 | *subbobjects, 34 | centered = False, 35 | multiline = False, 36 | align_y = 'top', 37 | line_height = 1.2, 38 | **kwargs 39 | ): 40 | super().__init__(*subbobjects, **kwargs) 41 | self.centered = centered 42 | self.multiline = multiline 43 | self.align_y = align_y 44 | self.line_height = line_height 45 | self.tex_bobjects = list(subbobjects) 46 | self.annotations = [] 47 | 48 | def add_to_blender(self, **kwargs): 49 | self.arrange_tex_bobjects() 50 | super().add_to_blender(**kwargs) 51 | 52 | def arrange_tex_bobjects( 53 | self, 54 | start_time = None, 55 | end_time = None, 56 | start_frame = None, 57 | end_frame = None, 58 | centered = None 59 | ): 60 | #Convert time args to frames 61 | if start_time != None: 62 | if start_frame != None: 63 | raise Warning("You defined both start frame and start time." +\ 64 | "Just do one, ya dick.") 65 | start_frame = start_time * FRAME_RATE 66 | if end_time != None: 67 | if end_frame != None: 68 | raise Warning("You defined both end frame and end time." +\ 69 | "Just do one, ya dick.") 70 | end_frame = end_time * FRAME_RATE 71 | 72 | t_bobjs = self.tex_bobjects 73 | 74 | #for t_bobj in t_bobjs: 75 | # t_bobj.ref_obj.location[1] = 0 76 | 77 | if start_frame != None: 78 | bpy.context.scene.frame_set(start_frame) 79 | for t_bobj in t_bobjs: 80 | t_bobj.ref_obj.keyframe_insert(data_path = 'location', frame = start_frame) 81 | 82 | if end_frame != None: 83 | bpy.context.scene.frame_set(end_frame) 84 | 85 | next_align = 0 86 | if self.multiline == False: 87 | for i, t_bobj in enumerate(t_bobjs): 88 | #Align y 89 | t_bobj.ref_obj.location[1] = 0 90 | 91 | #Align x 92 | t_bobj_length = t_bobj.ref_obj.scale[0] * \ 93 | t_bobj.imported_svg_data[t_bobj.active_path]['length'] 94 | if t_bobj.centered == True: 95 | t_bobj.ref_obj.location[0] = next_align + t_bobj_length / 2 96 | else: 97 | t_bobj.ref_obj.location[0] = next_align 98 | expr_length = t_bobj_length 99 | next_align += expr_length + \ 100 | SPACE_BETWEEN_EXPRESSIONS * t_bobj.ref_obj.scale[0] 101 | 102 | else: 103 | #Align y 104 | if self.align_y == 'center': 105 | num_newlines = len(t_bobjs) - 1 106 | vert_disp = num_newlines * self.line_height / 2 107 | elif self.align_y == 'top': vert_disp = 0 108 | elif self.align_y == 'bottom': raise Warning('Not implemented') #TODO 109 | for t_bobj in t_bobjs: 110 | #Align x 111 | t_bobj.ref_obj.location[0] = 0 112 | 113 | t_bobj.ref_obj.location[1] = vert_disp 114 | vert_disp -= self.line_height 115 | #if t_bobj.centered == True: 116 | # t_bobj.ref_obj.location[0] = 0 117 | 118 | #Overall alignment and justification is a bit janky. 119 | if centered == None: centered = self.centered 120 | if centered == True and self.multiline == False: 121 | next_align -= SPACE_BETWEEN_EXPRESSIONS 122 | for t_bobj in t_bobjs: 123 | t_bobj.ref_obj.location[0] -= next_align / 2 124 | 125 | 126 | for i, t_bobj in enumerate(t_bobjs): 127 | #If any annotations are targeting the current t_bobj, move them too 128 | for annotation in self.annotations: 129 | if annotation[1] == i: 130 | #Avoid adding starting keyframe if it's in the right place 131 | #already. This is correct if the start position is already 132 | #keyframed, but it might mess up if that's not the case. 133 | #A more robust way would check the fcurve. 134 | if start_frame != None and \ 135 | annotation[0].ref_obj.location[0] != t_bobj.ref_obj.location[0]: 136 | annotation[0].ref_obj.keyframe_insert(data_path = 'location', frame = start_frame) 137 | annotation[0].ref_obj.location[0] = t_bobj.ref_obj.location[0] 138 | if end_frame != None: 139 | annotation[0].ref_obj.keyframe_insert(data_path = 'location', frame = end_frame) 140 | 141 | if end_frame != None: 142 | for t_bobj in t_bobjs: 143 | t_bobj.ref_obj.keyframe_insert(data_path = 'location', frame = end_frame) 144 | 145 | def add_annotation( 146 | self, 147 | targets = None, 148 | alignment = 'top', 149 | labels = None, 150 | angle = 0, 151 | length = 1, 152 | label_scale = 0.67, 153 | gest_scale = 1 154 | ): 155 | #calc points from targets 156 | gesture_series = [] 157 | tex_bobj = self.tex_bobjects[targets[0]] 158 | #label_anchor = None 159 | for j, target in enumerate(targets[1]): 160 | bobjs = [] 161 | path = tex_bobj.paths[target[0]] 162 | for i in range(target[1], target[2] + 1): 163 | try: 164 | bobjs.append(tex_bobj.imported_svg_data[path]['curves'][i]) 165 | except: 166 | print(i) 167 | print(tex_bobj.imported_svg_data[path]['curves']) 168 | raise() 169 | 170 | left_most = math.inf 171 | right_most = -math.inf 172 | y = 0 173 | for bobj in bobjs: 174 | cur = bobj.objects[0] 175 | for spline in cur.data.splines: 176 | for point in spline.bezier_points: 177 | candidatex = cur.location[0] * cur.parent.scale[0] + \ 178 | cur.parent.location[0] * cur.parent.parent.scale[0] + \ 179 | point.co[0] * cur.scale[0] 180 | if right_most < candidatex: 181 | right_most = candidatex 182 | if left_most > candidatex: 183 | left_most = candidatex 184 | for point in spline.bezier_points: 185 | candidatey = cur.location[1] * cur.parent.scale[1] + \ 186 | cur.parent.location[1] * cur.parent.parent.scale[1] + \ 187 | point.co[1] * cur.scale[1] 188 | if alignment == 'top': 189 | if y < candidatey: 190 | y = candidatey 191 | elif alignment == 'bottom': 192 | if y > candidatey: 193 | y = candidatey 194 | 195 | if isinstance(angle, (float, int)): 196 | this_angle = angle 197 | elif isinstance(angle, list): 198 | this_angle = angle[j] 199 | 200 | if len(target) > 3 and target[3] == None: #No bobjs, empty gesture. HEH. 201 | if alignment == 'top': 202 | #y += 0 * self.ref_obj.scale[1] * tex_bobj.ref_obj.scale[1] 203 | head = ((right_most + left_most) / 2 / gest_scale, 204 | y + length, 205 | 0) 206 | rot = (0, 0, 0) 207 | elif alignment == 'bottom': 208 | #y -= 0 * self.ref_obj.scale[1] * tex_bobj.ref_obj.scale[1] 209 | head = ((right_most + left_most) / 2 / gest_scale, 210 | y - length, 211 | 0) 212 | rot = (0, 0, math.pi) 213 | #if label_anchor == None: 214 | # label_anchor = list(head) 215 | gesture_series.append( 216 | { 217 | 'type' : None, 218 | 'points' : { 219 | 'location' : head, 220 | 'rotation' : rot 221 | } 222 | } 223 | ) 224 | elif len(target) > 3 and target[3] == 'bracket' or \ 225 | (len(target) == 3 and len(bobjs) > 1): #Bracket 226 | if alignment == 'top': 227 | y += 0.2 * self.ref_obj.scale[1] * tex_bobj.ref_obj.scale[1] 228 | annotation_point = ((right_most + left_most) / 2 / gest_scale, y + length, 0) 229 | left_point = (left_most / gest_scale, y, 0) 230 | right_point = (right_most / gest_scale, y, 0) 231 | elif alignment == 'bottom': 232 | y -= 0.2 * self.ref_obj.scale[1] * tex_bobj.ref_obj.scale[1] 233 | annotation_point = ((right_most + left_most) / 2 / gest_scale, y - length, 0) 234 | left_point = [right_most / gest_scale, y, 0] 235 | right_point = [left_most / gest_scale, y, 0] 236 | #if label_anchor == None: 237 | # label_anchor = list(annotation_point) 238 | gesture_series.append( 239 | { 240 | 'type' : 'bracket', 241 | 'points' : { 242 | 'annotation_point' : annotation_point, 243 | 'left_point' : left_point, 244 | 'right_point' : right_point 245 | } 246 | } 247 | ) 248 | 249 | elif len(target) > 3 and target[3] == 'arrow' or \ 250 | (len(target) == 3 and len(bobjs) == 1): #Arrow 251 | if alignment == 'top': 252 | y += 0.3 * tex_bobj.ref_obj.scale[1] #* self.ref_obj.scale[1] 253 | head = ((right_most + left_most) / 2 / gest_scale + math.tan(this_angle) * 0.4, 254 | y / gest_scale, 255 | 0) 256 | tail = ((right_most + left_most) / 2 / gest_scale + math.tan(this_angle) * length, 257 | (y + length) / gest_scale, 258 | 0) 259 | elif alignment == 'bottom': 260 | y -= 0.3 * tex_bobj.ref_obj.scale[1] #* self.ref_obj.scale[1] 261 | head = ((right_most + left_most) / 2 / gest_scale - math.tan(this_angle) * 0.4, 262 | y / gest_scale, 263 | 0) 264 | tail = ((right_most + left_most) / 2 / gest_scale - math.tan(this_angle) * length, 265 | (y - length) / gest_scale, 266 | 0) 267 | #if label_anchor == None: 268 | # label_anchor = list(tail) 269 | gesture_series.append( 270 | { 271 | 'type' : 'arrow', 272 | 'points' : { 273 | 'head' : head, 274 | 'tail' : tail, 275 | } 276 | } 277 | ) 278 | else: 279 | raise Warning('Something is wrong with the gesture targets.') 280 | 281 | container = bobject.Bobject(name = 'annotation') 282 | 283 | gest = gesture.Gesture( 284 | gesture_series = gesture_series, 285 | color = 'color2', 286 | scale = gest_scale 287 | ) 288 | container.add_subbobject(gest) 289 | tex_bobj.annotations.append([container, targets[1], alignment]) 290 | self.annotations.append([container, targets[0]]) 291 | 292 | #Make TexComplex for the annotation_text 293 | t_bobj_count = 0 294 | for label in labels: 295 | t_bobj_count = max(len(label), t_bobj_count) 296 | for label in labels: 297 | while len(label) < t_bobj_count: 298 | label.append(None) 299 | t_bobjs = [] 300 | for i in range(t_bobj_count): 301 | strings = [] 302 | for label in labels: 303 | strings.append(label[i]) 304 | #print(len(strings)) 305 | t_bobj = tex_bobject.TexBobject(*strings, centered = True, color = 'color2') 306 | t_bobjs.append(t_bobj) 307 | 308 | #label_scale = 0.67 #Smaller than text. Could do this in a more robust way 309 | #se;fline_height = 1.2 #Could make this a constant. It's already a default. 310 | #dy = (1 + t_bobj_count) / 2 * self.line_height 311 | #print(t_bobj_count) 312 | if alignment == 'top': 313 | dy = (t_bobj_count / 2 + 1/2) * self.line_height 314 | if alignment == 'bottom': 315 | dy = (t_bobj_count / 2) * self.line_height 316 | 317 | #Some t_bobjs may start with empty expressions. Initial position 318 | #shouldn't take empty lines into account, and position will be adjusted on morph 319 | if alignment == 'top': 320 | for t_bobj in t_bobjs: 321 | if t_bobj.paths[0] == None: 322 | dy -= self.line_height# * label_scale 323 | 324 | #label_anchor[1] += dy 325 | 326 | label_text = TexComplex( 327 | *t_bobjs, 328 | multiline = True, 329 | centered = True, 330 | align_y = 'center', 331 | scale = label_scale, 332 | name = 'label', 333 | location = (0, dy, 0),#label_anchor 334 | rotation_euler = [0, 0, -gest.subbobjects[0].ref_obj.rotation_euler[2]] 335 | ) 336 | 337 | gest.subbobjects[0].add_subbobject(label_text) 338 | self.add_subbobject(container) 339 | 340 | def add_tex_bobject(self, bobj, index = None): 341 | super().add_subbobject(bobj) 342 | if index == None: 343 | self.tex_bobjects.append(bobj) 344 | else: 345 | self.tex_bobjects.insert(index, bobj) 346 | -------------------------------------------------------------------------------- /blender_scripts/video_scenes/fecal_transplant.py: -------------------------------------------------------------------------------- 1 | import collections 2 | import math 3 | 4 | import imp 5 | import scene 6 | imp.reload(scene) 7 | from scene import Scene 8 | 9 | import svg_bobject 10 | imp.reload(svg_bobject) 11 | import tex_bobject 12 | imp.reload(tex_bobject) 13 | import creature 14 | imp.reload(creature) 15 | import drawn_world 16 | imp.reload(drawn_world) 17 | import population 18 | imp.reload(population) 19 | import gesture 20 | imp.reload(gesture) 21 | import graph_bobject 22 | imp.reload(graph_bobject) 23 | import tex_complex 24 | imp.reload(tex_complex) 25 | 26 | import helpers 27 | imp.reload(helpers) 28 | from helpers import * 29 | 30 | class LastVideoExp(Scene): 31 | def __init__(self): 32 | self.subscenes = collections.OrderedDict([ 33 | ('pre_transplant', {'duration': 10}), 34 | #('post_transplant', {'duration': 10}),\ 35 | ]) 36 | super().__init__() 37 | 38 | def play(self): 39 | super().play() 40 | #self.subscenes 41 | #self.duration 42 | 43 | self.pre_transplant() 44 | 45 | def pre_transplant(self): 46 | #Stretch goal: Make blobs look around in a more directed way 47 | cues = self.subscenes['pre_transplant'] 48 | 49 | make_image_background('colon_zoom_right.png') 50 | 51 | #Zoom out to effectively make the bacteria smaller 52 | zoom_out_factor = 2 53 | x_shift = 10.5 54 | cam_bobj = bobject.Bobject( 55 | location = [ 56 | CAMERA_LOCATION[0], 57 | CAMERA_LOCATION[1], 58 | zoom_out_factor * CAMERA_LOCATION[2], 59 | ] 60 | ) 61 | cam_bobj.add_to_blender(appear_time = 0) 62 | cam_obj = bpy.data.objects['Camera'] 63 | cam_obj.location = [0, 0, 0] 64 | cam_obj.parent = cam_bobj.ref_obj 65 | 66 | colon_points = [ 67 | [-5.498534679412842, -3.8365087509155273, 0], 68 | [-6.38494873046875, -3.227099657058716, 0], 69 | [-6.717353343963623, -2.146782875061035, 0], 70 | [-6.634251117706299, -0.9279640316963196, 0], 71 | [-6.551150321960449, 1.2049685716629028, 0], 72 | [-6.384949207305908, 2.4237873554229736, 0], 73 | [-6.218745708465576, 3.42100191116333, 0], 74 | [-6.440348148345947, 4.584420204162598, 0], 75 | [-6.274146556854248, 6.163344860076904, 0], 76 | [-5.249231338500977, 6.27414608001709, 0], 77 | [-4.196613788604736, 5.969441890716553, 0], 78 | [-3.227100133895874, 5.609334945678711, 0], 79 | [-2.063681125640869, 5.415432929992676, 0], 80 | [-0.9556646347045898, 5.332330703735352, 0], 81 | [0.26315388083457947, 5.24923038482666, 0], 82 | [1.5096733570098877, 5.332330703735352, 0], 83 | [2.589988946914673, 5.415432453155518, 0], 84 | [3.5318045616149902, 5.637035846710205, 0], 85 | [4.639822483062744, 6.052542209625244, 0], 86 | [5.637034893035889, 6.440348148345947, 0], 87 | [6.4957499504089355, 6.301846504211426, 0], 88 | [6.301847457885742, 4.667520999908447, 0], 89 | [6.495749473571777, 3.531803846359253, 0], 90 | [6.163345813751221, 2.4237871170043945, 0], 91 | [6.274145603179932, 0.7063607573509216, 0], 92 | [6.246445178985596, -1.1772679090499878, 0], 93 | [5.914041996002197, -2.8115925788879395, 0], 94 | [5.221530437469482, -3.947310209274292, 0], 95 | [4.55672025680542, -5.05532693862915, 0], 96 | [2.9223949909210205, -6.052542686462402, 0], 97 | [1.6481753587722778, -6.191044807434082, 0], 98 | [0.06925218552350998, -6.4403486251831055, 0], 99 | [-0.016619933769106865, -8.26026725769043, 0], 100 | [-0.9805952310562134, -7.828138828277588, 0], 101 | [-1.1135575771331787, -6.465278148651123, 0], 102 | [-0.9473539590835571, -5.434823036193848, 0], 103 | [-0.44874706864356995, -5.069177150726318, 0], 104 | [0.7146719098091125, -5.335101127624512, 0], 105 | [1.711885690689087, -5.733987808227539, 0], 106 | [2.742342472076416, -5.534544944763184, 0], 107 | [3.6065964698791504, -4.803253650665283, 0], 108 | [4.537330627441406, -3.83927845954895, 0], 109 | [5.035937309265137, -2.5096583366394043, 0], 110 | [5.401581287384033, -1.0138355493545532, 0], 111 | [5.534543514251709, 1.1135568618774414, 0], 112 | [5.501305103302002, 2.5428988933563232, 0], 113 | [5.468064785003662, 3.8060386180877686, 0], 114 | [5.069177150726318, 5.36834192276001, 0], 115 | [3.8060388565063477, 4.902975559234619, 0], 116 | [1.8448481559753418, 4.437607288360596, 0], 117 | [0.648189127445221, 4.171682834625244, 0], 118 | [-0.5152283310890198, 4.2049241065979, 0], 119 | [-1.645405888557434, 4.204923629760742, 0], 120 | [-2.8420634269714355, 4.736772060394287, 0], 121 | [-4.204924583435059, 5.102417945861816, 0], 122 | [-5.501304626464844, 5.401582717895508, 0], 123 | [-5.667506694793701, 4.969455242156982, 0], 124 | [-5.733987808227539, 3.6065945625305176, 0], 125 | [-5.733988285064697, 2.376697063446045, 0], 126 | [-5.634267330169678, 1.6786457300186157, 0], 127 | [-5.7672295570373535, 1.0470755100250244, 0], 128 | [-5.9334306716918945, -0.2493036538362503, 0], 129 | [-5.501305103302002, -1.8116075992584229, 0], 130 | [-5.035937786102295, -2.8753042221069336, 0], 131 | [-4.969456672668457, -3.506873369216919, 0], 132 | ] 133 | #Not sure exactly how this happened, but all those points are off by 134 | #a scale factor 135 | for i in range(len(colon_points)): 136 | new_point = deepcopy(colon_points[i]) 137 | colon_points[i] = [ 138 | new_point[0] * 1.45 * zoom_out_factor + x_shift, 139 | new_point[1] * 1.45 * zoom_out_factor, 140 | new_point[2] * 1.45 * zoom_out_factor, 141 | ] 142 | 143 | 144 | sim_duration = 500 145 | start_delay = 0.5 146 | frames_per_time_step = 1 147 | 148 | #initial_creature_count = 10 149 | blue_count = 90 150 | green_count = 10 151 | initial_creatures = [] 152 | for i in range(blue_count): 153 | new_creature = creature.Creature( 154 | color = 'creature_color_1', 155 | shape = 'shape1', 156 | size = '0.5' 157 | ) 158 | initial_creatures.append(new_creature) 159 | for i in range(green_count): 160 | new_creature = creature.Creature( 161 | color = 'creature_color_2', 162 | shape = 'shape1', 163 | size = '0.5' 164 | ) 165 | initial_creatures.append(new_creature) 166 | sim = drawn_world.DrawnWorld( 167 | name = 'limited_sim', 168 | location = [0, 0, 0], 169 | scale = 0.7, 170 | start_delay = start_delay, 171 | frames_per_time_step = frames_per_time_step, 172 | sim_duration = sim_duration, 173 | spin_creatures = True, 174 | initial_creatures = initial_creatures, 175 | creature_model = ['bacteria', 'biochem'], 176 | #save = True, 177 | #load = 'o_logistic2', 178 | overlap_okay = True, 179 | gene_updates = [ 180 | #Other alleles 181 | ['shape', 'shape1', 'birth_modifier', 1, 0], 182 | ['size', '0.5', 'birth_modifier', 1, 0], 183 | ['shape', 'shape1', 'mutation_chance', 0, 0], 184 | ['size', '0.5', 'mutation_chance', 0, 0], 185 | #Color 1 initial settings 186 | ['color', 'creature_color_1', 'birth_modifier', 900, 0], 187 | ['color', 'creature_color_1', 'death_modifier', 10, 0], 188 | ['color', 'creature_color_1', 'replication_modifier', 0, 0], 189 | ['color', 'creature_color_1', 'mutation_chance', 0, 0], 190 | #Color 2 initial settings 191 | ['color', 'creature_color_2', 'birth_modifier', 100, 0], 192 | ['color', 'creature_color_2', 'death_modifier', 10, 0], 193 | ['color', 'creature_color_2', 'replication_modifier', 0, 0], 194 | ['color', 'creature_color_2', 'mutation_chance', 0, 0], 195 | #Antibiotic 196 | ['color', 'creature_color_1', 'birth_modifier', 900, 120], 197 | ['color', 'creature_color_1', 'death_modifier', 90, 120], 198 | ['color', 'creature_color_2', 'birth_modifier', 100, 120], 199 | ['color', 'creature_color_2', 'death_modifier', 20, 120], 200 | #Color 2 dominates 201 | ['color', 'creature_color_1', 'birth_modifier', 400, 180], 202 | ['color', 'creature_color_1', 'death_modifier', 10, 180], 203 | ['color', 'creature_color_2', 'birth_modifier', 600, 180], 204 | ['color', 'creature_color_2', 'death_modifier', 10, 180], 205 | 206 | 207 | ], 208 | world_bound_points = colon_points, 209 | bound_mode = 'points', 210 | show_world = False 211 | ) 212 | 213 | 214 | sim.add_to_blender(appear_time = 0) 215 | 216 | healthy = import_object( 217 | 'bacteria', 'biochem', 218 | location = [-21.5, 10, 0], 219 | scale = 6 220 | ) 221 | apply_material(healthy.ref_obj.children[0], 'creature_color3') 222 | healthy_text = tex_bobject.TexBobject( 223 | '\\text{Healthy Bacteria}', 224 | location = [-21.5, 3, 0], 225 | centered = True, 226 | scale = 2, 227 | color = 'color2' 228 | ) 229 | 230 | healthy.add_to_blender(appear_time = 0) 231 | healthy_text.add_to_blender(appear_time = 0) 232 | 233 | 234 | 235 | unhealthy = import_object( 236 | 'bacteria', 'biochem', 237 | location = [-21.5, -5, 0], 238 | scale = 6 239 | ) 240 | apply_material(unhealthy.ref_obj.children[0], 'creature_color7') 241 | clos = tex_bobject.TexBobject( 242 | '\\text{Clostridium}', 243 | color = 'color2', 244 | centered = True 245 | ) 246 | diff = tex_bobject.TexBobject( 247 | '\\text{difficile}', 248 | color = 'color2', 249 | centered = True 250 | ) 251 | unhealthy_text = tex_complex.TexComplex( 252 | clos, diff, 253 | location = [-21.5, -13, 0], 254 | centered = True, 255 | scale = 2, 256 | multiline = True, 257 | ) 258 | unhealthy_text.add_to_blender(appear_time = 0) 259 | unhealthy.add_to_blender(appear_time = 0) 260 | 261 | #Prep for next scene 262 | to_disappear = [] 263 | for i, thing in enumerate(to_disappear): 264 | thing.disappear(disappear_time = cues['end'] - (len(to_disappear) - 1 - i) * 0.05) 265 | 266 | 267 | """print() 268 | points = [ 269 | [2, 0, 0], 270 | [0, 0, 0], 271 | [-0.1, 0.5, 0], 272 | [-0.4594877792145916, -5.443530545247961, 0.0], 273 | [-5.066866886029154, -4.334055768904699, 0], 274 | [5, 4, 0] 275 | ] 276 | for point in points: 277 | print(sim.is_point_in_bounds(point)) 278 | print() 279 | print()""" 280 | -------------------------------------------------------------------------------- /blender_scripts/video_scenes/recurring_assets.py: -------------------------------------------------------------------------------- 1 | import collections 2 | import math 3 | from random import random, uniform, randrange 4 | import bpy 5 | 6 | import imp 7 | #import scene 8 | #imp.reload(scene) 9 | from scene import Scene 10 | 11 | import svg_bobject 12 | imp.reload(svg_bobject) 13 | import tex_bobject 14 | imp.reload(tex_bobject) 15 | import tex_complex 16 | imp.reload(tex_complex) 17 | import gesture 18 | imp.reload(gesture) 19 | import graph_bobject 20 | imp.reload(graph_bobject) 21 | import natural_sim 22 | imp.reload(natural_sim) 23 | import table_bobject 24 | imp.reload(table_bobject) 25 | import blobject 26 | imp.reload(blobject) 27 | 28 | import helpers 29 | imp.reload(helpers) 30 | from helpers import * 31 | 32 | BLOB_VOLUME_DENSITY = 0.04 33 | 34 | class Asset(Scene): 35 | def __init__(self): 36 | self.subscenes = collections.OrderedDict([ 37 | ('card', {'duration': 1000}) 38 | ]) 39 | super().__init__() 40 | 41 | def play(self): 42 | super().play() 43 | 44 | #self.end_card() 45 | #self.banner() 46 | self.waving_blob() 47 | #self.banner_angled() 48 | #self.dftba_banner() 49 | 50 | def end_card(self): 51 | cues = self.subscenes 52 | scene_end = self.duration 53 | 54 | bpy.ops.mesh.primitive_plane_add() 55 | play_bar = bpy.context.object 56 | play_bar.scale[0] = 15 57 | play_bar.scale[1] = 90 / 720 * 8.4 58 | play_bar.location = [0, -8.4 + play_bar.scale[1], -0.01] 59 | 60 | bpy.ops.mesh.primitive_plane_add() 61 | vid_rec = bpy.context.object 62 | vid_rec.scale[0] = 410 / 1280 * 15 63 | vid_rec.scale[1] = 230 / 720 * 8.4 64 | vid_rec.location = [9, -3, -0.01] 65 | apply_material(vid_rec, 'color6') 66 | 67 | bpy.ops.mesh.primitive_cylinder_add() 68 | sub_cir = bpy.context.object 69 | sub_cir.scale = [98 / 1280 * 30, 98 / 1280 * 30, 0] 70 | sub_cir.location = [-11, 3.2, -0.01] 71 | 72 | #Whole end area 73 | bpy.ops.mesh.primitive_plane_add() 74 | end_area = bpy.context.object 75 | end_area.scale[0] = 1225 / 1280 * 15 76 | end_area.scale[1] = 518 / 720 * 8.4 77 | end_area.location = [0, 0.2, -0.15] 78 | 79 | logo = svg_bobject.SVGBobject( 80 | "Layer", 81 | #file_name = "PrimerLogoWhite", 82 | location = (-8.7, 3, 0), 83 | scale = 1.4 84 | ) 85 | for bobj in logo.rendered_curve_bobjects: 86 | apply_material(bobj.ref_obj.children[0], 'color2') 87 | stroke = logo.rendered_curve_bobjects[0] 88 | apply_material(stroke.ref_obj.children[0], 'color3') 89 | logo.morph_chains[0][0].ref_obj.location[2] = -1 90 | logo.add_to_blender( 91 | appear_time = cues['card']['start'], 92 | #subbobject_timing = [90, 30, 40, 50, 60, 70, 80], 93 | subbobject_timing = [42, 30, 33, 36, 39, 42, 45], 94 | animate = True 95 | ) 96 | 97 | patreon = import_object( 98 | 'patreon', 'svgblend', 99 | scale = 2.297, 100 | location = (-11, -3, 0), 101 | name = 'Patreon' 102 | ) 103 | patreon.add_to_blender(appear_time = 0) 104 | thanks = tex_bobject.TexBobject( 105 | '\\text{Special thanks:}', 106 | location = [-8.35, -1.4, 0], 107 | color = 'color2' 108 | ) 109 | thanks.add_to_blender(appear_time = 0) 110 | js = tex_bobject.TexBobject( 111 | '\\text{Jordan Scales}', 112 | location = [-7.8, -2.75, 0], 113 | color = 'color2', 114 | scale = 1 115 | ) 116 | js.add_to_blender(appear_time = 0.5) 117 | 118 | ap = tex_bobject.TexBobject( 119 | '\\text{Anonymous Patrons}', 120 | location = [-7.8, -4, 0], 121 | color = 'color2', 122 | scale = 1 123 | ) 124 | ap.add_to_blender(appear_time = 0.75) 125 | 126 | 127 | remaining = [logo, patreon, thanks, js, ap] 128 | for thing in remaining: 129 | thing.disappear(disappear_time = 2.5) 130 | 131 | def banner(self): 132 | cam_bobj, cam_swivel = cam_and_swivel( 133 | cam_location = [0, 0, 32.8], 134 | cam_rotation_euler = [0, 0, 0], 135 | cam_name = "Camera Bobject", 136 | swivel_location = [0, 0, 0], 137 | swivel_rotation_euler = [0, 0, 0], 138 | swivel_name = 'Cam swivel', 139 | #control_sun = True 140 | ) 141 | cam_swivel.add_to_blender(appear_time = -1) 142 | 143 | logo = svg_bobject.SVGBobject( 144 | "Layer", 145 | #file_name = "PrimerLogoWhite", 146 | location = [0, 0, 0], 147 | rotation_euler = [0, 0, 0], 148 | scale = 2, 149 | centered = True 150 | ) 151 | for bobj in logo.rendered_curve_bobjects: 152 | apply_material(bobj.ref_obj.children[0], 'color2') 153 | stroke = logo.rendered_curve_bobjects[0] 154 | apply_material(stroke.ref_obj.children[0], 'color3') 155 | logo.morph_chains[0][0].ref_obj.location[2] = -1 156 | logo.add_to_blender( 157 | appear_time = -1, 158 | #subbobject_timing = [90, 30, 40, 50, 60, 70, 80], 159 | #subbobject_timing = [42, 30, 33, 36, 39, 42, 45], 160 | animate = False 161 | ) 162 | 163 | b_blob = blobject.Blobject( 164 | scale = 2, 165 | location = [-9, 0, 0], 166 | rotation_euler = [0, 20 * math.pi / 180, 0], 167 | mat = 'creature_color3' 168 | ) 169 | b_blob.add_to_blender(appear_time = 0, animate = False) 170 | #apply_material(b_blob.ref_obj.children[0].children[0], 'creature_color3') 171 | b_blob.hello( 172 | start_time = 2, 173 | end_time = 8 174 | ) 175 | 176 | r_blob = blobject.Blobject( 177 | scale = 2, 178 | location = [12.5, 0, 0], 179 | rotation_euler = [0, 0, 0], 180 | mat = 'creature_color6' 181 | #wiggle = True 182 | ) 183 | r_blob.add_to_blender(appear_time = 0, animate = False) 184 | #apply_material(r_blob.ref_obj.children[0].children[0], 'creature_color6') 185 | r_blob.evil_pose( 186 | start_time = 6, 187 | end_time = 10 188 | ) 189 | 190 | g_blob = blobject.Blobject( 191 | scale = 2, 192 | location = [9, 0, 0], 193 | rotation_euler = [0, - 10 * math.pi / 180, 0], 194 | wiggle = True, 195 | cycle_length = 600, 196 | mat = 'creature_color7' 197 | ) 198 | g_blob.add_to_blender(appear_time = 0, animate = False) 199 | #apply_material(g_blob.ref_obj.children[0].children[0], 'creature_color7') 200 | 201 | o_blob = blobject.Blobject( 202 | scale = 2, 203 | location = [-12.5, 0, 0], 204 | rotation_euler = [0, 0, 0], 205 | mat = 'creature_color4' 206 | #wiggle = True 207 | ) 208 | o_blob.add_to_blender(appear_time = 0, animate = False) 209 | #apply_material(o_blob.ref_obj.children[0].children[0], 'creature_color4') 210 | 211 | def waving_blob(self): 212 | cam_bobj, cam_swivel = cam_and_swivel( 213 | cam_location = [0, 0, 32.8], 214 | cam_rotation_euler = [0, 0, 0], 215 | cam_name = "Camera Bobject", 216 | swivel_location = [0, 0, 0], 217 | swivel_rotation_euler = [0, 0, 0], 218 | swivel_name = 'Cam swivel', 219 | #control_sun = True 220 | ) 221 | cam_swivel.add_to_blender(appear_time = -1) 222 | 223 | b_blob = blobject.Blobject( 224 | scale = 7, 225 | location = [0, 0, 0], 226 | rotation_euler = [0, 0 * math.pi / 180, 0], 227 | mat = 'creature_color3' 228 | ) 229 | b_blob.add_to_blender(appear_time = 0, animate = False) 230 | #apply_material(b_blob.ref_obj.children[0].children[0], 'creature_color3') 231 | b_blob.hello( 232 | start_time = 2, 233 | end_time = 8 234 | ) 235 | b_blob.show_mouth(start_time = 2) 236 | 237 | r_blob = blobject.Blobject( 238 | scale = 2, 239 | location = [12.5, 0, 0], 240 | rotation_euler = [0, 0, 0], 241 | mat = 'creature_color6' 242 | #wiggle = True 243 | ) 244 | 245 | def banner_angled(self): 246 | cam_bobj, cam_swivel = cam_and_swivel( 247 | cam_location = [0, 0, 32.8], 248 | cam_rotation_euler = [0, 0, 0], 249 | cam_name = "Camera Bobject", 250 | swivel_location = [0, 0, 0], 251 | swivel_rotation_euler = [70 * math.pi / 180, 0, 0], 252 | swivel_name = 'Cam swivel', 253 | control_sun = True 254 | ) 255 | cam_swivel.add_to_blender(appear_time = -1) 256 | 257 | logo = svg_bobject.SVGBobject( 258 | "Layer", 259 | #file_name = "PrimerLogoWhite", 260 | location = [0, 0, 0], 261 | rotation_euler = [math.pi / 2, 0, 0], 262 | scale = 4, 263 | centered = True 264 | ) 265 | for bobj in logo.rendered_curve_bobjects: 266 | apply_material(bobj.ref_obj.children[0], 'color2') 267 | stroke = logo.rendered_curve_bobjects[0] 268 | apply_material(stroke.ref_obj.children[0], 'color3') 269 | logo.morph_chains[0][0].ref_obj.location[2] = -1 270 | logo.add_to_blender( 271 | appear_time = -1, 272 | #subbobject_timing = [90, 30, 40, 50, 60, 70, 80], 273 | #subbobject_timing = [42, 30, 33, 36, 39, 42, 45], 274 | animate = False 275 | ) 276 | 277 | b_blob = import_object( 278 | 'boerd_blob', 'creatures', 279 | scale = 2, 280 | location = [2, -10, 0], 281 | rotation_euler = [math.pi / 2, 0, 0], 282 | wiggle = True 283 | ) 284 | b_blob.add_to_blender(appear_time = 0) 285 | apply_material(b_blob.ref_obj.children[0].children[0], 'creature_color3') 286 | 287 | 288 | r_blob = import_object( 289 | 'boerd_blob', 'creatures', 290 | scale = 2, 291 | location = [-7, -7, 0], 292 | rotation_euler = [math.pi / 2, 0, 100 * math.pi / 180], 293 | wiggle = True 294 | ) 295 | r_blob.add_to_blender(appear_time = 0) 296 | apply_material(r_blob.ref_obj.children[0].children[0], 'creature_color6') 297 | 298 | g_blob = import_object( 299 | 'boerd_blob', 'creatures', 300 | scale = 2, 301 | location = [-5, -5, 0], 302 | rotation_euler = [math.pi / 2, 0, -10 * math.pi / 180], 303 | wiggle = True 304 | ) 305 | g_blob.add_to_blender(appear_time = 0) 306 | apply_material(g_blob.ref_obj.children[0].children[0], 'creature_color7') 307 | 308 | o_blob = import_object( 309 | 'boerd_blob', 'creatures', 310 | scale = 2, 311 | location = [10, -6, 0], 312 | rotation_euler = [math.pi / 2, 0, math.pi / 2], 313 | wiggle = True 314 | ) 315 | o_blob.add_to_blender(appear_time = 0) 316 | apply_material(o_blob.ref_obj.children[0].children[0], 'creature_color4') 317 | 318 | def dftba_banner(self): 319 | cam_bobj, cam_swivel = cam_and_swivel( 320 | cam_location = [0, 0, 32.8], 321 | cam_rotation_euler = [0, 0, 0], 322 | cam_name = "Camera Bobject", 323 | swivel_location = [0, 0, 0], 324 | swivel_rotation_euler = [70 * math.pi / 180, 0, 0], 325 | swivel_name = 'Cam swivel', 326 | control_sun = True 327 | ) 328 | cam_swivel.add_to_blender(appear_time = -1) 329 | 330 | scn = bpy.context.scene 331 | scn.render.tile_y = RENDER_TILE_SIZE 332 | scn.render.resolution_x = 1300 333 | scn.render.resolution_y = 875 334 | 335 | logo = svg_bobject.SVGBobject( 336 | "Layer", 337 | #file_name = "PrimerLogoWhite", 338 | location = [0, 1.5, 4], 339 | rotation_euler = [74 * math.pi / 180, 0, 0], 340 | scale = 4, 341 | centered = True 342 | ) 343 | for bobj in logo.rendered_curve_bobjects: 344 | apply_material(bobj.ref_obj.children[0], 'color2') 345 | stroke = logo.rendered_curve_bobjects[0] 346 | apply_material(stroke.ref_obj.children[0], 'color3') 347 | logo.morph_chains[0][0].ref_obj.location[2] = -1 348 | logo.add_to_blender( 349 | appear_time = -1, 350 | #subbobject_timing = [90, 30, 40, 50, 60, 70, 80], 351 | #subbobject_timing = [42, 30, 33, 36, 39, 42, 45], 352 | animate = False 353 | ) 354 | 355 | b_blob = blobject.Blobject( 356 | scale = 2, 357 | location = [1, -10, 0], 358 | rotation_euler = [math.pi / 2, 0, 0], 359 | #wiggle = True 360 | ) 361 | b_blob.add_to_blender(appear_time = 0) 362 | apply_material(b_blob.ref_obj.children[0].children[0], 'creature_color3') 363 | 364 | 365 | r_blob = blobject.Blobject( 366 | scale = 2, 367 | location = [-5, -7, 0], 368 | rotation_euler = [math.pi / 2, 0, -10 * math.pi / 180], 369 | wiggle = True 370 | ) 371 | r_blob.add_to_blender(appear_time = 0) 372 | apply_material(r_blob.ref_obj.children[0].children[0], 'creature_color6') 373 | 374 | g_blob = blobject.Blobject( 375 | scale = 2, 376 | location = [-7, -9, 0], 377 | rotation_euler = [math.pi / 2, 0, 100 * math.pi / 180], 378 | wiggle = True 379 | ) 380 | g_blob.add_to_blender(appear_time = 0) 381 | apply_material(g_blob.ref_obj.children[0].children[0], 'creature_color7') 382 | 383 | o_blob = blobject.Blobject( 384 | scale = 2, 385 | location = [8, -6, 0], 386 | rotation_euler = [math.pi / 2, 0, -50 * math.pi / 180], 387 | #wiggle = True 388 | ) 389 | o_blob.add_to_blender(appear_time = 0) 390 | apply_material(o_blob.ref_obj.children[0].children[0], 'creature_color4') 391 | 392 | b_blob.hello( 393 | start_time = 1, 394 | end_time = 10 395 | ) 396 | '''b_blob.eat_animation( 397 | start_frame = 1 * FRAME_RATE, 398 | end_frame = 10 * FRAME_RATE, 399 | attack_frames = 0.5 * FRAME_RATE, 400 | decay_frames = 0.5 * FRAME_RATE 401 | )''' 402 | b_blob.show_mouth( 403 | start_time = 1, 404 | #end_time = 10 405 | ) 406 | 407 | r_blob.angry_eyes( 408 | start_time = 0 409 | ) 410 | 411 | o_blob.eat_animation( 412 | start_frame = 1 * FRAME_RATE, 413 | end_frame = 10 * FRAME_RATE, 414 | attack_frames = 0.5 * FRAME_RATE, 415 | decay_frames = 0.5 * FRAME_RATE 416 | ) 417 | o_blob.blob_scoop( 418 | start_time = 1, 419 | duration = 10 420 | ) 421 | 422 | food = import_object( 423 | 'goodicosphere', 'primitives', 424 | location = [-0.05, 0.25, 1], 425 | scale = 0.1, 426 | mat = 'color7' 427 | ) 428 | food.ref_obj.parent = o_blob.ref_obj 429 | food.add_to_blender(appear_time = 0) 430 | 431 | '''floor = import_object( 432 | 'xyplane', 'primitives', 433 | location = [0, 0, 0], 434 | name = 'ground' 435 | ) 436 | apply_material(floor.ref_obj.children[0], 'color2') 437 | floor.add_to_blender(appear_time = 0)''' 438 | -------------------------------------------------------------------------------- /blender_scripts/video_scenes/vn.py: -------------------------------------------------------------------------------- 1 | ''' 2 | When using draw_scenes.py to play this, clear should be set to false, and 3 | inner_ear.blend should be open. 4 | ''' 5 | 6 | import bpy 7 | import collections 8 | import math 9 | from copy import deepcopy 10 | #import imp 11 | 12 | from constants import * 13 | from helpers import * 14 | #import scene 15 | #imp.reload(scene) 16 | from scene import Scene 17 | import bobject 18 | import svg_bobject 19 | import tex_bobject 20 | import tex_complex 21 | import gesture 22 | 23 | class TextScene(Scene): 24 | def __init__(self): 25 | self.subscenes = collections.OrderedDict([ 26 | ('zoom', {'duration': 1000}), 27 | #('post_transplant', {'duration': 10}),\ 28 | ]) 29 | super().__init__() 30 | 31 | def play(self): 32 | super().play() 33 | #self.subscenes 34 | #self.duration 35 | #bpy.ops.wm.revert_mainfile() 36 | 37 | #These don't really need to be object methods ¯\_(ツ)_/¯ 38 | #self.intro_card() 39 | #self.outline() 40 | #self.inner_ear_intro() 41 | #self.asymmetrical_inputs() 42 | self.long_term_symptoms() 43 | #self.transition_card() 44 | #self.end_card() 45 | 46 | def intro_card(self): 47 | logo = svg_bobject.SVGBobject( 48 | "UCSF_logo_signature", 49 | #location = (-5, 3.75, 0), #Centered position 50 | #scale = 0.26, #Centered scale 51 | location = [-10.675, -6.3, 0], 52 | scale = 0.128, 53 | color = 'color2', 54 | centered = True 55 | ) 56 | baf = svg_bobject.SVGBobject( 57 | 'BaFC_Arial', 58 | location = [4.325, -5.2, 0], 59 | scale = 1.85, 60 | color = 'color2', 61 | centered = True 62 | ) 63 | vest = tex_bobject.TexBobject( 64 | '\\text{Vestibular Videos:}', 65 | location = [0, 4.5, 0], 66 | scale = 2, 67 | color = 'color2', 68 | centered = True, 69 | typeface = 'garamond' 70 | ) 71 | title = tex_bobject.TexBobject( 72 | '\\text{Vestibular Neuritis}', 73 | location = [0, 1.5, 0], 74 | scale = 3.14, 75 | color = 'color2', 76 | centered = True, 77 | typeface = 'garamond' 78 | ) 79 | vert = tex_bobject.TexBobject( 80 | '|', 81 | location = [-6.35, -4.74, 0], 82 | scale = [2, 5.32, 4], 83 | centered = True, 84 | color = 'color2', 85 | ) 86 | 87 | logo.add_to_blender(appear_time = -1, animate = False) 88 | baf.add_to_blender(appear_time = -1, animate = False) 89 | vest.add_to_blender(appear_time = -1, animate = False) 90 | title.add_to_blender(appear_time = -1, animate = False) 91 | vert.add_to_blender(appear_time = -1, animate = False) 92 | 93 | for bobj in [logo, baf, vest, vert]: 94 | for handle in bobj.ref_obj.children: 95 | print(handle.name) 96 | print(handle.children[0].name) 97 | #For some reason, some handles have extra children 98 | try: 99 | fade( 100 | object = handle.children[0], 101 | start_time = 0, 102 | duration_time = 1, 103 | fade_out = False 104 | ) 105 | except: 106 | pass 107 | for bobj in [title]: 108 | for handle in bobj.ref_obj.children: 109 | print(handle.name) 110 | print(handle.children[0].name) 111 | #For some reason, some handles have extra children 112 | try: 113 | fade( 114 | object = handle.children[0], 115 | start_time = 2, 116 | duration_time = 1, 117 | fade_out = False 118 | ) 119 | except: 120 | pass 121 | 122 | def outline(self): 123 | vn = tex_bobject.TexBobject( 124 | "\\text{Vestibular Neuritis}", 125 | location = [0, 0, 0], 126 | centered = True, 127 | typeface = 'arial', 128 | scale = 3 129 | ) 130 | vn.add_to_blender( 131 | appear_time = 0, 132 | subbobject_timing = [30] * 10 + [75] * 8 133 | ) 134 | vn.move_to( 135 | new_location = [0, 3.5, 0], 136 | start_time = 2.5 137 | ) 138 | 139 | acronym = tex_bobject.TexBobject( 140 | '\\bullet\\text{Vestibular system overview}', 141 | color = 'color2', 142 | typeface = 'arial' 143 | ) 144 | cause = tex_bobject.TexBobject( 145 | '\\bullet\\text{Symptoms}', 146 | color = 'color2', 147 | typeface = 'arial' 148 | ) 149 | treat = tex_bobject.TexBobject( 150 | '\\bullet\\text{Treatments}', 151 | color = 'color2', 152 | typeface = 'arial' 153 | ) 154 | contents = tex_complex.TexComplex( 155 | acronym, cause, treat, 156 | location = [-9, 0.5, 0], 157 | scale = 1.5, 158 | multiline = True 159 | ) 160 | contents.add_to_blender( 161 | appear_time = 4, 162 | subbobject_timing = [0, 35, 70] 163 | ) 164 | contents.disappear(disappear_time = 7) 165 | #vn.disappear(disappear_time = 7) 166 | 167 | vn.move_to( 168 | new_location = [0, 5.5, 0], 169 | start_time = 6.5 170 | ) 171 | 172 | itis = [] 173 | for i in range(14, 18): 174 | itis.append(vn.lookup_table[0][i]) 175 | for char in itis: 176 | char.color_shift( 177 | color = COLORS_SCALED[2], 178 | start_time = 8, 179 | duration_time = 2, 180 | ) 181 | 182 | neur = [] 183 | for i in range(10, 14): 184 | neur.append(vn.lookup_table[0][i]) 185 | for char in neur: 186 | char.color_shift( 187 | color = COLORS_SCALED[2], 188 | start_time = 10.5, 189 | duration_time = 2, 190 | ) 191 | 192 | vest = [] 193 | for i in range(0, 10): 194 | vest.append(vn.lookup_table[0][i]) 195 | for char in vest: 196 | char.color_shift( 197 | color = COLORS_SCALED[2], 198 | start_time = 13, 199 | duration_time = 2, 200 | ) 201 | 202 | vn.disappear(disappear_time = 16) 203 | 204 | def inner_ear_intro(self): 205 | cam_bobj, cam_swivel = cam_and_swivel( 206 | cam_location = [0, 0, 5], 207 | cam_rotation_euler = [0, 0, 0], 208 | cam_name = "Camera Bobject", 209 | swivel_location = [0, 0, 0.25], 210 | swivel_rotation_euler = [math.pi / 2, 0, 0], 211 | swivel_name = 'Cam swivel', 212 | #control_sun = True 213 | ) 214 | cam_swivel.add_to_blender(appear_time = -1) 215 | cam_bobj.ref_obj.children[0].data.clip_end = 200 216 | 217 | turn_green_time = 16.5 218 | 219 | zoom_out_time = 20.5 220 | 221 | turn_red_time = 22.5 222 | 223 | r_inner_ear = bpy.data.objects['inner ear_from microCT'] 224 | brain = bpy.data.objects['Brain'] 225 | v_nerve = bpy.data.objects['Vestibular nerve origin'] 226 | to_keep = [r_inner_ear] 227 | for obj in bpy.data.objects: 228 | if obj not in to_keep: 229 | obj.hide = True 230 | obj.hide_render = True 231 | 232 | slots = r_inner_ear.material_slots 233 | v_sys_mats = [ 234 | slots[1].material, 235 | slots[2].material, 236 | slots[3].material, 237 | slots[4].material 238 | ] 239 | coch_mat = slots[0].material 240 | 241 | for mat in v_sys_mats + [coch_mat]: 242 | print(mat) 243 | nodes = mat.node_tree.nodes 244 | mix = nodes['Mix Shader'] 245 | mix.inputs[0].default_value = 0 246 | princ = nodes['Principled BSDF'] 247 | color = princ.inputs[0] 248 | 249 | if mat != coch_mat: 250 | color.keyframe_insert( 251 | data_path = 'default_value', 252 | frame = turn_green_time * FRAME_RATE 253 | ) 254 | color.default_value = [0, 1, 0, 1] 255 | color.keyframe_insert( 256 | data_path = 'default_value', 257 | frame = turn_green_time * FRAME_RATE + OBJECT_APPEARANCE_TIME 258 | ) 259 | 260 | #Zoom out 261 | for thing in [brain, v_nerve]: 262 | thing.hide = True 263 | thing.hide_render = True 264 | thing.keyframe_insert( 265 | data_path = 'hide', 266 | frame = zoom_out_time * FRAME_RATE - 1 267 | ) 268 | thing.hide = False 269 | thing.hide_render = False 270 | thing.keyframe_insert( 271 | data_path = 'hide', 272 | frame = zoom_out_time * FRAME_RATE 273 | ) 274 | 275 | full_scale = list(thing.scale) 276 | thing.scale = [0, 0, 0] 277 | thing.keyframe_insert( 278 | data_path = 'scale', 279 | frame = zoom_out_time * FRAME_RATE - 1 280 | ) 281 | thing.scale = full_scale 282 | thing.keyframe_insert( 283 | data_path = 'scale', 284 | frame = zoom_out_time * FRAME_RATE 285 | ) 286 | 287 | nodes = thing.material_slots[0].material.node_tree.nodes 288 | mix = nodes['Mix Shader'].inputs[0] 289 | mix.default_value = 1 290 | mix.keyframe_insert( 291 | data_path = 'default_value', 292 | frame = (zoom_out_time + 1) * FRAME_RATE 293 | ) 294 | mix.default_value = 0 295 | mix.keyframe_insert( 296 | data_path = 'default_value', 297 | frame = (zoom_out_time + 1) * FRAME_RATE + 2 * OBJECT_APPEARANCE_TIME 298 | ) 299 | if thing == v_nerve: 300 | mix2 = nodes['Mix Shader.001'].inputs[0] 301 | mix2.default_value = 0 302 | mix2.keyframe_insert( 303 | data_path = 'default_value', 304 | frame = turn_red_time * FRAME_RATE 305 | ) 306 | mix2.default_value = 1 307 | mix2.keyframe_insert( 308 | data_path = 'default_value', 309 | frame = turn_red_time * FRAME_RATE + 2 * OBJECT_APPEARANCE_TIME 310 | ) 311 | 312 | em = nodes['Emission'] 313 | color = em.inputs[0] 314 | color.default_value = [1, 1, 1, 1] 315 | color.keyframe_insert( 316 | data_path = 'default_value', 317 | frame = turn_red_time * FRAME_RATE 318 | ) 319 | color.default_value = [1, 0, 0, 1] 320 | color.keyframe_insert( 321 | data_path = 'default_value', 322 | frame = turn_red_time * FRAME_RATE + 2 * OBJECT_APPEARANCE_TIME 323 | ) 324 | 325 | 326 | cam_swivel.move_to( 327 | new_location = [0, 5.1, 4.5], 328 | start_time = zoom_out_time, 329 | end_time = zoom_out_time + 2 * OBJECT_APPEARANCE_TIME / FRAME_RATE 330 | ) 331 | 332 | cam_swivel.move_to( 333 | new_angle = [math.pi / 2, 0, 70 * math.pi / 180], 334 | start_time = 14, 335 | end_time = 25 336 | ) 337 | '''cam_swivel.move_to( 338 | new_angle = [math.pi / 2, 0, 0], 339 | start_time = turn_red_time, 340 | end_time = turn_red_time + turn_red_time - turn_green_time 341 | )''' 342 | cam_bobj.move_to( 343 | new_location = [0, 0, 35], 344 | start_time = zoom_out_time, 345 | end_time = zoom_out_time + 2 * OBJECT_APPEARANCE_TIME / FRAME_RATE 346 | ) 347 | 348 | def asymmetrical_inputs(self): 349 | cam_bobj, cam_swivel = cam_and_swivel( 350 | cam_location = [0, 0, 100], 351 | cam_rotation_euler = [0, 0, 0], 352 | cam_name = "Camera Bobject", 353 | swivel_location = [0, 5.1, -2], 354 | swivel_rotation_euler = [75 * math.pi / 180, 0, 135 * math.pi / 180], 355 | swivel_name = 'Cam swivel', 356 | #control_sun = True 357 | ) 358 | cam_swivel.add_to_blender(appear_time = -1) 359 | cam_bobj.ref_obj.children[0].data.clip_end = 200 360 | 361 | rot_start = 78 362 | 363 | l_green_time = 80.5 364 | l_back_time = 85.5 365 | spin_time_1 = 83 366 | spins_1 = 5 367 | spin_duration_1 = 3 368 | 369 | r_red_time = 90.5 370 | r_back_time = 102.5 371 | spin_time_2 = 100 372 | spins_2 = 5 373 | spin_duration_2 = 3 374 | 375 | 376 | skin = bpy.data.objects['robertot'] 377 | r_inner_ear = bpy.data.objects['inner ear_from microCT'] 378 | l_inner_ear = bpy.data.objects['inner ear_from microCT.001'] 379 | to_keep = [skin, r_inner_ear, l_inner_ear] 380 | for obj in bpy.data.objects: 381 | if obj not in to_keep: 382 | obj.hide = True 383 | obj.hide_render = True 384 | 385 | mix = skin.material_slots[0].material.node_tree.nodes['Mix Shader'].inputs[0] 386 | mix.default_value = 0.9 387 | 388 | 389 | #Prep inner ear materials 390 | slots = r_inner_ear.material_slots 391 | v_sys_mats = [ 392 | slots[0].material, 393 | slots[1].material, 394 | slots[2].material, 395 | slots[3].material, 396 | slots[4].material 397 | ] 398 | #Set initial state for inner ear materials 399 | for mat in v_sys_mats: 400 | nodes = mat.node_tree.nodes 401 | mix = nodes['Mix Shader'] 402 | mix.inputs[0].default_value = 0 403 | #princ = nodes['Principled BSDF'] 404 | #color = princ.inputs[0] 405 | #color.default_value = [0, 1, 0, 1] 406 | 407 | #Make separate materials for left inner ear to animate separately 408 | for slot in l_inner_ear.material_slots: 409 | mat_copy = slot.material.copy() 410 | slot.material = mat_copy 411 | 412 | #Turn left inner ear green and back 413 | for slot in l_inner_ear.material_slots: 414 | nodes = slot.material.node_tree.nodes 415 | color = nodes['Principled BSDF'].inputs[0] 416 | initial_color = list(color.default_value) 417 | 418 | color.keyframe_insert( 419 | data_path = 'default_value', 420 | frame = l_green_time * FRAME_RATE 421 | ) 422 | color.default_value = [0, 1, 0, 1] 423 | color.keyframe_insert( 424 | data_path = 'default_value', 425 | frame = l_green_time * FRAME_RATE + 2 * OBJECT_APPEARANCE_TIME 426 | ) 427 | color.keyframe_insert( 428 | data_path = 'default_value', 429 | frame = l_back_time * FRAME_RATE 430 | ) 431 | color.default_value = initial_color 432 | color.keyframe_insert( 433 | data_path = 'default_value', 434 | frame = l_back_time * FRAME_RATE + 2 * OBJECT_APPEARANCE_TIME 435 | ) 436 | 437 | 438 | 439 | #Turn right inner ear red and back 440 | for mat in v_sys_mats: 441 | nodes = mat.node_tree.nodes 442 | color = nodes['Principled BSDF'].inputs[0] 443 | initial_color = list(color.default_value) 444 | 445 | color.keyframe_insert( 446 | data_path = 'default_value', 447 | frame = r_red_time * FRAME_RATE 448 | ) 449 | color.default_value = [1, 0, 0, 1] 450 | color.keyframe_insert( 451 | data_path = 'default_value', 452 | frame = r_red_time * FRAME_RATE + 2 * OBJECT_APPEARANCE_TIME 453 | ) 454 | color.keyframe_insert( 455 | data_path = 'default_value', 456 | frame = r_back_time * FRAME_RATE 457 | ) 458 | color.default_value = initial_color 459 | color.keyframe_insert( 460 | data_path = 'default_value', 461 | frame = r_back_time * FRAME_RATE + 2 * OBJECT_APPEARANCE_TIME 462 | ) 463 | 464 | #Spins 465 | skull = bpy.data.objects['Skull_Top'] 466 | skull_bobj = bobject.Bobject(objects = [skull]) 467 | skull_bobj.add_to_blender(appear_time = 0, unhide = False) 468 | skull_bobj.move_to( 469 | new_angle = [0, 0, spins_1 * 2 * math.pi], 470 | start_time = spin_time_1, 471 | end_time = spin_time_1 + spin_duration_1 472 | ) 473 | 474 | skull_bobj.move_to( 475 | new_angle = [ 476 | skull_bobj.ref_obj.rotation_euler[0], 477 | skull_bobj.ref_obj.rotation_euler[1], 478 | skull_bobj.ref_obj.rotation_euler[2] + spins_2 * 2 * math.pi, 479 | ], 480 | start_time = spin_time_2, 481 | end_time = spin_time_2 + spin_duration_2 482 | ) 483 | 484 | 485 | 486 | cam_swivel.move_to( 487 | new_angle = [75 * math.pi / 180, 0, 45 * math.pi / 180], 488 | start_time = rot_start, 489 | end_time = r_red_time 490 | ) 491 | cam_swivel.move_to( 492 | new_angle = [75 * math.pi / 180, 0, 135 * math.pi / 180], 493 | start_time = r_red_time, 494 | end_time = spin_time_2 + spin_duration_2 495 | ) 496 | 497 | '''rate = 0.025 498 | cam_swivel.spin( 499 | axis = 2, 500 | spin_rate = rate, 501 | start_time = zoom_out_time - 1 / rate * 0.125 502 | )''' 503 | 504 | def long_term_symptoms(self): 505 | lt = tex_bobject.TexBobject( 506 | "\\text{Long-term effects}", 507 | location = [-12.5, 5.5, 0], 508 | #centered = True, 509 | typeface = 'arial', 510 | scale = 3 511 | ) 512 | lt.add_to_blender(appear_time = 0) 513 | 514 | vnga = tex_bobject.TexBobject( 515 | "\\bullet\\text{Vertigo and nystagmus go away}", 516 | typeface = 'arial', 517 | ) 518 | rb = tex_bobject.TexBobject( 519 | "\\bullet\\text{Reduced balance}", 520 | typeface = 'arial', 521 | ) 522 | d = tex_bobject.TexBobject( 523 | "\\bullet\\text{Dizziness}", 524 | typeface = 'arial', 525 | ) 526 | bv = tex_bobject.TexBobject( 527 | "\\bullet\\text{Blurred vision}", 528 | typeface = 'arial', 529 | ) 530 | nhl = tex_bobject.TexBobject( 531 | "\\bullet\\text{Not hearing loss}", 532 | typeface = 'arial', 533 | ) 534 | n = [] 535 | for i in range(1, 4): 536 | n.append(nhl.lookup_table[0][i]) 537 | for char in n: 538 | char.color_shift( 539 | color = COLORS_SCALED[2], 540 | start_time = 4, 541 | duration_time = 200, 542 | ) 543 | 544 | 545 | contents = tex_complex.TexComplex( 546 | vnga, rb, d, bv, nhl, 547 | location = [-11.5, 2, 0], 548 | scale = 1.5, 549 | multiline = True 550 | ) 551 | contents.add_to_blender( 552 | appear_time = 1, 553 | subbobject_timing = [0, 35, 70, 105, 140] 554 | ) 555 | 556 | def transition_card(self): 557 | text = tex_bobject.TexBobject( 558 | #'\\text{The Cause of BPPV}', 559 | '\\text{Diagnosis and Treatment}', 560 | location = [0, 0, 0], 561 | scale = 2.5, 562 | color = 'color2', 563 | centered = True, 564 | typeface = 'arial' 565 | ) 566 | text.add_to_blender(appear_time = -1, animate = False) 567 | 568 | for bobj in [text]: 569 | for handle in bobj.ref_obj.children: 570 | print(handle.name) 571 | print(handle.children[0].name) 572 | #For some reason, some handles have extra children 573 | try: 574 | fade( 575 | object = handle.children[0], 576 | start_time = 0, 577 | duration_time = 1, 578 | fade_out = False 579 | ) 580 | except: 581 | pass 582 | 583 | def end_card(self): 584 | logo = svg_bobject.SVGBobject( 585 | "UCSF_logo", 586 | #location = (-5, 3.75, 0), #Centered position 587 | #scale = 0.26, #Centered scale 588 | location = [0, 0, 0], 589 | scale = 0.121, 590 | color = 'color2', 591 | #centered = True, 592 | ) 593 | baf = svg_bobject.SVGBobject( 594 | 'BaFC_Arial', 595 | location = [5.2257280349731445, -0.26257357001304626, 0.0], 596 | scale = 2.23, 597 | color = 'color2', 598 | #centered = True, 599 | ) 600 | logobaf = bobject.Bobject( 601 | logo, baf, 602 | location = [-11.57, 2.5, 0], 603 | #location = [0, 1.5, Z0], 604 | scale = 0.852, 605 | #centered = True 606 | ) 607 | logobaf.add_to_blender( 608 | appear_time = 0, 609 | animate = False, 610 | ) 611 | url = tex_bobject.TexBobject( 612 | '\\text{ohns.ucsf.edu/otology-neurotology/balance-and-falls}', 613 | location = [0, 0.8, 0], 614 | color = 'color2', 615 | name = 'url', 616 | typeface = 'arial', 617 | scale = 0.8, 618 | centered = True 619 | ) 620 | url.add_to_blender(appear_time = 0) 621 | 622 | mpb_loc = [1, -4.25, 0] 623 | mpb = tex_bobject.TexBobject( 624 | '\\text{Made possible by:}', 625 | location = mpb_loc, 626 | color = 'color2', 627 | name = 'mpb', 628 | typeface = 'arial' 629 | ) 630 | mzhf = tex_bobject.TexBobject( 631 | '\\text{Mount Zion Health Fund}', 632 | color = 'color2', 633 | scale = 1.2, 634 | location = [ 635 | mpb_loc[0] + 0.5, 636 | mpb_loc[1] - 1.4, 637 | mpb_loc[2] 638 | ], 639 | name = 'mzhf', 640 | typeface = 'arial' 641 | ) 642 | vpb_loc = [-13, -4.25, 0] 643 | vpb = tex_bobject.TexBobject( 644 | '\\text{Video produced by:}', 645 | color = 'color2', 646 | location = vpb_loc, 647 | name = 'vpb', 648 | typeface = 'arial' 649 | ) 650 | jh = tex_bobject.TexBobject( 651 | '\\text{Justin Helps}', 652 | location = [ 653 | vpb_loc[0] + 0.5, 654 | vpb_loc[1] - 1.4, 655 | vpb_loc[2] 656 | ], 657 | scale = 1.2, 658 | color = 'color2', 659 | name = 'jh', 660 | typeface = 'arial' 661 | ) 662 | jds = tex_bobject.TexBobject( 663 | '\\text{Jeffrey D. Sharon, MD}', 664 | location = [ 665 | vpb_loc[0] + 0.5, 666 | vpb_loc[1] - 2.8, 667 | vpb_loc[2] 668 | ], 669 | scale = 1.2, 670 | color = 'color2', 671 | name = 'jds', 672 | typeface = 'arial' 673 | ) 674 | 675 | for bobj in [mpb, mzhf, vpb, jh, jds]: 676 | bobj.add_to_blender( 677 | appear_time = 0, 678 | animate = False 679 | ) 680 | -------------------------------------------------------------------------------- /draw_molecules/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Helpsypoo/primerpython/9d895cc56f14c5d1f9d809c5d8a4c7f0258a76a3/draw_molecules/__init__.py -------------------------------------------------------------------------------- /draw_molecules/atoms.json: -------------------------------------------------------------------------------- 1 | { 2 | "Ac": {"color": [0.439216, 0.670588, 0.980392], "radius": 1.114285}, 3 | "Ag": {"color": [0.752941, 0.752941, 0.752941], "radius": 0.914285}, 4 | "Al": {"color": [0.74902, 0.65098, 0.65098], "radius": 0.714285}, 5 | "Am": {"color": [0.329412, 0.360784, 0.94902], "radius": 1.0}, 6 | "Ar": {"color": [0.501961, 0.819608, 0.890196], "radius": 0.4057145}, 7 | "As": {"color": [0.741176, 0.501961, 0.890196], "radius": 0.657145}, 8 | "Au": {"color": [1, 0.819608, 0.137255], "radius": 0.77143}, 9 | "B": {"color": [1, 0.709804, 0.709804], "radius": 0.4857145}, 10 | "Ba": {"color": [0, 0.788235, 0], "radius": 1.22857}, 11 | "Be": {"color": [0.760784, 1, 0], "radius": 0.6}, 12 | "Bi": {"color": [0.619608, 0.309804, 0.709804], "radius": 0.914285}, 13 | "Br": {"color": [0.65098, 0.160784, 0.160784], "radius": 0.657145}, 14 | "C": {"color": [0.564706, 0.564706, 0.564706], "radius": 0.4}, 15 | "Ca": {"color": [0.239216, 1, 0], "radius": 1.02857}, 16 | "Cd": {"color": [1, 0.85098, 0.560784], "radius": 0.885715}, 17 | "Ce": {"color": [1, 1, 0.780392], "radius": 1.057145}, 18 | "Cl": {"color": [0.121569, 0.941176, 0.121569], "radius": 0.57143}, 19 | "Co": {"color": [0.941176, 0.564706, 0.627451], "radius": 0.77143}, 20 | "Cr": {"color": [0.541176, 0.6, 0.780392], "radius": 0.8}, 21 | "Cs": {"color": [0.341176, 0.0901961, 0.560784], "radius": 1.485715}, 22 | "Cu": {"color": [0.784314, 0.501961, 0.2], "radius": 0.77143}, 23 | "Dy": {"color": [0.121569, 1, 0.780392], "radius": 1.0}, 24 | "Er": {"color": [0, 0.901961, 0.458824], "radius": 1.0}, 25 | "Eu": {"color": [0.380392, 1, 0.780392], "radius": 1.057145}, 26 | "F": {"color": [0.564706, 0.878431, 0.313725], "radius": 0.2857145}, 27 | "Fe": {"color": [0.878431, 0.4, 0.2], "radius": 0.8}, 28 | "Ga": {"color": [0.760784, 0.560784, 0.560784], "radius": 0.742855}, 29 | "Gd": {"color": [0.270588, 1, 0.780392], "radius": 1.02857}, 30 | "Ge": {"color": [0.4, 0.560784, 0.560784], "radius": 0.714285}, 31 | "H": {"color": [1, 1, 1], "radius": 0.142857}, 32 | "Hf": {"color": [0.301961, 0.760784, 1], "radius": 0.885715}, 33 | "Hg": {"color": [0.721569, 0.721569, 0.815686], "radius": 0.857145}, 34 | "Ho": {"color": [0, 1, 0.611765], "radius": 1.0}, 35 | "I": {"color": [0.580392, 0, 0.580392], "radius": 0.8}, 36 | "In": {"color": [0.65098, 0.458824, 0.45098], "radius": 0.885715}, 37 | "Ir": {"color": [0.0901961, 0.329412, 0.529412], "radius": 0.77143}, 38 | "K": {"color": [0.560784, 0.25098, 0.831373], "radius": 1.257145}, 39 | "La": {"color": [0.439216, 0.831373, 1], "radius": 1.114285}, 40 | "Li": {"color": [0.8, 0.501961, 1], "radius": 0.82857}, 41 | "Lu": {"color": [0, 0.670588, 0.141176], "radius": 1.0}, 42 | "Mg": {"color": [0.541176, 1, 0], "radius": 0.857145}, 43 | "Mn": {"color": [0.611765, 0.478431, 0.780392], "radius": 0.8}, 44 | "Mo": {"color": [0.329412, 0.709804, 0.709804], "radius": 0.82857}, 45 | "N": {"color": [0.188235, 0.313725, 0.972549], "radius": 0.3714285}, 46 | "Na": {"color": [0.670588, 0.360784, 0.94902], "radius": 1.02857}, 47 | "Nb": {"color": [0.45098, 0.760784, 0.788235], "radius": 0.82857}, 48 | "Nd": {"color": [0.780392, 1, 0.780392], "radius": 1.057145}, 49 | "Ni": {"color": [0.313725, 0.815686, 0.313725], "radius": 0.77143}, 50 | "Np": {"color": [0, 0.501961, 1], "radius": 1.0}, 51 | "O": {"color": [1, 0.0509804, 0.0509804], "radius": 0.342857}, 52 | "Os": {"color": [0.14902, 0.4, 0.588235], "radius": 0.742855}, 53 | "P": {"color": [1, 0.501961, 0], "radius": 0.57143}, 54 | "Pa": {"color": [0, 0.631373, 1], "radius": 1.02857}, 55 | "Pb": {"color": [0.341176, 0.34902, 0.380392], "radius": 1.02857}, 56 | "Pd": {"color": [0, 0.411765, 0.521569], "radius": 0.8}, 57 | "Pm": {"color": [0.639216, 1, 0.780392], "radius": 1.057145}, 58 | "Po": {"color": [0.670588, 0.360784, 0], "radius": 1.085715}, 59 | "Pr": {"color": [0.85098, 1, 0.780392], "radius": 1.057145}, 60 | "Pt": {"color": [0.815686, 0.815686, 0.878431], "radius": 0.77143}, 61 | "Pu": {"color": [0, 0.419608, 1], "radius": 1.0}, 62 | "Ra": {"color": [0, 0.490196, 0], "radius": 1.22857}, 63 | "Rb": {"color": [0.439216, 0.180392, 0.690196], "radius": 1.342855}, 64 | "Re": {"color": [0.14902, 0.490196, 0.670588], "radius": 0.77143}, 65 | "Rh": {"color": [0.0392157, 0.490196, 0.54902], "radius": 0.77143}, 66 | "Ru": {"color": [0.141176, 0.560784, 0.560784], "radius": 0.742855}, 67 | "S": {"color": [1, 1, 0.188235], "radius": 0.57143}, 68 | "Sb": {"color": [0.619608, 0.388235, 0.709804], "radius": 0.82857}, 69 | "Sc": {"color": [0.901961, 0.901961, 0.901961], "radius": 0.914285}, 70 | "Se": {"color": [1, 0.631373, 0], "radius": 0.657145}, 71 | "Si": {"color": [0.941176, 0.784314, 0.627451], "radius": 0.62857}, 72 | "Sm": {"color": [0.560784, 1, 0.780392], "radius": 1.057145}, 73 | "Sn": {"color": [0.4, 0.501961, 0.501961], "radius": 0.82857}, 74 | "Sr": {"color": [0, 1, 0], "radius": 1.142855}, 75 | "Ta": {"color": [0.301961, 0.65098, 1], "radius": 0.82857}, 76 | "Tb": {"color": [0.188235, 1, 0.780392], "radius": 1.0}, 77 | "Tc": {"color": [0.231373, 0.619608, 0.619608], "radius": 0.77143}, 78 | "Te": {"color": [0.831373, 0.478431, 0], "radius": 0.8}, 79 | "Th": {"color": [0, 0.729412, 1], "radius": 1.02857}, 80 | "Ti": {"color": [0.74902, 0.760784, 0.780392], "radius": 0.8}, 81 | "Tl": {"color": [0.65098, 0.329412, 0.301961], "radius": 1.085715}, 82 | "Tm": {"color": [0, 0.831373, 0.321569], "radius": 1.0}, 83 | "U": {"color": [0, 0.560784, 1], "radius": 1.0}, 84 | "V": {"color": [0.65098, 0.65098, 0.670588], "radius": 0.77143}, 85 | "W": {"color": [0.129412, 0.580392, 0.839216], "radius": 0.77143}, 86 | "Y": {"color": [0.580392, 1, 1], "radius": 1.02857}, 87 | "Yb": {"color": [0, 0.74902, 0.219608], "radius": 1.0}, 88 | "Zn": {"color": [0.490196, 0.501961, 0.690196], "radius": 0.77143}, 89 | "Zr": {"color": [0.580392, 0.878431, 0.878431], "radius": 0.885715}, 90 | "undefined": {"color": [0, 0, 0], "radius": 0.405}, 91 | "bond": {"color": [0.05, 0.05, 0.05], "radius": 0.103} 92 | } 93 | -------------------------------------------------------------------------------- /draw_molecules/draw.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | Loads a json molecule and draws atoms in Blender. 4 | 5 | Blender scripts are weird. Either run this inside of Blender or in a shell with 6 | blender foo.blend -P molecule_to_blender.py 7 | 8 | The script expects an input file named "molecule.json" and should be in the 9 | same directory as "atoms.json" 10 | 11 | Written by Patrick Fuller, patrickfuller@gmail.com, 28 Nov 12 12 | """ 13 | import bpy 14 | from math import acos 15 | from mathutils import Vector 16 | import json 17 | import os 18 | import sys 19 | 20 | # Atomic radii from wikipedia, scaled to Blender radii (C = 0.4 units) 21 | # http://en.wikipedia.org/wiki/Atomic_radii_of_the_elements_(data_page) 22 | # Atomic colors from cpk 23 | # http://jmol.sourceforge.net/jscolors/ 24 | PATH = os.path.dirname(os.path.realpath(__file__)) 25 | with open(os.path.join(PATH, 'atoms.json')) as in_file: 26 | atom_data = json.load(in_file) 27 | 28 | 29 | def draw_molecule(molecule, center=(0, 0, 0), show_bonds=True, join=False): 30 | """Draws a JSON-formatted molecule in Blender. 31 | 32 | This method uses a couple of tricks from [1] to improve rendering speed. 33 | In particular, it minimizes the amount of unique meshes and materials, 34 | and doesn't draw until all objects are initialized. 35 | 36 | [1] https://blenderartists.org/forum/showthread.php 37 | ?273149-Generating-a-large-number-of-mesh-primitives 38 | 39 | Args: 40 | molecule: The molecule to be drawn, as a python object following the 41 | JSON convention set in this project. 42 | center: (Optional, default (0, 0, 0)) Cartesian center of molecule. Use 43 | to draw multiple molecules in different locations. 44 | show_bonds: (Optional, default True) Draws a ball-and-stick model if 45 | True, and a space-filling model if False. 46 | join: (Optional, default True) Joins the molecule into a single object. 47 | Set to False if you want to individually manipulate atoms/bonds. 48 | Returns: 49 | If run in a blender context, will return a visual object of the 50 | molecule. 51 | """ 52 | shapes = [] 53 | 54 | # If using space-filling model, scale up atom size and remove bonds 55 | 56 | # Add atom primitive 57 | bpy.ops.object.select_all(action='DESELECT') 58 | bpy.ops.mesh.primitive_uv_sphere_add() 59 | sphere = bpy.context.object 60 | 61 | # Initialize bond material if it's going to be used. 62 | if show_bonds: 63 | bpy.data.materials.new(name='bond') 64 | bpy.data.materials['bond'].diffuse_color = atom_data['bond']['color'] 65 | bpy.data.materials['bond'].specular_intensity = 0.2 66 | bpy.ops.mesh.primitive_cylinder_add() 67 | cylinder = bpy.context.object 68 | cylinder.active_material = bpy.data.materials['bond'] 69 | 70 | for atom in molecule['atoms']: 71 | if atom['element'] not in atom_data: 72 | atom['element'] = 'undefined' 73 | 74 | if atom['element'] not in bpy.data.materials: 75 | key = atom['element'] 76 | bpy.data.materials.new(name=key) 77 | bpy.data.materials[key].diffuse_color = atom_data[key]['color'] 78 | bpy.data.materials[key].specular_intensity = 0.2 79 | 80 | atom_sphere = sphere.copy() 81 | atom_sphere.data = sphere.data.copy() 82 | atom_sphere.location = [l + c for l, c in 83 | zip(atom['location'], center)] 84 | scale = 1 if show_bonds else 2.5 85 | atom_sphere.dimensions = [atom_data[atom['element']]['radius'] * 86 | scale * 2] * 3 87 | atom_sphere.active_material = bpy.data.materials[atom['element']] 88 | bpy.context.scene.objects.link(atom_sphere) 89 | shapes.append(atom_sphere) 90 | 91 | for bond in (molecule['bonds'] if show_bonds else []): 92 | start = molecule['atoms'][bond['atoms'][0]]['location'] 93 | end = molecule['atoms'][bond['atoms'][1]]['location'] 94 | diff = [c2 - c1 for c2, c1 in zip(start, end)] 95 | cent = [(c2 + c1) / 2 for c2, c1 in zip(start, end)] 96 | mag = sum([(c2 - c1) ** 2 for c1, c2 in zip(start, end)]) ** 0.5 97 | 98 | v_axis = Vector(diff).normalized() 99 | v_obj = Vector((0, 0, 1)) 100 | v_rot = v_obj.cross(v_axis) 101 | 102 | # This check prevents gimbal lock (ie. weird behavior when v_axis is 103 | # close to (0, 0, 1)) 104 | if v_rot.length > 0.01: 105 | v_rot = v_rot.normalized() 106 | axis_angle = [acos(v_obj.dot(v_axis))] + list(v_rot) 107 | else: 108 | v_rot = Vector((1, 0, 0)) 109 | axis_angle = [0] * 4 110 | 111 | if bond['order'] not in range(1, 4): 112 | sys.stderr.write("Improper number of bonds! Defaulting to 1.\n") 113 | bond['order'] = 1 114 | 115 | if bond['order'] == 1: 116 | trans = [[0] * 3] 117 | elif bond['order'] == 2: 118 | trans = [[1.4 * atom_data['bond']['radius'] * x for x in v_rot], 119 | [-1.4 * atom_data['bond']['radius'] * x for x in v_rot]] 120 | elif bond['order'] == 3: 121 | trans = [[0] * 3, 122 | [2.2 * atom_data['bond']['radius'] * x for x in v_rot], 123 | [-2.2 * atom_data['bond']['radius'] * x for x in v_rot]] 124 | 125 | for i in range(bond['order']): 126 | bond_cylinder = cylinder.copy() 127 | bond_cylinder.data = cylinder.data.copy() 128 | bond_cylinder.dimensions = [atom_data['bond']['radius'] * scale * 129 | 2] * 2 + [mag] 130 | bond_cylinder.location = [c + scale * v for c, 131 | v in zip(cent, trans[i])] 132 | bond_cylinder.rotation_mode = 'AXIS_ANGLE' 133 | bond_cylinder.rotation_axis_angle = axis_angle 134 | bpy.context.scene.objects.link(bond_cylinder) 135 | shapes.append(bond_cylinder) 136 | 137 | # Remove primitive meshes 138 | bpy.ops.object.select_all(action='DESELECT') 139 | sphere.select = True 140 | if show_bonds: 141 | cylinder.select = True 142 | # If the starting cube is there, remove it 143 | if 'Cube' in bpy.data.objects.keys(): 144 | bpy.data.objects.get('Cube').select = True 145 | bpy.ops.object.delete() 146 | 147 | for shape in shapes: 148 | shape.select = True 149 | bpy.context.scene.objects.active = shapes[0] 150 | bpy.ops.object.shade_smooth() 151 | if join == 'True': 152 | bpy.ops.object.join() 153 | if join == 'colors': 154 | for mat in bpy.data.materials: 155 | for shape in bpy.data.objects: 156 | shape.select = False 157 | print(mat) 158 | if mat in list(shape.data.materials): 159 | shape.select = True 160 | bpy.context.scene.objects.active = shape 161 | bpy.ops.object.join() 162 | 163 | bpy.ops.object.origin_set(type='ORIGIN_GEOMETRY', center='MEDIAN') 164 | bpy.context.scene.update() 165 | 166 | 167 | if __name__ == '__main__': 168 | """Uses Blender's limited argv interface to pass args from main script.""" 169 | args = sys.argv[sys.argv.index('--') + 1:] 170 | show_bonds, join = True, True 171 | if '--space-filling' in args: 172 | show_bonds = False 173 | args.remove('--space-filling') 174 | if '--no-join' in args: 175 | join = False 176 | args.remove('--no-join') 177 | if '--join-colors' in args: 178 | join = 'colors' 179 | args.remove('--join-colors') 180 | 181 | try: 182 | with open(args[0]) as in_file: 183 | molecule = json.load(in_file) 184 | except IOError: 185 | molecule = json.loads(args[0]) 186 | 187 | draw_molecule(molecule, show_bonds=show_bonds, join=join) 188 | -------------------------------------------------------------------------------- /draw_molecules/parse.py: -------------------------------------------------------------------------------- 1 | """ 2 | Converts a pybel object to a simplified JSON format. Runs various checks to 3 | ensure that locations, hydrogens, and bonds are specified. 4 | """ 5 | import json 6 | from itertools import takewhile 7 | 8 | import pybel 9 | ob = pybel.ob 10 | 11 | def process(mol, hydrogens=True, generate_coords=True, 12 | infer_bonds=True, pretty=False): 13 | """Performs a number of standard edits, then outputs simplified JSON. 14 | 15 | Args: 16 | mol: Input molecule, as an instance of `pybel.Molecule`. 17 | hydrogens: (Optional, default True) Includes hydrogen atoms, and infers 18 | missing hydrogens if True. Removes if False. 19 | generate_coords: (Optional, default True). Generates atom 20 | coordinates. Default behavior generates if no coordinates found or 21 | if molecule is reasonably small. 22 | infer_bonds: (Optional, default True) Calculates bond locations and 23 | bond orders. Default behaviors infers if no bond information 24 | exists. 25 | pretty: (Optional, default False) Pretty-prints the JSON output. Use 26 | when learning the code or creating custom scripts. 27 | Returns: 28 | A JSON-formatted string representing the molecule. 29 | """ 30 | 31 | if hydrogens: 32 | mol.addh() 33 | else: 34 | mol.removeh() 35 | 36 | if generate_coords: 37 | if not mol.OBMol.HasNonZeroCoords() or len(mol.atoms) < 50: 38 | mol.make3D(steps=500) 39 | 40 | if infer_bonds: 41 | bonds = list(ob.OBMolBondIter(mol.OBMol)) 42 | if not bonds: 43 | mol.OBMol.ConnectTheDots() 44 | mol.OBMol.PerceiveBondOrders() 45 | 46 | mol.OBMol.Center() 47 | 48 | obj = _pybel_to_object(mol) 49 | if pretty: 50 | json_obj = json.dumps(obj, indent=4, sort_keys=True, cls=CustomEncoder) 51 | else: 52 | json_obj = json.dumps(obj, separators=(',', ':')) 53 | return json_obj 54 | 55 | 56 | def _pybel_to_object(molecule): 57 | """Converts a pybel molecule to json.""" 58 | table = pybel.ob.OBElementTable() 59 | atoms = [{'element': table.GetSymbol(atom.atomicnum), 60 | 'location': [float('{:.5f}'.format(c)) for c in atom.coords]} 61 | for atom in molecule.atoms] 62 | bonds = [{'atoms': [b.GetBeginAtom().GetIndex(), 63 | b.GetEndAtom().GetIndex()], 64 | 'order': b.GetBondOrder()} 65 | for b in ob.OBMolBondIter(molecule.OBMol)] 66 | return {'atoms': atoms, 'bonds': bonds} 67 | 68 | 69 | class CustomEncoder(json.JSONEncoder): 70 | """Some small edits to pretty-printed json output. 71 | 72 | * Float decimals are truncated to six digits 73 | * [x, y, z] vectors are displayed on one line 74 | * Converts numpy arrays to lists and defined objects to dictionaries 75 | * Atoms and bonds are on one line each (looks more like other formats) 76 | """ 77 | def default(self, obj): 78 | """Fired when an unserializable object is hit.""" 79 | if hasattr(obj, '__dict__'): 80 | return obj.__dict__.copy() 81 | else: 82 | raise TypeError(("Object of type %s with value of %s is not JSON " 83 | "serializable") % (type(obj), repr(obj))) 84 | 85 | def encode(self, obj): 86 | """Fired for every object.""" 87 | s = super(CustomEncoder, self).encode(obj) 88 | # If uncompressed, postprocess for formatting 89 | if len(s.splitlines()) > 1: 90 | s = self.postprocess(s) 91 | return s 92 | 93 | def postprocess(self, json_string): 94 | """Displays each atom and bond entry on its own line.""" 95 | is_compressing = False 96 | compressed = [] 97 | spaces = 0 98 | for row in json_string.split('\n'): 99 | if is_compressing: 100 | if row.strip() == '{': 101 | compressed.append(row.rstrip()) 102 | elif (len(row) > spaces and row[:spaces] == ' ' * spaces and 103 | row[spaces:].rstrip() in [']', '],']): 104 | compressed.append(row.rstrip()) 105 | is_compressing = False 106 | else: 107 | compressed[-1] += ' ' + row.strip() 108 | else: 109 | compressed.append(row.rstrip()) 110 | if any(a in row for a in ['atoms', 'bonds']): 111 | # Fix to handle issues that arise with empty lists 112 | if '[]' in row: 113 | continue 114 | spaces = sum(1 for _ in takewhile(str.isspace, row)) 115 | is_compressing = True 116 | return '\n'.join(compressed) 117 | -------------------------------------------------------------------------------- /draw_molecules/run.py: -------------------------------------------------------------------------------- 1 | """ 2 | Command-line interface for drawing chemicals. 3 | """ 4 | import argparse 5 | import os 6 | import shutil 7 | import subprocess 8 | import sys 9 | 10 | import pybel 11 | 12 | #from blender_chemicals.parse import process 13 | from parse import process 14 | 15 | def run(): 16 | """This method is run by typing `blender-chemicals` into a terminal.""" 17 | parser = argparse.ArgumentParser(description="Imports chemicals into " 18 | "Blender with Open Babel.") 19 | parser.add_argument('input', help="The file or smiles string to draw.") 20 | parser.add_argument('--format', type=str, default='auto', help="The " 21 | "chemical format of the input file. Defaults to " 22 | "'auto', which uses the file extension.") 23 | parser.add_argument('--convert-only', action='store_true', help="Converts " 24 | "the input into a simplified JSON format and prints " 25 | "to stdout. Does not draw.") 26 | parser.add_argument('--space-filling', action='store_true', help="Draws " 27 | "a space-filling (instead of ball-and-stick) " 28 | "representation.") 29 | parser.add_argument('--no-join', dest='join', action='store_false', 30 | help="Skips joining the atoms/bonds into a single " 31 | "mesh. Use if you want to individually edit atoms in " 32 | "Blender, but note it will impair performance.") 33 | parser.add_argument('--join-colors', dest='join', action='store_const', 34 | const = 'colors', 35 | help="For each material (color), joins all objects " 36 | "with that material. Cuts down on the number of " 37 | "particle systems needed when morphing.") 38 | parser.add_argument('--no-hydrogens', dest='hydrogens', 39 | action='store_false', help="Avoids drawing hydrogens.") 40 | parser.add_argument('--no-generate-coords', dest='generate_coords', 41 | action='store_false', help="Skips generating 3D " 42 | "coordinates.") 43 | parser.add_argument('--no-infer-bonds', dest='infer_bonds', 44 | action='store_false', help="Skips inferring bonds.") 45 | 46 | args = parser.parse_args() 47 | 48 | try: 49 | with open(args.input) as in_file: 50 | data = in_file.read() 51 | is_file = True 52 | except IOError: 53 | data = args.input 54 | is_file = False 55 | 56 | if args.format == 'auto': 57 | chemformat = os.path.splitext(args.input)[1][1:] if is_file else 'smi' 58 | else: 59 | chemformat = args.format 60 | 61 | if not pybel.informats: 62 | sys.stderr.write("Open babel not properly installed. Exiting.\n") 63 | sys.exit() 64 | if chemformat not in pybel.informats: 65 | prefix = "Inferred" if args.format == 'auto' else "Supplied" 66 | formats = ', '.join(pybel.informats.keys()) 67 | sys.stderr.write(("{} format '{}' not in available open babel formats." 68 | "\n\nSupported formats:\n{}\n" 69 | ).format(prefix, chemformat, formats)) 70 | sys.exit() 71 | 72 | try: 73 | mol = pybel.readstring(chemformat, data) 74 | except OSError: 75 | prefix = "Inferred" if args.format == 'auto' else "Supplied" 76 | debug = ((" - Read input as file." if is_file else 77 | " - Inferred input as string, not file.") + 78 | "\n - {} format of '{}'.".format(prefix, chemformat)) 79 | sys.stderr.write("Could not read molecule.\n\nDebug:\n" + debug + "\n") 80 | sys.exit() 81 | 82 | json_mol = process(mol, args.hydrogens, args.generate_coords, 83 | args.infer_bonds, args.convert_only) 84 | #print(json_mol) 85 | if args.convert_only: 86 | print(json_mol) 87 | sys.exit() 88 | 89 | mac_path = '/Applications/blender.app/Contents/MacOS/./blender' 90 | if shutil.which('blender') is not None: 91 | blender = 'blender' 92 | elif os.path.isfile(mac_path): 93 | blender = mac_path 94 | else: 95 | sys.stderr.write("Could not find installed copy of Blender. Either " 96 | "make sure it's on your path or copy the contents of " 97 | "`draw.py` into a running blender instance.\n") 98 | sys.exit() 99 | 100 | root = os.path.normpath(os.path.dirname(__file__)) 101 | script = os.path.join(root, 'draw.py') 102 | command = [blender, '--python', script, '--', json_mol] 103 | if args.space_filling: 104 | command.append('--space-filling') 105 | if args.join == False: 106 | command.append('--no-join') 107 | if args.join == 'colors': 108 | command.append('--join-colors') 109 | #print(command) 110 | 111 | if len(command) > 255: 112 | pass 113 | 114 | #command = ['//?/'] + command 115 | print() 116 | print() 117 | print() 118 | print() 119 | #print(command) 120 | 121 | print(len(command)) 122 | 123 | #with open(os.devnull, 'w') as null: 124 | subprocess.Popen(command) 125 | #, stdout=null, stderr=null) 126 | 127 | 128 | if __name__ == '__main__': 129 | run() 130 | 131 | 132 | #C1=CN(C(=O)N=C1N)C2C(C(C(O2)COP(=O)(O)O)OP(=O)(O)OCC3C(C(C(O3)N4C=NC5=C4N=C(NC5=O)N)O)OP(=O)(O)OCC6C(C(C(O6)N7C=NC8=C7N=C(NC8=O)N)O)OP(=O)(O)OCC9C(C(C(O9)N1C=CC(=NC1=O)N)O)OP(=O)(O)OCC1C(C(C(O1)N1C=CC(=O)NC1=O)O)OP(=O)(O)OCC1C(C(C(O1)N1C=CC(=NC1=O)N)O)OP(=O)(O)OCC1C(C(C(O1)N1C=NC2=C1N=CN=C2N)O)OP(=O)(O)OCC1C(C(C(O1)N1C=NC2=C1N=CN=C2N)O)O)O 133 | -------------------------------------------------------------------------------- /files/template.tex: -------------------------------------------------------------------------------- 1 | \documentclass[preview]{standalone} 2 | 3 | \usepackage[english]{babel} 4 | \usepackage[utf8x]{inputenc} 5 | \usepackage{amsmath} 6 | \usepackage{amssymb} 7 | \usepackage{dsfont} 8 | \usepackage{setspace} 9 | \usepackage{tipa} 10 | \usepackage{relsize} 11 | \usepackage{mathrsfs} 12 | \usepackage{calligra} 13 | \usepackage{wasysym} 14 | \usepackage{cancel} 15 | %\usepackage[UTF8]{ctex} %This breaks non-default fonts. It's chinese! 16 | 17 | \usepackage[math]{kurier} 18 | \usepackage[T1]{fontenc} 19 | 20 | %\usepackage{tgchorus} 21 | %\usepackage[T1]{fontenc} 22 | 23 | \usepackage{nicefrac} 24 | %\usepackage{graphicx} 25 | %\newcommand\bsfrac[2]{% 26 | %\scalebox{-1}[1]{\nicefrac{\scalebox{-1}[1]{$#1$}}{\scalebox{-1}[1]{$#2$}}}% 27 | %} 28 | 29 | %\renewcommand{\rmdefault}{phv} % Arial. A bit improper to set the roman default to a sans serif font, but I'm a renegade. 30 | 31 | %\usepackage[T1]{fontenc} 32 | %\usepackage[sfdefault,scaled=.85]{FiraSans} 33 | %\usepackage{newtxsf} 34 | 35 | %\usepackage[sfdefault]{roboto} %% Option 'sfdefault' only if the base font of the document is to be sans serif 36 | %\usepackage[T1]{fontenc} 37 | 38 | %\usepackage[T1]{fontenc} 39 | %\usepackage{baskervillef} 40 | %\usepackage[varqu,varl,var0]{inconsolata} 41 | %\usepackage[scale=.95,type1]{cabin} 42 | %\usepackage[baskerville,vvarbb]{newtxmath} 43 | %\usepackage[cal=boondoxo]{mathalfa} 44 | 45 | \begin{document} 46 | \centering 47 | 48 | \begin{align*} 49 | YourTextHere 50 | \end{align*} 51 | 52 | \end{document} 53 | -------------------------------------------------------------------------------- /files/template_arial.tex: -------------------------------------------------------------------------------- 1 | \documentclass[preview]{standalone} 2 | 3 | \usepackage[english]{babel} 4 | \usepackage[utf8x]{inputenc} 5 | \usepackage{amsmath} 6 | \usepackage{amssymb} 7 | \usepackage{dsfont} 8 | \usepackage{setspace} 9 | \usepackage{tipa} 10 | \usepackage{relsize} 11 | \usepackage{mathrsfs} 12 | \usepackage{calligra} 13 | \usepackage{wasysym} 14 | \usepackage{cancel} 15 | %\usepackage[UTF8]{ctex} %This breaks non-default fonts. It's chinese! 16 | 17 | %\usepackage[math]{kurier} 18 | %\usepackage[T1]{fontenc} 19 | 20 | \renewcommand{\rmdefault}{phv} % Arial. A bit improper to set the roman default to a sans serif font, but I'm a renegade. 21 | 22 | %\usepackage[T1]{fontenc} 23 | %\usepackage[sfdefault,scaled=.85]{FiraSans} 24 | %\usepackage{newtxsf} 25 | 26 | %\usepackage[sfdefault]{roboto} %% Option 'sfdefault' only if the base font of the document is to be sans serif 27 | %\usepackage[T1]{fontenc} 28 | 29 | %\usepackage[T1]{fontenc} 30 | %\usepackage{baskervillef} 31 | %\usepackage[varqu,varl,var0]{inconsolata} 32 | %\usepackage[scale=.95,type1]{cabin} 33 | %\usepackage[baskerville,vvarbb]{newtxmath} 34 | %\usepackage[cal=boondoxo]{mathalfa} 35 | 36 | \begin{document} 37 | \centering 38 | 39 | \begin{align*} 40 | YourTextHere 41 | \end{align*} 42 | 43 | \end{document} 44 | -------------------------------------------------------------------------------- /files/template_garamond.tex: -------------------------------------------------------------------------------- 1 | \documentclass[preview]{standalone} 2 | 3 | \usepackage[english]{babel} 4 | \usepackage[utf8x]{inputenc} 5 | \usepackage{amsmath} 6 | \usepackage{amssymb} 7 | \usepackage{dsfont} 8 | \usepackage{setspace} 9 | \usepackage{tipa} 10 | \usepackage{relsize} 11 | \usepackage{mathrsfs} 12 | \usepackage{calligra} 13 | \usepackage{wasysym} 14 | \usepackage{cancel} 15 | %\usepackage[UTF8]{ctex} %This breaks non-default fonts. It's chinese! 16 | 17 | %\usepackage[math]{kurier} 18 | %\usepackage[T1]{fontenc} 19 | 20 | \usepackage[cmintegrals,cmbraces]{newtxmath} 21 | \usepackage{ebgaramond-maths} 22 | \usepackage[T1]{fontenc} 23 | 24 | %\renewcommand{\rmdefault}{phv} % Arial. A bit improper to set the roman default to a sans serif font, but I'm a renegade. 25 | 26 | %\usepackage[T1]{fontenc} 27 | %\usepackage[sfdefault,scaled=.85]{FiraSans} 28 | %\usepackage{newtxsf} 29 | 30 | %\usepackage[sfdefault]{roboto} %% Option 'sfdefault' only if the base font of the document is to be sans serif 31 | %\usepackage[T1]{fontenc} 32 | 33 | %\usepackage[T1]{fontenc} 34 | %\usepackage{baskervillef} 35 | %\usepackage[varqu,varl,var0]{inconsolata} 36 | %\usepackage[scale=.95,type1]{cabin} 37 | %\usepackage[baskerville,vvarbb]{newtxmath} 38 | %\usepackage[cal=boondoxo]{mathalfa} 39 | 40 | \begin{document} 41 | \centering 42 | 43 | \begin{align*} 44 | YourTextHere 45 | \end{align*} 46 | 47 | \end{document} 48 | --------------------------------------------------------------------------------