glitch3d 0.5.0.0 → 0.5.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -1
  3. data/README.md +21 -9
  4. data/bin/glitch3d +1 -1
  5. data/fixtures/base.blend +0 -0
  6. data/fixtures/{osl-shaders → osl_shaders}/glass.osl +0 -0
  7. data/fixtures/{osl-shaders → osl_shaders}/gold.osl +0 -0
  8. data/fixtures/{osl-shaders → osl_shaders}/green_marble.osl +0 -0
  9. data/fixtures/{osl-shaders → osl_shaders}/turbulent.osl +0 -0
  10. data/fixtures/{osl-shaders → osl_shaders}/veined_marble.osl +0 -0
  11. data/fixtures/{text → texts}/strings.txt +0 -0
  12. data/lib/glitch3d.rb +75 -22
  13. data/lib/glitch3d/bpy/canvas/aether.py +22 -58
  14. data/lib/glitch3d/bpy/canvas/dreamatorium.py +3 -9
  15. data/lib/glitch3d/bpy/canvas/empty.py +8 -0
  16. data/lib/glitch3d/bpy/canvas/fernandez.py +36 -10
  17. data/lib/glitch3d/bpy/canvas/lyfe.py +1 -3
  18. data/lib/glitch3d/bpy/canvas/metaballs.py +11 -7
  19. data/lib/glitch3d/bpy/canvas/sphere.py +23 -38
  20. data/lib/glitch3d/bpy/canvas/waves.py +15 -41
  21. data/lib/glitch3d/bpy/helpers.py +70 -40
  22. data/lib/glitch3d/bpy/main.py +219 -193
  23. data/lib/glitch3d/bpy/post-processing/average.py +8 -10
  24. data/lib/glitch3d/bpy/post-processing/mosaic.py +1 -1
  25. data/lib/glitch3d/bpy/post-processing/optimize.py +3 -6
  26. data/lib/glitch3d/bpy/post-processing/palette.py +3 -4
  27. data/lib/glitch3d/bpy/render_settings.py +35 -23
  28. data/lib/glitch3d/objects/vertex.rb +1 -1
  29. data/lib/glitch3d/strategies/default.rb +10 -8
  30. data/lib/glitch3d/strategies/duplication.rb +14 -12
  31. data/lib/glitch3d/strategies/find_and_replace.rb +19 -16
  32. data/lib/glitch3d/strategies/localized.rb +28 -18
  33. data/lib/glitch3d/strategies/none.rb +7 -5
  34. data/lib/glitch3d/version.rb +1 -1
  35. metadata +9 -9
  36. data/lib/glitch3d/bpy/canvas/particles.py +0 -33
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: a6891f01ce5cfa2a1f32042d4c8e8fd04d976b38
4
- data.tar.gz: b33d3ef8d3a296835f1ee5807544f75537d06181
3
+ metadata.gz: e35a975d66a59b0803f10f9b4adff8a34cec401b
4
+ data.tar.gz: d408635bb3bcd718723929f54d2c743ef03275f1
5
5
  SHA512:
6
- metadata.gz: 458c39cad088dcba2af7d28d82194aecf6fdfc3f89a05ce37b32a4e0250565bffb81611778925e0c81ce87098e84850cc0d2f0bae87bacb0f317b741172b59c9
7
- data.tar.gz: 657088d23f8bb9dca698025a492f30cedf77cb58eb174296298e2439399c3ef26be43cf052e0b90b0306e3594ae9ee9e497809013582f64dec1ef0db14054715
6
+ metadata.gz: 75cf5cfa3091803d1f8706f5670fc947a7ec645297e24477685e8fab019683061df340dd2dc42ba423a019b27d928b4f2272b23ed47c46c01e5364c70d4b5475
7
+ data.tar.gz: d99b789dbf4829ff3993b24979360942a22b1a4070615af8e46d9d8644d6d0c4d23baa7d54392f56b9823b6fbaf988ea8ecb60f00283101b850e056e570e4a6d
data/.gitignore CHANGED
@@ -20,4 +20,4 @@
20
20
  *.png
21
21
  lib/glitch3d/bpy/canvas/__pycache__/
22
22
  lib/glitch3d/bpy/__pycache__/
