Last active
April 12, 2016 19:14
-
-
Save mdunschen/8cb1b6ef71cd9d9d0305 to your computer and use it in GitHub Desktop.
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
# 60 pieces for one lampshade, ratio of short arm to long neck: 16/25 | |
# needs more spacing in y direction, fewer elements in x direction | |
import math | |
s60 = math.sqrt(3.0) / 2.0 | |
c60 = 0.5 | |
rad = 10 # rad of all arcs | |
l0 = 90 # neck | |
l1 = (16.0 / 25.0) * l0 # legs and arms | |
mrad = 1.5 | |
# tiling, number of pieces in x an y direction | |
nnx = 10 | |
nny = 13 | |
totalpieces = 60 # for one lampshade we only need 60 pieces | |
# the spacing | |
xstep = l1 + 2 * rad + 1.0 | |
ystep = l0 + l1 * 0.5 | |
xphase = l1 * 0.95 | |
yphase = 2 * rad + 1.0 | |
class SVGWriter: | |
def __init__(self, fn): | |
self.f = open(fn, "w") | |
self.f.write( | |
'''<?xml version="1.0" standalone="no"?> | |
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" | |
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> | |
<svg width="297mm" height="420mm" version="1.1" | |
xmlns="http://www.w3.org/2000/svg"> | |
<!-- The dimensions for this rectange are given in real | |
world units. --> | |
<rect x="0mm" y="0mm" width="297mm" height="420mm" fill="none" stroke="blue" stroke-width="1mm"/>\n''') | |
self.pathopened = False | |
self.lastP = None | |
self.trans = False | |
def openPath(self): | |
assert not self.pathopened | |
self.p = ['<path d="'] | |
self.pathopened = True | |
def moveTo(self, p): | |
assert self.pathopened | |
self.p.append("M %f,%f" % (p.u, p.v)) | |
self.lastP = p | |
def lineTo(self, p): | |
assert self.pathopened | |
self.p.append("L %f,%f" % (p.u, p.v)) | |
self.lastP = p | |
def arcTo(self, p, c): | |
assert self.pathopened | |
rad = (p - c).Len() | |
assert abs(rad - (self.lastP - c).Len()) < 1.e-5, (rad, (self.lastP - c).Len()) | |
xaxisrot = math.acos((p - c).Norm().Dot(P(1,0))) | |
large_flag, sweep_flag = 0, 0 | |
self.p.append("A %f,%f %f %d,%d %f,%f" % (rad, rad, xaxisrot, large_flag, sweep_flag, p.u, p.v)) | |
self.lastP = p | |
def closePath(self): | |
self.p.append('" fill="none" stroke="blue" stroke-width="0.01mm" />') | |
self.f.write('%s\n' % ' '.join(self.p)) | |
self.p = None | |
self.pathopened = False | |
def popTransform(self): | |
self.f.write('</g>\n') | |
self.f.write('</g>\n') | |
self.trans = False | |
def pushTransform(self, trans, rot): | |
assert not self.trans | |
self.trans = True | |
self.f.write('<g transform="translate(%f, %f)">\n' % trans) | |
self.f.write('<g transform="rotate(%f)">\n' % rot) | |
def close(self): | |
assert self.p == None | |
self.f.write('</svg>\n') | |
self.f.close() | |
class P: | |
def __init__(self, u, v): | |
self.u = u | |
self.v = v | |
def __add__(self, pp): | |
return P(self.u + pp.u, self.v + pp.v) | |
def __sub__(self, pp): | |
return P(self.u - pp.u, self.v - pp.v) | |
def Len(self): | |
return math.sqrt(self.u * self.u + self.v * self.v) | |
def Dot(self, p): | |
return self.u * p.u + self.v * p.v | |
def Norm(self): | |
l = self.Len() | |
return P(self.u / l, self.v / l) | |
def APerp(self): | |
return P(-self.v, self.u) | |
def CPerp(self): | |
return P(self.v, -self.u) | |
def draw_base(w, c): | |
p0 = c + P(rad, rad) | |
p1 = p0 + P(l1 - rad, 0) | |
# arc connecting p1, p2, p3 | |
p2 = p1 + P(rad, -rad) | |
p3 = p2 + P(-rad, -rad) | |
p4 = p3 - P(l1 - rad/c60, 0) | |
p5 = p4 + P((l1 - 2 * rad) * c60, -(l1 - 2 * rad) * s60) | |
p5_c = p5 + P(rad * c60, -rad * s60).CPerp() | |
p6 = p5 + P(rad * c60, -rad * s60) + P(rad * c60, -rad * s60).CPerp() | |
p7 = p5 + P(2 * rad * c60, -2 * rad * s60).CPerp() | |
p8 = c - P(0, 2 * rad) | |
p9 = P(c.u - p7.u, p7.v)# mirror of p7 | |
p9_c = P(c.u - p5_c.u, p5_c.v) | |
p10 = P(c.u - p6.u, p6.v)# mirror of p6 | |
p11 = P(c.u - p5.u, p5.v)# mirror of p5 | |
p12 = P(c.u - p4.u, p4.v) | |
p13 = P(c.u - p3.u, p3.v) | |
p14 = P(c.u - p2.u, p2.v) | |
p15 = P(c.u - p1.u, p1.v) | |
p16 = P(c.u - p0.u, p0.v) | |
p17 = p16 + P(0, l0 - 2 * rad) | |
p18 = c + P(0, l0) | |
p19 = P(c.u - p17.u, p17.v) | |
p20 = p0 | |
w.openPath() | |
w.moveTo(p0) | |
w.lineTo(p1) | |
w.arcTo(p2, P(p1.u, p1.v - rad)) | |
w.arcTo(p3, P(p1.u, p1.v - rad)) | |
w.lineTo(p4) | |
w.lineTo(p5) | |
w.arcTo(p6, p5_c) | |
w.arcTo(p7, p5_c) | |
w.lineTo(p8) | |
w.lineTo(p9) | |
w.arcTo(p10, p9_c) | |
w.arcTo(p11, p9_c) | |
w.lineTo(p12) | |
w.lineTo(p13) | |
w.arcTo(p14, p13 + P(0, rad)) | |
w.arcTo(p15, p14 + P(0, rad)) | |
w.lineTo(p16) | |
w.lineTo(p17) | |
w.arcTo(p18, c + P(0, l0 - rad)) | |
w.arcTo(p19, c + P(0, l0 - rad)) | |
w.lineTo(p20) | |
w.closePath() | |
# 5 circles for mounting | |
for cc in [c + P(l1, 0), c + P(-l1, 0), p5_c, p9_c, c + P(0, l0 - rad)]: | |
w.openPath() | |
w.moveTo(cc + P(mrad, 0)) | |
w.arcTo(cc + P(0, -mrad), cc) | |
w.arcTo(cc + P(-mrad, 0), cc) | |
w.arcTo(cc + P(0, mrad), cc) | |
w.arcTo(cc + P(mrad, 0), cc) | |
w.closePath() | |
if __name__ == "__main__": | |
w = SVGWriter("lamp_base.svg") | |
c0 = P(0, 0) | |
xmargin = l1 + rad + 5.0 | |
ymargin = 1.5 * l1 + 1.0 | |
yp = 0 | |
for j in range(nny): | |
if totalpieces <= 0: | |
break | |
xp = 0 | |
if j % 2: | |
xp += xphase | |
for i in range((j%2 and nnx) or (nnx + 1)): | |
if i % 2: | |
w.pushTransform((xp + xmargin, yp + yphase + ymargin), 180) | |
else: | |
w.pushTransform((xp + xmargin, yp + ymargin), 0) | |
draw_base(w, c0) | |
totalpieces -= 1 | |
w.popTransform() | |
if totalpieces <= 0: | |
break | |
xp += xstep | |
yp += ystep | |
w.close() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment