Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save pukpr/df719e2c7176b7a4d4466e01edb6d3d9 to your computer and use it in GitHub Desktop.

Select an option

Save pukpr/df719e2c7176b7a4d4466e01edb6d3d9 to your computer and use it in GitHub Desktop.
Blender orbits
https://chat.openai.com/share/7555f139-f9dc-4788-abbd-6e4b57b86a46
@pukpr
Copy link
Copy Markdown
Author

pukpr commented Sep 1, 2023

import bpy
import math

Clear existing mesh objects

bpy.ops.object.select_all(action='DESELECT')
bpy.ops.object.select_by_type(type='MESH')
bpy.ops.object.delete()

Create Earth, Moon, and Sun spheres

bpy.ops.mesh.primitive_uv_sphere_add(location=(0, 0, 0))
earth = bpy.context.active_object
earth.name = "Earth"
earth.scale = (1, 1, 1)

bpy.ops.mesh.primitive_uv_sphere_add(location=(10, 0, 0))
moon = bpy.context.active_object
moon.name = "Moon"
moon.scale = (0.25, 0.25, 0.25)

bpy.ops.mesh.primitive_uv_sphere_add(location=(-20, 0, 0))
sun = bpy.context.active_object
sun.name = "Sun"
sun.scale = (5, 5, 5)

Time parameters

seconds_per_year = 2 * 60 # 2 minutes in seconds
frames_per_second = 24 # Blender default
frames_per_year = seconds_per_year * frames_per_second
total_animation_frames = 10 * 60 * frames_per_second # 10 minutes

Set the end frame for the animation

bpy.context.scene.frame_end = total_animation_frames

Earth rotation and wobble

rotation_speed = 360 / frames_per_year # Degrees per frame
for frame in range(0, total_animation_frames + 1):
earth.rotation_euler[2] = rotation_speed * frame
# Add Chandler and annual wobble (simplified for visualization)
wobble_angle = math.sin(frame * 2 * math.pi / frames_per_year)
earth.rotation_euler[1] = 0.5 * wobble_angle # Adjust as needed
earth.keyframe_insert(data_path="rotation_euler", frame=frame)

# Moon's orbit around Earth

moon_orbit_radius = 10 # Not to scale, for visualization
moon_orbit_speed = 2 * math.pi / (27.3 * frames_per_second) # Tropical orbit
for frame in range(0, total_animation_frames + 1):
moon_angle = moon_orbit_speed * frame
moon.location = (moon_orbit_radius * math.cos(moon_angle), moon_orbit_radius * math.sin(moon_angle), 0.5 * moon_orbit_radius * math.sin(moon_angle))
moon.keyframe_insert(data_path="location", frame=frame)
# Assuming the moon object is named 'Moon'

Assuming the moon object is named 'Moon'

moon = bpy.data.objects["Moon"]

Create or get a material for the moon

mat_name = "MoonIlluminatedMaterial"
if mat_name not in bpy.data.materials:
mat = bpy.data.materials.new(name=mat_name)
moon.data.materials.append(mat)
else:
mat = bpy.data.materials[mat_name]

Use nodes for the material

mat.use_nodes = True
nodes = mat.node_tree.nodes

Clear default nodes

for node in nodes:
nodes.remove(node)

Add a Principled BSDF shader and connect it to the Material Output

shader = nodes.new(type='ShaderNodeBsdfPrincipled')
shader.location = (0, 0)
shader.inputs["Alpha"].default_value = 0.5 # Set transparency
shader.inputs["Emission"].default_value = (1, 1, 1, 1) # Set emission color

material_output = nodes.new(type='ShaderNodeOutputMaterial')
material_output.location = (400, 0)

noodle = mat.node_tree.links.new
noodle(shader.outputs["BSDF"], material_output.inputs["Surface"])

Add a point light inside the moon for internal illumination

if "MoonInternalLight" not in bpy.data.objects:
bpy.ops.object.light_add(type='POINT', location=moon.location)
light = bpy.context.active_object
light.name = "MoonInternalLight"
light.data.energy = 500 # Adjust as necessary
light.parent = moon # Parent the light to the moon so it moves with it

# Sun's pseudo-orbit around Earth

sun_orbit_radius = 50 # Not to scale, for visualization
sun_orbit_speed = 2 * math.pi / frames_per_year
for frame in range(0, total_animation_frames + 1):
sun_angle = sun_orbit_speed * frame
sun.location = (sun_orbit_radius * math.cos(sun_angle), sun_orbit_radius * math.sin(sun_angle), 0.1 * sun_orbit_radius * math.sin(sun_angle))
sun.keyframe_insert(data_path="location", frame=frame)

Assuming the sphere is named 'Sphere'

sphere = bpy.data.objects["Earth"]

Create arrow shaft (cylinder)

bpy.ops.mesh.primitive_cylinder_add(location=(0, 0, 1.5))
arrow_shaft = bpy.context.active_object
arrow_shaft.name = "ArrowShaft"
arrow_shaft.scale = (0.1, 0.1, 1)

