Source code for pybosl2.shapes3d.cylinder

# 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/cylinder.py
# FileSummary: Cylinders, cones, shear cylinders and pipe shapes.
# DocCategory: Foundational
# FileGroup: BOSL2

"""Cylinders, cones, shear cylinders and pipe 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.texture import TextureType

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

# Import base class and helper functions from shapes3d.base
from .base import (
    Bosl2Solid,
    _anchor_offset_cyl,
    _finish3,
    _ocylinder,
    _osphere,
    _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")


# ---------------------------------------------------------------------------
# Section: Cylinders
# ---------------------------------------------------------------------------


[docs] def cylinder( height: float | None = None, radius: float | None = None, center: bool | None = None, length: float | None = None, radius1: float | None = None, radius2: float | None = None, diameter: float | None = None, diameter1: float | None = None, diameter2: float | None = None, chamfer: float | None = None, chamfer1: float | None = None, chamfer2: float | None = None, rounding: float | None = None, rounding1: float | None = None, rounding2: float | None = None, circumscribe: bool = False, realign: bool = False, shift: Sequence[float] = [0, 0], anchor: Anchor | Sequence[float] | None = None, spin: float = 0, orient: Anchor | Sequence[float] = Anchor.TOP, fn: int | None = None, fa: float | None = None, fs: float | None = None, chamfer_angle: float | None = None, chamfer_angle1: float | None = None, chamfer_angle2: float | None = None, from_end: bool = False, from_end1: bool | None = None, from_end2: bool | None = None, extra: float = 0.0, extra1: float | None = None, extra2: float | None = None, teardrop: float | bool = False, clip_angle: float = 90.0, texture: str | TextureType | None = None, tex_size: float | Sequence[float] | None = None, tex_reps: int | Sequence[int] | None = None, tex_depth: float = 1.0, tex_inset: float | bool = False, ) -> Bosl2Solid: """Return a cylinder with optional chamfering/rounding of its end rims, built with. cube()/cylinder()/sphere()/rotate_extrude(). Positive rounding is built as a minkowski() of a shorter cylinder with a sphere at each rounded end (an inset fillet, not an outward bulge), matching BOSL2's own rounded-end geometry. Chamfering builds the exact half-profile (with the requested bevel at each end) and revolves it with rotate_extrude(). Note: ``texture=`` (VNF surface texturing) is not supported by this pure-Python port. Args: length: length of the cylinder along its axis (default 1) height: length of the cylinder along its axis (default 1) radius: radius of the cylinder (default 1) diameter: diameter of the cylinder radius1: radius of the negative end of the cylinder radius2: radius of the positive end of the cylinder diameter1: diameter of the negative end of the cylinder diameter2: diameter of the positive end of the cylinder center: if given, overrides anchor (True -> CENTER, False -> BOTTOM) chamfer: chamfer size on the end rims (overall/negative/positive) chamfer1: chamfer size on the end rims (overall/negative/positive) chamfer2: chamfer size on the end rims (overall/negative/positive) rounding: rounding radius on the end rims (overall/negative/positive) rounding1: rounding radius on the end rims (overall/negative/positive) rounding2: rounding radius on the end rims (overall/negative/positive) circumscribe: circumscribe rather than inscribe the given radius (default False) realign: shift point alignment (default False) shift: X/Y offset for the positive end (shear) (default [0,0]) anchor: anchor point (default BOTTOM if center=False, otherwise 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 chamfer_angle: chamfer angle in degrees away from ends chamfer_angle1: chamfer angle in degrees away from ends chamfer_angle2: chamfer angle in degrees away from ends from_end: measure chamfer along conic face (default False) from_end1: measure chamfer along conic face (default False) from_end2: measure chamfer along conic face (default False) extra: add extra height at ends (invisible to anchoring) extra1: add extra height at ends (invisible to anchoring) extra2: add extra height at ends (invisible to anchoring) teardrop: limit rounding angle from horizontal clip_angle: clip rounding arc at bottom of cylinder texture: named texture to apply to cylinder side surface tex_size: size of texture tile tex_reps: number of texture repetitions tex_depth: depth of the texture tex_inset: inset the texture Examples: A basic cylinder: .. pythonscad-example:: from pybosl2.solid import cylinder cylinder(height=30, radius=10).show() A cylinder with chamfered ends: .. pythonscad-example:: from pybosl2.solid import cylinder cylinder(height=40, radius=15, chamfer=2).show() A cylinder with rounded ends: .. pythonscad-example:: from pybosl2.solid import cylinder cylinder(height=30, radius=12, rounding=2).show() """ return cyl( height=height, radius=radius, center=center, length=length, radius1=radius1, radius2=radius2, diameter=diameter, diameter1=diameter1, diameter2=diameter2, chamfer=chamfer, chamfer1=chamfer1, chamfer2=chamfer2, rounding=rounding, rounding1=rounding1, rounding2=rounding2, circumscribe=circumscribe, realign=realign, shift=list(shift), anchor=anchor, spin=spin, orient=orient, fn=fn, fa=fa, fs=fs, chamfer_angle=chamfer_angle, chamfer_angle1=chamfer_angle1, chamfer_angle2=chamfer_angle2, from_end=from_end, from_end1=from_end1, from_end2=from_end2, extra=extra, extra1=extra1, extra2=extra2, teardrop=teardrop, clip_angle=clip_angle, texture=texture, tex_size=tex_size, tex_reps=tex_reps, tex_depth=tex_depth, tex_inset=tex_inset, )
[docs] def cyl( height: float | None = None, radius: float | None = None, center: bool | None = None, length: float | None = None, radius1: float | None = None, radius2: float | None = None, diameter: float | None = None, diameter1: float | None = None, diameter2: float | None = None, chamfer: float | None = None, chamfer1: float | None = None, chamfer2: float | None = None, rounding: float | None = None, rounding1: float | None = None, rounding2: float | None = None, circumscribe: bool = False, realign: bool = False, shift: Sequence[float] = [0, 0], anchor: Anchor | Sequence[float] | None = None, spin: float = 0, orient: Anchor | Sequence[float] = Anchor.TOP, fn: int | None = None, fa: float | None = None, fs: float | None = None, # Additional missing args chamfer_angle: float | None = None, chamfer_angle1: float | None = None, chamfer_angle2: float | None = None, from_end: bool = False, from_end1: bool | None = None, from_end2: bool | None = None, extra: float = 0.0, extra1: float | None = None, extra2: float | None = None, teardrop: float | bool = False, clip_angle: float = 90.0, texture: str | TextureType | None = None, tex_size: float | Sequence[float] | None = None, tex_reps: int | Sequence[int] | None = None, tex_depth: float = 1.0, tex_inset: float | bool = False, ) -> Bosl2Solid: """Return a cylinder with optional chamfering/rounding of its end rims, built with. cube()/cylinder()/sphere()/rotate_extrude(). Positive rounding is built as a minkowski() of a shorter cylinder with a sphere at each rounded end (an inset fillet, not an outward bulge), matching BOSL2's own rounded-end geometry. Chamfering builds the exact half-profile (with the requested bevel at each end) and revolves it with rotate_extrude(). Note: `texture=` (VNF surface texturing) is not supported by this pure-Python port. Args: length: length of the cylinder along its axis (default 1) height: length of the cylinder along its axis (default 1) radius: radius of the cylinder (default 1) diameter: diameter of the cylinder radius1: radius of the negative end of the cylinder radius2: radius of the positive end of the cylinder diameter1: diameter of the negative end of the cylinder diameter2: diameter of the positive end of the cylinder center: if given, overrides anchor (True -> CENTER, False -> BOTTOM) chamfer: chamfer size on the end rims (overall/negative/positive) chamfer1: chamfer size on the end rims (overall/negative/positive) chamfer2: chamfer size on the end rims (overall/negative/positive) rounding: rounding radius on the end rims (overall/negative/positive) rounding1: rounding radius on the end rims (overall/negative/positive) rounding2: rounding radius on the end rims (overall/negative/positive) circumscribe: circumscribe rather than inscribe the given radius (default False) realign: shift point alignment (default False) shift: X/Y offset for the positive end (shear) (default [0,0]) 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 chamfer_angle: chamfer angle in degrees away from ends chamfer_angle1: chamfer angle in degrees away from ends chamfer_angle2: chamfer angle in degrees away from ends from_end: measure chamfer along conic face (default False) from_end1: measure chamfer along conic face (default False) from_end2: measure chamfer along conic face (default False) extra: add extra height at ends (invisible to anchoring) extra1: add extra height at ends (invisible to anchoring) extra2: add extra height at ends (invisible to anchoring) teardrop: limit rounding angle from horizontal clip_angle: clip rounding arc at bottom of cylinder texture: named texture to apply to cylinder side surface tex_size: size of texture tile tex_reps: number of texture repetitions tex_depth: depth of the texture tex_inset: inset the texture Examples: A basic cylinder: .. pythonscad-example:: from pybosl2 import cyl shape = cyl(radius=10, height=30) shape.show() A cylinder with chamfered ends: .. pythonscad-example:: from pybosl2 import cyl shape = cyl(radius=15, height=40, chamfer=2) shape.show() A cylinder with rounded ends: .. pythonscad-example:: from pybosl2 import cyl shape = cyl(radius=12, height=35, rounding=3) shape.show() """ if texture is not None and texture != "none": raise NotImplementedError("texture= (VNF surface texturing) is not supported by this pure-Python port.") _ = (tex_size, tex_reps, tex_depth, tex_inset) length_val = next((v for v in (length, height) if v is not None), 1.0) rad1 = _pick_radius(radius1=radius1, diameter1=diameter1, radius=radius, diameter=diameter, dflt=1) rad2 = _pick_radius(radius1=radius2, diameter1=diameter2, radius=radius, diameter=diameter, dflt=1) if circumscribe: sides = _frag_count(max(rad1, rad2), fn, fa, fs) sc = 1 / math.cos(math.pi / sides) rad1 *= sc rad2 *= sc use_anchor = anchor if use_anchor is None: use_anchor = CENTER if center is None or center else BOTTOM r1v = rounding1 if rounding1 is not None else (rounding if rounding is not None else 0) r2v = rounding2 if rounding2 is not None else (rounding if rounding is not None else 0) c1v = chamfer1 if chamfer1 is not None else (chamfer if chamfer is not None else 0) c2v = chamfer2 if chamfer2 is not None else (chamfer if chamfer is not None else 0) assert not ((r1v or r2v) and (c1v or c2v)), "Cannot specify nonzero value for both chamfer and rounding" cfang1 = chamfer_angle1 if chamfer_angle1 is not None else (chamfer_angle if chamfer_angle is not None else None) cfang2 = chamfer_angle2 if chamfer_angle2 is not None else (chamfer_angle if chamfer_angle is not None else None) fe1 = from_end1 if from_end1 is not None else from_end fe2 = from_end2 if from_end2 is not None else from_end if not (r1v or r2v or c1v or c2v): shape = _ocylinder( height=length_val, radius1=rad1, radius2=rad2, center=True, fn=fn, fa=fa, fs=fs, ) elif ( rad1 == rad2 and r1v == r2v and r1v > 0 and not c1v and not c2v and (teardrop is False or teardrop is None) and clip_angle == 90.0 ): # Straight cylinder, uniform rounding on both ends: exact via minkowski(cylinder, sphere). inner_r = max(0.001, rad1 - r1v) inner_l = max(0.001, length_val - 2 * r1v) sphere_fn = int(quantup(_frag_count(r1v, fn, fa, fs), 4)) shape = _ominkowski( _ocylinder(height=inner_l, radius=inner_r, center=True, fn=fn, fa=fa, fs=fs), _osphere(radius=r1v, fn=sphere_fn), ) else: profile = cyl_profile( rad1, rad2, length_val, rounding1=r1v, rounding2=r2v, chamfer1=c1v, chamfer2=c2v, chamfer_angle1=cfang1, chamfer_angle2=cfang2, from_end1=fe1, from_end2=fe2, fn=fn, fa=fa, fs=fs, teardrop=teardrop, clip_angle=clip_angle, ) from pybosl2._native import native _opolygon = native("polygon") shape = _orotate_extrude(_opolygon(profile), fn=fn, fa=fa, fs=fs) if realign: sides = _frag_count(max(rad1, rad2), fn, fa, fs) shape = shape.rotate(180 / sides, [0, 0, 1]) if shift[0] or shift[1]: shear = [ [1, 0, shift[0] / length_val, 0], [0, 1, shift[1] / length_val, 0], [0, 0, 1, 0], [0, 0, 0, 1], ] shape = shape.multmatrix(shear) extra1_val = extra1 if extra1 is not None else extra extra2_val = extra2 if extra2 is not None else extra if extra1_val > 0: ext1 = _ocylinder(height=extra1_val, radius=rad1, center=False, fn=fn, fa=fa, fs=fs).translate( [0, 0, -length_val / 2 - extra1_val] ) shape = shape | ext1 if extra2_val > 0: ext2 = _ocylinder(height=extra2_val, radius=rad2, center=False, fn=fn, fa=fa, fs=fs).translate( [0, 0, length_val / 2] ) shape = shape | ext2 offset = _anchor_offset_cyl(rad1, rad2, length_val, use_anchor) return _finish3(shape, offset, spin, orient, size=None, anchor=use_anchor)
[docs] def cyl_profile( radius1: float, radius2: float, length: float, rounding1: float = 0, rounding2: float = 0, chamfer1: float = 0, chamfer2: float = 0, chamfer_angle1: float | None = None, chamfer_angle2: float | None = None, from_end1: bool = False, from_end2: bool = False, fn: int | None = None, fa: float | None = None, fs: float | None = None, teardrop: float | bool = False, clip_angle: float = 90.0, ) -> list[list[float]]: """Generate a 2D cylinder profile with optional rounding and chamfering.""" from pybosl2._helpers import arc_points as _arc_points eff_clip = float(clip_angle) if teardrop is not False and teardrop is not None: td_ang = teardrop if isinstance(teardrop, (int, float)) else 45.0 eff_clip = min(eff_clip, 90.0 - td_ang) path = [[0.0, -length / 2]] if rounding1: sides = max(3, _frag_count(rounding1, fn, fa, fs) // 4) center = [radius1 - rounding1, -length / 2 + rounding1] pts = _arc_points(sides, rounding1, 360 - eff_clip, eff_clip, center) if eff_clip < 90.0: path.append([pts[0][0], -length / 2]) path.extend(pts) elif chamfer1: angle1 = chamfer_angle1 if chamfer_angle1 is not None else 45.0 if from_end1: dx = chamfer1 * math.cos(math.radians(angle1)) dy = chamfer1 * math.sin(math.radians(angle1)) else: dx = chamfer1 dy = chamfer1 * math.tan(math.radians(angle1)) path.append([radius1 - dx, -length / 2]) path.append([radius1, -length / 2 + dy]) else: path.append([radius1, -length / 2]) if rounding2: sides = max(3, _frag_count(rounding2, fn, fa, fs) // 4) center = [radius2 - rounding2, length / 2 - rounding2] pts = _arc_points(sides, rounding2, 0, eff_clip, center) path.extend(pts) if eff_clip < 90.0: path.append([pts[-1][0], length / 2]) elif chamfer2: angle2 = chamfer_angle2 if chamfer_angle2 is not None else 45.0 if from_end2: dx = chamfer2 * math.cos(math.radians(angle2)) dy = chamfer2 * math.sin(math.radians(angle2)) else: dx = chamfer2 dy = chamfer2 * math.tan(math.radians(angle2)) path.append([radius2, length / 2 - dy]) path.append([radius2 - dx, length / 2]) else: path.append([radius2, length / 2]) path.append([0.0, length / 2]) return path
[docs] def xcyl( height: float | None = None, radius: float | None = None, center: bool | None = None, length: float | None = None, radius1: float | None = None, radius2: float | None = None, diameter: float | None = None, diameter1: float | None = None, diameter2: float | None = None, chamfer: float | None = None, chamfer1: float | None = None, chamfer2: float | None = None, rounding: float | None = None, rounding1: float | None = None, rounding2: float | None = None, circumscribe: bool = False, realign: bool = False, shift: Sequence[float] = [0, 0], anchor: Anchor | Sequence[float] | None = None, spin: float = 0, orient: Anchor | Sequence[float] = Anchor.TOP, fn: int | None = None, fa: float | None = None, fs: float | None = None, # Additional missing args chamfer_angle: float | None = None, chamfer_angle1: float | None = None, chamfer_angle2: float | None = None, from_end: bool = False, from_end1: bool | None = None, from_end2: bool | None = None, extra: float = 0.0, extra1: float | None = None, extra2: float | None = None, teardrop: float | bool = False, clip_angle: float = 90.0, texture: str | TextureType | None = None, tex_size: float | Sequence[float] | None = None, tex_reps: int | Sequence[int] | None = None, tex_depth: float = 1.0, tex_inset: float | bool = False, ) -> Bosl2Solid: """Return a cylinder oriented along the X axis. See cyl() for argument details. Examples: .. pythonscad-example:: from pybosl2.shapes3d.cylinder import xcyl shape = xcyl(radius=10, height=30) shape.show() """ length_val = next((v for v in (length, height) if v is not None), 1.0) rad1 = _pick_radius(radius1=radius1, diameter1=diameter1, radius=radius, diameter=diameter, dflt=1) rad2 = _pick_radius(radius1=radius2, diameter1=diameter2, radius=radius, diameter=diameter, dflt=1) if circumscribe: sides = _frag_count(max(rad1, rad2), fn, fa, fs) sc = 1 / math.cos(math.pi / sides) rad1 *= sc rad2 *= sc use_anchor = anchor if use_anchor is None: use_anchor = CENTER if center is None or center else BOTTOM shape = cyl( length=length_val, radius1=rad1, radius2=rad2, chamfer=chamfer, chamfer1=chamfer1, chamfer2=chamfer2, rounding=rounding, rounding1=rounding1, rounding2=rounding2, circumscribe=circumscribe, realign=realign, shift=shift, anchor=CENTER, fn=fn, fa=fa, fs=fs, chamfer_angle=chamfer_angle, chamfer_angle1=chamfer_angle1, chamfer_angle2=chamfer_angle2, from_end=from_end, from_end1=from_end1, from_end2=from_end2, extra=extra, extra1=extra1, extra2=extra2, teardrop=teardrop, clip_angle=clip_angle, texture=texture, tex_size=tex_size, tex_reps=tex_reps, tex_depth=tex_depth, tex_inset=tex_inset, ).shape.rotate(90, [0, 1, 0]) offset = _anchor_offset_cyl(rad1, rad2, length_val, use_anchor, axis=0) return _finish3(shape, offset, spin, orient, size=None, anchor=use_anchor)
[docs] def ycyl( height: float | None = None, radius: float | None = None, center: bool | None = None, length: float | None = None, radius1: float | None = None, radius2: float | None = None, diameter: float | None = None, diameter1: float | None = None, diameter2: float | None = None, chamfer: float | None = None, chamfer1: float | None = None, chamfer2: float | None = None, rounding: float | None = None, rounding1: float | None = None, rounding2: float | None = None, circumscribe: bool = False, realign: bool = False, shift: Sequence[float] = [0, 0], anchor: Anchor | Sequence[float] | None = None, spin: float = 0, orient: Anchor | Sequence[float] = Anchor.TOP, fn: int | None = None, fa: float | None = None, fs: float | None = None, # Additional missing args chamfer_angle: float | None = None, chamfer_angle1: float | None = None, chamfer_angle2: float | None = None, from_end: bool = False, from_end1: bool | None = None, from_end2: bool | None = None, extra: float = 0.0, extra1: float | None = None, extra2: float | None = None, teardrop: float | bool = False, clip_angle: float = 90.0, texture: str | TextureType | None = None, tex_size: float | Sequence[float] | None = None, tex_reps: int | Sequence[int] | None = None, tex_depth: float = 1.0, tex_inset: float | bool = False, ) -> Bosl2Solid: """Return a cylinder oriented along the Y axis. See cyl() for argument details. Examples: .. pythonscad-example:: from pybosl2.shapes3d.cylinder import ycyl shape = ycyl(radius=10, height=30) shape.show() """ length_val = next((v for v in (length, height) if v is not None), 1.0) rad1 = _pick_radius(radius1=radius1, diameter1=diameter1, radius=radius, diameter=diameter, dflt=1) rad2 = _pick_radius(radius1=radius2, diameter1=diameter2, radius=radius, diameter=diameter, dflt=1) if circumscribe: sides = _frag_count(max(rad1, rad2), fn, fa, fs) sc = 1 / math.cos(math.pi / sides) rad1 *= sc rad2 *= sc use_anchor = anchor if use_anchor is None: use_anchor = CENTER if center is None or center else BOTTOM shape = cyl( length=length_val, radius1=rad1, radius2=rad2, chamfer=chamfer, chamfer1=chamfer1, chamfer2=chamfer2, rounding=rounding, rounding1=rounding1, rounding2=rounding2, circumscribe=circumscribe, realign=realign, shift=shift, anchor=CENTER, fn=fn, fa=fa, fs=fs, chamfer_angle=chamfer_angle, chamfer_angle1=chamfer_angle1, chamfer_angle2=chamfer_angle2, from_end=from_end, from_end1=from_end1, from_end2=from_end2, extra=extra, extra1=extra1, extra2=extra2, teardrop=teardrop, clip_angle=clip_angle, texture=texture, tex_size=tex_size, tex_reps=tex_reps, tex_depth=tex_depth, tex_inset=tex_inset, ).shape.rotate(-90, [1, 0, 0]) offset = _anchor_offset_cyl(rad1, rad2, length_val, use_anchor, axis=1) return _finish3(shape, offset, spin, orient, size=None, anchor=use_anchor)
[docs] def zcyl( height: float | None = None, radius: float | None = None, center: bool | None = None, length: float | None = None, radius1: float | None = None, radius2: float | None = None, diameter: float | None = None, diameter1: float | None = None, diameter2: float | None = None, chamfer: float | None = None, chamfer1: float | None = None, chamfer2: float | None = None, rounding: float | None = None, rounding1: float | None = None, rounding2: float | None = None, circumscribe: bool = False, realign: bool = False, shift: Sequence[float] = [0, 0], anchor: Anchor | Sequence[float] | None = None, spin: float = 0, orient: Anchor | Sequence[float] = Anchor.TOP, fn: int | None = None, fa: float | None = None, fs: float | None = None, # Additional missing args chamfer_angle: float | None = None, chamfer_angle1: float | None = None, chamfer_angle2: float | None = None, from_end: bool = False, from_end1: bool | None = None, from_end2: bool | None = None, extra: float = 0.0, extra1: float | None = None, extra2: float | None = None, teardrop: float | bool = False, clip_angle: float = 90.0, texture: str | TextureType | None = None, tex_size: float | Sequence[float] | None = None, tex_reps: int | Sequence[int] | None = None, tex_depth: float = 1.0, tex_inset: float | bool = False, ) -> Bosl2Solid: """Return a cylinder oriented along the Z axis (same as cyl() with default orientation). See cyl() for. argument details. Examples: .. pythonscad-example:: from pybosl2.shapes3d.cylinder import zcyl shape = zcyl(radius=10, height=30) shape.show() """ return cyl( height=height, radius=radius, center=center, length=length, radius1=radius1, radius2=radius2, diameter=diameter, diameter1=diameter1, diameter2=diameter2, chamfer=chamfer, chamfer1=chamfer1, chamfer2=chamfer2, rounding=rounding, rounding1=rounding1, rounding2=rounding2, circumscribe=circumscribe, realign=realign, shift=shift, anchor=anchor, spin=spin, orient=orient, fn=fn, fa=fa, fs=fs, chamfer_angle=chamfer_angle, chamfer_angle1=chamfer_angle1, chamfer_angle2=chamfer_angle2, from_end=from_end, from_end1=from_end1, from_end2=from_end2, extra=extra, extra1=extra1, extra2=extra2, teardrop=teardrop, clip_angle=clip_angle, texture=texture, tex_size=tex_size, tex_reps=tex_reps, tex_depth=tex_depth, tex_inset=tex_inset, )
[docs] def tube( height: float | None = None, outer_radius: float | None = None, inner_radius: float | None = None, center: bool | None = None, outer_diameter: float | None = None, inner_diameter: float | None = None, wall: float | None = None, outer_radius1: float | None = None, outer_radius2: float | None = None, outer_diameter1: float | None = None, outer_diameter2: float | None = None, inner_radius1: float | None = None, inner_radius2: float | None = None, inner_diameter1: float | None = None, inner_diameter2: float | None = None, chamfer: float | None = None, chamfer1: float | None = None, chamfer2: float | None = None, rounding: float | None = None, rounding1: float | None = None, rounding2: float | None = None, realign: bool = False, 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 tube() -- a hollow cylindrical tube, with optional chamfer/rounding on end rims. Note: BOSL2's outer-radius parameters are named ``or``/``or1``/``or2``, which collide with the Python keyword ``or``; they are exposed here as ``outer_radius``/``outer_radius1``/``outer_radius2`` instead. Args: height: height of the tube (default 1) length: height of the tube (default 1) outer_radius: outer radius of the tube (BOSL2 ``or``) (default 1) inner_radius: inner radius of the tube center: if given, overrides anchor (True -> CENTER, False -> DOWN) outer_diameter: outer diameter of the tube inner_diameter: inner diameter of the tube wall: horizontal wall thickness (default 1) outer_radius1: outer radius of the bottom/top outer_radius2: outer radius of the bottom/top outer_diameter1: outer diameter of the bottom/top outer_diameter2: outer diameter of the bottom/top inner_radius1: inner radius of the bottom/top inner_radius2: inner radius of the bottom/top inner_diameter1: inner diameter of the bottom/top inner_diameter2: inner diameter of the bottom/top chamfer: chamfer size on end rims (overall/bottom/top) chamfer1: chamfer size on end rims (overall/bottom/top) chamfer2: chamfer size on end rims (overall/bottom/top) rounding: rounding radius on end rims (overall/bottom/top) rounding1: rounding radius on end rims (overall/bottom/top) rounding2: rounding radius on end rims (overall/bottom/top) realign: rotate by half the angle of one face (default False) 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 import tube shape = tube(height=20, outer_radius=15, inner_radius=10) shape.show() A tube with chamfered end rims: .. pythonscad-example:: from pybosl2 import tube shape = tube(height=20, outer_radius=15, inner_radius=10, chamfer=1) shape.show() """ height = height if height is not None else (length if length is not None else 1) orr1 = _pick_radius( radius1=outer_radius1, diameter1=outer_diameter1, radius=outer_radius, diameter=outer_diameter, dflt=None, ) orr2 = _pick_radius( radius1=outer_radius2, diameter1=outer_diameter2, radius=outer_radius, diameter=outer_diameter, dflt=None, ) irr1 = _pick_radius( radius1=inner_radius1, diameter1=inner_diameter1, radius=inner_radius, diameter=inner_diameter, dflt=None, ) irr2 = _pick_radius( radius1=inner_radius2, diameter1=inner_diameter2, radius=inner_radius, diameter=inner_diameter, dflt=None, ) wall_v = wall if wall is not None else 1 rad1 = orr1 if orr1 is not None else (irr1 + wall_v if irr1 is not None else None) rad2 = orr2 if orr2 is not None else (irr2 + wall_v if irr2 is not None else None) irad1 = irr1 if irr1 is not None else (orr1 - wall_v if orr1 is not None else None) irad2 = irr2 if irr2 is not None else (orr2 - wall_v if orr2 is not None else None) assert rad1 is not None, "tube(): must specify two of inner radius/diam, outer radius/diam, and wall width." assert rad2 is not None, "tube(): must specify two of inner radius/diam, outer radius/diam, and wall width." assert irad1 is not None, "tube(): must specify two of inner radius/diam, outer radius/diam, and wall width." assert irad2 is not None, "tube(): must specify two of inner radius/diam, outer radius/diam, and wall width." assert irad1 <= rad1, "tube(): inner radius is larger than outer radius." assert irad2 <= rad2, "tube(): inner radius is larger than outer radius." use_anchor = _resolve_center_anchor(center, anchor, BOTTOM) # Build outer and inner cylinders via cyl() for chamfer/rounding support outer = cyl( height=height, radius1=rad1, radius2=rad2, center=True, chamfer=chamfer, chamfer1=chamfer1, chamfer2=chamfer2, rounding=rounding, rounding1=rounding1, rounding2=rounding2, fn=fn, fa=fa, fs=fs, ) extra_h = max(chamfer or 0, rounding or 0, chamfer1 or 0, rounding1 or 0, chamfer2 or 0, rounding2 or 0) inner = cyl( height=height + extra_h + 0.02, radius1=irad1, radius2=irad2, center=True, fn=fn, fa=fa, fs=fs, ) shape = outer.shape - inner.shape if realign: sides = _frag_count(max(rad1, rad2), fn, fa, fs) shape = shape.rotate(180 / sides, [0, 0, 1]) offset = _anchor_offset_cyl(rad1, rad2, height, use_anchor) return _finish3(shape, offset, spin, orient, size=None, anchor=use_anchor)
[docs] def cone( height: float | None = None, radius: float | None = None, radius1: float | None = None, radius2: float | None = None, center: bool | None = None, diameter: float | None = None, diameter1: float | None = None, diameter2: float | None = None, chamfer: float | None = None, chamfer1: float | None = None, chamfer2: float | None = None, rounding: float | None = None, rounding1: float | None = None, rounding2: float | None = None, length: float | None = None, anchor: Anchor | Sequence[float] | None = None, spin: float = 0, orient: Anchor | Sequence[float] = Anchor.TOP, fn: int | None = None, fa: float | None = None, fs: float | None = None, ) -> Bosl2Solid: """Return a cone/truncated cone with optional chamfering or rounding of the end rims. Convenience wrapper around :func:`cyl` / :func:`cylinder` with ``radius2=0`` by default for a pointed cone, or with explicit ``radius2`` for a truncated (frustum) form. Args: height: height of the cone (default 1) length: height of the cone (default 1) radius: base radius (default 1) radius1: bottom radius (overrides *radius*) radius2: top radius (default 0 for a pointed cone) center: if given, overrides anchor diameter: base diameter diameter1: bottom diameter diameter2: top diameter chamfer: chamfer size on the end rims chamfer1: chamfer size on the end rims chamfer2: chamfer size on the end rims rounding: rounding radius on the end rims rounding1: rounding radius on the end rims rounding2: rounding radius on the end rims anchor: anchor point 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: A pointed cone: .. pythonscad-example:: from pybosl2 import shapes3d as s3 s3.cone(height=30, radius=15).show() A truncated cone (frustum): .. pythonscad-example:: from pybosl2 import shapes3d as s3 s3.cone(height=30, radius1=15, radius2=8).show() A cone with chamfered base: .. pythonscad-example:: from pybosl2 import shapes3d as s3 s3.cone(height=30, radius1=15, radius2=3, chamfer=2).show() """ r1 = _pick_radius(radius1=radius1, diameter1=diameter1, radius=radius, diameter=diameter, dflt=1) r2 = _pick_radius(radius1=radius2, diameter1=diameter2, dflt=0) return cyl( height=height, radius1=r1, radius2=r2, center=center, length=length, chamfer=chamfer, chamfer1=chamfer1, chamfer2=chamfer2, rounding=rounding, rounding1=rounding1, rounding2=rounding2, anchor=anchor, spin=spin, orient=orient, fn=fn, fa=fa, fs=fs, )