# Copyright (c) 2026, pinkfish
#
# Licensed under the BSD 2-Clause License. See the LICENSE file in the project
# root for the full license text.
# SPDX-License-Identifier: BSD-2-Clause
# LibFile: pybosl2/parts/cubetruss.py
# Pure-Python port of the core of BOSL2's cubetruss.scad: modular cubical truss segments and
# the trusses assembled from them. :class:`TrussSegment` builds one cube segment
# (a hollow cube lightened with octagonal tunnels through all three axes, optionally cross-braced);
# :class:`Truss` tiles a grid of them; :class:`TrussCorner` builds an
# L/T corner truss; :class:`TrussSupport` builds a diagonal support brace;
# :func:`truss_dist` gives a truss's length. Sizes default to the BOSL2 conventions
# (30 mm cube, 3 mm struts, braced).
#
# The clip accessories are ported too: :class:`TrussClip`,
# :class:`TrussFoot`, :class:`TrussJoiner` and
# :class:`TrussUClip`, and the ``clips=`` option on :class:`Truss` (for the
# FRONT/BACK/LEFT/RIGHT faces).
#
# FileSummary: Modular cubical truss segments and trusses.
# DocCategory: Parts library
# FileGroup: BOSL2
"""Modular cubical truss segments and trusses."""
from __future__ import annotations
import math
from typing import TYPE_CHECKING, Any
from pybosl2._edges_lang import Anchor
from pybosl2._helpers import union
from pybosl2.constants import BOTTOM, CENTER
from pybosl2.distributors import DistributableMatrix
from pybosl2.masking import chamfer_edge_mask
from pybosl2.shapes3d import Bosl2Solid, cuboid, prismoid, regular_prism
if TYPE_CHECKING:
from collections.abc import Sequence
__all__ = [
"TrussSegment",
"Truss",
"TrussCorner",
"TrussSupport",
"TrussClip",
"TrussFoot",
"TrussUClip",
"TrussJoiner",
"truss_dist",
]
# BOSL2 defaults ($cubetruss_size / $cubetruss_strut_size / $cubetruss_bracing / clip thickness).
CUBETRUSS_SIZE = 30.0
CUBETRUSS_STRUT_SIZE = 3.0
CUBETRUSS_BRACING = True
CUBETRUSS_CLIP_THICKNESS = 1.6
def _union(shapes: list[Any]) -> Any:
return union(shapes)
def _cmask(length: float, chamfer: float, orient: str | None = None) -> Bosl2Solid:
"""chamfer_edge_mask as a Bosl2Solid, optionally re-oriented (RIGHT -> X axis, BACK -> Y axis)."""
m = Bosl2Solid(chamfer_edge_mask(length=length, chamfer=chamfer))
if orient == "RIGHT":
return m.rotate([0, 90, 0])
if orient == "BACK":
return m.rotate([90, 0, 0])
return m
def _yflip_copy(offset: float) -> Any:
return DistributableMatrix.mirror_copy(v=[0, 1, 0], offset=offset)
def _clip_placement(vec: Sequence[float], extents: Sequence[float]) -> tuple[int, tuple[float, float, float]]:
"""For a face direction *vec*, return (z-rotation, rotated [X,Y,Z] extents) placing a clip.
(BOSL2 rot(from=FWD, to=vec)). Supports the four horizontal cardinal faces.
"""
x, y = float(vec[0]), float(vec[1])
w, length, hh = extents
if y < 0: # FRONT (-Y): FWD itself
return 0, (w, length, hh)
if y > 0: # BACK (+Y)
return 180, (w, length, hh)
if x > 0: # RIGHT (+X)
return 90, (length, w, hh)
if x < 0: # LEFT (-X)
return -90, (length, w, hh)
raise ValueError(f"cubetruss(clips=): unsupported clip direction {vec!r} (use FRONT/BACK/LEFT/RIGHT)")
def _octagon_tunnel(
size: float, strut: float, h: float, fn: int | None = None, fa: float | None = None, fs: float | None = None
) -> Bosl2Solid:
"""Return a long octagonal-prism cutter for the axial lightening tunnels (BOSL2 cylinder($fn=8))."""
oct_d = (min(h, size) - 2 * strut) / math.cos(math.radians(180 / 8))
return regular_prism(8, diameter=oct_d, height=max(h, size) + 1, anchor=CENTER, fn=fn, fa=fa, fs=fs).rotate(
[0, 0, 180 / 8]
)
[docs]
def truss_dist(
cubes: int = 0,
gaps: int = 0,
size: float | None = None,
strut: float | None = None,
) -> float:
"""Return the length of a truss *cubes* long, plus *gaps* extra strut-widths.
Args:
cubes: Number of cubes along the truss.
gaps: Number of extra strut-width gaps.
size: Cube size in mm. Defaults to CUBETRUSS_SIZE (30 mm).
strut: Strut thickness in mm. Defaults to CUBETRUSS_STRUT_SIZE (3 mm).
Returns:
The total length of the truss in mm.
"""
sz = CUBETRUSS_SIZE if size is None else size
st = CUBETRUSS_STRUT_SIZE if strut is None else strut
return cubes * (sz - st) + gaps * st
# ---------------------------------------------------------------------------
# Section: truss classes
# ---------------------------------------------------------------------------
[docs]
class TrussSegment:
"""A single cubetruss cube segment — a hollow cube lightened with octagonal tunnels.
Examples:
A braced segment:
.. pythonscad-example::
from pybosl2.parts.cubetruss import TrussSegment
TrussSegment().show()
"""
def __init__(
self,
size: float | None = None,
strut: float | None = None,
bracing: bool | None = None,
fn: int | None = None,
fa: float | None = None,
fs: float | None = None,
) -> None:
"""Create a cube truss segment.
Args:
size: Cube size in mm. Defaults to CUBETRUSS_SIZE (30 mm).
strut: Strut thickness in mm. Defaults to CUBETRUSS_STRUT_SIZE (3 mm).
bracing: If True, add cross bracing inside the cube. Defaults to CUBETRUSS_BRACING.
fn: Number of fragments for rounded geometry.
fa: Fragment angle for rounded geometry.
fs: Fragment size for rounded geometry.
Returns:
None.
"""
sz = CUBETRUSS_SIZE if size is None else size
st = CUBETRUSS_STRUT_SIZE if strut is None else strut
br = CUBETRUSS_BRACING if bracing is None else bracing
height = sz
crossthick = st / math.sqrt(2)
voffset = 0.333
body = cuboid([sz, sz, height], fn=fn, fa=fa, fs=fs) - cuboid(
[sz - 2 * st, sz - 2 * st, height - 2 * st], fn=fn, fa=fa, fs=fs
)
body = body - _octagon_tunnel(sz, st, height, fn=fn, fa=fa, fs=fs).rotate([90, 0, 0])
body = body - _octagon_tunnel(sz, st, height, fn=fn, fa=fa, fs=fs).rotate([90, 0, 0]).rotate([0, 0, 90])
body = body - _octagon_tunnel(sz, st, height, fn=fn, fa=fa, fs=fs)
if br:
hex_d = (min(height, sz) - 2 * st) / math.cos(math.radians(180 / 6)) - 2 * voffset
for i in (-1, 1):
brace = cuboid([crossthick, (sz - st) * math.sqrt(2), height], fn=fn, fa=fa, fs=fs)
hole = (
regular_prism(6, diameter=hex_d, height=crossthick + 1, anchor=CENTER, fn=fn, fa=fa, fs=fs)
.rotate([0, 0, 180 / 6])
.rotate([0, 90, 0])
.scale([1, 1.3, 1])
.up(i * voffset)
)
body = body | (brace - hole).rotate([0, 0, i * 45])
self._solid: Bosl2Solid = Bosl2Solid(body.shape, size=[sz, sz, sz])
self._size: float = sz
self._strut: float = st
@property
def size(self) -> float:
"""Cube size in mm."""
return self._size
@property
def strut(self) -> float:
"""Strut thickness in mm."""
return self._strut
[docs]
def shape(self) -> Bosl2Solid:
"""Return the segment geometry."""
return self._solid
[docs]
def show(self) -> None:
"""Display the segment in the viewer."""
self._solid.show()
[docs]
class Truss:
"""A truss assembled from a grid of cube segments.
*extents* is the number of cubes long, or an ``[X, Y, Z]`` count. *clips*
adds end clips on the named faces — each a direction vector ``FRONT`` /
``BACK`` / ``LEFT`` / ``RIGHT`` (or a list of them).
Examples:
A 3-long truss with a front clip:
.. pythonscad-example::
from pybosl2.parts.cubetruss import Truss
from pybosl2.constants import FRONT
Truss(extents=3, clips=FRONT).show()
"""
def __init__(
self,
extents: int | Sequence[int] = 6,
clips: Sequence[Sequence[float]] | Sequence[float] | None = None,
bracing: bool | None = None,
size: float | None = None,
strut: float | None = None,
clipthick: float | None = None,
slop: float = 0.0,
fn: int | None = None,
fa: float | None = None,
fs: float | None = None,
) -> None:
"""Create a truss from a grid of segments.
Args:
extents: Number of cubes long, or an [X, Y, Z] count. Defaults to 6.
clips: Direction vector(s) for end clips on the named faces.
bracing: If True, add cross bracing inside each cube.
size: Cube size in mm. Defaults to CUBETRUSS_SIZE (30 mm).
strut: Strut thickness in mm. Defaults to CUBETRUSS_STRUT_SIZE (3 mm).
clipthick: Clip thickness in mm. Defaults to CUBETRUSS_CLIP_THICKNESS (1.6 mm).
slop: Extra clearance for clips.
fn: Number of fragments for rounded geometry.
fa: Fragment angle for rounded geometry.
fs: Fragment size for rounded geometry.
Returns:
None.
"""
sz = CUBETRUSS_SIZE if size is None else size
st = CUBETRUSS_STRUT_SIZE if strut is None else strut
ct = CUBETRUSS_CLIP_THICKNESS if clipthick is None else clipthick
if isinstance(extents, (int, float)):
w, length, hh = 1, int(extents), 1
else:
e = list(extents) + [1] * (3 - len(extents))
w, length, hh = int(e[0]), int(e[1]), int(e[2])
step = sz - st
segs: list[Bosl2Solid] = []
for zrow in range(hh):
for xcol in range(w):
for ycol in range(length):
seg = TrussSegment(size=sz, strut=st, bracing=bracing, fn=fn, fa=fa, fs=fs).shape()
seg = (
seg.up((zrow - (hh - 1) / 2) * step)
.right((xcol - (w - 1) / 2) * step)
.back((ycol - (length - 1) / 2) * step)
)
segs.append(seg)
if clips is not None and ct > 0:
raw: Any = clips
vecs: list[Sequence[float]] = [list(v) for v in raw] if isinstance(raw[0], (list, tuple)) else [list(raw)]
for vec in vecs:
zang, (exx, exy, exz) = _clip_placement(vec, (w, length, hh))
for zrow in range(int(exz)):
clip = TrussClip(
extents=int(exx),
size=sz,
strut=st,
clipthick=ct,
slop=slop,
fn=fn,
fa=fa,
fs=fs,
).shape()
segs.append(
clip.forward((exy * step + st) / 2).up((zrow - (exz - 1) / 2) * step).rotate([0, 0, zang])
)
result = _union(segs)
s = [
truss_dist(w, 1, sz, st),
truss_dist(length, 1, sz, st),
truss_dist(hh, 1, sz, st),
]
self._solid: Bosl2Solid = Bosl2Solid(result.shape, size=s)
self._extents: int | tuple[int, ...] = extents if isinstance(extents, int) else tuple(extents)
@property
def extents(self) -> int | tuple[int, ...]:
"""Grid dimensions."""
return self._extents
[docs]
def shape(self) -> Bosl2Solid:
"""Return the truss geometry."""
return self._solid
[docs]
def show(self) -> None:
"""Display the truss in the viewer."""
self._solid.show()
[docs]
class TrussSupport:
"""A diagonal support truss — a block cut on the diagonal and lightened.
*extents* is the vertical segment count, or an ``[X, Y, Z]`` count.
Examples:
A 2-high support:
.. pythonscad-example::
from pybosl2.parts.cubetruss import TrussSupport
TrussSupport(extents=2).show()
"""
def __init__(
self,
extents: int | Sequence[int] = 1,
size: float | None = None,
strut: float | None = None,
fn: int | None = None,
fa: float | None = None,
fs: float | None = None,
) -> None:
"""Create a diagonal support truss.
Args:
extents: Vertical segment count, or an [X, Y, Z] count. Defaults to 1.
size: Cube size in mm. Defaults to CUBETRUSS_SIZE (30 mm).
strut: Strut thickness in mm. Defaults to CUBETRUSS_STRUT_SIZE (3 mm).
fn: Number of fragments for rounded geometry.
fa: Fragment angle for rounded geometry.
fs: Fragment size for rounded geometry.
Returns:
None.
"""
sz = CUBETRUSS_SIZE if size is None else size
st = CUBETRUSS_STRUT_SIZE if strut is None else strut
if isinstance(extents, (int, float)):
ex, ey, ez = 1, 1, int(extents)
else:
e = [int(x) for x in (list(extents) + [1, 1, 1])[:3]]
ex, ey, ez = e
step = sz - st
w, length, height = step * ex + st, step * ey + st, step * ez + st
v = [0.0, 1.0 / ey, 1.0 / ez]
smax = sz * (max(ex, ey, ez) + 1)
octid = sz - 2 * st
def octprism(length_: float, rot: list[float] | None) -> Bosl2Solid:
p = regular_prism(8, inner_diameter=octid, height=length_, anchor=CENTER, fn=fn, fa=fa, fs=fs).rotate(
[0, 0, 180 / 8]
)
return p.rotate(rot) if rot else p
def hollow_cell() -> Bosl2Solid:
return (
octprism(sz + 1, [0, 90, 0])
| octprism(sz + 1, None)
| cuboid([octid, octid, octid], fn=fn, fa=fa, fs=fs)
)
pieces = []
for mx in DistributableMatrix.xcopies(step, num_copies=ex):
base = cuboid([sz, length, height], fn=fn, fa=fa, fs=fs).half_of(v=v, s=smax)
cells = [
hollow_cell().multmatrix((my @ mz).tolist())
for my in DistributableMatrix.ycopies(step, num_copies=ey)
for mz in DistributableMatrix.zcopies(step, num_copies=ez)
]
holes = _union(cells).half_of(v=v, center=st, s=smax)
ytun = _union(
[
octprism(ey * sz + 1, [90, 0, 0]).multmatrix(mz.tolist())
for mz in DistributableMatrix.zcopies(step, num_copies=ez)
]
)
pieces.append((base - holes - ytun).multmatrix(mx.tolist()))
self._solid: Bosl2Solid = Bosl2Solid(_union(pieces).shape, size=[w, length, height])
[docs]
def shape(self) -> Bosl2Solid:
"""Return the support truss geometry."""
return self._solid
[docs]
def show(self) -> None:
"""Display the support truss in the viewer."""
self._solid.show()
[docs]
class TrussCorner:
"""A corner truss with arms jutting out in one or more directions.
*height* is the central column height in cubes. *extents* is a scalar
(equal arms in +X, +Y and +Z) or a length-≤5 vector.
Examples:
An L-corner:
.. pythonscad-example::
from pybosl2.parts.cubetruss import TrussCorner
TrussCorner(extents=2).show()
"""
def __init__(
self,
height: int = 1,
extents: int | Sequence[int] = 1,
bracing: bool | None = None,
size: float | None = None,
strut: float | None = None,
fn: int | None = None,
fa: float | None = None,
fs: float | None = None,
) -> None:
"""Create a corner truss.
Args:
height: Central column height in cubes. Defaults to 1.
extents: Scalar (equal arms) or length-≤5 vector. Defaults to 1.
bracing: If True, add cross bracing inside each cube.
size: Cube size in mm. Defaults to CUBETRUSS_SIZE (30 mm).
strut: Strut thickness in mm. Defaults to CUBETRUSS_STRUT_SIZE (3 mm).
fn: Number of fragments for rounded geometry.
fa: Fragment angle for rounded geometry.
fs: Fragment size for rounded geometry.
Returns:
None.
"""
sz = CUBETRUSS_SIZE if size is None else size
st = CUBETRUSS_STRUT_SIZE if strut is None else strut
h = int(height)
if isinstance(extents, (int, float)):
exts = [int(extents), int(extents), 0, 0, int(extents)]
else:
exts = [int(x) for x in (list(extents) + [0] * 5)[:5]]
step = sz - st
def seg() -> Bosl2Solid:
return TrussSegment(size=sz, strut=st, bracing=bracing, fn=fn, fa=fa, fs=fs).shape()
segs = [seg().up(step * zcol) for zcol in range(h)]
for d in range(4):
for zcol in range(h):
for i in range(1, exts[d] + 1):
segs.append(seg().right((step + 0.01) * i).up((step + 0.01) * zcol).rotate([0, 0, d * 90]))
for i in range(1, exts[4] + 1):
segs.append(seg().up((step + 0.01) * (i + h - 1)))
result = _union(segs)
s = [
truss_dist(exts[0] + 1 + exts[2], 1, sz, st),
truss_dist(exts[1] + 1 + exts[3], 1, sz, st),
truss_dist(h + exts[4], 1, sz, st),
]
self._solid: Bosl2Solid = Bosl2Solid(result.shape, size=s)
self._height: int = h
@property
def height(self) -> int:
"""Central column height in cubes."""
return self._height
[docs]
def shape(self) -> Bosl2Solid:
"""Return the corner geometry."""
return self._solid
[docs]
def show(self) -> None:
"""Display the corner in the viewer."""
self._solid.show()
[docs]
class TrussClip:
"""A pair of snap clips for the end of a truss.
Examples:
A truss clip:
.. pythonscad-example::
from pybosl2.parts.cubetruss import TrussClip
TrussClip().show()
"""
def __init__(
self,
extents: int = 1,
size: float | None = None,
strut: float | None = None,
clipthick: float | None = None,
slop: float = 0.0,
fn: int | None = None,
fa: float | None = None,
fs: float | None = None,
) -> None:
"""Create a truss clip pair.
Args:
extents: Width in cubes. Defaults to 1.
size: Cube size in mm. Defaults to CUBETRUSS_SIZE (30 mm).
strut: Strut thickness in mm. Defaults to CUBETRUSS_STRUT_SIZE (3 mm).
clipthick: Clip thickness in mm. Defaults to CUBETRUSS_CLIP_THICKNESS (1.6 mm).
slop: Extra clearance for clips.
fn: Number of fragments for rounded geometry.
fa: Fragment angle for rounded geometry.
fs: Fragment size for rounded geometry.
Returns:
None.
"""
sz = CUBETRUSS_SIZE if size is None else size
st = CUBETRUSS_STRUT_SIZE if strut is None else strut
ct = CUBETRUSS_CLIP_THICKNESS if clipthick is None else clipthick
cliplen = st * 2.6
clipheight = min(sz + st, sz / 3 + 2 * st * 2.6)
clipsize = 0.5
def one_clip() -> Bosl2Solid:
hook = prismoid(
[ct, clipheight],
[ct, clipheight - cliplen * 2],
height=cliplen,
fn=fn,
fa=fa,
fs=fs,
).rotate([90, 0, 0])
hook = hook - _cmask(clipheight + 0.1, ct).right(ct / 2)
hook = hook.back(st).right(ct / 2 - 0.01)
if slop > 0:
hook = hook - cuboid([slop, st * 3, sz], fn=fn, fa=fa, fs=fs).forward(st * 3 / 2)
lip = (
prismoid(
[clipheight - cliplen * 2, st / 2],
[clipheight - cliplen * 2 - 2 * clipsize, st / 2],
height=clipsize + 0.01,
fn=fn,
fa=fa,
fs=fs,
)
.rotate([0, -90, 0])
.forward(st * 1.25 + slop)
.right(slop / 2 + 0.01)
)
clip = hook | lip
clip = clip - _cmask(sz + 1, clipsize + ct / 3).scale([1, 1.5, 1]).left(clipsize).forward(st * 1.6)
for mz in DistributableMatrix.zcopies(clipheight - st, num_copies=2):
clip = clip - cuboid([ct * 3, cliplen * 2, st], fn=fn, fa=fa, fs=fs).multmatrix(mz.tolist())
for mz in DistributableMatrix.zcopies(clipheight - 2 * st, num_copies=2):
clip = clip - _cmask(cliplen * 2, ct, orient="BACK").right(ct).multmatrix(mz.tolist())
return clip
pair = _union(
[
one_clip().multmatrix(m.tolist())
for m in DistributableMatrix.xflip_copy(offset=(extents * (sz - st) + st) / 2)
]
)
s_arr = [
extents * (sz - st) + st + 2 * ct,
st * 2,
clipheight - 2 * st,
]
self._solid: Bosl2Solid = Bosl2Solid(pair.shape, size=s_arr)
[docs]
def shape(self) -> Bosl2Solid:
"""Return the clip geometry."""
return self._solid
[docs]
def show(self) -> None:
"""Display the clip in the viewer."""
self._solid.show()
[docs]
class TrussUClip:
"""A U-shaped clip that joins two trusses face to face.
Examples:
A U-clip:
.. pythonscad-example::
from pybosl2.parts.cubetruss import TrussUClip
TrussUClip().show()
"""
def __init__(
self,
dual: bool = True,
size: float | None = None,
strut: float | None = None,
clipthick: float | None = None,
slop: float = 0.0,
fn: int | None = None,
fa: float | None = None,
fs: float | None = None,
) -> None:
"""Create a U-clip.
Args:
dual: If True, create clips on both sides. Defaults to True.
size: Cube size in mm. Defaults to CUBETRUSS_SIZE (30 mm).
strut: Strut thickness in mm. Defaults to CUBETRUSS_STRUT_SIZE (3 mm).
clipthick: Clip thickness in mm. Defaults to CUBETRUSS_CLIP_THICKNESS (1.6 mm).
slop: Extra clearance for clips.
fn: Number of fragments for rounded geometry.
fa: Fragment angle for rounded geometry.
fs: Fragment size for rounded geometry.
Returns:
None.
"""
sz = CUBETRUSS_SIZE if size is None else size
st = CUBETRUSS_STRUT_SIZE if strut is None else strut
ct = CUBETRUSS_CLIP_THICKNESS if clipthick is None else clipthick
clipsize = 0.5
nd = 2 if dual else 1
s_arr = [nd * st + 2 * ct + slop, st + 2 * ct, sz / 3.5]
body = cuboid(s_arr, fn=fn, fa=fa, fs=fs) - cuboid(
[nd * st + slop, st + 2 * ct, sz + 1],
fn=fn,
fa=fa,
fs=fs,
).back(ct)
prism = (
prismoid(
[sz / 3.5, ct * 1.87],
[sz / 3.5, 0.1],
height=clipsize,
anchor=BOTTOM,
fn=fn,
fa=fa,
fs=fs,
)
.back_half()
.rotate([0, -90, 0])
)
clips = _union(
[
prism.multmatrix(m.tolist())
for m in DistributableMatrix.xflip_copy(offset=(1 if dual else 0.5) * st + slop / 2)
]
).back((st + slop) / 2)
self._solid: Bosl2Solid = Bosl2Solid((body | clips).shape, size=s_arr)
[docs]
def shape(self) -> Bosl2Solid:
"""Return the U-clip geometry."""
return self._solid
[docs]
def show(self) -> None:
"""Display the U-clip in the viewer."""
self._solid.show()
[docs]
class TrussJoiner:
"""A joiner that clips two trusses end to end.
Examples:
A truss joiner:
.. pythonscad-example::
from pybosl2.parts.cubetruss import TrussJoiner
TrussJoiner().show()
"""
def __init__(
self,
w: int = 1,
vert: bool = True,
size: float | None = None,
strut: float | None = None,
clipthick: float | None = None,
slop: float = 0.0,
fn: int | None = None,
fa: float | None = None,
fs: float | None = None,
) -> None:
"""Create a truss joiner.
Args:
w: Width in cubes. Defaults to 1.
vert: If True, add vertical supports. Defaults to True.
size: Cube size in mm. Defaults to CUBETRUSS_SIZE (30 mm).
strut: Strut thickness in mm. Defaults to CUBETRUSS_STRUT_SIZE (3 mm).
clipthick: Clip thickness in mm. Defaults to CUBETRUSS_CLIP_THICKNESS (1.6 mm).
slop: Extra clearance for clips.
fn: Number of fragments for rounded geometry.
fa: Fragment angle for rounded geometry.
fs: Fragment size for rounded geometry.
Returns:
None.
"""
sz = CUBETRUSS_SIZE if size is None else size
st = CUBETRUSS_STRUT_SIZE if strut is None else strut
ct = CUBETRUSS_CLIP_THICKNESS if clipthick is None else clipthick
clipsize = 0.5
span = w * (sz - st) + st
parts: list[Bosl2Solid] = [cuboid([span + 2 * ct, sz, ct], fn=fn, fa=fa, fs=fs).up(ct / 2)]
for mx in DistributableMatrix.xcopies(span + ct, num_copies=2):
parts.append(
cuboid([ct, sz, ct + st * 3 / 4], fn=fn, fa=fa, fs=fs).up((ct + st * 3 / 4) / 2).multmatrix(mx.tolist())
)
for my in DistributableMatrix.ycopies(sz, num_copies=2):
parts.append(
TrussFoot(w=w, size=sz, strut=st, clipthick=ct, slop=slop, fn=fn, fa=fa, fs=fs)
.shape()
.up((st + ct) / 2)
.multmatrix(my.tolist())
)
if vert:
for mx in DistributableMatrix.xcopies(span + ct, num_copies=2):
parts.append(
prismoid(
[ct, sz],
[ct, 2 * st + 2 * ct],
height=sz * 0.6,
anchor=BOTTOM,
fn=fn,
fa=fa,
fs=fs,
)
.up(ct - 0.01)
.multmatrix(mx.tolist())
)
wallclip = (
prismoid(
[sz / 3.5, ct * 2],
[sz / 3.5 - 4 * 2 * clipsize, 0.1],
height=2 * clipsize,
anchor=BOTTOM,
fn=fn,
fa=fa,
fs=fs,
)
.back_half()
.rotate([0, -90, 0])
)
for mx in DistributableMatrix.xflip_copy(offset=(span + 0.02) / 2):
for my in _yflip_copy(offset=st + slop / 2):
parts.append(wallclip.multmatrix((mx @ my).tolist()).up(sz / 2))
result = _union(parts).down(ct)
s_arr = [span + 2 * ct, 2 * (sz - st) + st, st + ct]
self._solid: Bosl2Solid = Bosl2Solid(result.shape, size=s_arr)
[docs]
def shape(self) -> Bosl2Solid:
"""Return the joiner geometry."""
return self._solid
[docs]
def show(self) -> None:
"""Display the joiner in the viewer."""
self._solid.show()