Source code for pybosl2.parts.linear_bearings

# 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/linear_bearings.py
#    Pure-Python port of BOSL2's linear_bearings.scad: models of linear ball bearings that run along
#    a rod, and the pillow-block housings that hold them. :meth:`LinearBearings.linear_bearing` is a
#    generic bearing; :meth:`~LinearBearings.lmxuu_bearing` looks a standard LMxUU size up in
#    :meth:`~LinearBearings.lmxuu_info` (a :class:`LinearBearingSpec` table). The housings clamp a
#    bearing to a plate with a teardrop bore and a screw.
#
# FileSummary: Linear (LMxUU) ball bearings and their pillow-block housings.
# DocCategory: Parts library
# FileGroup: BOSL2

"""Linear (LMxUU) ball bearings and their pillow-block housings."""

from __future__ import annotations

import math
from dataclasses import dataclass
from typing import Any

from pybosl2._helpers import union
from pybosl2.shapes3d import Bosl2Solid, cuboid, teardrop, tube

__all__ = ["LinearBearings", "LinearBearingSpec"]


def _union(shapes: list[Any]) -> Any:
    return union(shapes)


[docs] @dataclass(frozen=True) class LinearBearingSpec: """Dimensions of a standard LMxUU linear bearing (BOSL2 lmxuu_info()).""" outer_diameter: float # outer diameter length: float # axial length
# the bore (shaft) diameter equals the nominal size, which keys the table. # nominal size (shaft Ø) -> spec, transcribed from linear_bearings.scad. _LMXUU = { 4: LinearBearingSpec(8, 12), 5: LinearBearingSpec(10, 15), 6: LinearBearingSpec(12, 19), 8: LinearBearingSpec(15, 24), 10: LinearBearingSpec(19, 29), 12: LinearBearingSpec(21, 30), 13: LinearBearingSpec(23, 32), 16: LinearBearingSpec(28, 37), 20: LinearBearingSpec(32, 42), 25: LinearBearingSpec(40, 59), 30: LinearBearingSpec(45, 64), 35: LinearBearingSpec(52, 70), 40: LinearBearingSpec(60, 80), 50: LinearBearingSpec(80, 100), 60: LinearBearingSpec(90, 110), 80: LinearBearingSpec(120, 140), 100: LinearBearingSpec(150, 175), }
[docs] class LinearBearings: """Linear (LMxUU) ball bearings and pillow-block housings (BOSL2 linear_bearings.scad). .. seealso:: `Visual spec sheet <specs/linear_bearings.html>`_ — measurements and STL previews """
[docs] @staticmethod def lmxuu_info(size: int) -> LinearBearingSpec: """Return the :class:`LinearBearingSpec` (outer_diameter, length) for a standard LMxUU size (BOSL2. lmxuu_info()). """ try: return _LMXUU[int(size)] except (KeyError, ValueError): raise ValueError(f"Unsupported lmXuu linear bearing size: {size!r}") from None
[docs] @staticmethod def linear_bearing( length: float = 24, outer_diameter: float = 15, inner_diameter: float = 8, color: str | None = "silver", fn: int | None = None, fa: float | None = None, fs: float | None = None, ) -> Bosl2Solid: """Return a generic linear ball-bearing cartridge, bore. *inner_diameter* / outer *outer_diameter* / length *length* (BOSL2 linear_bearing()). Examples: An LM8UU-sized bearing: .. pythonscad-example:: from pybosl2.parts.linear_bearings import LinearBearings LinearBearings.linear_bearing(length=24, outer_diameter=15, inner_diameter=8).show() """ body = _union( [ tube( inner_diameter=inner_diameter, outer_diameter=outer_diameter, height=length - 1, fn=fn, fa=fa, fs=fs, ), tube( inner_diameter=outer_diameter - 1, outer_diameter=outer_diameter, height=length, fn=fn, fa=fa, fs=fs, ), tube( inner_diameter=inner_diameter, outer_diameter=inner_diameter + 1, height=length, fn=fn, fa=fa, fs=fs, ), tube( inner_diameter=inner_diameter + 2, outer_diameter=outer_diameter - 2, height=length, fn=fn, fa=fa, fs=fs, ), ] ) result = Bosl2Solid(body.shape, size=[outer_diameter, outer_diameter, length]) return result.color(color) if color else result
[docs] @staticmethod def lmxuu_bearing(size: int = 8, color: str | None = "silver") -> Bosl2Solid: """Return a standard LMxUU linear bearing for a *size* mm rod (BOSL2 lmxuu_bearing()).""" spec = LinearBearings.lmxuu_info(size) return LinearBearings.linear_bearing( length=spec.length, inner_diameter=size, outer_diameter=spec.outer_diameter, color=color )
[docs] @staticmethod def linear_bearing_housing( diameter: float = 15, length: float = 24, tab: float = 8, gap: float = 5, wall: float = 3, tabwall: float = 5, screwsize: float = 3, fn: int | None = None, fa: float | None = None, fs: float | None = None, ) -> Bosl2Solid: """Return a pillow-block housing that clamps a linear bearing (bore. *diameter*, length *length*) to a plate (BOSL2 linear_bearing_housing()). The teardrop bore prints without support; the split *gap* and a *screwsize* clamp screw through the tabs let it grip the bearing. Args: diameter: Outer diameter of the bearing in mm. Defaults to 15. length: Axial length of the bearing in mm. Defaults to 24. tab: Tab height above the clamping gap in mm. Defaults to 8. gap: Split gap width in mm (cut between the clamp tabs). Defaults to 5. wall: Wall thickness around the bearing in mm. Defaults to 3. tabwall: Extra wall thickness for the clamp tabs in mm. Defaults to 5. screwsize: Clamp screw diameter in mm. Defaults to 3. fn: Number of fragments for cylinder resolution. Passed to the geometry primitives. fa: Minimum fragment angle. Passed to the geometry primitives. fs: Minimum fragment size. Passed to the geometry primitives. Returns: A pillow-block housing as a :class:`~pybosl2.shapes3d.Bosl2Solid`. Examples: An LM8UU-sized housing: .. pythonscad-example:: from pybosl2.parts.linear_bearings import LinearBearings LinearBearings.linear_bearing_housing(diameter=15, length=24).show() """ outer_diameter = diameter + 2 * wall ogap = gap + 2 * tabwall tabh = tab / 2 + outer_diameter / 2 * math.sqrt(2) - ogap / 2 - 1 # teardrop bearing shell + base + clamp tabs, then the bore, split gap and screw hole removed. body = _union( [ teardrop(diameter=outer_diameter, height=length, fn=fn, fa=fa, fs=fs).rotate( [0, 90, 0] ), # teardrop shell, axis along X cuboid([length, outer_diameter, outer_diameter / 2], fn=fn, fa=fa, fs=fs).down( outer_diameter / 4 ), # base cuboid([length, ogap, outer_diameter / 2 + tab / 2], fn=fn, fa=fa, fs=fs).up( (outer_diameter / 2 + tab / 2) / 2 ), # clamp tabs ] ) body = body - teardrop(diameter=diameter, height=length + 0.1, fn=fn, fa=fa, fs=fs).rotate( [0, 90, 0] ) # bearing bore body = body - cuboid([length + 0.1, gap, outer_diameter], fn=fn, fa=fa, fs=fs) # split gap # clamp screw across the tabs (a simple clearance hole) from pybosl2.parts.screws import ScrewHole screw = ( ScrewHole(f"M{screwsize:g}", length=ogap + 1, fn=fn or 16, fa=fa, fs=fs).shape().rotate([90, 0, 0]).up(tabh) ) body = body - screw return Bosl2Solid(body.shape, size=[length, outer_diameter, outer_diameter + tab / 2])
[docs] @staticmethod def lmxuu_housing( size: int = 8, tab: float = 7, gap: float = 5, wall: float = 3, tabwall: float = 5, screwsize: float = 3, fn: int | None = None, fa: float | None = None, fs: float | None = None, ) -> Bosl2Solid: """Return a pillow-block housing sized for a standard LMxUU bearing (BOSL2 lmxuu_housing()).""" spec = LinearBearings.lmxuu_info(size) return LinearBearings.linear_bearing_housing( diameter=spec.outer_diameter, length=spec.length, tab=tab, gap=gap, wall=wall, tabwall=tabwall, screwsize=screwsize, fn=fn, fa=fa, fs=fs, )
lmXuu_bearing = lmxuu_bearing # noqa: N815 lmXuu_housing = lmxuu_housing # noqa: N815