Groups

Frozen argument groups (Placement, Facets) for parameters that travel together

Frozen argument groups (Placement, Facets) for parameters that travel together.

class pybosl2.groups.Placement(anchor=Anchor.CENTER, spin=0.0, orient=Anchor.TOP)[source]

Bases: object

Where a shape sits: its anchor, its spin about Z, and the direction it faces.

The three always travel together (SPEC G-1), so they can be one value that is built once and reused. Frozen, like every group: with_() returns a new one.

A placement reads in two dimensions as well as three. In the plane a shape has an anchor and a spin but nothing to orient – there is no third axis to turn a face towards – so a 2-D constructor honours those two. One placement can therefore serve a 2-D outline and the solid extruded from it, which is the case worth having. What it must not do is quietly honour two of the three: a placement carrying a real orient asks for something the plane cannot do, so passing one to a 2-D constructor raises rather than silently dropping it (SPEC E-5). The default orient is not “a real one” – Placement() and Placement(anchor=...) are dimension-neutral and pass anywhere.

Examples

from pybosl2 import Anchor, Placement, cuboid

upright = Placement(anchor=Anchor.BOTTOM)
cuboid([40, 30, 20], placement=upright).show()
Loading 3-D preview…

⬇ Download STL mesh

Parameters:
anchor : Anchor | Sequence[float]

spin : float

orient : Anchor | Sequence[float]

anchor : Anchor | Sequence[float]

The point on the shape that lands at the origin.

spin : float

Rotation about Z in degrees, applied after anchoring.

orient : Anchor | Sequence[float]

The direction the shape’s top is rotated towards.

with_(*, anchor=None, spin=None, orient=None)[source]

Return a copy with the given members replaced.

Parameters:
anchor : Anchor | Sequence[float] | None

New anchor, or None to keep this one’s.

spin : float | None

New spin in degrees, or None to keep this one’s.

orient : Anchor | Sequence[float] | None

New orientation, or None to keep this one’s.

Returns:

A new Placement; this one is unchanged.

Return type:

Placement

Examples

>>> from pybosl2 import Anchor, Placement
>>> Placement(anchor=Anchor.BOTTOM).with_(spin=45).spin
45.0
orients()[source]

Whether this placement asks for an orientation the plane cannot give.

Returns:

True when orient differs from the default, which is what makes a placement three-dimensional. A placement that only anchors and spins reads in either dimension.

Return type:

bool

Examples

>>> from pybosl2 import Anchor, Placement
>>> Placement(anchor=Anchor.BOTTOM).orients()
False
>>> Placement(orient=Anchor.RIGHT).orients()
True
as_kwargs()[source]

Return the three members as keyword arguments.

Returns:

A mapping with anchor, spin and orient.

Return type:

dict[str, Any]

as_plane_kwargs()[source]

Return the members a 2-D constructor can honour.

Returns:

A mapping with anchor and spin. orient is absent because the plane has no third axis to turn a face towards; orients() says whether dropping it would lose anything, and resolve_placement_2d() refuses rather than drop it.

Return type:

dict[str, Any]

class pybosl2.groups.Facets(fn=None, fa=None, fs=None, res=None)[source]

Bases: object

The curve-resolution controls, carried as one value.

Mostly internal. SPEC R-1 requires every construction that approximates a curve to accept fn/fa/fs (or res) and pass them to everything it builds, and the rule is broken most often by a function that simply forgets to forward one of the four. One value is harder to drop than four parameters.

Callers wanting to set resolution should reach for use_defaults() instead, which sets it for a whole block without threading anything through any call (SPEC R-4). None in any member means “not given, decide for me” (SPEC D-4).

Examples

>>> from pybosl2.groups import Facets
>>> Facets(fn=64).as_kwargs()
{'fn': 64}
Parameters:
fn : int | None

fa : float | None

fs : float | None

res : int | None

fn : int | None

Fixed fragment count for a full circle.

fa : float | None

Minimum fragment angle in degrees.

fs : float | None

Minimum fragment size in millimetres.

res : int | None

Sampling resolution for the SDF backend.

classmethod ambient()[source]

Return the values use_defaults() currently has in force.

Returns:

A Facets holding the ambient values, each None where nothing is set.

Return type:

Facets

classmethod resolved(fn=None, fa=None, fs=None, res=None)[source]

Return the values to actually use: what the caller passed, over the ambient defaults.

This is the one place the rule lives (SPEC R-5: an explicit value always wins over an ambient one). resolve_facets() and resolve_res() both go through it, so the CSG facet controls and the SDF resolution cannot drift into resolving differently – they were two implementations of one rule before.

fn=0 passes through unchanged: it is the caller opting out of an ambient fn, and frag_count() reads any fn below 3 as “use fa/fs” (SPEC R-5).

Parameters:
fn : int | None

Caller-supplied fragment count, or None. Omitted, the ambient use_defaults(fn=...) value applies; fn=0 opts back out to fa/fs.

fa : float | None

Caller-supplied fragment angle, or None. Omitted, the ambient use_defaults(fa=...) value applies.

fs : float | None

Caller-supplied fragment size, or None. Omitted, the ambient use_defaults(fs=...) value applies.

res : int | None

Caller-supplied SDF resolution, or None. Omitted, the ambient use_defaults(res=...) value applies.

Returns:

A Facets with each member filled from the ambient default where the caller gave nothing, and still None where nothing is set anywhere.

Return type:

Facets

Examples

>>> from pybosl2 import use_defaults
>>> from pybosl2.groups import Facets
>>> with use_defaults(fn=64):
...     Facets.resolved(fa=6).as_kwargs()
{'fn': 64, 'fa': 6}
merge(other)[source]

Return these values with other’s non-None members taking precedence.

An explicitly passed value always beats an ambient one (SPEC R-5), so the caller’s Facets is the one passed as other.

Parameters:
other : Facets

The values that win where they are set.

Returns:

A new Facets.

Return type:

Facets

as_kwargs()[source]

Return just the members that are set, as keyword arguments.

Returns:

A mapping of the non-None members, so it can be splatted into a callee that declares only some of them.

Return type:

dict[str, Any]

class pybosl2.groups.EdgeTreatment(kind=EdgeTreatmentKind.NONE, size=0.0)[source]

Bases: object

What happens to an edge: a rounding of some radius, a chamfer of some size, or nothing.

Rounding and chamfer are mutually exclusive on one edge – an edge is rounded or chamfered, never both – and the library checked that in six places with six different wordings, none of which said what to do instead. As one value the conflict is not checkable, it is unrepresentable: there is one size and one kind, so there is nothing to disagree.

Build one with rounding() or chamfer() rather than the constructor, so the kind and the size are set together.

Examples

from pybosl2 import EdgeTreatment, cuboid

cuboid([40, 30, 20], treatment=EdgeTreatment.rounding(4)).show()
Loading 3-D preview…

⬇ Download STL mesh

Parameters:
kind : EdgeTreatmentKind

size : float | Sequence[float]

kind : EdgeTreatmentKind

Which treatment, or NONE.

size : float | Sequence[float]

The radius (rounding) or inset (chamfer). Negative rounds outward, as BOSL2’s does. A sequence gives a size per corner, which the 2-D constructors accept.

classmethod rounding(size)[source]

Return a rounding treatment of radius size.

Parameters:
size : float | Sequence[float]

The rounding radius, or one radius per corner.

Returns:

An EdgeTreatment.

Return type:

EdgeTreatment

Examples

>>> from pybosl2 import EdgeTreatment
>>> EdgeTreatment.rounding(4).as_kwargs()
{'rounding': 4.0}
classmethod chamfer(size)[source]

Return a chamfer treatment of inset size.

Parameters:
size : float | Sequence[float]

The chamfer size, inset from the sides, or one per corner.

Returns:

An EdgeTreatment.

Return type:

EdgeTreatment

Examples

>>> from pybosl2 import EdgeTreatment
>>> EdgeTreatment.chamfer(2).as_kwargs()
{'chamfer': 2.0}
classmethod none()[source]

Return the treatment that leaves edges sharp.

Returns:

An EdgeTreatment that contributes no arguments.

Return type:

EdgeTreatment

as_kwargs()[source]

Return the treatment as the keyword argument a constructor declares.

Returns:

{"rounding": size}, {"chamfer": size}, or {} for no treatment – so it can be splatted into a constructor that names the two separately.

Return type:

dict[str, Any]

class pybosl2.groups.EdgeSelection(edges=Anchor.ALL, excepted=None)[source]

Bases: object

Which edges a treatment applies to, and which are spared.

The pair travels together on 15 callables and neither member means much alone: edges without except_edges is the common case, and except_edges without edges reads as “all of them but these”, which is what the default makes it. Unlike EdgeTreatment the two are not exclusive – they compose, the second narrowing the first.

Both are expressed in the anchor language (SPEC C-10, O-6b), never as strings.

Examples

from pybosl2 import Anchor, EdgeSelection, EdgeTreatment, cuboid

top_only = EdgeSelection(edges=Anchor.TOP)
cuboid([40, 30, 20], treatment=EdgeTreatment.rounding(4), selection=top_only).show()
Loading 3-D preview…

⬇ Download STL mesh

Parameters:
edges : Any

excepted : Any

edges : Any

The edges to treat. Defaults to every edge.

excepted : Any

The edges to spare, spelled except_edges at a call site because except is a Python keyword (SPEC B2-3).

as_kwargs()[source]

Return the pair as the keyword arguments a constructor declares.

Returns:

A mapping with edges and, when one is set, except_edges.

Return type:

dict[str, Any]

class pybosl2.groups.Texturing(texture=None, size=None, reps=None, depth=1.0, inset=False)[source]

Bases: object

A surface texture and how it is applied: the five parameters that always travel together.

They travel together on all 11 callables that take more than one of them, which is the cleanest group in the library by that measure. It could not be built until there was something to group – every one of those parameters refused until T37 built the application half of the texture subsystem (SPEC S-34, S-35).

size and reps are alternatives, and the group holds at most one, so the pair cannot disagree (SPEC G-7): give the tile’s size in millimetres, or how many times it repeats.

Examples

from pybosl2 import Texturing, cyl

cyl(height=30, radius=12, texturing=Texturing("ribs", reps=[16, 1], depth=1.5)).show()
Loading 3-D preview…

⬇ Download STL mesh

Parameters:
texture : Any

size : float | Sequence[float] | None

reps : int | Sequence[int] | None

depth : float

inset : float | bool

texture : Any

The texture, by name or already built.

size : float | Sequence[float] | None

Size of one tile as [around, along] in millimetres.

reps : int | Sequence[int] | None

Repeat counts as [around, along], instead of size.

depth : float

How far the texture displaces the surface. Negative sinks it in.

inset : float | bool

How far the surface is sunk before the texture is added. True means one depth.

as_kwargs()[source]

Return the group as the keyword arguments a constructor declares.

Returns:

A mapping with texture, tex_depth and tex_inset, plus whichever of tex_size/tex_reps is set.

Return type:

dict[str, Any]

pybosl2.groups.resolve_center_anchor(*, center, anchor, centred, uncentred)[source]

Fold BOSL2’s center= shorthand into the anchor language (SPEC G-1, B2-3).

center= is a placement option wearing a boolean: True means “sit on the origin”, False means “sit on the shape’s own base”. BOSL2 gives it precedence over anchor=anchor = center==true ? CENTER : center==false ? uncentred : anchor – and this is the one place that rule is written.

It was written in eleven places before T40, in three spellings and two contradicting precedences. Seven gave center precedence, which is right. The other four spelled it inline as use_anchor = anchor; if use_anchor is None: use_anchor = CENTER if center is None or center else BOTTOM, which lets anchor win — while their own docstrings said “center: if given, overrides anchor”. cyl(height=10, radius=5, anchor=TOP, center=False) sat on its top face, and the documentation next to it said it would sit on the bottom one. That is E-5’s silent wrong answer, and it survived because the rule had no single home to be right in.

Both are named at the call site rather than defaulted, because each backend anchors in its own vocabulary: the CSG backend passes Anchor members and the SDF backend passes the raw direction vectors of pybosl2/sdf/_constants.py. One type variable spans all three anchors so the resolver hands back exactly what it was given.

Parameters:
center : bool | None

True for a centred anchor, False for uncentred, None to leave anchor be.

anchor : _CenterAnchorT

The anchor as passed, used only when center is None.

centred : _CenterAnchorT

What center=True means for this shape.

uncentred : _CenterAnchorT

What center=False means – BOTTOM for the cylinders, BOTTOM_FRONT_LEFT for the boxes.

Returns:

The anchor to place with. None propagates, so a constructor whose own default depends on more than this (SPEC D-4) still gets to compute it.

Return type:

_CenterAnchorT

pybosl2.groups.resolve_placement(placement, anchor, spin, orient, function, *, defaults=None)[source]

Resolve a placement group against the loose members, refusing a call that gives both.

SPEC G-3: the loose spellings survive, and supplying a group and one of its members is an error rather than a silent preference – the same rule a radius and its own diameter follow (SPEC D-5), for the same reason: the call cannot mean two things at once.

