Skip to content

Code Reference

LockableBox

LockableBox(
    inner_width,
    inner_length,
    inner_height,
    inner_lid_height=None,
    thickness=1.5,
    clearance=0.4,
)

Lockable boxes are simple boxes which can be locked with a padlock. There is an overlap between the body and the lid, which has to be long enough to prevent opening the box when it's locked.

All dimensions are the internal space available in the container.

Parameters:

Name Type Description Default
inner_width float

Inner width. This side will have the lock parts

required
inner_length float

Inner length

required
inner_height float

Inner height

required
inner_lid_height float | None

Optional inner lid height. Defaults to the minimum acceptable height when not specified.

None
thickness float

Optional walls thickness

1.5
clearance float

Optional assembly clearance. A good value is two times the diameter of the printing nozzle

0.4

Raises:

Type Description
BaseException

when provided arguments won't work to create valid solids

Source code in src/bd_el_parts/lockable_box.py
def __init__(
    self,
    inner_width: float,
    inner_length: float,
    inner_height: float,
    inner_lid_height: float | None = None,
    thickness: float = 1.5,
    clearance: float = 0.4,
):
    """
    {% 3d: /models/lockable_box_assembly.gltf}

    Lockable boxes are simple boxes which can be locked with a padlock.
    There is an overlap between the body and the lid, which has to be long enough
    to prevent opening the box when it's locked.

    All dimensions are the internal space available in the container.

    Args:
        inner_width (float): Inner width. This side will have the lock parts
        inner_length (float): Inner length
        inner_height (float): Inner height
        inner_lid_height (float|None): Optional inner lid height. Defaults to the minimum acceptable height when not specified.
        thickness (float): Optional walls thickness
        clearance (float): Optional assembly clearance. A good value is two times the diameter of the printing nozzle

    Raises:
        BaseException: when provided arguments won't work to create valid solids
    """

    min_lid_height = self.LOCK_SIZE + self.LOCK_THICKNESS - self.OVERLAP

    self.inner_width = inner_width
    self.inner_length = inner_length
    self.inner_height = inner_height
    self.inner_lid_height = inner_lid_height if inner_lid_height is not None else min_lid_height
    self.inner_body_height = inner_height - self.inner_lid_height
    self.thickness = thickness
    self.clearance = clearance

    if self.inner_lid_height < min_lid_height:
        raise BaseException(f"inner_lid_height should be >= {min_lid_height}")

    min_body_height = self.OVERLAP + self.LOCK_SIZE + self.LOCK_THICKNESS
    if self.inner_body_height < min_body_height:
        min_inner_height = min_body_height + self.inner_lid_height
        raise BaseException(f"inner_height should be >= {min_inner_height}")

    if self.inner_width < self.LOCK_SIZE:
        raise BaseException(f"inner_width should be >= {self.LOCK_SIZE}")

    self.outer_width = inner_width + thickness * 2
    self.outer_length = inner_length + thickness * 2

LOCK_SIZE class-attribute instance-attribute

LOCK_SIZE = 15

Lock part width, and base height

LOCK_THICKNESS class-attribute instance-attribute

LOCK_THICKNESS = 2

Thickness of the flat part of the lock part

OVERLAP class-attribute instance-attribute

OVERLAP = 5

Overlap between lid and body

clearance instance-attribute

clearance = clearance

Value from initializer: assembly clearance

inner_body_height instance-attribute

inner_body_height = inner_height - inner_lid_height

Computed body height

inner_height instance-attribute

inner_height = inner_height

Value from initializer: inner height

inner_length instance-attribute

inner_length = inner_length

Value from initializer: inner length

inner_lid_height instance-attribute

inner_lid_height = (
    inner_lid_height
    if inner_lid_height is not None
    else min_lid_height
)

Value from initializer: optional lid height

inner_width instance-attribute

inner_width = inner_width

Value from initializer: inner width

outer_length instance-attribute

outer_length = inner_length + thickness * 2

Computed outer length

outer_width instance-attribute

outer_width = inner_width + thickness * 2

Computed outer width

thickness instance-attribute

thickness = thickness

Value from initializer: walls thickness

assembly

assembly()

Creates a compound with constrained box parts

Source code in src/bd_el_parts/lockable_box.py
def assembly(self) -> Compound:
    """
    Creates a compound with constrained box parts
    """
    body = self.body()
    lid = self.lid()
    lid.joints["bottom"].connect_to(body.joints["top"])
    return Compound([body, lid])

body

body()

Generates the box body

Source code in src/bd_el_parts/lockable_box.py
def body(self) -> Solid:
    """
    Generates the box body

    {% 3d: /models/lockable_box_body.gltf}
    """

    # --------------------------------------------------------------
    # Base
    # --------------------------------------------------------------
    solid = self._part(inner_height=self.inner_body_height)

    # --------------------------------------------------------------
    # "Stop" shape
    # --------------------------------------------------------------
    outside_wire = solid.wires().filter_by(Plane.XY).sort_by(SortBy.LENGTH)[-1]
    stop_sk = make_face(offset(outside_wire, self.thickness + self.clearance / 2))
    stop_sk -= make_face(offset(outside_wire, -self.clearance))
    stop = extrude(stop_sk, -self.LOCK_THICKNESS)
    stop = chamfer(stop.wires().filter_by(Plane.XY).sort_by(Axis.Z)[0].edges(), self.thickness)
    solid += Location((0, 0, -self.OVERLAP)) * stop

    # --------------------------------------------------------------
    # Lock part
    # --------------------------------------------------------------
    solid += Location((self.outer_length / 2, 0, self.inner_body_height + self.thickness - self.OVERLAP)) * self._lock_part()

    # --------------------------------------------------------------
    # Groove wall
    # --------------------------------------------------------------
    back_point = outside_wire.vertices().sort_by(Axis.X)[0]
    groove_size = self.thickness
    groove_location = Location((-self.outer_length / 2, 0, back_point.Z - self.OVERLAP + groove_size)) * Location(Plane.XZ)

    groove_wall_size = self.thickness * 2
    groove_inner_sk = groove_location * Polyline(
        (groove_wall_size, 0),
        (0, groove_wall_size),
        (0, -groove_wall_size),
        (groove_wall_size, 0),
    )
    solid += extrude(make_face(groove_inner_sk), self.inner_width / 2, both=True)

    # --------------------------------------------------------------
    # Groove
    # --------------------------------------------------------------
    groove_sk = groove_location * Polyline(
        (groove_size, 0),
        (0, groove_size),
        (0, -groove_size),
        (groove_size, 0),
    )
    solid -= extrude(make_face(groove_sk), self.inner_width / 2, both=True)

    # --------------------------------------------------------------
    # Final chamfers
    # --------------------------------------------------------------
    solid = chamfer(solid.faces().sort_by(Axis.Z)[-1].wires().sort_by(SortBy.LENGTH)[-1].edges(), 0.5)

    # --------------------------------------------------------------
    # Joints
    # --------------------------------------------------------------
    top_center = Location(solid.faces().filter_by(Plane.XY).sort_by(Axis.Z)[-1].center())
    RigidJoint(label="top", to_part=solid, joint_location=top_center)

    return solid

lid

lid()

Generates the box lid

Source code in src/bd_el_parts/lockable_box.py
def lid(self) -> Part:
    """
    Generates the box lid

    {% 3d: /models/lockable_box_lid.gltf}
    """

    # --------------------------------------------------------------
    # Base shape
    # --------------------------------------------------------------
    solid = (
        Location((0, 0, self.inner_lid_height + self.thickness))
        * Rotation(X=180)
        * self._part(inner_height=self.inner_lid_height)
    )

    # --------------------------------------------------------------
    # Overlap
    # --------------------------------------------------------------
    r = RectangleRounded(height=self.outer_width, width=self.outer_length, radius=self.thickness)
    r2 = offset(r, self.thickness + self.clearance / 2)
    solid += extrude(r2 - r, self.thickness)
    r3 = offset(r2, -self.thickness)
    solid += extrude(r2 - r3, -self.OVERLAP)

    edges = solid.wires().filter_by(Plane.XY).sort_by(SortBy.LENGTH)[-2].edges()
    solid = chamfer(edges, self.thickness)

    # --------------------------------------------------------------
    # Lock part
    # --------------------------------------------------------------
    lock = Location((self.inner_length / 2 + self.thickness, 0, -self.OVERLAP)) * Rotation(X=180) * self._lock_part()
    lock -= extrude(r3, -self.LOCK_SIZE)

    solid += lock

    # --------------------------------------------------------------
    # Counter groove
    # --------------------------------------------------------------
    groove_size = self.thickness
    groove_location = Location((-(self.outer_length + self.clearance) / 2, 0, -self.OVERLAP + groove_size)) * Location(
        Plane.XZ
    )
    groove_sk = groove_location * Polyline(
        (groove_size, 0),
        (0, groove_size),
        (0, -groove_size),
        (groove_size, 0),
    )
    solid += extrude(make_face(groove_sk), self.inner_width / 2 - self.clearance, both=True)

    # --------------------------------------------------------------
    # Joints
    # --------------------------------------------------------------
    RigidJoint(label="bottom", to_part=solid, joint_location=Location((0, 0, 0)))

    return solid