Create arrowhead (cone)

bpy.ops.mesh.primitive_cone_add(vertices=32, radius1=0.3, depth=0.6, location=(0, 0, 2.5))
arrowhead = bpy.context.active_object
arrowhead.name = "Arrowhead"

Join arrow shaft and arrowhead to make a single arrow object

bpy.ops.object.select_all(action='DESELECT')
arrow_shaft.select_set(True)
arrowhead.select_set(True)
bpy.context.view_layer.objects.active = arrow_shaft
bpy.ops.object.join()

Rename the joined arrow object

arrow = bpy.context.active_object
arrow.name = "Arrow"

Position the arrow at the sphere's north pole

arrow.location = sphere.location
arrow.location[2] += sphere.dimensions[2] / 2 + arrow.dimensions[2] / 2

Parent the arrow to the sphere so that it moves with the sphere

arrow.parent = sphere

Assuming the sphere is named 'Earth'

earth = bpy.data.objects["Earth"]

Create the ecliptic plane

bpy.ops.mesh.primitive_plane_add(size=earth.dimensions[0]*2.5, location=earth.location)
ecliptic_plane = bpy.context.active_object
ecliptic_plane.name = "EclipticPlane"

Rotate the plane to represent the inclination of the ecliptic (approximately 23.5 degrees)

bpy.ops.transform.rotate(value=-0.409, orient_axis='Y')

Assign a semi-transparent material to the plane

if "EclipticMaterial" not in bpy.data.materials:
mat = bpy.data.materials.new(name="EclipticMaterial")
mat.use_nodes = True
bsdf = mat.node_tree.nodes["Principled BSDF"]
bsdf.inputs["Alpha"].default_value = 0.5 # 50% transparency
bsdf.inputs["Base Color"].default_value = (0.5, 0.5, 1, 1) # Blue color
else:
mat = bpy.data.materials["EclipticMaterial"]
ecliptic_plane.data.materials.append(mat)

Parent the ecliptic plane to the sphere so that it moves with the sphere

ecliptic_plane.parent = earth

Create and position the text object

bpy.ops.object.text_add(location=(0, 0, 5))
text_object = bpy.context.active_object
text_object.name = "TimeText"
text_object.data.body = "Day: 0"

Scale and adjust the text for better visibility

text_object.scale = (0.5, 0.5, 0.5)

def create_vertical_line_for_object(obj_name):
obj = bpy.data.objects[obj_name]

Create the line segment (using a cylinder)

bpy.ops.mesh.primitive_cylinder_add(radius=0.02, depth=1, location=(0, 0, 0))
line_segment = bpy.context.active_object
[line_segment.name](http://line_segment.name/) = f"{obj_name}LineSegment"

# Parent the line segment to the object
line_segment.parent = obj

# Constrain the line segment to follow the object's x-y location
constraint = line_segment.constraints.new('COPY_LOCATION')
constraint.target = obj
constraint.use_x = True
constraint.use_y = True
constraint.use_z = False

# Animate the line segment's z-scale to match the object's z-location
for frame in range(bpy.context.scene.frame_start, bpy.context.scene.frame_end + 1):
    bpy.context.scene.frame_set(frame)
    line_segment.scale.z = obj.location.z / 2
    line_segment.location.z = obj.location.z / 2
    line_segment.keyframe_insert(data_path="scale", index=2, frame=frame)
    line_segment.keyframe_insert(data_path="location", index=2, frame=frame)

Create vertical lines for the Moon and Sun

create_vertical_line_for_object("Moon")
create_vertical_line_for_object("Sun")

Assuming the sun object is named 'Sun'

sun = bpy.data.objects["Sun"]

Compute the radius of the sun's pseudo-orbit (distance from the origin)

radius = sun.location.length

Create the circular sheet

bpy.ops.mesh.primitive_circle_add(radius=radius, location=(0, 0, 0))
circle_sheet = bpy.context.active_object
circle_sheet.name = "CircularSheet"

Convert the circle to a mesh (fill it)

bpy.ops.object.editmode_toggle()
bpy.ops.mesh.fill()
bpy.ops.object.editmode_toggle()

Create or get a semi-transparent material for the circular sheet

mat_name = "TransparentMaterial"
if mat_name not in bpy.data.materials:
mat = bpy.data.materials.new(name=mat_name)
mat.use_nodes = True
nodes = mat.node_tree.nodes
# Clear default nodes
for node in nodes:
nodes.remove(node)
# Add a Principled BSDF shader and connect it to the Material Output
shader = nodes.new(type='ShaderNodeBsdfPrincipled')
shader.location = (0, 0)
shader.inputs["Alpha"].default_value = 0.8 # 50% transparency
material_output = nodes.new(type='ShaderNodeOutputMaterial')
material_output.location = (400, 0)
noodle = mat.node_tree.links.new
noodle(shader.outputs["BSDF"], material_output.inputs["Surface"])
else:
mat = bpy.data.materials[mat_name]

circle_sheet.data.materials.append(mat)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment