Last active
July 6, 2021 06:27
-
-
Save arthur-flam/3704a2d91864a30562d09da8bfe593e0 to your computer and use it in GitHub Desktop.
Create DXF files for super-ellipsis / squircle
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
""" | |
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