sdf.PolarPattern

sdf.PolarPattern(sdf, count, axis='z', *, skip=())

Union of count copies rotated evenly around an axis.

Copy i is the child rotated by i * 2π/count about the axis; evaluated as the minimum over all rotated child evaluations, so the count must be a static Python int. Copy 0 is the original, which is why the child’s analytic faces still land on the result.

The default axis is the local z axis through the origin. A bolt circle rarely sits there, and no letter can be made to fit it either: a name says which way an axis points but not where it is, and where it is happens to be most of what a bolt circle means. So any other axis is given as a line — an :class:~cadjoint.construction.faces.Axis, most usefully the one a revolve already declares as solid.axis.

Parameters

Name Type Description Default
sdf Child shape (SDF node or plain callable). required
count int Number of copies (static Python int, >= 1). required
axis str 'z' for the local z axis through the origin, or an Axis to rotate about that line. 'z'
skip Instance indices in 1 .. count - 1 that are not emitted. A ring of ribs or bolts is a pattern right up until something else — a coolant gallery, a cable exit, a keyway — has to occupy one of its stations, and then the choice is between abandoning the pattern and suppressing an instance. The kept copies keep the angles they would have had: instance i is still at i * 2π/count, so suppressing one leaves a gap rather than respacing the ring. Instance 0 is the seed the child’s faces are declared against and cannot be suppressed — rotate the seed instead. ()

Example

# Six screws on the bearing housing's own axis of revolution.
bolts = PolarPattern(screw, count=6, axis=housing.axis)

# Eight gussets, minus the two the coolant gallery passes through.
ribs = PolarPattern(rib, count=8, axis=housing.axis, skip=(3, 5))

Attributes

Name Description
skip The instance indices this pattern suppresses, ascending.

Methods

Name Description
patch_fields Every kept copy’s patches, instance-major.
sdf Pure function for the polar pattern (count must be concrete).

patch_fields

sdf.PolarPattern.patch_fields()

Every kept copy’s patches, instance-major.

:meth:sdf is a min over the kept instances of the child evaluated at p rotated by -i * 2π/count about the pattern axis, so the ring’s surface is the union of those copies’ surfaces and the decomposition is the child’s fields under each kept instance’s point map. Instance 0 is left unrotated, exactly as :meth:sdf leaves it — origin + (p - origin) only equals p up to rounding, and copy 0 is the seed the child’s faces belong to.

The result holds len(kept) * len(child fields) fields laid out instance-major: all of instance kept[0]’s patches, then all of kept[1]’s, in the order :func:_kept_instances returns. Patch j of instance slot n is index n * len(child fields) + j — the slot, not the instance index, since suppressed copies contribute no fields at all.

Ownership by argmin_i |f_i| is exact wherever the copies are disjoint (a ring of bolts or ribs); where two copies overlap, the seam between them is a CSG seam the consumer re-derives from the surfaces like any other.

sdf

sdf.PolarPattern.sdf(child_sdf, p, count, origin, direction, skip_mask=0)

Pure function for the polar pattern (count must be concrete).

Copies 1..N-1 are traced once, over an array of instance angles, so the emitted program holds two copies of the pattern’s geometry (the unrotated original and the batched rest) rather than count of them. Copy 0 stays a separate, unrotated evaluation: origin + (p - origin) is only equal to p up to rounding, and copy 0 is the one the child’s face references are declared against. Under :func:~cadjoint.sdf._lowering.scalar_lowering every instance is unrolled again, because a shader cannot carry the batch axis.