Initial broken code
This commit is contained in:
commit
255844589f
3 changed files with 51592 additions and 0 deletions
91
CLAUDE.md
Normal file
91
CLAUDE.md
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
# Catastrophe Theory — 3D Print Models
|
||||
|
||||
## Project Overview
|
||||
|
||||
This project generates 3D-printable STL models of surfaces from **catastrophe theory** — a branch of mathematics studying how small changes in parameters can cause sudden, discontinuous changes in a system's equilibrium state.
|
||||
|
||||
Two surfaces are being produced:
|
||||
|
||||
| Model | Catastrophe Type | Codimension | Potential |
|
||||
|-------|-----------------|-------------|-----------|
|
||||
| Cusp | Cusp catastrophe | 2 | x⁴ + ax² + bx |
|
||||
| Butterfly | Butterfly catastrophe | 4 | x⁶ + ax⁴ + bx³ + cx² + dx |
|
||||
|
||||
---
|
||||
|
||||
## The Mathematics
|
||||
|
||||
Each surface is the **equilibrium manifold** — the set of all points where the system is in equilibrium. For a potential V(x), equilibria satisfy:
|
||||
|
||||
> dV/dx = 0
|
||||
|
||||
The 3D surface is swept over the control parameter space (a, b), with x (the state variable) as the third axis. Where the surface folds back on itself is the **bifurcation set** — the region where the system can catastrophically jump between states.
|
||||
|
||||
### Butterfly Catastrophe
|
||||
|
||||
- **Potential:** V(x) = x⁶ + ax⁴ + bx³ (with c=0, d=0 fixed)
|
||||
- **Equilibrium condition:** dV/dx = 6x⁵ + 4ax³ + 3bx² = 0
|
||||
- **Control space:** (a, b) swept over a 2D grid
|
||||
- **State space:** up to 5 real roots x at any given (a, b)
|
||||
- **Characteristic feature:** nested "butterfly wing" fold structure — more complex than the cusp, with additional inner fold lobes
|
||||
|
||||
---
|
||||
|
||||
## Files
|
||||
|
||||
- `butterfly_catastrophe.py` — generates the butterfly surface STL
|
||||
- `butterfly_catastrophe.stl` — ready-to-slice output (ASCII STL, ~7,300 triangles)
|
||||
- `CLAUDE.md` — this file
|
||||
|
||||
> A cusp catastrophe script also exists and was the starting point for this project.
|
||||
|
||||
---
|
||||
|
||||
## How the Generator Works
|
||||
|
||||
1. **Root finding** — at each (a, b) grid point, all real roots of dV/dx = 0 are found using Newton-Raphson with dense initial seeding across the x range
|
||||
2. **Branch tracking** — roots are sorted and matched by branch index across adjacent grid cells
|
||||
3. **Mesh construction** — adjacent grid quads on the same branch are triangulated into a surface mesh
|
||||
4. **Base slab** — a flat rectangular base is added so the model is self-supporting on a print bed
|
||||
5. **ASCII STL output** — written as ASCII (not binary) for maximum compatibility with slicers and viewers
|
||||
|
||||
---
|
||||
|
||||
## Running the Generator
|
||||
|
||||
```bash
|
||||
python butterfly_catastrophe.py
|
||||
```
|
||||
|
||||
Output: `butterfly_catastrophe.stl`
|
||||
|
||||
### Tuning Parameters (inside the script)
|
||||
|
||||
| Parameter | Default | Effect |
|
||||
|-----------|---------|--------|
|
||||
| `grid` | `40` | Resolution of the (a,b) control grid — increase for finer mesh |
|
||||
| `a_vals` range | `(-2.5, 1.2)` | Range of control parameter a |
|
||||
| `b_vals` range | `(-2.5, 2.5)` | Range of control parameter b |
|
||||
| `x_range` | `2.2` | Search window for equilibrium roots |
|
||||
|
||||
For a final high-quality print, increase `grid` to `80`–`100`. The default of `40` is optimised for STL viewer compatibility.
|
||||
|
||||
---
|
||||
|
||||
## 3D Printing Tips
|
||||
|
||||
- **Orientation:** flat base down — no supports needed
|
||||
- **Layer height:** 0.15–0.20 mm for good surface detail
|
||||
- **Perimeters:** ≥ 2, as the fold regions are thin
|
||||
- **Scale:** ~120 mm along the a-axis makes a good desk model
|
||||
- **Material:** PLA or PETG both work well; the overhangs are gentle
|
||||
|
||||
---
|
||||
|
||||
## Dependencies
|
||||
|
||||
```
|
||||
numpy
|
||||
```
|
||||
|
||||
No other dependencies — STL writing uses Python's built-in `struct` module (binary) or plain file I/O (ASCII).
|
||||
147
butterfly_catastrophe.py
Normal file
147
butterfly_catastrophe.py
Normal file
|
|
@ -0,0 +1,147 @@
|
|||
"""
|
||||
Butterfly Catastrophe Surface - ASCII STL Generator
|
||||
Potential: V(x) = x^6 + a*x^4 + b*x^3
|
||||
Equilibrium surface: dV/dx = 6x^5 + 4a*x^3 + 3b*x^2 = 0
|
||||
"""
|
||||
|
||||
import numpy as np
|
||||
import os
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 1. Find equilibrium roots of dV/dx = 0
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def dV(x, a, b):
|
||||
return 6*x**5 + 4*a*x**3 + 3*b*x**2
|
||||
|
||||
def d2V(x, a, b):
|
||||
return 30*x**4 + 12*a*x**2 + 6*b*x
|
||||
|
||||
def find_roots(a, b, n_starts=40, x_range=2.2):
|
||||
xs = np.linspace(-x_range, x_range, n_starts)
|
||||
roots = []
|
||||
for x0 in xs:
|
||||
x = float(x0)
|
||||
for _ in range(100):
|
||||
fx = dV(x, a, b)
|
||||
dfx = d2V(x, a, b)
|
||||
if abs(dfx) < 1e-14:
|
||||
break
|
||||
step = fx / dfx
|
||||
x -= step
|
||||
if abs(step) < 1e-10:
|
||||
break
|
||||
if abs(dV(x, a, b)) < 1e-7 and abs(x) <= x_range + 0.05:
|
||||
if not any(abs(x - r) < 1e-4 for r in roots):
|
||||
roots.append(x)
|
||||
return sorted(roots)
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 2. Build mesh
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def build_mesh(grid=40):
|
||||
a_vals = np.linspace(-2.5, 1.2, grid)
|
||||
b_vals = np.linspace(-2.5, 2.5, grid)
|
||||
|
||||
# Pre-compute roots at every grid point
|
||||
roots_grid = [[find_roots(a, b) for b in b_vals] for a in a_vals]
|
||||
|
||||
triangles = []
|
||||
|
||||
for i in range(grid - 1):
|
||||
for j in range(grid - 1):
|
||||
corners_roots = [
|
||||
roots_grid[i ][j ],
|
||||
roots_grid[i+1][j ],
|
||||
roots_grid[i+1][j+1],
|
||||
roots_grid[i ][j+1],
|
||||
]
|
||||
a_c = [a_vals[i], a_vals[i+1], a_vals[i+1], a_vals[i ]]
|
||||
b_c = [b_vals[j], b_vals[j ], b_vals[j+1], b_vals[j+1]]
|
||||
|
||||
max_branch = max(len(r) for r in corners_roots)
|
||||
|
||||
for branch in range(max_branch):
|
||||
pts = []
|
||||
for k in range(4):
|
||||
rs = corners_roots[k]
|
||||
if rs:
|
||||
x = rs[branch] if branch < len(rs) else rs[-1]
|
||||
else:
|
||||
pts = None
|
||||
break
|
||||
pts.append((a_c[k], b_c[k], x))
|
||||
|
||||
if pts and len(pts) == 4:
|
||||
p0, p1, p2, p3 = pts
|
||||
triangles.append((p0, p1, p2))
|
||||
triangles.append((p0, p2, p3))
|
||||
|
||||
return triangles
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 3. Add flat base
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def add_base(triangles, z_base=-2.4):
|
||||
a0, a1 = -2.5, 1.2
|
||||
b0, b1 = -2.5, 2.5
|
||||
zb, zt = z_base, z_base + 0.15
|
||||
|
||||
triangles += [
|
||||
((a0,b0,zt),(a1,b0,zt),(a1,b1,zt)),
|
||||
((a0,b0,zt),(a1,b1,zt),(a0,b1,zt)),
|
||||
((a0,b0,zb),(a1,b1,zb),(a1,b0,zb)),
|
||||
((a0,b0,zb),(a0,b1,zb),(a1,b1,zb)),
|
||||
]
|
||||
walls = [
|
||||
((a0,b0),(a1,b0)), ((a1,b0),(a1,b1)),
|
||||
((a1,b1),(a0,b1)), ((a0,b1),(a0,b0)),
|
||||
]
|
||||
for (x0,y0),(x1,y1) in walls:
|
||||
triangles += [
|
||||
((x0,y0,zb),(x1,y1,zb),(x1,y1,zt)),
|
||||
((x0,y0,zb),(x1,y1,zt),(x0,y0,zt)),
|
||||
]
|
||||
return triangles
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 4. Write ASCII STL
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def normal(v0, v1, v2):
|
||||
a = np.subtract(v1, v0)
|
||||
b = np.subtract(v2, v0)
|
||||
n = np.cross(a, b)
|
||||
length = np.linalg.norm(n)
|
||||
return n / length if length > 1e-14 else np.array([0, 0, 1])
|
||||
|
||||
def write_ascii_stl(triangles, filename):
|
||||
with open(filename, 'w') as f:
|
||||
f.write('solid butterfly_catastrophe\n')
|
||||
for tri in triangles:
|
||||
v0, v1, v2 = [np.array(v, dtype=float) for v in tri]
|
||||
nx, ny, nz = normal(v0, v1, v2)
|
||||
f.write(f' facet normal {nx:.6e} {ny:.6e} {nz:.6e}\n')
|
||||
f.write(' outer loop\n')
|
||||
for v in (v0, v1, v2):
|
||||
f.write(f' vertex {v[0]:.6e} {v[1]:.6e} {v[2]:.6e}\n')
|
||||
f.write(' endloop\n')
|
||||
f.write(' endfacet\n')
|
||||
f.write('endsolid butterfly_catastrophe\n')
|
||||
size_kb = os.path.getsize(filename) / 1024
|
||||
print(f' Triangles : {len(triangles):,}')
|
||||
print(f' File size : {size_kb:.0f} KB → {filename}')
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 5. Main
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
if __name__ == '__main__':
|
||||
print('Building butterfly catastrophe mesh (grid=40)…')
|
||||
tris = build_mesh(grid=40)
|
||||
tris = add_base(tris)
|
||||
print('Writing ASCII STL…')
|
||||
write_ascii_stl(tris, 'butterfly_catastrophe.stl')
|
||||
print('Done.')
|
||||
51354
butterfly_catastrophe.stl
Normal file
51354
butterfly_catastrophe.stl
Normal file
File diff suppressed because it is too large
Load diff
Loading…
Add table
Reference in a new issue