Skip to content

Instantly share code, notes, and snippets.

@smurfix
Created December 17, 2022 22:54
Show Gist options
  • Save smurfix/3f1e5966265a0094649abb28e65c56e6 to your computer and use it in GitHub Desktop.
Save smurfix/3f1e5966265a0094649abb28e65c56e6 to your computer and use it in GitHub Desktop.
Borromean Ring
"""
The purpose of this script is to mash three elongated toruses, linked as
Borromean Rings, into a single circular torus.
https://en.wikipedia.org/wiki/Borromean_ringshttps://en.wikipedia.org/wiki/Borromean_rings
The script starts in a state where the linked rings do not touch.
Then it reduces both elogation and angles in small steps, removing the
overlap and smoothing the resulting mesh every time, until the link
lies flat and is no longer stretched.
The result should be a contiguous, irregularly-cut third of a torus that
does not self-intersect when rotated by 120°.
"""
# in Blender's Python console:
# filename = "/…/rings.py"
# exec(compile(open(filename).read(), filename, 'exec'))
import bpy
C = bpy.context
OBJ = bpy.ops.object
MESH = bpy.ops.mesh
import math as m
DEG = m.pi/180
import time
# Ring data
# elongation, sufficient to remove touching
E=1.6
# ring angle
A=30
# radii
R1=1
R2=0.2
# How fine grained do we want this?
steps=30
def deleteAllObjects():
"""
Deletes all objects in the current scene
"""
deleteObjects = {'MESH', 'CURVE', 'SURFACE', 'META', 'FONT', 'HAIR',
'POINTCLOUD', 'VOLUME', 'GPENCIL', 'ARMATURE', 'LATTICE',
'EMPTY'}
# Select all objects in the scene to be deleted:
OBJ.mode_set(mode='OBJECT')
OBJ.select_all(action='DESELECT')
for o in C.scene.objects:
o.select_set(o.type in deleteObjects)
# Deletes all selected objects in the scene:
OBJ.delete()
deleteAllObjects()
def setup():
MESH.primitive_torus_add(major_segments=24, major_radius=R1, minor_radius=R2)
t = C.active_object
t.scale[0]=E
t.rotation_euler=[A*DEG, A*DEG, 0]
t.name="ring"
return t
def triples(t):
"""
Add two copies of t, rotated 120°
"""
t1 = t.copy()
t1.data = t.data.copy()
C.collection.objects.link(t1)
t1.rotation_euler[2]=120*DEG
t2 = t.copy()
t2.data = t.data.copy()
C.collection.objects.link(t2)
t2.rotation_euler[2]=-120*DEG
return t1,t2
def diff(a, b):
name = a.name
mod_bool = a.modifiers.new('diffy', 'BOOLEAN')
mod_bool.operation = 'DIFFERENCE'
# Set the object to be used by the modifier.
mod_bool.object = b
bpy.context.view_layer.objects.active = a
# Apply the modifier.
res = OBJ.modifier_apply(modifier = 'diffy')
OBJ.select_all(action='DESELECT')
b.select_set(True)
OBJ.delete()
a = bpy.data.objects[name]
a.select_set(True)
return a
def simplify(t):
merge_threshold = 0.01
OBJ.select_all(action='DESELECT')
t.select_set(True)
name = t.name
OBJ.mode_set(mode='EDIT')
MESH.select_all(action='SELECT')
MESH.remove_doubles(threshold = merge_threshold)
MESH.select_all(action='DESELECT')
#MESH.select_mode(type = 'FACE')
#MESH.select_interior_faces()
#MESH.delete(type='FACE')
OBJ.mode_set(mode='OBJECT')
OBJ.select_all(action='DESELECT')
t = bpy.data.objects[name]
t.select_set(True)
return t
def work(t, f, delay=0.5):
t.scale[0]=1+(E-1)*f # scale=1…E for f=0…1
t.rotation_euler=[A*DEG*f, A*DEG*f, 0]
a,b = triples(t)
t = diff(t,a)
t = diff(t,b)
t = simplify(t)
if delay:
bpy.ops.wm.redraw_timer(type='DRAW_WIN_SWAP', iterations=1)
time.sleep(delay)
return t
t = setup()
for n in range(steps+1):
t = work(t, 1-n/steps)
# if n == 11:
# break
# show the combined result
# triples(t)
# This script exhibits a problem (Blender 3.3.1, Debian Unstable).
#
# On my system, after a few steps Blender shows what looks like memory
# corruption. The "simplify" step either resurrects one of the copied
# meshes that should have been deleted by the "diff" step, adding it to the
# ring, or it creates a non-manifold fragment.
#
# Reducing E to 1.5, which causes the rings to initially overlap, triggers
# the problem on n==4 instead of 11.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment