Source code for pybosl2.shapes3d.torus

# 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/shapes3d/torus.py
# FileSummary: Toruses and pie slice shapes.
# DocCategory: Foundational
# FileGroup: BOSL2

"""Toruses and pie slice shapes."""

from __future__ import annotations

import math
from typing import TYPE_CHECKING

from pybosl2._edges_lang import Anchor
from pybosl2._native import native

if TYPE_CHECKING:
    from collections.abc import Sequence


from pybosl2._helpers import frag_count as _frag_count
from pybosl2._helpers import pick_radius as _pick_radius
from pybosl2.constants import BOTTOM, DOWN

# Import base class and helper functions from shapes3d.base
from .base import (
    Bosl2Solid,
    _anchor_offset_cyl,
    _finish3,
    _ocylinder,
    _resolve_center_anchor,
)

if TYPE_CHECKING:  # real stub-typed imports for the checker (identical to pre-lazy)
    from pythonscad import cube as _ocube
    from pythonscad import cylinder as _ocylinder_native
    from pythonscad import hull as _ohull
    from pythonscad import minkowski as _ominkowski
    from pythonscad import polyhedron as _opolyhedron
    from pythonscad import rotate_extrude as _orotate_extrude
    from pythonscad import sphere as _osphere_native
    from pythonscad import textmetrics as _otextmetrics
else:
    _ocube = native("cube")
    _ocylinder_native = native("cylinder")
    _ohull = native("hull")
    _ominkowski = native("minkowski")
    _opolyhedron = native("polyhedron")
    _orotate_extrude = native("rotate_extrude")
    _osphere_native = native("sphere")
    _otextmetrics = native("textmetrics")


