Created
May 10, 2021 06:50
-
-
Save CGArtPython/4170d8ecec3c6212b3f8db531b194924 to your computer and use it in GitHub Desktop.
The code for this art: https://www.artstation.com/artwork/AqDyDq
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
""" | |
Author: Viktor Stepanov | |
Licence: MIT | |
The code for this art: | |
https://www.artstation.com/artwork/AqDyDq | |
Tested with Blender 2.92 | |
""" | |
import random | |
import bpy | |
import mathutils | |
def setup_camera(): | |
""" | |
Adds a camera to the scene | |
""" | |
loc = (-15.297300338745117, -20.347808837890625, -1.1497830152511597) | |
rot = (1.5498526096343994, -1.3148469690804632e-07, -0.6248275637626648) | |
bpy.ops.object.camera_add( | |
enter_editmode=False, align="VIEW", location=loc, rotation=rot | |
) | |
cam = bpy.context.active_object | |
bpy.context.scene.camera = cam | |
bpy.context.object.data.lens = 71 | |
def set_cycles_scene(fps, loop_sec): | |
""" | |
Setup Cycles setting | |
""" | |
bpy.context.scene.render.fps = fps | |
frame_count = fps * loop_sec | |
bpy.context.scene.frame_end = frame_count | |
# set the background to black | |
world = bpy.data.worlds["World"] | |
world.node_tree.nodes["Background"].inputs[0].default_value = (0, 0, 0, 1) | |
# output to PNG images | |
bpy.context.scene.render.image_settings.file_format = "PNG" | |
# Set the render engine to Cycles | |
bpy.context.scene.render.engine = "CYCLES" | |
bpy.context.scene.cycles.device = "GPU" | |
bpy.context.scene.cycles.samples = 128 | |
# Turn on denoising | |
bpy.context.scene.cycles.use_denoising = True | |
bpy.context.scene.cycles.use_adaptive_sampling = True | |
bpy.context.scene.cycles.denoiser = "NLM" | |
# Set Tile size | |
bpy.context.scene.render.tile_y = 512 | |
bpy.context.scene.render.tile_x = 512 | |
def create_control_empty(frame_count): | |
""" | |
Create an empty that will follow a path | |
making a perfect loop. | |
This will allow to drive the Displace modifier | |
""" | |
bpy.ops.curve.primitive_bezier_circle_add( | |
enter_editmode=False, align="WORLD", location=(0, 0, 0) | |
) | |
bezier_obj = bpy.context.active_object | |
bpy.context.object.data.path_duration = frame_count | |
bpy.ops.object.empty_add(type="PLAIN_AXES", align="WORLD", location=(0, 0, 0)) | |
control_empty = bpy.context.active_object | |
bpy.ops.object.constraint_add(type="FOLLOW_PATH") | |
bpy.context.object.constraints["Follow Path"].target = bezier_obj | |
bpy.ops.constraint.followpath_path_animate(constraint="Follow Path", owner="OBJECT") | |
bpy.context.object.constraints["Follow Path"].use_curve_follow = True | |
return control_empty | |
def make_obj(frame_count): | |
""" | |
Generate the main object | |
""" | |
control_empty = create_control_empty(frame_count) | |
# create base object | |
bpy.ops.mesh.primitive_cube_add( | |
size=3, enter_editmode=False, align="WORLD", location=(0, 0, 0) | |
) | |
subdivide_lvl = 2 | |
# apply the base Subdivision modifier | |
bpy.ops.object.modifier_add(type="SUBSURF") | |
mod_name = "base_Subdivision" | |
bpy.context.object.modifiers["Subdivision"].name = mod_name | |
bpy.context.object.modifiers[mod_name].levels = subdivide_lvl | |
bpy.context.object.modifiers[mod_name].render_levels = subdivide_lvl | |
# create texture that will drive the Displace modifier | |
bpy.ops.texture.new() | |
tex = bpy.data.textures["Texture"] | |
bpy.data.textures["Texture"].type = "CLOUDS" | |
bpy.data.textures["Texture"].noise_scale = 0.85 | |
bpy.data.textures["Texture"].noise_depth = 10 | |
# apply the Displace modifier | |
bpy.ops.object.modifier_add(type="DISPLACE") | |
bpy.context.object.modifiers["Displace"].texture = tex | |
bpy.context.object.modifiers["Displace"].texture_coords = "OBJECT" | |
bpy.context.object.modifiers["Displace"].strength = 1.6 | |
bpy.context.object.modifiers["Displace"].texture_coords_object = control_empty | |
# apply the second Subdivision modifier | |
bpy.ops.object.modifier_add(type="SUBSURF") | |
mod_name = "scnd_Subdivision" | |
bpy.context.object.modifiers["Subdivision"].name = mod_name | |
bpy.context.object.modifiers[mod_name].render_levels = subdivide_lvl | |
bpy.context.object.modifiers[mod_name].levels = subdivide_lvl | |
# apply the Remesh modifier | |
bpy.ops.object.modifier_add(type="REMESH") | |
bpy.context.object.modifiers["Remesh"].mode = "BLOCKS" | |
bpy.context.object.modifiers["Remesh"].octree_depth = 5 | |
# create and apply the base materail | |
mat = generate_material_glass() | |
apply_mat(mat) | |
# apply the Wireframe modifier | |
bpy.ops.object.modifier_add(type="WIREFRAME") | |
bpy.context.object.modifiers["Wireframe"].thickness = 0.03 | |
bpy.context.object.modifiers["Wireframe"].use_replace = False | |
bpy.context.object.modifiers["Wireframe"].material_offset = 1 | |
# create and apply the wireframe materail | |
mat = generate_material_edge() | |
apply_mat(mat) | |
def delete_all_objects(): | |
""" | |
Removing all of the objects from the scene | |
""" | |
# if Edit mode is enabled, then toggle it off | |
if bpy.context.active_object and bpy.context.active_object.mode == "EDIT": | |
bpy.ops.object.editmode_toggle() | |
bpy.ops.object.select_all(action="SELECT") | |
bpy.ops.object.delete() | |
# Remove orphan data | |
bpy.ops.outliner.orphans_purge() | |
def apply_mat(mat): | |
""" | |
Add material of current active object | |
""" | |
obj = bpy.context.active_object | |
obj.data.materials.append(mat) | |
def gen_scene(frame_count): | |
""" | |
Generate the scene | |
""" | |
make_obj(frame_count) | |
# add light | |
bpy.ops.object.light_add(type="POINT", radius=1, align="WORLD", location=(0, 0, 0)) | |
bpy.context.object.data.energy = 100 | |
bpy.context.object.data.color = (1, 0.342081, 0) | |
gen_floor() | |
def gen_floor(): | |
""" | |
Generate the floor | |
""" | |
bpy.ops.mesh.primitive_plane_add( | |
size=50, | |
enter_editmode=False, | |
align="WORLD", | |
location=(0, 0, -2), | |
scale=(1, 1, 1), | |
) | |
mat = generate_material_floor() | |
apply_mat(mat) | |
def generate_material_floor(): | |
""" | |
Generate the material for the floor | |
""" | |
mat = bpy.data.materials.new(name="Material_gen_floor") | |
mat.use_nodes = True | |
# remove all nodes except the output node | |
to_rm = [] | |
for node in mat.node_tree.nodes: | |
if node.type == "OUTPUT_MATERIAL": | |
continue | |
to_rm.append(node) | |
for node in to_rm: | |
print("Removing", node.name) | |
mat.node_tree.nodes.remove(node) | |
node_1 = mat.node_tree.nodes.new(type="ShaderNodeValToRGB") | |
node_1.location = mathutils.Vector((-367.4133605957031, 88.715576171875)) | |
node_1.name = "ColorRamp" | |
node_1.color_ramp.elements[0].color = (0.0, 0.0, 0.0, 1.0) | |
node_1.color_ramp.elements[0].position = 0.0 | |
node_1.color_ramp.elements[1].color = (1.0, 1.0, 1.0, 1.0) | |
node_1.color_ramp.elements[1].position = 1.0 | |
node_2 = mat.node_tree.nodes.new(type="ShaderNodeMapping") | |
node_2.location = mathutils.Vector((-845.0983276367188, 173.80348205566406)) | |
node_2.name = "Mapping" | |
node_2.inputs[1].default_value = mathutils.Vector( | |
(random.randint(0, 90), random.randint(0, 90), 0.0) | |
) | |
node_2.inputs[2].default_value = mathutils.Euler((0.0, 0.0, 0.0)) | |
node_2.inputs[3].default_value = mathutils.Vector((1.0, 1.0, 1.0)) | |
node_2.vector_type = "POINT" | |
node_3 = mat.node_tree.nodes.new(type="ShaderNodeTexCoord") | |
node_3.location = mathutils.Vector((-1055.098388671875, 156.80348205566406)) | |
node_3.name = "Texture Coordinate" | |
node_4 = mat.node_tree.nodes.new(type="ShaderNodeTexMusgrave") | |
node_4.location = mathutils.Vector((-555.0983276367188, 141.20697021484375)) | |
node_4.name = "Musgrave Texture" | |
node_4.inputs[2].default_value = 48.099998474121094 | |
node_4.inputs[3].default_value = 8.399999618530273 | |
node_5 = mat.node_tree.nodes.new(type="ShaderNodeBsdfPrincipled") | |
node_5.location = mathutils.Vector((10.0, 300.0)) | |
node_5.name = "Principled BSDF" | |
node_5.inputs[0].default_value = (0.0, 0.0, 0.0, 1.0) | |
# links | |
from_node = mat.node_tree.nodes.get("Mapping") | |
to_node = mat.node_tree.nodes.get("Musgrave Texture") | |
mat.node_tree.links.new(from_node.outputs["Vector"], to_node.inputs["Vector"]) | |
from_node = mat.node_tree.nodes.get("Texture Coordinate") | |
to_node = mat.node_tree.nodes.get("Mapping") | |
mat.node_tree.links.new(from_node.outputs["Generated"], to_node.inputs["Vector"]) | |
from_node = mat.node_tree.nodes.get("Musgrave Texture") | |
to_node = mat.node_tree.nodes.get("ColorRamp") | |
mat.node_tree.links.new(from_node.outputs["Fac"], to_node.inputs["Fac"]) | |
from_node = mat.node_tree.nodes.get("ColorRamp") | |
to_node = mat.node_tree.nodes.get("Principled BSDF") | |
mat.node_tree.links.new(from_node.outputs["Color"], to_node.inputs["Roughness"]) | |
from_node = mat.node_tree.nodes.get("Principled BSDF") | |
to_node = mat.node_tree.nodes.get("Material Output") | |
mat.node_tree.links.new(from_node.outputs["BSDF"], to_node.inputs["Surface"]) | |
return mat | |
def generate_material_edge(): | |
""" | |
generate material for the edge | |
""" | |
mat = bpy.data.materials.new(name="Material_gen_edge") | |
mat.use_nodes = True | |
# remove all nodes except the output node | |
to_rm = [] | |
for node in mat.node_tree.nodes: | |
if node.type == "OUTPUT_MATERIAL": | |
continue | |
to_rm.append(node) | |
for node in to_rm: | |
print("Removing", node.name) | |
mat.node_tree.nodes.remove(node) | |
node_1 = mat.node_tree.nodes.new(type="ShaderNodeBsdfPrincipled") | |
node_1.location = mathutils.Vector((10.0, 300.0)) | |
node_1.name = "Principled BSDF" | |
node_1.inputs[0].default_value = ( | |
0.005134937819093466, | |
0.005134937819093466, | |
0.005134937819093466, | |
1.0, | |
) | |
node_1.inputs[1].default_value = 0.0 | |
node_1.inputs[2].default_value = (1.0, 0.20000000298023224, 0.10000000149011612) | |
node_1.inputs[17].default_value = (1.0, 0.04015472158789635, 0.0, 1.0) | |
node_1.inputs[18].default_value = 0.30000007152557373 | |
# links | |
from_node = mat.node_tree.nodes.get("Principled BSDF") | |
to_node = mat.node_tree.nodes.get("Material Output") | |
mat.node_tree.links.new(from_node.outputs["BSDF"], to_node.inputs["Surface"]) | |
return mat | |
def generate_material_glass(): | |
""" | |
generate material for the sides | |
""" | |
mat = bpy.data.materials.new(name="Material_gen_glass") | |
mat.use_nodes = True | |
# remove all nodes except the output node | |
to_rm = [] | |
for node in mat.node_tree.nodes: | |
if node.type == "OUTPUT_MATERIAL": | |
continue | |
to_rm.append(node) | |
for node in to_rm: | |
print("Removing", node.name) | |
mat.node_tree.nodes.remove(node) | |
node_0 = mat.node_tree.nodes.new(type="ShaderNodeBsdfPrincipled") | |
node_0.location = mathutils.Vector((10.0, 300.0)) | |
node_0.name = "Principled BSDF" | |
node_0.inputs[15].default_value = 1.0 | |
node_2 = mat.node_tree.nodes.new(type="ShaderNodeValToRGB") | |
node_2.location = mathutils.Vector((-346.4429931640625, 95.01454162597656)) | |
node_2.name = "ColorRamp" | |
node_3 = mat.node_tree.nodes.new(type="ShaderNodeTexCoord") | |
node_3.location = mathutils.Vector((-1064.5350341796875, 97.6335678100586)) | |
node_3.name = "Texture Coordinate" | |
node_4 = mat.node_tree.nodes.new(type="ShaderNodeMapping") | |
node_4.location = mathutils.Vector((-854.5350341796875, 114.6335678100586)) | |
node_4.name = "Mapping" | |
node_5 = mat.node_tree.nodes.new(type="ShaderNodeTexNoise") | |
node_5.location = mathutils.Vector((-564.5350341796875, 79.26714324951172)) | |
node_5.name = "Noise Texture" | |
node_5.inputs[2].default_value = 26.299999237060547 | |
node_5.inputs[3].default_value = 16.0 | |
# links | |
from_node = mat.node_tree.nodes.get("Principled BSDF") | |
to_node = mat.node_tree.nodes.get("Material Output") | |
mat.node_tree.links.new(from_node.outputs["BSDF"], to_node.inputs["Surface"]) | |
from_node = mat.node_tree.nodes.get("Mapping") | |
to_node = mat.node_tree.nodes.get("Noise Texture") | |
mat.node_tree.links.new(from_node.outputs["Vector"], to_node.inputs["Vector"]) | |
from_node = mat.node_tree.nodes.get("Texture Coordinate") | |
to_node = mat.node_tree.nodes.get("Mapping") | |
mat.node_tree.links.new(from_node.outputs["Object"], to_node.inputs["Vector"]) | |
from_node = mat.node_tree.nodes.get("ColorRamp") | |
to_node = mat.node_tree.nodes.get("Principled BSDF") | |
mat.node_tree.links.new(from_node.outputs["Color"], to_node.inputs["Roughness"]) | |
from_node = mat.node_tree.nodes.get("Noise Texture") | |
to_node = mat.node_tree.nodes.get("ColorRamp") | |
mat.node_tree.links.new(from_node.outputs["Color"], to_node.inputs["Fac"]) | |
return mat | |
def main(): | |
""" | |
Start here | |
""" | |
fps = 30 | |
loop_sec = 5 | |
frame_count = fps * loop_sec | |
# pin the random seed | |
random.seed(4) | |
# Utility Building Blocks | |
delete_all_objects() | |
setup_camera() | |
set_cycles_scene(fps, loop_sec) | |
gen_scene(frame_count) | |
day = "day75" | |
scene_num = 1 | |
bpy.context.scene.render.filepath = f"/tmp/day{day}_{scene_num}/" | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment