fabmodules/src/py/cad_shapes.py

497 wiersze
16 KiB
Python

# cad_shapes.py
# Standard library of shapes and operations
# Assembled by Matt Keeter (with code from Neil Gershenfeld)
# matt.keeter@cba.mit.edu
# kokompe.cba.mit.edu
###############################################################################
# 2D shapes:
# circle(x0, y0, r)
# rectangle(x0, x1, y0, y1)
# right_triangle(x0, y0, l)
# triangle(x0, y0, x1, y1, x2, y2) [clockwise order]
# polygon(x, y, r, n)
# tab(x, y, width, height, angle = 0, chamfer = 0.2)
# 3D shapes:
# extrusion(part, z0, z1)
# cylinder(x0, y0, z0, z1, r)
# sphere(x0, y0, z0, r)
# cube(x0, x1, y0, y1, z0, z1)
# cone(x0, y0, z0, z1, r0)
# pyramid(x0, x1, y0, y1, z0, z1)
# Logic operations:
# add(part1, part2)
# subtract(part1, part2)
# intersect(part1, part2)
# invert(part)
# Translation:
# move(part, dx, dy, dz = 0)
# Rotation:
# rotate(part, angle)
# rotate_90(part)
# rotate_180(part)
# rotate_270(part)
# rotate_x(part, angle)
# rotate_y(part, angle)
# rotate_z(part, angle)
# Reflection:
# reflect_x(part,x0 = 0)
# reflect_y(part,y0 = 0)
# reflect_z(part,z0 = 0)
# reflect_xy(part)
# reflect_xz(part)
# reflect_yz(part)
# Scaling:
# scale_x(part, x0, sx)
# scale_y(part, y0, sy)
# scale_z(part, z0, sz)
# scale_xy(part, x0, y0, sxy)
# scale_xyz(part, x0, y0, z0, sxyz)
# Distortion:
# attract(part, value, x0, y0, z0=0)
# Coscaling:
# coscale_x_y(part, x0, y0, y1, angle0, angle1, amplitude, offset)
# coscale_x_z(part, x0, z0, z1, angle0, angle1, amplitude, offset)
# coscale_xy_z(part, x0, y0, z0, z1, angle0, angle1, amplitude, offset)
# Tapering:
# taper_x_y(part, x0, y0, y1, s0, s1)
# taper_x_z(part, x0, z0, z1, s0, s1)
# taper_xy_z(part, x0, y0, z0, z1, s0, s1)
# Shearing:
# shear_x_y(part, y0, y1, dx0, dx1)
# shear_x_z(part, z0, z1, dx0, dx1)
# coshear_x_z(part, z0, z1, angle0, angle1, amplitude, offset)
# Color:
# color(color,part)
#
# Colors are implemented as an integer bit-field:
# R = bits 0 through 7
# G = bits 8 through 15
# B = bits 16 through 23
#
# The following colors are pre-defined:
# red, green, blue, gray, white, teal, pink,
# yellow, brown, navy, black
###############################################################################
from math_string import MString
from math import pi, sin, cos
import re
###############################################################################
# 2D Shapes
###############################################################################
def circle(x0, y0, r):
return move(MString("(pow(X,2) + pow(Y,2) <= %f)" % (r*r)), x0, y0)
def rectangle(x0, x1, y0, y1):
return MString("((X >= %f) && (X <= %f) && " % (x0, x1) +
"(Y >= %f) && (Y <= %f))" % (y0, y1))
def right_triangle(x, y, L):
tri = MString("(X < -Y) && (X > -{0}) && (Y > -{0})".format(L))
return move(tri, x + L, y + L)
def triangle(x0, y0, x1, y1, x2, y2): # points in clockwise order
tri = ("((((({y1})-({y0}))*(X-({x0}))-(({x1})-({x0}))*(Y-({y0}))) >= 0) && " + \
"(((({y2})-({y1}))*(X-({x1}))-(({x2})-({x1}))*(Y-({y1}))) >= 0) && " + \
"(((({y0})-({y2}))*(X-({x2}))-(({x0})-({x2}))*(Y-({y2}))) >= 0))").format(
x0 = x0, y0 = y0, x1 = x1, y1 = y1, x2 = x2, y2 = y2)
return MString(tri)
def polygon(x, y, r, n):
if n <= 2:
return '0'
part = circle(0, 0, r)
cutoff = 'Y > -%f' % (cos(pi/n) * r)
for i in range(n):
part = intersect(part, rotate(cutoff, i*360./n))
return move(MString(part), x, y)
def tab(x, y, width, height, angle = 0, chamfer = 0.2):
tab = rectangle(-width/2, width/2, 0, height)
cutout = triangle(width/2 - chamfer*height, height,
width/2, height,
width/2, height - chamfer*height)
cutout = add(cutout, reflect_x(cutout))
tab = subtract(tab, cutout)
tab = rotate(tab, angle)
tab = move(tab, x, y)
return MString(tab)
def slot(x, y, width, height, angle = 0, chamfer = 0.2):
slot = rectangle(-width/2, width/2, -height, 0)
inset = triangle(width/2, 0,
width/2 + height * chamfer, 0,
width/2, -chamfer*height)
inset = add(inset, reflect_x(inset))
slot = add(slot, inset)
slot = rotate(slot, angle)
slot = move(slot, x, y)
return MString(slot)
###############################################################################
# 3D Shapes
###############################################################################
def extrusion(part, z0, z1):
return MString("((%s) && (Z >= %f) && (Z <= %f))" % (part, z0, z1))
def cylinder(x0, y0, z0, z1, r):
return extrusion(circle(x0, y0, r), z0, z1)
def sphere(x0, y0, z0, r):
return move("(pow(X,2) + pow(Y,2) + pow(Z,2) <= %f)" % (r * r), x0, y0, z0)
def cube(x0, x1, y0, y1, z0, z1):
return MString("((X >= %f) && (X <= %f) && " % (x0, x1) +
" (Y >= %f) && (Y <= %f) && " % (y0, y1) +\
" (Z >= %f) && (Z <= %f))" % (z0, z1))
def cone(x0, y0, z0, z1, r0):
cyl = cylinder(x0, y0, z0, z1, r0)
return taper_xy_z(cyl, x0, y0, z0, z1, 1.0, 0.0)
def pyramid(x0, x1, y0, y1, z0, z1):
c = cube(x0, x1, y0, y1, z0, z1)
return taper_xy_z(c, (x0+x1)/2., (y0+y1)/2., z0, z1, 1.0, 0.0)
###############################################################################
# Logic Operations
###############################################################################
def add(part1, part2):
return MString("(%s) || (%s)" % (part1, part2))
def subtract(part1, part2):
return MString("(%s) && !(%s)" % (part1, part2))
def intersect(part1, part2):
return MString("(%s) && (%s)" % (part1, part2))
def invert(part):
return MString("!(%s)" % part)
###############################################################################
# Translation
###############################################################################
def move(part,dx,dy,dz = 0):
if dx:
part = part.replace('X','(X-%f)' % dx)
if dy:
part = part.replace('Y','(Y-%f)' % dy)
if dz:
part = part.replace('Z','(Z-%f)' % dz)
return part
###############################################################################
# Rotation
###############################################################################
def rotate(part, angle):
if angle == 90:
return rotate_90(part)
elif angle == 180:
return rotate_180(part)
elif angle == 270:
return rotate_270(part)
elif angle == 0:
return part
angle = str(angle*pi/180)
# find all already-rotated points and increment that
# rotation instead of stacking more and more trig
# functions
xrot_regex = re.compile(r'\( cos\(([\-\d.]+)\)\*X\+sin\(\1\)\*Y\)')
yrot_regex = re.compile(r'\(-sin\(([\-\d.]+)\)\*X\+cos\(\1\)\*Y\)')
def update_angle_x(match, angle):
match = match.groups()
angle = str(float(angle) + float(match[0]))
return '(cos({0})*x+sin({0})*y)'.format(angle)
def update_angle_y(match, angle):
match = match.groups()
angle = str(float(angle) + float(match[0]))
return '(-sin({0})*x+cos({0})*y)'.format(angle)
part = xrot_regex.sub(lambda x: update_angle_x(x, angle), str(part))
part = yrot_regex.sub(lambda x: update_angle_y(x, angle), str(part))
# replace all bare X, Y as before
# move all upper to lower case
part = part.replace('X','(cos({0})*X+sin({0})*y)'.format(angle))
part = part.replace('Y','(-sin({0})*X+cos({0})*y)'.format(angle))
part = part.replace('y','Y')
part = part.replace('x','X')
return MString(part)
def rotate_90(part):
part = reflect_y(part)
part = reflect_xy(part)
return part
def rotate_180(part):
part = rotate_90(part)
part = rotate_90(part)
return part
def rotate_270(part):
part = rotate_90(part)
part = rotate_90(part)
part = rotate_90(part)
return part
def rotate_x(part, angle):
angle = angle*pi/180
part = part.replace('Y','(cos(angle)*Y+sin(angle)*z)')
part = part.replace('Z','(-sin(angle)*Y+cos(angle)*z)')
part = part.replace('z','Z')
part = part.replace('angle',str(angle))
return part
def rotate_y(part, angle):
angle = angle*pi/180
part = part.replace('X','(cos(angle)*X+sin(angle)*z)')
part = part.replace('Z','(-sin(angle)*X+cos(angle)*z)')
part = part.replace('z','Z')
part = part.replace('angle',str(angle))
return part
def rotate_z(part, angle):
return rotate(part, angle)
###############################################################################
# Reflection
###############################################################################
def reflect_x(part, x0 = 0):
return part.replace('X','(%f-X)' % x0)
def reflect_y(part, y0 = 0):
return part.replace('Y','(%f-Y)' % y0)
def reflect_z(part,z0):
return part.replace('Z','(%f-Z)' % z0)
return part
def reflect_xy(part):
part = part.replace('X','temp')
part = part.replace('Y','X')
part = part.replace('temp','Y')
return part
def reflect_xz(part):
part = part.replace('X','temp')
part = part.replace('Z','X')
part = part.replace('temp','Z')
return part
def reflect_yz(part):
part = part.replace('Y','temp')
part = part.replace('Z','Y')
part = part.replace('temp','Z')
return part
###############################################################################
# Scaling
###############################################################################
def scale_x(part, x0, sx):
part = part.replace('X','((x0) + (X-(x0))/(sx))')
part = part.replace('x0',str(x0))
part = part.replace('sx',str(sx))
return part
def scale_y(part, y0, sy):
part = part.replace('Y','((y0) + (Y-(y0))/(sy))')
part = part.replace('y0',str(y0))
part = part.replace('sy',str(sy))
return part
def scale_z(part, z0, sz):
part = part.replace('Z','((z0) + (Z-(z0))/(sz))')
part = part.replace('z0',str(z0))
part = part.replace('sz',str(sz))
return part
def scale_xy(part, x0, y0, sxy):
part = part.replace('X','((x0) + (X-(x0))/(sxy))')
part = part.replace('Y','((y0) + (Y-(y0))/(sxy))')
part = part.replace('x0',str(x0))
part = part.replace('y0',str(y0))
part = part.replace('sxy',str(sxy))
return part
def scale_xyz(part, x0, y0, z0, sxyz):
part = part.replace('X','((x0) + (X-(x0))/(sxyz))')
part = part.replace('Y','((y0) + (Y-(y0))/(sxyz))')
part = part.replace('Z','((z0) + (Z-(z0))/(sxyz))')
part = part.replace('x0',str(x0))
part = part.replace('y0',str(y0))
part = part.replace('z0',str(z0))
part = part.replace('sxyz',str(sxyz))
return part
###############################################################################
# Distortion:
###############################################################################
def attract(part, radius, x0, y0, z0=0):
part = part.replace('X',
('({x0}+(X-{x0})*(1+{r}*exp(-pow(X-{x0},2)+'+
'pow(yt-{y0},2)+pow(zt-{z0},2)/{r})))').format(
x0=x0,y0=y0,z0=z0,r=radius))
part = part.replace('Y',
('({y0}+(Y-{y0})*(1+{r}*exp(-pow(xt-{x0},2)+'+
'pow(Y-{y0},2)+pow(zt-{z0},2)/{r})))').format(
x0=x0,y0=y0,z0=z0,r=radius))
part = part.replace('Z',
('({z0}+(Z-{z0})*(1+{r}*exp(-pow(xt-{x0},2)+'+
'pow(yt-{y0},2)+pow(Z-{z0},2)/{r})))').format(
x0=x0,y0=y0,z0=z0,r=radius))
part = part.replace('xt','X')
part = part.replace('yt','Y')
part = part.replace('zt','Z')
return part
###############################################################################
# Coscaling
###############################################################################
def coscale_x_y(part, x0, y0, y1, angle0, angle1, amplitude, offset):
phase0 = pi*angle0/180.
phase1 = pi*angle1/180.
part = part.replace('X','((x0) + (X-(x0))/((offset) + (amplitude)*cos((phase0) + ((phase1)-(phase0))*(Y-(y0))/((y1)-(y0)))))')
part = part.replace('x0',str(x0))
part = part.replace('y0',str(y0))
part = part.replace('y1',str(y1))
part = part.replace('phase0',str(phase0))
part = part.replace('phase1',str(phase1))
part = part.replace('amplitude',str(amplitude))
part = part.replace('offset',str(offset))
return part
def coscale_x_z(part, x0, z0, z1, angle0, angle1, amplitude, offset):
phase0 = pi*angle0/180.
phase1 = pi*angle1/180.
part = part.replace('X','((x0) + (X-(x0))/((offset) + (amplitude)*cos((phase0) + ((phase1)-(phase0))*(Z-(z0))/((z1)-(z0)))))')
part = part.replace('x0',str(x0))
part = part.replace('z0',str(z0))
part = part.replace('z1',str(z1))
part = part.replace('phase0',str(phase0))
part = part.replace('phase1',str(phase1))
part = part.replace('amplitude',str(amplitude))
part = part.replace('offset',str(offset))
return part
def coscale_xy_z(part, x0, y0, z0, z1, angle0, angle1, amplitude, offset):
phase0 = pi*angle0/180.
phase1 = pi*angle1/180.
part = part.replace('X','((x0) + (X-(x0))/((offset) + (amplitude)*cos((phase0) + ((phase1)-(phase0))*(Z-(z0))/((z1)-(z0)))))')
part = part.replace('Y','((y0) + (Y-(y0))/((offset) + (amplitude)*cos((phase0) + ((phase1)-(phase0))*(Z-(z0))/((z1)-(z0)))))')
part = part.replace('x0',str(x0))
part = part.replace('y0',str(y0))
part = part.replace('z0',str(z0))
part = part.replace('z1',str(z1))
part = part.replace('phase0',str(phase0))
part = part.replace('phase1',str(phase1))
part = part.replace('amplitude',str(amplitude))
part = part.replace('offset',str(offset))
return part
###############################################################################
# Tapering
###############################################################################
def taper_x_y(part, x0, y0, y1, s0, s1):
part = part.replace('X','((x0) + (X-(x0))*((y1)-(y0))/((s1)*(Y-(y0)) + (s0)*((y1)-Y)))')
part = part.replace('x0',str(x0))
part = part.replace('y0',str(y0))
part = part.replace('y1',str(y1))
part = part.replace('s0',str(s0))
part = part.replace('s1',str(s1))
return part
def taper_x_z(part, x0, z0, z1, s0, s1):
part = part.replace('X','((x0) + (X-(x0))*((z1)-(z0))/((s1)*(Z-(z0)) + (s0)*((z1)-Z)))')
part = part.replace('x0',str(x0))
part = part.replace('z0',str(z0))
part = part.replace('z1',str(z1))
part = part.replace('s0',str(s0))
part = part.replace('s1',str(s1))
return part
def taper_xy_z(part, x0, y0, z0, z1, s0, s1):
part = part.replace('X','((x0) + (X-(x0))*((z1)-(z0))/((s1)*(Z-(z0)) + (s0)*((z1)-Z)))')
part = part.replace('Y','((y0) + (Y-(y0))*((z1)-(z0))/((s1)*(Z-(z0)) + (s0)*((z1)-Z)))')
part = part.replace('x0',str(x0))
part = part.replace('y0',str(y0))
part = part.replace('z0',str(z0))
part = part.replace('z1',str(z1))
part = part.replace('s0',str(s0))
part = part.replace('s1',str(s1))
return part
###############################################################################
# Shearing
###############################################################################
def shear_x_y(part, y0, y1, dx0, dx1):
part = part.replace('X','(X - (dx0) - ((dx1)-(dx0))*(Y-(y0))/((y1)-(y0)))')
part = part.replace('y0',str(y0))
part = part.replace('y1',str(y1))
part = part.replace('dx0',str(dx0))
part = part.replace('dx1',str(dx1))
return part
def shear_x_z(part, z0, z1, dx0, dx1):
part = part.replace('X','(X - (dx0) - ((dx1)-(dx0))*(Z-(z0))/((z1)-(z0)))')
part = part.replace('z0',str(z0))
part = part.replace('z1',str(z1))
part = part.replace('dx0',str(dx0))
part = part.replace('dx1',str(dx1))
return part
def coshear_x_z(part, z0, z1, angle0, angle1, amplitude, offset):
phase0 = pi*angle0/180.
phase1 = pi*angle1/180.
part = part.replace('X','(X - (offset) - (amplitude)*cos((phase0) + ((phase1)-(phase0))*(Z-(z0))/((z1)-(z0))))')
part = part.replace('z0',str(z0))
part = part.replace('z1',str(z1))
part = part.replace('phase0',str(phase0))
part = part.replace('phase1',str(phase1))
part = part.replace('amplitude',str(amplitude))
part = part.replace('offset',str(offset))
return part
###############################################################################
# Color
###############################################################################
red = (225 << 0)
green = (225 << 8)
blue = (225 << 16)
gray = (128 << 16) + (128 << 8) + (128 << 0)
white = (255 << 16) + (255 << 8) + (255 << 0)
teal = (255 << 16) + (255 << 8)
pink = (255 << 16) + (255 << 0)
yellow = (255 << 8) + (255 << 0)
brown = (45 << 16) + (82 << 8) + (145 << 0)
navy = (128 << 16) + (0 << 8) + (0 << 0)
tan = (60 << 16) + (90 << 8) + (125 << 0)
black = 0
def color(color, part):
return MString('(%s * (%s))' % (color, part))