2016-07-11 23:01:57 +02:00
|
|
|
#!/usr/bin/env python3
|
2016-03-29 19:15:13 +02:00
|
|
|
# -*- coding: utf-8 -*-
|
2016-03-25 14:02:52 +01:00
|
|
|
# Copyright (C) 2013-2016 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/>.
|
|
|
|
|
|
|
|
import math
|
2016-04-17 14:26:27 +02:00
|
|
|
import inspect
|
|
|
|
|
|
|
|
def getDescriptions():
|
2016-06-30 11:12:51 +02:00
|
|
|
d = {edge.char : edge.description for edge in globals().values()
|
|
|
|
if inspect.isclass(edge) and issubclass(edge, BaseEdge)
|
|
|
|
and edge.char}
|
|
|
|
d['k'] = "Straight edge with hinge eye (both ends)"
|
|
|
|
return d
|
2016-03-25 14:02:52 +01:00
|
|
|
|
2016-05-20 20:32:40 +02:00
|
|
|
class BoltPolicy(object):
|
2016-03-25 14:02:52 +01:00
|
|
|
"""Abstract class
|
2016-04-04 01:28:02 +02:00
|
|
|
|
2016-03-25 14:02:52 +01:00
|
|
|
Distributes (bed) bolts on a number of segments
|
|
|
|
(fingers of a finger joint)
|
2016-03-28 16:55:41 +02:00
|
|
|
|
2016-03-25 14:02:52 +01:00
|
|
|
"""
|
|
|
|
def drawbolt(self, pos):
|
2016-03-28 16:55:41 +02:00
|
|
|
"""Add a bolt to this segment?
|
|
|
|
|
|
|
|
:param pos: number of the finger
|
|
|
|
|
|
|
|
"""
|
2016-03-25 14:02:52 +01:00
|
|
|
return False
|
|
|
|
|
|
|
|
def numFingers(self, numfingers):
|
2016-03-28 16:55:41 +02:00
|
|
|
"""Return next smaller, possible number of fingers
|
|
|
|
|
|
|
|
:param numfingers: number of fingers to aim for
|
|
|
|
|
|
|
|
"""
|
2016-03-25 14:02:52 +01:00
|
|
|
return numFingers
|
|
|
|
|
|
|
|
def _even(self, numFingers):
|
2016-03-28 16:55:41 +02:00
|
|
|
"""
|
|
|
|
Return same or next smaller even number
|
|
|
|
|
|
|
|
:param numFingers:
|
|
|
|
|
|
|
|
"""
|
2016-03-25 14:02:52 +01:00
|
|
|
return (numFingers//2) * 2
|
|
|
|
def _odd(self, numFingers):
|
2016-03-28 16:55:41 +02:00
|
|
|
"""
|
|
|
|
Return same or next smaller odd number
|
|
|
|
|
|
|
|
:param numFingers:
|
|
|
|
|
|
|
|
"""
|
2016-03-25 14:02:52 +01:00
|
|
|
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
|
2016-03-28 16:55:41 +02:00
|
|
|
|
2016-03-25 14:02:52 +01:00
|
|
|
def numFingers(self, numFingers):
|
|
|
|
if self.bolts % 2:
|
|
|
|
self.fingers = self._even(numFingers)
|
|
|
|
else:
|
|
|
|
self.fingers = numFingers
|
|
|
|
return self.fingers
|
|
|
|
|
|
|
|
def drawBolt(self, pos):
|
2016-03-28 16:55:41 +02:00
|
|
|
"""
|
|
|
|
Return if this finger needs a bolt
|
|
|
|
|
|
|
|
:param pos: number of this finger
|
|
|
|
|
|
|
|
"""
|
2016-03-25 14:02:52 +01:00
|
|
|
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
|
|
|
|
|
|
|
|
#############################################################################
|
|
|
|
### Settings
|
|
|
|
#############################################################################
|
|
|
|
|
2016-05-20 20:32:40 +02:00
|
|
|
class Settings(object):
|
2016-03-28 16:55:41 +02:00
|
|
|
"""Generic Settings class
|
|
|
|
|
|
|
|
Used by different other classes to store messurements and details.
|
2016-04-04 01:28:02 +02:00
|
|
|
Supports absolute values and settings that grow with the thickness
|
2016-03-28 16:55:41 +02:00
|
|
|
of the material used.
|
2016-04-04 01:28:02 +02:00
|
|
|
|
|
|
|
Overload the absolute_params and relative_params class attributes with
|
|
|
|
the suported keys and default values. The values are available via
|
|
|
|
attribute access.
|
2016-03-28 16:55:41 +02:00
|
|
|
"""
|
2016-03-25 14:02:52 +01:00
|
|
|
absolute_params = { }
|
|
|
|
relative_params = { }
|
|
|
|
|
|
|
|
def __init__(self, thickness, relative=True, **kw):
|
|
|
|
self.values = self.absolute_params.copy()
|
2016-04-09 14:22:41 +02:00
|
|
|
self.thickness = thickness
|
2016-03-25 14:02:52 +01:00
|
|
|
factor = 1.0
|
|
|
|
if relative:
|
|
|
|
factor = thickness
|
|
|
|
for name, value in self.relative_params.items():
|
|
|
|
self.values[name] = value * factor
|
|
|
|
self.setValues(thickness, relative, **kw)
|
|
|
|
|
|
|
|
def setValues(self, thickness, relative=True, **kw):
|
2016-03-28 16:55:41 +02:00
|
|
|
"""
|
|
|
|
Set values
|
|
|
|
|
|
|
|
:param thickness: thickness of the material used
|
2016-04-04 01:28:02 +02:00
|
|
|
:param relative: (Default value = True) Do scale by thickness
|
|
|
|
:param \*\*kw: parameters to set
|
2016-03-28 16:55:41 +02:00
|
|
|
|
|
|
|
"""
|
2016-03-25 14:02:52 +01:00
|
|
|
factor = 1.0
|
|
|
|
if relative:
|
|
|
|
factor = thickness
|
|
|
|
for name, value in kw.items():
|
|
|
|
if name in self.absolute_params:
|
|
|
|
self.values[name] = value
|
|
|
|
elif name in self.relative_params:
|
|
|
|
self.values[name] = value * factor
|
|
|
|
else:
|
|
|
|
raise ValueError("Unknown parameter for %s: %s" % (
|
|
|
|
self.__class__.__name__, name))
|
|
|
|
|
|
|
|
def __getattr__(self, name):
|
|
|
|
return self.values[name]
|
|
|
|
|
|
|
|
#############################################################################
|
|
|
|
### Edges
|
|
|
|
#############################################################################
|
|
|
|
|
|
|
|
|
2016-05-20 20:32:40 +02:00
|
|
|
class BaseEdge(object):
|
2016-04-17 16:30:11 +02:00
|
|
|
"""Abstract base class for all Edges"""
|
|
|
|
char = None
|
2016-04-17 14:26:27 +02:00
|
|
|
description = "Abstract Edge Class"
|
2016-03-25 14:02:52 +01:00
|
|
|
|
|
|
|
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):
|
2016-04-04 01:28:02 +02:00
|
|
|
"""Draw edge of length mm"""
|
2016-03-25 14:02:52 +01:00
|
|
|
self.ctx.move_to(0,0)
|
|
|
|
self.ctx.line_to(length, 0)
|
|
|
|
self.ctx.translate(*self.ctx.get_current_point())
|
|
|
|
|
2016-04-18 22:05:11 +02:00
|
|
|
def startwidth(self):
|
2016-03-28 16:55:41 +02:00
|
|
|
"""Amount of space the beginning of the edge is set below the inner space of the part """
|
2016-03-25 14:02:52 +01:00
|
|
|
return 0.0
|
|
|
|
|
2016-04-18 22:05:11 +02:00
|
|
|
def endwidth(self):
|
|
|
|
return self.startwidth()
|
|
|
|
|
2016-03-25 14:02:52 +01:00
|
|
|
def margin(self):
|
2016-03-28 16:55:41 +02:00
|
|
|
"""Space needed right of the starting point"""
|
2016-03-25 14:02:52 +01:00
|
|
|
return self.boxes.spacing
|
|
|
|
|
|
|
|
def spacing(self):
|
2016-03-28 16:55:41 +02:00
|
|
|
"""Space the edge needs outside of the inner space of the part"""
|
2016-04-18 22:05:11 +02:00
|
|
|
return self.startwidth() + self.margin()
|
2016-03-25 14:02:52 +01:00
|
|
|
|
2016-04-17 18:04:23 +02:00
|
|
|
def startAngle(self):
|
|
|
|
"""Not yet supported"""
|
|
|
|
return 0.0
|
|
|
|
|
|
|
|
def endAngle(self):
|
|
|
|
"""Not yet supported"""
|
|
|
|
return 0.0
|
|
|
|
|
2016-04-17 17:43:16 +02:00
|
|
|
class Edge(BaseEdge):
|
2016-04-17 16:30:11 +02:00
|
|
|
"""Straight edge"""
|
|
|
|
char = 'e'
|
2016-04-17 14:26:27 +02:00
|
|
|
description = "Straight Edge"
|
2016-03-25 14:02:52 +01:00
|
|
|
|
2016-04-17 16:30:11 +02:00
|
|
|
class OutSetEdge(BaseEdge):
|
2016-03-28 16:55:41 +02:00
|
|
|
"""Straight edge out set by one thickness"""
|
2016-03-25 14:02:52 +01:00
|
|
|
char = 'E'
|
2016-04-17 14:26:27 +02:00
|
|
|
description = "Straight Edge (outset by thickness)"
|
2016-03-25 14:02:52 +01:00
|
|
|
|
2016-04-18 22:05:11 +02:00
|
|
|
def startwidth(self):
|
2016-03-25 14:02:52 +01:00
|
|
|
return self.boxes.thickness
|
|
|
|
|
2016-06-30 14:25:29 +02:00
|
|
|
#############################################################################
|
|
|
|
#### Gripping Edge
|
|
|
|
#############################################################################
|
|
|
|
|
|
|
|
class GripSettings(Settings):
|
|
|
|
"""Settings for GrippingEdge
|
|
|
|
Values:
|
|
|
|
|
|
|
|
* absolute_params
|
|
|
|
|
|
|
|
* style : A : "A" (wiggly line) or "B" (bumps)
|
|
|
|
* outset : True : extend outward the straight edge
|
|
|
|
|
|
|
|
* relative (in multiples of thickness)
|
|
|
|
|
|
|
|
* depth : 0.3 : depth of the grooves
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
absolute_params = {
|
|
|
|
"style" : "A",
|
|
|
|
"outset" : True,
|
|
|
|
}
|
|
|
|
|
|
|
|
relative_params = {
|
|
|
|
"depth" : 0.3,
|
|
|
|
}
|
|
|
|
|
|
|
|
class GrippingEdge(BaseEdge):
|
|
|
|
description = """Corrugated edge useful as an gipping area"""
|
|
|
|
char = 'g'
|
|
|
|
|
|
|
|
|
|
|
|
def A(self, length):
|
|
|
|
depth = self.settings.depth
|
|
|
|
grooves = int(length // (depth*2.0)) + 1
|
|
|
|
depth = length / grooves / 4.0
|
|
|
|
|
|
|
|
o = 1 if self.settings.outset else -1
|
|
|
|
for groove in range(grooves):
|
|
|
|
self.corner(o * -90, depth)
|
|
|
|
self.corner(o * 180, depth)
|
|
|
|
self.corner(o * -90, depth)
|
|
|
|
|
|
|
|
def B(self, length):
|
|
|
|
depth = self.settings.depth
|
|
|
|
grooves = int(length // (depth*2.0)) + 1
|
|
|
|
depth = length / grooves / 2.0
|
|
|
|
o = 1 if self.settings.outset else -1
|
|
|
|
if self.settings.outset:
|
|
|
|
self.corner(-90)
|
|
|
|
else:
|
|
|
|
self.corner(90)
|
|
|
|
self.edge(depth)
|
|
|
|
self.corner(-180)
|
|
|
|
for groove in range(grooves):
|
|
|
|
self.corner(180, depth)
|
|
|
|
self.corner(-180, 0)
|
|
|
|
if self.settings.outset:
|
|
|
|
self.corner(90)
|
|
|
|
else:
|
|
|
|
self.edge(depth)
|
|
|
|
self.corner(90)
|
|
|
|
|
|
|
|
def margin(self):
|
|
|
|
if self.settings.outset:
|
|
|
|
return self.settings.depth + self.boxes.spacing
|
|
|
|
else:
|
|
|
|
return self.boxes.spacing
|
|
|
|
|
|
|
|
def __call__(self, length, **kw):
|
2016-07-03 09:55:59 +02:00
|
|
|
if length == 0.0:
|
|
|
|
return
|
2016-06-30 14:25:29 +02:00
|
|
|
getattr(self, self.settings.style)(length)
|
|
|
|
|
2016-04-17 16:30:11 +02:00
|
|
|
class CompoundEdge(BaseEdge):
|
2016-03-28 16:55:41 +02:00
|
|
|
"""Edge composed of multiple different Edges"""
|
2016-04-17 14:26:27 +02:00
|
|
|
description = "Compound Edge"
|
2016-03-28 16:55:41 +02:00
|
|
|
|
2016-03-25 14:02:52 +01:00
|
|
|
def __init__(self, boxes, types, lengths):
|
2016-05-20 20:32:40 +02:00
|
|
|
super(CompoundEdge, self).__init__(boxes, None)
|
2016-03-25 14:02:52 +01:00
|
|
|
self.types = [self.edges.get(edge, edge) for edge in types]
|
|
|
|
self.lengths = lengths
|
|
|
|
self.length = sum(lengths)
|
|
|
|
|
2016-04-18 22:05:11 +02:00
|
|
|
def startwidth(self):
|
|
|
|
return self.types[0].startwidth()
|
|
|
|
|
|
|
|
def endwidth(self):
|
|
|
|
return self.types[-1].endwidth()
|
2016-03-25 14:02:52 +01:00
|
|
|
|
|
|
|
def margin(self):
|
|
|
|
return max((e.margin() for e in self.types))
|
|
|
|
|
|
|
|
def __call__(self, length, **kw):
|
|
|
|
if length and abs(length - self.length) > 1E-5:
|
|
|
|
raise ValueError("Wrong length for CompoundEdge")
|
2016-04-18 22:05:11 +02:00
|
|
|
lastwidth = self.types[0].startwidth()
|
2016-03-25 14:02:52 +01:00
|
|
|
for e, l in zip(self.types, self.lengths):
|
2016-04-18 22:05:11 +02:00
|
|
|
diff = e.startwidth() - lastwidth
|
|
|
|
if diff > 1E-5:
|
|
|
|
self.boxes.corner(-90)
|
|
|
|
self.boxes.edge(diff)
|
|
|
|
self.boxes.corner(90)
|
|
|
|
elif diff < -1E-5:
|
|
|
|
self.boxes.corner(90)
|
|
|
|
self.boxes.edge(-diff)
|
|
|
|
self.boxes.corner(-90)
|
2016-03-25 14:02:52 +01:00
|
|
|
e(l)
|
2016-04-18 22:05:11 +02:00
|
|
|
lastwidth = e.endwidth()
|
2016-03-25 14:02:52 +01:00
|
|
|
|
2016-04-09 14:24:32 +02:00
|
|
|
#############################################################################
|
|
|
|
#### Slots
|
|
|
|
#############################################################################
|
|
|
|
|
2016-04-17 16:30:11 +02:00
|
|
|
class Slot(BaseEdge):
|
2016-03-28 16:55:41 +02:00
|
|
|
"""Edge with an slot to slid another pice through """
|
2016-04-17 16:30:11 +02:00
|
|
|
|
2016-04-17 14:26:27 +02:00
|
|
|
description = "Slot"
|
|
|
|
|
2016-03-25 14:02:52 +01:00
|
|
|
def __init__(self, boxes, depth):
|
2016-05-20 20:32:40 +02:00
|
|
|
super(Slot, self).__init__(boxes, None)
|
2016-03-25 14:02:52 +01:00
|
|
|
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)
|
|
|
|
|
2016-04-17 16:30:11 +02:00
|
|
|
class SlottedEdge(BaseEdge):
|
2016-03-28 16:55:41 +02:00
|
|
|
"""Edge with multiple slots"""
|
2016-04-17 14:26:27 +02:00
|
|
|
description = "Straight Edge with slots"
|
2016-03-25 14:02:52 +01:00
|
|
|
|
|
|
|
def __init__(self, boxes, sections, edge="e", slots=0):
|
2016-05-20 20:32:40 +02:00
|
|
|
super(SlottedEdge, self).__init__(boxes, None)
|
2016-03-25 14:02:52 +01:00
|
|
|
self.edge = self.edges.get(edge, edge)
|
|
|
|
self.sections = sections
|
|
|
|
self.slots = slots
|
|
|
|
|
2016-04-18 22:05:11 +02:00
|
|
|
def startwidth(self):
|
|
|
|
return self.edge.startwidth()
|
|
|
|
|
|
|
|
def endwidth(self):
|
|
|
|
return self.edge.endwidth()
|
2016-03-25 14:02:52 +01:00
|
|
|
|
|
|
|
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])
|
|
|
|
|
2016-04-09 14:24:32 +02:00
|
|
|
#############################################################################
|
|
|
|
#### Finger Joints
|
|
|
|
#############################################################################
|
|
|
|
|
2016-03-25 14:02:52 +01:00
|
|
|
class FingerJointSettings(Settings):
|
2016-04-04 01:28:02 +02:00
|
|
|
"""Settings for finger joints
|
|
|
|
|
|
|
|
Values:
|
|
|
|
|
|
|
|
* absolute
|
|
|
|
|
|
|
|
* surroundingspaces : 2 : maximum space at the start and end in multiple
|
|
|
|
of normal spaces
|
2016-03-28 16:55:41 +02:00
|
|
|
|
2016-04-04 01:28:02 +02:00
|
|
|
* relative (in multiples of thickness)
|
|
|
|
|
|
|
|
* space : 1.0 : space between fingers
|
|
|
|
* finger : 1.0 : width of the fingers
|
|
|
|
* height : 1.0 : length of the fingers
|
|
|
|
* width : 1.0 : width of finger holes
|
2016-07-03 16:37:11 +02:00
|
|
|
* edge_width : 1.0 : space below holes of FingerHoleEdge
|
2016-04-04 01:28:02 +02:00
|
|
|
|
|
|
|
"""
|
2016-03-28 16:55:41 +02:00
|
|
|
|
2016-03-25 14:02:52 +01:00
|
|
|
absolute_params = {
|
|
|
|
"surroundingspaces" : 2,
|
|
|
|
}
|
|
|
|
|
|
|
|
relative_params = {
|
|
|
|
"space" : 1.0,
|
|
|
|
"finger" : 1.0,
|
|
|
|
"height" : 1.0,
|
|
|
|
"width" : 1.0,
|
2016-07-03 16:37:11 +02:00
|
|
|
"edge_width" : 1.0,
|
2016-03-25 14:02:52 +01:00
|
|
|
}
|
|
|
|
|
2016-04-17 16:30:11 +02:00
|
|
|
class FingerJointEdge(BaseEdge):
|
2016-03-28 16:55:41 +02:00
|
|
|
"""Finger joint edge """
|
2016-03-25 14:02:52 +01:00
|
|
|
char = 'f'
|
2016-04-17 14:26:27 +02:00
|
|
|
description = "Finger Joint"
|
2016-03-25 14:02:52 +01:00
|
|
|
positive = True
|
|
|
|
|
|
|
|
def __call__(self, length,
|
|
|
|
bedBolts=None, bedBoltSettings=None, **kw):
|
|
|
|
positive = self.positive
|
|
|
|
space, finger = self.settings.space, self.settings.finger
|
|
|
|
|
|
|
|
fingers = int((length-(self.settings.surroundingspaces-1)*space) //
|
|
|
|
(space+finger))
|
|
|
|
|
|
|
|
if bedBolts:
|
|
|
|
fingers = bedBolts.numFingers(fingers)
|
|
|
|
leftover = length - fingers*(space+finger) + space
|
|
|
|
|
|
|
|
s, f, thickness = space, finger, self.thickness
|
|
|
|
d, d_nut, h_nut, l, l1 = bedBoltSettings or self.boxes.bedBoltSettings
|
|
|
|
p = 1 if positive else -1
|
|
|
|
|
|
|
|
if fingers <= 0:
|
|
|
|
fingers = 0
|
|
|
|
leftover = length
|
|
|
|
|
|
|
|
self.edge(leftover/2.0)
|
|
|
|
for i in range(fingers):
|
|
|
|
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)
|
|
|
|
self.corner(-90*p)
|
|
|
|
self.edge(self.settings.height)
|
|
|
|
self.corner(90*p)
|
|
|
|
self.edge(f)
|
|
|
|
self.corner(90*p)
|
|
|
|
self.edge(self.settings.height)
|
|
|
|
self.corner(-90*p)
|
|
|
|
self.edge(leftover/2.0)
|
|
|
|
|
|
|
|
def margin(self):
|
2016-03-28 16:55:41 +02:00
|
|
|
""" """
|
2016-03-25 14:02:52 +01:00
|
|
|
return self.boxes.spacing + self.boxes.thickness
|
|
|
|
|
|
|
|
class FingerJointEdgeCounterPart(FingerJointEdge):
|
2016-03-28 16:55:41 +02:00
|
|
|
"""Finger joint edge - other side"""
|
2016-03-25 14:02:52 +01:00
|
|
|
char = 'F'
|
2016-04-17 14:26:27 +02:00
|
|
|
description = "Finger Joint (opposing side)"
|
2016-03-25 14:02:52 +01:00
|
|
|
positive = False
|
|
|
|
|
2016-04-26 12:19:43 +02:00
|
|
|
def startwidth(self):
|
2016-03-28 16:55:41 +02:00
|
|
|
""" """
|
2016-03-25 14:02:52 +01:00
|
|
|
return self.boxes.thickness
|
|
|
|
|
|
|
|
def margin(self):
|
2016-03-28 16:55:41 +02:00
|
|
|
""" """
|
2016-03-25 14:02:52 +01:00
|
|
|
return self.boxes.spacing
|
|
|
|
|
|
|
|
class FingerHoles:
|
2016-04-04 01:28:02 +02:00
|
|
|
"""Hole matching a finger joint edge"""
|
2016-03-25 14:02:52 +01:00
|
|
|
def __init__(self, boxes, settings):
|
|
|
|
self.boxes = boxes
|
|
|
|
self.ctx = boxes.ctx
|
|
|
|
self.settings = settings
|
|
|
|
|
2016-04-09 16:18:05 +02:00
|
|
|
def __call__(self, x, y, length, angle=90, bedBolts=None, bedBoltSettings=None):
|
|
|
|
"""
|
|
|
|
Draw holes for a matching finger joint edge
|
|
|
|
|
|
|
|
:param x: position
|
|
|
|
:param y: position
|
|
|
|
:param length: length of matching edge
|
|
|
|
:param angle: (Default value = 90)
|
|
|
|
:param bedBolts: (Default value = None)
|
|
|
|
:param bedBoltSettings: (Default value = None)
|
|
|
|
|
|
|
|
"""
|
|
|
|
self.boxes.ctx.save()
|
|
|
|
self.boxes.moveTo(x, y, angle)
|
|
|
|
|
2016-03-25 14:02:52 +01:00
|
|
|
s, f = self.settings.space, self.settings.finger
|
|
|
|
fingers = int((length-(self.settings.surroundingspaces-1)*s) //
|
|
|
|
(s+f))
|
|
|
|
if bedBolts:
|
|
|
|
fingers = bedBolts.numFingers(fingers)
|
|
|
|
d, d_nut, h_nut, l, l1 = bedBoltSettings or self.boxes.bedBoltSettings
|
|
|
|
leftover = length - fingers*(s+f) - f
|
|
|
|
b = self.boxes.burn
|
|
|
|
if self.boxes.debug:
|
|
|
|
self.ctx.rectangle(0, -self.settings.width/2+b,
|
|
|
|
length, self.settings.width - 2*b)
|
|
|
|
for i in range(fingers):
|
|
|
|
pos = leftover/2.0+i*(s+f)
|
|
|
|
if bedBolts and bedBolts.drawBolt(i):
|
|
|
|
self.boxes.hole(pos+0.5*s, 0, d*0.5)
|
2016-06-21 11:25:17 +02:00
|
|
|
self.boxes.rectangularHole(pos+s+0.5*f, 0,
|
|
|
|
f, self.settings.width)
|
2016-03-25 14:02:52 +01:00
|
|
|
|
2016-04-09 16:18:05 +02:00
|
|
|
self.ctx.restore()
|
|
|
|
|
2016-04-17 16:30:11 +02:00
|
|
|
class FingerHoleEdge(BaseEdge):
|
2016-04-09 16:18:05 +02:00
|
|
|
"""Edge with holes for a parallel finger joint"""
|
|
|
|
char = 'h'
|
2016-04-17 14:26:27 +02:00
|
|
|
description = "Edge (parallel Finger Joint Holes)"
|
2016-04-09 16:18:05 +02:00
|
|
|
def __init__(self, boxes, fingerHoles=None, **kw):
|
2016-05-20 20:32:40 +02:00
|
|
|
super(FingerHoleEdge, self).__init__(boxes, None, **kw)
|
2016-04-09 16:18:05 +02:00
|
|
|
self.fingerHoles = fingerHoles or boxes.fingerHolesAt
|
|
|
|
|
2016-07-03 16:37:11 +02:00
|
|
|
def __call__(self, length,
|
2016-04-09 16:18:05 +02:00
|
|
|
bedBolts=None, bedBoltSettings=None, **kw):
|
2016-07-03 16:37:11 +02:00
|
|
|
dist = self.fingerHoles.settings.edge_width
|
2016-04-09 16:18:05 +02:00
|
|
|
self.ctx.save()
|
|
|
|
self.fingerHoles(0, dist+self.thickness/2, length, 0,
|
|
|
|
bedBolts=bedBolts, bedBoltSettings=bedBoltSettings)
|
|
|
|
self.ctx.restore()
|
|
|
|
# XXX continue path
|
|
|
|
self.ctx.move_to(0, 0)
|
|
|
|
self.ctx.line_to(length, 0)
|
2016-03-25 14:02:52 +01:00
|
|
|
self.ctx.translate(*self.ctx.get_current_point())
|
|
|
|
|
2016-04-18 22:05:11 +02:00
|
|
|
def startwidth(self):
|
2016-04-09 16:18:05 +02:00
|
|
|
""" """
|
2016-07-03 16:37:11 +02:00
|
|
|
return self.fingerHoles.settings.edge_width + self.thickness
|
2016-04-09 16:18:05 +02:00
|
|
|
|
2016-04-17 16:30:11 +02:00
|
|
|
class CrossingFingerHoleEdge(BaseEdge):
|
2016-03-28 16:55:41 +02:00
|
|
|
"""Edge with holes for finger joints 90° above"""
|
2016-04-17 14:26:27 +02:00
|
|
|
|
|
|
|
description = "Edge (orthogonal Finger Joint Holes)"
|
|
|
|
|
2016-04-09 16:18:05 +02:00
|
|
|
def __init__(self, boxes, height, fingerHoles=None, **kw):
|
2016-05-20 20:32:40 +02:00
|
|
|
super(CrossingFingerHoleEdge, self).__init__(boxes, None, **kw)
|
2016-04-09 16:18:05 +02:00
|
|
|
self.fingerHoles = fingerHoles or boxes.fingerHolesAt
|
2016-03-25 14:02:52 +01:00
|
|
|
self.height = height
|
|
|
|
|
|
|
|
def __call__(self, length, **kw):
|
2016-04-09 16:18:05 +02:00
|
|
|
self.fingerHoles(length/2.0, 0, self.height)
|
2016-05-22 12:37:02 +02:00
|
|
|
super(CrossingFingerHoleEdge, self).__call__(length)
|
2016-03-25 14:02:52 +01:00
|
|
|
|
2016-04-09 14:27:51 +02:00
|
|
|
#############################################################################
|
|
|
|
#### Stackable Joints
|
|
|
|
#############################################################################
|
|
|
|
|
|
|
|
class StackableSettings(Settings):
|
|
|
|
"""Settings for StackableEdge classes
|
|
|
|
|
|
|
|
Values:
|
|
|
|
|
|
|
|
* absolute_params
|
|
|
|
|
|
|
|
* angle : 60 : inside angle of the feet
|
|
|
|
|
|
|
|
* relative (in multiples of thickness)
|
|
|
|
|
|
|
|
* height : 2.0 : height of the feet
|
|
|
|
* width : 4.0 : width of the feet
|
|
|
|
* holedistance : 1.0 : distance from finger holes to bottom edge
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
absolute_params = {
|
|
|
|
"angle" : 60,
|
|
|
|
}
|
|
|
|
relative_params = {
|
|
|
|
"height" : 2.0,
|
|
|
|
"width" : 4.0,
|
|
|
|
"holedistance" : 1.0,
|
|
|
|
}
|
|
|
|
|
2016-04-17 16:30:11 +02:00
|
|
|
class StackableEdge(BaseEdge):
|
2016-04-09 14:27:51 +02:00
|
|
|
"""Edge for having stackable Boxes. The Edge creates feet on the bottom
|
|
|
|
and has matching recesses on the top corners."""
|
|
|
|
|
|
|
|
char = "s"
|
2016-04-17 14:26:27 +02:00
|
|
|
description = "Stackable (bottom, finger joint holes)"
|
2016-04-09 14:27:51 +02:00
|
|
|
bottom = True
|
|
|
|
|
|
|
|
def __init__(self, boxes, settings, fingerjointsettings):
|
2016-05-20 20:32:40 +02:00
|
|
|
super(StackableEdge, self).__init__(boxes, settings)
|
2016-04-09 14:27:51 +02:00
|
|
|
self.fingerjointsettings = fingerjointsettings
|
|
|
|
|
|
|
|
def __call__(self, length, **kw):
|
|
|
|
s = self.settings
|
|
|
|
r = s.height / 2.0 / (1-math.cos(math.radians(s.angle)))
|
|
|
|
l = r * math.sin(math.radians(s.angle))
|
|
|
|
p = 1 if self.bottom else -1
|
|
|
|
|
|
|
|
if self.bottom:
|
|
|
|
self.boxes.fingerHolesAt(0, s.height+self.settings.holedistance+0.5*self.boxes.thickness,
|
|
|
|
length, 0)
|
|
|
|
|
|
|
|
self.boxes.edge(s.width)
|
|
|
|
self.boxes.corner(p*s.angle, r)
|
|
|
|
self.boxes.corner(-p*s.angle, r)
|
|
|
|
self.boxes.edge(length-2*s.width-4*l)
|
|
|
|
self.boxes.corner(-p*s.angle, r)
|
|
|
|
self.boxes.corner(p*s.angle, r)
|
|
|
|
self.boxes.edge(s.width)
|
|
|
|
|
|
|
|
def _height(self):
|
|
|
|
return self.settings.height + self.settings.holedistance + self.settings.thickness
|
|
|
|
|
2016-04-18 22:05:11 +02:00
|
|
|
def startwidth(self):
|
2016-04-09 14:27:51 +02:00
|
|
|
return self._height() if self.bottom else 0
|
|
|
|
|
|
|
|
def margin(self):
|
|
|
|
return 0 if self.bottom else self._height()
|
|
|
|
|
|
|
|
class StackableEdgeTop(StackableEdge):
|
|
|
|
char = "S"
|
2016-04-17 14:26:27 +02:00
|
|
|
description = "Stackable (top)"
|
2016-04-09 14:27:51 +02:00
|
|
|
bottom = False
|
|
|
|
|
2016-05-25 19:59:49 +02:00
|
|
|
#############################################################################
|
|
|
|
#### Hinges
|
|
|
|
#############################################################################
|
|
|
|
|
|
|
|
class HingeSettings(Settings):
|
|
|
|
|
|
|
|
"""Settings for Hinge and HingePin classes
|
|
|
|
Values:
|
|
|
|
|
|
|
|
* absolute_params
|
|
|
|
|
|
|
|
* style : B : currently "A" or "B"
|
|
|
|
* outset : False : have lid overlap at the sides (similar to OutSetEdge)
|
|
|
|
* pinwidth : 1.0 : set to lower value to get disks surrounding the pins
|
2016-07-03 09:55:59 +02:00
|
|
|
* grip_percentage" : 0 : percentage of the lid that should get grips
|
2016-05-25 19:59:49 +02:00
|
|
|
|
|
|
|
* relative (in multiples of thickness)
|
|
|
|
|
|
|
|
* hingestrength : 1 : thickness of the arc holding the pin in place
|
|
|
|
* axle : 2 : diameter of the pin hole
|
2016-07-03 09:55:59 +02:00
|
|
|
* grip_length : 0 : fixed length of the grips on he lids
|
2016-05-25 19:59:49 +02:00
|
|
|
|
|
|
|
"""
|
|
|
|
absolute_params = {
|
2016-07-06 23:08:50 +02:00
|
|
|
"style" : "A",
|
2016-05-25 19:59:49 +02:00
|
|
|
"outset" : False,
|
|
|
|
"pinwidth" : 0.5,
|
2016-07-03 09:55:59 +02:00
|
|
|
"grip_percentage" : 0,
|
2016-05-25 19:59:49 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
relative_params = {
|
|
|
|
"hingestrength" : 1,#1.5-0.5*2**0.5,
|
|
|
|
"axle" : 2,
|
2016-07-03 09:55:59 +02:00
|
|
|
"grip_length" : 0,
|
2016-05-25 19:59:49 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
class Hinge(BaseEdge):
|
|
|
|
|
|
|
|
char = 'i'
|
|
|
|
description = "Straight edge with hinge eye"
|
|
|
|
|
|
|
|
def __init__(self, boxes, settings=None, layout=1):
|
|
|
|
super(Hinge, self).__init__(boxes,settings)
|
2016-07-03 09:55:59 +02:00
|
|
|
if not (0 < layout <= 3):
|
|
|
|
raise ValueError("layout must be 1, 2 or 3 (got %i)" % layout)
|
2016-05-25 19:59:49 +02:00
|
|
|
self.layout = layout
|
|
|
|
self.char = "eijk"[layout]
|
|
|
|
self.description = self.description + ('', ' (start)', ' (end)', ' (both ends)')[layout]
|
|
|
|
|
|
|
|
def margin(self):
|
|
|
|
return 3 * self.thickness
|
|
|
|
|
|
|
|
def A(self, _reversed=False):
|
|
|
|
t = self.thickness
|
2016-07-06 23:08:50 +02:00
|
|
|
r = 0.5 * self.settings.axle
|
|
|
|
alpha = math.degrees(math.asin(0.5*t/r))
|
|
|
|
pinl = (self.settings.axle**2-self.thickness**2)**0.5 * self.settings.pinwidth
|
|
|
|
pos = math.cos(math.radians(alpha)) * r
|
2016-05-25 19:59:49 +02:00
|
|
|
hinge = (
|
|
|
|
0,
|
2016-07-06 23:08:50 +02:00
|
|
|
90-alpha, 0,
|
2016-05-25 19:59:49 +02:00
|
|
|
(-360, r), 0,
|
2016-07-06 23:08:50 +02:00
|
|
|
90+alpha,
|
2016-05-25 19:59:49 +02:00
|
|
|
t,
|
|
|
|
90,
|
|
|
|
0.5*t,
|
2016-07-06 23:08:50 +02:00
|
|
|
(180, t+ pos), 0,
|
2016-05-25 19:59:49 +02:00
|
|
|
(-90, 0.5*t), 0
|
|
|
|
)
|
|
|
|
if _reversed:
|
|
|
|
hinge = reversed(hinge)
|
2016-07-06 23:08:50 +02:00
|
|
|
self.polyline(*hinge)
|
|
|
|
self.boxes.rectangularHole(-pos, -0.5*t, pinl, self.thickness)
|
|
|
|
else:
|
|
|
|
self.boxes.rectangularHole(pos, -0.5*t, pinl, self.thickness)
|
|
|
|
self.polyline(*hinge)
|
2016-05-25 19:59:49 +02:00
|
|
|
|
|
|
|
def Alen(self):
|
2016-07-06 23:08:50 +02:00
|
|
|
t = self.thickness
|
|
|
|
r = 0.5 * self.settings.axle
|
|
|
|
alpha = math.degrees(math.asin(0.5*t/r))
|
|
|
|
pos = math.cos(math.radians(alpha)) * r
|
|
|
|
return 2 * pos + 1.5 * t
|
2016-05-25 19:59:49 +02:00
|
|
|
|
|
|
|
def B(self, _reversed=False):
|
|
|
|
t = self.thickness
|
|
|
|
|
|
|
|
hinge = (
|
|
|
|
0, -90,
|
|
|
|
0.5*t,
|
|
|
|
(180, 0.5*self.settings.axle+self.settings.hingestrength), 0,
|
|
|
|
(-90, 0.5*t), 0
|
|
|
|
)
|
|
|
|
pos = 0.5*self.settings.axle+self.settings.hingestrength
|
2016-07-06 23:07:44 +02:00
|
|
|
pinl = (self.settings.axle**2-self.thickness**2)**0.5 * self.settings.pinwidth
|
|
|
|
|
2016-05-25 19:59:49 +02:00
|
|
|
if _reversed:
|
|
|
|
hinge = reversed(hinge)
|
|
|
|
self.hole(0.5*t+pos, -0.5*t, 0.5*self.settings.axle)
|
2016-07-06 23:07:44 +02:00
|
|
|
self.boxes.rectangularHole(0.5*t+pos, -0.5*t, pinl, self.thickness)
|
2016-05-25 19:59:49 +02:00
|
|
|
else:
|
|
|
|
self.hole(pos, -0.5*t, 0.5*self.settings.axle)
|
2016-07-06 23:07:44 +02:00
|
|
|
self.boxes.rectangularHole(pos, -0.5*t, pinl, self.thickness)
|
2016-05-25 19:59:49 +02:00
|
|
|
self.polyline(*hinge)
|
|
|
|
|
|
|
|
def Blen(self):
|
|
|
|
return self.settings.axle + 2*self.settings.hingestrength + 0.5*self.thickness
|
|
|
|
|
|
|
|
def __call__(self, l, **kw):
|
|
|
|
hlen = getattr(self, self.settings.style+'len')()
|
|
|
|
if self.layout & 1:
|
|
|
|
getattr(self, self.settings.style)()
|
|
|
|
self.edge(l - (self.layout & 1)*hlen - bool(self.layout & 2)*hlen)
|
|
|
|
if self.layout & 2:
|
|
|
|
getattr(self, self.settings.style)(True)
|
|
|
|
|
|
|
|
class HingePin(BaseEdge):
|
|
|
|
char = 'I'
|
|
|
|
description = "Edge with hinge pin"
|
|
|
|
|
|
|
|
def __init__(self, boxes, settings=None, layout=1):
|
|
|
|
super(HingePin, self).__init__(boxes,settings)
|
2016-07-03 09:55:59 +02:00
|
|
|
if not (0 < layout <= 3):
|
|
|
|
raise ValueError("layout must be 1, 2 or 3 (got %i)" % layout)
|
2016-05-25 19:59:49 +02:00
|
|
|
self.layout = layout
|
|
|
|
self.char = "EIJK"[layout]
|
|
|
|
self.description = self.description + ('', ' (start)', ' (end)', ' (both ends)')[layout]
|
|
|
|
|
|
|
|
def startwidth(self):
|
|
|
|
if self.layout & 1:
|
|
|
|
return 0
|
|
|
|
else:
|
|
|
|
return self.settings.outset * self.boxes.thickness
|
|
|
|
|
|
|
|
def endwidth(self):
|
|
|
|
if self.layout & 2:
|
|
|
|
return 0
|
|
|
|
else:
|
|
|
|
return self.settings.outset * self.boxes.thickness
|
|
|
|
|
|
|
|
def margin(self):
|
|
|
|
return self.thickness + self.boxes.spacing
|
|
|
|
|
|
|
|
def A(self, _reversed=False):
|
|
|
|
t = self.thickness
|
2016-07-06 23:08:50 +02:00
|
|
|
r = 0.5 * self.settings.axle
|
|
|
|
alpha = math.degrees(math.asin(0.5*t/r))
|
|
|
|
pos = math.cos(math.radians(alpha)) * r
|
|
|
|
pinl = (self.settings.axle**2-self.thickness**2)**0.5 * self.settings.pinwidth
|
|
|
|
pin = (pos-0.5*pinl, -90,
|
2016-05-25 19:59:49 +02:00
|
|
|
t, 90,
|
2016-07-06 23:08:50 +02:00
|
|
|
pinl,
|
2016-05-25 19:59:49 +02:00
|
|
|
90,
|
|
|
|
t,
|
|
|
|
-90)
|
|
|
|
|
|
|
|
if self.settings.outset:
|
|
|
|
pin += (
|
2016-07-06 23:08:50 +02:00
|
|
|
pos-0.5*pinl+1.5*t,
|
2016-05-25 19:59:49 +02:00
|
|
|
-90,
|
|
|
|
t,
|
|
|
|
90,
|
|
|
|
0,
|
|
|
|
)
|
|
|
|
else:
|
2016-07-06 23:08:50 +02:00
|
|
|
pin += (pos-0.5*pinl,)
|
2016-05-25 19:59:49 +02:00
|
|
|
if _reversed:
|
|
|
|
pin = reversed(pin)
|
|
|
|
self.polyline(*pin)
|
|
|
|
|
|
|
|
def Alen(self):
|
2016-07-06 23:08:50 +02:00
|
|
|
t = self.thickness
|
|
|
|
r = 0.5 * self.settings.axle
|
|
|
|
alpha = math.degrees(math.asin(0.5*t/r))
|
|
|
|
pos = math.cos(math.radians(alpha)) * r
|
|
|
|
|
2016-05-25 19:59:49 +02:00
|
|
|
if self.settings.outset:
|
2016-07-06 23:08:50 +02:00
|
|
|
return 2 * pos + 1.5* self.thickness
|
2016-05-25 19:59:49 +02:00
|
|
|
else:
|
2016-07-06 23:08:50 +02:00
|
|
|
return 2 * pos
|
2016-05-25 19:59:49 +02:00
|
|
|
|
|
|
|
def B(self, _reversed=False):
|
|
|
|
t = self.thickness
|
|
|
|
pinl = (self.settings.axle**2-t**2)**0.5 * self.settings.pinwidth
|
|
|
|
d = (self.settings.axle - pinl) / 2.0
|
|
|
|
pin = (self.settings.hingestrength+d, -90,
|
|
|
|
t, 90,
|
|
|
|
pinl,
|
|
|
|
90,
|
|
|
|
t,
|
|
|
|
-90, d)
|
|
|
|
|
|
|
|
if self.settings.outset:
|
|
|
|
pin += (
|
|
|
|
0,
|
|
|
|
self.settings.hingestrength+0.5*t,
|
|
|
|
-90,
|
|
|
|
t,
|
|
|
|
90,
|
|
|
|
0,
|
|
|
|
)
|
|
|
|
if _reversed:
|
|
|
|
pin = reversed(pin)
|
|
|
|
self.polyline(*pin)
|
|
|
|
|
|
|
|
def Blen(self):
|
|
|
|
l = self.settings.hingestrength+self.settings.axle
|
|
|
|
if self.settings.outset:
|
|
|
|
l += self.settings.hingestrength + 0.5 * self.thickness
|
|
|
|
return l
|
|
|
|
|
|
|
|
def __call__(self, l, **kw):
|
|
|
|
plen = getattr(self, self.settings.style+'len')()
|
2016-07-03 09:55:59 +02:00
|
|
|
glen = l * self.settings.grip_percentage + \
|
|
|
|
self.settings.grip_length
|
|
|
|
if not self.settings.outset:
|
|
|
|
glen = 0.0
|
|
|
|
glen = min(glen, l-plen)
|
2016-05-25 19:59:49 +02:00
|
|
|
|
2016-07-03 09:55:59 +02:00
|
|
|
if self.layout & 1 and self.layout & 2:
|
2016-05-25 19:59:49 +02:00
|
|
|
getattr(self, self.settings.style)()
|
2016-07-03 09:55:59 +02:00
|
|
|
self.edge(l - 2 * plen)
|
|
|
|
getattr(self, self.settings.style)(True)
|
|
|
|
elif self.layout & 1:
|
|
|
|
getattr(self, self.settings.style)()
|
|
|
|
self.edge(l - plen - glen)
|
|
|
|
self.edges['g'](glen)
|
|
|
|
else:
|
|
|
|
self.edges['g'](glen)
|
|
|
|
self.edge(l - plen - glen)
|
2016-05-25 19:59:49 +02:00
|
|
|
getattr(self, self.settings.style)(True)
|
|
|
|
|
2016-05-03 22:56:35 +02:00
|
|
|
#############################################################################
|
|
|
|
#### Click Joints
|
|
|
|
#############################################################################
|
|
|
|
|
|
|
|
class ClickSettings(Settings):
|
|
|
|
absolute_params = {
|
|
|
|
"angle" : 5,
|
|
|
|
}
|
|
|
|
|
|
|
|
relative_params = {
|
|
|
|
"depth" : 3.0,
|
|
|
|
"bottom_radius" : 0.1,
|
|
|
|
}
|
|
|
|
|
|
|
|
class ClickConnector(BaseEdge):
|
|
|
|
char = "c"
|
|
|
|
description = "Click on (bottom side)"
|
|
|
|
|
|
|
|
def hook(self, reverse=False):
|
|
|
|
t = self.thickness
|
|
|
|
a = self.settings.angle
|
|
|
|
d = self.settings.depth
|
|
|
|
r = self.settings.bottom_radius
|
|
|
|
c = math.cos(math.radians(a))
|
|
|
|
s = math.sin(math.radians(a))
|
|
|
|
|
|
|
|
p1 = (0, 90-a, c*d)
|
|
|
|
p2 = (
|
|
|
|
d+t,
|
|
|
|
-90,
|
|
|
|
t*0.5,
|
|
|
|
135,
|
|
|
|
t*2**0.5,
|
|
|
|
135,
|
|
|
|
d+2*t+s*0.5*t)
|
|
|
|
p3 = (c*d-s*c*0.2*t, -a, 0)
|
|
|
|
|
|
|
|
if not reverse:
|
|
|
|
self.polyline(*p1)
|
|
|
|
self.corner(-180, r)
|
|
|
|
self.polyline(*p2)
|
|
|
|
self.corner(-180+2*a, r)
|
|
|
|
self.polyline(*p3)
|
|
|
|
else:
|
|
|
|
self.polyline(*reversed(p3))
|
|
|
|
self.corner(-180+2*a, r)
|
|
|
|
self.polyline(*reversed(p2))
|
|
|
|
self.corner(-180, r)
|
|
|
|
self.polyline(*reversed(p1))
|
|
|
|
|
|
|
|
def hookWidth(self):
|
|
|
|
t = self.thickness
|
|
|
|
a = self.settings.angle
|
|
|
|
d = self.settings.depth
|
|
|
|
r = self.settings.bottom_radius
|
|
|
|
c = math.cos(math.radians(a))
|
|
|
|
s = math.sin(math.radians(a))
|
|
|
|
return 2 * s * d * c + 0.5 * c * t + c * 4 * r
|
|
|
|
|
|
|
|
def hookOffset(self):
|
|
|
|
t = self.thickness
|
|
|
|
a = self.settings.angle
|
|
|
|
d = self.settings.depth
|
|
|
|
r = self.settings.bottom_radius
|
|
|
|
c = math.cos(math.radians(a))
|
|
|
|
s = math.sin(math.radians(a))
|
|
|
|
return s * d * c + 2 * r
|
|
|
|
|
|
|
|
def finger(self, length):
|
|
|
|
t = self.thickness
|
|
|
|
self.polyline(
|
|
|
|
2*t,
|
|
|
|
90,
|
|
|
|
length,
|
|
|
|
90,
|
|
|
|
2*t,
|
|
|
|
)
|
|
|
|
|
|
|
|
def __call__(self, length, **kw):
|
|
|
|
t = self.thickness
|
|
|
|
self.edge(4*t)
|
|
|
|
self.hook()
|
|
|
|
self.finger(2*t)
|
|
|
|
self.hook(reverse=True)
|
|
|
|
|
|
|
|
self.edge(length - 2* (6*t + 2*self.hookWidth()))
|
|
|
|
|
|
|
|
self.hook()
|
|
|
|
self.finger(2*t)
|
|
|
|
self.hook(reverse=True)
|
|
|
|
self.edge(4*t)
|
|
|
|
|
|
|
|
def margin(self):
|
|
|
|
return 2 * self.thickness
|
|
|
|
|
|
|
|
class ClickEdge(ClickConnector):
|
|
|
|
char ="C"
|
|
|
|
description = "Click on (top)"
|
|
|
|
|
|
|
|
def startwidth(self):
|
|
|
|
return self.boxes.thickness
|
|
|
|
|
|
|
|
def margin(self):
|
|
|
|
return self.boxes.spacing
|
|
|
|
|
|
|
|
def __call__(self, length, **kw):
|
|
|
|
t = self.thickness
|
|
|
|
o = self.hookOffset()
|
|
|
|
w = self.hookWidth()
|
|
|
|
p1 = (
|
|
|
|
4*t + o,
|
|
|
|
90,
|
|
|
|
t,
|
|
|
|
-90,
|
|
|
|
2*(t+w-o),
|
|
|
|
-90,
|
|
|
|
t,
|
|
|
|
90,
|
|
|
|
0)
|
|
|
|
self.polyline(*p1)
|
|
|
|
self.edge(length - 2 * (6*t + 2* w) + 2*o)
|
|
|
|
self.polyline(*reversed(p1))
|
|
|
|
|
2016-04-09 14:24:32 +02:00
|
|
|
#############################################################################
|
|
|
|
#### Dove Tail Joints
|
|
|
|
#############################################################################
|
2016-03-25 14:02:52 +01:00
|
|
|
|
|
|
|
class DoveTailSettings(Settings):
|
2016-03-28 16:55:41 +02:00
|
|
|
"""Settings used for dove tail joints
|
|
|
|
|
2016-04-04 01:28:02 +02:00
|
|
|
Values:
|
|
|
|
|
|
|
|
* absolute
|
|
|
|
|
|
|
|
* angle : 50 : how much should fingers widen (-80 to 80)
|
|
|
|
|
|
|
|
* relative (in multiples of thickness)
|
|
|
|
|
|
|
|
* size : 3 : from one middle of a dove tail to another
|
|
|
|
* depth : 1.5 : how far the dove tails stick out of/into the edge
|
|
|
|
* radius : 0.2 : radius used on all four corners
|
|
|
|
|
|
|
|
"""
|
2016-03-25 14:02:52 +01:00
|
|
|
absolute_params = {
|
|
|
|
"angle" : 50,
|
|
|
|
}
|
|
|
|
relative_params = {
|
|
|
|
"size" : 3,
|
|
|
|
"depth" : 1.5,
|
|
|
|
"radius" : 0.2,
|
|
|
|
}
|
|
|
|
|
2016-04-17 16:30:11 +02:00
|
|
|
class DoveTailJoint(BaseEdge):
|
2016-03-28 16:55:41 +02:00
|
|
|
"""Edge with dove tail joints """
|
2016-03-25 14:02:52 +01:00
|
|
|
char = 'd'
|
2016-04-17 14:26:27 +02:00
|
|
|
description = "Dove Tail Joint"
|
2016-03-25 14:02:52 +01:00
|
|
|
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)
|
|
|
|
for i in range(sections):
|
|
|
|
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):
|
2016-03-28 16:55:41 +02:00
|
|
|
""" """
|
2016-03-25 14:02:52 +01:00
|
|
|
return self.settings.depth + self.boxes.spacing
|
|
|
|
|
|
|
|
class DoveTailJointCounterPart(DoveTailJoint):
|
2016-03-28 16:55:41 +02:00
|
|
|
"""Edge for other side of dove joints """
|
2016-03-25 14:02:52 +01:00
|
|
|
char = 'D'
|
2016-04-17 14:26:27 +02:00
|
|
|
description = "Dove Tail Joint (opposing side)"
|
2016-03-25 14:02:52 +01:00
|
|
|
|
|
|
|
positive = False
|
|
|
|
|
|
|
|
def margin(self):
|
|
|
|
return self.boxes.spacing
|
|
|
|
|
|
|
|
class FlexSettings(Settings):
|
2016-04-04 01:28:02 +02:00
|
|
|
"""Settings for one directional flex cuts
|
|
|
|
|
|
|
|
Values:
|
|
|
|
|
|
|
|
* absolute
|
|
|
|
|
|
|
|
* stretch : 1.05 : Hint of how much the flex part should be shortend
|
|
|
|
|
|
|
|
* relative (in multiples of thickness)
|
|
|
|
|
|
|
|
* distance : 0.5 : width of the pattern perpendicular to the cuts
|
|
|
|
* connection : 1.0 : width of the gaps in the cuts
|
|
|
|
* width" : 5.0 : width of the pattern in direction of the cuts
|
|
|
|
|
|
|
|
"""
|
2016-03-25 14:02:52 +01:00
|
|
|
relative_params = {
|
|
|
|
"distance" : 0.5,
|
|
|
|
"connection" : 1.0,
|
|
|
|
"width" : 5.0,
|
|
|
|
}
|
|
|
|
absolute_params = {
|
2016-03-30 21:09:19 +02:00
|
|
|
"stretch" : 1.05,
|
2016-03-25 14:02:52 +01:00
|
|
|
}
|
|
|
|
|
2016-04-17 16:30:11 +02:00
|
|
|
class FlexEdge(BaseEdge):
|
2016-03-28 16:55:41 +02:00
|
|
|
"""Edge with flex cuts - use straight edge for the opposing side"""
|
2016-03-25 14:02:52 +01:00
|
|
|
char = 'X'
|
2016-04-17 14:26:27 +02:00
|
|
|
description = "Flex cut"
|
2016-03-25 14:02:52 +01:00
|
|
|
|
|
|
|
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
|
|
|
|
|
2016-07-09 10:46:14 +02:00
|
|
|
for i in range(1, lines):
|
2016-03-25 14:02:52 +01:00
|
|
|
pos = i*dist + leftover/2
|
|
|
|
if i % 2:
|
|
|
|
self.ctx.move_to(pos, 0)
|
|
|
|
self.ctx.line_to(pos, connection+sheight)
|
|
|
|
for j in range((sections-1)//2):
|
|
|
|
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)
|
|
|
|
for j in range((sections-1)//2):
|
|
|
|
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:
|
|
|
|
for j in range(sections//2):
|
|
|
|
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())
|