Skip to content

Instantly share code, notes, and snippets.

@CGArtPython
Created May 10, 2021 06:50
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save CGArtPython/4170d8ecec3c6212b3f8db531b194924 to your computer and use it in GitHub Desktop.
Save CGArtPython/4170d8ecec3c6212b3f8db531b194924 to your computer and use it in GitHub Desktop.
"""
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