448 lines
14 KiB
Python
448 lines
14 KiB
Python
from __future__ import annotations
|
|
|
|
import math
|
|
from typing import Any
|
|
|
|
from boxes import Boxes, edges
|
|
|
|
from .edges import BaseEdge, Settings
|
|
|
|
|
|
class _WallMountedBox(Boxes):
|
|
ui_group = "WallMounted"
|
|
|
|
def __init__(self) -> None:
|
|
super().__init__()
|
|
self.addWallSettingsArgs()
|
|
|
|
def addWallSettingsArgs(self):
|
|
self.addSettingsArgs(edges.FingerJointSettings)
|
|
self.addSettingsArgs(WallSettings)
|
|
self.addSettingsArgs(SlatWallSettings)
|
|
self.addSettingsArgs(DinRailSettings)
|
|
self.addSettingsArgs(FrenchCleatSettings)
|
|
self.argparser.add_argument(
|
|
"--walltype", action="store", type=str, default="plain",
|
|
choices=["plain", "plain reinforced", "slatwall", "dinrail",
|
|
"french cleat"],
|
|
help="Type of wall system to attach to")
|
|
|
|
def generateWallEdges(self):
|
|
if self.walltype.startswith("plain"):
|
|
s = WallSettings(
|
|
self.thickness, True,
|
|
**self.edgesettings.get("Wall", {}))
|
|
elif self.walltype == "slatwall":
|
|
s = SlatWallSettings(
|
|
self.thickness, True,
|
|
**self.edgesettings.get("SlatWall", {}))
|
|
elif self.walltype == "dinrail":
|
|
s = DinRailSettings(
|
|
self.thickness, True,
|
|
**self.edgesettings.get("DinRail", {}))
|
|
elif self.walltype == "french cleat":
|
|
s = FrenchCleatSettings(
|
|
self.thickness, True,
|
|
**self.edgesettings.get("FrenchCleat", {}))
|
|
|
|
s.edgeObjects(self)
|
|
self.wallHolesAt = self.edges["|"]
|
|
if self.walltype.endswith("reinforced"):
|
|
self.edges["c"] = self.edges["d"]
|
|
self.edges["C"] = self.edges["D"]
|
|
|
|
#############################################################################
|
|
#### Straight Edge / Base class
|
|
#############################################################################
|
|
|
|
class WallEdge(BaseEdge):
|
|
|
|
_reversed = False
|
|
|
|
def lengths(self, length):
|
|
return [length]
|
|
|
|
def _joint(self, length):
|
|
self.edge(length)
|
|
|
|
def _section(self, nr, length):
|
|
self.edge(length)
|
|
|
|
def __call__(self, length, **kw):
|
|
lengths = list(enumerate(self.lengths(length)))
|
|
if self._reversed:
|
|
lengths = list(reversed(lengths))
|
|
|
|
for nr, l in lengths:
|
|
if l == 0.0:
|
|
continue
|
|
if nr % 2:
|
|
self._section(nr // 2, l)
|
|
else:
|
|
self._joint(l)
|
|
|
|
class WallJoinedEdge(WallEdge):
|
|
char = "b"
|
|
|
|
def _joint(self, length):
|
|
t = self.settings.thickness
|
|
self.step(-t)
|
|
self.edges["f"](length)
|
|
self.step(t)
|
|
|
|
def startwidth(self) -> float:
|
|
return self.settings.thickness
|
|
|
|
class WallBackEdge(WallEdge):
|
|
|
|
def _section(self, nr, length):
|
|
self.edge(length)
|
|
|
|
def _joint(self, length):
|
|
t = self.settings.thickness
|
|
self.step(t)
|
|
self.edges["F"](length)
|
|
self.step(-t)
|
|
|
|
def margin(self) -> float:
|
|
return self.settings.thickness
|
|
|
|
class WallHoles(WallEdge):
|
|
|
|
def _section(self, nr, length):
|
|
self.rectangularHole(length/2, 0, length, self.settings.thickness)
|
|
self.moveTo(length, 0)
|
|
|
|
def _joint(self, length):
|
|
self.fingerHolesAt(0, 0, length, 0)
|
|
self.moveTo(length, 0)
|
|
|
|
def __call__(self, x, y, length, angle, **kw):
|
|
"""
|
|
Draw holes for a matching WallJoinedEdge
|
|
|
|
:param x: position
|
|
:param y: position
|
|
:param length: length of matching edge
|
|
:param angle: (Default value = 90)
|
|
"""
|
|
with self.boxes.saved_context():
|
|
self.boxes.moveTo(x, y, angle)
|
|
b = self.boxes.burn
|
|
t = self.settings.thickness
|
|
|
|
if self.boxes.debug: # XXX
|
|
width = self.settings.thickness
|
|
self.ctx.rectangle(b, -width / 2 + b,
|
|
length - 2 * b, width - 2 * b)
|
|
|
|
self.boxes.moveTo(length, 0, 180)
|
|
super().__call__(length)
|
|
|
|
class WallHoleEdge(WallHoles):
|
|
"""Edge with holes for a parallel finger joint"""
|
|
description = "Edge (parallel slot wall Holes)"
|
|
|
|
def __init__(self, boxes, wallHoles, **kw) -> None:
|
|
super().__init__(boxes, wallHoles.settings, **kw)
|
|
self.wallHoles = wallHoles
|
|
|
|
def __call__(self, length, bedBolts=None, bedBoltSettings=None, **kw):
|
|
dist = self.wallHoles.settings.edge_width + self.settings.thickness / 2
|
|
with self.saved_context():
|
|
px, angle = (0, 0) if self._reversed else (length, 180)
|
|
self.wallHoles(
|
|
px, dist, length, angle)
|
|
self.edge(length, tabs=2)
|
|
|
|
def startwidth(self) -> float:
|
|
""" """
|
|
return self.wallHoles.settings.edge_width + self.settings.thickness
|
|
|
|
def margin(self) -> float:
|
|
return 0.0
|
|
|
|
class WallSettings(Settings):
|
|
"""Settings for plain WallEdges
|
|
Values:
|
|
|
|
* relative (in multiples of thickness)
|
|
|
|
* edge_width : 1.0 : space below holes of FingerHoleEdge (multiples of thickness)
|
|
|
|
"""
|
|
|
|
absolute_params: dict[str, Any] = {
|
|
}
|
|
|
|
relative_params = {
|
|
"edge_width": 1.0,
|
|
}
|
|
|
|
base_class = WallEdge
|
|
|
|
def edgeObjects(self, boxes, chars="aAbBcCdD|", add=True):
|
|
bc = self.base_class
|
|
bn = bc.__name__
|
|
wallholes = type(bn+"Hole", (WallHoles, bc), {})(boxes, self)
|
|
|
|
edges = [bc(boxes, self),
|
|
type(bn+"Reversed", (bc,), {'_reversed' : True})(boxes, self),
|
|
type(bn+"Joined", (WallJoinedEdge, bc), {})(boxes, self),
|
|
type(bn+"JoinedReversed", (WallJoinedEdge, bc), {'_reversed' : True})(boxes, self),
|
|
type(bn+"Back", (WallBackEdge, bc), {})(boxes, self),
|
|
type(bn+"BackReversed", (WallBackEdge, bc), {'_reversed' : True})(boxes, self),
|
|
type(bn+"Hole", (WallHoleEdge, bc), {})(boxes, wallholes),
|
|
type(bn+"HoleReversed", (WallHoleEdge, bc), {'_reversed' : True})(boxes, wallholes),
|
|
wallholes,
|
|
]
|
|
return self._edgeObjects(edges, boxes, chars, add)
|
|
|
|
#############################################################################
|
|
#### Slat wall
|
|
#############################################################################
|
|
|
|
|
|
class SlatWallEdge(WallEdge):
|
|
|
|
def lengths(self, length):
|
|
pitch = self.settings.pitch
|
|
h = self.settings.hook_height
|
|
he = self.settings.hook_extra_height
|
|
|
|
lengths = []
|
|
if length < h + he:
|
|
return [length]
|
|
lengths = [0, h + he]
|
|
length -= h + he
|
|
if length > pitch:
|
|
lengths.extend([(length // pitch) * pitch - h - 2 - 2*he,
|
|
h + 2 + 2*he,
|
|
length % pitch])
|
|
else:
|
|
lengths.append(length)
|
|
return lengths
|
|
|
|
def _section(self, nr, length):
|
|
w = self.settings.hook_height # vertical width of hook
|
|
hd = self.settings.hook_depth
|
|
hdist = self.settings.hook_distance
|
|
hh = self.settings.hook_overall_height
|
|
ro = w # outer radius
|
|
ri = min(w/2, hd/2) # inner radius
|
|
rt = min(1, hd/2) # top radius
|
|
slot = self.settings.hook_height + 2 # XXX
|
|
if nr == 0:
|
|
poly = [0, -90, hdist-ri, (-90, ri), hh-ri-w-rt, (90, rt),
|
|
hd-2*rt, (90, rt), hh-ro-rt, (90, ro), hdist+hd-ro, -90,
|
|
length-6]
|
|
elif nr == 1:
|
|
if self.settings.bottom_hook == "spring":
|
|
r_plug = slot*.4
|
|
slotslot = slot - r_plug * 2**0.5
|
|
poly = [self.settings.hook_extra_height, -90,
|
|
5.0, -45, 0, (135, r_plug),
|
|
0, 90, 10, -90, slotslot, -90, 10, 90, 0,
|
|
(135, r_plug), 0, -45, 5, -90,
|
|
self.settings.hook_extra_height]
|
|
elif self.settings.bottom_hook == "hook":
|
|
d = 2
|
|
poly = [self.settings.hook_extra_height + d - 1, -90,
|
|
4.5+hd, (90,1), slot-2, (90, 1), hd-1, 90, d,
|
|
-90, 5.5, -90, self.settings.hook_extra_height + 1]
|
|
elif self.settings.bottom_hook == "stud":
|
|
poly = [self.settings.hook_extra_height, -90,
|
|
6, (90, 1) , slot-2, (90, 1), 6, -90,
|
|
self.settings.hook_extra_height]
|
|
else:
|
|
poly = [2*self.settings.hook_extra_height + slot]
|
|
|
|
if self._reversed:
|
|
poly = reversed(poly)
|
|
self.polyline(*poly)
|
|
|
|
def margin(self) -> float:
|
|
return self.settings.hook_depth + self.settings.hook_distance
|
|
|
|
class SlatWallSettings(WallSettings):
|
|
"""Settings for SlatWallEdges
|
|
Values:
|
|
|
|
* absolute_params
|
|
|
|
* bottom_hook : "hook" : "spring", "stud" or "none"
|
|
* pitch : 101.6 : vertical spacing of slots middle to middle (in mm)
|
|
* hook_depth : 4.0 : horizontal width of the hook
|
|
* hook_distance : 5.5 : horizontal space to the hook
|
|
* hook_height : 6.0 : height of the horizontal bar of the hook
|
|
* hook_overall_height : 12.0 : height of the hook top to bottom
|
|
|
|
* relative (in multiples of thickness)
|
|
|
|
* hook_extra_height : 2.0 : space surrounding connectors (multiples of thickness)
|
|
* edge_width : 1.0 : space below holes of FingerHoleEdge (multiples of thickness)
|
|
|
|
"""
|
|
|
|
absolute_params = {
|
|
"bottom_hook" : ("hook", "spring", "stud", "none"),
|
|
"pitch" : 101.6,
|
|
"hook_depth" : 4.0,
|
|
"hook_distance" : 5.5,
|
|
"hook_height" : 6.0,
|
|
"hook_overall_height" : 12.0,
|
|
}
|
|
|
|
relative_params = {
|
|
"hook_extra_height" : 2.0,
|
|
"edge_width": 1.0,
|
|
}
|
|
|
|
base_class = SlatWallEdge
|
|
|
|
|
|
#############################################################################
|
|
#### DIN rail
|
|
#############################################################################
|
|
|
|
class DinRailEdge(WallEdge):
|
|
|
|
def lengths(self, length):
|
|
if length < 20:
|
|
return [length]
|
|
if length > 50 and self.settings.bottom == "stud":
|
|
return [0, 20, length - 40, 20]
|
|
return [0, 20,
|
|
length - 20]
|
|
|
|
def _section(self, nr, length):
|
|
d = self.settings.depth
|
|
|
|
if nr == 0:
|
|
r = 1.
|
|
poly = [0, -90, d-0.5-r, (90, r), 15+3-2*r, (90, r),
|
|
d-4-r, 45,
|
|
4*2**.5, -45, .5, -90, 6]
|
|
elif nr == 1:
|
|
slot = 20
|
|
if self.settings.bottom == "stud":
|
|
r = 1.
|
|
poly = [0, -90, 7.5-r, (90, r),
|
|
slot - 2*r,
|
|
(90, r), 7.5-r, -90, 0]
|
|
else:
|
|
poly = [slot]
|
|
if self._reversed:
|
|
poly = reversed(poly)
|
|
self.polyline(*poly)
|
|
|
|
def margin(self) -> float:
|
|
return self.settings.depth
|
|
|
|
class DinRailSettings(WallSettings):
|
|
"""Settings for DinRailEdges
|
|
Values:
|
|
|
|
* absolute_params
|
|
|
|
* bottom : "stud" : "stud" or "none"
|
|
* depth : 4.0 : horizontal width of the hook
|
|
|
|
* relative (in multiples of thickness)
|
|
|
|
* edge_width : 1.0 : space below holes of FingerHoleEdge (multiples of thickness)
|
|
|
|
"""
|
|
|
|
absolute_params = {
|
|
"bottom" : ("stud", "none"),
|
|
"depth" : 8.0,
|
|
}
|
|
|
|
relative_params = {
|
|
"edge_width": 1.0,
|
|
}
|
|
|
|
base_class = DinRailEdge
|
|
|
|
#############################################################################
|
|
#### French Cleats
|
|
#############################################################################
|
|
|
|
class FrenchCleatEdge(WallEdge):
|
|
|
|
def lengths(self, length):
|
|
d = self.settings.depth
|
|
t = self.settings.thickness
|
|
s = self.settings.spacing
|
|
h = d * math.tan(math.radians(self.settings.angle))
|
|
# make small enough to not have finger holes
|
|
top = 0.5*t
|
|
bottom = 0.5 * t
|
|
if length < top + bottom + 1.5*d + h:
|
|
return [length]
|
|
if length > top + bottom + 2*t + 1.5*d + h and \
|
|
self.settings.bottom == "stud":
|
|
return [top, 1.5*d + h, length - top - bottom - 2.5*d - h,
|
|
d, bottom]
|
|
if length > top + bottom + 2.5*d + s and \
|
|
self.settings.bottom == "hook":
|
|
dist = ((length - top - t - 1.5*d - h) // s ) * s - 1.5*d - h
|
|
return [top, 1.5*d + h, dist, 1.5*d + h, length-dist-top-3*d-2*h]
|
|
return [top, 2.5*d, length-top-2.5*d]
|
|
|
|
def _section(self, nr, length):
|
|
d = self.settings.depth
|
|
t = self.settings.thickness
|
|
r = min(0.5*t, 0.1*d)
|
|
a = self.settings.angle
|
|
h = d * math.tan(math.radians(a))
|
|
l = d / math.cos(math.radians(a))
|
|
|
|
if nr == 0 or self.settings.bottom == "hook":
|
|
poly = [0, -90, 0, (90, d), .5*d+h, 90+a, l, -90-a, length-1.5*d]
|
|
elif nr == 1:
|
|
if self.settings.bottom == "stud":
|
|
r = min(t, length/4, d)
|
|
poly = [0, -90, d-r, (90, r),
|
|
length - 2*r,
|
|
(90, r), d-r, -90, 0]
|
|
else:
|
|
poly = [length]
|
|
if self._reversed:
|
|
poly = reversed(poly)
|
|
self.polyline(*poly)
|
|
|
|
def margin(self) -> float:
|
|
return self.settings.depth
|
|
|
|
class FrenchCleatSettings(WallSettings):
|
|
"""Settings for FrenchCleatEdges
|
|
Values:
|
|
|
|
* absolute_params
|
|
|
|
* bottom : "stud" : "stud", "hook" or "none"
|
|
* depth : 18.0 : horizontal width of the hook in mm
|
|
* angle : 45.0 : angle of the cut (0 for horizontal)
|
|
* spacing : 200.0 : distance of the cleats in mm (for bottom hook)
|
|
|
|
* relative (in multiples of thickness)
|
|
|
|
* edge_width : 1.0 : space below holes of FingerHoleEdge (multiples of thickness)
|
|
|
|
"""
|
|
|
|
absolute_params = {
|
|
"bottom" : ("stud", "hook", "none"),
|
|
"depth" : 18.0,
|
|
"spacing" : 200.0,
|
|
"angle" : 45.0,
|
|
}
|
|
|
|
relative_params = {
|
|
"edge_width": 1.0,
|
|
}
|
|
|
|
base_class = FrenchCleatEdge
|