2016-07-11 23:01:57 +02:00
|
|
|
#!/usr/bin/env python3
|
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/>.
|
2023-01-13 13:46:16 +01:00
|
|
|
from __future__ import annotations
|
2016-03-25 14:02:52 +01:00
|
|
|
|
2016-10-31 18:12:39 +01:00
|
|
|
import argparse
|
2022-12-31 15:52:55 +01:00
|
|
|
import inspect
|
|
|
|
import math
|
2016-10-31 18:12:39 +01:00
|
|
|
import re
|
2023-01-23 20:08:04 +01:00
|
|
|
from abc import ABC, abstractmethod
|
2023-01-13 13:46:16 +01:00
|
|
|
from typing import Any
|
2016-10-31 18:12:39 +01:00
|
|
|
|
2016-10-12 22:47:08 +02:00
|
|
|
from boxes import gears
|
2016-08-17 15:07:41 +02:00
|
|
|
|
2022-12-31 15:52:55 +01:00
|
|
|
|
2023-01-23 20:08:04 +01:00
|
|
|
def argparseSections(s: str) -> list[float]:
|
2022-06-16 14:59:36 +02:00
|
|
|
"""
|
|
|
|
Parse sections parameter
|
|
|
|
|
|
|
|
:param s: string to parse
|
|
|
|
"""
|
|
|
|
|
2023-01-23 20:08:04 +01:00
|
|
|
result: list[float] = []
|
2022-06-16 14:59:36 +02:00
|
|
|
|
2023-01-23 20:08:04 +01:00
|
|
|
parse = re.split(r"\s|:", s)
|
2022-06-16 14:59:36 +02:00
|
|
|
|
|
|
|
try:
|
2023-01-23 20:08:04 +01:00
|
|
|
for part in parse:
|
2022-06-16 14:59:36 +02:00
|
|
|
m = re.match(r"^(\d+(\.\d+)?)/(\d+)$", part)
|
|
|
|
if m:
|
|
|
|
n = int(m.group(3))
|
|
|
|
result.extend([float(m.group(1)) / n] * n)
|
|
|
|
continue
|
|
|
|
m = re.match(r"^(\d+(\.\d+)?)\*(\d+)$", part)
|
|
|
|
if m:
|
|
|
|
n = int(m.group(3))
|
|
|
|
result.extend([float(m.group(1))] * n)
|
|
|
|
continue
|
|
|
|
result.append(float(part))
|
|
|
|
except ValueError:
|
|
|
|
raise argparse.ArgumentTypeError("Don't understand sections string")
|
|
|
|
|
|
|
|
if not result:
|
|
|
|
result.append(0.0)
|
|
|
|
|
|
|
|
return result
|
|
|
|
|
2023-01-23 20:08:03 +01:00
|
|
|
|
2023-01-23 20:08:04 +01:00
|
|
|
def getDescriptions() -> dict:
|
2016-08-17 15:07:41 +02:00
|
|
|
d = {edge.char: edge.description for edge in globals().values()
|
2016-06-30 11:12:51 +02:00
|
|
|
if inspect.isclass(edge) and issubclass(edge, BaseEdge)
|
|
|
|
and edge.char}
|
2018-12-27 17:12:13 +01:00
|
|
|
d['j'] = d['i'] + " (other end)"
|
|
|
|
d['J'] = d['I'] + " (other end)"
|
|
|
|
d['k'] = d['i'] + " (both ends)"
|
|
|
|
d['K'] = d['I'] + " (both ends)"
|
|
|
|
d['O'] = d['o'] + ' (other end)'
|
|
|
|
d['P'] = d['p'] + ' (other end)'
|
|
|
|
d['U'] = d['u'] + ' top side'
|
2023-01-13 15:32:33 +01:00
|
|
|
d['v'] = d['u'] + ' for 90° lid'
|
|
|
|
d['V'] = d['u'] + ' 90° lid'
|
2016-06-30 11:12:51 +02:00
|
|
|
return d
|
2016-03-25 14:02:52 +01:00
|
|
|
|
2016-08-17 15:07:41 +02:00
|
|
|
|
2023-01-23 20:08:04 +01:00
|
|
|
class BoltPolicy(ABC):
|
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-08-17 15:07:41 +02:00
|
|
|
|
2023-01-23 20:08:04 +01:00
|
|
|
def drawbolt(self, pos) -> bool:
|
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
|
|
|
|
|
2023-01-23 20:08:04 +01:00
|
|
|
def numFingers(self, numFingers: int) -> int:
|
2016-03-28 16:55:41 +02:00
|
|
|
"""Return next smaller, possible number of fingers
|
|
|
|
|
2023-01-23 20:08:04 +01:00
|
|
|
:param numFingers: number of fingers to aim for
|
2016-03-28 16:55:41 +02:00
|
|
|
"""
|
2023-01-23 20:08:04 +01:00
|
|
|
return numFingers
|
2016-03-25 14:02:52 +01:00
|
|
|
|
2023-01-23 20:08:04 +01:00
|
|
|
def _even(self, numFingers: int) -> int:
|
2016-03-28 16:55:41 +02:00
|
|
|
"""
|
|
|
|
Return same or next smaller even number
|
|
|
|
|
|
|
|
:param numFingers:
|
|
|
|
"""
|
2016-08-17 15:07:41 +02:00
|
|
|
return (numFingers // 2) * 2
|
|
|
|
|
2023-01-23 20:08:04 +01:00
|
|
|
def _odd(self, numFingers: int) -> int:
|
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
|
2023-01-23 20:08:05 +01:00
|
|
|
return numFingers - 1
|
2016-03-25 14:02:52 +01:00
|
|
|
|
2016-08-17 15:07:41 +02:00
|
|
|
|
2016-03-25 14:02:52 +01:00
|
|
|
class Bolts(BoltPolicy):
|
|
|
|
"""Distribute a fixed number of bolts evenly"""
|
2016-08-17 15:07:41 +02:00
|
|
|
|
2023-01-23 20:08:04 +01:00
|
|
|
def __init__(self, bolts: int = 1) -> None:
|
2016-03-25 14:02:52 +01:00
|
|
|
self.bolts = bolts
|
2016-03-28 16:55:41 +02:00
|
|
|
|
2023-01-23 20:08:04 +01:00
|
|
|
def numFingers(self, numFingers: int) -> int:
|
2016-03-25 14:02:52 +01:00
|
|
|
if self.bolts % 2:
|
|
|
|
self.fingers = self._even(numFingers)
|
|
|
|
else:
|
|
|
|
self.fingers = numFingers
|
2016-08-17 15:07:41 +02:00
|
|
|
|
2016-03-25 14:02:52 +01:00
|
|
|
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-08-17 15:07:41 +02:00
|
|
|
if pos > self.fingers // 2:
|
2016-03-25 14:02:52 +01:00
|
|
|
pos = self.fingers - pos
|
2016-08-17 15:07:41 +02:00
|
|
|
|
|
|
|
if pos == 0:
|
2016-03-25 14:02:52 +01:00
|
|
|
return False
|
2016-08-17 15:07:41 +02:00
|
|
|
|
|
|
|
if pos == self.fingers // 2 and not (self.bolts % 2):
|
2016-03-25 14:02:52 +01:00
|
|
|
return False
|
2016-08-17 15:07:41 +02:00
|
|
|
|
|
|
|
return (math.floor((float(pos) * (self.bolts + 1) / self.fingers) - 0.01) !=
|
2023-01-23 20:08:03 +01:00
|
|
|
math.floor((float(pos + 1) * (self.bolts + 1) / self.fingers) - 0.01))
|
2016-08-17 15:07:41 +02:00
|
|
|
|
2016-03-25 14:02:52 +01:00
|
|
|
|
|
|
|
#############################################################################
|
|
|
|
### Settings
|
|
|
|
#############################################################################
|
|
|
|
|
2023-01-13 15:32:33 +01:00
|
|
|
class Settings:
|
2016-03-28 16:55:41 +02:00
|
|
|
"""Generic Settings class
|
|
|
|
|
2022-03-28 22:01:28 +02:00
|
|
|
Used by different other classes to store measurements 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
|
2023-01-02 00:32:42 +01:00
|
|
|
the supported keys and default values. The values are available via
|
2016-04-04 01:28:02 +02:00
|
|
|
attribute access.
|
2016-03-28 16:55:41 +02:00
|
|
|
"""
|
2023-01-13 13:46:16 +01:00
|
|
|
absolute_params: dict[str, Any] = {} # TODO find better typing.
|
|
|
|
relative_params: dict[str, Any] = {} # TODO find better typing.
|
2016-03-25 14:02:52 +01:00
|
|
|
|
2016-10-31 18:12:39 +01:00
|
|
|
@classmethod
|
2016-11-01 11:45:59 +01:00
|
|
|
def parserArguments(cls, parser, prefix=None, **defaults):
|
2016-10-31 18:12:39 +01:00
|
|
|
prefix = prefix or cls.__name__[:-len("Settings")]
|
|
|
|
|
2023-01-23 20:08:03 +01:00
|
|
|
lines = cls.__doc__.split("\n")
|
2016-10-31 18:12:39 +01:00
|
|
|
|
|
|
|
# Parse doc string
|
|
|
|
descriptions = {}
|
|
|
|
r = re.compile(r"^ +\* +(\S+) +: .* : +(.*)")
|
|
|
|
for l in lines:
|
|
|
|
m = r.search(l)
|
|
|
|
if m:
|
|
|
|
descriptions[m.group(1)] = m.group(2)
|
|
|
|
|
|
|
|
group = parser.add_argument_group(lines[0] or lines[1])
|
2016-10-31 22:06:52 +01:00
|
|
|
group.prefix = prefix
|
|
|
|
for name, default in (sorted(cls.absolute_params.items()) +
|
|
|
|
sorted(cls.relative_params.items())):
|
2016-11-01 20:12:47 +01:00
|
|
|
# Handle choices
|
|
|
|
choices = None
|
|
|
|
if isinstance(default, tuple):
|
|
|
|
choices = default
|
|
|
|
t = type(default[0])
|
|
|
|
for val in default:
|
|
|
|
if (type(val) is not t or
|
2023-01-23 20:08:03 +01:00
|
|
|
type(val) not in (bool, int, float, str)):
|
2016-11-01 20:12:47 +01:00
|
|
|
raise ValueError("Type not supported: %r", val)
|
|
|
|
default = default[0]
|
|
|
|
|
|
|
|
# Overwrite default
|
2016-11-01 11:45:59 +01:00
|
|
|
if name in defaults:
|
2022-06-27 08:12:02 +02:00
|
|
|
default = type(default)(defaults[name])
|
2016-11-01 20:12:47 +01:00
|
|
|
|
|
|
|
if type(default) not in (bool, int, float, str):
|
|
|
|
raise ValueError("Type not supported: %r", default)
|
2017-02-20 20:24:47 +01:00
|
|
|
if type(default) is bool:
|
|
|
|
from boxes import BoolArg
|
|
|
|
t = BoolArg()
|
|
|
|
else:
|
|
|
|
t = type(default)
|
2016-11-01 20:12:47 +01:00
|
|
|
|
2023-01-23 20:08:04 +01:00
|
|
|
group.add_argument(f"--{prefix}_{name}",
|
2017-02-20 20:24:47 +01:00
|
|
|
type=t,
|
2016-10-31 18:12:39 +01:00
|
|
|
action="store", default=default,
|
2016-11-01 20:12:47 +01:00
|
|
|
choices=choices,
|
2016-10-31 18:12:39 +01:00
|
|
|
help=descriptions.get(name))
|
|
|
|
|
2023-01-23 20:08:04 +01:00
|
|
|
def __init__(self, thickness, relative: bool = True, **kw) -> None:
|
2016-11-01 20:12:47 +01:00
|
|
|
self.values = {}
|
|
|
|
for name, value in self.absolute_params.items():
|
|
|
|
if isinstance(value, tuple):
|
|
|
|
value = value[0]
|
|
|
|
if type(value) not in (bool, int, float, str):
|
|
|
|
raise ValueError("Type not supported: %r", value)
|
|
|
|
self.values[name] = value
|
|
|
|
|
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)
|
|
|
|
|
2023-01-23 20:08:04 +01:00
|
|
|
def edgeObjects(self, boxes, chars: str = "", add: bool = True):
|
2016-11-23 21:11:15 +01:00
|
|
|
"""
|
|
|
|
Generate Edge objects using this kind of settings
|
|
|
|
|
|
|
|
:param boxes: Boxes object
|
|
|
|
:param chars: sequence of chars to be used by Edge objects
|
|
|
|
:param add: add the resulting Edge objects to the Boxes object's edges
|
|
|
|
"""
|
2023-01-23 20:08:04 +01:00
|
|
|
edges: list[Any] = []
|
2016-11-23 21:11:15 +01:00
|
|
|
return self._edgeObjects(edges, boxes, chars, add)
|
|
|
|
|
2023-01-23 20:08:04 +01:00
|
|
|
def _edgeObjects(self, edges, boxes, chars: str, add: bool):
|
2016-11-23 21:11:15 +01:00
|
|
|
for i, edge in enumerate(edges):
|
|
|
|
try:
|
|
|
|
char = chars[i]
|
|
|
|
edge.char = char
|
|
|
|
except IndexError:
|
|
|
|
pass
|
|
|
|
except TypeError:
|
|
|
|
pass
|
|
|
|
if add:
|
|
|
|
boxes.addParts(edges)
|
|
|
|
return edges
|
|
|
|
|
2023-01-23 20:08:04 +01:00
|
|
|
def setValues(self, thickness, relative: bool = True, **kw):
|
2016-03-28 16:55:41 +02:00
|
|
|
"""
|
|
|
|
Set values
|
|
|
|
|
|
|
|
:param thickness: thickness of the material used
|
2023-01-02 00:32:42 +01:00
|
|
|
:param relative: Do scale by thickness (Default value = True)
|
|
|
|
: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:
|
2023-01-23 20:08:04 +01:00
|
|
|
raise ValueError(f"Unknown parameter for {self.__class__.__name__}: {name}")
|
2019-11-25 16:56:55 +01:00
|
|
|
self.checkValues()
|
|
|
|
|
2023-01-23 20:08:04 +01:00
|
|
|
def checkValues(self) -> None:
|
2019-11-25 16:56:55 +01:00
|
|
|
"""
|
2023-01-23 20:08:05 +01:00
|
|
|
Check if all values are in the right range. Raise ValueError if needed.
|
2019-11-25 16:56:55 +01:00
|
|
|
"""
|
2023-01-23 20:08:05 +01:00
|
|
|
pass
|
2016-03-25 14:02:52 +01:00
|
|
|
|
|
|
|
def __getattr__(self, name):
|
2018-04-25 21:52:51 +02:00
|
|
|
if "values" in self.__dict__ and name in self.values:
|
|
|
|
return self.values[name]
|
|
|
|
raise AttributeError
|
2016-08-17 15:07:41 +02:00
|
|
|
|
2023-01-23 20:08:03 +01:00
|
|
|
|
2016-03-25 14:02:52 +01:00
|
|
|
#############################################################################
|
|
|
|
### Edges
|
|
|
|
#############################################################################
|
|
|
|
|
|
|
|
|
2023-01-23 20:08:04 +01:00
|
|
|
class BaseEdge(ABC):
|
2016-04-17 16:30:11 +02:00
|
|
|
"""Abstract base class for all Edges"""
|
2023-01-13 13:46:16 +01:00
|
|
|
char: str | None = None
|
2023-01-23 20:08:04 +01:00
|
|
|
description: str = "Abstract Edge Class"
|
2016-03-25 14:02:52 +01:00
|
|
|
|
2023-01-08 19:41:02 +01:00
|
|
|
def __init__(self, boxes, settings) -> None:
|
2016-03-25 14:02:52 +01:00
|
|
|
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)
|
|
|
|
|
2023-01-23 20:08:04 +01:00
|
|
|
@abstractmethod
|
2016-03-25 14:02:52 +01:00
|
|
|
def __call__(self, length, **kw):
|
2018-01-18 14:56:33 +01:00
|
|
|
pass
|
2016-03-25 14:02:52 +01:00
|
|
|
|
2023-01-23 20:08:04 +01:00
|
|
|
def startwidth(self) -> float:
|
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
|
|
|
|
|
2023-01-23 20:08:04 +01:00
|
|
|
def endwidth(self) -> float:
|
2016-04-18 22:05:11 +02:00
|
|
|
return self.startwidth()
|
2016-08-17 15:07:41 +02:00
|
|
|
|
2023-01-23 20:08:04 +01:00
|
|
|
def margin(self) -> float:
|
2016-03-28 16:55:41 +02:00
|
|
|
"""Space needed right of the starting point"""
|
2016-07-27 22:19:32 +02:00
|
|
|
return 0.0
|
2016-03-25 14:02:52 +01:00
|
|
|
|
2023-01-23 20:08:04 +01:00
|
|
|
def spacing(self) -> float:
|
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
|
|
|
|
2023-01-23 20:08:04 +01:00
|
|
|
def startAngle(self) -> float:
|
2016-04-17 18:04:23 +02:00
|
|
|
"""Not yet supported"""
|
|
|
|
return 0.0
|
|
|
|
|
2023-01-23 20:08:04 +01:00
|
|
|
def endAngle(self) -> float:
|
2016-04-17 18:04:23 +02:00
|
|
|
"""Not yet supported"""
|
|
|
|
return 0.0
|
|
|
|
|
2016-08-17 15:07:41 +02:00
|
|
|
|
2016-04-17 17:43:16 +02:00
|
|
|
class Edge(BaseEdge):
|
2016-04-17 16:30:11 +02:00
|
|
|
"""Straight edge"""
|
|
|
|
char = 'e'
|
2016-08-17 15:07:41 +02:00
|
|
|
description = "Straight Edge"
|
2018-01-18 15:00:25 +01:00
|
|
|
positive = False
|
2016-08-17 15:07:41 +02:00
|
|
|
|
2018-01-18 14:56:33 +01:00
|
|
|
def __call__(self, length, bedBolts=None, bedBoltSettings=None, **kw):
|
|
|
|
"""Draw edge of length mm"""
|
2018-01-18 15:00:25 +01:00
|
|
|
if bedBolts:
|
2023-01-02 00:32:42 +01:00
|
|
|
# distribute the bolts equidistantly
|
2018-01-18 15:00:25 +01:00
|
|
|
interval_length = length / bedBolts.bolts
|
|
|
|
if self.positive:
|
|
|
|
d = (bedBoltSettings or self.bedBoltSettings)[0]
|
|
|
|
for i in range(bedBolts.bolts):
|
|
|
|
self.hole(0.5 * interval_length,
|
|
|
|
0.5 * self.thickness, 0.5 * d)
|
2018-01-19 23:48:27 +01:00
|
|
|
self.edge(interval_length, tabs=
|
2023-01-23 20:08:03 +01:00
|
|
|
(i == 0 or i == bedBolts.bolts - 1))
|
2018-01-18 15:00:25 +01:00
|
|
|
else:
|
|
|
|
for i in range(bedBolts.bolts):
|
2018-01-19 23:48:27 +01:00
|
|
|
self.bedBoltHole(interval_length, bedBoltSettings, tabs=
|
2023-01-23 20:08:03 +01:00
|
|
|
(i == 0 or i == bedBolts.bolts - 1))
|
2018-01-18 15:00:25 +01:00
|
|
|
else:
|
|
|
|
self.edge(length, tabs=2)
|
2018-01-18 14:56:33 +01:00
|
|
|
|
2016-03-25 14:02:52 +01:00
|
|
|
|
2018-01-18 14:56:33 +01:00
|
|
|
class OutSetEdge(Edge):
|
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)"
|
2018-01-18 15:00:25 +01:00
|
|
|
positive = True
|
2016-03-25 14:02:52 +01:00
|
|
|
|
2023-01-23 20:08:04 +01:00
|
|
|
def startwidth(self) -> float:
|
2016-03-25 14:02:52 +01:00
|
|
|
return self.boxes.thickness
|
|
|
|
|
2023-01-23 20:08:03 +01:00
|
|
|
|
2022-05-25 23:31:55 +02:00
|
|
|
#############################################################################
|
|
|
|
#### MountingEdge
|
|
|
|
#############################################################################
|
|
|
|
|
|
|
|
class MountingSettings(Settings):
|
2022-07-28 22:26:55 +02:00
|
|
|
"""Settings for Mounting Edge
|
2022-05-25 23:31:55 +02:00
|
|
|
Values:
|
|
|
|
* absolute_params
|
|
|
|
|
|
|
|
* style : "straight edge, within" : edge style
|
2022-07-28 22:26:55 +02:00
|
|
|
* side : "back" : side of box (not all valid configurations make sense...)
|
2022-05-25 23:31:55 +02:00
|
|
|
* num : 2 : number of mounting holes (integer)
|
|
|
|
* margin : 0.125 : minimum space left and right without holes (fraction of the edge length)
|
|
|
|
* d_shaft : 3.0 : shaft diameter of mounting screw (in mm)
|
|
|
|
* d_head : 6.5 : head diameter of mounting screw (in mm)
|
|
|
|
"""
|
|
|
|
|
2023-01-23 20:08:03 +01:00
|
|
|
PARAM_IN = "straight edge, within"
|
2022-05-25 23:31:55 +02:00
|
|
|
PARAM_EXT = "straight edge, extended"
|
|
|
|
PARAM_TAB = "mounting tab"
|
2023-01-23 20:08:03 +01:00
|
|
|
|
2022-05-25 23:31:55 +02:00
|
|
|
PARAM_LEFT = "left"
|
|
|
|
PARAM_BACK = "back"
|
|
|
|
PARAM_RIGHT = "right"
|
|
|
|
PARAM_FRONT = "front"
|
|
|
|
|
|
|
|
absolute_params = {
|
|
|
|
"style": (PARAM_IN, PARAM_EXT, PARAM_TAB),
|
2022-07-28 22:26:55 +02:00
|
|
|
"side": (PARAM_BACK, PARAM_LEFT, PARAM_RIGHT, PARAM_FRONT),
|
2022-05-25 23:31:55 +02:00
|
|
|
"num": 2,
|
|
|
|
"margin": 0.125,
|
2023-01-23 20:08:03 +01:00
|
|
|
"d_shaft": 3.0,
|
|
|
|
"d_head": 6.5
|
2022-05-25 23:31:55 +02:00
|
|
|
}
|
|
|
|
|
2023-01-23 20:08:04 +01:00
|
|
|
def edgeObjects(self, boxes, chars: str = "G", add: bool = True):
|
2022-05-25 23:31:55 +02:00
|
|
|
edges = [MountingEdge(boxes, self)]
|
|
|
|
return self._edgeObjects(edges, boxes, chars, add)
|
|
|
|
|
|
|
|
|
|
|
|
class MountingEdge(BaseEdge):
|
2023-01-23 20:08:03 +01:00
|
|
|
description = """Edge with pear shaped mounting holes""" # for slide-on mounting using flat-head screws"""
|
2022-05-25 23:31:55 +02:00
|
|
|
char = 'G'
|
|
|
|
|
2023-01-23 20:08:04 +01:00
|
|
|
def margin(self) -> float:
|
2022-05-25 23:31:55 +02:00
|
|
|
if self.settings.style == MountingSettings.PARAM_TAB:
|
|
|
|
return 2.75 * self.boxes.thickness + self.settings.d_head
|
2023-01-23 20:08:05 +01:00
|
|
|
return 0.0
|
2023-01-23 20:08:03 +01:00
|
|
|
|
2023-01-23 20:08:04 +01:00
|
|
|
def startwidth(self) -> float:
|
2022-05-25 23:31:55 +02:00
|
|
|
if self.settings.style == MountingSettings.PARAM_EXT:
|
|
|
|
return 2.5 * self.boxes.thickness + self.settings.d_head
|
2023-01-23 20:08:05 +01:00
|
|
|
return 0.0
|
2023-01-23 20:08:03 +01:00
|
|
|
|
2022-05-25 23:31:55 +02:00
|
|
|
def __call__(self, length, **kw):
|
|
|
|
if length == 0.0:
|
|
|
|
return
|
|
|
|
|
|
|
|
def check_bounds(val, mn, mx, name):
|
|
|
|
if not mn <= val <= mx:
|
|
|
|
raise ValueError(f"MountingEdge: {name} needs to be in [{mn}, {mx}] but is {val}")
|
|
|
|
|
|
|
|
style = self.settings.style
|
|
|
|
margin = self.settings.margin
|
|
|
|
num = self.settings.num
|
|
|
|
ds = self.settings.d_shaft
|
|
|
|
dh = self.settings.d_head
|
|
|
|
if dh > 0:
|
|
|
|
width = 3 * self.thickness + dh
|
|
|
|
else:
|
|
|
|
width = ds
|
|
|
|
|
|
|
|
if num != int(num):
|
|
|
|
raise ValueError(f"MountingEdge: num needs to be an integer number")
|
2023-01-23 20:08:03 +01:00
|
|
|
|
2022-05-25 23:31:55 +02:00
|
|
|
check_bounds(margin, 0, 0.5, "margin")
|
|
|
|
if not dh == 0:
|
|
|
|
if not dh > ds:
|
|
|
|
raise ValueError(f"MountingEdge: d_shaft needs to be in 0 or > {ds}, but is {dh}")
|
|
|
|
|
|
|
|
# Check how many holes fit
|
|
|
|
count = max(1, int(num))
|
|
|
|
if count > 1:
|
|
|
|
margin_ = length * margin
|
2023-01-23 20:08:03 +01:00
|
|
|
gap = (length - 2 * margin_ - width * count) / (count - 1)
|
2022-05-25 23:31:55 +02:00
|
|
|
if gap < width:
|
2023-01-23 20:08:03 +01:00
|
|
|
count = int(((length - 2 * margin + width) / (2 * width)) - 0.5)
|
2022-05-25 23:31:55 +02:00
|
|
|
if count < 1:
|
|
|
|
self.edge(length)
|
|
|
|
return
|
|
|
|
if count < 2:
|
|
|
|
margin_ = (length - width) / 2
|
|
|
|
gap = 0
|
|
|
|
else:
|
2023-01-23 20:08:03 +01:00
|
|
|
gap = (length - 2 * margin_ - width * count) / (count - 1)
|
2022-05-25 23:31:55 +02:00
|
|
|
else:
|
|
|
|
margin_ = (length - width) / 2
|
|
|
|
gap = 0
|
2023-01-23 20:08:03 +01:00
|
|
|
|
2022-05-25 23:31:55 +02:00
|
|
|
if style == MountingSettings.PARAM_TAB:
|
2023-01-23 20:08:03 +01:00
|
|
|
|
2022-05-25 23:31:55 +02:00
|
|
|
# The edge until the first groove
|
|
|
|
self.edge(margin_, tabs=1)
|
2023-01-23 20:08:03 +01:00
|
|
|
|
2022-05-25 23:31:55 +02:00
|
|
|
for i in range(count):
|
|
|
|
if i > 0:
|
|
|
|
self.edge(gap)
|
2023-01-23 20:08:03 +01:00
|
|
|
self.corner(-90, self.thickness / 2)
|
|
|
|
self.edge(dh + 1.5 * ds - self.thickness / 4 - dh / 2)
|
|
|
|
self.corner(90, self.thickness + dh / 2)
|
2022-05-25 23:31:55 +02:00
|
|
|
self.corner(-90)
|
|
|
|
self.corner(90)
|
2023-01-23 20:08:03 +01:00
|
|
|
self.mountingHole(0, self.thickness * 1.25 + ds / 2, ds, dh, -90)
|
|
|
|
self.corner(90, self.thickness + dh / 2)
|
|
|
|
self.edge(dh + 1.5 * ds - self.thickness / 4 - dh / 2)
|
|
|
|
self.corner(-90, self.thickness / 2)
|
|
|
|
|
2022-05-25 23:31:55 +02:00
|
|
|
# The edge until the end
|
|
|
|
self.edge(margin_, tabs=1)
|
|
|
|
else:
|
|
|
|
x = margin_
|
|
|
|
for i in range(count):
|
2023-01-23 20:08:03 +01:00
|
|
|
x += width / 2
|
|
|
|
self.mountingHole(x, ds / 2 + self.thickness * 1.5, ds, dh, -90)
|
|
|
|
x += width / 2
|
2022-05-25 23:31:55 +02:00
|
|
|
x += gap
|
|
|
|
self.edge(length)
|
|
|
|
|
2016-08-17 15:07:41 +02:00
|
|
|
|
2020-09-28 23:45:54 +02:00
|
|
|
#############################################################################
|
|
|
|
#### GroovedEdge
|
|
|
|
#############################################################################
|
|
|
|
|
|
|
|
class GroovedSettings(Settings):
|
|
|
|
"""Settings for Grooved Edge
|
|
|
|
Values:
|
|
|
|
|
|
|
|
* absolute_params
|
|
|
|
|
|
|
|
* style : "arc" : the style of grooves
|
|
|
|
* tri_angle : 30 : the angle of triangular cuts
|
|
|
|
* arc_angle : 120 : the angle of arc cuts
|
|
|
|
* width : 0.2 : the width of each groove (fraction of the edge length)
|
|
|
|
* gap : 0.1 : the gap between grooves (fraction of the edge length)
|
|
|
|
* margin : 0.3 : minimum space left and right without grooves (fraction of the edge length)
|
|
|
|
* inverse : False : invert the groove directions
|
|
|
|
* interleave : False : alternate the direction of grooves
|
|
|
|
"""
|
|
|
|
|
|
|
|
PARAM_ARC = "arc"
|
|
|
|
PARAM_FLAT = "flat"
|
|
|
|
PARAM_SOFTARC = "softarc"
|
|
|
|
PARAM_TRIANGLE = "triangle"
|
|
|
|
|
|
|
|
absolute_params = {
|
|
|
|
"style": (PARAM_ARC, PARAM_FLAT, PARAM_TRIANGLE, PARAM_SOFTARC),
|
|
|
|
"tri_angle": 30,
|
|
|
|
"arc_angle": 120,
|
|
|
|
"width": 0.2,
|
|
|
|
"gap": 0.1,
|
|
|
|
"margin": 0.3,
|
|
|
|
"inverse": False,
|
|
|
|
"interleave": False,
|
|
|
|
}
|
|
|
|
|
2023-01-23 20:08:04 +01:00
|
|
|
def edgeObjects(self, boxes, chars: str = "zZ", add: bool = True):
|
2020-09-28 23:45:54 +02:00
|
|
|
edges = [GroovedEdge(boxes, self),
|
|
|
|
GroovedEdgeCounterPart(boxes, self)]
|
|
|
|
return self._edgeObjects(edges, boxes, chars, add)
|
|
|
|
|
|
|
|
|
|
|
|
class GroovedEdgeBase(BaseEdge):
|
2023-01-23 20:08:04 +01:00
|
|
|
def is_inverse(self) -> bool:
|
2020-09-28 23:45:54 +02:00
|
|
|
return self.settings.inverse != self.inverse
|
|
|
|
|
2023-01-23 20:08:04 +01:00
|
|
|
def groove_arc(self, width, angle: float = 90.0, inv: float = -1.0) -> None:
|
2020-09-30 20:36:14 +02:00
|
|
|
side_length = width / math.sin(math.radians(angle)) / 2
|
|
|
|
self.corner(inv * -angle)
|
|
|
|
self.corner(inv * angle, side_length)
|
|
|
|
self.corner(inv * angle, side_length)
|
|
|
|
self.corner(inv * -angle)
|
|
|
|
|
2023-01-23 20:08:04 +01:00
|
|
|
def groove_soft_arc(self, width, angle: float = 60.0, inv: float = -1.0) -> None:
|
2020-09-30 20:36:14 +02:00
|
|
|
side_length = width / math.sin(math.radians(angle)) / 4
|
|
|
|
self.corner(inv * -angle, side_length)
|
|
|
|
self.corner(inv * angle, side_length)
|
|
|
|
self.corner(inv * angle, side_length)
|
|
|
|
self.corner(inv * -angle, side_length)
|
|
|
|
|
2023-01-23 20:08:04 +01:00
|
|
|
def groove_triangle(self, width, angle: float = 45.0, inv: float = -1.0) -> None:
|
2020-09-30 20:36:14 +02:00
|
|
|
side_length = width / math.cos(math.radians(angle)) / 2
|
|
|
|
self.corner(inv * -angle)
|
|
|
|
self.edge(side_length)
|
|
|
|
self.corner(inv * 2 * angle)
|
|
|
|
self.edge(side_length)
|
|
|
|
self.corner(inv * -angle)
|
|
|
|
|
2020-09-28 23:45:54 +02:00
|
|
|
def __call__(self, length, **kw):
|
|
|
|
if length == 0.0:
|
|
|
|
return
|
|
|
|
|
|
|
|
def check_bounds(val, mn, mx, name):
|
|
|
|
if not mn <= val <= mx:
|
|
|
|
raise ValueError(f"{name} needs to be in [{mn}, {mx}] but is {val}")
|
|
|
|
|
|
|
|
style = self.settings.style
|
|
|
|
width = self.settings.width
|
|
|
|
margin = self.settings.margin
|
|
|
|
gap = self.settings.gap
|
|
|
|
interleave = self.settings.interleave
|
|
|
|
|
|
|
|
check_bounds(width, 0, 1, "width")
|
|
|
|
check_bounds(margin, 0, 0.5, "margin")
|
|
|
|
check_bounds(gap, 0, 1, "gap")
|
|
|
|
|
|
|
|
# Check how many grooves fit
|
|
|
|
count = max(0, int((1 - 2 * margin + gap) / (width + gap)))
|
|
|
|
inside_width = max(0, count * (width + gap) - gap)
|
|
|
|
margin = (1 - inside_width) / 2
|
|
|
|
|
|
|
|
# Convert to actual length
|
|
|
|
margin = length * margin
|
|
|
|
gap = length * gap
|
|
|
|
width = length * width
|
|
|
|
|
|
|
|
# Determine the initial inversion
|
|
|
|
inv = 1 if self.is_inverse() else -1
|
|
|
|
if interleave and self.inverse and count % 2 == 0:
|
|
|
|
inv = -inv
|
|
|
|
|
|
|
|
# The edge until the first groove
|
|
|
|
self.edge(margin, tabs=1)
|
|
|
|
|
|
|
|
# Grooves
|
|
|
|
for i in range(count):
|
|
|
|
if i > 0:
|
|
|
|
self.edge(gap)
|
|
|
|
if interleave:
|
|
|
|
inv = -inv
|
|
|
|
if style == GroovedSettings.PARAM_FLAT:
|
|
|
|
self.edge(width)
|
|
|
|
elif style == GroovedSettings.PARAM_ARC:
|
|
|
|
angle = self.settings.arc_angle / 2
|
2020-09-30 20:36:14 +02:00
|
|
|
self.groove_arc(width, angle, inv)
|
2020-09-28 23:45:54 +02:00
|
|
|
elif style == GroovedSettings.PARAM_SOFTARC:
|
|
|
|
angle = self.settings.arc_angle / 2
|
2020-09-30 20:36:14 +02:00
|
|
|
self.groove_soft_arc(width, angle, inv)
|
2020-09-28 23:45:54 +02:00
|
|
|
elif style == GroovedSettings.PARAM_TRIANGLE:
|
|
|
|
angle = self.settings.tri_angle
|
2020-09-30 20:36:14 +02:00
|
|
|
self.groove_triangle(width, angle, inv)
|
2020-09-28 23:45:54 +02:00
|
|
|
else:
|
|
|
|
raise ValueError("Unknown GroovedEdge style: %s)" % style)
|
|
|
|
|
|
|
|
# The final edge
|
|
|
|
self.edge(margin, tabs=1)
|
|
|
|
|
|
|
|
|
|
|
|
class GroovedEdge(GroovedEdgeBase):
|
|
|
|
description = """Edge with grooves"""
|
|
|
|
char = 'z'
|
|
|
|
inverse = False
|
|
|
|
|
|
|
|
|
|
|
|
class GroovedEdgeCounterPart(GroovedEdgeBase):
|
|
|
|
description = """Edge with grooves (opposing side)"""
|
|
|
|
char = 'Z'
|
|
|
|
inverse = True
|
|
|
|
|
|
|
|
|
2016-06-30 14:25:29 +02:00
|
|
|
#############################################################################
|
|
|
|
#### Gripping Edge
|
|
|
|
#############################################################################
|
|
|
|
|
|
|
|
class GripSettings(Settings):
|
|
|
|
"""Settings for GrippingEdge
|
|
|
|
Values:
|
|
|
|
|
|
|
|
* absolute_params
|
|
|
|
|
2023-01-23 20:08:04 +01:00
|
|
|
* style : "wave" : "wave" or "bumps"
|
2016-06-30 14:25:29 +02:00
|
|
|
* outset : True : extend outward the straight edge
|
|
|
|
|
|
|
|
* relative (in multiples of thickness)
|
|
|
|
|
|
|
|
* depth : 0.3 : depth of the grooves
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
absolute_params = {
|
2016-11-01 20:41:25 +01:00
|
|
|
"style": ("wave", "bumps"),
|
2016-08-17 15:07:41 +02:00
|
|
|
"outset": True,
|
2016-06-30 14:25:29 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
relative_params = {
|
2016-08-17 15:07:41 +02:00
|
|
|
"depth": 0.3,
|
2016-06-30 14:25:29 +02:00
|
|
|
}
|
|
|
|
|
2023-01-23 20:08:04 +01:00
|
|
|
def edgeObjects(self, boxes, chars: str = "g", add: bool = True):
|
2016-11-23 21:11:15 +01:00
|
|
|
edges = [GrippingEdge(boxes, self)]
|
|
|
|
return self._edgeObjects(edges, boxes, chars, add)
|
2016-08-17 15:07:41 +02:00
|
|
|
|
2023-01-23 20:08:03 +01:00
|
|
|
|
2016-06-30 14:25:29 +02:00
|
|
|
class GrippingEdge(BaseEdge):
|
|
|
|
description = """Corrugated edge useful as an gipping area"""
|
|
|
|
char = 'g'
|
|
|
|
|
2023-01-23 20:08:04 +01:00
|
|
|
def wave(self, length) -> None:
|
2016-06-30 14:25:29 +02:00
|
|
|
depth = self.settings.depth
|
2016-08-17 15:07:41 +02:00
|
|
|
grooves = int(length // (depth * 2.0)) + 1
|
2016-06-30 14:25:29 +02:00
|
|
|
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)
|
|
|
|
|
2023-01-23 20:08:04 +01:00
|
|
|
def bumps(self, length) -> None:
|
2016-06-30 14:25:29 +02:00
|
|
|
depth = self.settings.depth
|
2016-08-17 15:07:41 +02:00
|
|
|
grooves = int(length // (depth * 2.0)) + 1
|
2016-06-30 14:25:29 +02:00
|
|
|
depth = length / grooves / 2.0
|
|
|
|
o = 1 if self.settings.outset else -1
|
2016-08-17 15:07:41 +02:00
|
|
|
|
2016-06-30 14:25:29 +02:00
|
|
|
if self.settings.outset:
|
|
|
|
self.corner(-90)
|
|
|
|
else:
|
|
|
|
self.corner(90)
|
|
|
|
self.edge(depth)
|
|
|
|
self.corner(-180)
|
2016-08-17 15:07:41 +02:00
|
|
|
|
2016-06-30 14:25:29 +02:00
|
|
|
for groove in range(grooves):
|
|
|
|
self.corner(180, depth)
|
|
|
|
self.corner(-180, 0)
|
2016-08-17 15:07:41 +02:00
|
|
|
|
2016-06-30 14:25:29 +02:00
|
|
|
if self.settings.outset:
|
|
|
|
self.corner(90)
|
|
|
|
else:
|
|
|
|
self.edge(depth)
|
|
|
|
self.corner(90)
|
|
|
|
|
2023-01-23 20:08:04 +01:00
|
|
|
def margin(self) -> float:
|
2016-06-30 14:25:29 +02:00
|
|
|
if self.settings.outset:
|
2016-07-27 22:19:32 +02:00
|
|
|
return self.settings.depth
|
2023-01-23 20:08:05 +01:00
|
|
|
return 0.0
|
2016-06-30 14:25:29 +02:00
|
|
|
|
|
|
|
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-08-17 15:07:41 +02:00
|
|
|
|
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
|
|
|
|
2023-01-08 19:41:02 +01:00
|
|
|
def __init__(self, boxes, types, lengths) -> None:
|
2023-01-13 15:32:33 +01:00
|
|
|
super().__init__(boxes, None)
|
2016-08-17 15:07:41 +02:00
|
|
|
|
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)
|
|
|
|
|
2023-01-23 20:08:04 +01:00
|
|
|
def startwidth(self) -> float:
|
2016-04-18 22:05:11 +02:00
|
|
|
return self.types[0].startwidth()
|
|
|
|
|
2023-01-23 20:08:04 +01:00
|
|
|
def endwidth(self) -> float:
|
2016-04-18 22:05:11 +02:00
|
|
|
return self.types[-1].endwidth()
|
2016-08-17 15:07:41 +02:00
|
|
|
|
2023-01-23 20:08:04 +01:00
|
|
|
def margin(self) -> float:
|
2023-01-13 15:32:32 +01:00
|
|
|
return max(e.margin() + e.startwidth() for e in self.types) - self.types[0].startwidth()
|
2016-03-25 14:02:52 +01:00
|
|
|
|
|
|
|
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-08-17 15:07:41 +02:00
|
|
|
|
2016-03-25 14:02:52 +01:00
|
|
|
for e, l in zip(self.types, self.lengths):
|
2020-02-03 16:59:19 +01:00
|
|
|
self.step(e.startwidth() - lastwidth)
|
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-08-17 15:07:41 +02:00
|
|
|
|
2016-04-09 14:24:32 +02:00
|
|
|
#############################################################################
|
|
|
|
#### Slots
|
|
|
|
#############################################################################
|
|
|
|
|
2016-04-17 16:30:11 +02:00
|
|
|
class Slot(BaseEdge):
|
2023-01-02 00:32:42 +01:00
|
|
|
"""Edge with a slot to slide another piece through """
|
2016-04-17 16:30:11 +02:00
|
|
|
|
2016-04-17 14:26:27 +02:00
|
|
|
description = "Slot"
|
|
|
|
|
2023-01-08 19:41:02 +01:00
|
|
|
def __init__(self, boxes, depth) -> None:
|
2023-01-13 15:32:33 +01:00
|
|
|
super().__init__(boxes, None)
|
2016-08-17 15:07:41 +02:00
|
|
|
|
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-08-17 15:07:41 +02:00
|
|
|
|
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
|
|
|
|
2023-01-23 20:08:04 +01:00
|
|
|
def __init__(self, boxes, sections, edge: str = "e", slots: int = 0) -> None:
|
2023-01-13 15:32:33 +01:00
|
|
|
super().__init__(boxes, Settings(boxes.thickness))
|
2016-08-17 15:07:41 +02:00
|
|
|
|
2016-03-25 14:02:52 +01:00
|
|
|
self.edge = self.edges.get(edge, edge)
|
|
|
|
self.sections = sections
|
|
|
|
self.slots = slots
|
|
|
|
|
2023-01-23 20:08:04 +01:00
|
|
|
def startwidth(self) -> float:
|
2016-04-18 22:05:11 +02:00
|
|
|
return self.edge.startwidth()
|
|
|
|
|
2023-01-23 20:08:04 +01:00
|
|
|
def endwidth(self) -> float:
|
2016-04-18 22:05:11 +02:00
|
|
|
return self.edge.endwidth()
|
2016-03-25 14:02:52 +01:00
|
|
|
|
2023-01-23 20:08:04 +01:00
|
|
|
def margin(self) -> float:
|
2016-03-25 14:02:52 +01:00
|
|
|
return self.edge.margin()
|
|
|
|
|
|
|
|
def __call__(self, length, **kw):
|
2016-08-17 15:07:41 +02:00
|
|
|
|
2016-03-25 14:02:52 +01:00
|
|
|
for l in self.sections[:-1]:
|
|
|
|
self.edge(l)
|
2016-08-17 15:07:41 +02:00
|
|
|
|
2016-03-25 14:02:52 +01:00
|
|
|
if self.slots:
|
2017-02-15 22:34:27 +01:00
|
|
|
Slot(self.boxes, self.slots)(self.settings.thickness)
|
2016-03-25 14:02:52 +01:00
|
|
|
else:
|
2022-11-12 09:17:56 +01:00
|
|
|
self.boxes.edge(self.settings.thickness)
|
2016-08-17 15:07:41 +02:00
|
|
|
|
2016-03-25 14:02:52 +01:00
|
|
|
self.edge(self.sections[-1])
|
|
|
|
|
2016-08-17 15:07:41 +02:00
|
|
|
|
2016-04-09 14:24:32 +02:00
|
|
|
#############################################################################
|
|
|
|
#### Finger Joints
|
|
|
|
#############################################################################
|
|
|
|
|
2016-03-25 14:02:52 +01:00
|
|
|
class FingerJointSettings(Settings):
|
2016-10-31 22:06:52 +01:00
|
|
|
"""Settings for Finger Joints
|
2016-04-04 01:28:02 +02:00
|
|
|
|
|
|
|
Values:
|
|
|
|
|
|
|
|
* absolute
|
2019-10-11 22:32:57 +02:00
|
|
|
* style : "rectangular" : style of the fingers
|
2022-09-25 14:00:28 +02:00
|
|
|
* surroundingspaces : 2.0 : space at the start and end in multiple of normal spaces
|
2017-02-05 16:30:37 +01:00
|
|
|
* angle: 90 : Angle of the walls meeting
|
2016-03-28 16:55:41 +02:00
|
|
|
|
2016-04-04 01:28:02 +02:00
|
|
|
* relative (in multiples of thickness)
|
|
|
|
|
2022-01-07 22:15:05 +01:00
|
|
|
* space : 2.0 : space between fingers (multiples of thickness)
|
|
|
|
* finger : 2.0 : width of the fingers (multiples of thickness)
|
|
|
|
* width : 1.0 : width of finger holes (multiples of thickness)
|
|
|
|
* edge_width : 1.0 : space below holes of FingerHoleEdge (multiples of thickness)
|
|
|
|
* play : 0.0 : extra space to allow finger move in and out (multiples of thickness)
|
2022-01-16 15:18:11 +01:00
|
|
|
* extra_length : 0.0 : extra material to grind away burn marks (multiples of thickness)
|
2023-01-01 23:58:22 +01:00
|
|
|
* bottom_lip : 0.0 : height of the bottom lips sticking out (multiples of thickness) FingerHoleEdge only!
|
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 = {
|
2023-01-23 20:08:03 +01:00
|
|
|
"style": ("rectangular", "springs", "barbs", "snap"),
|
2016-11-15 21:56:08 +01:00
|
|
|
"surroundingspaces": 2.0,
|
2023-01-23 20:08:03 +01:00
|
|
|
"angle": 90.0,
|
2016-08-17 15:07:41 +02:00
|
|
|
}
|
2016-03-25 14:02:52 +01:00
|
|
|
|
|
|
|
relative_params = {
|
2017-02-05 16:30:37 +01:00
|
|
|
"space": 2.0,
|
2017-01-30 09:55:44 +01:00
|
|
|
"finger": 2.0,
|
2017-02-05 16:30:37 +01:00
|
|
|
"width": 1.0,
|
2016-08-17 15:07:41 +02:00
|
|
|
"edge_width": 1.0,
|
2023-01-23 20:08:03 +01:00
|
|
|
"play": 0.0,
|
|
|
|
"extra_length": 0.0,
|
|
|
|
"bottom_lip": 0.0,
|
2016-08-17 15:07:41 +02:00
|
|
|
}
|
|
|
|
|
2023-01-23 20:08:04 +01:00
|
|
|
def checkValues(self) -> None:
|
2019-11-25 16:56:55 +01:00
|
|
|
if abs(self.space + self.finger) < 0.1:
|
|
|
|
raise ValueError("FingerJointSettings: space + finger must not be close to zero")
|
|
|
|
|
2023-01-23 20:08:04 +01:00
|
|
|
def edgeObjects(self, boxes, chars: str = "fFh", add: bool = True):
|
2016-11-23 21:11:15 +01:00
|
|
|
edges = [FingerJointEdge(boxes, self),
|
|
|
|
FingerJointEdgeCounterPart(boxes, self),
|
|
|
|
FingerHoleEdge(boxes, self),
|
2023-01-23 20:08:03 +01:00
|
|
|
]
|
2016-11-23 21:11:15 +01:00
|
|
|
return self._edgeObjects(edges, boxes, chars, add)
|
|
|
|
|
2023-01-23 20:08:03 +01:00
|
|
|
|
2023-01-23 20:08:04 +01:00
|
|
|
class FingerJointBase(ABC):
|
|
|
|
"""Abstract base class for finger joint."""
|
2016-03-25 14:02:52 +01:00
|
|
|
|
2023-01-23 20:08:04 +01:00
|
|
|
def calcFingers(self, length: float, bedBolts) -> tuple[int, float]:
|
|
|
|
space, finger = self.settings.space, self.settings.finger # type: ignore
|
|
|
|
fingers = int((length - (self.settings.surroundingspaces - 1) * space) // (space + finger)) # type: ignore
|
2022-09-25 14:00:28 +02:00
|
|
|
# shrink surrounding space up to half a thickness each side
|
2023-01-23 20:08:04 +01:00
|
|
|
if fingers == 0 and length > finger + 1.0 * self.settings.thickness: # type: ignore
|
2022-09-25 14:00:28 +02:00
|
|
|
fingers = 1
|
2016-10-30 19:45:38 +01:00
|
|
|
if not finger:
|
|
|
|
fingers = 0
|
|
|
|
if bedBolts:
|
|
|
|
fingers = bedBolts.numFingers(fingers)
|
|
|
|
leftover = length - fingers * (space + finger) + space
|
|
|
|
|
|
|
|
if fingers <= 0:
|
|
|
|
fingers = 0
|
|
|
|
leftover = length
|
|
|
|
|
|
|
|
return fingers, leftover
|
|
|
|
|
2023-01-23 20:08:04 +01:00
|
|
|
def fingerLength(self, angle: float) -> tuple[float, float]:
|
2022-01-16 15:18:11 +01:00
|
|
|
# sharp corners
|
2023-01-23 20:08:03 +01:00
|
|
|
if angle >= 90 or angle <= -90:
|
2023-01-23 20:08:04 +01:00
|
|
|
return self.settings.thickness + self.settings.extra_length, 0.0 # type: ignore
|
2016-11-15 21:56:08 +01:00
|
|
|
|
2022-01-16 15:18:11 +01:00
|
|
|
# inner blunt corners
|
2019-03-08 17:52:52 +01:00
|
|
|
if angle < 0:
|
2023-01-23 20:08:04 +01:00
|
|
|
return (math.sin(math.radians(-angle)) * self.settings.thickness + self.settings.extra_length), 0 # type: ignore
|
2019-03-08 17:52:52 +01:00
|
|
|
|
2022-01-16 15:18:11 +01:00
|
|
|
# 0 to 90 (blunt corners)
|
2023-01-23 20:08:03 +01:00
|
|
|
a = 90 - (180 - angle) / 2.0
|
2023-01-23 20:08:04 +01:00
|
|
|
fingerlength = self.settings.thickness * math.tan(math.radians(a)) # type: ignore
|
2023-01-23 20:08:03 +01:00
|
|
|
b = 90 - 2 * a
|
2016-12-14 22:07:36 +01:00
|
|
|
spacerecess = -math.sin(math.radians(b)) * fingerlength
|
2023-01-23 20:08:04 +01:00
|
|
|
return fingerlength + self.settings.extra_length, spacerecess # type: ignore
|
2016-11-15 21:56:08 +01:00
|
|
|
|
2023-01-23 20:08:03 +01:00
|
|
|
|
2016-10-30 19:45:38 +01:00
|
|
|
class FingerJointEdge(BaseEdge, FingerJointBase):
|
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
|
|
|
|
|
2023-01-23 20:08:04 +01:00
|
|
|
def draw_finger(self, f, h, style, positive: bool = True, firsthalf: bool = True) -> None:
|
2022-06-27 14:58:08 +02:00
|
|
|
t = self.settings.thickness
|
|
|
|
|
|
|
|
if positive:
|
|
|
|
if style == "springs":
|
|
|
|
self.polyline(
|
2023-01-23 20:08:03 +01:00
|
|
|
0, -90, 0.8 * h, (90, 0.2 * h),
|
|
|
|
0.1 * h, 90, 0.9 * h, -180, 0.9 * h, 90,
|
|
|
|
f - 0.6 * h,
|
|
|
|
90, 0.9 * h, -180, 0.9 * h, 90, 0.1 * h,
|
|
|
|
(90, 0.2 * h), 0.8 * h, -90)
|
2022-06-27 14:58:08 +02:00
|
|
|
elif style == "barbs":
|
2023-01-23 20:08:03 +01:00
|
|
|
n = int((h - 0.1 * t) // (0.3 * t))
|
2022-06-27 14:58:08 +02:00
|
|
|
a = math.degrees(math.atan(0.5))
|
2023-01-23 20:08:03 +01:00
|
|
|
l = 5 ** 0.5
|
|
|
|
poly = [h - n * 0.3 * t] + \
|
|
|
|
([-45, 0.1 * 2 ** 0.5 * t, 45 + a, l * 0.1 * t, -a, 0] * n)
|
2022-06-27 14:58:08 +02:00
|
|
|
self.polyline(
|
|
|
|
0, -90, *poly, 90, f, 90, *reversed(poly), -90
|
|
|
|
)
|
2022-08-20 08:59:39 +02:00
|
|
|
elif style == "snap" and f > 1.9 * t:
|
2022-06-27 14:58:08 +02:00
|
|
|
a12 = math.degrees(math.atan(0.5))
|
|
|
|
l12 = t / math.cos(math.radians(a12))
|
2023-01-23 20:08:03 +01:00
|
|
|
d = 4 * t
|
|
|
|
d2 = d + 1 * t
|
|
|
|
a = math.degrees(math.atan((0.5 * t) / (h + d2)))
|
|
|
|
l = (h + d2) / math.cos(math.radians(a))
|
|
|
|
poly = [0, 90, d, -180, d + h, -90, 0.5 * t, 90 + a12, l12, 90 - a12,
|
|
|
|
0.5 * t, 90 - a, l, +a, 0, (-180, 0.1 * t), h + d2, 90, f - 1.7 * t, 90 - a12, l12, a12, h, -90, 0]
|
2022-08-13 08:25:28 +02:00
|
|
|
if firsthalf:
|
2022-06-27 14:58:08 +02:00
|
|
|
poly = list(reversed(poly))
|
|
|
|
self.polyline(*poly)
|
|
|
|
else:
|
|
|
|
self.polyline(0, -90, h, 90, f, 90, h, -90)
|
|
|
|
else:
|
|
|
|
self.polyline(0, 90, h, -90, f, -90, h, 90)
|
|
|
|
|
2016-08-17 15:07:41 +02:00
|
|
|
def __call__(self, length, bedBolts=None, bedBoltSettings=None, **kw):
|
|
|
|
|
2016-03-25 14:02:52 +01:00
|
|
|
positive = self.positive
|
2022-03-20 23:50:56 +01:00
|
|
|
t = self.settings.thickness
|
2016-03-25 14:02:52 +01:00
|
|
|
|
2022-09-25 14:00:28 +02:00
|
|
|
s, f = self.settings.space, self.settings.finger
|
|
|
|
thickness = self.settings.thickness
|
|
|
|
style = self.settings.style
|
|
|
|
play = self.settings.play
|
2016-03-25 14:02:52 +01:00
|
|
|
|
2016-10-30 19:45:38 +01:00
|
|
|
fingers, leftover = self.calcFingers(length, bedBolts)
|
|
|
|
|
2022-09-25 14:00:28 +02:00
|
|
|
# not enough space for normal fingers - use small rectangular one
|
|
|
|
if (fingers == 0 and f and
|
2023-01-23 20:08:03 +01:00
|
|
|
leftover > 0.75 * thickness and leftover > 4 * play):
|
2022-09-25 14:00:28 +02:00
|
|
|
fingers = 1
|
|
|
|
f = leftover = leftover / 2.0
|
|
|
|
bedBolts = None
|
|
|
|
style = "rectangular"
|
|
|
|
|
2016-10-30 19:47:06 +01:00
|
|
|
if not positive:
|
|
|
|
f += play
|
|
|
|
s -= play
|
|
|
|
leftover -= play
|
2016-03-25 14:02:52 +01:00
|
|
|
|
2017-12-11 18:48:33 +01:00
|
|
|
self.edge(leftover / 2.0, tabs=1)
|
2016-08-17 15:07:41 +02:00
|
|
|
|
2023-01-23 20:08:03 +01:00
|
|
|
l1, l2 = self.fingerLength(self.settings.angle)
|
|
|
|
h = l1 - l2
|
2016-11-23 21:26:31 +01:00
|
|
|
|
2017-09-13 11:37:03 +02:00
|
|
|
d = (bedBoltSettings or self.bedBoltSettings)[0]
|
|
|
|
|
2016-03-25 14:02:52 +01:00
|
|
|
for i in range(fingers):
|
2016-08-17 15:07:41 +02:00
|
|
|
if i != 0:
|
2016-03-25 14:02:52 +01:00
|
|
|
if not positive and bedBolts and bedBolts.drawBolt(i):
|
2017-09-13 11:37:03 +02:00
|
|
|
self.hole(0.5 * s,
|
2017-02-15 22:34:27 +01:00
|
|
|
0.5 * self.settings.thickness, 0.5 * d)
|
2016-08-17 15:07:41 +02:00
|
|
|
|
2016-03-25 14:02:52 +01:00
|
|
|
if positive and bedBolts and bedBolts.drawBolt(i):
|
|
|
|
self.bedBoltHole(s, bedBoltSettings)
|
|
|
|
else:
|
|
|
|
self.edge(s)
|
2022-09-25 14:00:28 +02:00
|
|
|
self.draw_finger(f, h, style,
|
2023-01-23 20:08:03 +01:00
|
|
|
positive, i < fingers // 2)
|
2016-08-17 15:07:41 +02:00
|
|
|
|
2017-12-11 18:48:33 +01:00
|
|
|
self.edge(leftover / 2.0, tabs=1)
|
2016-03-25 14:02:52 +01:00
|
|
|
|
2023-01-23 20:08:04 +01:00
|
|
|
def margin(self) -> float:
|
2016-03-28 16:55:41 +02:00
|
|
|
""" """
|
2016-11-15 21:56:08 +01:00
|
|
|
widths = self.fingerLength(self.settings.angle)
|
|
|
|
if self.positive:
|
2022-03-20 23:50:56 +01:00
|
|
|
if self.settings.style == "snap":
|
|
|
|
return widths[0] - widths[1] + self.settings.thickness
|
2016-12-14 22:07:36 +01:00
|
|
|
return widths[0] - widths[1]
|
2023-01-23 20:08:05 +01:00
|
|
|
return 0.0
|
2016-11-15 21:56:08 +01:00
|
|
|
|
2023-01-23 20:08:04 +01:00
|
|
|
def startwidth(self) -> float:
|
2016-11-15 21:56:08 +01:00
|
|
|
widths = self.fingerLength(self.settings.angle)
|
|
|
|
return widths[self.positive]
|
2016-03-25 14:02:52 +01:00
|
|
|
|
2016-08-17 15:07:41 +02:00
|
|
|
|
2016-03-25 14:02:52 +01:00
|
|
|
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-08-17 15:07:41 +02:00
|
|
|
|
2016-10-30 19:45:38 +01:00
|
|
|
class FingerHoles(FingerJointBase):
|
2016-04-04 01:28:02 +02:00
|
|
|
"""Hole matching a finger joint edge"""
|
2016-08-17 15:07:41 +02:00
|
|
|
|
2023-01-08 19:41:02 +01:00
|
|
|
def __init__(self, boxes, settings) -> None:
|
2016-03-25 14:02:52 +01:00
|
|
|
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)
|
|
|
|
"""
|
2019-02-24 14:42:48 +01:00
|
|
|
with self.boxes.saved_context():
|
|
|
|
self.boxes.moveTo(x, y, angle)
|
|
|
|
s, f = self.settings.space, self.settings.finger
|
|
|
|
p = self.settings.play
|
|
|
|
b = self.boxes.burn
|
|
|
|
fingers, leftover = self.calcFingers(length, bedBolts)
|
|
|
|
|
2022-09-25 14:00:28 +02:00
|
|
|
# not enough space for normal fingers - use small rectangular one
|
|
|
|
if (fingers == 0 and f and
|
2023-01-23 20:08:03 +01:00
|
|
|
leftover > 0.75 * self.settings.thickness and leftover > 4 * p):
|
2022-09-25 14:00:28 +02:00
|
|
|
fingers = 1
|
|
|
|
f = leftover = leftover / 2.0
|
|
|
|
bedBolts = None
|
|
|
|
|
2019-02-24 14:42:48 +01:00
|
|
|
if self.boxes.debug:
|
|
|
|
self.ctx.rectangle(b, -self.settings.width / 2 + b,
|
|
|
|
length - 2 * b, self.settings.width - 2 * b)
|
|
|
|
for i in range(fingers):
|
|
|
|
pos = leftover / 2.0 + i * (s + f)
|
|
|
|
|
|
|
|
if bedBolts and bedBolts.drawBolt(i):
|
|
|
|
d = (bedBoltSettings or self.boxes.bedBoltSettings)[0]
|
|
|
|
self.boxes.hole(pos - 0.5 * s, 0, d * 0.5)
|
|
|
|
|
|
|
|
self.boxes.rectangularHole(pos + 0.5 * f, 0,
|
2023-01-23 20:08:03 +01:00
|
|
|
f + p, self.settings.width + p)
|
|
|
|
|
2016-08-17 15:07:41 +02:00
|
|
|
|
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-08-17 15:07:41 +02:00
|
|
|
|
2023-01-08 19:41:02 +01:00
|
|
|
def __init__(self, boxes, fingerHoles=None, **kw) -> None:
|
2016-10-27 21:33:29 +02:00
|
|
|
settings = None
|
|
|
|
if isinstance(fingerHoles, Settings):
|
|
|
|
settings = fingerHoles
|
|
|
|
fingerHoles = FingerHoles(boxes, settings)
|
2023-01-13 15:32:33 +01:00
|
|
|
super().__init__(boxes, settings, **kw)
|
2016-08-17 15:07:41 +02:00
|
|
|
|
2016-04-09 16:18:05 +02:00
|
|
|
self.fingerHoles = fingerHoles or boxes.fingerHolesAt
|
|
|
|
|
2016-08-17 15:07:41 +02:00
|
|
|
def __call__(self, length, bedBolts=None, bedBoltSettings=None, **kw):
|
2016-07-03 16:37:11 +02:00
|
|
|
dist = self.fingerHoles.settings.edge_width
|
2019-02-24 12:19:57 +01:00
|
|
|
with self.saved_context():
|
|
|
|
self.fingerHoles(
|
2019-07-05 19:35:10 +02:00
|
|
|
0, self.burn + dist + self.settings.thickness / 2, length, 0,
|
2019-02-24 12:19:57 +01:00
|
|
|
bedBolts=bedBolts, bedBoltSettings=bedBoltSettings)
|
2023-01-01 23:58:22 +01:00
|
|
|
if self.settings.bottom_lip:
|
|
|
|
h = self.settings.bottom_lip + \
|
|
|
|
self.fingerHoles.settings.edge_width
|
|
|
|
sp = self.boxes.spacing
|
2023-01-23 20:08:03 +01:00
|
|
|
self.moveTo(-sp / 2, -h - sp)
|
2023-01-01 23:58:22 +01:00
|
|
|
self.rectangularWall(length - 1.05 * self.boxes.thickness, h)
|
2019-02-24 12:19:57 +01:00
|
|
|
self.edge(length, tabs=2)
|
2016-03-25 14:02:52 +01:00
|
|
|
|
2023-01-23 20:08:04 +01:00
|
|
|
def startwidth(self) -> float:
|
2016-04-09 16:18:05 +02:00
|
|
|
""" """
|
2017-02-15 22:34:27 +01:00
|
|
|
return self.fingerHoles.settings.edge_width + self.settings.thickness
|
2016-04-09 16:18:05 +02:00
|
|
|
|
2023-01-23 20:08:04 +01:00
|
|
|
def margin(self) -> float:
|
2023-01-01 23:58:22 +01:00
|
|
|
if self.settings.bottom_lip:
|
|
|
|
return self.settings.bottom_lip + self.fingerHoles.settings.edge_width + self.boxes.spacing
|
2023-01-23 20:08:05 +01:00
|
|
|
return 0.0
|
2016-08-17 15:07:41 +02:00
|
|
|
|
2023-01-23 20:08:03 +01:00
|
|
|
|
2018-02-12 22:04:20 +01:00
|
|
|
class CrossingFingerHoleEdge(Edge):
|
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)"
|
2018-07-02 19:13:52 +02:00
|
|
|
char = '|'
|
2016-04-17 14:26:27 +02:00
|
|
|
|
2023-01-23 20:08:04 +01:00
|
|
|
def __init__(self, boxes, height, fingerHoles=None, outset: float = 0.0, **kw) -> None:
|
2023-01-13 15:32:33 +01:00
|
|
|
super().__init__(boxes, None, **kw)
|
2016-08-17 15:07:41 +02:00
|
|
|
|
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
|
2023-01-17 20:53:18 +01:00
|
|
|
self.outset = outset
|
2016-03-25 14:02:52 +01:00
|
|
|
|
|
|
|
def __call__(self, length, **kw):
|
2023-01-23 20:08:03 +01:00
|
|
|
self.fingerHoles(length / 2.0, self.outset + self.burn, self.height)
|
2023-01-13 15:32:33 +01:00
|
|
|
super().__call__(length)
|
2016-03-25 14:02:52 +01:00
|
|
|
|
2023-01-23 20:08:04 +01:00
|
|
|
def startwidth(self) -> float:
|
2023-01-17 20:53:18 +01:00
|
|
|
return self.outset
|
2016-08-17 15:07:41 +02:00
|
|
|
|
2023-01-23 20:08:03 +01:00
|
|
|
|
2016-04-09 14:27:51 +02:00
|
|
|
#############################################################################
|
|
|
|
#### Stackable Joints
|
|
|
|
#############################################################################
|
|
|
|
|
|
|
|
class StackableSettings(Settings):
|
2016-10-31 22:06:52 +01:00
|
|
|
"""Settings for Stackable Edges
|
2016-04-09 14:27:51 +02:00
|
|
|
|
|
|
|
Values:
|
|
|
|
|
|
|
|
* absolute_params
|
|
|
|
|
|
|
|
* angle : 60 : inside angle of the feet
|
|
|
|
|
|
|
|
* relative (in multiples of thickness)
|
|
|
|
|
2022-01-07 22:15:05 +01:00
|
|
|
* height : 2.0 : height of the feet (multiples of thickness)
|
|
|
|
* width : 4.0 : width of the feet (multiples of thickness)
|
|
|
|
* holedistance : 1.0 : distance from finger holes to bottom edge (multiples of thickness)
|
2023-01-01 21:20:42 +01:00
|
|
|
* bottom_stabilizers : 0.0 : height of strips to be glued to the inside of bottom edges (multiples of thickness)
|
2016-04-09 14:27:51 +02:00
|
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
absolute_params = {
|
2016-08-17 15:07:41 +02:00
|
|
|
"angle": 60,
|
2016-04-09 14:27:51 +02:00
|
|
|
}
|
2016-08-17 15:07:41 +02:00
|
|
|
|
2016-04-09 14:27:51 +02:00
|
|
|
relative_params = {
|
2016-08-17 15:07:41 +02:00
|
|
|
"height": 2.0,
|
|
|
|
"width": 4.0,
|
|
|
|
"holedistance": 1.0,
|
2023-01-23 20:08:03 +01:00
|
|
|
"bottom_stabilizers": 0.0,
|
2016-04-09 14:27:51 +02:00
|
|
|
}
|
|
|
|
|
2023-01-23 20:08:04 +01:00
|
|
|
def checkValues(self) -> None:
|
2020-02-01 14:16:26 +01:00
|
|
|
if self.angle < 20:
|
|
|
|
raise ValueError("StackableSettings: 'angle' is too small. Use value >= 20")
|
|
|
|
if self.angle > 260:
|
|
|
|
raise ValueError("StackableSettings: 'angle' is too big. Use value < 260")
|
|
|
|
|
2023-01-23 20:08:04 +01:00
|
|
|
def edgeObjects(self, boxes, chars: str = "sSšŠ", add: bool = True, fingersettings=None):
|
2016-11-23 21:11:15 +01:00
|
|
|
fingersettings = fingersettings or boxes.edges["f"].settings
|
|
|
|
edges = [StackableEdge(boxes, self, fingersettings),
|
2021-05-14 09:50:33 +02:00
|
|
|
StackableEdgeTop(boxes, self, fingersettings),
|
2022-05-04 22:19:35 +02:00
|
|
|
StackableFeet(boxes, self, fingersettings),
|
|
|
|
StackableHoleEdgeTop(boxes, self, fingersettings),
|
|
|
|
]
|
2016-11-23 21:11:15 +01:00
|
|
|
return self._edgeObjects(edges, boxes, chars, add)
|
2016-08-17 15:07:41 +02:00
|
|
|
|
2023-01-23 20:08:03 +01:00
|
|
|
|
2021-05-14 09:50:33 +02:00
|
|
|
class StackableBaseEdge(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"
|
2021-05-14 09:50:33 +02:00
|
|
|
description = "Abstract Stackable class"
|
2016-04-09 14:27:51 +02:00
|
|
|
bottom = True
|
|
|
|
|
2023-01-08 19:41:02 +01:00
|
|
|
def __init__(self, boxes, settings, fingerjointsettings) -> None:
|
2021-05-14 09:50:33 +02:00
|
|
|
super().__init__(boxes, settings)
|
2016-08-17 15:07:41 +02:00
|
|
|
|
2016-04-09 14:27:51 +02:00
|
|
|
self.fingerjointsettings = fingerjointsettings
|
|
|
|
|
|
|
|
def __call__(self, length, **kw):
|
|
|
|
s = self.settings
|
2016-08-17 15:07:41 +02:00
|
|
|
r = s.height / 2.0 / (1 - math.cos(math.radians(s.angle)))
|
2016-04-09 14:27:51 +02:00
|
|
|
l = r * math.sin(math.radians(s.angle))
|
|
|
|
p = 1 if self.bottom else -1
|
|
|
|
|
2023-01-01 21:20:42 +01:00
|
|
|
if self.bottom and s.bottom_stabilizers:
|
|
|
|
with self.saved_context():
|
|
|
|
sp = self.boxes.spacing
|
2023-01-23 20:08:03 +01:00
|
|
|
self.moveTo(-sp / 2, -s.height - sp)
|
2023-01-01 21:20:42 +01:00
|
|
|
self.rectangularWall(length - 1.05 * self.boxes.thickness,
|
|
|
|
s.bottom_stabilizers)
|
|
|
|
|
2017-12-11 18:48:33 +01:00
|
|
|
self.boxes.edge(s.width, tabs=1)
|
2016-08-17 15:07:41 +02:00
|
|
|
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)
|
2017-12-11 18:48:33 +01:00
|
|
|
self.boxes.edge(s.width, tabs=1)
|
2016-04-09 14:27:51 +02:00
|
|
|
|
|
|
|
def _height(self):
|
|
|
|
return self.settings.height + self.settings.holedistance + self.settings.thickness
|
2016-08-17 15:07:41 +02:00
|
|
|
|
2023-01-23 20:08:04 +01:00
|
|
|
def startwidth(self) -> float:
|
2016-04-09 14:27:51 +02:00
|
|
|
return self._height() if self.bottom else 0
|
|
|
|
|
2023-01-23 20:08:04 +01:00
|
|
|
def margin(self) -> float:
|
2023-01-01 21:20:42 +01:00
|
|
|
if self.bottom:
|
|
|
|
if self.settings.bottom_stabilizers:
|
|
|
|
return self.settings.bottom_stabilizers + self.boxes.spacing
|
|
|
|
else:
|
|
|
|
return 0
|
|
|
|
else:
|
|
|
|
return self.settings.height
|
2016-04-09 14:27:51 +02:00
|
|
|
|
2023-01-23 20:08:03 +01:00
|
|
|
|
2021-05-14 09:50:33 +02:00
|
|
|
class StackableEdge(StackableBaseEdge):
|
|
|
|
"""Edge for having stackable Boxes. The Edge creates feet on the bottom
|
|
|
|
and has matching recesses on the top corners."""
|
|
|
|
|
|
|
|
char = "s"
|
|
|
|
description = "Stackable (bottom, finger joint holes)"
|
|
|
|
|
|
|
|
def __call__(self, length, **kw):
|
|
|
|
s = self.settings
|
|
|
|
self.boxes.fingerHolesAt(
|
|
|
|
0,
|
|
|
|
s.height + s.holedistance + 0.5 * self.boxes.thickness,
|
|
|
|
length, 0)
|
|
|
|
super().__call__(length, **kw)
|
2016-08-17 15:07:41 +02:00
|
|
|
|
2023-01-23 20:08:03 +01:00
|
|
|
|
2021-05-14 09:50:33 +02:00
|
|
|
class StackableEdgeTop(StackableBaseEdge):
|
2016-04-09 14:27:51 +02:00
|
|
|
char = "S"
|
2016-04-17 14:26:27 +02:00
|
|
|
description = "Stackable (top)"
|
2016-04-09 14:27:51 +02:00
|
|
|
bottom = False
|
|
|
|
|
2023-01-23 20:08:03 +01:00
|
|
|
|
2021-05-14 09:50:33 +02:00
|
|
|
class StackableFeet(StackableBaseEdge):
|
|
|
|
char = "š"
|
|
|
|
description = "Stackable feet (bottom)"
|
|
|
|
|
|
|
|
def _height(self):
|
|
|
|
return self.settings.height
|
2016-08-17 15:07:41 +02:00
|
|
|
|
2023-01-23 20:08:03 +01:00
|
|
|
|
2022-05-04 22:19:35 +02:00
|
|
|
class StackableHoleEdgeTop(StackableBaseEdge):
|
|
|
|
char = "Š"
|
|
|
|
description = "Stackable edge with finger holes (top)"
|
|
|
|
bottom = False
|
|
|
|
|
2023-01-23 20:08:04 +01:00
|
|
|
def startwidth(self) -> float:
|
2022-05-04 22:19:35 +02:00
|
|
|
return self.settings.thickness + self.settings.holedistance
|
|
|
|
|
|
|
|
def __call__(self, length, **kw):
|
|
|
|
s = self.settings
|
|
|
|
self.boxes.fingerHolesAt(
|
|
|
|
0,
|
|
|
|
s.holedistance + 0.5 * self.boxes.thickness,
|
|
|
|
length, 0)
|
|
|
|
super().__call__(length, **kw)
|
|
|
|
|
2023-01-23 20:08:03 +01:00
|
|
|
|
2016-05-25 19:59:49 +02:00
|
|
|
#############################################################################
|
|
|
|
#### Hinges
|
|
|
|
#############################################################################
|
|
|
|
|
|
|
|
class HingeSettings(Settings):
|
2016-10-31 22:06:52 +01:00
|
|
|
"""Settings for Hinges and HingePins
|
2016-05-25 19:59:49 +02:00
|
|
|
Values:
|
|
|
|
|
|
|
|
* absolute_params
|
|
|
|
|
2016-11-01 20:41:25 +01:00
|
|
|
* style : "outset" : "outset" or "flush"
|
2016-05-25 19:59:49 +02:00
|
|
|
* 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)
|
|
|
|
|
2022-01-07 22:15:05 +01:00
|
|
|
* hingestrength : 1 : thickness of the arc holding the pin in place (multiples of thickness)
|
|
|
|
* axle : 2 : diameter of the pin hole (multiples of thickness)
|
|
|
|
* grip_length : 0 : fixed length of the grips on he lids (multiples of thickness)
|
2016-05-25 19:59:49 +02:00
|
|
|
|
|
|
|
"""
|
|
|
|
absolute_params = {
|
2016-11-01 20:41:25 +01:00
|
|
|
"style": ("outset", "flush"),
|
2016-08-17 15:07:41 +02:00
|
|
|
"outset": False,
|
|
|
|
"pinwidth": 0.5,
|
|
|
|
"grip_percentage": 0,
|
|
|
|
}
|
2016-05-25 19:59:49 +02:00
|
|
|
|
|
|
|
relative_params = {
|
2016-08-17 15:07:41 +02:00
|
|
|
"hingestrength": 1, # 1.5-0.5*2**0.5,
|
2020-02-28 17:46:20 +01:00
|
|
|
"axle": 2.0,
|
2016-08-17 15:07:41 +02:00
|
|
|
"grip_length": 0,
|
|
|
|
}
|
2016-05-25 19:59:49 +02:00
|
|
|
|
2023-01-23 20:08:04 +01:00
|
|
|
def checkValues(self) -> None:
|
2020-02-28 17:46:20 +01:00
|
|
|
if self.axle / self.thickness < 0.1:
|
|
|
|
raise ValueError("HingeSettings: 'axle' need to be at least 0.1 strong")
|
|
|
|
|
2023-01-23 20:08:04 +01:00
|
|
|
def edgeObjects(self, boxes, chars: str = "iIjJkK", add: bool = True):
|
2016-11-23 21:11:15 +01:00
|
|
|
edges = [
|
|
|
|
Hinge(boxes, self, 1),
|
|
|
|
HingePin(boxes, self, 1),
|
|
|
|
Hinge(boxes, self, 2),
|
|
|
|
HingePin(boxes, self, 2),
|
|
|
|
Hinge(boxes, self, 3),
|
|
|
|
HingePin(boxes, self, 3),
|
|
|
|
]
|
|
|
|
return self._edgeObjects(edges, boxes, chars, add)
|
2016-05-25 19:59:49 +02:00
|
|
|
|
2023-01-23 20:08:03 +01:00
|
|
|
|
2016-08-17 15:07:41 +02:00
|
|
|
class Hinge(BaseEdge):
|
2016-05-25 19:59:49 +02:00
|
|
|
char = 'i'
|
|
|
|
description = "Straight edge with hinge eye"
|
|
|
|
|
2023-01-23 20:08:04 +01:00
|
|
|
def __init__(self, boxes, settings=None, layout: int = 1) -> None:
|
2023-01-13 15:32:33 +01:00
|
|
|
super().__init__(boxes, settings)
|
2016-08-17 15:07:41 +02:00
|
|
|
|
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-08-17 15:07:41 +02:00
|
|
|
|
2016-05-25 19:59:49 +02:00
|
|
|
self.layout = layout
|
|
|
|
self.char = "eijk"[layout]
|
|
|
|
self.description = self.description + ('', ' (start)', ' (end)', ' (both ends)')[layout]
|
|
|
|
|
2023-01-23 20:08:04 +01:00
|
|
|
def margin(self) -> float:
|
2017-02-15 22:34:27 +01:00
|
|
|
return 3 * self.settings.thickness
|
2016-05-25 19:59:49 +02:00
|
|
|
|
2023-01-23 20:08:04 +01:00
|
|
|
def outset(self, _reversed: bool = False) -> None:
|
|
|
|
t: float = self.settings.thickness
|
2016-07-06 23:08:50 +02:00
|
|
|
r = 0.5 * self.settings.axle
|
2016-08-17 15:07:41 +02:00
|
|
|
alpha = math.degrees(math.asin(0.5 * t / r))
|
2017-02-15 22:34:27 +01:00
|
|
|
pinl = (self.settings.axle ** 2 - self.settings.thickness ** 2) ** 0.5 * self.settings.pinwidth
|
2016-07-06 23:08:50 +02:00
|
|
|
pos = math.cos(math.radians(alpha)) * r
|
2016-05-25 19:59:49 +02:00
|
|
|
hinge = (
|
2023-01-23 20:08:04 +01:00
|
|
|
0.,
|
|
|
|
90. - alpha, 0.,
|
|
|
|
(-360., r), 0.,
|
|
|
|
90. + alpha,
|
2016-05-25 19:59:49 +02:00
|
|
|
t,
|
2023-01-23 20:08:04 +01:00
|
|
|
90.,
|
2016-08-17 15:07:41 +02:00
|
|
|
0.5 * t,
|
2023-01-23 20:08:04 +01:00
|
|
|
(180., t + pos), 0.,
|
|
|
|
(-90., 0.5 * t), 0.
|
2016-05-25 19:59:49 +02:00
|
|
|
)
|
2016-08-17 15:07:41 +02:00
|
|
|
|
2016-05-25 19:59:49 +02:00
|
|
|
if _reversed:
|
2023-01-23 20:08:04 +01:00
|
|
|
hinge = reversed(hinge) # type: ignore
|
2016-07-06 23:08:50 +02:00
|
|
|
self.polyline(*hinge)
|
2017-02-15 22:34:27 +01:00
|
|
|
self.boxes.rectangularHole(-pos, -0.5 * t, pinl, self.settings.thickness)
|
2016-07-06 23:08:50 +02:00
|
|
|
else:
|
2017-02-15 22:34:27 +01:00
|
|
|
self.boxes.rectangularHole(pos, -0.5 * t, pinl, self.settings.thickness)
|
2016-07-06 23:08:50 +02:00
|
|
|
self.polyline(*hinge)
|
2016-05-25 19:59:49 +02:00
|
|
|
|
2023-01-23 20:08:04 +01:00
|
|
|
def outsetlen(self) -> float:
|
2017-02-15 22:34:27 +01:00
|
|
|
t = self.settings.thickness
|
2016-07-06 23:08:50 +02:00
|
|
|
r = 0.5 * self.settings.axle
|
2016-08-17 15:07:41 +02:00
|
|
|
alpha = math.degrees(math.asin(0.5 * t / r))
|
2016-07-06 23:08:50 +02:00
|
|
|
pos = math.cos(math.radians(alpha)) * r
|
2016-08-17 15:07:41 +02:00
|
|
|
|
2023-01-23 20:08:04 +01:00
|
|
|
return 2.0 * pos + 1.5 * t
|
2016-05-25 19:59:49 +02:00
|
|
|
|
2023-01-23 20:08:04 +01:00
|
|
|
def flush(self, _reversed: bool = False) -> None:
|
2017-02-15 22:34:27 +01:00
|
|
|
t = self.settings.thickness
|
2016-05-25 19:59:49 +02:00
|
|
|
|
|
|
|
hinge = (
|
2023-01-23 20:08:04 +01:00
|
|
|
0., -90.,
|
2016-08-17 15:07:41 +02:00
|
|
|
0.5 * t,
|
2023-01-23 20:08:04 +01:00
|
|
|
(180., 0.5 * self.settings.axle + self.settings.hingestrength), 0.,
|
|
|
|
(-90., 0.5 * t), 0.
|
2016-05-25 19:59:49 +02:00
|
|
|
)
|
2016-08-17 15:07:41 +02:00
|
|
|
pos = 0.5 * self.settings.axle + self.settings.hingestrength
|
2017-02-15 22:34:27 +01:00
|
|
|
pinl = (self.settings.axle ** 2 - self.settings.thickness ** 2) ** 0.5 * self.settings.pinwidth
|
2016-07-06 23:07:44 +02:00
|
|
|
|
2016-05-25 19:59:49 +02:00
|
|
|
if _reversed:
|
2023-01-23 20:08:04 +01:00
|
|
|
hinge = reversed(hinge) # type: ignore
|
2016-08-17 15:07:41 +02:00
|
|
|
self.hole(0.5 * t + pos, -0.5 * t, 0.5 * self.settings.axle)
|
2017-02-15 22:34:27 +01:00
|
|
|
self.boxes.rectangularHole(0.5 * t + pos, -0.5 * t, pinl, self.settings.thickness)
|
2016-05-25 19:59:49 +02:00
|
|
|
else:
|
2016-08-17 15:07:41 +02:00
|
|
|
self.hole(pos, -0.5 * t, 0.5 * self.settings.axle)
|
2017-02-15 22:34:27 +01:00
|
|
|
self.boxes.rectangularHole(pos, -0.5 * t, pinl, self.settings.thickness)
|
2016-08-17 15:07:41 +02:00
|
|
|
|
2016-05-25 19:59:49 +02:00
|
|
|
self.polyline(*hinge)
|
|
|
|
|
2023-01-23 20:08:04 +01:00
|
|
|
def flushlen(self) -> float:
|
|
|
|
return self.settings.axle + 2.0 * self.settings.hingestrength + 0.5 * self.settings.thickness
|
2016-05-25 19:59:49 +02:00
|
|
|
|
|
|
|
def __call__(self, l, **kw):
|
2016-11-01 20:41:25 +01:00
|
|
|
hlen = getattr(self, self.settings.style + 'len', self.outsetlen)()
|
2016-08-17 15:07:41 +02:00
|
|
|
|
2016-05-25 19:59:49 +02:00
|
|
|
if self.layout & 1:
|
2016-11-01 20:41:25 +01:00
|
|
|
getattr(self, self.settings.style, self.outset)()
|
2016-08-17 15:07:41 +02:00
|
|
|
|
2017-12-11 18:48:33 +01:00
|
|
|
self.edge(l - (self.layout & 1) * hlen - bool(self.layout & 2) * hlen,
|
|
|
|
tabs=2)
|
2016-08-17 15:07:41 +02:00
|
|
|
|
2016-05-25 19:59:49 +02:00
|
|
|
if self.layout & 2:
|
2016-11-01 20:41:25 +01:00
|
|
|
getattr(self, self.settings.style, self.outset)(True)
|
2016-05-25 19:59:49 +02:00
|
|
|
|
2016-08-17 15:07:41 +02:00
|
|
|
|
2016-05-25 19:59:49 +02:00
|
|
|
class HingePin(BaseEdge):
|
|
|
|
char = 'I'
|
|
|
|
description = "Edge with hinge pin"
|
|
|
|
|
2023-01-23 20:08:04 +01:00
|
|
|
def __init__(self, boxes, settings=None, layout: int = 1) -> None:
|
2023-01-13 15:32:33 +01:00
|
|
|
super().__init__(boxes, settings)
|
2016-08-17 15:07:41 +02:00
|
|
|
|
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-08-17 15:07:41 +02:00
|
|
|
|
2016-05-25 19:59:49 +02:00
|
|
|
self.layout = layout
|
|
|
|
self.char = "EIJK"[layout]
|
|
|
|
self.description = self.description + ('', ' (start)', ' (end)', ' (both ends)')[layout]
|
|
|
|
|
2023-01-23 20:08:04 +01:00
|
|
|
def startwidth(self) -> float:
|
2016-05-25 19:59:49 +02:00
|
|
|
if self.layout & 1:
|
2023-01-23 20:08:05 +01:00
|
|
|
return 0.0
|
|
|
|
return self.settings.outset * self.boxes.thickness
|
2016-05-25 19:59:49 +02:00
|
|
|
|
2023-01-23 20:08:04 +01:00
|
|
|
def endwidth(self) -> float:
|
2016-05-25 19:59:49 +02:00
|
|
|
if self.layout & 2:
|
2023-01-23 20:08:05 +01:00
|
|
|
return 0.0
|
|
|
|
return self.settings.outset * self.boxes.thickness
|
2016-05-25 19:59:49 +02:00
|
|
|
|
2023-01-23 20:08:04 +01:00
|
|
|
def margin(self) -> float:
|
2017-02-15 22:34:27 +01:00
|
|
|
return self.settings.thickness
|
2016-05-25 19:59:49 +02:00
|
|
|
|
2023-01-23 20:08:04 +01:00
|
|
|
def outset(self, _reversed: bool = False) -> None:
|
|
|
|
t: float = self.settings.thickness
|
2016-07-06 23:08:50 +02:00
|
|
|
r = 0.5 * self.settings.axle
|
2016-08-17 15:07:41 +02:00
|
|
|
alpha = math.degrees(math.asin(0.5 * t / r))
|
2016-07-06 23:08:50 +02:00
|
|
|
pos = math.cos(math.radians(alpha)) * r
|
2017-02-15 22:34:27 +01:00
|
|
|
pinl = (self.settings.axle ** 2 - self.settings.thickness ** 2) ** 0.5 * self.settings.pinwidth
|
2023-01-23 20:08:04 +01:00
|
|
|
pin = (pos - 0.5 * pinl, -90.,
|
|
|
|
t, 90.,
|
2016-07-06 23:08:50 +02:00
|
|
|
pinl,
|
2023-01-23 20:08:04 +01:00
|
|
|
90.,
|
2016-05-25 19:59:49 +02:00
|
|
|
t,
|
2023-01-23 20:08:04 +01:00
|
|
|
-90.)
|
2016-05-25 19:59:49 +02:00
|
|
|
|
|
|
|
if self.settings.outset:
|
2023-01-23 20:08:04 +01:00
|
|
|
pin += ( # type: ignore
|
2016-08-17 15:07:41 +02:00
|
|
|
pos - 0.5 * pinl + 1.5 * t,
|
2023-01-23 20:08:04 +01:00
|
|
|
-90.,
|
2016-05-25 19:59:49 +02:00
|
|
|
t,
|
2023-01-23 20:08:04 +01:00
|
|
|
90.,
|
|
|
|
0.,
|
2016-05-25 19:59:49 +02:00
|
|
|
)
|
|
|
|
else:
|
2023-01-23 20:08:04 +01:00
|
|
|
pin += (pos - 0.5 * pinl,) # type: ignore
|
2016-08-17 15:07:41 +02:00
|
|
|
|
2016-05-25 19:59:49 +02:00
|
|
|
if _reversed:
|
2023-01-23 20:08:04 +01:00
|
|
|
pin = reversed(pin) # type: ignore
|
2016-08-17 15:07:41 +02:00
|
|
|
|
2016-05-25 19:59:49 +02:00
|
|
|
self.polyline(*pin)
|
|
|
|
|
2016-11-01 20:41:25 +01:00
|
|
|
def outsetlen(self):
|
2017-02-15 22:34:27 +01:00
|
|
|
t = self.settings.thickness
|
2016-07-06 23:08:50 +02:00
|
|
|
r = 0.5 * self.settings.axle
|
2016-08-17 15:07:41 +02:00
|
|
|
alpha = math.degrees(math.asin(0.5 * t / r))
|
2016-07-06 23:08:50 +02:00
|
|
|
pos = math.cos(math.radians(alpha)) * r
|
|
|
|
|
2016-05-25 19:59:49 +02:00
|
|
|
if self.settings.outset:
|
2017-02-15 22:34:27 +01:00
|
|
|
return 2 * pos + 1.5 * self.settings.thickness
|
2023-01-23 20:08:05 +01:00
|
|
|
return 2 * pos
|
2016-05-25 19:59:49 +02:00
|
|
|
|
2023-01-23 20:08:04 +01:00
|
|
|
def flush(self, _reversed: bool = False) -> None:
|
|
|
|
t: float = self.settings.thickness
|
2016-08-17 15:07:41 +02:00
|
|
|
pinl = (self.settings.axle ** 2 - t ** 2) ** 0.5 * self.settings.pinwidth
|
2016-05-25 19:59:49 +02:00
|
|
|
d = (self.settings.axle - pinl) / 2.0
|
2023-01-23 20:08:04 +01:00
|
|
|
pin = (self.settings.hingestrength + d, -90.,
|
|
|
|
t, 90.,
|
2016-05-25 19:59:49 +02:00
|
|
|
pinl,
|
2023-01-23 20:08:04 +01:00
|
|
|
90.,
|
2016-05-25 19:59:49 +02:00
|
|
|
t,
|
2023-01-23 20:08:04 +01:00
|
|
|
-90., d)
|
2016-05-25 19:59:49 +02:00
|
|
|
|
|
|
|
if self.settings.outset:
|
2023-01-23 20:08:04 +01:00
|
|
|
pin += ( # type: ignore
|
|
|
|
0.,
|
2016-08-17 15:07:41 +02:00
|
|
|
self.settings.hingestrength + 0.5 * t,
|
2023-01-23 20:08:04 +01:00
|
|
|
-90.,
|
2016-05-25 19:59:49 +02:00
|
|
|
t,
|
2023-01-23 20:08:04 +01:00
|
|
|
90.,
|
|
|
|
0.,
|
2016-05-25 19:59:49 +02:00
|
|
|
)
|
2016-08-17 15:07:41 +02:00
|
|
|
|
2016-05-25 19:59:49 +02:00
|
|
|
if _reversed:
|
2023-01-23 20:08:04 +01:00
|
|
|
pin = reversed(pin) # type: ignore
|
2016-08-17 15:07:41 +02:00
|
|
|
|
2016-05-25 19:59:49 +02:00
|
|
|
self.polyline(*pin)
|
|
|
|
|
2016-11-01 20:41:25 +01:00
|
|
|
def flushlen(self):
|
2016-08-17 15:07:41 +02:00
|
|
|
l = self.settings.hingestrength + self.settings.axle
|
|
|
|
|
2016-05-25 19:59:49 +02:00
|
|
|
if self.settings.outset:
|
2017-02-15 22:34:27 +01:00
|
|
|
l += self.settings.hingestrength + 0.5 * self.settings.thickness
|
2016-08-17 15:07:41 +02:00
|
|
|
|
2016-05-25 19:59:49 +02:00
|
|
|
return l
|
|
|
|
|
|
|
|
def __call__(self, l, **kw):
|
2016-11-01 20:41:25 +01:00
|
|
|
plen = getattr(self, self.settings.style + 'len', self.outsetlen)()
|
2022-10-11 16:41:33 +02:00
|
|
|
glen = l * self.settings.grip_percentage / 100 + \
|
2016-07-03 09:55:59 +02:00
|
|
|
self.settings.grip_length
|
2016-08-17 15:07:41 +02:00
|
|
|
|
2016-07-03 09:55:59 +02:00
|
|
|
if not self.settings.outset:
|
|
|
|
glen = 0.0
|
2016-08-17 15:07:41 +02:00
|
|
|
|
|
|
|
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-11-01 20:41:25 +01:00
|
|
|
getattr(self, self.settings.style, self.outset)()
|
2017-12-11 18:48:33 +01:00
|
|
|
self.edge(l - 2 * plen, tabs=2)
|
2016-11-01 20:41:25 +01:00
|
|
|
getattr(self, self.settings.style, self.outset)(True)
|
2016-07-03 09:55:59 +02:00
|
|
|
elif self.layout & 1:
|
2016-11-01 20:41:25 +01:00
|
|
|
getattr(self, self.settings.style, self.outset)()
|
2017-12-11 18:48:33 +01:00
|
|
|
self.edge(l - plen - glen, tabs=2)
|
2016-07-03 09:55:59 +02:00
|
|
|
self.edges['g'](glen)
|
|
|
|
else:
|
|
|
|
self.edges['g'](glen)
|
2017-12-11 18:48:33 +01:00
|
|
|
self.edge(l - plen - glen, tabs=2)
|
2016-11-01 20:41:25 +01:00
|
|
|
getattr(self, self.settings.style, self.outset)(True)
|
2016-05-25 19:59:49 +02:00
|
|
|
|
2023-01-23 20:08:03 +01:00
|
|
|
|
2017-02-06 22:35:26 +01:00
|
|
|
#############################################################################
|
|
|
|
#### Chest Hinge
|
|
|
|
#############################################################################
|
|
|
|
|
|
|
|
class ChestHingeSettings(Settings):
|
|
|
|
"""Settings for Chest Hinges
|
|
|
|
Values:
|
|
|
|
|
|
|
|
* relative (in multiples of thickness)
|
|
|
|
|
2022-01-07 22:15:05 +01:00
|
|
|
* pin_height : 2.0 : radius of the disc rotating in the hinge (multiples of thickness)
|
|
|
|
* hinge_strength : 1.0 : thickness of the arc holding the pin in place (multiples of thickness)
|
2022-08-10 20:19:52 +02:00
|
|
|
|
|
|
|
* absolute
|
|
|
|
|
|
|
|
* finger_joints_on_box : False : whether to include finger joints on the edge with the box
|
|
|
|
* finger_joints_on_lid : False : whether to include finger joints on the edge with the lid
|
2017-02-06 22:35:26 +01:00
|
|
|
"""
|
|
|
|
|
|
|
|
relative_params = {
|
2023-01-23 20:08:03 +01:00
|
|
|
"pin_height": 2.0,
|
|
|
|
"hinge_strength": 1.0,
|
|
|
|
"play": 0.1,
|
2022-08-10 20:19:52 +02:00
|
|
|
}
|
2023-01-23 20:08:03 +01:00
|
|
|
|
2022-08-10 20:19:52 +02:00
|
|
|
absolute_params = {
|
2023-01-23 20:08:03 +01:00
|
|
|
"finger_joints_on_box": False,
|
|
|
|
"finger_joints_on_lid": False,
|
2022-08-10 20:19:52 +02:00
|
|
|
}
|
2017-02-06 22:35:26 +01:00
|
|
|
|
2023-01-23 20:08:04 +01:00
|
|
|
def checkValues(self) -> None:
|
2020-02-01 14:27:17 +01:00
|
|
|
if self.pin_height / self.thickness < 1.2:
|
|
|
|
raise ValueError("ChestHingeSettings: 'pin_height' must be >= 1.2")
|
|
|
|
|
2017-02-06 22:35:26 +01:00
|
|
|
def pinheight(self):
|
2023-01-23 20:08:03 +01:00
|
|
|
return ((0.9 * self.pin_height) ** 2 - self.thickness ** 2) ** 0.5
|
2017-02-06 22:35:26 +01:00
|
|
|
|
2023-01-23 20:08:04 +01:00
|
|
|
def edgeObjects(self, boxes, chars: str = "oOpPqQ", add: bool = True):
|
2017-02-06 22:35:26 +01:00
|
|
|
edges = [
|
|
|
|
ChestHinge(boxes, self),
|
2023-01-23 20:08:04 +01:00
|
|
|
ChestHinge(boxes, self, True),
|
2017-02-06 22:35:26 +01:00
|
|
|
ChestHingeTop(boxes, self),
|
2023-01-23 20:08:04 +01:00
|
|
|
ChestHingeTop(boxes, self, True),
|
2017-02-06 22:35:26 +01:00
|
|
|
ChestHingePin(boxes, self),
|
|
|
|
ChestHingeFront(boxes, self),
|
|
|
|
]
|
|
|
|
return self._edgeObjects(edges, boxes, chars, add)
|
|
|
|
|
|
|
|
|
2023-01-23 20:08:03 +01:00
|
|
|
class ChestHinge(BaseEdge):
|
2018-12-27 17:12:13 +01:00
|
|
|
description = "Edge with chest hinge"
|
2017-02-06 22:35:26 +01:00
|
|
|
|
|
|
|
char = "o"
|
|
|
|
|
2023-01-23 20:08:04 +01:00
|
|
|
def __init__(self, boxes, settings=None, reversed: bool = False) -> None:
|
2023-01-13 15:32:33 +01:00
|
|
|
super().__init__(boxes, settings)
|
2017-02-06 22:35:26 +01:00
|
|
|
|
|
|
|
self.reversed = reversed
|
|
|
|
self.char = "oO"[reversed]
|
|
|
|
self.description = self.description + (' (start)', ' (end)')[reversed]
|
|
|
|
|
|
|
|
def __call__(self, l, **kw):
|
|
|
|
t = self.settings.thickness
|
|
|
|
p = self.settings.pin_height
|
|
|
|
s = self.settings.hinge_strength
|
|
|
|
pinh = self.settings.pinheight()
|
|
|
|
if self.reversed:
|
2023-01-23 20:08:03 +01:00
|
|
|
self.hole(l + t, 0, p, tabs=4)
|
|
|
|
self.rectangularHole(l + 0.5 * t, -0.5 * pinh, t, pinh)
|
2017-02-06 22:35:26 +01:00
|
|
|
else:
|
2023-01-23 20:08:03 +01:00
|
|
|
self.hole(-t, -s - p, p, tabs=4)
|
|
|
|
self.rectangularHole(-0.5 * t, -s - p - 0.5 * pinh, t, pinh)
|
2017-02-06 22:35:26 +01:00
|
|
|
|
2022-08-10 20:19:52 +02:00
|
|
|
if self.settings.finger_joints_on_box:
|
2023-01-23 20:08:03 +01:00
|
|
|
final_segment = t - s
|
|
|
|
draw_rest_of_edge = lambda: self.edges["F"](l - p)
|
2022-08-10 20:19:52 +02:00
|
|
|
else:
|
2023-01-23 20:08:03 +01:00
|
|
|
final_segment = l + t - p - s
|
|
|
|
draw_rest_of_edge = lambda: None
|
2022-08-10 20:19:52 +02:00
|
|
|
|
2023-01-23 20:08:03 +01:00
|
|
|
poly = (0, -180, t, (270, p + s), 0, -90, final_segment)
|
2022-08-10 20:19:52 +02:00
|
|
|
|
2017-02-06 22:35:26 +01:00
|
|
|
if self.reversed:
|
2022-08-10 20:19:52 +02:00
|
|
|
draw_rest_of_edge()
|
|
|
|
self.polyline(*reversed(poly))
|
|
|
|
else:
|
|
|
|
self.polyline(*poly)
|
|
|
|
draw_rest_of_edge()
|
2017-02-06 22:35:26 +01:00
|
|
|
|
2023-01-23 20:08:04 +01:00
|
|
|
def margin(self) -> float:
|
2017-02-06 22:35:26 +01:00
|
|
|
if self.reversed:
|
2023-01-23 20:08:05 +01:00
|
|
|
return 0.0
|
|
|
|
return 1 * (self.settings.pin_height + self.settings.hinge_strength)
|
2017-02-06 22:35:26 +01:00
|
|
|
|
2023-01-23 20:08:04 +01:00
|
|
|
def startwidth(self) -> float:
|
2017-02-06 22:35:26 +01:00
|
|
|
if self.reversed:
|
2023-01-23 20:08:03 +01:00
|
|
|
return self.settings.pin_height + self.settings.hinge_strength
|
2023-01-23 20:08:05 +01:00
|
|
|
return 0.0
|
2017-02-06 22:35:26 +01:00
|
|
|
|
2023-01-23 20:08:04 +01:00
|
|
|
def endwidth(self) -> float:
|
2017-02-06 22:35:26 +01:00
|
|
|
if self.reversed:
|
2023-01-23 20:08:05 +01:00
|
|
|
return 0.0
|
2023-01-23 20:08:03 +01:00
|
|
|
return self.settings.pin_height + self.settings.hinge_strength
|
|
|
|
|
2017-02-06 22:35:26 +01:00
|
|
|
|
|
|
|
class ChestHingeTop(ChestHinge):
|
2023-01-02 00:32:42 +01:00
|
|
|
"""Edge above a chest hinge"""
|
2017-02-06 22:35:26 +01:00
|
|
|
|
|
|
|
char = "p"
|
|
|
|
|
2023-01-23 20:08:04 +01:00
|
|
|
def __init__(self, boxes, settings=None, reversed: bool = False) -> None:
|
2023-01-13 15:32:33 +01:00
|
|
|
super().__init__(boxes, settings)
|
2017-02-06 22:35:26 +01:00
|
|
|
|
|
|
|
self.reversed = reversed
|
|
|
|
self.char = "oO"[reversed]
|
|
|
|
self.description = self.description + (' (start)', ' (end)')[reversed]
|
|
|
|
|
|
|
|
def __call__(self, l, **kw):
|
|
|
|
t = self.settings.thickness
|
|
|
|
p = self.settings.pin_height
|
|
|
|
s = self.settings.hinge_strength
|
2017-12-09 14:32:25 +01:00
|
|
|
play = self.settings.play
|
2022-08-10 20:19:52 +02:00
|
|
|
|
|
|
|
if self.settings.finger_joints_on_lid:
|
2023-01-23 20:08:03 +01:00
|
|
|
final_segment = t - s - play
|
|
|
|
draw_rest_of_edge = lambda: self.edges["F"](l - p)
|
2022-08-10 20:19:52 +02:00
|
|
|
else:
|
2023-01-23 20:08:03 +01:00
|
|
|
final_segment = l + t - p - s - play
|
|
|
|
draw_rest_of_edge = lambda: None
|
2022-08-10 20:19:52 +02:00
|
|
|
|
2023-01-23 20:08:03 +01:00
|
|
|
poly = (0, -180, t, -180, 0, (-90, p + s + play), 0, 90, final_segment)
|
2022-08-10 20:19:52 +02:00
|
|
|
|
2017-02-06 22:35:26 +01:00
|
|
|
if self.reversed:
|
2022-08-10 20:19:52 +02:00
|
|
|
draw_rest_of_edge()
|
|
|
|
self.polyline(*reversed(poly))
|
|
|
|
else:
|
|
|
|
self.polyline(*poly)
|
|
|
|
draw_rest_of_edge()
|
2017-02-06 22:35:26 +01:00
|
|
|
|
2023-01-23 20:08:04 +01:00
|
|
|
def startwidth(self) -> float:
|
2017-12-07 22:24:15 +01:00
|
|
|
if self.reversed:
|
2023-01-23 20:08:03 +01:00
|
|
|
return self.settings.play + self.settings.pin_height + self.settings.hinge_strength
|
2023-01-23 20:08:05 +01:00
|
|
|
return 0.0
|
2017-12-07 22:24:15 +01:00
|
|
|
|
2023-01-23 20:08:04 +01:00
|
|
|
def endwidth(self) -> float:
|
2017-12-07 22:24:15 +01:00
|
|
|
if self.reversed:
|
2023-01-23 20:08:05 +01:00
|
|
|
return 0.0
|
2023-01-23 20:08:03 +01:00
|
|
|
return self.settings.play + self.settings.pin_height + self.settings.hinge_strength
|
2017-12-07 22:24:15 +01:00
|
|
|
|
2023-01-23 20:08:04 +01:00
|
|
|
def margin(self) -> float:
|
2017-02-06 22:35:26 +01:00
|
|
|
if self.reversed:
|
2023-01-23 20:08:05 +01:00
|
|
|
return 0.0
|
|
|
|
return 1 * (self.settings.play + self.settings.pin_height + self.settings.hinge_strength)
|
2017-02-06 22:35:26 +01:00
|
|
|
|
|
|
|
|
2023-01-23 20:08:03 +01:00
|
|
|
class ChestHingePin(BaseEdge):
|
2018-12-27 17:12:13 +01:00
|
|
|
description = "Edge with pins for an chest hinge"
|
2017-02-06 22:35:26 +01:00
|
|
|
|
|
|
|
char = "q"
|
|
|
|
|
|
|
|
def __call__(self, l, **kw):
|
|
|
|
t = self.settings.thickness
|
|
|
|
p = self.settings.pin_height
|
|
|
|
s = self.settings.hinge_strength
|
|
|
|
pinh = self.settings.pinheight()
|
2022-08-10 20:19:52 +02:00
|
|
|
|
|
|
|
if self.settings.finger_joints_on_lid:
|
|
|
|
middle_segment = [0]
|
2023-01-23 20:08:03 +01:00
|
|
|
draw_rest_of_edge = lambda: self.edges["F"](l + 2 * t)
|
2022-08-10 20:19:52 +02:00
|
|
|
else:
|
2023-01-23 20:08:03 +01:00
|
|
|
middle_segment = [l + 2 * t, ]
|
|
|
|
draw_rest_of_edge = lambda: None
|
2022-08-10 20:19:52 +02:00
|
|
|
|
2023-01-23 20:08:03 +01:00
|
|
|
poly = [0, -90, s + p - pinh, -90, t, 90, pinh, 90, ]
|
2017-02-06 22:35:26 +01:00
|
|
|
self.polyline(*poly)
|
2022-08-10 20:19:52 +02:00
|
|
|
draw_rest_of_edge()
|
|
|
|
self.polyline(*(middle_segment + list(reversed(poly))))
|
2017-02-06 22:35:26 +01:00
|
|
|
|
2023-01-23 20:08:04 +01:00
|
|
|
def margin(self) -> float:
|
2023-01-23 20:08:03 +01:00
|
|
|
return (self.settings.pin_height + self.settings.hinge_strength)
|
2017-02-06 22:35:26 +01:00
|
|
|
|
|
|
|
|
2018-12-27 17:12:13 +01:00
|
|
|
class ChestHingeFront(Edge):
|
|
|
|
description = "Edge opposing a chest hinge"
|
2017-02-06 22:35:26 +01:00
|
|
|
|
|
|
|
char = "Q"
|
|
|
|
|
2023-01-23 20:08:04 +01:00
|
|
|
def startwidth(self) -> float:
|
2023-01-23 20:08:03 +01:00
|
|
|
return self.settings.pin_height + self.settings.hinge_strength
|
|
|
|
|
2016-08-17 15:07:41 +02:00
|
|
|
|
2017-02-10 23:32:23 +01:00
|
|
|
#############################################################################
|
|
|
|
#### Cabinet Hinge
|
|
|
|
#############################################################################
|
|
|
|
|
|
|
|
class CabinetHingeSettings(Settings):
|
|
|
|
"""Settings for Cabinet Hinges
|
|
|
|
Values:
|
|
|
|
|
|
|
|
* absolute_params
|
|
|
|
|
|
|
|
* bore : 3.2 : diameter of the pin hole in mm
|
|
|
|
* eyes_per_hinge : 5 : pieces per hinge
|
2018-05-08 22:02:14 +02:00
|
|
|
* hinges : 2 : number of hinges per edge
|
2018-09-25 23:19:46 +02:00
|
|
|
* style : inside : style of hinge used
|
2019-02-13 09:01:20 +01:00
|
|
|
|
2017-02-10 23:32:23 +01:00
|
|
|
* relative (in multiples of thickness)
|
|
|
|
|
2022-01-07 22:15:05 +01:00
|
|
|
* eye : 1.5 : radius of the eye (multiples of thickness)
|
|
|
|
* play : 0.05 : space between eyes (multiples of thickness)
|
|
|
|
* spacing : 2.0 : minimum space around the hinge (multiples of thickness)
|
2017-02-10 23:32:23 +01:00
|
|
|
"""
|
|
|
|
absolute_params = {
|
|
|
|
"bore": 3.2,
|
2023-01-23 20:08:03 +01:00
|
|
|
"eyes_per_hinge": 5,
|
|
|
|
"hinges": 2,
|
|
|
|
"style": ("inside", "outside"),
|
2017-02-10 23:32:23 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
relative_params = {
|
|
|
|
"eye": 1.5,
|
2023-01-23 20:08:03 +01:00
|
|
|
"play": 0.05,
|
2018-05-08 22:02:14 +02:00
|
|
|
"spacing": 2.0,
|
2017-02-10 23:32:23 +01:00
|
|
|
}
|
|
|
|
|
2023-01-23 20:08:04 +01:00
|
|
|
def edgeObjects(self, boxes, chars: str = "uUvV", add: bool = True):
|
2017-02-10 23:32:23 +01:00
|
|
|
edges = [CabinetHingeEdge(boxes, self),
|
|
|
|
CabinetHingeEdge(boxes, self, top=True),
|
|
|
|
CabinetHingeEdge(boxes, self, angled=True),
|
|
|
|
CabinetHingeEdge(boxes, self, top=True, angled=True),
|
2023-01-23 20:08:03 +01:00
|
|
|
]
|
2017-02-10 23:32:23 +01:00
|
|
|
for e, c in zip(edges, chars):
|
|
|
|
e.char = c
|
|
|
|
return self._edgeObjects(edges, boxes, chars, add)
|
|
|
|
|
2023-01-23 20:08:03 +01:00
|
|
|
|
2017-02-10 23:32:23 +01:00
|
|
|
class CabinetHingeEdge(BaseEdge):
|
|
|
|
"""Edge with cabinet hinges"""
|
|
|
|
|
2018-12-27 17:12:13 +01:00
|
|
|
char = "u"
|
2017-02-10 23:32:23 +01:00
|
|
|
description = "Edge with cabinet hinges"
|
|
|
|
|
2023-01-23 20:08:04 +01:00
|
|
|
def __init__(self, boxes, settings=None, top: bool = False, angled: bool = False) -> None:
|
2023-01-13 15:32:33 +01:00
|
|
|
super().__init__(boxes, settings)
|
2017-02-10 23:32:23 +01:00
|
|
|
self.top = top
|
|
|
|
self.angled = angled
|
2023-01-23 20:08:03 +01:00
|
|
|
self.char = "uUvV"[bool(top) + 2 * bool(angled)]
|
2017-02-10 23:32:23 +01:00
|
|
|
|
2023-01-23 20:08:04 +01:00
|
|
|
def startwidth(self) -> float:
|
2017-02-15 22:34:27 +01:00
|
|
|
return self.settings.thickness if self.top and self.angled else 0.0
|
2017-02-10 23:32:23 +01:00
|
|
|
|
2018-05-08 22:02:14 +02:00
|
|
|
def __poly(self):
|
2017-02-10 23:32:23 +01:00
|
|
|
n = self.settings.eyes_per_hinge
|
2018-05-08 22:02:14 +02:00
|
|
|
p = self.settings.play
|
2017-02-10 23:32:23 +01:00
|
|
|
e = self.settings.eye
|
2017-02-15 22:34:27 +01:00
|
|
|
t = self.settings.thickness
|
2018-05-08 22:02:14 +02:00
|
|
|
spacing = self.settings.spacing
|
2017-02-10 23:32:23 +01:00
|
|
|
|
2018-09-25 23:19:46 +02:00
|
|
|
if self.settings.style == "outside" and self.angled:
|
|
|
|
e = t
|
|
|
|
elif self.angled and not self.top:
|
2017-02-10 23:32:23 +01:00
|
|
|
# move hinge up to leave space for lid
|
|
|
|
e -= t
|
|
|
|
|
|
|
|
if self.top:
|
|
|
|
# start with space
|
2023-01-23 20:08:03 +01:00
|
|
|
poly = [spacing, 90, e + p]
|
2017-02-10 23:32:23 +01:00
|
|
|
else:
|
|
|
|
# start with hinge eye
|
2023-01-23 20:08:03 +01:00
|
|
|
poly = [spacing + p, 90, e + p, 0]
|
2017-02-10 23:32:23 +01:00
|
|
|
for i in range(n):
|
|
|
|
if (i % 2) ^ self.top:
|
|
|
|
# space
|
2022-06-06 19:55:24 +02:00
|
|
|
if i == 0:
|
2023-01-23 20:08:03 +01:00
|
|
|
poly += [-90, t + 2 * p, 90]
|
2022-06-06 19:55:24 +02:00
|
|
|
else:
|
2023-01-23 20:08:03 +01:00
|
|
|
poly += [90, t + 2 * p, 90]
|
2017-02-10 23:32:23 +01:00
|
|
|
else:
|
|
|
|
# hinge eye
|
2023-01-23 20:08:03 +01:00
|
|
|
poly += [t - p, -90, t, -90, t - p]
|
2017-02-10 23:32:23 +01:00
|
|
|
|
|
|
|
if (n % 2) ^ self.top:
|
|
|
|
# stopped with hinge eye
|
2023-01-23 20:08:03 +01:00
|
|
|
poly += [0, e + p, 90, p + spacing]
|
2017-02-10 23:32:23 +01:00
|
|
|
else:
|
|
|
|
# stopped with space
|
2023-01-23 20:08:03 +01:00
|
|
|
poly[-1:] = [-90, e + p, 90, 0 + spacing]
|
2017-02-10 23:32:23 +01:00
|
|
|
|
2023-01-23 20:08:03 +01:00
|
|
|
width = (t + p) * n + p + 2 * spacing
|
2017-02-10 23:32:23 +01:00
|
|
|
|
2018-05-08 22:02:14 +02:00
|
|
|
return poly, width
|
|
|
|
|
|
|
|
def __call__(self, l, **kw):
|
|
|
|
n = self.settings.eyes_per_hinge
|
|
|
|
p = self.settings.play
|
|
|
|
e = self.settings.eye
|
|
|
|
t = self.settings.thickness
|
|
|
|
hn = self.settings.hinges
|
|
|
|
|
|
|
|
poly, width = self.__poly()
|
|
|
|
|
2018-09-25 23:19:46 +02:00
|
|
|
if self.settings.style == "outside" and self.angled:
|
|
|
|
e = t
|
|
|
|
elif self.angled and not self.top:
|
2018-05-08 22:02:14 +02:00
|
|
|
# move hinge up to leave space for lid
|
|
|
|
e -= t
|
|
|
|
|
|
|
|
hn = min(hn, int(l // width))
|
|
|
|
|
|
|
|
if hn == 1:
|
2023-01-23 20:08:03 +01:00
|
|
|
self.edge((l - width) / 2, tabs=2)
|
2018-05-08 22:02:14 +02:00
|
|
|
|
|
|
|
for j in range(hn):
|
|
|
|
for i in range(n):
|
|
|
|
if not (i % 2) ^ self.top:
|
2023-01-23 20:08:03 +01:00
|
|
|
self.rectangularHole(self.settings.spacing + 0.5 * t + p + i * (t + p), e + 2.5 * t, t, t)
|
2018-05-08 22:02:14 +02:00
|
|
|
self.polyline(*poly)
|
|
|
|
if j < (hn - 1):
|
2023-01-23 20:08:03 +01:00
|
|
|
self.edge((l - hn * width) / (hn - 1), tabs=2)
|
2018-05-08 22:02:14 +02:00
|
|
|
|
|
|
|
if hn == 1:
|
2023-01-23 20:08:03 +01:00
|
|
|
self.edge((l - width) / 2, tabs=2)
|
2017-02-10 23:32:23 +01:00
|
|
|
|
2023-01-23 20:08:04 +01:00
|
|
|
def parts(self, move=None) -> None:
|
2017-02-10 23:32:23 +01:00
|
|
|
e, b = self.settings.eye, self.settings.bore
|
2017-02-15 22:34:27 +01:00
|
|
|
t = self.settings.thickness
|
2017-02-10 23:32:23 +01:00
|
|
|
|
2018-05-08 22:02:14 +02:00
|
|
|
n = self.settings.eyes_per_hinge * self.settings.hinges
|
|
|
|
pairs = n // 2 + 2 * (n % 2)
|
|
|
|
|
2018-09-25 23:19:46 +02:00
|
|
|
if self.settings.style == "outside":
|
2023-01-23 20:08:03 +01:00
|
|
|
th = 2 * e + 4 * t
|
|
|
|
tw = n * (max(3 * t, 2 * e) + self.boxes.spacing)
|
2018-09-25 23:19:46 +02:00
|
|
|
else:
|
2023-01-23 20:08:03 +01:00
|
|
|
th = 4 * e + 3 * t + self.boxes.spacing
|
|
|
|
tw = max(e, 2 * t) * pairs
|
2018-09-25 23:19:46 +02:00
|
|
|
|
2022-05-14 10:35:58 +02:00
|
|
|
if self.move(tw, th, move, True, label="hinges"):
|
2018-09-25 23:19:46 +02:00
|
|
|
return
|
2017-02-10 23:32:23 +01:00
|
|
|
|
2018-09-25 23:19:46 +02:00
|
|
|
if self.settings.style == "outside":
|
2023-01-23 20:08:03 +01:00
|
|
|
ax = max(t / 2, e - t)
|
|
|
|
self.moveTo(t + ax)
|
2018-09-25 23:19:46 +02:00
|
|
|
for i in range(n):
|
|
|
|
if self.angled:
|
|
|
|
if i > n // 2:
|
|
|
|
l = 4 * t + ax
|
|
|
|
else:
|
|
|
|
l = 5 * t + ax
|
|
|
|
else:
|
|
|
|
l = 3 * t + e
|
2023-01-23 20:08:03 +01:00
|
|
|
self.hole(0, e, b / 2.0)
|
|
|
|
da = math.asin((t - ax) / e)
|
2018-09-25 23:19:46 +02:00
|
|
|
dad = math.degrees(da)
|
2023-01-23 20:08:03 +01:00
|
|
|
dy = e * (1 - math.cos(da))
|
|
|
|
self.polyline(0, (180 - dad, e), 0, (-90 + dad), dy + l - e, (90, t))
|
2018-09-25 23:19:46 +02:00
|
|
|
self.polyline(0, 90, t, -90, t, 90, t, 90, t, -90, t, -90, t,
|
2023-01-23 20:08:03 +01:00
|
|
|
90, t, 90, (ax + t) - e, -90, l - 3 * t, (90, e))
|
|
|
|
self.moveTo(2 * max(e, 1.5 * t) + self.boxes.spacing)
|
2018-09-25 23:19:46 +02:00
|
|
|
|
2022-05-14 10:35:58 +02:00
|
|
|
self.move(tw, th, move, label="hinges")
|
2017-02-10 23:32:23 +01:00
|
|
|
return
|
|
|
|
|
2023-01-23 20:08:03 +01:00
|
|
|
if e <= 2 * t:
|
2017-02-10 23:32:23 +01:00
|
|
|
if self.angled:
|
2023-01-23 20:08:03 +01:00
|
|
|
corner = [2 * e - t, (90, 2 * t - e), 0, -90, t, (90, e)]
|
2017-02-10 23:32:23 +01:00
|
|
|
else:
|
2023-01-23 20:08:03 +01:00
|
|
|
corner = [2 * e, (90, 2 * t)]
|
2017-02-10 23:32:23 +01:00
|
|
|
else:
|
2023-01-23 20:08:03 +01:00
|
|
|
a = math.asin(2 * t / e)
|
2017-02-10 23:32:23 +01:00
|
|
|
ang = math.degrees(a)
|
2023-01-23 20:08:03 +01:00
|
|
|
corner = [e * (1 - math.cos(a)) + 2 * t, -90 + ang, 0, (180 - ang, e)]
|
|
|
|
self.moveTo(max(e, 2 * t))
|
2018-05-08 22:02:14 +02:00
|
|
|
for i in range(n):
|
2023-01-23 20:08:03 +01:00
|
|
|
self.hole(0, e, b / 2.0)
|
2017-02-10 23:32:23 +01:00
|
|
|
self.polyline(*[0, (180, e), 0, -90, t, 90, t, -90, t, -90, t, 90, t, 90, t, (90, t)] + corner)
|
2023-01-23 20:08:03 +01:00
|
|
|
self.moveTo(self.boxes.spacing, 4 * e + 3 * t + self.boxes.spacing, 180)
|
2017-02-10 23:32:23 +01:00
|
|
|
if i % 2:
|
2023-01-23 20:08:03 +01:00
|
|
|
self.moveTo(2 * max(e, 2 * t) + 2 * self.boxes.spacing)
|
2017-02-10 23:32:23 +01:00
|
|
|
|
2022-05-14 10:35:58 +02:00
|
|
|
self.move(th, tw, move, label="hinges")
|
2017-02-10 23:32:23 +01:00
|
|
|
|
2023-01-23 20:08:03 +01:00
|
|
|
|
2016-10-27 21:33:29 +02:00
|
|
|
#############################################################################
|
2016-11-06 16:20:08 +01:00
|
|
|
#### Slide-on lid
|
2016-10-27 21:33:29 +02:00
|
|
|
#############################################################################
|
|
|
|
|
2023-04-06 20:21:44 +02:00
|
|
|
class SlideOnLidSettings(FingerJointSettings):
|
2016-11-01 14:02:50 +01:00
|
|
|
"""Settings for Slide-on Lids
|
|
|
|
|
2019-07-05 19:49:15 +02:00
|
|
|
Note that edge_width below also determines how much the sides extend above the lid.
|
|
|
|
|
2020-09-30 20:36:14 +02:00
|
|
|
Values:
|
|
|
|
|
|
|
|
* absolute_params
|
|
|
|
|
|
|
|
* second_pin : True : additional pin for better positioning
|
|
|
|
* spring : "both" : position(s) of the extra locking springs in the lid
|
|
|
|
* hole_width : 0 : width of the "finger hole" in mm
|
2016-11-01 14:02:50 +01:00
|
|
|
"""
|
2022-12-31 19:20:55 +01:00
|
|
|
__doc__ += FingerJointSettings.__doc__ or ""
|
2016-11-01 14:02:50 +01:00
|
|
|
|
2016-10-27 21:33:29 +02:00
|
|
|
absolute_params = FingerJointSettings.absolute_params.copy()
|
|
|
|
relative_params = FingerJointSettings.relative_params.copy()
|
|
|
|
|
2023-01-23 20:08:03 +01:00
|
|
|
relative_params.update({
|
2016-11-06 16:20:08 +01:00
|
|
|
"play": 0.05,
|
|
|
|
"finger": 3.0,
|
|
|
|
"space": 2.0,
|
2023-01-23 20:08:03 +01:00
|
|
|
})
|
2016-10-27 21:33:29 +02:00
|
|
|
|
2023-01-23 20:08:03 +01:00
|
|
|
absolute_params.update({
|
2016-11-06 20:08:45 +01:00
|
|
|
"second_pin": True,
|
2018-03-05 20:09:17 +01:00
|
|
|
"spring": ("both", "none", "left", "right"),
|
2020-09-30 20:36:14 +02:00
|
|
|
"hole_width": 0
|
2023-01-23 20:08:03 +01:00
|
|
|
})
|
2016-11-06 20:08:45 +01:00
|
|
|
|
2023-01-23 20:08:04 +01:00
|
|
|
def edgeObjects(self, boxes, chars=None, add: bool = True):
|
2016-11-23 21:11:15 +01:00
|
|
|
edges = [LidEdge(boxes, self),
|
|
|
|
LidHoleEdge(boxes, self),
|
|
|
|
LidRight(boxes, self),
|
|
|
|
LidLeft(boxes, self),
|
|
|
|
LidSideRight(boxes, self),
|
|
|
|
LidSideLeft(boxes, self),
|
2023-01-23 20:08:03 +01:00
|
|
|
]
|
2016-11-23 21:11:15 +01:00
|
|
|
return self._edgeObjects(edges, boxes, chars, add)
|
|
|
|
|
2023-01-23 20:08:03 +01:00
|
|
|
|
2016-10-27 21:33:29 +02:00
|
|
|
class LidEdge(FingerJointEdge):
|
|
|
|
char = "l"
|
2019-07-05 17:35:51 +02:00
|
|
|
description = "Edge for slide on lid (back)"
|
2016-10-27 21:33:29 +02:00
|
|
|
|
2020-09-30 20:36:14 +02:00
|
|
|
def __call__(self, length, bedBolts=None, bedBoltSettings=None, **kw):
|
2022-12-28 18:14:57 +01:00
|
|
|
hole_width = self.settings.hole_width
|
2020-09-30 20:36:14 +02:00
|
|
|
if hole_width > 0:
|
|
|
|
super().__call__((length - hole_width) / 2)
|
|
|
|
GroovedEdgeBase.groove_arc(self, hole_width)
|
|
|
|
super().__call__((length - hole_width) / 2)
|
|
|
|
else:
|
|
|
|
super().__call__(length)
|
|
|
|
|
2023-01-23 20:08:03 +01:00
|
|
|
|
2016-10-27 21:33:29 +02:00
|
|
|
class LidHoleEdge(FingerHoleEdge):
|
|
|
|
char = "L"
|
2019-07-05 17:35:51 +02:00
|
|
|
description = "Edge for slide on lid (box back)"
|
2016-10-27 21:33:29 +02:00
|
|
|
|
2023-01-23 20:08:04 +01:00
|
|
|
def __call__(self, length, bedBolts=None, bedBoltSettings=None, **kw) -> None:
|
2022-12-28 18:14:57 +01:00
|
|
|
hole_width = self.settings.hole_width
|
2020-09-30 20:36:14 +02:00
|
|
|
if hole_width > 0:
|
|
|
|
super().__call__((length - hole_width) / 2)
|
|
|
|
self.edge(hole_width)
|
|
|
|
super().__call__((length - hole_width) / 2)
|
|
|
|
else:
|
|
|
|
super().__call__(length)
|
|
|
|
|
2023-01-23 20:08:03 +01:00
|
|
|
|
2016-10-27 21:33:29 +02:00
|
|
|
class LidRight(BaseEdge):
|
|
|
|
char = "n"
|
|
|
|
description = "Edge for slide on lid (right)"
|
|
|
|
rightside = True
|
|
|
|
|
|
|
|
def __call__(self, length, **kw):
|
|
|
|
t = self.boxes.thickness
|
2016-11-06 20:08:45 +01:00
|
|
|
|
|
|
|
if self.rightside:
|
|
|
|
spring = self.settings.spring in ("right", "both")
|
|
|
|
else:
|
|
|
|
spring = self.settings.spring in ("left", "both")
|
|
|
|
|
|
|
|
if spring:
|
2023-01-23 20:08:03 +01:00
|
|
|
l = min(6 * t, length - 2 * t)
|
2016-11-06 20:08:45 +01:00
|
|
|
a = 30
|
|
|
|
sqt = 0.4 * t / math.cos(math.radians(a))
|
|
|
|
sw = 0.5 * t
|
2023-01-23 20:08:03 +01:00
|
|
|
p = [0, 90, 1.5 * t + sw, -90, l, (-180, 0.25 * t), l - 0.2 * t, 90, sw, 90 - a, sqt, 2 * a, sqt, -a, length - t]
|
2016-11-06 20:08:45 +01:00
|
|
|
else:
|
2023-01-23 20:08:03 +01:00
|
|
|
p = [t, 90, t, -90, length - t]
|
2016-11-06 20:08:45 +01:00
|
|
|
|
2016-10-27 21:33:29 +02:00
|
|
|
pin = self.settings.second_pin
|
|
|
|
|
|
|
|
if pin:
|
2023-01-23 20:08:03 +01:00
|
|
|
pinl = 2 * t
|
|
|
|
p[-1:] = [length - 2 * t - pinl, -90, t, 90, pinl, 90, t, -90, t]
|
2016-10-27 21:33:29 +02:00
|
|
|
|
|
|
|
if not self.rightside:
|
|
|
|
p = list(reversed(p))
|
|
|
|
self.polyline(*p)
|
|
|
|
|
2023-01-23 20:08:04 +01:00
|
|
|
def startwidth(self) -> float:
|
2023-01-23 20:08:03 +01:00
|
|
|
if self.rightside: # or self.settings.second_pin:
|
2016-10-27 21:33:29 +02:00
|
|
|
return self.boxes.thickness
|
2023-01-23 20:08:05 +01:00
|
|
|
return 0.0
|
2016-10-27 21:33:29 +02:00
|
|
|
|
2023-01-23 20:08:04 +01:00
|
|
|
def endwidth(self) -> float:
|
2023-01-23 20:08:03 +01:00
|
|
|
if not self.rightside: # or self.settings.second_pin:
|
2016-10-27 21:33:29 +02:00
|
|
|
return self.boxes.thickness
|
2023-01-23 20:08:05 +01:00
|
|
|
return 0.0
|
2016-10-27 21:33:29 +02:00
|
|
|
|
2023-01-23 20:08:04 +01:00
|
|
|
def margin(self) -> float:
|
2023-01-23 20:08:03 +01:00
|
|
|
if not self.rightside: # and not self.settings.second_pin:
|
2016-10-27 21:33:29 +02:00
|
|
|
return self.boxes.thickness
|
2023-01-23 20:08:05 +01:00
|
|
|
return 0.0
|
2016-10-27 21:33:29 +02:00
|
|
|
|
2023-01-23 20:08:03 +01:00
|
|
|
|
2016-10-27 21:33:29 +02:00
|
|
|
class LidLeft(LidRight):
|
|
|
|
char = "m"
|
|
|
|
description = "Edge for slide on lid (left)"
|
|
|
|
rightside = False
|
|
|
|
|
2023-01-23 20:08:03 +01:00
|
|
|
|
2016-10-27 21:33:29 +02:00
|
|
|
class LidSideRight(BaseEdge):
|
|
|
|
char = "N"
|
|
|
|
description = "Edge for slide on lid (box right)"
|
|
|
|
|
|
|
|
rightside = True
|
2016-11-06 20:08:45 +01:00
|
|
|
|
2023-01-23 20:08:03 +01:00
|
|
|
def __call__(self, length, **kw):
|
2016-10-27 21:33:29 +02:00
|
|
|
t = self.boxes.thickness
|
|
|
|
s = self.settings.play
|
|
|
|
pin = self.settings.second_pin
|
2019-07-05 19:49:15 +02:00
|
|
|
edge_width = self.settings.edge_width
|
2023-01-23 20:08:03 +01:00
|
|
|
r = edge_width / 3
|
2016-10-27 21:33:29 +02:00
|
|
|
|
2016-11-06 20:08:45 +01:00
|
|
|
if self.rightside:
|
|
|
|
spring = self.settings.spring in ("right", "both")
|
|
|
|
else:
|
|
|
|
spring = self.settings.spring in ("left", "both")
|
|
|
|
|
|
|
|
if spring:
|
2023-01-23 20:08:03 +01:00
|
|
|
p = [s, -90, t + s, -90, t + s, 90, edge_width - s / 2, 90, length + t]
|
2016-11-06 20:08:45 +01:00
|
|
|
else:
|
2023-01-23 20:08:03 +01:00
|
|
|
p = [t + s, -90, t + s, -90, 2 * t + s, 90, edge_width - s / 2, 90, length + t]
|
2016-10-27 21:33:29 +02:00
|
|
|
|
|
|
|
if pin:
|
2023-01-23 20:08:03 +01:00
|
|
|
pinl = 2 * t
|
|
|
|
p[-1:] = [p[-1] - 1.5 * t - 2 * pinl - r, (90, r), edge_width + t + s / 2 - r, -90, 2 * pinl + s + 0.5 * t, -90, t + s, -90,
|
|
|
|
pinl - r, (90, r), edge_width - s / 2 - 2 * r, (90, r), pinl + t - s - r]
|
2016-11-06 20:08:45 +01:00
|
|
|
|
|
|
|
holex = 0.6 * t
|
2023-01-23 20:08:03 +01:00
|
|
|
holey = -0.5 * t + self.burn - s / 2
|
2016-11-06 20:08:45 +01:00
|
|
|
if self.rightside:
|
2016-10-27 21:33:29 +02:00
|
|
|
p = list(reversed(p))
|
2016-11-06 20:08:45 +01:00
|
|
|
holex = length - holex
|
2023-01-23 20:08:03 +01:00
|
|
|
holey = edge_width + 0.5 * t + self.burn
|
2016-11-06 20:08:45 +01:00
|
|
|
|
|
|
|
if spring:
|
2023-01-23 20:08:03 +01:00
|
|
|
self.rectangularHole(holex, holey, 0.4 * t, t + 2 * s)
|
2016-10-27 21:33:29 +02:00
|
|
|
self.polyline(*p)
|
|
|
|
|
2023-01-23 20:08:04 +01:00
|
|
|
def startwidth(self) -> float:
|
2019-07-05 23:59:22 +02:00
|
|
|
return self.boxes.thickness + self.settings.edge_width if self.rightside else -self.settings.play / 2
|
2016-10-27 21:33:29 +02:00
|
|
|
|
2023-01-23 20:08:04 +01:00
|
|
|
def endwidth(self) -> float:
|
2019-07-05 23:59:22 +02:00
|
|
|
return self.boxes.thickness + self.settings.edge_width if not self.rightside else -self.settings.play / 2
|
2016-10-27 21:33:29 +02:00
|
|
|
|
2023-01-23 20:08:04 +01:00
|
|
|
def margin(self) -> float:
|
2019-07-05 23:59:22 +02:00
|
|
|
return self.boxes.thickness + self.settings.edge_width + self.settings.play / 2 if not self.rightside else 0.0
|
2016-10-27 21:33:29 +02:00
|
|
|
|
2023-01-23 20:08:03 +01:00
|
|
|
|
2016-10-27 21:33:29 +02:00
|
|
|
class LidSideLeft(LidSideRight):
|
|
|
|
char = "M"
|
|
|
|
description = "Edge for slide on lid (box left)"
|
|
|
|
rightside = False
|
|
|
|
|
2023-01-23 20:08:03 +01:00
|
|
|
|
2016-05-03 22:56:35 +02:00
|
|
|
#############################################################################
|
|
|
|
#### Click Joints
|
|
|
|
#############################################################################
|
|
|
|
|
|
|
|
class ClickSettings(Settings):
|
2016-11-01 14:02:50 +01:00
|
|
|
"""Settings for Click-on Lids
|
2020-08-04 23:42:12 +02:00
|
|
|
Values:
|
2016-11-01 14:02:50 +01:00
|
|
|
|
2020-08-04 23:42:12 +02:00
|
|
|
* absolute_params
|
|
|
|
|
|
|
|
* angle : 5.0 : angle of the hooks bending outward
|
|
|
|
|
|
|
|
* relative (in multiples of thickness)
|
|
|
|
|
2022-01-07 22:15:05 +01:00
|
|
|
* depth : 3.0 : length of the hooks (multiples of thickness)
|
|
|
|
* bottom_radius : 0.1 : radius at the bottom (multiples of thickness)
|
2020-08-04 23:42:12 +02:00
|
|
|
"""
|
2016-11-01 14:02:50 +01:00
|
|
|
|
2016-05-03 22:56:35 +02:00
|
|
|
absolute_params = {
|
2020-08-04 23:42:12 +02:00
|
|
|
"angle": 5.0,
|
2016-08-17 15:07:41 +02:00
|
|
|
}
|
2016-05-03 22:56:35 +02:00
|
|
|
|
|
|
|
relative_params = {
|
2016-08-17 15:07:41 +02:00
|
|
|
"depth": 3.0,
|
|
|
|
"bottom_radius": 0.1,
|
|
|
|
}
|
|
|
|
|
2023-01-23 20:08:04 +01:00
|
|
|
def edgeObjects(self, boxes, chars: str = "cC", add: bool = True):
|
2016-11-23 21:11:15 +01:00
|
|
|
edges = [ClickConnector(boxes, self),
|
|
|
|
ClickEdge(boxes, self)]
|
|
|
|
return self._edgeObjects(edges, boxes, chars, add)
|
2016-05-03 22:56:35 +02:00
|
|
|
|
2023-01-23 20:08:03 +01:00
|
|
|
|
2016-05-03 22:56:35 +02:00
|
|
|
class ClickConnector(BaseEdge):
|
|
|
|
char = "c"
|
|
|
|
description = "Click on (bottom side)"
|
|
|
|
|
2023-01-23 20:08:04 +01:00
|
|
|
def hook(self, reverse: bool = False) -> None:
|
2017-02-15 22:34:27 +01:00
|
|
|
t = self.settings.thickness
|
2016-05-03 22:56:35 +02:00
|
|
|
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))
|
|
|
|
|
2016-08-17 15:07:41 +02:00
|
|
|
p1 = (0, 90 - a, c * d)
|
2016-05-03 22:56:35 +02:00
|
|
|
p2 = (
|
2016-08-17 15:07:41 +02:00
|
|
|
d + t,
|
2016-05-03 22:56:35 +02:00
|
|
|
-90,
|
2016-08-17 15:07:41 +02:00
|
|
|
t * 0.5,
|
2016-05-03 22:56:35 +02:00
|
|
|
135,
|
2016-08-17 15:07:41 +02:00
|
|
|
t * 2 ** 0.5,
|
2016-05-03 22:56:35 +02:00
|
|
|
135,
|
2016-08-17 15:07:41 +02:00
|
|
|
d + 2 * t + s * 0.5 * t)
|
|
|
|
p3 = (c * d - s * c * 0.2 * t, -a, 0)
|
2016-05-03 22:56:35 +02:00
|
|
|
|
|
|
|
if not reverse:
|
|
|
|
self.polyline(*p1)
|
|
|
|
self.corner(-180, r)
|
|
|
|
self.polyline(*p2)
|
2016-08-17 15:07:41 +02:00
|
|
|
self.corner(-180 + 2 * a, r)
|
2016-05-03 22:56:35 +02:00
|
|
|
self.polyline(*p3)
|
|
|
|
else:
|
|
|
|
self.polyline(*reversed(p3))
|
2016-08-17 15:07:41 +02:00
|
|
|
self.corner(-180 + 2 * a, r)
|
2016-05-03 22:56:35 +02:00
|
|
|
self.polyline(*reversed(p2))
|
|
|
|
self.corner(-180, r)
|
|
|
|
self.polyline(*reversed(p1))
|
|
|
|
|
|
|
|
def hookWidth(self):
|
2017-02-15 22:34:27 +01:00
|
|
|
t = self.settings.thickness
|
2016-05-03 22:56:35 +02:00
|
|
|
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))
|
2016-08-17 15:07:41 +02:00
|
|
|
|
2016-05-03 22:56:35 +02:00
|
|
|
return 2 * s * d * c + 0.5 * c * t + c * 4 * r
|
|
|
|
|
|
|
|
def hookOffset(self):
|
|
|
|
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))
|
2016-08-17 15:07:41 +02:00
|
|
|
|
2016-05-03 22:56:35 +02:00
|
|
|
return s * d * c + 2 * r
|
|
|
|
|
2023-01-23 20:08:04 +01:00
|
|
|
def finger(self, length) -> None:
|
2017-02-15 22:34:27 +01:00
|
|
|
t = self.settings.thickness
|
2016-05-03 22:56:35 +02:00
|
|
|
self.polyline(
|
2016-08-17 15:07:41 +02:00
|
|
|
2 * t,
|
2016-05-03 22:56:35 +02:00
|
|
|
90,
|
|
|
|
length,
|
|
|
|
90,
|
2016-08-17 15:07:41 +02:00
|
|
|
2 * t,
|
2016-05-03 22:56:35 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
def __call__(self, length, **kw):
|
2017-02-15 22:34:27 +01:00
|
|
|
t = self.settings.thickness
|
2016-08-17 15:07:41 +02:00
|
|
|
self.edge(4 * t)
|
2016-05-03 22:56:35 +02:00
|
|
|
self.hook()
|
2016-08-17 15:07:41 +02:00
|
|
|
self.finger(2 * t)
|
2016-05-03 22:56:35 +02:00
|
|
|
self.hook(reverse=True)
|
|
|
|
|
2017-12-11 18:48:33 +01:00
|
|
|
self.edge(length - 2 * (6 * t + 2 * self.hookWidth()), tabs=2)
|
2016-05-03 22:56:35 +02:00
|
|
|
|
|
|
|
self.hook()
|
2016-08-17 15:07:41 +02:00
|
|
|
self.finger(2 * t)
|
2016-05-03 22:56:35 +02:00
|
|
|
self.hook(reverse=True)
|
2016-08-17 15:07:41 +02:00
|
|
|
self.edge(4 * t)
|
2016-05-03 22:56:35 +02:00
|
|
|
|
2023-01-23 20:08:04 +01:00
|
|
|
def margin(self) -> float:
|
2017-02-15 22:34:27 +01:00
|
|
|
return 2 * self.settings.thickness
|
2016-05-03 22:56:35 +02:00
|
|
|
|
2016-08-17 15:07:41 +02:00
|
|
|
|
2016-05-03 22:56:35 +02:00
|
|
|
class ClickEdge(ClickConnector):
|
2016-08-17 15:07:41 +02:00
|
|
|
char = "C"
|
2016-05-03 22:56:35 +02:00
|
|
|
description = "Click on (top)"
|
|
|
|
|
2023-01-23 20:08:04 +01:00
|
|
|
def startwidth(self) -> float:
|
2016-05-03 22:56:35 +02:00
|
|
|
return self.boxes.thickness
|
|
|
|
|
2023-01-23 20:08:04 +01:00
|
|
|
def margin(self) -> float:
|
2016-07-27 22:19:32 +02:00
|
|
|
return 0.0
|
2016-05-03 22:56:35 +02:00
|
|
|
|
|
|
|
def __call__(self, length, **kw):
|
2017-02-15 22:34:27 +01:00
|
|
|
t = self.settings.thickness
|
2016-05-03 22:56:35 +02:00
|
|
|
o = self.hookOffset()
|
|
|
|
w = self.hookWidth()
|
|
|
|
p1 = (
|
2016-08-17 15:07:41 +02:00
|
|
|
4 * t + o,
|
2016-05-03 22:56:35 +02:00
|
|
|
90,
|
|
|
|
t,
|
|
|
|
-90,
|
2016-08-17 15:07:41 +02:00
|
|
|
2 * (t + w - o),
|
2016-05-03 22:56:35 +02:00
|
|
|
-90,
|
|
|
|
t,
|
|
|
|
90,
|
|
|
|
0)
|
|
|
|
self.polyline(*p1)
|
2017-12-11 18:48:33 +01:00
|
|
|
self.edge(length - 2 * (6 * t + 2 * w) + 2 * o, tabs=2)
|
2016-05-03 22:56:35 +02:00
|
|
|
self.polyline(*reversed(p1))
|
|
|
|
|
2016-08-17 15:07:41 +02:00
|
|
|
|
2016-04-09 14:24:32 +02:00
|
|
|
#############################################################################
|
|
|
|
#### Dove Tail Joints
|
|
|
|
#############################################################################
|
2016-08-17 15:07:41 +02:00
|
|
|
|
2016-03-25 14:02:52 +01:00
|
|
|
class DoveTailSettings(Settings):
|
2016-10-31 22:06:52 +01:00
|
|
|
"""Settings for Dove Tail Joints
|
2016-03-28 16:55:41 +02:00
|
|
|
|
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)
|
|
|
|
|
2022-01-07 22:15:05 +01:00
|
|
|
* size : 3 : from one middle of a dove tail to another (multiples of thickness)
|
|
|
|
* depth : 1.5 : how far the dove tails stick out of/into the edge (multiples of thickness)
|
|
|
|
* radius : 0.2 : radius used on all four corners (multiples of thickness)
|
2016-04-04 01:28:02 +02:00
|
|
|
|
|
|
|
"""
|
2016-03-25 14:02:52 +01:00
|
|
|
absolute_params = {
|
2016-08-17 15:07:41 +02:00
|
|
|
"angle": 50,
|
|
|
|
}
|
|
|
|
|
2016-03-25 14:02:52 +01:00
|
|
|
relative_params = {
|
2016-08-17 15:07:41 +02:00
|
|
|
"size": 3,
|
|
|
|
"depth": 1.5,
|
|
|
|
"radius": 0.2,
|
|
|
|
}
|
|
|
|
|
2023-01-23 20:08:04 +01:00
|
|
|
def edgeObjects(self, boxes, chars: str = "dD", add: bool = True):
|
2016-11-23 21:11:15 +01:00
|
|
|
edges = [DoveTailJoint(boxes, self),
|
|
|
|
DoveTailJointCounterPart(boxes, self)]
|
|
|
|
return self._edgeObjects(edges, boxes, chars, add)
|
2016-03-25 14:02:52 +01:00
|
|
|
|
2023-01-23 20:08:03 +01:00
|
|
|
|
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-08-17 15:07:41 +02:00
|
|
|
|
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
|
2016-08-17 15:07:41 +02:00
|
|
|
radius = max(s.radius, self.boxes.burn) # no smaller than burn
|
2016-03-25 14:02:52 +01:00
|
|
|
positive = self.positive
|
|
|
|
a = s.angle + 90
|
2016-08-17 15:07:41 +02:00
|
|
|
alpha = 0.5 * math.pi - math.pi * s.angle / 180.0
|
2016-03-25 14:02:52 +01:00
|
|
|
|
2016-08-17 15:07:41 +02:00
|
|
|
l1 = radius / math.tan(alpha / 2.0)
|
|
|
|
diffx = 0.5 * s.depth / math.tan(alpha)
|
|
|
|
l2 = 0.5 * s.depth / math.sin(alpha)
|
2016-03-25 14:02:52 +01:00
|
|
|
|
2016-08-17 15:07:41 +02:00
|
|
|
sections = int((length) // (s.size * 2))
|
|
|
|
leftover = length - sections * s.size * 2
|
2016-03-25 14:02:52 +01:00
|
|
|
|
2022-01-05 18:29:09 +01:00
|
|
|
if sections == 0:
|
|
|
|
self.edge(length)
|
|
|
|
return
|
|
|
|
|
2016-03-25 14:02:52 +01:00
|
|
|
p = 1 if positive else -1
|
|
|
|
|
2017-12-11 18:48:33 +01:00
|
|
|
self.edge((s.size + leftover) / 2.0 + diffx - l1, tabs=1)
|
2016-08-17 15:07:41 +02:00
|
|
|
|
2016-03-25 14:02:52 +01:00
|
|
|
for i in range(sections):
|
2016-08-17 15:07:41 +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)
|
|
|
|
|
2017-12-11 18:48:33 +01:00
|
|
|
self.edge((s.size + leftover) / 2.0 + diffx - l1, tabs=1)
|
2016-03-25 14:02:52 +01:00
|
|
|
|
2023-01-23 20:08:04 +01:00
|
|
|
def margin(self) -> float:
|
2016-03-28 16:55:41 +02:00
|
|
|
""" """
|
2016-07-27 22:19:32 +02:00
|
|
|
return self.settings.depth
|
2016-03-25 14:02:52 +01:00
|
|
|
|
2016-08-17 15:07:41 +02:00
|
|
|
|
2016-03-25 14:02:52 +01:00
|
|
|
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
|
|
|
|
|
2023-01-23 20:08:04 +01:00
|
|
|
def margin(self) -> float:
|
2016-07-27 22:19:32 +02:00
|
|
|
return 0.0
|
2016-03-25 14:02:52 +01:00
|
|
|
|
2016-08-17 15:07:41 +02:00
|
|
|
|
2016-03-25 14:02:52 +01:00
|
|
|
class FlexSettings(Settings):
|
2016-11-01 14:02:50 +01:00
|
|
|
"""Settings for Flex
|
2016-04-04 01:28:02 +02:00
|
|
|
|
|
|
|
Values:
|
|
|
|
|
|
|
|
* absolute
|
|
|
|
|
2023-01-02 00:32:42 +01:00
|
|
|
* stretch : 1.05 : Hint of how much the flex part should be shortened
|
2016-04-04 01:28:02 +02:00
|
|
|
|
|
|
|
* relative (in multiples of thickness)
|
|
|
|
|
2022-01-07 22:15:05 +01:00
|
|
|
* distance : 0.5 : width of the pattern perpendicular to the cuts (multiples of thickness)
|
|
|
|
* connection : 1.0 : width of the gaps in the cuts (multiples of thickness)
|
|
|
|
* width : 5.0 : width of the pattern in direction of the cuts (multiples of thickness)
|
2016-04-04 01:28:02 +02:00
|
|
|
|
|
|
|
"""
|
2016-03-25 14:02:52 +01:00
|
|
|
relative_params = {
|
2016-08-17 15:07:41 +02:00
|
|
|
"distance": 0.5,
|
|
|
|
"connection": 1.0,
|
|
|
|
"width": 5.0,
|
|
|
|
}
|
|
|
|
|
2016-03-25 14:02:52 +01:00
|
|
|
absolute_params = {
|
2016-08-17 15:07:41 +02:00
|
|
|
"stretch": 1.05,
|
|
|
|
}
|
|
|
|
|
2023-01-23 20:08:04 +01:00
|
|
|
def checkValues(self) -> None:
|
2019-12-14 12:33:09 +01:00
|
|
|
if self.distance < 0.01:
|
|
|
|
raise ValueError("Flex Settings: distance parameter must be > 0.01mm")
|
|
|
|
if self.width < 0.1:
|
|
|
|
raise ValueError("Flex Settings: width parameter must be > 0.1mm")
|
2016-03-25 14:02:52 +01:00
|
|
|
|
2023-01-23 20:08:03 +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
|
2016-08-17 15:07:41 +02:00
|
|
|
h += 2 * burn
|
2016-03-25 14:02:52 +01:00
|
|
|
lines = int(x // dist)
|
|
|
|
leftover = x - lines * dist
|
2018-11-05 21:36:05 +01:00
|
|
|
sections = max(int((h - connection) // width), 1)
|
2016-08-17 15:07:41 +02:00
|
|
|
sheight = ((h - connection) / sections) - connection
|
2016-03-25 14:02:52 +01:00
|
|
|
|
2020-09-15 17:42:38 +02:00
|
|
|
self.ctx.stroke()
|
2016-07-09 10:46:14 +02:00
|
|
|
for i in range(1, lines):
|
2016-08-17 15:07:41 +02:00
|
|
|
pos = i * dist + leftover / 2
|
|
|
|
|
2016-03-25 14:02:52 +01:00
|
|
|
if i % 2:
|
|
|
|
self.ctx.move_to(pos, 0)
|
2016-08-17 15:07:41 +02:00
|
|
|
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))
|
|
|
|
|
2016-03-25 14:02:52 +01:00
|
|
|
if not sections % 2:
|
2016-08-17 15:07:41 +02:00
|
|
|
self.ctx.move_to(pos, h - sheight - connection)
|
2016-03-25 14:02:52 +01:00
|
|
|
self.ctx.line_to(pos, h)
|
|
|
|
else:
|
|
|
|
if sections % 2:
|
|
|
|
self.ctx.move_to(pos, h)
|
2016-08-17 15:07:41 +02:00
|
|
|
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))
|
2016-03-25 14:02:52 +01:00
|
|
|
|
|
|
|
else:
|
2016-08-17 15:07:41 +02:00
|
|
|
for j in range(sections // 2):
|
2016-03-25 14:02:52 +01:00
|
|
|
self.ctx.move_to(pos,
|
2016-08-17 15:07:41 +02:00
|
|
|
h - connection - 2 * j * (sheight + connection))
|
|
|
|
self.ctx.line_to(pos, h - 2 * (j + 1) * (sheight + connection))
|
2016-03-25 14:02:52 +01:00
|
|
|
|
2020-09-15 17:42:38 +02:00
|
|
|
self.ctx.stroke()
|
2016-03-25 14:02:52 +01:00
|
|
|
self.ctx.move_to(0, 0)
|
|
|
|
self.ctx.line_to(x, 0)
|
|
|
|
self.ctx.translate(*self.ctx.get_current_point())
|
2016-10-12 22:47:08 +02:00
|
|
|
|
2023-01-23 20:08:03 +01:00
|
|
|
|
2016-10-12 22:47:08 +02:00
|
|
|
class GearSettings(Settings):
|
2018-12-27 11:29:57 +01:00
|
|
|
"""Settings for rack (and pinion) edge
|
|
|
|
Values:
|
|
|
|
* absolute_params
|
|
|
|
|
|
|
|
* dimension : 3.0 : modulus of the gear (in mm)
|
|
|
|
* angle : 20.0 : pressure angle
|
|
|
|
* profile_shift : 20.0 : Profile shift
|
|
|
|
* clearance : 0.0 : clearance
|
|
|
|
"""
|
|
|
|
|
2016-10-12 22:47:08 +02:00
|
|
|
absolute_params = {
|
2023-01-23 20:08:03 +01:00
|
|
|
"dimension": 3.0,
|
|
|
|
"angle": 20.0,
|
|
|
|
"profile_shift": 20.0,
|
|
|
|
"clearance": 0.0,
|
|
|
|
}
|
2016-10-12 22:47:08 +02:00
|
|
|
|
2023-04-07 19:53:20 +02:00
|
|
|
relative_params: dict[str, Any] = {}
|
2016-10-12 22:47:08 +02:00
|
|
|
|
|
|
|
|
2023-01-23 20:08:03 +01:00
|
|
|
class RackEdge(BaseEdge):
|
2016-10-12 22:47:08 +02:00
|
|
|
char = "R"
|
|
|
|
|
2018-12-27 17:12:13 +01:00
|
|
|
description = "Rack (and pinion) Edge"
|
|
|
|
|
2023-01-08 19:41:02 +01:00
|
|
|
def __init__(self, boxes, settings) -> None:
|
2023-01-13 15:32:33 +01:00
|
|
|
super().__init__(boxes, settings)
|
2016-10-12 22:47:08 +02:00
|
|
|
self.gear = gears.Gears(boxes)
|
|
|
|
|
|
|
|
def __call__(self, length, **kw):
|
|
|
|
params = self.settings.values.copy()
|
|
|
|
params["draw_rack"] = True
|
|
|
|
params["rack_base_height"] = -1E-36
|
2023-03-27 21:15:03 +02:00
|
|
|
params["rack_teeth_length"] = int(length // (params["dimension"] * math.pi))
|
|
|
|
params["rack_base_tab"] = (length - (params["rack_teeth_length"]) * params["dimension"] * math.pi) / 2.0
|
2016-10-12 22:47:08 +02:00
|
|
|
s_tmp = self.boxes.spacing
|
|
|
|
self.boxes.spacing = 0
|
|
|
|
self.moveTo(length, 0, 180)
|
|
|
|
self.gear(move="", **params)
|
|
|
|
self.moveTo(0, 0, 180)
|
|
|
|
self.boxes.spacing = s_tmp
|
|
|
|
|
2023-01-23 20:08:04 +01:00
|
|
|
def margin(self) -> float:
|
2016-10-12 22:47:08 +02:00
|
|
|
return self.settings.dimension * 1.1
|
2019-01-06 10:09:31 +01:00
|
|
|
|
2023-01-23 20:08:03 +01:00
|
|
|
|
2019-01-06 10:09:31 +01:00
|
|
|
class RoundedTriangleEdgeSettings(Settings):
|
|
|
|
"""Settings for RoundedTriangleEdge
|
|
|
|
Values:
|
|
|
|
|
|
|
|
* absolute_params
|
|
|
|
|
|
|
|
* height : 150. : height above the wall
|
|
|
|
* radius : 30. : radius of top corner
|
|
|
|
* r_hole : 0. : radius of hole
|
2019-01-08 23:35:44 +01:00
|
|
|
|
|
|
|
* relative (in multiples of thickness)
|
|
|
|
|
2022-01-07 22:15:05 +01:00
|
|
|
* outset : 0 : extend the triangle along the length of the edge (multiples of thickness)
|
2019-01-08 23:35:44 +01:00
|
|
|
|
2019-01-06 10:09:31 +01:00
|
|
|
"""
|
|
|
|
|
|
|
|
absolute_params = {
|
2023-01-23 20:08:03 +01:00
|
|
|
"height": 50.,
|
|
|
|
"radius": 30.,
|
|
|
|
"r_hole": 2.,
|
2019-01-06 10:09:31 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
relative_params = {
|
2023-01-23 20:08:03 +01:00
|
|
|
"outset": 0.,
|
2019-01-06 10:09:31 +01:00
|
|
|
}
|
|
|
|
|
2023-01-23 20:08:04 +01:00
|
|
|
def edgeObjects(self, boxes, chars: str = "t", add: bool = True):
|
2020-11-09 19:09:42 +01:00
|
|
|
edges = [RoundedTriangleEdge(boxes, self),
|
|
|
|
RoundedTriangleFingerHolesEdge(boxes, self)]
|
2019-01-06 10:09:31 +01:00
|
|
|
return self._edgeObjects(edges, boxes, chars, add)
|
|
|
|
|
2023-01-23 20:08:03 +01:00
|
|
|
|
2019-01-06 10:09:31 +01:00
|
|
|
class RoundedTriangleEdge(Edge):
|
|
|
|
"""Makes an 'edge' with a rounded triangular bumpout and
|
|
|
|
optional hole"""
|
|
|
|
description = "Triangle for handle"
|
|
|
|
char = "t"
|
2023-01-23 20:08:03 +01:00
|
|
|
|
2019-01-06 10:09:31 +01:00
|
|
|
def __call__(self, length, **kw):
|
2019-01-08 23:35:44 +01:00
|
|
|
length += 2 * self.settings.outset
|
2019-01-06 10:09:31 +01:00
|
|
|
r = self.settings.radius
|
2023-01-23 20:08:03 +01:00
|
|
|
if r > length / 2:
|
2019-01-06 10:09:31 +01:00
|
|
|
r = length / 2
|
2023-01-23 20:08:03 +01:00
|
|
|
if length - 2 * r < self.settings.height: # avoid division by zero
|
|
|
|
angle = 90 - math.degrees(math.atan(
|
|
|
|
(length - 2 * r) / (2 * self.settings.height)))
|
|
|
|
l = self.settings.height / math.cos(math.radians(90 - angle))
|
2019-01-06 10:09:31 +01:00
|
|
|
else:
|
|
|
|
angle = math.degrees(math.atan(
|
2023-01-23 20:08:03 +01:00
|
|
|
2 * self.settings.height / (length - 2 * r)))
|
|
|
|
l = 0.5 * (length - 2 * r) / math.cos(math.radians(angle))
|
2019-01-08 23:35:44 +01:00
|
|
|
if self.settings.outset:
|
|
|
|
self.polyline(0, -180, self.settings.outset, 90)
|
|
|
|
else:
|
|
|
|
self.corner(-90)
|
2019-01-06 10:09:31 +01:00
|
|
|
if self.settings.r_hole:
|
2023-01-23 20:08:03 +01:00
|
|
|
self.hole(self.settings.height, length / 2., self.settings.r_hole)
|
|
|
|
self.corner(90 - angle, r, tabs=1)
|
2019-02-24 12:16:47 +01:00
|
|
|
self.edge(l, tabs=1)
|
2023-01-23 20:08:03 +01:00
|
|
|
self.corner(2 * angle, r, tabs=1)
|
2019-02-24 12:16:47 +01:00
|
|
|
self.edge(l, tabs=1)
|
2023-01-23 20:08:03 +01:00
|
|
|
self.corner(90 - angle, r, tabs=1)
|
2019-01-08 23:35:44 +01:00
|
|
|
if self.settings.outset:
|
|
|
|
self.polyline(0, 90, self.settings.outset, -180)
|
|
|
|
else:
|
|
|
|
self.corner(-90)
|
2019-01-06 10:09:31 +01:00
|
|
|
|
2023-01-23 20:08:04 +01:00
|
|
|
def margin(self) -> float:
|
2019-01-06 10:09:31 +01:00
|
|
|
return self.settings.height + self.settings.radius
|
2019-06-11 23:11:12 +02:00
|
|
|
|
2020-11-09 19:09:42 +01:00
|
|
|
|
2023-01-23 20:08:03 +01:00
|
|
|
class RoundedTriangleFingerHolesEdge(RoundedTriangleEdge):
|
2020-11-09 19:09:42 +01:00
|
|
|
char = "T"
|
|
|
|
|
2023-01-23 20:08:04 +01:00
|
|
|
def startwidth(self) -> float:
|
2020-11-09 19:09:42 +01:00
|
|
|
return self.settings.thickness
|
|
|
|
|
2023-01-23 20:08:03 +01:00
|
|
|
def __call__(self, length, **kw):
|
|
|
|
self.fingerHolesAt(0, 0.5 * self.settings.thickness, length, 0)
|
2020-11-09 19:09:42 +01:00
|
|
|
super().__call__(length, **kw)
|
2022-06-16 14:59:36 +02:00
|
|
|
|
|
|
|
|
|
|
|
class HandleEdgeSettings(Settings):
|
|
|
|
"""Settings for HandleEdge
|
|
|
|
Values:
|
|
|
|
|
|
|
|
* absolute_params
|
|
|
|
|
|
|
|
* height : 20. : height above the wall in mm
|
|
|
|
* radius : 10. : radius of corners in mm
|
|
|
|
* hole_width : "40:40" : width of hole(s) in percentage of maximum hole width (width of edge - (n+1) * material thickness)
|
|
|
|
* hole_height : 75. : height of hole(s) in percentage of maximum hole height (handle height - 2 * material thickness)
|
|
|
|
* on_sides : True, : added to side panels if checked, to front and back otherwise (only used with top_edge parameter)
|
|
|
|
|
|
|
|
* relative
|
|
|
|
|
|
|
|
* outset : 1. : extend the handle along the length of the edge (multiples of thickness)
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
absolute_params = {
|
2023-01-23 20:08:03 +01:00
|
|
|
"height": 20.,
|
|
|
|
"radius": 10.,
|
|
|
|
"hole_width": "40:40",
|
|
|
|
"hole_height": 75.,
|
2022-06-16 14:59:36 +02:00
|
|
|
"on_sides": True,
|
|
|
|
}
|
|
|
|
|
|
|
|
relative_params = {
|
2023-01-23 20:08:03 +01:00
|
|
|
"outset": 1.,
|
2022-06-16 14:59:36 +02:00
|
|
|
}
|
|
|
|
|
2023-01-23 20:08:04 +01:00
|
|
|
def edgeObjects(self, boxes, chars: str = "yY", add: bool = True):
|
2022-06-16 14:59:36 +02:00
|
|
|
edges = [HandleEdge(boxes, self),
|
|
|
|
HandleHoleEdge(boxes, self)]
|
|
|
|
return self._edgeObjects(edges, boxes, chars, add)
|
|
|
|
|
2023-01-23 20:08:03 +01:00
|
|
|
|
2022-06-16 14:59:36 +02:00
|
|
|
# inspiration came from https://www.thingiverse.com/thing:327393
|
|
|
|
|
|
|
|
class HandleEdge(Edge):
|
|
|
|
"""Extends an 'edge' by adding a rounded bumpout with optional holes"""
|
|
|
|
description = "Handle for e.g. a drawer"
|
|
|
|
char = "y"
|
|
|
|
extra_height = 0.0
|
|
|
|
|
|
|
|
def __call__(self, length, **kw):
|
|
|
|
length += 2 * self.settings.outset
|
|
|
|
extra_height = self.extra_height * self.settings.thickness
|
|
|
|
|
|
|
|
r = self.settings.radius
|
2023-01-23 20:08:03 +01:00
|
|
|
if r > length / 2:
|
2022-06-16 14:59:36 +02:00
|
|
|
r = length / 2
|
2023-01-23 20:08:03 +01:00
|
|
|
if r > self.settings.height:
|
2022-06-16 14:59:36 +02:00
|
|
|
r = self.settings.height
|
|
|
|
|
|
|
|
widths = argparseSections(self.settings.hole_width)
|
|
|
|
|
|
|
|
if self.settings.outset:
|
|
|
|
self.polyline(0, -180, self.settings.outset, 90)
|
|
|
|
else:
|
|
|
|
self.corner(-90)
|
|
|
|
|
|
|
|
if self.settings.hole_height and sum(widths) > 0:
|
|
|
|
if sum(widths) < 100:
|
|
|
|
slot_offset = ((1 - sum(widths) / 100) * (length - (len(widths) + 1) * self.thickness)) / (len(widths) * 2)
|
|
|
|
else:
|
|
|
|
slot_offset = 0
|
|
|
|
|
|
|
|
slot_height = (self.settings.height - 2 * self.thickness) * self.settings.hole_height / 100
|
|
|
|
slot_x = self.thickness + slot_offset
|
|
|
|
|
|
|
|
for w in widths:
|
|
|
|
if sum(widths) > 100:
|
|
|
|
slotwidth = w / sum(widths) * (length - (len(widths) + 1) * self.thickness)
|
|
|
|
else:
|
|
|
|
slotwidth = w / 100 * (length - (len(widths) + 1) * self.thickness)
|
|
|
|
slot_x += slotwidth / 2
|
|
|
|
with self.saved_context():
|
|
|
|
self.moveTo((self.settings.height / 2) + extra_height, slot_x, 0)
|
2023-01-23 20:08:03 +01:00
|
|
|
self.rectangularHole(0, 0, slot_height, slotwidth, slot_height / 2, True, True)
|
2022-06-16 14:59:36 +02:00
|
|
|
slot_x += slotwidth / 2 + slot_offset + self.thickness + slot_offset
|
|
|
|
|
|
|
|
self.edge(self.settings.height - r + extra_height, tabs=1)
|
|
|
|
self.corner(90, r, tabs=1)
|
|
|
|
self.edge(length - 2 * r, tabs=1)
|
|
|
|
self.corner(90, r, tabs=1)
|
|
|
|
self.edge(self.settings.height - r + extra_height, tabs=1)
|
|
|
|
|
|
|
|
if self.settings.outset:
|
|
|
|
self.polyline(0, 90, self.settings.outset, -180)
|
|
|
|
else:
|
|
|
|
self.corner(-90)
|
|
|
|
|
2023-01-23 20:08:04 +01:00
|
|
|
def margin(self) -> float:
|
2022-06-16 14:59:36 +02:00
|
|
|
return self.settings.height
|
|
|
|
|
2023-01-23 20:08:03 +01:00
|
|
|
|
2022-06-16 14:59:36 +02:00
|
|
|
class HandleHoleEdge(HandleEdge):
|
|
|
|
"""Extends an 'edge' by adding a rounded bumpout with optional holes and holes for parallel finger joint"""
|
|
|
|
description = "Handle with holes for parallel finger joint"
|
|
|
|
char = "Y"
|
|
|
|
extra_height = 1.0
|
|
|
|
|
2023-01-23 20:08:03 +01:00
|
|
|
def __call__(self, length, **kw):
|
|
|
|
self.fingerHolesAt(0, -0.5 * self.settings.thickness, length, 0)
|
2022-06-16 14:59:36 +02:00
|
|
|
super().__call__(length, **kw)
|
|
|
|
|
2023-01-23 20:08:04 +01:00
|
|
|
def margin(self) -> float:
|
2023-01-23 20:08:03 +01:00
|
|
|
return self.settings.height + self.extra_height * self.settings.thickness
|