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.
- checksums.yaml +4 -4
- data/.gitignore +1 -1
- data/README.md +21 -9
- data/bin/glitch3d +1 -1
- data/fixtures/base.blend +0 -0
- data/fixtures/{osl-shaders → osl_shaders}/glass.osl +0 -0
- data/fixtures/{osl-shaders → osl_shaders}/gold.osl +0 -0
- data/fixtures/{osl-shaders → osl_shaders}/green_marble.osl +0 -0
- data/fixtures/{osl-shaders → osl_shaders}/turbulent.osl +0 -0
- data/fixtures/{osl-shaders → osl_shaders}/veined_marble.osl +0 -0
- data/fixtures/{text → texts}/strings.txt +0 -0
- data/lib/glitch3d.rb +75 -22
- data/lib/glitch3d/bpy/canvas/aether.py +22 -58
- data/lib/glitch3d/bpy/canvas/dreamatorium.py +3 -9
- data/lib/glitch3d/bpy/canvas/empty.py +8 -0
- data/lib/glitch3d/bpy/canvas/fernandez.py +36 -10
- data/lib/glitch3d/bpy/canvas/lyfe.py +1 -3
- data/lib/glitch3d/bpy/canvas/metaballs.py +11 -7
- data/lib/glitch3d/bpy/canvas/sphere.py +23 -38
- data/lib/glitch3d/bpy/canvas/waves.py +15 -41
- data/lib/glitch3d/bpy/helpers.py +70 -40
- data/lib/glitch3d/bpy/main.py +219 -193
- data/lib/glitch3d/bpy/post-processing/average.py +8 -10
- data/lib/glitch3d/bpy/post-processing/mosaic.py +1 -1
- data/lib/glitch3d/bpy/post-processing/optimize.py +3 -6
- data/lib/glitch3d/bpy/post-processing/palette.py +3 -4
- data/lib/glitch3d/bpy/render_settings.py +35 -23
- data/lib/glitch3d/objects/vertex.rb +1 -1
- data/lib/glitch3d/strategies/default.rb +10 -8
- data/lib/glitch3d/strategies/duplication.rb +14 -12
- data/lib/glitch3d/strategies/find_and_replace.rb +19 -16
- data/lib/glitch3d/strategies/localized.rb +28 -18
- data/lib/glitch3d/strategies/none.rb +7 -5
- data/lib/glitch3d/version.rb +1 -1
- metadata +9 -9
- data/lib/glitch3d/bpy/canvas/particles.py +0 -33
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e35a975d66a59b0803f10f9b4adff8a34cec401b
|
4
|
+
data.tar.gz: d408635bb3bcd718723929f54d2c743ef03275f1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 75cf5cfa3091803d1f8706f5670fc947a7ec645297e24477685e8fab019683061df340dd2dc42ba423a019b27d928b4f2272b23ed47c46c01e5364c70d4b5475
|
7
|
+
data.tar.gz: d99b789dbf4829ff3993b24979360942a22b1a4070615af8e46d9d8644d6d0c4d23baa7d54392f56b9823b6fbaf988ea8ecb60f00283101b850e056e570e4a6d
|
data/.gitignore
CHANGED
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
|
39
|
-
- `shots-number
|
40
|
-
- `quality
|
41
|
-
- `animate
|
42
|
-
- `frames
|
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
|
|
data/bin/glitch3d
CHANGED
data/fixtures/base.blend
CHANGED
Binary file
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
data/lib/glitch3d.rb
CHANGED
@@ -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
|
-
|
10
|
-
VERTEX_GLITCH_OFFSET = 1
|
9
|
+
class ProcessError < StandardError; end
|
11
10
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
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
|
-
|
55
|
-
|
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 =
|
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
|
139
|
-
faces: alter_faces
|
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
|
-
|
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
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
bpy.
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
emitter
|
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 = (
|
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 =
|
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
|
-
|
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.
|
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
|
@@ -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
|
-
|
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
|
-
|
41
|
-
|
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,
|
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
|
-
|
64
|
-
helpers.
|
65
|
-
helpers.
|
66
|
-
helpers.
|
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()
|