2013-04-01 09:42:29 +02:00
|
|
|
#!/usr/bin/python
|
2014-03-16 18:26:12 +01:00
|
|
|
# Copyright (C) 2013-2014 Florian Festi
|
|
|
|
#
|
|
|
|
# This program is free software: you can redistribute it and/or modify
|
|
|
|
# it under the terms of the GNU General Public License as published by
|
|
|
|
# the Free Software Foundation, either version 3 of the License, or
|
|
|
|
# (at your option) any later version.
|
|
|
|
#
|
|
|
|
# This program is distributed in the hope that it will be useful,
|
|
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
# GNU General Public License for more details.
|
|
|
|
#
|
|
|
|
# You should have received a copy of the GNU General Public License
|
|
|
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
2013-04-01 09:42:29 +02:00
|
|
|
|
|
|
|
import cairo
|
|
|
|
import math
|
2013-05-14 17:52:33 +02:00
|
|
|
from functools import wraps
|
2013-04-01 09:42:29 +02:00
|
|
|
|
2013-04-17 13:25:30 +02:00
|
|
|
def dist(dx, dy):
|
|
|
|
return (dx*dx+dy*dy)**0.5
|
2013-04-01 09:42:29 +02:00
|
|
|
|
2013-05-14 17:52:33 +02:00
|
|
|
def restore(func):
|
|
|
|
@wraps(func)
|
|
|
|
def f(self, *args, **kw):
|
|
|
|
self.ctx.save()
|
2013-06-07 12:50:13 +02:00
|
|
|
pt = self.ctx.get_current_point()
|
2013-05-14 17:52:33 +02:00
|
|
|
func(self, *args, **kw)
|
|
|
|
self.ctx.restore()
|
2013-06-07 12:50:13 +02:00
|
|
|
self.ctx.move_to(*pt)
|
2013-05-14 17:52:33 +02:00
|
|
|
return f
|
|
|
|
|
2013-06-15 23:26:22 +02:00
|
|
|
class BoltPolicy:
|
|
|
|
"""Abstract class
|
|
|
|
Distributes (bed) bolts on a number of segments
|
|
|
|
(fingers of a finger joint)
|
|
|
|
"""
|
|
|
|
def drawbolt(self, pos):
|
|
|
|
"""Add a bolt to this segment?"""
|
|
|
|
return False
|
|
|
|
|
|
|
|
def numFingers(self, numfingers):
|
|
|
|
"""returns next smaller, possible number of fingers"""
|
|
|
|
return numFingers
|
|
|
|
|
|
|
|
def _even(self, numFingers):
|
|
|
|
return (numFingers//2) * 2
|
|
|
|
def _odd(self, numFingers):
|
|
|
|
if numFingers % 2:
|
|
|
|
return numFingers
|
|
|
|
else:
|
|
|
|
return numFingers - 1
|
|
|
|
|
|
|
|
class Bolts(BoltPolicy):
|
|
|
|
"""Distribute a fixed number of bolts evenly"""
|
|
|
|
def __init__(self, bolts=1):
|
|
|
|
self.bolts = bolts
|
|
|
|
def numFingers(self, numFingers):
|
|
|
|
if self.bolts % 2:
|
|
|
|
self.fingers = self._even(numFingers)
|
|
|
|
else:
|
|
|
|
self.fingers = numFingers
|
|
|
|
return self.fingers
|
|
|
|
|
|
|
|
def drawBolt(self, pos):
|
|
|
|
if pos > self.fingers//2:
|
|
|
|
pos = self.fingers - pos
|
|
|
|
if pos==0:
|
|
|
|
return False
|
|
|
|
if pos == self.fingers//2 and not (self.bolts % 2):
|
|
|
|
return False
|
|
|
|
result = (math.floor((float(pos)*(self.bolts+1)/self.fingers)-0.01) !=
|
|
|
|
math.floor((float(pos+1)*(self.bolts+1)/self.fingers)-0.01))
|
|
|
|
#print pos, result, ((float(pos)*(self.bolts+1)/self.fingers)-0.01), ((float(pos+1)*(self.bolts+1)/self.fingers)-0.01)
|
|
|
|
return result
|
2013-05-14 17:52:33 +02:00
|
|
|
|
2013-07-20 10:49:45 +02:00
|
|
|
#############################################################################
|
|
|
|
### Settings
|
|
|
|
#############################################################################
|
|
|
|
|
|
|
|
class Settings:
|
|
|
|
absolute_params = { }
|
|
|
|
relative_params = { }
|
|
|
|
|
|
|
|
def __init__(self, thickness, relative=True, **kw):
|
|
|
|
self.values = self.absolute_params.copy()
|
|
|
|
|
|
|
|
factor = 1.0
|
|
|
|
if relative:
|
|
|
|
factor = thickness
|
2014-03-03 21:45:01 +01:00
|
|
|
for name, value in self.relative_params.items():
|
2013-07-20 10:49:45 +02:00
|
|
|
self.values[name] = value * factor
|
|
|
|
self.setValues(thickness, relative, **kw)
|
|
|
|
|
|
|
|
def setValues(self, thickness, relative=True, **kw):
|
|
|
|
factor = 1.0
|
|
|
|
if relative:
|
|
|
|
factor = thickness
|
2014-03-03 21:45:01 +01:00
|
|
|
for name, value in kw.items():
|
2013-07-20 10:49:45 +02:00
|
|
|
if name in self.absolute_params:
|
|
|
|
self.values[name] = value
|
|
|
|
elif name in self.relative_params:
|
|
|
|
self.values[name] = value * factor
|
|
|
|
else:
|
2014-03-03 21:45:01 +01:00
|
|
|
raise ValueError("Unknown parameter for %s: %s" % (
|
|
|
|
self.__class__.__name__, name))
|
2013-07-20 10:49:45 +02:00
|
|
|
|
|
|
|
def __getattr__(self, name):
|
|
|
|
return self.values[name]
|
|
|
|
|
|
|
|
#############################################################################
|
|
|
|
### Edges
|
|
|
|
#############################################################################
|
|
|
|
|
|
|
|
|
|
|
|
class Edge:
|
|
|
|
char = 'e'
|
|
|
|
|
|
|
|
def __init__(self, boxes, settings):
|
|
|
|
self.boxes = boxes
|
|
|
|
self.ctx = boxes.ctx
|
|
|
|
self.settings = settings
|
|
|
|
|
|
|
|
def __getattr__(self, name):
|
|
|
|
"""Hack for using unalter code form Boxes class"""
|
|
|
|
return getattr(self.boxes, name)
|
|
|
|
|
|
|
|
def __call__(self, length, **kw):
|
|
|
|
self.ctx.move_to(0,0)
|
|
|
|
self.ctx.line_to(length, 0)
|
|
|
|
self.ctx.translate(*self.ctx.get_current_point())
|
|
|
|
|
|
|
|
def width(self):
|
|
|
|
return 0.0
|
|
|
|
|
|
|
|
def margin(self):
|
|
|
|
return self.boxes.spacing
|
|
|
|
|
|
|
|
def spacing(self):
|
|
|
|
return self.width() + self.margin()
|
|
|
|
|
|
|
|
def startAngle(self):
|
|
|
|
return 0.0
|
|
|
|
|
|
|
|
def endAngle(self):
|
|
|
|
return 0.0
|
|
|
|
|
|
|
|
class OutSetEdge(Edge):
|
|
|
|
char = 'E'
|
|
|
|
|
|
|
|
def width(self):
|
|
|
|
return self.boxes.thickness
|
|
|
|
|
2014-04-06 16:21:29 +02:00
|
|
|
class Slot(Edge):
|
|
|
|
def __init__(self, boxes, depth):
|
|
|
|
Edge.__init__(self, boxes, None)
|
|
|
|
self.depth = depth
|
|
|
|
|
|
|
|
def __call__(self, length, **kw):
|
|
|
|
if self.depth:
|
|
|
|
self.boxes.corner(90)
|
|
|
|
self.boxes.edge(self.depth)
|
|
|
|
self.boxes.corner(-90)
|
|
|
|
self.boxes.edge(length)
|
|
|
|
self.boxes.corner(-90)
|
|
|
|
self.boxes.edge(self.depth)
|
|
|
|
self.boxes.corner(90)
|
|
|
|
else:
|
|
|
|
self.boxes.edge(self.length)
|
|
|
|
|
|
|
|
class SlottedEdge(Edge):
|
|
|
|
|
|
|
|
def __init__(self, boxes, sections, edge="e", slots=0):
|
|
|
|
Edge.__init__(self, boxes, None)
|
|
|
|
self.edge = self.edges.get(edge, edge)
|
|
|
|
self.sections = sections
|
|
|
|
self.slots = slots
|
|
|
|
|
|
|
|
def width(self):
|
|
|
|
return self.edge.width()
|
|
|
|
|
|
|
|
def margin(self):
|
|
|
|
return self.edge.margin()
|
|
|
|
|
|
|
|
def __call__(self, length, **kw):
|
|
|
|
for l in self.sections[:-1]:
|
|
|
|
self.edge(l)
|
|
|
|
if self.slots:
|
|
|
|
Slot(self.boxes, self.slots)(self.thickness)
|
|
|
|
else:
|
|
|
|
self.edge(self.thickness)
|
|
|
|
self.edge(self.sections[-1])
|
2013-07-20 10:49:45 +02:00
|
|
|
|
|
|
|
class FingerJointSettings(Settings):
|
2013-07-25 14:15:30 +02:00
|
|
|
absolute_params = {
|
|
|
|
"surroundingspaces" : 2,
|
|
|
|
}
|
|
|
|
|
2013-07-20 10:49:45 +02:00
|
|
|
relative_params = {
|
|
|
|
"space" : 1.0,
|
|
|
|
"finger" : 1.0,
|
2013-07-20 13:10:03 +02:00
|
|
|
"height" : 1.0,
|
|
|
|
"width" : 1.0,
|
2013-07-20 10:49:45 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
class FingerJointEdge(Edge):
|
|
|
|
char = 'f'
|
|
|
|
positive = True
|
|
|
|
|
|
|
|
def __call__(self, length,
|
|
|
|
bedBolts=None, bedBoltSettings=None, **kw):
|
|
|
|
positive = self.positive
|
|
|
|
space, finger = self.settings.space, self.settings.finger
|
|
|
|
|
2013-07-25 14:15:30 +02:00
|
|
|
fingers = int((length-(self.settings.surroundingspaces-1)*space) //
|
|
|
|
(space+finger))
|
2013-08-31 19:36:06 +02:00
|
|
|
|
2013-07-20 10:49:45 +02:00
|
|
|
if bedBolts:
|
|
|
|
fingers = bedBolts.numFingers(fingers)
|
2013-07-25 14:15:30 +02:00
|
|
|
leftover = length - fingers*(space+finger) + space
|
2013-08-31 19:36:06 +02:00
|
|
|
|
2013-07-20 10:49:45 +02:00
|
|
|
s, f, thickness = space, finger, self.thickness
|
|
|
|
d, d_nut, h_nut, l, l1 = bedBoltSettings or self.bedBoltSettings
|
|
|
|
p = 1 if positive else -1
|
|
|
|
|
2013-09-01 14:39:26 +02:00
|
|
|
if fingers <= 0:
|
2013-08-31 19:36:06 +02:00
|
|
|
fingers = 0
|
|
|
|
leftover = length
|
|
|
|
|
2013-07-20 10:49:45 +02:00
|
|
|
self.edge(leftover/2.0)
|
2014-03-03 21:45:01 +01:00
|
|
|
for i in range(fingers):
|
2013-07-25 14:15:30 +02:00
|
|
|
if i !=0:
|
|
|
|
if not positive and bedBolts and bedBolts.drawBolt(i):
|
|
|
|
self.hole(0.5*space,
|
|
|
|
0.5*self.thickness, 0.5*d)
|
|
|
|
if positive and bedBolts and bedBolts.drawBolt(i):
|
|
|
|
self.bedBoltHole(s, bedBoltSettings)
|
|
|
|
else:
|
|
|
|
self.edge(s)
|
2013-07-20 10:49:45 +02:00
|
|
|
self.corner(-90*p)
|
2013-07-20 13:10:03 +02:00
|
|
|
self.edge(self.settings.height)
|
2013-07-20 10:49:45 +02:00
|
|
|
self.corner(90*p)
|
|
|
|
self.edge(f)
|
|
|
|
self.corner(90*p)
|
2013-07-20 13:10:03 +02:00
|
|
|
self.edge(self.settings.height)
|
2013-07-20 10:49:45 +02:00
|
|
|
self.corner(-90*p)
|
2013-07-25 14:15:30 +02:00
|
|
|
self.edge(leftover/2.0)
|
2013-07-20 10:49:45 +02:00
|
|
|
|
|
|
|
def margin(self):
|
|
|
|
return self.boxes.spacing + self.boxes.thickness
|
|
|
|
|
|
|
|
class FingerJointEdgeCounterPart(FingerJointEdge):
|
|
|
|
char = 'F'
|
|
|
|
positive = False
|
|
|
|
|
|
|
|
def width(self):
|
|
|
|
return self.boxes.thickness
|
|
|
|
|
|
|
|
def margin(self):
|
|
|
|
return self.boxes.spacing
|
|
|
|
|
|
|
|
class FingerHoleEdge(Edge):
|
|
|
|
char = 'h'
|
|
|
|
|
|
|
|
def __call__(self, length, dist=None,
|
|
|
|
bedBolts=None, bedBoltSettings=None, **kw):
|
|
|
|
if dist is None:
|
|
|
|
dist = self.fingerHoleEdgeWidth * self.thickness
|
|
|
|
self.ctx.save()
|
|
|
|
self.moveTo(0, dist+self.thickness/2)
|
|
|
|
self.fingerHoles(length, bedBolts, bedBoltSettings)
|
|
|
|
self.ctx.restore()
|
|
|
|
# XXX continue path
|
|
|
|
self.ctx.move_to(0, 0)
|
|
|
|
self.ctx.line_to(length, 0)
|
|
|
|
self.ctx.translate(*self.ctx.get_current_point())
|
|
|
|
|
|
|
|
def width(self):
|
|
|
|
return (self.fingerHoleEdgeWidth+1) * self.thickness
|
|
|
|
|
|
|
|
class DoveTailSettings(Settings):
|
|
|
|
absolute_params = {
|
|
|
|
"angle" : 50,
|
|
|
|
}
|
|
|
|
relative_params = {
|
|
|
|
"size" : 3,
|
|
|
|
"depth" : 1.5,
|
|
|
|
"radius" : 0.2,
|
|
|
|
}
|
|
|
|
|
|
|
|
class DoveTailJoint(Edge):
|
|
|
|
char = 'd'
|
|
|
|
positive = True
|
|
|
|
|
|
|
|
def __call__(self, length, **kw):
|
|
|
|
s = self.settings
|
|
|
|
radius = max(s.radius, self.boxes.burn) # no smaller than burn
|
|
|
|
positive = self.positive
|
|
|
|
a = s.angle + 90
|
|
|
|
alpha = 0.5*math.pi - math.pi*s.angle/180.0
|
|
|
|
|
|
|
|
l1 = radius/math.tan(alpha/2.0)
|
|
|
|
diffx = 0.5*s.depth/math.tan(alpha)
|
|
|
|
l2 = 0.5*s.depth / math.sin(alpha)
|
|
|
|
|
|
|
|
sections = int((length) // (s.size*2))
|
|
|
|
leftover = length - sections*s.size*2
|
|
|
|
|
|
|
|
p = 1 if positive else -1
|
|
|
|
|
|
|
|
self.edge((s.size+leftover)/2.0+diffx-l1)
|
2014-03-03 21:45:01 +01:00
|
|
|
for i in range(sections):
|
2013-07-20 10:49:45 +02:00
|
|
|
self.corner(-1*p*a, radius)
|
|
|
|
self.edge(2*(l2-l1))
|
|
|
|
self.corner(p*a, radius)
|
|
|
|
self.edge(2*(diffx-l1)+s.size)
|
|
|
|
self.corner(p*a, radius)
|
|
|
|
self.edge(2*(l2-l1))
|
|
|
|
self.corner(-1*p*a, radius)
|
|
|
|
if i<sections-1: # all but the last
|
|
|
|
self.edge(2*(diffx-l1)+s.size)
|
|
|
|
self.edge((s.size+leftover)/2.0+diffx-l1)
|
|
|
|
self.ctx.translate(*self.ctx.get_current_point())
|
|
|
|
|
|
|
|
def margin(self):
|
|
|
|
return self.settings.depth + self.boxes.spacing
|
|
|
|
|
|
|
|
class DoveTailJointCounterPart(DoveTailJoint):
|
|
|
|
char = 'D'
|
|
|
|
|
|
|
|
positive = False
|
|
|
|
|
|
|
|
def width(self):
|
|
|
|
return self.settings.depth
|
|
|
|
|
|
|
|
def margin(self):
|
|
|
|
return self.boxes.spacing
|
|
|
|
|
|
|
|
class FlexSettings(Settings):
|
|
|
|
relative_params = {
|
|
|
|
"distance" : 0.5,
|
|
|
|
"connection" : 1.0,
|
|
|
|
"width" : 5.0,
|
|
|
|
}
|
|
|
|
absolute_params = {
|
|
|
|
"stretch" : 1.0,
|
|
|
|
}
|
|
|
|
|
|
|
|
class FlexEdge(Edge):
|
|
|
|
char = 'X'
|
|
|
|
|
|
|
|
def __call__(self, x, h, **kw):
|
|
|
|
dist = self.settings.distance
|
|
|
|
connection = self.settings.connection
|
|
|
|
width = self.settings.width
|
|
|
|
|
|
|
|
burn = self.boxes.burn
|
|
|
|
h += 2*burn
|
|
|
|
lines = int(x // dist)
|
|
|
|
leftover = x - lines * dist
|
|
|
|
sections = int((h-connection) // width)
|
|
|
|
sheight = ((h-connection) / sections)-connection
|
|
|
|
|
2014-03-03 21:45:01 +01:00
|
|
|
for i in range(lines):
|
2013-07-20 10:49:45 +02:00
|
|
|
pos = i*dist + leftover/2
|
|
|
|
if i % 2:
|
|
|
|
self.ctx.move_to(pos, 0)
|
|
|
|
self.ctx.line_to(pos, connection+sheight)
|
2014-03-03 21:45:01 +01:00
|
|
|
for j in range((sections-1)//2):
|
2013-07-20 10:49:45 +02:00
|
|
|
self.ctx.move_to(pos, (2*j+1)* sheight+ (2*j+2)*connection)
|
|
|
|
self.ctx.line_to(pos, (2*j+3)* (sheight+ connection))
|
|
|
|
if not sections % 2:
|
|
|
|
self.ctx.move_to(pos, h - sheight- connection)
|
|
|
|
self.ctx.line_to(pos, h)
|
|
|
|
else:
|
|
|
|
if sections % 2:
|
|
|
|
self.ctx.move_to(pos, h)
|
|
|
|
self.ctx.line_to(pos, h-connection-sheight)
|
2014-03-03 21:45:01 +01:00
|
|
|
for j in range((sections-1)//2):
|
2013-07-20 10:49:45 +02:00
|
|
|
self.ctx.move_to(
|
|
|
|
pos, h-((2*j+1)* sheight+ (2*j+2)*connection))
|
|
|
|
self.ctx.line_to(
|
|
|
|
pos, h-(2*j+3)* (sheight+ connection))
|
|
|
|
|
|
|
|
else:
|
2014-03-03 21:45:01 +01:00
|
|
|
for j in range(sections//2):
|
2013-07-20 10:49:45 +02:00
|
|
|
self.ctx.move_to(pos,
|
|
|
|
h-connection-2*j*(sheight+connection))
|
|
|
|
self.ctx.line_to(pos, h-2*(j+1)*(sheight+connection))
|
|
|
|
|
|
|
|
self.ctx.move_to(0, 0)
|
|
|
|
self.ctx.line_to(x, 0)
|
|
|
|
self.ctx.translate(*self.ctx.get_current_point())
|
|
|
|
|
|
|
|
#############################################################################
|
|
|
|
### Building blocks
|
|
|
|
#############################################################################
|
|
|
|
|
|
|
|
class FingerHoles:
|
|
|
|
def __init__(self, boxes, settings):
|
|
|
|
self.boxes = boxes
|
|
|
|
self.ctx = boxes.ctx
|
|
|
|
self.settings = settings
|
|
|
|
|
|
|
|
def __call__(self, length, bedBolts=None, bedBoltSettings=None):
|
|
|
|
s, f = self.settings.space, self.settings.finger
|
2013-07-25 14:15:30 +02:00
|
|
|
fingers = int((length-(self.settings.surroundingspaces-1)*s) //
|
|
|
|
(s+f))
|
2013-07-20 10:49:45 +02:00
|
|
|
if bedBolts:
|
|
|
|
fingers = bedBolts.numFingers(fingers)
|
|
|
|
d, d_nut, h_nut, l, l1 = bedBoltSettings or self.bedBoltSettings
|
|
|
|
leftover = length - fingers*(s+f) - f
|
|
|
|
b = self.boxes.burn
|
2013-08-31 20:04:47 +02:00
|
|
|
if self.boxes.debug:
|
|
|
|
self.ctx.rectangle(0, -self.settings.width/2+b,
|
|
|
|
length, self.settings.width - 2*b)
|
2014-03-03 21:45:01 +01:00
|
|
|
for i in range(fingers):
|
2013-07-20 10:49:45 +02:00
|
|
|
pos = leftover/2.0+i*(s+f)
|
|
|
|
if bedBolts and bedBolts.drawBolt(i):
|
|
|
|
self.hole(pos+0.5*s, 0, d*0.5)
|
2013-07-20 13:10:03 +02:00
|
|
|
self.ctx.rectangle(pos+s+b, -self.settings.width/2+b,
|
|
|
|
f-2*b, self.settings.width - 2*b)
|
2013-07-20 10:49:45 +02:00
|
|
|
|
|
|
|
self.ctx.move_to(0, length)
|
|
|
|
self.ctx.translate(*self.ctx.get_current_point())
|
|
|
|
|
2013-07-25 14:22:55 +02:00
|
|
|
class NutHole:
|
|
|
|
sizes = {
|
|
|
|
"M1.6" : 3.2,
|
|
|
|
"M2" : 4,
|
|
|
|
"M2.5" : 5,
|
|
|
|
"M3" : 5.5,
|
|
|
|
"M4" : 7,
|
|
|
|
"M5" : 8,
|
|
|
|
"M6" : 10,
|
|
|
|
"M8" : 13,
|
|
|
|
"M10" : 16,
|
|
|
|
"M12" : 18,
|
|
|
|
"M14" : 21,
|
|
|
|
"M16" : 24,
|
|
|
|
"M20" : 30,
|
|
|
|
"M24" : 36,
|
|
|
|
"M30" : 46,
|
|
|
|
"M36" : 55,
|
|
|
|
"M42" : 65,
|
|
|
|
"M48" : 75,
|
|
|
|
"M56" : 85,
|
|
|
|
"M64" : 95,
|
|
|
|
}
|
|
|
|
|
|
|
|
def __init__(self, boxes, settings):
|
|
|
|
self.boxes = boxes
|
|
|
|
self.ctx = boxes.ctx
|
|
|
|
self.settings = settings
|
|
|
|
|
|
|
|
@restore
|
|
|
|
def __call__(self, size, x=0, y=0, angle=0):
|
|
|
|
size = self.sizes.get(size, size)
|
|
|
|
side = size / 3**0.5
|
|
|
|
self.boxes.moveTo(x, y, angle)
|
|
|
|
self.boxes.moveTo(-0.5*side, 0.5*size, angle)
|
|
|
|
for i in range(6):
|
|
|
|
self.boxes.edge(side)
|
|
|
|
self.boxes.corner(-60)
|
|
|
|
|
2013-04-01 09:42:29 +02:00
|
|
|
class Boxes:
|
|
|
|
|
2013-07-21 23:02:19 +02:00
|
|
|
def __init__(self, width=300, height=200, thickness=3.0, burn=0.05):
|
2013-04-01 09:42:29 +02:00
|
|
|
self.thickness = thickness
|
2013-06-29 13:55:57 +02:00
|
|
|
self.burn = burn
|
2013-07-20 10:49:45 +02:00
|
|
|
self.spacing = 2*self.burn + 0.5 * self.thickness
|
|
|
|
|
2013-04-16 16:50:29 +02:00
|
|
|
self.fingerHoleEdgeWidth = 1.0 # multitudes of self.thickness
|
2013-06-15 23:26:22 +02:00
|
|
|
self.bedBoltSettings = (3, 5.5, 2, 20, 15) #d, d_nut, h_nut, l, l1
|
2013-04-18 04:44:33 +02:00
|
|
|
self.hexHolesSettings = (5, 3, 'circle') # r, dist, style
|
2013-04-01 09:42:29 +02:00
|
|
|
self.output = "box.svg"
|
2013-08-31 20:04:47 +02:00
|
|
|
self.debug = 0
|
2013-04-05 20:43:34 +02:00
|
|
|
self._init_surface(width, height)
|
2013-07-20 10:49:45 +02:00
|
|
|
self._buildObjects()
|
|
|
|
|
|
|
|
def addPart(self, part, name=None):
|
|
|
|
if name is None:
|
|
|
|
name = part.__class__.__name__
|
|
|
|
name = name[0].lower() + name[1:]
|
2013-09-15 19:19:24 +02:00
|
|
|
if not hasattr(self, name):
|
|
|
|
setattr(self, name, part)
|
2013-07-20 10:49:45 +02:00
|
|
|
if isinstance(part, Edge):
|
|
|
|
self.edges[part.char] = part
|
|
|
|
|
|
|
|
def _buildObjects(self):
|
|
|
|
self.edges = {}
|
|
|
|
self.addPart(Edge(self, None))
|
|
|
|
self.addPart(OutSetEdge(self, None))
|
|
|
|
|
|
|
|
# Share settings object
|
|
|
|
s = FingerJointSettings(self.thickness)
|
|
|
|
self.addPart(FingerJointEdge(self, s))
|
|
|
|
self.addPart(FingerJointEdgeCounterPart(self, s))
|
|
|
|
self.addPart(FingerHoleEdge(self, s))
|
|
|
|
self.addPart(FingerHoles(self, s))
|
|
|
|
|
|
|
|
s = DoveTailSettings(self.thickness)
|
|
|
|
self.addPart(DoveTailJoint(self, s))
|
|
|
|
self.addPart(DoveTailJointCounterPart(self, s))
|
|
|
|
s = FlexSettings(self.thickness)
|
|
|
|
self.addPart(FlexEdge(self, s))
|
2013-04-01 09:42:29 +02:00
|
|
|
|
2013-07-25 14:22:55 +02:00
|
|
|
self.addPart(NutHole(self, None))
|
|
|
|
|
2013-04-05 20:43:34 +02:00
|
|
|
def _init_surface(self, width, height):
|
2014-03-29 08:23:18 +01:00
|
|
|
#mm2pt = 90 / 25.4 / 1.25
|
|
|
|
mm2pt = 1
|
|
|
|
#width *= mm2pt
|
|
|
|
#height *= mm2pt #3.543307
|
2013-04-01 09:42:29 +02:00
|
|
|
self.surface = cairo.SVGSurface(self.output, width, height)
|
|
|
|
self.ctx = ctx = cairo.Context(self.surface)
|
|
|
|
ctx.translate(0, height)
|
2013-06-25 19:45:09 +02:00
|
|
|
ctx.scale(mm2pt, -mm2pt)
|
2013-04-01 09:42:29 +02:00
|
|
|
|
2014-03-29 08:23:18 +01:00
|
|
|
#ctx.set_source_rgb(1.0, 1.0, 1.0)
|
|
|
|
#ctx.rectangle(0, 0, width, height)
|
|
|
|
#ctx.fill()
|
2013-04-01 09:42:29 +02:00
|
|
|
|
|
|
|
ctx.set_source_rgb(0.0, 0.0, 0.0)
|
2013-06-15 23:26:22 +02:00
|
|
|
ctx.set_line_width(2*self.burn)
|
2013-04-01 09:42:29 +02:00
|
|
|
|
|
|
|
|
2013-06-29 13:55:57 +02:00
|
|
|
def cc(self, callback, number, x=0.0, y=None):
|
2013-04-16 04:27:48 +02:00
|
|
|
"""call callback"""
|
2013-06-29 13:55:57 +02:00
|
|
|
if y is None:
|
|
|
|
y = self.burn
|
2013-04-16 04:27:48 +02:00
|
|
|
self.ctx.save()
|
|
|
|
self.moveTo(x, y)
|
|
|
|
if callable(callback):
|
|
|
|
callback(number)
|
|
|
|
elif hasattr(callback, '__getitem__'):
|
|
|
|
try:
|
|
|
|
callback = callback[number]
|
|
|
|
if callable(callback):
|
|
|
|
callback()
|
2013-04-16 05:21:38 +02:00
|
|
|
except (KeyError, IndexError):
|
2013-04-16 04:27:48 +02:00
|
|
|
pass
|
|
|
|
except:
|
|
|
|
self.ctx.restore()
|
|
|
|
raise
|
|
|
|
self.ctx.restore()
|
|
|
|
|
2013-06-15 23:26:22 +02:00
|
|
|
def getEntry(self, param, idx):
|
|
|
|
if isinstance(param, list):
|
|
|
|
if len(param)>idx:
|
|
|
|
return param[idx]
|
|
|
|
else:
|
|
|
|
return None
|
|
|
|
else:
|
|
|
|
return param
|
2013-05-14 17:52:33 +02:00
|
|
|
|
2014-03-21 21:08:54 +01:00
|
|
|
def close(self):
|
|
|
|
self.ctx.stroke()
|
|
|
|
self.surface.flush()
|
|
|
|
self.surface.finish()
|
2013-07-20 10:49:45 +02:00
|
|
|
|
2014-03-29 08:23:18 +01:00
|
|
|
f = open(self.output, "r+")
|
|
|
|
s = f.read(1024)
|
|
|
|
pos = s.find('pt"')
|
|
|
|
if pos > 0:
|
|
|
|
f.seek(pos)
|
|
|
|
f.write("mm")
|
|
|
|
else:
|
|
|
|
print "Could not replace pt with mm"
|
|
|
|
pos = s.find('pt"', pos+3)
|
|
|
|
if pos > 0:
|
|
|
|
f.seek(pos)
|
|
|
|
f.write("mm")
|
|
|
|
else:
|
|
|
|
print "Could not replace pt with mm"
|
|
|
|
|
2013-04-01 09:42:29 +02:00
|
|
|
############################################################
|
|
|
|
### Turtle graphics commands
|
|
|
|
############################################################
|
|
|
|
|
|
|
|
def corner(self, degrees, radius=0):
|
|
|
|
rad = degrees*math.pi/180
|
|
|
|
if degrees > 0:
|
|
|
|
self.ctx.arc(0, radius+self.burn, radius+self.burn,
|
|
|
|
-0.5*math.pi, rad - 0.5*math.pi)
|
2013-06-15 23:26:22 +02:00
|
|
|
elif radius > self.burn:
|
2013-06-08 15:19:28 +02:00
|
|
|
self.ctx.arc_negative(0, -(radius-self.burn), radius-self.burn,
|
2013-04-01 09:42:29 +02:00
|
|
|
0.5*math.pi, rad + 0.5*math.pi)
|
2013-06-15 23:26:22 +02:00
|
|
|
else: # not rounded inner corner
|
|
|
|
self.ctx.arc_negative(0, self.burn-radius, self.burn-radius,
|
|
|
|
-0.5*math.pi, -0.5*math.pi+rad)
|
|
|
|
|
2013-04-01 18:32:07 +02:00
|
|
|
self.continueDirection(rad)
|
2013-04-01 09:42:29 +02:00
|
|
|
|
|
|
|
def edge(self, length):
|
2013-06-15 23:26:22 +02:00
|
|
|
self.ctx.move_to(0,0)
|
2013-04-01 09:42:29 +02:00
|
|
|
self.ctx.line_to(length, 0)
|
|
|
|
self.ctx.translate(*self.ctx.get_current_point())
|
|
|
|
|
2013-04-05 20:43:34 +02:00
|
|
|
def curveTo(self, x1, y1, x2, y2, x3, y3):
|
|
|
|
"""control point 1, control point 2, end point"""
|
|
|
|
self.ctx.curve_to(x1, y1, x2, y2, x3, y3)
|
|
|
|
dx = x3-x2
|
|
|
|
dy = y3-y2
|
|
|
|
rad = math.atan2(dy, dx)
|
|
|
|
self.continueDirection(rad)
|
|
|
|
|
2014-04-20 21:54:33 +02:00
|
|
|
def polyline(self, *args):
|
|
|
|
for i, arg in enumerate(args):
|
|
|
|
if i % 2:
|
|
|
|
self.corner(arg)
|
|
|
|
else:
|
|
|
|
self.edge(arg)
|
|
|
|
|
2013-06-15 23:26:22 +02:00
|
|
|
def bedBoltHole(self, length, bedBoltSettings=None):
|
|
|
|
d, d_nut, h_nut, l, l1 = bedBoltSettings or self.bedBoltSettings
|
|
|
|
self.edge((length-d)/2.0)
|
|
|
|
self.corner(90)
|
|
|
|
self.edge(l1)
|
|
|
|
self.corner(90)
|
|
|
|
self.edge((d_nut-d)/2.0)
|
|
|
|
self.corner(-90)
|
|
|
|
self.edge(h_nut)
|
|
|
|
self.corner(-90)
|
|
|
|
self.edge((d_nut-d)/2.0)
|
|
|
|
self.corner(90)
|
|
|
|
self.edge(l-l1-h_nut)
|
|
|
|
self.corner(-90)
|
|
|
|
self.edge(d)
|
|
|
|
self.corner(-90)
|
|
|
|
self.edge(l-l1-h_nut)
|
|
|
|
self.corner(90)
|
|
|
|
self.edge((d_nut-d)/2.0)
|
|
|
|
self.corner(-90)
|
|
|
|
self.edge(h_nut)
|
|
|
|
self.corner(-90)
|
|
|
|
self.edge((d_nut-d)/2.0)
|
|
|
|
self.corner(90)
|
|
|
|
self.edge(l1)
|
|
|
|
self.corner(90)
|
|
|
|
self.edge((length-d)/2.0)
|
|
|
|
|
2013-04-01 09:42:29 +02:00
|
|
|
|
2013-06-03 09:49:51 +02:00
|
|
|
def grip(self, length, depth):
|
|
|
|
"""corrugated edge useful as an gipping area"""
|
|
|
|
grooves = int(length // (depth*2.0)) + 1
|
|
|
|
depth = length / grooves / 4.0
|
2014-03-03 21:45:01 +01:00
|
|
|
for groove in range(grooves):
|
2013-06-03 09:49:51 +02:00
|
|
|
self.corner(90, depth)
|
|
|
|
self.corner(-180, depth)
|
|
|
|
self.corner(90, depth)
|
|
|
|
|
|
|
|
def _latchHole(self, length):
|
2013-06-25 13:40:22 +02:00
|
|
|
self.edge(1.1*self.thickness)
|
2013-06-03 09:49:51 +02:00
|
|
|
self.corner(-90)
|
2013-06-25 13:40:22 +02:00
|
|
|
self.edge(length/2.0+0.2*self.thickness)
|
2013-06-03 09:49:51 +02:00
|
|
|
self.corner(-90)
|
2013-06-25 13:40:22 +02:00
|
|
|
self.edge(1.1*self.thickness)
|
2013-06-03 09:49:51 +02:00
|
|
|
|
|
|
|
def _latchGrip(self, length):
|
|
|
|
self.corner(90, self.thickness/4.0)
|
2013-06-25 13:40:22 +02:00
|
|
|
self.grip(length/2.0-self.thickness/2.0-0.2*self.thickness, self.thickness/2.0)
|
2013-06-03 09:49:51 +02:00
|
|
|
self.corner(90, self.thickness/4.0)
|
|
|
|
|
|
|
|
def latch(self, length, positive=True, reverse=False):
|
|
|
|
"""Fix a flex box door at the box
|
|
|
|
positive: False: Door side; True: Box side
|
|
|
|
reverse: True when running away from the latch
|
|
|
|
"""
|
|
|
|
if positive:
|
|
|
|
if reverse:
|
|
|
|
self.edge(length/2.0-self.burn)
|
|
|
|
self.corner(-90)
|
|
|
|
self.edge(self.thickness)
|
|
|
|
self.corner(90)
|
|
|
|
self.edge(length/2.0)
|
|
|
|
self.corner(90)
|
|
|
|
self.edge(self.thickness)
|
|
|
|
self.corner(-90)
|
|
|
|
if not reverse:
|
|
|
|
self.edge(length/2.0-self.burn)
|
|
|
|
else:
|
|
|
|
if reverse:
|
|
|
|
self._latchGrip(length)
|
|
|
|
else:
|
|
|
|
self.corner(90)
|
|
|
|
self._latchHole(length)
|
|
|
|
if not reverse:
|
|
|
|
self._latchGrip(length)
|
|
|
|
else:
|
|
|
|
self.corner(90)
|
|
|
|
|
2013-07-20 17:47:18 +02:00
|
|
|
def handle(self, x, h, hl, r=30):
|
2013-04-16 05:07:26 +02:00
|
|
|
"""Creates and Edge with a handle"""
|
|
|
|
d = (x-hl-2*r)/2.0
|
|
|
|
if d < 0:
|
2014-03-03 21:45:01 +01:00
|
|
|
print("Handle too wide")
|
2013-04-16 05:07:26 +02:00
|
|
|
|
|
|
|
self.ctx.save()
|
|
|
|
|
|
|
|
# Hole
|
2013-07-20 17:47:18 +02:00
|
|
|
self.moveTo(d+2*r, 0)
|
2013-04-16 05:07:26 +02:00
|
|
|
self.edge(hl-2*r)
|
|
|
|
self.corner(-90, r)
|
2013-07-20 17:47:18 +02:00
|
|
|
self.edge(h-3*r)
|
2013-04-16 05:07:26 +02:00
|
|
|
self.corner(-90, r)
|
|
|
|
self.edge(hl-2*r)
|
|
|
|
self.corner(-90, r)
|
2013-07-20 17:47:18 +02:00
|
|
|
self.edge(h-3*r)
|
2013-04-16 05:07:26 +02:00
|
|
|
self.corner(-90, r)
|
|
|
|
|
|
|
|
self.ctx.restore()
|
|
|
|
self.moveTo(0,0)
|
|
|
|
|
|
|
|
self.curveTo(d, 0, d, 0, d, -h+r)
|
|
|
|
self.curveTo(r, 0, r, 0, r, r)
|
|
|
|
self.edge(hl)
|
|
|
|
self.curveTo(r, 0, r, 0, r, r)
|
|
|
|
self.curveTo(h-r, 0, h-r, 0, h-r, -d)
|
|
|
|
|
2013-04-01 09:42:29 +02:00
|
|
|
### Navigation
|
|
|
|
|
2013-07-20 10:49:45 +02:00
|
|
|
def moveTo(self, x, y=0.0, degrees=0):
|
|
|
|
self.ctx.move_to(0, 0)
|
2013-04-01 09:42:29 +02:00
|
|
|
self.ctx.translate(x, y)
|
|
|
|
self.ctx.rotate(degrees*math.pi/180.0)
|
|
|
|
self.ctx.move_to(0, 0)
|
|
|
|
|
2013-04-01 18:32:07 +02:00
|
|
|
def continueDirection(self, angle=0):
|
2013-04-01 09:42:29 +02:00
|
|
|
self.ctx.translate(*self.ctx.get_current_point())
|
2013-04-01 18:32:07 +02:00
|
|
|
self.ctx.rotate(angle)
|
2013-04-01 09:42:29 +02:00
|
|
|
|
2013-07-20 10:49:45 +02:00
|
|
|
def move(self, x, y, where, before=False):
|
|
|
|
"""Intended to be used by parts
|
|
|
|
where can be combinations of "up", "down", "left", "right" and "only"
|
|
|
|
when "only" is included the move is only done when before is True
|
|
|
|
The function returns whether actual drawing of the part
|
|
|
|
should be omited.
|
|
|
|
"""
|
|
|
|
if not where:
|
|
|
|
return False
|
|
|
|
|
|
|
|
terms = where.split()
|
|
|
|
dontdraw = before and "only" in terms
|
|
|
|
|
|
|
|
moves = {
|
|
|
|
"up": (0, y, False),
|
|
|
|
"down" : (0, -y, True),
|
|
|
|
"left" : (-x, 0, True),
|
|
|
|
"right" : (x, 0, False),
|
|
|
|
"only" : (0, 0, None),
|
|
|
|
}
|
|
|
|
for term in terms:
|
|
|
|
if not term in moves:
|
2014-03-03 21:45:01 +01:00
|
|
|
raise ValueError("Unknown direction: '%s'" % term)
|
2013-07-20 10:49:45 +02:00
|
|
|
x, y, movebeforeprint = moves[term]
|
|
|
|
if movebeforeprint and before:
|
|
|
|
self.moveTo(x, y)
|
|
|
|
elif (not movebeforeprint and not before) or dontdraw:
|
|
|
|
self.moveTo(x, y)
|
|
|
|
return dontdraw
|
|
|
|
|
|
|
|
|
|
|
|
|
2013-04-16 04:27:48 +02:00
|
|
|
# Building blocks
|
|
|
|
|
2013-07-20 17:48:11 +02:00
|
|
|
def fingerHolesAt(self, x, y, length, angle=90,
|
2013-07-20 10:49:45 +02:00
|
|
|
bedBolts=None, bedBoltSettings=None):
|
2013-04-01 22:20:22 +02:00
|
|
|
self.ctx.save()
|
2013-07-20 17:48:11 +02:00
|
|
|
self.moveTo(x, y, angle)
|
2013-07-20 10:49:45 +02:00
|
|
|
self.fingerHoles(length, bedBolts, bedBoltSettings)
|
2013-04-01 22:20:22 +02:00
|
|
|
self.ctx.restore()
|
|
|
|
|
2013-06-07 12:50:13 +02:00
|
|
|
@restore
|
2013-04-16 04:24:35 +02:00
|
|
|
def hole(self, x, y, r):
|
|
|
|
self.moveTo(x+r, y)
|
|
|
|
self.ctx.arc(-r, 0, r, 0, 2*math.pi)
|
|
|
|
|
2013-07-20 17:51:54 +02:00
|
|
|
@restore
|
|
|
|
def rectangularHole(self, x, y, dx, dy, r=0):
|
|
|
|
self.moveTo(x+r-dx/2.0, y-dy/2.0, 180)
|
|
|
|
for d in (dy, dx, dy, dx):
|
|
|
|
self.corner(-90, r)
|
|
|
|
self.edge(d)
|
|
|
|
|
2014-01-21 22:44:22 +01:00
|
|
|
@restore
|
|
|
|
def text(self, text, x=0, y=0, angle=0, align=""):
|
|
|
|
self.moveTo(x, y, angle)
|
|
|
|
(tx, ty, width, height, dx, dy) = self.ctx.text_extents(text)
|
|
|
|
align = align.split()
|
|
|
|
moves = {
|
|
|
|
"top" : (0, -height),
|
|
|
|
"middle" : (0, -0.5*height),
|
|
|
|
"bottom" : (0, 0),
|
|
|
|
"left" : (0, 0),
|
|
|
|
"center" : (-0.5*width, 0),
|
|
|
|
"right" : (-width, 0),
|
|
|
|
}
|
|
|
|
for a in align:
|
|
|
|
if a in moves:
|
|
|
|
self.moveTo(*moves[a])
|
|
|
|
else:
|
|
|
|
raise ValueError("Unknown alignment: %s" % align)
|
|
|
|
|
|
|
|
self.ctx.scale(1, -1)
|
|
|
|
self.ctx.show_text(text)
|
|
|
|
|
2013-07-20 17:52:28 +02:00
|
|
|
@restore
|
2013-07-21 23:01:35 +02:00
|
|
|
def NEMA(self, size, x=0, y=0, angle=0):
|
2013-07-20 17:52:28 +02:00
|
|
|
nema = {
|
|
|
|
# motor,flange, holes, screws
|
|
|
|
8 : (20.3, 16, 15.4, 3),
|
|
|
|
11 : (28.2, 22, 23, 4),
|
|
|
|
14 : (35.2, 22, 26, 4),
|
|
|
|
16 : (39.2, 22, 31, 4),
|
|
|
|
17 : (42.2, 22, 31, 4),
|
|
|
|
23 : (56.4, 38.1, 47.1, 5.2),
|
|
|
|
24 : (60, 36, 49.8, 5.1),
|
|
|
|
34 : (86.3, 73, 69.8, 6.6),
|
|
|
|
42 : (110, 55.5, 89, 8.5),
|
|
|
|
}
|
|
|
|
width, flange, holedistance, diameter = nema[size]
|
2013-07-21 23:01:35 +02:00
|
|
|
self.moveTo(x, y, angle)
|
2013-08-31 20:04:47 +02:00
|
|
|
if self.debug:
|
|
|
|
self.rectangularHole(0, 0, width, width)
|
2013-07-20 17:52:28 +02:00
|
|
|
self.hole(0,0, 0.5*flange)
|
|
|
|
for x in (-1, 1):
|
|
|
|
for y in (-1, 1):
|
|
|
|
self.hole(x*0.5*holedistance,
|
|
|
|
y*0.5*holedistance,
|
|
|
|
0.5*diameter)
|
|
|
|
|
|
|
|
|
2013-04-18 04:44:33 +02:00
|
|
|
# hexHoles
|
|
|
|
|
|
|
|
def hexHolesRectangle(self, x, y, settings=None, skip=None):
|
2013-04-16 16:50:29 +02:00
|
|
|
"""
|
|
|
|
Fills a rectangle with holes.
|
|
|
|
r : radius of holes
|
|
|
|
b : space between holes
|
|
|
|
style : what types of holes (not yet implemented)
|
|
|
|
skip : function to check if hole should be present
|
|
|
|
gets x, y, r, b, posx, posy
|
|
|
|
"""
|
2013-04-18 04:44:33 +02:00
|
|
|
if settings is None:
|
|
|
|
settings = self.hexHolesSettings
|
|
|
|
r, b, style = settings
|
|
|
|
|
2013-04-16 04:24:35 +02:00
|
|
|
w = r+b/2.0
|
|
|
|
dist = w * math.cos(math.pi/6.0)
|
2013-04-16 16:50:29 +02:00
|
|
|
|
|
|
|
# how many half circles do fit
|
|
|
|
cx = int((x-2*r) // (w)) + 2
|
|
|
|
cy = int((y-2*r) // (dist)) + 2
|
|
|
|
|
|
|
|
# what's left on the sides
|
|
|
|
lx = (x - (2*r+(cx-2)*w))/2.0
|
2013-04-18 04:44:33 +02:00
|
|
|
ly = (y - (2*r+((cy//2)*2)*dist-2*dist))/2.0
|
2013-04-16 16:50:29 +02:00
|
|
|
|
2014-03-03 21:45:01 +01:00
|
|
|
for i in range(cy//2):
|
|
|
|
for j in range((cx-(i%2))//2):
|
2013-04-16 16:50:29 +02:00
|
|
|
px = 2*j*w + r + lx
|
|
|
|
py = i*2*dist + r + ly
|
|
|
|
if i % 2:
|
2013-04-17 13:23:23 +02:00
|
|
|
px += w
|
2013-04-16 16:50:29 +02:00
|
|
|
if skip and skip(x, y, r, b, px, py):
|
|
|
|
continue
|
|
|
|
self.hole(px, py, r)
|
|
|
|
|
|
|
|
def __skipcircle(self, x, y, r, b, posx, posy):
|
|
|
|
cx, cy = x/2.0, y/2.0
|
2013-04-17 13:25:30 +02:00
|
|
|
return (dist(posx-cx, posy-cy) > (cx-r))
|
2013-04-16 16:50:29 +02:00
|
|
|
|
2013-04-18 04:44:33 +02:00
|
|
|
def hexHolesCircle(self, d, settings=None):
|
2013-04-16 16:50:29 +02:00
|
|
|
d2 = d/2.0
|
2013-04-18 04:44:33 +02:00
|
|
|
self.hexHolesRectangle(d, d, settings=settings, skip=self.__skipcircle)
|
2013-04-16 16:50:29 +02:00
|
|
|
|
2013-04-18 04:44:33 +02:00
|
|
|
def hexHolesPlate(self, x, y, rc, settings=None):
|
2013-04-17 13:25:30 +02:00
|
|
|
def skip(x, y, r, b, posx, posy):
|
|
|
|
posx = abs(posx-(x/2.0))
|
|
|
|
posy = abs(posy-(y/2.0))
|
|
|
|
|
2013-04-18 04:44:33 +02:00
|
|
|
wx = 0.5*x-rc-r
|
|
|
|
wy = 0.5*y-rc-r
|
2013-04-17 13:25:30 +02:00
|
|
|
|
|
|
|
if (posx <= wx) or (posy <= wx):
|
|
|
|
return 0
|
2013-04-18 04:44:33 +02:00
|
|
|
return dist(posx-wx, posy-wy) > rc
|
|
|
|
|
|
|
|
self.hexHolesRectangle(x, y, settings, skip=skip)
|
2013-04-17 13:25:30 +02:00
|
|
|
|
2013-04-18 04:44:33 +02:00
|
|
|
def hexHolesHex(self, h, settings=None, grow=None):
|
|
|
|
if settings is None:
|
|
|
|
settings = self.hexHolesSettings
|
|
|
|
r, b, style = settings
|
2013-04-16 04:24:35 +02:00
|
|
|
|
|
|
|
self.ctx.rectangle(0, 0, h, h)
|
|
|
|
w = r+b/2.0
|
|
|
|
dist = w * math.cos(math.pi/6.0)
|
|
|
|
cy = 2 * int((h-4*dist)// (4*w)) + 1
|
|
|
|
|
|
|
|
leftover = h-2*r-(cy-1)*2*r
|
|
|
|
if grow=='space ':
|
|
|
|
b += leftover / (cy-1) / 2
|
|
|
|
|
|
|
|
# recalulate with adjusted values
|
|
|
|
w = r+b/2.0
|
|
|
|
dist = w * math.cos(math.pi/6.0)
|
|
|
|
|
|
|
|
self.moveTo(h/2.0-(cy//2)*2*w, h/2.0)
|
2014-03-03 21:45:01 +01:00
|
|
|
for j in range(cy):
|
2013-04-16 04:24:35 +02:00
|
|
|
self.hole(2*j*w, 0, r)
|
2014-03-03 21:45:01 +01:00
|
|
|
for i in range(1, cy/2+1):
|
|
|
|
for j in range(cy-i):
|
2013-04-16 04:24:35 +02:00
|
|
|
self.hole(j*2*w+i*w, i*2*dist, r)
|
|
|
|
self.hole(j*2*w+i*w, -i*2*dist, r)
|
|
|
|
|
2013-04-16 11:32:13 +02:00
|
|
|
##################################################
|
|
|
|
### parts
|
|
|
|
##################################################
|
2013-04-16 04:24:35 +02:00
|
|
|
|
2013-05-14 17:52:33 +02:00
|
|
|
def roundedPlate(self, x, y, r, callback=None,
|
2013-06-15 23:26:22 +02:00
|
|
|
holesMargin=None, holesSettings=None,
|
2013-07-20 10:49:45 +02:00
|
|
|
bedBolts=None, bedBoltSettings=None,
|
|
|
|
move=None):
|
2013-04-16 04:27:48 +02:00
|
|
|
"""fits surroundingWall
|
|
|
|
first edge is split to have a joint in the middle of the side
|
|
|
|
callback is called at the beginning of the straight edges
|
2013-05-14 17:52:33 +02:00
|
|
|
0, 1 for the two part of the first edge, 2, 3, 4 for the others
|
|
|
|
|
|
|
|
set holesMargin to get hex holes.
|
|
|
|
"""
|
|
|
|
|
2013-07-20 10:49:45 +02:00
|
|
|
overallwidth = x+2*self.fingerJointEdge.spacing()
|
|
|
|
overallheight = y+2*self.fingerJointEdge.spacing()
|
|
|
|
|
|
|
|
if self.move(overallwidth, overallheight, move, before=True):
|
|
|
|
return
|
|
|
|
|
2013-04-16 04:27:48 +02:00
|
|
|
self.ctx.save()
|
2013-07-20 19:29:41 +02:00
|
|
|
self.moveTo(self.fingerJointEdge.margin(),
|
|
|
|
self.fingerJointEdge.margin())
|
|
|
|
self.moveTo(r, 0)
|
2013-07-20 10:49:45 +02:00
|
|
|
|
2013-04-16 04:27:48 +02:00
|
|
|
self.cc(callback, 0)
|
2013-07-20 10:49:45 +02:00
|
|
|
self.fingerJointEdge(x/2.0-r, bedBolts=self.getEntry(bedBolts, 0),
|
2013-06-15 23:26:22 +02:00
|
|
|
bedBoltSettings=self.getEntry(bedBoltSettings, 0))
|
2013-04-16 04:27:48 +02:00
|
|
|
self.cc(callback, 1)
|
2013-07-20 10:49:45 +02:00
|
|
|
self.fingerJointEdge(x/2.0-r, bedBolts=self.getEntry(bedBolts, 1),
|
2013-06-15 23:26:22 +02:00
|
|
|
bedBoltSettings=self.getEntry(bedBoltSettings, 1))
|
2013-04-16 04:27:48 +02:00
|
|
|
for i, l in zip(range(3), (y, x, y)):
|
|
|
|
self.corner(90, r)
|
|
|
|
self.cc(callback, i+2)
|
2013-07-20 10:49:45 +02:00
|
|
|
self.fingerJointEdge(l-2*r, bedBolts=self.getEntry(bedBolts, i+2),
|
2013-06-15 23:26:22 +02:00
|
|
|
bedBoltSettings=self.getEntry(bedBoltSettings, i+2))
|
2013-04-16 04:27:48 +02:00
|
|
|
self.corner(90, r)
|
2013-05-14 17:52:33 +02:00
|
|
|
|
|
|
|
self.ctx.restore()
|
|
|
|
self.ctx.save()
|
|
|
|
|
2013-07-20 19:29:41 +02:00
|
|
|
self.moveTo(self.fingerJointEdge.margin(),
|
|
|
|
self.fingerJointEdge.margin())
|
|
|
|
|
2013-05-14 17:52:33 +02:00
|
|
|
if holesMargin is not None:
|
|
|
|
self.moveTo(holesMargin, holesMargin)
|
|
|
|
if r > holesMargin:
|
|
|
|
r -= holesMargin
|
|
|
|
else:
|
|
|
|
r = 0
|
|
|
|
self.hexHolesPlate(x-2*holesMargin, y-2*holesMargin, r,
|
|
|
|
settings=holesSettings)
|
2013-04-16 04:27:48 +02:00
|
|
|
self.ctx.restore()
|
2013-12-15 20:41:18 +01:00
|
|
|
self.ctx.stroke()
|
2013-07-20 10:49:45 +02:00
|
|
|
self.move(overallwidth, overallheight, move)
|
2013-04-16 04:27:48 +02:00
|
|
|
|
|
|
|
def surroundingWall(self, x, y, r, h,
|
2013-04-16 11:32:13 +02:00
|
|
|
bottom='e', top='e',
|
2013-07-20 10:49:45 +02:00
|
|
|
callback=None,
|
|
|
|
move=None):
|
2013-04-16 04:27:48 +02:00
|
|
|
"""
|
|
|
|
h : inner height, not counting the joints
|
|
|
|
callback is called a beginn of the flat sides with
|
|
|
|
0 for right half of first x side;
|
|
|
|
1 and 3 for y sides;
|
|
|
|
2 for second x side
|
2013-05-14 17:52:33 +02:00
|
|
|
4 for second half of the first x side
|
2013-04-16 04:27:48 +02:00
|
|
|
"""
|
|
|
|
c4 = (r+self.burn)*math.pi*0.5 # circumference of quarter circle
|
2013-06-29 13:57:05 +02:00
|
|
|
c4 = 0.9 * c4 # stretch flex 10%
|
2013-07-20 10:49:45 +02:00
|
|
|
|
|
|
|
top = self.edges.get(top, top)
|
|
|
|
bottom = self.edges.get(bottom, bottom)
|
|
|
|
|
|
|
|
topwidth = top.width()
|
|
|
|
bottomwidth = bottom.width()
|
|
|
|
|
|
|
|
overallwidth = 2*x + 2*y - 8*r + 4*c4 + \
|
|
|
|
self.edges["d"].spacing() + self.edges["D"].spacing()
|
|
|
|
overallheight = h + top.spacing() + bottom.spacing()
|
|
|
|
|
|
|
|
|
|
|
|
if self.move(overallwidth, overallheight, move, before=True):
|
|
|
|
return
|
|
|
|
|
|
|
|
self.ctx.save()
|
|
|
|
self.moveTo(self.edges["D"].margin(), bottom.margin())
|
2013-04-16 04:27:48 +02:00
|
|
|
|
|
|
|
self.cc(callback, 0, y=bottomwidth+self.burn)
|
2013-07-20 10:49:45 +02:00
|
|
|
bottom(x/2.0-r)
|
2013-04-16 04:27:48 +02:00
|
|
|
for i, l in zip(range(4), (y, x, y, 0)):
|
2013-07-20 10:49:45 +02:00
|
|
|
self.flexEdge(c4, h+topwidth+bottomwidth)
|
2013-04-16 04:27:48 +02:00
|
|
|
self.cc(callback, i+1, y=bottomwidth+self.burn)
|
|
|
|
if i < 3:
|
2013-07-20 10:49:45 +02:00
|
|
|
bottom(l-2*r)
|
|
|
|
bottom(x/2.0-r)
|
2013-04-16 04:27:48 +02:00
|
|
|
|
|
|
|
self.corner(90)
|
|
|
|
self.edge(bottomwidth)
|
|
|
|
self.doveTailJoint(h)
|
|
|
|
self.edge(topwidth)
|
|
|
|
self.corner(90)
|
|
|
|
|
2013-07-20 10:49:45 +02:00
|
|
|
top(x/2.0-r)
|
2013-04-16 04:27:48 +02:00
|
|
|
for i, l in zip(range(4), (y, x, y, 0)):
|
|
|
|
self.edge(c4)
|
|
|
|
if i < 3:
|
2013-07-20 10:49:45 +02:00
|
|
|
top(l - 2*r)
|
|
|
|
top(x/2.0-r)
|
2013-04-16 04:27:48 +02:00
|
|
|
|
|
|
|
self.corner(90)
|
|
|
|
self.edge(topwidth)
|
2013-07-20 10:49:45 +02:00
|
|
|
self.doveTailJointCounterPart(h)
|
2013-04-16 04:27:48 +02:00
|
|
|
self.edge(bottomwidth)
|
|
|
|
self.corner(90)
|
|
|
|
|
2013-07-20 10:49:45 +02:00
|
|
|
self.ctx.restore()
|
2013-12-15 20:41:18 +01:00
|
|
|
self.ctx.stroke()
|
2013-07-20 10:49:45 +02:00
|
|
|
|
|
|
|
self.move(overallwidth, overallheight, move)
|
|
|
|
|
|
|
|
|
2013-05-14 17:52:33 +02:00
|
|
|
def rectangularWall(self, x, y, edges="eeee",
|
2013-06-15 23:26:22 +02:00
|
|
|
holesMargin=None, holesSettings=None,
|
2013-07-20 10:49:45 +02:00
|
|
|
bedBolts=None, bedBoltSettings=None,
|
2013-07-20 17:54:07 +02:00
|
|
|
callback=None,
|
2013-07-20 10:49:45 +02:00
|
|
|
move=None):
|
2013-05-14 17:52:33 +02:00
|
|
|
if len(edges) != 4:
|
2014-03-03 21:45:01 +01:00
|
|
|
raise ValueError("four edges required")
|
2013-11-26 22:53:06 +01:00
|
|
|
edges = [self.edges.get(e, e) for e in edges]
|
2013-04-16 11:32:13 +02:00
|
|
|
edges += edges # append for wrapping around
|
2013-07-20 10:49:45 +02:00
|
|
|
|
|
|
|
overallwidth = x + edges[-1].spacing() + edges[1].spacing()
|
|
|
|
overallheight = y + edges[0].spacing() + edges[2].spacing()
|
|
|
|
|
|
|
|
if self.move(overallwidth, overallheight, move, before=True):
|
|
|
|
return
|
|
|
|
|
|
|
|
self.ctx.save()
|
|
|
|
self.moveTo(edges[-1].margin(), edges[0].margin())
|
2013-04-16 11:32:13 +02:00
|
|
|
for i, l in enumerate((x, y, x, y)):
|
2013-07-20 10:49:45 +02:00
|
|
|
self.edge(edges[i-1].width())
|
2013-08-31 20:06:30 +02:00
|
|
|
self.cc(callback, i, y=edges[i].width()+self.burn)
|
2013-07-20 10:49:45 +02:00
|
|
|
edges[i](l,
|
|
|
|
bedBolts=self.getEntry(bedBolts, i),
|
|
|
|
bedBoltSettings=self.getEntry(bedBoltSettings, i))
|
|
|
|
self.edge(edges[i+1].width())
|
|
|
|
self.corner(90-edges[i].endAngle()-edges[i+1].startAngle())
|
2013-04-01 22:20:22 +02:00
|
|
|
|
2013-05-14 17:52:33 +02:00
|
|
|
if holesMargin is not None:
|
2013-07-20 10:49:45 +02:00
|
|
|
self.moveTo(holesMargin+edges[-1].width(),
|
|
|
|
holesMargin+edges[0].width())
|
2013-05-14 17:52:33 +02:00
|
|
|
self.hexHolesRectangle(x-2*holesMargin, y-2*holesMargin)
|
2013-07-20 10:49:45 +02:00
|
|
|
self.ctx.restore()
|
2013-12-15 20:41:18 +01:00
|
|
|
self.ctx.stroke()
|
2013-07-20 10:49:45 +02:00
|
|
|
|
|
|
|
self.move(overallwidth, overallheight, move)
|
|
|
|
|
2013-05-14 17:52:33 +02:00
|
|
|
|
2013-04-01 09:42:29 +02:00
|
|
|
##################################################
|
|
|
|
### main
|
|
|
|
##################################################
|
|
|
|
|
2013-04-16 11:32:13 +02:00
|
|
|
def render(self, x, y, h):
|
2013-04-01 22:20:22 +02:00
|
|
|
self.ctx.save()
|
|
|
|
|
2013-04-16 11:32:13 +02:00
|
|
|
self.moveTo(10, 10)
|
|
|
|
self.roundedPlate(x, y, 0)
|
|
|
|
self.moveTo(x+40, 0)
|
|
|
|
self.rectangularWall(x, y, "FFFF")
|
2013-04-01 22:20:22 +02:00
|
|
|
|
|
|
|
self.ctx.restore()
|
2013-04-16 04:27:48 +02:00
|
|
|
|
2013-04-16 11:32:13 +02:00
|
|
|
self.moveTo(10, y+20)
|
|
|
|
for i in range(2):
|
|
|
|
for l in (x, y):
|
|
|
|
self.rectangularWall(l, h, "hffF")
|
|
|
|
self.moveTo(l+20, 0)
|
|
|
|
self.moveTo(-x-y-40, h+20)
|
|
|
|
|
|
|
|
|
2014-03-21 21:08:54 +01:00
|
|
|
self.close()
|
2013-04-01 09:42:29 +02:00
|
|
|
|
|
|
|
if __name__ == '__main__':
|
2013-04-06 00:20:10 +02:00
|
|
|
b = Boxes(900, 700)
|
2013-04-16 11:32:13 +02:00
|
|
|
b.render(100, 161.8, 120)
|