Skip to content

Instantly share code, notes, and snippets.

@arthur-flam
Last active July 6, 2021 06:27
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 arthur-flam/3704a2d91864a30562d09da8bfe593e0 to your computer and use it in GitHub Desktop.
Save arthur-flam/3704a2d91864a30562d09da8bfe593e0 to your computer and use it in GitHub Desktop.
Create DXF files for super-ellipsis / squircle
"""
Generates a DXF file for a super-ellipsis table.
It's smooth, and less pointy than an elipsis.
Usage:
1. Edit in the code
- output_file: where the output file is saved
- dimension: the half width/height for the shape
- n: interpolation points
- pp: we'll generate a layer for each of those p-parameters.
2. python table-super-ellipsis.py
3. To view output files you can use any CAD software. Installing LibreCAD [is easy](https://github.com/LibreCAD/LibreCAD/releases).
4. Cut your slab of marble. TODO: some tools require interpolation via circles arcs, not splines...
Dependencies:
- python3.7
- pip install matplotlib numpy ezdxf[draw]
References
- https://scipython.com/blog/superellipses/
- https://www.johndcook.com/blog/2014/06/07/swedish-superellipse/
- https://www.johndcook.com/blog/2018/02/13/squircle-curvature/
- https://fritzhansen.com/en/super-elliptical
"""
from dataclasses import dataclass
from typing import Iterator
import numpy as np
import matplotlib.pyplot as plt
import ezdxf
@dataclass
class Point():
x: float
y: float
def as_tuple(self):
return (self.x, self.y)
@dataclass
class Size():
width: float
height: float
@dataclass
class SuperEllipsis():
p: float
width: float
height: float
def sample_t(self, t: float) -> Point:
cos_t = np.cos(t)
sin_t = np.sin(t)
x = self.width * np.abs(cos_t)**(2/self.p) * np.sign(cos_t)
y = self.height * np.abs(sin_t)**(2/self.p) * np.sign(sin_t)
return Point(x=x, y=y)
def sample(self, n: int) -> Iterator[Point]:
"""Yields n+2 points sampling the curve"""
# yes we could operate on whole arrays at once
# t = np.linspace(0, 2*np.pi, 500)
for i in range(n):
t = i * 2*np.pi / n
yield self.sample_t(t)
yield self.sample_t(0)
# https://ezdxf.mozman.at/docs/usage_for_beginners.html#create-a-new-dxf-file
doc = ezdxf.new()# dxfversion='R2010')
doc.header['$INSUNITS'] = 6 # meters
msp = doc.modelspace()
output_file = 'shapes-multiple.dxf'
# this is the dimensions for our table
dimension = Size(width=1.580/2, height=1.100/2)
n = 1024
# n = 128
pp = [
# 2,
2.5,
# 2.758,
# 3,
# 3.5,
# 4,
# 5,
]
cmap = plt.get_cmap('Set1')
for p_idx, p in enumerate(pp):
layer_name = f'p {p}' if p != 2.0 else "ellipse"
layer = doc.layers.new(
name=layer_name,
# dxfattribs={'linetype': 'DASHED', 'color': 7}
)
color = cmap(p_idx/(len(pp)-1) if len(pp) > 1 else 1.0)
layer.rgb = (255 * color[0], 255 * color[1], 255 * color[2])
ellipsis = SuperEllipsis(p, dimension.width, dimension.height)
use_splines = True
if not use_splines:
previous_point = Point(ellipsis.width, 0)
for point in ellipsis.sample(n):
# https://ezdxf.mozman.at/docs/dxfentities/spline.html
msp.add_line(
previous_point.as_tuple(),
point.as_tuple(),
dxfattribs={
'layer': layer_name,
},
)
previous_point = point
else:
# https://ezdxf.mozman.at/docs/dxfentities/spline.html
fit_points = [ezdxf.math.Vec3(p.x, p.y, 0) for p in ellipsis.sample(n)]
spline = ezdxf.math.fit_points_to_cubic_bezier(fit_points)
spline = msp.add_spline(
fit_points,
dxfattribs={
'layer': layer_name,
},
)
# https://ezdxf.mozman.at/docs/howto/document.html#set-msp-initial-view
from ezdxf import zoom
zoom.extents(msp)
doc.saveas(output_file)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment