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
@@ -5,7 +5,7 @@ sys.path.append(os.path.dirname(os.path.dirname(__file__)))
|
|
5
5
|
import helpers
|
6
6
|
|
7
7
|
class Lyfe(canvas.Canvas):
|
8
|
-
SIZE =
|
8
|
+
SIZE = 4
|
9
9
|
|
10
10
|
def render(self):
|
11
11
|
self.cubes = helpers.build_composite_object('Cube', self.SIZE-1, 0.5)
|
@@ -13,7 +13,6 @@ class Lyfe(canvas.Canvas):
|
|
13
13
|
for b in a:
|
14
14
|
for c in b:
|
15
15
|
c.location += mathutils.Vector((self.SIZE/2, self.SIZE/2, self.SIZE/2))
|
16
|
-
# code.interact(local=dict(globals(), **locals()))
|
17
16
|
self.cells = [[[ 0 for i in range(self.SIZE)] for k in range(self.SIZE)] for j in range(self.SIZE)]
|
18
17
|
self.next_generation = [[[ 0 for i in range(self.SIZE)] for k in range(self.SIZE)] for j in range(self.SIZE)]
|
19
18
|
|
@@ -52,7 +51,6 @@ class Lyfe(canvas.Canvas):
|
|
52
51
|
x_index = (x + i + self.SIZE) % self.SIZE
|
53
52
|
y_index = (y + j + self.SIZE) % self.SIZE
|
54
53
|
z_index = (z + k + self.SIZE) % self.SIZE
|
55
|
-
# code.interact(local=dict(globals(), **locals()))
|
56
54
|
if not( x_index == x and y_index == y and z_index == z):
|
57
55
|
neighbors_alive_count += self.cells[x_index][y_index][z_index]
|
58
56
|
if self.cells[x][y][z] == 1 and neighbors_alive_count > 6:
|
@@ -8,10 +8,13 @@ sys.path.append(os.path.dirname(os.path.dirname(__file__)))
|
|
8
8
|
import helpers
|
9
9
|
|
10
10
|
class Metaballs(canvas.Canvas):
|
11
|
+
|
12
|
+
TYPES = ['BALL', 'CAPSULE', 'PLANE', 'ELLIPSOID', 'CUBE']
|
13
|
+
|
11
14
|
def render(self):
|
12
|
-
diameter =
|
15
|
+
diameter = 4.0
|
13
16
|
sz = 2.125 / diameter
|
14
|
-
latitude =
|
17
|
+
latitude = 10
|
15
18
|
longitude = latitude * 2
|
16
19
|
invlatitude = 1.0 / (latitude - 1)
|
17
20
|
current_frame = 0
|
@@ -23,12 +26,13 @@ class Metaballs(canvas.Canvas):
|
|
23
26
|
center = Vector((0.0, 0.0, 0.0))
|
24
27
|
baseaxis = Vector((0.0, 1.0, 0.0))
|
25
28
|
axis = Vector((0.0, 0.0, 0.0))
|
26
|
-
|
27
|
-
mbdata = bpy.data.metaballs.new('
|
29
|
+
metaball_type = random.choice(self.TYPES)
|
30
|
+
mbdata = bpy.data.metaballs.new('meta_balls')
|
28
31
|
mbdata.render_resolution = 0.075
|
29
|
-
mbdata.resolution = 0.
|
30
|
-
mbobj = bpy.data.objects.new("
|
32
|
+
mbdata.resolution = 0.1
|
33
|
+
mbobj = bpy.data.objects.new("meta_bollocks", mbdata)
|
31
34
|
bpy.context.scene.objects.link(mbobj)
|
35
|
+
helpers.assign_material(mbobj, helpers.random_material(self.MATERIALS_NAMES))
|
32
36
|
for i in range(0, latitude, 1):
|
33
37
|
phi = pi * (i + 1) * invlatitude
|
34
38
|
pt.z = cos(phi) * diameter
|
@@ -36,7 +40,7 @@ class Metaballs(canvas.Canvas):
|
|
36
40
|
theta = TWOPI * j / longitude
|
37
41
|
pt.y = center.y + sin(phi) * sin(theta) * diameter
|
38
42
|
pt.x = center.x + sin(phi) * cos(theta) * diameter
|
39
|
-
mbelm = mbdata.elements.new(type=
|
43
|
+
mbelm = mbdata.elements.new(type=metaball_type)
|
40
44
|
mbelm.co = (latitude, longitude, 6)
|
41
45
|
mbelm.radius = 0.15 + sz * abs(sin(phi)) * 1.85
|
42
46
|
mbelm.stiffness = 1.0
|
@@ -9,8 +9,9 @@ import helpers
|
|
9
9
|
|
10
10
|
class Sphere(canvas.Canvas):
|
11
11
|
def render(self):
|
12
|
-
diameter =
|
12
|
+
diameter = 4.0
|
13
13
|
sz = 2.125 / diameter
|
14
|
+
base_object = helpers.infer_primitive(random.choice(self.PRIMITIVES), location = (100, 100, 100), radius=sz)
|
14
15
|
latitude = 16
|
15
16
|
longitude = latitude * 2
|
16
17
|
invlatitude = 1.0 / (latitude - 1)
|
@@ -19,11 +20,7 @@ class Sphere(canvas.Canvas):
|
|
19
20
|
jprc = 0.0
|
20
21
|
phi = 0.0
|
21
22
|
theta = 0.0
|
22
|
-
|
23
|
-
fcount = 10
|
24
|
-
invfcount = 1.0 / (fcount - 1)
|
25
|
-
frange = bpy.context.scene.frame_end - bpy.context.scene.frame_start
|
26
|
-
fincr = ceil(frange * invfcount)
|
23
|
+
invfcount = 1.0 / (self.NUMBER_OF_FRAMES - 1)
|
27
24
|
# Animate center of the sphere.
|
28
25
|
center = Vector((0.0, 0.0, 0.0))
|
29
26
|
startcenter = Vector((0.0, -4.0, 0.0))
|
@@ -41,52 +38,40 @@ class Sphere(canvas.Canvas):
|
|
41
38
|
for i in range(0, latitude, 1):
|
42
39
|
iprc = i * invlatitude
|
43
40
|
phi = pi * (i + 1) * invlatitude
|
44
|
-
|
45
|
-
|
46
|
-
rad = 0.01 + sz * abs(sinphi) * 0.99
|
47
|
-
pt.z = cosphi * diameter
|
41
|
+
rad = 0.01 + sz * abs(sin(phi)) * 0.99
|
42
|
+
pt.z = cos(phi) * diameter
|
48
43
|
for j in range(0, longitude, 1):
|
49
44
|
jprc = j * invlongitude
|
50
45
|
theta = TWOPI * j / longitude
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
current = bpy.context.object
|
57
|
-
current.name = 'Cube ({0:0>2d}, {1:0>2d})'.format(i, j)
|
46
|
+
pt.y = center.y + sin(phi) * sin(theta) * diameter
|
47
|
+
pt.x = center.x + sin(phi) * cos(theta) * diameter
|
48
|
+
current = helpers.duplicate_object(base_object)
|
49
|
+
current.location = pt
|
50
|
+
current.name = 'Object ({0:0>2d}, {1:0>2d})'.format(i, j)
|
58
51
|
current.data.name = 'Mesh ({0:0>2d}, {1:0>2d})'.format(i, j)
|
59
52
|
current.rotation_euler = (0.0, phi, theta)
|
60
|
-
|
61
|
-
|
62
|
-
mat.diffuse_color = colorsys.hsv_to_rgb(jprc, 1.0 - iprc, 1.0)
|
63
|
-
current.data.materials.append(mat)
|
64
|
-
# Append a bevel modifier to each cube for polish.
|
65
|
-
bpy.ops.object.modifier_add(type='BEVEL')
|
66
|
-
bpy.context.object.modifiers['Bevel'].segments = 2
|
67
|
-
bpy.context.object.modifiers['Bevel'].width = 0.03
|
68
|
-
self.vecrotatex(theta, baseaxis, axis)
|
69
|
-
currframe = bpy.context.scene.frame_start
|
53
|
+
helpers.assign_material(current, helpers.random_material(self.MATERIALS_NAMES))
|
54
|
+
axis = self.vecrotatex(theta, baseaxis)
|
70
55
|
currot = startrot
|
71
56
|
center = startcenter
|
72
|
-
for f in range(0,
|
73
|
-
fprc = f / (
|
57
|
+
for f in range(0, self.NUMBER_OF_FRAMES, 1):
|
58
|
+
fprc = f / (self.NUMBER_OF_FRAMES - 1)
|
74
59
|
osc = abs(sin(TWOPI * fprc))
|
75
|
-
bpy.context.scene.frame_set(
|
60
|
+
bpy.context.scene.frame_set(f)
|
76
61
|
center = startcenter.lerp(stopcenter, osc)
|
77
62
|
current.location = helpers.rotate_vector(TWOPI * fprc, axis, pt)
|
78
63
|
current.keyframe_insert(data_path='location')
|
79
64
|
currot = startrot.slerp(stoprot, jprc * fprc)
|
80
65
|
current.rotation_euler = currot.to_euler()
|
81
66
|
current.keyframe_insert(data_path='rotation_euler')
|
82
|
-
mat.diffuse_color = colorsys.hsv_to_rgb(jprc, osc, 1.0)
|
83
|
-
mat.keyframe_insert(data_path='diffuse_color')
|
84
|
-
currframe += fincr
|
85
67
|
|
86
|
-
|
87
|
-
|
88
|
-
|
68
|
+
# Rotate vector by a given angle from a starting vector and return a new vector
|
69
|
+
# Trigonometry:
|
70
|
+
# x' = x cos θ − y sin θ
|
71
|
+
# y' = x sin θ + y cos θ
|
72
|
+
def vecrotatex(self, angle, vin):
|
73
|
+
vout = Vector((0.0, 0.0, 0.0))
|
89
74
|
vout.x = vin.x
|
90
|
-
vout.y =
|
91
|
-
vout.z =
|
75
|
+
vout.y = cos(angle) * vin.y - sin(angle) * vin.z
|
76
|
+
vout.z = cos(angle) * vin.z + sin(angle) * vin.y
|
92
77
|
return vout
|
@@ -7,49 +7,34 @@ sys.path.append(os.path.dirname(os.path.dirname(__file__)))
|
|
7
7
|
import helpers
|
8
8
|
|
9
9
|
class Waves(canvas.Canvas):
|
10
|
+
|
11
|
+
COUNT=5
|
12
|
+
|
10
13
|
def render(self):
|
11
|
-
|
12
|
-
count = 16
|
14
|
+
count = self.COUNT
|
13
15
|
# Size of grid.
|
14
16
|
extents = 8.0
|
15
|
-
# Spacing between cubes.
|
16
|
-
padding = 0.002
|
17
|
-
# Size of each cube.
|
18
17
|
# The height of each cube will be animated, so we'll specify the minimum and maximum scale.
|
19
|
-
sz = (extents / count)
|
18
|
+
sz = (extents / count)
|
19
|
+
base_object = helpers.infer_primitive(random.choice(self.PRIMITIVES), location = (100, 100, 100), radius=sz)
|
20
|
+
helpers.assign_material(base_object, helpers.random_material(self.MATERIALS_NAMES))
|
20
21
|
minsz = sz * 0.25
|
21
22
|
maxsz = sz * extents
|
22
|
-
diffsz = maxsz - minsz
|
23
23
|
# To convert abstract grid position within loop to real-world coordinate.
|
24
24
|
jprc = 0.0
|
25
25
|
kprc = 0.0
|
26
26
|
countf = 1.0 / (count - 1)
|
27
27
|
diffex = extents * 2
|
28
|
-
# Position of each cube.
|
29
28
|
y = 0.0
|
30
29
|
x = 0.0
|
31
|
-
# Center of grid.
|
32
30
|
centerz = 0.0
|
33
31
|
centery = 0.0
|
34
32
|
centerx = 0.0
|
35
|
-
# Distances of cube from center.
|
36
33
|
# The maximum possible distance is used to normalize the distance.
|
37
34
|
rise = 0.0
|
38
35
|
run = 0.0
|
39
36
|
normdist = 0.0
|
40
37
|
maxdist = sqrt(2 * extents * extents)
|
41
|
-
# For animation, track current frame, specify desired number of key frames.
|
42
|
-
currframe = 0
|
43
|
-
fcount = 10
|
44
|
-
invfcount = 1.0 / (fcount - 1)
|
45
|
-
# If the default frame range is 0, then default to 1 .. 150.
|
46
|
-
frange = bpy.context.scene.frame_end - bpy.context.scene.frame_start
|
47
|
-
if frange == 0:
|
48
|
-
bpy.context.scene.frame_end = 150
|
49
|
-
bpy.context.scene.frame_start = 0
|
50
|
-
frange = 150
|
51
|
-
# Number of keyframes per frame.
|
52
|
-
fincr = ceil(frange * invfcount)
|
53
38
|
# For generating the wave.
|
54
39
|
offset = 0.0
|
55
40
|
angle = 0.0
|
@@ -71,29 +56,18 @@ class Waves(canvas.Canvas):
|
|
71
56
|
# Remap the normalized distance to a range -PI .. PI
|
72
57
|
normdist = sqrt(rise + run) / maxdist
|
73
58
|
offset = -TWOPI * normdist + pi
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
current = bpy.context.object
|
78
|
-
current.name = 'Cube ({0:0>2d}, {1:0>2d})'.format(k, j)
|
59
|
+
current = helpers.duplicate_object(base_object)
|
60
|
+
current.location = (centerx + x, centery + y, centerz)
|
61
|
+
current.name = 'Object ({0:0>2d}, {1:0>2d})'.format(k, j)
|
79
62
|
current.data.name = 'Mesh ({0:0>2d}, {1:0>2d})'.format(k, j)
|
80
|
-
|
81
|
-
mat = bpy.data.materials.new(name='Material ({0:0>2d}, {1:0>2d})'.format(k, j))
|
82
|
-
mat.diffuse_color = colorsys.hsv_to_rgb(normdist, 0.875, 1.0)
|
83
|
-
current.data.materials.append(mat)
|
84
|
-
# Track the current key frame.
|
85
|
-
currframe = bpy.context.scene.frame_start
|
86
|
-
for f in range(0, fcount, 1):
|
63
|
+
for f in range(0, self.NUMBER_OF_FRAMES, 1):
|
87
64
|
# Convert the keyframe into an angle.
|
88
|
-
fprc = f *
|
65
|
+
fprc = f * 1.0 / (self.NUMBER_OF_FRAMES - 1)
|
89
66
|
angle = TWOPI * fprc
|
90
67
|
# Set the scene to the current frame.
|
91
|
-
bpy.context.scene.frame_set(
|
68
|
+
bpy.context.scene.frame_set(f)
|
92
69
|
# Change the scale.
|
93
70
|
# sin returns a value in the range -1 .. 1. abs changes the range to 0 .. 1.
|
94
71
|
# The values are remapped to the desired scale with min + percent * (max - min).
|
95
|
-
current.scale
|
96
|
-
|
97
|
-
current.keyframe_insert(data_path='scale', index=2)
|
98
|
-
# Advance by the keyframe increment to the next keyframe.
|
99
|
-
currframe += fincr
|
72
|
+
current.scale.z = minsz + abs(sin(offset + angle)) * (maxsz - minsz)
|
73
|
+
helpers.add_frame([current], ['scale'])
|
data/lib/glitch3d/bpy/helpers.py
CHANGED
@@ -1,7 +1,6 @@
|
|
1
|
-
import sys, code, random, os, math, bpy, numpy, uuid, mathutils
|
1
|
+
import sys, code, random, os, math, bpy, numpy, uuid, mathutils, subprocess
|
2
2
|
|
3
3
|
def pry(globs=globals(), locs=locals()):
|
4
|
-
import sys
|
5
4
|
code.interact(local=dict(globs, **locs))
|
6
5
|
sys.exit("Aborting execution")
|
7
6
|
|
@@ -38,6 +37,12 @@ def apply_displacement(obj, height_map_folder, strength = 0.2, subdivisions = 2)
|
|
38
37
|
displace.texture = new_texture
|
39
38
|
displace.strength = strength
|
40
39
|
|
40
|
+
def decimate(obj):
|
41
|
+
modifier = obj.modifiers.new(name='decimate', type='DECIMATE')
|
42
|
+
modifier.decimate_type = 'DISSOLVE'
|
43
|
+
bpy.context.scene.objects.active = obj
|
44
|
+
bpy.ops.object.modifier_apply(modifier="decimate")
|
45
|
+
|
41
46
|
def look_at(obj):
|
42
47
|
location_camera = CAMERA.matrix_world.to_translation()
|
43
48
|
location_object = obj.matrix_world.to_translation()
|
@@ -61,7 +66,9 @@ def output_name(model_path, index = 0):
|
|
61
66
|
def rand_color_value():
|
62
67
|
return random.uniform(0, 255) / 255
|
63
68
|
|
64
|
-
def rand_location(boundary):
|
69
|
+
def rand_location(boundary, positive = False):
|
70
|
+
if positive:
|
71
|
+
return (random.uniform(0, boundary), random.uniform(0, boundary), random.uniform(0, boundary))
|
65
72
|
return (random.uniform(-boundary, boundary), random.uniform(-boundary, boundary), random.uniform(-boundary, boundary))
|
66
73
|
|
67
74
|
def rand_rotation():
|
@@ -102,8 +109,9 @@ def assign_material(obj, material):
|
|
102
109
|
obj.data.materials[0] = material
|
103
110
|
return material
|
104
111
|
|
105
|
-
def random_material(materials_list):
|
106
|
-
|
112
|
+
def random_material(materials_list, blacklist = [ 'Smoke Domain Material' ]):
|
113
|
+
eligible_materials = list(set(materials_list) - set(blacklist))
|
114
|
+
return fetch_material(random.choice(eligible_materials))
|
107
115
|
|
108
116
|
# Returns a new Cycles material with default DiffuseBsdf node linked to output
|
109
117
|
def create_cycles_material(name = 'object_material_', clean=False):
|
@@ -368,7 +376,6 @@ def duplicate_object(obj):
|
|
368
376
|
new_object.data = obj.data.copy()
|
369
377
|
new_object.animation_data_clear()
|
370
378
|
new_object.cycles_visibility.camera = True
|
371
|
-
# assign_material(new_object, obj.data.materials[-1])
|
372
379
|
bpy.context.scene.objects.link(new_object)
|
373
380
|
return new_object
|
374
381
|
|
@@ -382,53 +389,58 @@ def random_text(file_path):
|
|
382
389
|
return lines[random.randrange(len(lines))]
|
383
390
|
|
384
391
|
def add_faces(obj):
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
392
|
+
vertices = []
|
393
|
+
for v in obj.data.vertices:
|
394
|
+
vertices.append(v.co)
|
395
|
+
new_obj = create_mesh(obj.name, vertices, random_faces(vertices), obj.location)
|
396
|
+
bpy.data.objects.remove(obj, do_unlink=True)
|
397
|
+
return new_obj
|
391
398
|
|
392
399
|
def random_faces(vertices):
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
400
|
+
faces = []
|
401
|
+
for i in range(int(len(vertices)/100)):
|
402
|
+
target = vertices[random.choice((range(len(vertices))))]
|
403
|
+
if (random.randint(0, 1) == 1):
|
404
|
+
faces.append(((target + 2), int(target / 6), int(target - 1), target))
|
405
|
+
else:
|
406
|
+
faces.append((int(target / 6), int(target - 1), target))
|
407
|
+
return faces
|
401
408
|
|
402
409
|
############
|
403
410
|
# <geometry>
|
404
411
|
############
|
405
412
|
|
406
413
|
def center(obj):
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
|
421
|
-
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
|
414
|
+
bpy.context.scene.objects.active = obj
|
415
|
+
bpy.ops.object.transform_apply(location=False, rotation=True, scale=True)
|
416
|
+
bpy.ops.object.origin_set(type="ORIGIN_CENTER_OF_MASS")
|
417
|
+
local_bounding_box_center = 0.125 * sum((mathutils.Vector(b) for b in obj.bound_box), mathutils.Vector())
|
418
|
+
obj.location -= local_bounding_box_center
|
419
|
+
obj.location = (0, 0, 0)
|
420
|
+
return obj
|
421
|
+
|
422
|
+
def resize(obj, minimum = 4.0, maximum = 8.0):
|
423
|
+
print("Resizing: " + obj.name)
|
424
|
+
init_scale = obj.scale
|
425
|
+
assert minimum < maximum
|
426
|
+
scale_multiplier =init_scale.x / (max(obj.dimensions) / (maximum - minimum))
|
427
|
+
if max(obj.dimensions) > maximum:
|
428
|
+
print("Downscaling by: " + str(scale_multiplier))
|
429
|
+
while max(obj.dimensions) > maximum:
|
430
|
+
obj.scale = init_scale + mathutils.Vector((- scale_multiplier, - scale_multiplier, - scale_multiplier))
|
431
|
+
bpy.ops.wm.redraw_timer(type='DRAW', iterations=1)
|
432
|
+
elif max(obj.dimensions) < minimum:
|
433
|
+
print("Upscaling by: " + str(scale_multiplier))
|
434
|
+
while max(obj.dimensions) < minimum:
|
435
|
+
obj.scale = obj.scale + mathutils.Vector((scale_multiplier, scale_multiplier, scale_multiplier))
|
436
|
+
bpy.ops.wm.redraw_timer(type='DRAW', iterations=1)
|
426
437
|
|
427
438
|
def extrude(obj, thickness=0.05):
|
428
439
|
bpy.context.scene.objects.active = obj
|
429
440
|
bpy.ops.object.mode_set(mode='EDIT')
|
430
441
|
bpy.ops.mesh.extrude_region_move(MESH_OT_extrude_region={"mirror":False}, TRANSFORM_OT_translate={"value":(thickness, 0, 0), "constraint_orientation":'GLOBAL', "mirror":True, "proportional":'DISABLED', "proportional_edit_falloff":'SMOOTH', "proportional_size":1, "snap":False, "snap_target":'CLOSEST', "snap_point":(0, 0, 0), "snap_align":False, "snap_normal":(0, 0, 0), "gpencil_strokes":False, "texture_space":False, "remove_on_cancel":False, "release_confirm":False, "use_accurate":False})
|
431
442
|
bpy.ops.object.mode_set(mode='OBJECT')
|
443
|
+
bpy.ops.object.select_all(action='DESELECT')
|
432
444
|
|
433
445
|
def create_line(name, point_list, color, thickness = 0.002, location = (0,0,0)):
|
434
446
|
line_data = bpy.data.curves.new(name=name,type='CURVE')
|
@@ -468,7 +480,7 @@ def pitched_array(minimum, maximum, pitch):
|
|
468
480
|
|
469
481
|
def create_mesh(name, verts, faces, location, edges=[]):
|
470
482
|
mesh_data = bpy.data.meshes.new("mesh_data")
|
471
|
-
faces = faces if len(faces) == 0 else random_faces(verts)
|
483
|
+
faces = faces if (len(faces) == 0 or len(faces) > 0) else random_faces(verts)
|
472
484
|
mesh_data.from_pydata(verts, edges, faces)
|
473
485
|
mesh_data.update()
|
474
486
|
obj = bpy.data.objects.new(name, mesh_data)
|
@@ -520,4 +532,22 @@ def rotate_vector(angle, axis, vin):
|
|
520
532
|
|
521
533
|
#############
|
522
534
|
# </geometry>
|
535
|
+
#############
|
536
|
+
|
537
|
+
|
538
|
+
#############
|
539
|
+
# <physics>
|
540
|
+
#############
|
541
|
+
|
542
|
+
def add_rigid_body(objs=[], type = 'ACTIVE', mass=2.0):
|
543
|
+
for obj in objs:
|
544
|
+
obj.select = True
|
545
|
+
bpy.ops.rigidbody.objects_add(type='ACTIVE')
|
546
|
+
for obj in objs:
|
547
|
+
if type == 'ACTIVE':
|
548
|
+
obj.rigid_body.mass = mass
|
549
|
+
obj.rigid_body.collision_shape = 'MESH'
|
550
|
+
|
551
|
+
#############
|
552
|
+
# </physics>
|
523
553
|
#############
|
data/lib/glitch3d/bpy/main.py
CHANGED
@@ -1,27 +1,30 @@
|
|
1
1
|
# Rendering script
|
2
2
|
# Run by calling the blender executable with -b -P <script_name>
|
3
3
|
# Use `pry()` to pry into the script
|
4
|
-
import argparse, random, math, os, code
|
4
|
+
import sys, argparse, random, math, os, code, requests
|
5
5
|
|
6
6
|
def get_args():
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
7
|
+
parser = argparse.ArgumentParser()
|
8
|
+
_, all_arguments = parser.parse_known_args()
|
9
|
+
double_dash_index = all_arguments.index('--')
|
10
|
+
script_args = all_arguments[double_dash_index + 1: ]
|
11
|
+
parser.add_argument('-f', '--file', help="obj file to render")
|
12
|
+
parser.add_argument('-n', '--shots-number', help="number of shots desired")
|
13
|
+
parser.add_argument('-m', '--mode', help="quality mode: low | high")
|
14
|
+
parser.add_argument('-p', '--path', help="root path of gem assets")
|
15
|
+
parser.add_argument('-a', '--animate', help="render animation") # bool
|
16
|
+
parser.add_argument('-frames', '--frames', help="number of frames") # int
|
17
|
+
parser.add_argument('-normals', '--normals', help="normal render") # bool
|
18
|
+
parser.add_argument('-d', '--debug', help="render blank scene with subject for testing purposes") # bool
|
19
|
+
parser.add_argument('-width', '--width', help="width of render") # int
|
20
|
+
parser.add_argument('-eight', '--eight', help="height of render") # int
|
21
|
+
parser.add_argument('-assets', '--assets', help="user assets path") # string
|
22
|
+
parser.add_argument('-canvas', '--canvas', help="selection of canvas modules by name") # string
|
23
|
+
parser.add_argument('-post', '--post-process', help="post-processing") # bool
|
24
|
+
parser.add_argument('-webhook', '--webhook', help="webhook url") # string
|
25
|
+
parser.add_argument('-seed', '--seed', help="random seed") # int
|
26
|
+
parsed_script_args, _ = parser.parse_known_args(script_args)
|
27
|
+
return parsed_script_args
|
25
28
|
|
26
29
|
args = get_args()
|
27
30
|
file = args.file
|
@@ -32,183 +35,206 @@ animate = (args.animate == 'True')
|
|
32
35
|
shots_number = int(args.shots_number)
|
33
36
|
width = int(args.width)
|
34
37
|
height = int(args.eight)
|
38
|
+
post_process = (args.post_process == 'True')
|
39
|
+
assets = args.assets
|
40
|
+
webhook = args.webhook
|
35
41
|
|
36
42
|
# TODO: add proper args validation cycle
|
37
43
|
#####################################
|
38
44
|
#####################################
|
39
45
|
#####################################
|
40
46
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
#
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
47
|
+
try:
|
48
|
+
NUMBER_OF_FRAMES = int(args.frames)
|
49
|
+
NORMALS_RENDERING = (args.normals == 'True')
|
50
|
+
random.seed = args.seed
|
51
|
+
|
52
|
+
# Randomize module usage at runtime or pick selection from arguments
|
53
|
+
canvas_path = os.path.dirname(__file__) + '/canvas'
|
54
|
+
MODULES_AVAILABLE = args.canvas.split(",") if args.canvas else [ f[0:-3] for f in os.listdir(canvas_path) if os.path.isfile(os.path.join(canvas_path, f)) and f != 'canvas.py' and f != 'empty.py']
|
55
|
+
MODULES_ENABLED = MODULES_AVAILABLE if debug or args.canvas else random.sample(MODULES_AVAILABLE, int(random.uniform(0, len(MODULES_AVAILABLE)) + 1))
|
56
|
+
print("modules enabled: " + str(list(MODULES_ENABLED)))
|
57
|
+
|
58
|
+
FIXTURES_FOLDER_PATH = assets if assets else path + '/../fixtures/'
|
59
|
+
TEXTURE_FOLDER_PATH = FIXTURES_FOLDER_PATH + 'textures/'
|
60
|
+
MODELS_FOLDER_PATH = FIXTURES_FOLDER_PATH + 'models/'
|
61
|
+
HEIGHT_MAP_FOLDER_PATH = FIXTURES_FOLDER_PATH + 'height_maps/'
|
62
|
+
FONT_FOLDER_PATH = FIXTURES_FOLDER_PATH + 'fonts/'
|
63
|
+
SCENE_NAME = "glitch3d"
|
64
|
+
REFLECTOR_SCALE = random.uniform(9, 10)
|
65
|
+
REFLECTOR_STRENGTH = random.uniform(12, 15)
|
66
|
+
REFLECTOR_LOCATION_PADDING = random.uniform(10, 12)
|
67
|
+
REPLACE_TARGET = str(random.uniform(0, 9))
|
68
|
+
REPLACEMENT = str(random.uniform(0, 9))
|
69
|
+
OSL_ENABLED = True
|
70
|
+
ORIGIN = (0,0,2)
|
71
|
+
CANVAS_BOUNDARY = 6
|
72
|
+
SCATTER_INTENSITY = 0.015
|
73
|
+
ABSORPTION_INTENSITY = 0.25
|
74
|
+
DISPLAY_SCALE = (2, 2, 2)
|
75
|
+
PRIMITIVES = ['Pyramid', 'Cube']
|
76
|
+
YELLOW = (1, 0.7, 0.1, 1)
|
77
|
+
GREY = (0.2, 0.2, 0.2 ,1)
|
78
|
+
BLUE = (0.1, 0.1, 0.8, 0.4)
|
79
|
+
PINK = (0.8, 0.2, 0.7, 1.0)
|
80
|
+
RENDER_OUTPUT_PATHS = []
|
81
|
+
FIXED_CAMERA = False
|
82
|
+
FUNCTIONS = {
|
83
|
+
(lambda x: math.sin(x) * math.cos(20*x)): 4,
|
84
|
+
(lambda x: math.sin(x) * math.sin(20*x)): 3,
|
85
|
+
(lambda x: INITIAL_CAMERA_LOCATION[2]): 2,
|
86
|
+
(lambda x: x) : 2,
|
87
|
+
math.sin : 1,
|
88
|
+
math.cos : 1,
|
89
|
+
(lambda x: 0.5 * math.sin(0.5*x) * math.cos(x)) : 1,
|
90
|
+
(lambda x: random.uniform(1, 10) * math.cos(x) ** 3) : 1,
|
91
|
+
(lambda x: random.uniform(1, 10)) : 1,
|
92
|
+
(lambda x: random.uniform(1, 2) + random.uniform(0.75, 3) * math.sin(random.uniform(0.1, 1)*x) + math.cos(random.uniform(0.75, 5)*x)) : 1,
|
93
|
+
(lambda x: math.sin(math.pi*x) + x + 3 * math.pi) : 1,
|
94
|
+
(lambda x: x**3 + math.cos(x/2)) : 2,
|
95
|
+
(lambda x: random.uniform(1, 10) * math.sin(x)): 3
|
96
|
+
}
|
97
|
+
|
98
|
+
import importlib.util, os, ntpath, bpy, datetime, math, random, mathutils, random, sys, logging, string, colorsys, code, numpy
|
99
|
+
from subprocess import call
|
100
|
+
|
101
|
+
def load_file(file_path):
|
102
|
+
# load and define function and vars in global namespace, yolo
|
103
|
+
exec(open(file_path).read(), globals())
|
104
|
+
|
105
|
+
def load_module_path(file_path):
|
106
|
+
print("loading module " + file_path)
|
107
|
+
sys.path.append(os.path.dirname(os.path.expanduser(file_path)))
|
108
|
+
|
109
|
+
sys.path.append(os.environ['PYTHON_MODULES_PATH'])
|
110
|
+
|
111
|
+
# Create directory for renders
|
112
|
+
directory = os.path.dirname('./renders')
|
113
|
+
if not os.path.exists(directory):
|
114
|
+
os.makedirs(directory)
|
115
|
+
|
116
|
+
load_file(os.path.join(path + '/glitch3d/bpy/helpers.py'))
|
117
|
+
load_file(os.path.join(path + '/glitch3d/bpy/render_settings.py'))
|
118
|
+
load_file(os.path.join(path + '/glitch3d/bpy/lighting.py'))
|
119
|
+
|
120
|
+
# Create groups
|
121
|
+
for s in ['texts', 'lines', 'displays', 'reflectors', 'neons']:
|
122
|
+
bpy.data.groups.new(s)
|
123
|
+
for primitive in PRIMITIVES:
|
124
|
+
bpy.data.groups.new(primitive.lower().title())
|
125
|
+
|
126
|
+
FISHEYE = random.sample([True, False], 1)
|
127
|
+
COLORS = rand_color_palette(5)
|
128
|
+
CAMERA_OFFSET = 5
|
129
|
+
INITIAL_CAMERA_LOCATION = (CAMERA_OFFSET, CAMERA_OFFSET, random.uniform(0, 8))
|
130
|
+
TEXT_FILE_PATH = FIXTURES_FOLDER_PATH + 'texts/strings.txt'
|
131
|
+
|
132
|
+
print("Loading materials...")
|
133
|
+
MATERIALS_NAMES = []
|
134
|
+
load_osl_materials(FIXTURES_FOLDER_PATH + 'osl_shaders/')
|
135
|
+
for mat in bpy.data.materials: # merge base scene materials + osl shaders
|
136
|
+
if mat.name != 'emission':
|
137
|
+
MATERIALS_NAMES.append(mat.name)
|
138
|
+
print("Detected " + str(len(MATERIALS_NAMES)) + " materials in base scene: " + str(MATERIALS_NAMES))
|
139
|
+
|
140
|
+
# Scene
|
141
|
+
new_scene = bpy.data.scenes[SCENE_NAME]
|
142
|
+
bpy.context.screen.scene = new_scene
|
143
|
+
SCENE = new_scene
|
144
|
+
SCENE.render.engine = 'CYCLES'
|
145
|
+
|
146
|
+
flush_objects()
|
147
|
+
|
148
|
+
camera_data = bpy.data.cameras.new(name = 'CAMERA')
|
149
|
+
bpy.data.objects.new('CAMERA', object_data=camera_data)
|
150
|
+
CAMERA = bpy.data.objects['CAMERA']
|
151
|
+
new_scene.objects.link(CAMERA)
|
152
|
+
SCENE.camera = CAMERA
|
153
|
+
CAMERA.location = INITIAL_CAMERA_LOCATION
|
154
|
+
SCENE.frame_start = 0
|
155
|
+
SCENE.frame_end = NUMBER_OF_FRAMES
|
156
|
+
|
157
|
+
if FISHEYE:
|
158
|
+
CAMERA.data.type = 'PANO'
|
159
|
+
CAMERA.data.cycles.panorama_type = 'FISHEYE_EQUISOLID'
|
160
|
+
CAMERA.data.cycles.fisheye_lens = 12
|
161
|
+
CAMERA.data.cycles.fisheye_fov = 2.5
|
162
|
+
CAMERA.data.sensor_width = 20
|
163
|
+
CAMERA.data.sensor_height = 20
|
164
|
+
|
165
|
+
#####################################
|
166
|
+
#####################################
|
167
|
+
#####################################
|
168
|
+
|
169
|
+
# Load model
|
170
|
+
model_path = os.path.join(file)
|
171
|
+
bpy.ops.import_scene.obj(filepath = model_path, use_edges=True)
|
172
|
+
SUBJECT_NAME = '0_glitch3d_' + ntpath.basename(file).replace("_glitched.obj", '')
|
173
|
+
SUBJECT = bpy.data.objects[SUBJECT_NAME]
|
174
|
+
SUBJECT.select = True
|
175
|
+
center(SUBJECT)
|
176
|
+
SUBJECT.location = ORIGIN
|
177
|
+
let_there_be_light(SCENE)
|
178
|
+
random.shuffle(list(MODULES_ENABLED))
|
179
|
+
|
180
|
+
for module in MODULES_ENABLED:
|
181
|
+
load_module_path(os.path.join(path + '/glitch3d/bpy/canvas', module + '.py'))
|
182
|
+
mod = __import__(module)
|
183
|
+
new_canvas = eval("mod." + module[:1].upper() + module[1:] + "(locals())")
|
184
|
+
new_canvas.render()
|
185
|
+
|
186
|
+
render_settings(animate, mode, NORMALS_RENDERING, width, height, debug)
|
187
|
+
print('Rendering images with resolution: ' + str(SCENE.render.resolution_x) + ' x ' + str(SCENE.render.resolution_y))
|
188
|
+
|
189
|
+
if bpy.context.scene.rigidbody_world:
|
190
|
+
bpy.ops.rigidbody.bake_to_keyframes(frame_start=0, frame_end=NUMBER_OF_FRAMES)
|
191
|
+
|
192
|
+
CAMERA_PATH = camera_path(NUMBER_OF_FRAMES)
|
193
|
+
|
194
|
+
for frame in range(0, NUMBER_OF_FRAMES):
|
195
|
+
bpy.context.scene.frame_set(frame)
|
196
|
+
bpy.context.scene.camera.location = CAMERA_PATH[frame]
|
197
|
+
SUBJECT.rotation_euler.z += math.radians(1)
|
198
|
+
look_at(SUBJECT)
|
199
|
+
add_frame([bpy.context.scene.camera], ["location", "rotation_euler"])
|
200
|
+
|
201
|
+
if animate:
|
202
|
+
print('ANIMATION RENDERING BEGIN')
|
203
|
+
output_path = output_name(model_path)
|
204
|
+
print('AVI file -> ' + output_path)
|
205
|
+
shoot(output_path)
|
206
|
+
else:
|
207
|
+
print('STILL RENDERING BEGIN')
|
208
|
+
for index in range(0, shots_number):
|
209
|
+
frame_cursor = int(index * (bpy.context.scene.frame_end / shots_number))
|
210
|
+
print('>> FRAME #' + str(frame_cursor))
|
211
|
+
bpy.context.scene.frame_set(int(bpy.context.scene.frame_end/(index+1)))
|
212
|
+
SUBJECT.rotation_euler.z = math.radians(index * (360.0 / shots_number))
|
213
|
+
output_path = output_name(model_path, index)
|
214
|
+
print("-------------------------- " + str(index) + " --------------------------")
|
215
|
+
print("PNG file -> " + output_path)
|
216
|
+
shoot(output_path)
|
217
|
+
|
218
|
+
# Save scene as .blend file
|
219
|
+
bpy.ops.wm.save_as_mainfile(filepath=output_name(model_path) + '.blend')
|
220
|
+
|
221
|
+
print("Files rendered with " + str(NUMBER_OF_FRAMES) + " frames in simulation:")
|
222
|
+
for p in RENDER_OUTPUT_PATHS:
|
223
|
+
print(p)
|
224
|
+
|
225
|
+
if post_process:
|
226
|
+
load_file(os.path.join(path + '/glitch3d/bpy/post-processing/optimize.py'))
|
227
|
+
if shots_number > 1:
|
228
|
+
load_file(os.path.join(path + '/glitch3d/bpy/post-processing/average.py'))
|
229
|
+
if shots_number > 10:
|
230
|
+
load_file(os.path.join(path + '/glitch3d/bpy/post-processing/mosaic.py'))
|
231
|
+
load_file(os.path.join(path + '/glitch3d/bpy/post-processing/palette.py'))
|
232
|
+
print('FINISHED ¯\_(ツ)_/¯ with seed: ' + random.seed)
|
233
|
+
sys.exit(0)
|
234
|
+
|
235
|
+
except Exception as e:
|
236
|
+
if webhook:
|
237
|
+
requests.post(webhook, data={'error': str(e)})
|
238
|
+
if debug:
|
239
|
+
raise e # See error
|
240
|
+
sys.exit(1) # Just return 1 error code in production
|