Parameters:
placement : Placement | None

The group, or None.

anchor : Anchor | Sequence[float] | None

The loose anchor as passed.

spin : float | None

The loose spin as passed.

orient : Anchor | Sequence[float] | None

The loose orient as passed.

function : str

Name of the calling function, for the error message.

defaults : Placement | None

What the loose members default to, so “given” can be told from “left alone”. Defaults to Placement’s own defaults.

Returns:

The three values to use.

Raises:

Bosl2ValueError – if placement is given together with any of the loose members.

Return type:

tuple[Anchor | Sequence[float] | None, float | None, Anchor | Sequence[float] | None]

pybosl2.groups.resolve_placement_2d(placement, anchor, spin, function, *, defaults=None)[source]

Resolve a placement for a 2-D constructor, which has an anchor and a spin but no orient.

A separate function rather than a flag on resolve_placement(), because a boolean that selects how many values come back is a second function wearing the first one’s name (SPEC S-19b).

Parameters:
placement : Placement | None

The group, or None.

anchor : _AnchorT

The loose anchor as passed.

spin : _SpinT

The loose spin as passed.

function : str

Name of the calling function, for the error message.

defaults : Placement | None

What the loose members default to, so “given” can be told from “left alone”.

Returns:

The anchor and spin to use.

Raises:

Bosl2ValueError – if placement is given together with either loose member (SPEC G-3), or if it carries a real orient, which the plane cannot honour (SPEC E-5).

Return type:

tuple[__SPHINX_IMMATERIAL_TYPE_VAR__V__AnchorT | Anchor | Sequence[float], __SPHINX_IMMATERIAL_TYPE_VAR__V__SpinT | float]

pybosl2.groups.resolve_edge_treatment(treatment, rounding, chamfer, function, *, per_corner=True)[source]

Resolve an edge-treatment group against the loose rounding/chamfer.

Parameters:
treatment : EdgeTreatment | None

The group, or None.

rounding : _RoundingT

The loose rounding as passed.

chamfer : _ChamferT

The loose chamfer as passed.

function : str

Name of the calling function, for the error message.

per_corner : bool

Whether this constructor takes a size per corner. A 3-D primitive takes one size for the whole solid, so a per-corner group handed to it is refused here rather than left to fail as a TypeError from inside the backend (SPEC E-1, E-4).

Returns:

The rounding and chamfer to use, at most one of them set. Where a group decides the answer, the other comes back as 0 – “off” – rather than None, because None means “decide for me” and the group has just decided (SPEC D-4).

Raises:

Bosl2ValueError – if the group is given beside either loose member (SPEC G-3), if both loose members are given (the rule above), or if a per-corner group is given to a constructor that takes one size.

Return type:

tuple[__SPHINX_IMMATERIAL_TYPE_VAR__V__RoundingT, __SPHINX_IMMATERIAL_TYPE_VAR__V__ChamferT]

pybosl2.groups.resolve_edge_selection(selection, edges, except_edges, function)[source]

Resolve an edge-selection group against the loose edges/except_edges.

Parameters:
selection : EdgeSelection | None

The group, or None.

edges : Any

The loose edges as passed.

except_edges : Any

The loose except_edges as passed.

function : str

Name of the calling function, for the error message.

Returns:

The edges and except_edges to use.

Raises:

Bosl2ValueError – if the group is given beside either loose member (SPEC G-3).

Return type:

tuple[Any, Any]

pybosl2.groups.resolve_texturing(texturing, texture, tex_size, tex_reps, tex_depth, tex_inset, function)[source]

Resolve a texturing group against the loose tex_* arguments.

Parameters:
texturing : Texturing | None

The group, or None.

texture : Any

The loose texture as passed.

tex_size : float | Sequence[float] | None

The loose tex_size as passed.

tex_reps : int | Sequence[int] | None

The loose tex_reps as passed.

tex_depth : float | None

The loose tex_depth as passed.

tex_inset : float | bool | None

The loose tex_inset as passed.

function : str

Name of the calling function, for the error message.

Returns:

The (texture, tex_size, tex_reps, tex_depth, tex_inset) to use.

Raises:

Bosl2ValueError – if the group is given beside any loose member (SPEC G-3).

Return type:

tuple[Any, float | Sequence[float] | None, int | Sequence[int] | None, float | None, float | bool | None]