July 26, 2025
Description
E8 Lie group or E8 lattice (go with E8 Thick)
E8 root system projected onto its first three basis vectors.
Below is some code I generated in Gemini.
A simple game to learn the basics of E8. (some of the edu diags are sketch)
Below is a phyton script for blender. cut and paste it into the scripting window. hit play
# E8 Lattice Generation Script for Blender (Final Correction)
#
# Fixes a bug in edge generation where only the first edge was correctly placed.
# This version now correctly connects all vertices.
import bpy
import bmesh
import random
from mathutils import Vector, Matrix
# --- Configuration ---
# Set the fraction of vertices to generate (1.0 = all, 0.25 = 25% random sample)
SAMPLING_FRACTION = 1.0
VERT_RADIUS = 0.08 # Radius of the spheres representing vertices
EDGE_RADIUS = 0.02 # Radius of the cylinders representing edges
COLLECTION_NAME = "E8_Lattice" # Name for the new collection
# --- Helper Functions ---
def create_collection(name):
"""Creates a new collection or gets it if it already exists."""
if name in bpy.data.collections:
return bpy.data.collections[name]
else:
new_collection = bpy.data.collections.new(name)
bpy.context.scene.collection.children.link(new_collection)
return new_collection
def get_e8_root_vectors():
"""Generates the 240 root vectors of the E8 lattice."""
roots = set()
# Type 1: (±1, ±1, 0, ..., 0) permutations. (112 vectors)
for i in range(8):
for j in range(i + 1, 8):
for s1 in [-1, 1]:
for s2 in [-1, 1]:
v = [0] * 8
v[i] = s1
v[j] = s2
roots.add(tuple(v))
# Type 2: (±½, ±½, ..., ±½) with an even number of minus signs. (128 vectors)
for i in range(2**8):
v = []
neg_count = 0
for k in range(8):
if (i >> k) & 1:
v.append(0.5)
else:
v.append(-0.5)
neg_count += 1
if neg_count % 2 == 0:
roots.add(tuple(v))
# Scale roots by a factor of 2 for better visualization
return [Vector(r) * 2 for r in roots]
def create_mesh_object(name, collection, verts, faces, edges=None):
"""Creates a single mesh object from vertex, face, and edge data."""
if edges is None:
edges = []
mesh = bpy.data.meshes.new(name)
mesh.from_pydata(verts, edges, faces)
mesh.update()
obj = bpy.data.objects.new(name, mesh)
collection.objects.link(obj)
return obj
# --- Core BMesh Generation Functions ---
def generate_vertices_mesh(name, vectors, radius, collection):
"""Generates a single object containing spheres for all vertices."""
print(f"Generating mesh for {len(vectors)} vertices...")
if not vectors: return None
bm = bmesh.new()
for vec in vectors:
loc_3d = Vector((vec[0], vec[1], vec[2]))
bmesh.ops.create_icosphere(bm, subdivisions=2, radius=radius, matrix=Matrix.Translation(loc_3d))
obj = create_mesh_object(name, collection, [v.co for v in bm.verts], [[v.index for v in f.verts] for f in bm.faces])
bm.free()
obj.data.polygons.foreach_set('use_smooth', [True] * len(obj.data.polygons))
obj.data.update()
print("Vertex mesh created.")
return obj
def generate_edges_mesh(name, vectors, radius, collection):
"""Generates a single object containing cylinders for all edges."""
if not vectors or len(vectors) < 2: return None, 0
print("Calculating and generating edge mesh... (this is the slow part)")
bm = bmesh.new()
edge_count = 0
segments = 12
for i in range(len(vectors)):
for j in range(i + 1, len(vectors)):
v1 = vectors[i]
v2 = vectors[j]
# Check for connectivity (squared distance is 8.0 after scaling)
if abs((v1 - v2).length_squared - 8.0) < 0.001:
p1 = Vector((v1[0], v1[1], v1[2]))
p2 = Vector((v2[0], v2[1], v2[2]))
# --- BUG FIX AREA ---
# Robustly get the new cylinder's vertices by checking the mesh
# state before and after the operator.
verts_before = set(bm.verts)
bmesh.ops.create_cone(
bm,
cap_ends=True,
segments=segments,
radius1=radius,
radius2=radius,
depth=(p1 - p2).length
)
new_verts = list(set(bm.verts) - verts_before)
# --- END FIX ---
# Calculate rotation and translation to position the cylinder
direction = p2 - p1
if direction.length > 0: # Avoid issues with zero-length vectors
location = p1 + direction / 2.0
rotation_quat = direction.to_track_quat('Z', 'Y')
# Apply the transformation to ONLY the new vertices
bmesh.ops.rotate(bm, verts=new_verts, cent=Vector((0,0,0)), matrix=rotation_quat.to_matrix().to_4x4())
bmesh.ops.translate(bm, verts=new_verts, vec=location)
edge_count += 1
if edge_count % 500 == 0:
print(f" ...processed {edge_count} edges...")
obj = create_mesh_object(name, collection, [v.co for v in bm.verts], [[v.index for v in f.verts] for f in bm.faces])
bm.free()
obj.data.polygons.foreach_set('use_smooth', [True] * len(obj.data.polygons))
obj.data.update()
print(f"Edge mesh with {edge_count} edges created.")
return obj, edge_count
# --- Main Execution ---
def main():
"""Main function to generate the E8 lattice."""
e8_collection = create_collection(COLLECTION_NAME)
for obj in e8_collection.objects:
bpy.data.objects.remove(obj, do_unlink=True)
print("Generating 240 E8 root vectors...")
root_vectors = get_e8_root_vectors()
if SAMPLING_FRACTION < 1.0:
num_to_sample = int(len(root_vectors) * SAMPLING_FRACTION)
sampled_vectors = random.sample(root_vectors, num_to_sample)
print(f"Sampling {num_to_sample} of {len(root_vectors)} vertices ({SAMPLING_FRACTION * 100:.0f}%).")
else:
sampled_vectors = root_vectors
print("Using all 240 vertices.")
generate_vertices_mesh("E8_Vertices", sampled_vectors, VERT_RADIUS, e8_collection)
generate_edges_mesh("E8_Edges", sampled_vectors, EDGE_RADIUS, e8_collection)
print("\n✅ Finished! E8 lattice generated successfully.")
# --- Run the Script ---
if __name__ == "__main__":
main()License:
Creative Commons — Attribution — Noncommercial