23
- fixtures/osl-shaders/*.oso
23
+ fixtures/osl_shaders/*.oso
data/README.md CHANGED
@@ -10,6 +10,13 @@ This gem uses the Blender Python API to produces renders headlessly and leverage
10
10
 
11
11
  Cycles rendering engine does not support GLSL shaders so the shader library is using a node-based system but could be extended to serialize materials in the Open Shading Language.
12
12
 
13
+ ## Overview of the process
14
+
15
+ 1) the subject model (main argument of the cli call) is altered by one of the available 'glitching' strategies
16
+ 2) the base Blender scene is loaded and the subject is loaded and ajusted
17
+ 3) random canvas objects are picked up and drawn across the scene
18
+ 4) actual render begins and the scene is animated in each frame
19
+
13
20
  ## :warning: Warning
14
21
 
15
22
  Setting `BLENDER_EXECUTABLE_PATH` in your environment is required. In general this gem relies on the presence of Python and Blender on the host machine. I am very aware this is not standard practice and plan to split components later down the road but this proves convenient for now.
@@ -35,11 +42,15 @@ Or install it yourself as:
35
42
  - `glitch3d file.obj`
36
43
 
37
44
  CLI Options:
38
- - `mode` : (localized|default|none) => glitching strategy
39
- - `shots-number` : integer representing the number of - images desired (with animate: false)
40
- - `quality` : (high: 2000 x 2000|low 200 x 200) default: low => size of the render
41
- - `animate` : (true) default: false => Render .avi file
42
- - `frames` : (default: 200) => number of frames for simulation
45
+ - `mode`: (localized|default|none) default: randomized => glitching strategy (how the subject model is altered)
46
+ - `shots-number`: default: 4 => the number of images desired (only taken into account if `animate` is false)
47
+ - `quality`: (high|low) default: low => size of the render (low is 200x200 pixels and high is 2000x2000 pixels)
48
+ - `animate`: (true|false) default: false => enables render of .avi file
49
+ - `frames`: default: 200 => number of frames for the simulation
50
+ - `assets`: default: nil => URI to an asset folder that has the following subfolders: `/osl_shaders` `/fonts` `/texts` `/textures` `/height_maps` `/models`
51
+ - `debug`: (true|false) default: false => allows to re-raise errors from canvas specific code (otherwise the program just exits with status code 1)
52
+ - `webhook`: default: nil => url of endpoint to post potential error data to (useful for debugging)
53
+ - `seed`: default: rand(1000) => randomness seed that you can pass to the generator to reproduce a specific render result (useful for debugging and refining a canvas result)
43
54
 
44
55
  Renders (wether it is video or an image) will be output to `./renders` along with an export of the `.scene` file so that you can potentially fiddle with the resulting scene setup and adjust lights for instance.
45
56
 
@@ -61,10 +72,11 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
61
72
 
62
73
  ## Roadmap
63
74
 
64
- - extract fixtures management from gem
65
- - allow fixtures to be scraped from online resources such as [Thingiverse](https://www.thingiverse.com/)
66
- - use Blender Compositor feature to streamline post-processing
67
- - use realtime Eevee engine to visualize intermediate renders
75
+ - [x] extract fixtures management from gem
76
+ - [x] allow fixtures to be scraped from online resources such as [Thingiverse](https://www.thingiverse.com/)
77
+ - [ ] use Blender Compositor feature to streamline post-processing
78
+ - [ ] use realtime Eevee engine to visualize intermediate renders
79
+ - [ ] support more 3D file formats (fbx, etc)
68
80
 
69
81
  ## Contributing
70
82
 
@@ -10,7 +10,7 @@ args = Hash[ARGV.join(' ').scan(/--?([^=\s]+)(?:=(\S+))?/)] if args.nil?
10
10
  args.default = false
11
11
 
12
12
  if ARGV[0] && !ARGV[0].start_with?('--')
13
- source_file = ARGV[0]
13
+ source_file = ARGV[0]
14
14
  else
15
15
  source_file = nil
16
16
  end
Binary file
File without changes
@@ -6,17 +6,37 @@ require 'glitch3d/objects/face'
6
6
  Dir[File.dirname(__FILE__) + '/glitch3d/strategies/*.rb'].each { |file| require file }
7
7
 
8
8
  module Glitch3d
9
- VERTEX_GLITCH_ITERATION_RATIO = 0.1
10
- VERTEX_GLITCH_OFFSET = 1
9
+ class ProcessError < StandardError; end
11
10
 
12
- FACE_GLITCH_ITERATION_RATIO = 0.1
13
- FACE_GLITCH_OFFSET = 0.5
14
- BOUNDARY_LIMIT = 3 # Contain model within BOUNDARY_LIMITxBOUNDARY_LIMITxBOUNDARY_LIMIT cube
15
- CHUNK_SIZE=20
11
+ VERTEX_GLITCH_ITERATION_RATIO = 0.0001.freeze
12
+ VERTEX_GLITCH_OFFSET = 1.freeze
13
+
14
+ FACE_GLITCH_ITERATION_RATIO = 0.0001.freeze
15
+ FACE_GLITCH_OFFSET = 0.5.freeze
16
+ BOUNDARY_LIMIT = 3.freeze # Contain model within BOUNDARY_LIMITxBOUNDARY_LIMITxBOUNDARY_LIMIT cube
17
+ CHUNK_SIZE = 20.freeze
18
+ DEFAULT_SHOTS_NUMBER = 4.freeze
19
+ STRATEGIES = [
20
+ Glitch3d::Default,
21
+ Glitch3d::Duplication,
22
+ Glitch3d::FindAndReplace,
23
+ Glitch3d::Localized,
24
+ Glitch3d::None
25
+ ].freeze
16
26
 
17
27
  BLENDER_EXECUTABLE_PATH = ENV['BLENDER_EXECUTABLE_PATH'].freeze
18
28
  RENDERING_SCRIPT_PATH = File.dirname(__FILE__) + '/glitch3d/bpy/main.py'
19
29
  BASE_BLEND_FILE_PATH = File.dirname(__FILE__) + '/../fixtures/base.blend'
30
+ BENCHMARK_ARGS = {
31
+ 'quality' => 'high',
32
+ 'normals' => false,
33
+ 'width' => 1000.to_s,
34
+ 'height' => 1000.to_s,
35
+ 'debug' => 'false',
36
+ 'canvas' => 'empty',
37
+ 'animate' => 'false',
38
+ 'post-process' => 'false'
39
+ }
20
40
 
21
41
  ASCII_TITLE = "
22
42
  ██████╗ ██╗ ██╗████████╗ ██████╗██╗ ██╗██████╗ ██████╗
@@ -34,25 +54,53 @@ module Glitch3d
34
54
  create_glitched_file(glitch(read_source(source_file)), target_file, model_name)
35
55
  end
36
56
 
57
+ def benchmark_strategies(source_file, base_file_name, model_name)
58
+ require 'benchmark'
59
+ Benchmark.bm do |x|
60
+ STRATEGIES.each do |s|
61
+ new_model_name = model_name + "_#{s.to_s.downcase.gsub(/::/, '_')}"
62
+ target_file = new_model_name + '_glitched.obj'
63
+ x.report s do
64
+ create_glitched_file(
65
+ glitch(read_source(source_file), s),
66
+ target_file,
67
+ new_model_name
68
+ )
69
+ end
70
+ end
71
+ end
72
+ STRATEGIES.each do |s|
73
+ new_model_name = model_name + "_#{s.to_s.downcase.gsub(/::/, '_')}"
74
+ target_file = new_model_name + '_glitched.obj'
75
+ render(BENCHMARK_ARGS, target_file, 1)
76
+ end
77
+ end
78
+
37
79
  # @param String source_file, 3d model file to take as input
38
80
  # @param Hash args, parameters { 'stuff' => 'shit' }
39
81
  def process_model(source_file, args)
40
82
  raise 'Make sure Blender is correctly installed' if BLENDER_EXECUTABLE_PATH.nil?
83
+ @seed = args['seed'] ? args['seed'].to_i : rand(1000)
84
+ srand @seed
85
+ puts "Random seed: #{@seed}"
41
86
  puts ASCII_TITLE
42
87
  puts Glitch3d::VERSION
43
88
  return clean_model(source_file) if args['clean']
44
89
  source_file = random_fixture if source_file.nil?
45
90
  print_version if args.has_key?('version')
46
91
  raise 'Set Blender executable path in your env variables before using glitch3d' if BLENDER_EXECUTABLE_PATH.nil?
47
- self.class.include infer_strategy(args['mode'])
48
- @quality = args['quality'] || 'low'
49
92
  source_file = source_file
50
93
  base_file_name = source_file&.gsub(/.obj/, '')
51
94
  model_name = File.basename(source_file, '.obj')
52
95
  target_file = base_file_name + '_glitched.obj'
53
96
  puts "Target ~> #{target_file}"
54
- create_glitched_file(glitch(read_source(source_file)), target_file, model_name)
55
- render(args, target_file, args['shots-number'] || 6) unless args['no-render']
97
+ return benchmark_strategies(source_file, base_file_name, model_name) if args['benchmark']
98
+ create_glitched_file(
99
+ glitch(read_source(source_file), infer_strategy(args['mode'])),
100
+ target_file,
101
+ model_name
102
+ )
103
+ render(args, target_file, args['shots-number'] || DEFAULT_SHOTS_NUMBER) unless args['no-render']
56
104
  end
57
105
 
58
106
  # Print version number
@@ -78,7 +126,7 @@ module Glitch3d
78
126
  # @return [Glitch3d::Strategy]
79
127
  def infer_strategy(mode)
80
128
  if !mode
81
- mode_chosen = [ Glitch3d::Default, Glitch3d::Duplication, Glitch3d::FindAndReplace, Glitch3d::Localized, Glitch3d::None].sample
129
+ mode_chosen = STRATEGIES.sample
82
130
  puts "Strategy defaulting to #{mode_chosen}"
83
131
  return mode_chosen
84
132
  end
@@ -133,20 +181,15 @@ module Glitch3d
133
181
  faces_list
134
182
  end
135
183
 
136
- def glitch(file_hash_content)
184
+ def glitch(file_hash_content, strategy)
137
185
  {
138
- vertices: alter_vertices(file_hash_content[:vertices]),
139
- faces: alter_faces(file_hash_content[:faces], file_hash_content[:vertices])
186
+ vertices: strategy.public_send(:alter_vertices, file_hash_content[:vertices]),
187
+ faces: strategy.public_send(:alter_faces, file_hash_content[:faces], file_hash_content[:vertices])
140
188
  }
141
189
  end
142
190
 
143
- def random_element(array)
144
- array[rand(0..array.size - 1)]
145
- end
146
-
147
191
  def create_glitched_file(content_hash, target_file, model_name)
148
192
  boundaries = Vertex.boundaries(content_hash[:vertices])
149
- puts boundaries.to_s
150
193
  while rescale_needed?(boundaries)
151
194
  content_hash[:vertices] = Vertex.rescale(content_hash[:vertices], (boundaries.flatten.map(&:abs).max.abs - BOUNDARY_LIMIT).abs)
152
195
  boundaries = Vertex.boundaries(content_hash[:vertices])
@@ -182,7 +225,7 @@ module Glitch3d
182
225
  '-n',
183
226
  shots_number.to_s,
184
227
  '-m',
185
- @quality,
228
+ initial_args['quality'] || 'low',
186
229
  '-p',
187
230
  File.dirname(__FILE__).to_s,
188
231
  '-a',
@@ -198,8 +241,18 @@ module Glitch3d
198
241
  '-eight',
199
242
  initial_args['height'] || 2000.to_s,
200
243
  '-canvas',
201
- initial_args['canvas'] || nil.to_s
244
+ initial_args['canvas'] || nil.to_s,
245
+ '-assets',
246
+ initial_args['assets'] || nil.to_s,
247
+ '--post-process',
248
+ initial_args['post-process'].to_s.capitalize,
249
+ '--webhook',
250
+ initial_args['webhook'] || nil.to_s,
251
+ '--seed',
252
+ @seed.to_s,
253
+ '--python-exit-code',
254
+ 1.to_s
202
255
  ]
203
- system(*args)
256
+ raise(ProcessError, "bpy run failed, enable --debug=true for Python debugging") unless system(*args)
204
257
  end
205
258
  end
@@ -5,66 +5,30 @@ import helpers
5
5
 
6
6
  class Aether(canvas.Canvas):
7
7
  def render(self):
8
- ######################
9
- ## FLUID SIMULATION ##
10
- ######################
11
- RADIUS=20
12
-
13
- bpy.ops.mesh.primitive_cube_add(location=(0.0, 0.0, 17),radius=RADIUS)
14
- container = bpy.context.object
15
- container.name = 'fluid_container'
16
- container.modifiers.new(name='container', type='FLUID_SIMULATION')
17
- container.modifiers.new(name='smooth_container', type='SMOOTH')
18
- container.modifiers['container'].settings.type = 'DOMAIN'
19
- container.modifiers['container'].settings.generate_particles = 5
20
- container.modifiers['container'].settings.surface_subdivisions = 50
21
- container.modifiers['container'].settings.viscosity_exponent = 6
22
- container.modifiers['container'].settings.viscosity_base = 1.0
23
- container.modifiers['container'].settings.resolution = 25
24
- container.modifiers['container'].settings.simulation_scale = 1
25
- container.modifiers['container'].settings.simulation_rate = 5
26
-
27
- self.spawn_emitter_fluid((-2,-2,10),mathutils.Vector((-0.5, -0.5, -2)))
28
- self.spawn_emitter_fluid((2,2,10),mathutils.Vector((0.5, 0.5, -2)))
29
-
30
- helpers.assign_material(container, helpers.random_material(self.MATERIALS_NAMES))
31
-
8
+ RADIUS=3
32
9
  ######################
33
10
  ## SMOKE SIMULATION ##
34
11
  ######################
35
-
36
- # bpy.ops.mesh.primitive_cube_add(location=(0.0, 0.0, 17),radius=RADIUS)
37
- # container = bpy.context.object
38
- # container.name = 'smoke_container'
39
- # container.modifiers.new(name='container', type='SMOKE')
40
- # container.modifiers['container'].smoke_type = 'DOMAIN'
41
- # container.modifiers['container'].domain_settings.use_high_resolution = True
42
- # container.modifiers['container'].domain_settings.vorticity = 3
43
- # self.spawn_emitter_smoke(self.ORIGIN)
44
- # self.make_object_smoke_collider(self.SUBJECT)
45
-
46
- # Bake animation
47
- print("*** Baking commence *** (you might see a bunch of gibberish popping up cause baking is not supposed to be used headlessly")
48
- bpy.ops.ptcache.free_bake_all() # free bake cache
49
- bpy.ops.ptcache.bake_all(bake = True)
50
- print("*** Baking finished ***")
51
-
52
- def spawn_emitter_fluid(self, location, emission_vector):
53
- bpy.ops.mesh.primitive_uv_sphere_add(location=location)
54
- emitter = bpy.context.object
55
- emitter.cycles_visibility.camera = False
56
- emitter.name = 'fluid_emitter_' + str(uuid.uuid1())
57
- emitter.modifiers.new(name='emitter', type='FLUID_SIMULATION')
58
- emitter.modifiers['emitter'].settings.type = 'INFLOW'
59
- emitter.modifiers['emitter'].settings.inflow_velocity = emission_vector
60
- emitter.scale = (0.5, 0.5, 0.5)
61
- return emitter
62
-
63
- def make_object_fluid_collider(self, obj):
64
- obj.modifiers.new(name='obstacle', type='FLUID_SIMULATION')
65
- obj.modifiers['obstacle'].settings.type = 'OBSTACLE'
66
- obj.modifiers['obstacle'].settings.volume_initialization = 'BOTH'
67
- obj.modifiers['obstacle'].settings.partial_slip_factor = 0.15
12
+ bpy.ops.mesh.primitive_cube_add(location=(0.0, 0.0, 4.0),radius=RADIUS)
13
+ container = bpy.context.object
14
+ container.name = 'smoke_container'
15
+ container.modifiers.new(name='container', type='SMOKE')
16
+ container.modifiers['container'].smoke_type = 'DOMAIN'
17
+ container.modifiers['container'].domain_settings.use_high_resolution = True
18
+ container.modifiers['container'].domain_settings.vorticity = 3
19
+ container.modifiers['container'].domain_settings.vorticity = 3
20
+ container.modifiers["container"].domain_settings.alpha = - 1.2
21
+ helpers.assign_material(container, bpy.data.materials['Smoke Domain Material'])
22
+ emitter = self.spawn_emitter_smoke(self.ORIGIN)
23
+ self.make_object_smoke_collider(self.SUBJECT)
24
+
25
+ emitter.modifiers["emitter"].flow_settings.density = 1
26
+ bpy.context.scene.frame_set(0)
27
+ emitter.location = helpers.rand_location(7, positive=True)
28
+ helpers.add_frame([emitter], ['location'])
29
+ bpy.context.scene.frame_set(self.NUMBER_OF_FRAMES)
30
+ emitter.location = (4,4,4)
31
+ helpers.add_frame([emitter], ['location'])
68
32
 
69
33
  def spawn_emitter_smoke(self, location, obj = None):
70
34
  bpy.ops.mesh.primitive_uv_sphere_add(location=location)
@@ -75,7 +39,7 @@ class Aether(canvas.Canvas):
75
39
  emitter.modifiers['emitter'].smoke_type = 'FLOW'
76
40
  emitter.modifiers['emitter'].flow_settings.smoke_color = (helpers.rand_color_value(), helpers.rand_color_value(), helpers.rand_color_value())
77
41
  emitter.modifiers['emitter'].flow_settings.temperature = 1
78
- emitter.scale = (3,3,3)
42
+ emitter.scale = (0.4,0.4,0.4)
79
43
  return emitter
80
44
 
81
45
  def make_object_smoke_collider(self, obj):
@@ -8,7 +8,7 @@ class Dreamatorium(canvas.Canvas):
8
8
  props = []
9
9
  bpy.ops.import_scene.obj(filepath = os.path.join(self.MODELS_FOLDER_PATH + 'lightning.obj'), use_edges=True)
10
10
  logo = bpy.context.selected_objects[0]
11
- logo.location = self.rand_location(self.CANVAS_BOUNDARY)
11
+ logo.location = helpers.rand_location(self.CANVAS_BOUNDARY)
12
12
  props.append(logo)
13
13
 
14
14
  bpy.ops.mesh.primitive_grid_add(x_subdivisions=100, y_subdivisions=100, location=(0, 6, 2))
@@ -42,13 +42,12 @@ class Dreamatorium(canvas.Canvas):
42
42
  props.append(new_line)
43
43
 
44
44
  ocean = self.add_ocean(10, 20)
45
- helpers.apply_displacement(ocean, self.HEIGHT_MAP_FOLDER_PATH)
46
45
 
47
46
  for index in range(1, 5):
48
47
  new_object = helpers.spawn_text(self.TEXT_FILE_PATH)
49
48
  bpy.data.groups['texts'].objects.link(new_object)
50
49
  props.append(new_object)
51
- self.assign_material(new_object, helpers.random_material(self.MATERIALS_NAMES))
50
+ helpers.assign_material(new_object, helpers.random_material(self.MATERIALS_NAMES))
52
51
  text_scale = random.uniform(0.75, 3)
53
52
  new_object.scale = (text_scale, text_scale, text_scale)
54
53
  new_object.location = helpers.rand_location(self.CANVAS_BOUNDARY)
@@ -63,7 +62,6 @@ class Dreamatorium(canvas.Canvas):
63
62
  helpers.shuffle(prop, self.CANVAS_BOUNDARY)
64
63
  helpers.assign_material(prop, helpers.random_material(self.MATERIALS_NAMES))
65
64
 
66
-
67
65
  def add_ocean(self, spatial_size, resolution, depth = 100, scale=(4,4,4), wave_scale = 0.5):
68
66
  bpy.ops.mesh.primitive_cube_add(location=(0, 0, -0.4),radius=1)
69
67
  ocean = bpy.context.object
@@ -73,10 +71,6 @@ class Dreamatorium(canvas.Canvas):
73
71
  ocean.modifiers["Ocean"].resolution = resolution
74
72
  ocean.modifiers["Ocean"].wave_scale = wave_scale
75
73
  ocean.modifiers["Ocean"].depth = depth
76
- helpers.assign_material(ocean, helpers.fetch_material("jello"))
77
- shadow = helpers.duplicate_object(ocean)
78
- shadow.location += mathutils.Vector((1,1,-0.4))
79
- helpers.wireframize(shadow, random.choice(self.COLORS))
80
- shadow.name = 'shadow'
74
+ helpers.wireframize(ocean, random.choice(self.COLORS))
81
75
  ocean.name = 'ocean'
82
76
  return ocean
@@ -0,0 +1,8 @@
1
+ import sys, code, random, os, math, bpy, mathutils, uuid, canvas
2
+
3
+ sys.path.append(os.path.dirname(os.path.dirname(__file__)))
4
+ import helpers
5
+
6
+ class Empty(canvas.Canvas):
7
+ def render(self):
8
+ return True
@@ -1,13 +1,12 @@
1
1
  # Matthew Plummer Fernandez hommage
2
2
  # Draw interesting parametric curves and instantiate meshes on its path
3
- import sys, code, random, os, math, bpy, canvas
3
+ import sys, code, random, os, math, bpy, canvas, mathutils
4
4
 
5
5
  sys.path.append(os.path.dirname(os.path.dirname(__file__)))
6
6
  import helpers
7
7
 
8
8
  class Fernandez(canvas.Canvas):
9
- MESH_NUMBER_LIMIT = 200 # otherwise Blender crashes
10
- MESH_OCCURENCE = 2 # 1 mesh every X point of the curve
9
+ MESH_OCCURENCE = 1 # 1 mesh every X point of the curve
11
10
 
12
11
  FUNCTIONS = [
13
12
  # decorated knot (can expand to 20 units)
@@ -37,15 +36,25 @@ class Fernandez(canvas.Canvas):
37
36
  ]
38
37
 
39
38
  def render(self):
40
- art = self.matthew_curve(self.SUBJECT, 50)
41
- helpers.assign_material(art, helpers.random_material(self.MATERIALS_NAMES))
39
+ base_particle = helpers.infer_primitive(random.choice(self.PRIMITIVES), location = (100, 100, 100), radius=1)
40
+ art = self.matthew_curve(self.SUBJECT, 20)
41
+ self.spawn_particles_system(art, base_particle)
42
+
43
+ for f in range(self.NUMBER_OF_FRAMES):
44
+ bpy.context.scene.frame_set(f)
45
+ art.particle_systems["ParticleSystem"].seed += 1
46
+ settings = bpy.data.particles[-1]
47
+ settings.particle_size += 0.005
48
+ art.scale += mathutils.Vector((0.02,0.02,0.02))
49
+ helpers.add_frame([art], ['particle_systems["ParticleSystem"].seed', 'scale'])
50
+ helpers.add_frame([settings], ['particle_size'])
42
51
 
43
52
  def rand_curve(self):
44
53
  return random.choice(self.FUNCTIONS)
45
54
 
46
55
  def matthew_curve(self, obj, time, scale = 0.2):
47
56
  fx, fy, fz = self.rand_curve()
48
- verts = [(fx(t), fy(t), fz(t)) for t in helpers.pitched_array(0, time, 1)]
57
+ verts = [(fx(t), fy(t), fz(t)) for t in helpers.pitched_array(0, time, 0.2)]
49
58
  bpy.context.scene.objects.active = obj
50
59
  bpy.ops.object.select_all(action='DESELECT')
51
60
  for idx, coord in enumerate(verts[0::self.MESH_OCCURENCE]):
@@ -60,8 +69,25 @@ class Fernandez(canvas.Canvas):
60
69
  res.name = 'fernandez'
61
70
  helpers.resize(res)
62
71
  helpers.center(res)
63
- res = helpers.create_mesh('fernandez_support', verts, [], (0,0,0), [[v, v+1] for v in range(0, (len(verts) - 1))])
64
- helpers.resize(res)
65
- helpers.center(obj)
66
- helpers.extrude(res)
72
+ helpers.decimate(res)
73
+ helpers.assign_material(res, helpers.random_material(self.MATERIALS_NAMES))
74
+ support = helpers.create_mesh('fernandez_support', verts, [], (0,0,0), [[v, v+1] for v in range(0, (len(verts) - 1))])
75
+ helpers.resize(support)
76
+ helpers.center(support)
77
+ helpers.extrude(support)
78
+ helpers.assign_material(support, helpers.random_material(self.MATERIALS_NAMES))
67
79
  return res
80
+
81
+ def spawn_particles_system(self, base, obj):
82
+ base.modifiers.new("Particles", type='PARTICLE_SYSTEM')
83
+ settings = bpy.data.particles[-1]
84
+ settings.emit_from = 'VERT'
85
+ settings.physics_type = 'NO'
86
+ settings.count = 2000 # default 1000
87
+ settings.particle_size = 0.01
88
+ settings.render_type = 'OBJECT'
89
+ settings.dupli_object = obj
90
+ settings.show_unborn = True
91
+ settings.use_dead = True
92
+ settings.size_random = 0.5
93
+ bpy.ops.object.duplicates_make_real()