[docs] def pie_slice( height: float | None = None, radius: float | None = None, angle: float = 30, center: bool | None = None, radius1: float | None = None, radius2: float | None = None, diameter: float | None = None, diameter1: float | None = None, diameter2: float | None = None, length: float | None = None, anchor: Anchor | Sequence[float] = Anchor.CENTER, spin: float = 0, orient: Anchor | Sequence[float] = Anchor.TOP, fn: int | None = None, fa: float | None = None, fs: float | None = None, ) -> Bosl2Solid: """BOSL2 pie_slice() -- a pie slice, wedge of a cylinder/cone. Args: height: height of the pie slice length: height of the pie slice radius: radius of the pie slice angle: pie slice angle in degrees (default 30) center: if given, overrides anchor radius1: bottom radius of the pie slice radius2: top radius of the pie slice diameter: diameter of the pie slice diameter1: diameter of the bottom diameter2: diameter of the top anchor: anchor point (default CENTER) spin: Z-axis rotation in degrees after anchor (default 0) orient: direction to rotate the top towards, after spin (default UP) fn: arc smoothness overrides fa: arc smoothness overrides fs: arc smoothness overrides Examples: .. pythonscad-example:: from pybosl2.solid import pie_slice pie_slice(radius=20, angle=120, height=5).show() """ from pybosl2._helpers import arc_points as _arc_points from pybosl2._native import native _opolygon = native("polygon") length = height if height is not None else (length if length is not None else 1) rad1 = _pick_radius(radius1=radius1, diameter1=diameter1, radius=radius, diameter=diameter, dflt=10) rad2 = _pick_radius(radius1=radius2, diameter1=diameter2, radius=radius, diameter=diameter, dflt=10) use_anchor = _resolve_center_anchor(center, anchor, BOTTOM) base = _ocylinder( height=length, radius1=rad1, radius2=rad2, center=True, fn=fn, fa=fa, fs=fs, ) if isinstance(angle, (list, tuple)): # [start, end] wedge start, sweep = float(angle[0]), float(angle[1]) - float(angle[0]) else: start, sweep = 0.0, float(angle) ang_v = sweep % 360 if (sweep > 360 or sweep < 0) else sweep if ang_v <= 0 or ang_v >= 360: shape = base else: maxd = max(rad1, rad2) + 0.1 sides = max(3, math.ceil(_frag_count(maxd, fn, fa, fs) * ang_v / 360)) arc = _arc_points(sides, maxd, start, ang_v) sector = _opolygon([[0.0, 0.0]] + arc).linear_extrude(height=length + 0.2, center=True) shape = base & sector offset = _anchor_offset_cyl(rad1, rad2, length, use_anchor) return _finish3(shape, offset, spin, orient, size=None, anchor=use_anchor)
[docs] def torus( major_radius: float | None = None, minor_radius: float | None = None, center: bool | None = None, major_diameter: float | None = None, minor_diameter: float | None = None, outer_radius: float | None = None, inner_radius: float | None = None, outer_diameter: float | None = None, inner_diameter: float | None = None, anchor: Anchor | Sequence[float] = Anchor.CENTER, spin: float = 0, orient: Anchor | Sequence[float] = Anchor.TOP, fn: int | None = None, fa: float | None = None, fs: float | None = None, ) -> Bosl2Solid: """BOSL2 torus() -- a torus (donut) shape. Note: BOSL2's outer-radius parameter is named `or`, which collides with the Python keyword `or`; it is exposed here as `outer_radius` instead. Args: major_radius: major radius of the torus ring (use with minor_radius or minor_diameter) minor_radius: minor radius of the torus ring (use with major_radius or major_diameter) center: if given, overrides anchor (True -> CENTER, False -> DOWN) major_diameter: major diameter of the torus ring minor_diameter: minor diameter of the torus ring outer_radius: outer radius of the torus (BOSL2 `or`) (use with inner_radius or inner_diameter) inner_radius: inside radius of the torus (use with outer_radius or outer_diameter) outer_diameter: outer diameter of the torus (use with inner_radius or inner_diameter) inner_diameter: inside diameter of the torus (use with outer_radius or outer_diameter) anchor: anchor point (default CENTER) spin: Z-axis rotation in degrees (default 0) orient: direction to rotate the top towards, after spin (default UP) fn: arc smoothness overrides fa: arc smoothness overrides fs: arc smoothness overrides Examples: .. pythonscad-example:: from pybosl2.solid import torus shape = torus(major_radius=25, minor_radius=8) shape.show() """ from pybosl2._helpers import arc_points as _arc_points from pybosl2._native import native _opolygon = native("polygon") _or = _pick_radius(radius=outer_radius, diameter=outer_diameter, dflt=None) _ir = _pick_radius(radius=inner_radius, diameter=inner_diameter, dflt=None) _r_maj = _pick_radius(radius=major_radius, diameter=major_diameter, dflt=None) _r_min = _pick_radius(radius=minor_radius, diameter=minor_diameter, dflt=None) if _r_maj is not None: maj_rad = _r_maj elif _ir is not None and _or is not None: maj_rad = (_or + _ir) / 2 elif _ir is not None and _r_min is not None: maj_rad = _ir + _r_min elif _or is not None and _r_min is not None: maj_rad = _or - _r_min else: raise AssertionError("torus(): bad parameters.") if _r_min is not None: min_rad = _r_min elif _ir is not None: min_rad = maj_rad - _ir elif _or is not None: min_rad = _or - maj_rad else: raise AssertionError("torus(): bad parameters.") use_anchor = _resolve_center_anchor(center, anchor, DOWN) sides = _frag_count(min_rad, fn, fa, fs) profile = _arc_points(sides, min_rad, 0, 360, [maj_rad, 0.0], endpoint=False) shape = _orotate_extrude(_opolygon(profile), fn=fn, fa=fa, fs=fs) offset = _anchor_offset_cyl(maj_rad + min_rad, maj_rad + min_rad, min_rad * 2, use_anchor) return _finish3(shape, offset, spin, orient, size=None, anchor=use_anchor)