Skip to content

API Reference

igniscad

AlignmentMixin

A mixin of class Entity, specified to calculate alignment, bounding box and position.

Source code in src/igniscad/mixins.py
class AlignmentMixin:
    """
    A mixin of class Entity, specified to calculate alignment, bounding box and position.
    """
    # Syntactic properties
    @property
    def bbox(self):
        """Grabbing the bounding box of the entity."""
        if TYPE_CHECKING:
            assert isinstance(self, Entity)
        return self.wrap_result(self.part).bounding_box()

    @property
    def top(self):
        """Grabbing the Z pos of the center point of the top surface of the entity."""
        return self.bbox.max.Z

    @property
    def right(self):
        """Grabbing the maximum X pos."""
        return self.bbox.max.X

    @property
    def radius(self):
        """Estimate the radius of the entity(only for spheres and/or cylinders)."""
        # Simple logic: Grabbing half the width in the X direction.
        return self.bbox.size.X / 2

    @staticmethod
    def _vector_from_input(position) -> bd.Vector:
        """Normalize a supported point-like input into a build123d.Vector."""
        if isinstance(position, bd.Vector):
            return bd.Vector(position)
        if hasattr(position, "center"):
            return bd.Vector(position.center())
        return bd.Vector(position)

    @staticmethod
    def _location_from_input(position) -> bd.Location:
        """Normalize a supported location-like input into a build123d.Location."""
        if isinstance(position, bd.Location):
            return bd.Location(position)
        return bd.Location(AlignmentMixin._vector_from_input(position))

    def add_joint(self, name: str, position=(0, 0, 0), rotation=(0, 0, 0)) -> Joint:
        """
        Define a named joint on the entity.

        Args:
            name (str): Joint name.
            position: Local position relative to the entity origin. Accepts a
                tuple/list, build123d.Vector, build123d.Location, or any object
                exposing ``center()``.
            rotation: Optional local rotation in degrees as ``(x, y, z)``.

        Returns:
            Joint: The created joint instance.
        """
        if TYPE_CHECKING:
            assert isinstance(self, Entity)

        if isinstance(position, bd.Location):
            joint_location = bd.Location(position)
        else:
            joint_location = bd.Location(self._vector_from_input(position), rotation)

        joint = Joint(owner=self, location=joint_location, name=name)
        self.joints[name] = joint
        return joint

    def add_joint_on_face(self, name: str, face="top", offset=0, rotation=(0, 0, 0)) -> Joint:
        """
        Define a named joint at the center of one bbox face.

        Args:
            name (str): Joint name.
            face (str): ``top``, ``bottom``, ``left``, ``right``, ``front``, ``back``.
            offset (float): Extra distance along the face normal.
            rotation: Optional local rotation in degrees as ``(x, y, z)``.
        """
        center = self.bbox.center()
        f = face.lower()

        if f == "top":
            position = (center.X, center.Y, self.bbox.max.Z + offset)
        elif f == "bottom":
            position = (center.X, center.Y, self.bbox.min.Z - offset)
        elif f == "right":
            position = (self.bbox.max.X + offset, center.Y, center.Z)
        elif f == "left":
            position = (self.bbox.min.X - offset, center.Y, center.Z)
        elif f == "back":
            position = (center.X, self.bbox.max.Y + offset, center.Z)
        elif f == "front":
            position = (center.X, self.bbox.min.Y - offset, center.Z)
        else:
            raise ValueError(f"Unknown face: {face}. Use top/bottom/left/right/front/back")

        return self.add_joint(name, position=position, rotation=rotation)

    def joint(self, name: str) -> Joint:
        """Return a previously defined named joint."""
        if TYPE_CHECKING:
            assert isinstance(self, Entity)
        try:
            return self.joints[name]
        except KeyError as exc:
            raise ValueError(f"Unknown joint: {name}") from exc

    def join(self, target, offset=(0, 0, 0)):
        """
        Join the current entity to a joint-like target by pure translation.

        Args:
            target: A ``Joint``, ``build123d.Location``, point-like object, or an
                object exposing ``center()``.
            offset: Additional translation after the joint alignment.
        """
        if TYPE_CHECKING:
            assert isinstance(self, Entity)

        source_joint = self.joint("default") if "default" in self.joints else Joint(self, bd.Location())

        source_position = source_joint.position
        target_location = target.world_location if isinstance(target, Joint) else self._location_from_input(target)
        target_position = target_location.position
        extra = self._vector_from_input(offset)

        dx = target_position.X - source_position.X + extra.X
        dy = target_position.Y - source_position.Y + extra.Y
        dz = target_position.Z - source_position.Z + extra.Z
        return self.move(dx, dy, dz)

    # Universal alignment (syntactic)
    def align(self, target, face="top", offset=0):
        """
        *Snap* the current Entity to a specified face of the target.

        Calculation:
            Center pos of the target surface + Half of the current's thickness + Additional gap

        Args:
            target (Entity): The target Entity
            face (str): "top", "bottom", "left", "right", "front", "back"
            offset (float): Addition gap between the current and the target.
                (A positive number refers to a gap and a negative number refers to an embedding.)
        """
        # Grabbing the bounding box.
        t_box = target.bbox  # Target Bounding Box
        s_box = self.bbox  # Self Bounding Box (Current)

        # The default goal pos is target.center().
        dest_x = t_box.center().X
        dest_y = t_box.center().Y
        dest_z = t_box.center().Z

        # Own size (Width, Depth, Height)
        s_w = s_box.size.X
        s_d = s_box.size.Y
        s_h = s_box.size.Z

        # Adjust the goal pos according to the *face* argument.
        # Calculation:
        # Center pos of the target surface +/- Half of the current's thickness +/- Additional gap
        f = face.lower()

        # Z direction
        if f == "top":
            dest_z = t_box.max.Z + (s_h / 2) + offset
        elif f == "bottom":
            dest_z = t_box.min.Z - (s_h / 2) - offset

        # X direction
        elif f == "right":
            dest_x = t_box.max.X + (s_w / 2) + offset
        elif f == "left":
            dest_x = t_box.min.X - (s_w / 2) - offset

        # Y direction
        elif f == "back":
            dest_y = t_box.max.Y + (s_d / 2) + offset
        elif f == "front":
            dest_y = t_box.min.Y - (s_d / 2) - offset
        else:
            raise ValueError(f"❌ Unknown face: {face}. Use top/bottom/left/right/front/back")

        # Calculate the displacement vector (target.center() - current.center())
        # Necessary!
        curr_x = s_box.center().X
        curr_y = s_box.center().Y
        curr_z = s_box.center().Z

        dx = dest_x - curr_x
        dy = dest_y - curr_y
        dz = dest_z - curr_z

        if TYPE_CHECKING:
            assert isinstance(self, Entity)
        return self.move(dx, dy, dz)

    # Syntactic Sugar for AI agents

    def on_top_of(self, target, offset=0):
        return self.align(target, "top", offset)

    def under(self, target, offset=0):
        return self.align(target, "bottom", offset)

    def right_of(self, target, offset=0):
        return self.align(target, "right", offset)

    def left_of(self, target, offset=0):
        return self.align(target, "left", offset)

    def in_front_of(self, target, offset=0):
        return self.align(target, "front", offset)

    def behind(self, target, offset=0):
        return self.align(target, "back", offset)

bbox property

Grabbing the bounding box of the entity.

radius property

Estimate the radius of the entity(only for spheres and/or cylinders).

right property

Grabbing the maximum X pos.

top property

Grabbing the Z pos of the center point of the top surface of the entity.

add_joint(name, position=(0, 0, 0), rotation=(0, 0, 0))

Define a named joint on the entity.

Parameters:

Name Type Description Default
name str

Joint name.

required
position

Local position relative to the entity origin. Accepts a tuple/list, build123d.Vector, build123d.Location, or any object exposing center().

(0, 0, 0)
rotation

Optional local rotation in degrees as (x, y, z).

(0, 0, 0)

Returns:

Name Type Description
Joint Joint

The created joint instance.

Source code in src/igniscad/mixins.py
def add_joint(self, name: str, position=(0, 0, 0), rotation=(0, 0, 0)) -> Joint:
    """
    Define a named joint on the entity.

    Args:
        name (str): Joint name.
        position: Local position relative to the entity origin. Accepts a
            tuple/list, build123d.Vector, build123d.Location, or any object
            exposing ``center()``.
        rotation: Optional local rotation in degrees as ``(x, y, z)``.

    Returns:
        Joint: The created joint instance.
    """
    if TYPE_CHECKING:
        assert isinstance(self, Entity)

    if isinstance(position, bd.Location):
        joint_location = bd.Location(position)
    else:
        joint_location = bd.Location(self._vector_from_input(position), rotation)

    joint = Joint(owner=self, location=joint_location, name=name)
    self.joints[name] = joint
    return joint

add_joint_on_face(name, face='top', offset=0, rotation=(0, 0, 0))

Define a named joint at the center of one bbox face.

Parameters:

Name Type Description Default
name str

Joint name.

required
face str

top, bottom, left, right, front, back.

'top'
offset float

Extra distance along the face normal.

0
rotation

Optional local rotation in degrees as (x, y, z).

(0, 0, 0)
Source code in src/igniscad/mixins.py
def add_joint_on_face(self, name: str, face="top", offset=0, rotation=(0, 0, 0)) -> Joint:
    """
    Define a named joint at the center of one bbox face.

    Args:
        name (str): Joint name.
        face (str): ``top``, ``bottom``, ``left``, ``right``, ``front``, ``back``.
        offset (float): Extra distance along the face normal.
        rotation: Optional local rotation in degrees as ``(x, y, z)``.
    """
    center = self.bbox.center()
    f = face.lower()

    if f == "top":
        position = (center.X, center.Y, self.bbox.max.Z + offset)
    elif f == "bottom":
        position = (center.X, center.Y, self.bbox.min.Z - offset)
    elif f == "right":
        position = (self.bbox.max.X + offset, center.Y, center.Z)
    elif f == "left":
        position = (self.bbox.min.X - offset, center.Y, center.Z)
    elif f == "back":
        position = (center.X, self.bbox.max.Y + offset, center.Z)
    elif f == "front":
        position = (center.X, self.bbox.min.Y - offset, center.Z)
    else:
        raise ValueError(f"Unknown face: {face}. Use top/bottom/left/right/front/back")

    return self.add_joint(name, position=position, rotation=rotation)

align(target, face='top', offset=0)

Snap the current Entity to a specified face of the target.

Calculation

Center pos of the target surface + Half of the current's thickness + Additional gap

Parameters:

Name Type Description Default
target Entity

The target Entity

required
face str

"top", "bottom", "left", "right", "front", "back"

'top'
offset float

Addition gap between the current and the target. (A positive number refers to a gap and a negative number refers to an embedding.)

0
Source code in src/igniscad/mixins.py
def align(self, target, face="top", offset=0):
    """
    *Snap* the current Entity to a specified face of the target.

    Calculation:
        Center pos of the target surface + Half of the current's thickness + Additional gap

    Args:
        target (Entity): The target Entity
        face (str): "top", "bottom", "left", "right", "front", "back"
        offset (float): Addition gap between the current and the target.
            (A positive number refers to a gap and a negative number refers to an embedding.)
    """
    # Grabbing the bounding box.
    t_box = target.bbox  # Target Bounding Box
    s_box = self.bbox  # Self Bounding Box (Current)

    # The default goal pos is target.center().
    dest_x = t_box.center().X
    dest_y = t_box.center().Y
    dest_z = t_box.center().Z

    # Own size (Width, Depth, Height)
    s_w = s_box.size.X
    s_d = s_box.size.Y
    s_h = s_box.size.Z

    # Adjust the goal pos according to the *face* argument.
    # Calculation:
    # Center pos of the target surface +/- Half of the current's thickness +/- Additional gap
    f = face.lower()

    # Z direction
    if f == "top":
        dest_z = t_box.max.Z + (s_h / 2) + offset
    elif f == "bottom":
        dest_z = t_box.min.Z - (s_h / 2) - offset

    # X direction
    elif f == "right":
        dest_x = t_box.max.X + (s_w / 2) + offset
    elif f == "left":
        dest_x = t_box.min.X - (s_w / 2) - offset

    # Y direction
    elif f == "back":
        dest_y = t_box.max.Y + (s_d / 2) + offset
    elif f == "front":
        dest_y = t_box.min.Y - (s_d / 2) - offset
    else:
        raise ValueError(f"❌ Unknown face: {face}. Use top/bottom/left/right/front/back")

    # Calculate the displacement vector (target.center() - current.center())
    # Necessary!
    curr_x = s_box.center().X
    curr_y = s_box.center().Y
    curr_z = s_box.center().Z

    dx = dest_x - curr_x
    dy = dest_y - curr_y
    dz = dest_z - curr_z

    if TYPE_CHECKING:
        assert isinstance(self, Entity)
    return self.move(dx, dy, dz)

join(target, offset=(0, 0, 0))

Join the current entity to a joint-like target by pure translation.

Parameters:

Name Type Description Default
target

A Joint, build123d.Location, point-like object, or an object exposing center().

required
offset

Additional translation after the joint alignment.

(0, 0, 0)
Source code in src/igniscad/mixins.py
def join(self, target, offset=(0, 0, 0)):
    """
    Join the current entity to a joint-like target by pure translation.

    Args:
        target: A ``Joint``, ``build123d.Location``, point-like object, or an
            object exposing ``center()``.
        offset: Additional translation after the joint alignment.
    """
    if TYPE_CHECKING:
        assert isinstance(self, Entity)

    source_joint = self.joint("default") if "default" in self.joints else Joint(self, bd.Location())

    source_position = source_joint.position
    target_location = target.world_location if isinstance(target, Joint) else self._location_from_input(target)
    target_position = target_location.position
    extra = self._vector_from_input(offset)

    dx = target_position.X - source_position.X + extra.X
    dy = target_position.Y - source_position.Y + extra.Y
    dz = target_position.Z - source_position.Z + extra.Z
    return self.move(dx, dy, dz)

joint(name)

Return a previously defined named joint.

Source code in src/igniscad/mixins.py
def joint(self, name: str) -> Joint:
    """Return a previously defined named joint."""
    if TYPE_CHECKING:
        assert isinstance(self, Entity)
    try:
        return self.joints[name]
    except KeyError as exc:
        raise ValueError(f"Unknown joint: {name}") from exc

Axis

Bases: Enum

An enumeration for the three Cartesian axes (X, Y, Z).

Source code in src/igniscad/selectors.py
class Axis(Enum):
    """An enumeration for the three Cartesian axes (X, Y, Z)."""
    X = "X"
    Y = "Y"
    Z = "Z"

BaseContainer

Bases: Entity

The base class of all containers. Supports the context manager syntax and overrides the "<<" operator to capture models.

Source code in src/igniscad/containers.py
class BaseContainer(Entity):
    """
    The base class of all containers.
    Supports the context manager syntax and overrides the "<<" operator to capture models.
    """
    def __init__(self, name: str):
        """
        Args:
            name (str): name of the Group in context registry.
        """
        super().__init__(part=None, name=name)
        # Calling transforming functions right after initialization may cause an AttributeError
        # That is WAI.

    # Context manager for *with* statements.
    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        if self.part:
            self.part.label = self.name  # names as labels
        return

    def __lshift__(self, other):
        """
        Override the "<<" operator.
        Must be inherited in subclasses.
        """
        pass

__init__(name)

Parameters:

Name Type Description Default
name str

name of the Group in context registry.

required
Source code in src/igniscad/containers.py
def __init__(self, name: str):
    """
    Args:
        name (str): name of the Group in context registry.
    """
    super().__init__(part=None, name=name)

__lshift__(other)

Override the "<<" operator. Must be inherited in subclasses.

Source code in src/igniscad/containers.py
def __lshift__(self, other):
    """
    Override the "<<" operator.
    Must be inherited in subclasses.
    """
    pass

Entity

Bases: AlignmentMixin, ModificationMixin, EntitySelectorMixin

A base class for every wrapped build123d objects. The original build123d objects can be called with entity.part .

Source code in src/igniscad/core.py
class Entity(AlignmentMixin, ModificationMixin, EntitySelectorMixin):
    """
    A base class for every wrapped build123d objects.
    The original build123d objects can be called with entity.part .
    """
    def __init__(
        self,
        part: Union[bd.BasePartObject, bd.BaseSketchObject, bd.Part, bd.Face, bd.Shape] | None,
        name=None,
        tags=None,
        joints=None,
    ):
        self.part = part
        self.name = name
        self.tags = defaultdict(list)
        if tags:
            self.tags.update(tags)
        self.joints = dict(joints or {})
        for joint_name, joint in list(self.joints.items()):
            self.joints[joint_name] = joint.__class__(owner=self, location=joint.location, name=joint.name)

    def get_by_tag(self, tag: str):
        """
        Get a selector for objects with a given tag.
        """
        from igniscad.selectors import Selector
        items = self.tags.get(tag, [])
        return Selector(items, parent=self)

    # Transition logic
    def move(self, x=0, y=0, z=0):
        """
        Move the entity to a specific position.
        """
        new_part = self.wrap_result(self.part.moved(bd.Location((x, y, z))))
        return self.__class__(new_part, self.name, self.tags, self.joints)

    def rotate(self, x=0, y=0, z=0):
        """
        Rotate the entity to a specific angle and position.
        """
        p = self.part
        if x: p = p.rotate(bd.Axis.X, x)
        if y: p = p.rotate(bd.Axis.Y, y)
        if z: p = p.rotate(bd.Axis.Z, z)
        return self.__class__(p, self.name, self.tags, self.joints)

    # Set-like operations
    @staticmethod
    def wrap_result(res):
        """
        Inner helper function to wrap the result into a *single* build123d object.
        The *show()* function require a single Compound or Solid object to save the .stl file.
        """
        if not isinstance(res, (bd.Compound, bd.Solid, bd.Shell)):
            res = bd.Compound(res)
        return res

    # Overriding the operators.
    def __sub__(self, other):
        return self.__class__(bd.Part(self.wrap_result(self.part - other.part)), self.name, self.tags, self.joints)

    def __add__(self, other):
        return self.__class__(bd.Part(self.wrap_result(self.part + other.part)), self.name, self.tags, self.joints)

    def __and__(self, other):
        return self.__class__(bd.Part(self.wrap_result(self.part & other.part)), self.name, self.tags, self.joints)

get_by_tag(tag)

Get a selector for objects with a given tag.

Source code in src/igniscad/core.py
def get_by_tag(self, tag: str):
    """
    Get a selector for objects with a given tag.
    """
    from igniscad.selectors import Selector
    items = self.tags.get(tag, [])
    return Selector(items, parent=self)

move(x=0, y=0, z=0)

Move the entity to a specific position.

Source code in src/igniscad/core.py
def move(self, x=0, y=0, z=0):
    """
    Move the entity to a specific position.
    """
    new_part = self.wrap_result(self.part.moved(bd.Location((x, y, z))))
    return self.__class__(new_part, self.name, self.tags, self.joints)

rotate(x=0, y=0, z=0)

Rotate the entity to a specific angle and position.

Source code in src/igniscad/core.py
def rotate(self, x=0, y=0, z=0):
    """
    Rotate the entity to a specific angle and position.
    """
    p = self.part
    if x: p = p.rotate(bd.Axis.X, x)
    if y: p = p.rotate(bd.Axis.Y, y)
    if z: p = p.rotate(bd.Axis.Z, z)
    return self.__class__(p, self.name, self.tags, self.joints)

wrap_result(res) staticmethod

Inner helper function to wrap the result into a single build123d object. The show() function require a single Compound or Solid object to save the .stl file.

Source code in src/igniscad/core.py
@staticmethod
def wrap_result(res):
    """
    Inner helper function to wrap the result into a *single* build123d object.
    The *show()* function require a single Compound or Solid object to save the .stl file.
    """
    if not isinstance(res, (bd.Compound, bd.Solid, bd.Shell)):
        res = bd.Compound(res)
    return res

EntitySelectorMixin

A mixin for the Entity class that provides methods to initiate selections.

This mixin adds the .faces(), .edges(), and .vertices() methods to the Entity class, which are the entry points for the fluent selection API.

Source code in src/igniscad/mixins.py
class EntitySelectorMixin:
    """
    A mixin for the Entity class that provides methods to initiate selections.

    This mixin adds the .faces(), .edges(), and .vertices() methods to the
    Entity class, which are the entry points for the fluent selection API.
    """
    def faces(self) -> FaceSelector:
        """Selects all faces of the entity.

        This method retrieves all faces from the underlying build123d part
        and wraps them in a FaceSelector, enabling chainable filtering and
        modification.

        Returns:
            FaceSelector: A selector containing all faces of the entity.
        """
        if TYPE_CHECKING:
            assert isinstance(self, Entity)
        return FaceSelector(self.part.faces(), parent=self)

    def edges(self) -> EdgeSelector:
        """Selects all edges of the entity.

        This method retrieves all edges from the underlying build123d part
        and wraps them in an EdgeSelector, enabling chainable filtering and
        modification.

        Returns:
            EdgeSelector: A selector containing all edges of the entity.
        """
        if TYPE_CHECKING:
            assert isinstance(self, Entity)
        return EdgeSelector(self.part.edges(), parent=self)

    def vertices(self) -> VertexSelector:
        """Selects all vertices of the entity.

        This method retrieves all vertices from the underlying build123d part
        and wraps them in a VertexSelector, enabling chainable filtering and
        modification.

        Returns:
            VertexSelector: A selector containing all vertices of the entity.
        """
        if TYPE_CHECKING:
            assert isinstance(self, Entity)
        return VertexSelector(self.part.vertices(), parent=self)

edges()

Selects all edges of the entity.

This method retrieves all edges from the underlying build123d part and wraps them in an EdgeSelector, enabling chainable filtering and modification.

Returns:

Name Type Description
EdgeSelector EdgeSelector

A selector containing all edges of the entity.

Source code in src/igniscad/mixins.py
def edges(self) -> EdgeSelector:
    """Selects all edges of the entity.

    This method retrieves all edges from the underlying build123d part
    and wraps them in an EdgeSelector, enabling chainable filtering and
    modification.

    Returns:
        EdgeSelector: A selector containing all edges of the entity.
    """
    if TYPE_CHECKING:
        assert isinstance(self, Entity)
    return EdgeSelector(self.part.edges(), parent=self)

faces()

Selects all faces of the entity.

This method retrieves all faces from the underlying build123d part and wraps them in a FaceSelector, enabling chainable filtering and modification.

Returns:

Name Type Description
FaceSelector FaceSelector

A selector containing all faces of the entity.

Source code in src/igniscad/mixins.py
def faces(self) -> FaceSelector:
    """Selects all faces of the entity.

    This method retrieves all faces from the underlying build123d part
    and wraps them in a FaceSelector, enabling chainable filtering and
    modification.

    Returns:
        FaceSelector: A selector containing all faces of the entity.
    """
    if TYPE_CHECKING:
        assert isinstance(self, Entity)
    return FaceSelector(self.part.faces(), parent=self)

vertices()

Selects all vertices of the entity.

This method retrieves all vertices from the underlying build123d part and wraps them in a VertexSelector, enabling chainable filtering and modification.

Returns:

Name Type Description
VertexSelector VertexSelector

A selector containing all vertices of the entity.

Source code in src/igniscad/mixins.py
def vertices(self) -> VertexSelector:
    """Selects all vertices of the entity.

    This method retrieves all vertices from the underlying build123d part
    and wraps them in a VertexSelector, enabling chainable filtering and
    modification.

    Returns:
        VertexSelector: A selector containing all vertices of the entity.
    """
    if TYPE_CHECKING:
        assert isinstance(self, Entity)
    return VertexSelector(self.part.vertices(), parent=self)

Group

Bases: BaseContainer

Combine multiple entities into a single Group Entity. Support the same context-manager syntax (as Model does). Entities within a group are automatically unioned. The Group object can be moved or aligned like a normal Entity outside the with statements.

Source code in src/igniscad/containers.py
class Group(BaseContainer):
    """
    Combine multiple entities into a single Group Entity.
    Support the same context-manager syntax (as Model does).
    Entities within a group are automatically unioned.
    The Group object can be moved or aligned like a normal Entity outside the *with* statements.
    """

    def __init__(self, name=None):
        """
        Args:
            name: name of the Group in context registry.
        """
        super().__init__(name=name)

    def __lshift__(self, other):
        """
        Override the "<<" operator.
        Usage: group << Entity(...)

        Every entity added is unioned automatically.
        """
        if isinstance(other, Entity):
            if self.part is None:
                # Initialize a new group with the current part as a basement.
                self.part = other.part
            else:
                # Union the new part with the old parts.
                self.part += other.part
        else:
            raise ValueError("❌ Group implies adding Entity objects (Box, Cylinder, etc.)")

        return self

__init__(name=None)

Parameters:

Name Type Description Default
name

name of the Group in context registry.

None
Source code in src/igniscad/containers.py
def __init__(self, name=None):
    """
    Args:
        name: name of the Group in context registry.
    """
    super().__init__(name=name)

__lshift__(other)

Override the "<<" operator. Usage: group << Entity(...)

Every entity added is unioned automatically.

Source code in src/igniscad/containers.py
def __lshift__(self, other):
    """
    Override the "<<" operator.
    Usage: group << Entity(...)

    Every entity added is unioned automatically.
    """
    if isinstance(other, Entity):
        if self.part is None:
            # Initialize a new group with the current part as a basement.
            self.part = other.part
        else:
            # Union the new part with the old parts.
            self.part += other.part
    else:
        raise ValueError("❌ Group implies adding Entity objects (Box, Cylinder, etc.)")

    return self

Joint dataclass

A named alignment anchor attached to an Entity.

The location is stored relative to the owning entity so joints remain valid after the entity is moved or rotated.

Source code in src/igniscad/mixins.py
@dataclass(frozen=True)
class Joint:
    """
    A named alignment anchor attached to an Entity.

    The location is stored relative to the owning entity so joints remain valid
    after the entity is moved or rotated.
    """
    owner: "Entity"
    location: bd.Location
    name: str | None = None

    @property
    def world_location(self) -> bd.Location:
        """Return the joint location in world coordinates."""
        owner_location = self.owner.part.location or bd.Location()
        return owner_location * self.location

    @property
    def position(self) -> bd.Vector:
        """Return the joint position in world coordinates."""
        return self.world_location.position

position property

Return the joint position in world coordinates.

world_location property

Return the joint location in world coordinates.

Model

Bases: BaseContainer

A context manager to capture generated models(Entity objects).

Source code in src/igniscad/containers.py
class Model(BaseContainer):
    """
    A context manager to capture generated models(Entity objects).
    """

    def __init__(self, name: str) -> None:
        """
        Args:
            name (str): name of the Group in context registry.
        """
        super().__init__(name=name)
        self.registry = {}

    # Operator overriding
    def __lshift__(self, other):
        """
        Override the "<<" operator.
        Usage: model << Entity(...)
        """
        if isinstance(other, Entity):
            self.part = self.part + other.part if self.part else other.part
            if other.name:
                self.registry[other.name] = other
        return self

    # Registry utils

    # Note: advantages of using a registry:
    # being *disvariabled*
    # you can define an entity without wrapping them into a variable
    # variables are easy to be ripped off between different contexts
    # you can call this entity anywhere through model.f(<entity.name_in_registry>)
    # you can also edit the registry by yourself, but that's not recommended.
    def find(self, name: str):
        """
        To find an Entity by its name in the registry.
        Args:
            name (str): Entity name
        """
        if name in self.registry:
            return self.registry[name]
        raise ValueError(f"❌ Part '{name}' not found in this model.")

    def f(self, name: str):
        return self.find(name)

__init__(name)

Parameters:

Name Type Description Default
name str

name of the Group in context registry.

required
Source code in src/igniscad/containers.py
def __init__(self, name: str) -> None:
    """
    Args:
        name (str): name of the Group in context registry.
    """
    super().__init__(name=name)
    self.registry = {}

__lshift__(other)

Override the "<<" operator. Usage: model << Entity(...)

Source code in src/igniscad/containers.py
def __lshift__(self, other):
    """
    Override the "<<" operator.
    Usage: model << Entity(...)
    """
    if isinstance(other, Entity):
        self.part = self.part + other.part if self.part else other.part
        if other.name:
            self.registry[other.name] = other
    return self

find(name)

To find an Entity by its name in the registry. Args: name (str): Entity name

Source code in src/igniscad/containers.py
def find(self, name: str):
    """
    To find an Entity by its name in the registry.
    Args:
        name (str): Entity name
    """
    if name in self.registry:
        return self.registry[name]
    raise ValueError(f"❌ Part '{name}' not found in this model.")

ModificationMixin

A mixin of class Entity, specified to perform modification operations.

Source code in src/igniscad/mixins.py
class ModificationMixin:
    """
    A mixin of class Entity, specified to perform modification operations.
    """
    def fillet(self, radius, edges=None):
        """
        Fillets the edges of the entity.

        Args:
            radius (float): The radius of the fillet.
            edges (list, optional): A list of edges to fillet. If None, all edges are filleted. Defaults to None.
        """
        if TYPE_CHECKING:
            assert isinstance(self, Entity)

        edges_to_fillet = edges
        if edges_to_fillet is None:
            edges_to_fillet = self.part.edges()

        # Convert primitive to a generic Solid by using its .wrapped property
        part_as_solid = bd.Solid(self.part.wrapped)
        new_part = part_as_solid.fillet(radius, edge_list=edges_to_fillet)
        return self.__class__(self.wrap_result(new_part), self.name, self.tags, self.joints)

    def chamfer(self, distance, edges=None):
        """
        Chamfers the edges of the entity.

        Args:
            distance (float): The distance of the chamfer.
            edges (list, optional): A list of edges to chamfer. If None, all edges are chamfered. Defaults to None.
        """
        if TYPE_CHECKING:
            assert isinstance(self, Entity)

        edges_to_chamfer = edges
        if edges_to_chamfer is None:
            edges_to_chamfer = self.part.edges()

        # Convert primitive to a generic Solid by using its .wrapped property
        part_as_solid = bd.Solid(self.part.wrapped)
        new_part = part_as_solid.chamfer(distance, distance, edge_list=edges_to_chamfer)
        return self.__class__(self.wrap_result(new_part), self.name, self.tags, self.joints)

    def shell(self):
        """
        Creates a shell of the entity.
        """
        if TYPE_CHECKING:
            assert isinstance(self, Entity)

        shell_obj = self.part.shell()

        return self.__class__(shell_obj, self.name, self.tags, self.joints)

    def offset(self, distance, kind=bd.Kind.ARC):
        """
        Offsets the entity.

        Args:
            distance (float): The distance to offset.
            kind (str, optional): The kind of offset to perform. Defaults to 'arc'.
        """
        if TYPE_CHECKING:
            assert isinstance(self, Entity)

        try:
            new_part = bd.offset(self.part, amount=distance, kind=kind)
        except RuntimeError as e:
            if "Unexpected result type" in str(e):
                # This error can happen with complex sketches like text, where offsetting
                # a character results in a shape that build123d doesn't expect.
                # The workaround is to offset each face of the compound individually
                # and combine the results, skipping any faces that fail to offset.
                all_faces = self.part.faces()
                offset_faces = []
                for face in all_faces:
                    try:
                        # Each face of a text object is a letter.
                        offset_result = bd.offset(face, amount=distance, kind=kind)
                        if isinstance(offset_result, bd.Face):
                            offset_faces.append(offset_result)
                        elif isinstance(offset_result, bd.Compound):
                            offset_faces.extend(offset_result.faces())
                    except RuntimeError:
                        pass

                if not offset_faces:
                    raise RuntimeError("Offset operation failed for all faces.") from e

                new_part = bd.Sketch(offset_faces)
            else:
                raise e

        return self.__class__(self.wrap_result(new_part), self.name, self.tags, self.joints)

chamfer(distance, edges=None)

Chamfers the edges of the entity.

Parameters:

Name Type Description Default
distance float

The distance of the chamfer.

required
edges list

A list of edges to chamfer. If None, all edges are chamfered. Defaults to None.

None
Source code in src/igniscad/mixins.py
def chamfer(self, distance, edges=None):
    """
    Chamfers the edges of the entity.

    Args:
        distance (float): The distance of the chamfer.
        edges (list, optional): A list of edges to chamfer. If None, all edges are chamfered. Defaults to None.
    """
    if TYPE_CHECKING:
        assert isinstance(self, Entity)

    edges_to_chamfer = edges
    if edges_to_chamfer is None:
        edges_to_chamfer = self.part.edges()

    # Convert primitive to a generic Solid by using its .wrapped property
    part_as_solid = bd.Solid(self.part.wrapped)
    new_part = part_as_solid.chamfer(distance, distance, edge_list=edges_to_chamfer)
    return self.__class__(self.wrap_result(new_part), self.name, self.tags, self.joints)

fillet(radius, edges=None)

Fillets the edges of the entity.

Parameters:

Name Type Description Default
radius float

The radius of the fillet.

required
edges list

A list of edges to fillet. If None, all edges are filleted. Defaults to None.

None
Source code in src/igniscad/mixins.py
def fillet(self, radius, edges=None):
    """
    Fillets the edges of the entity.

    Args:
        radius (float): The radius of the fillet.
        edges (list, optional): A list of edges to fillet. If None, all edges are filleted. Defaults to None.
    """
    if TYPE_CHECKING:
        assert isinstance(self, Entity)

    edges_to_fillet = edges
    if edges_to_fillet is None:
        edges_to_fillet = self.part.edges()

    # Convert primitive to a generic Solid by using its .wrapped property
    part_as_solid = bd.Solid(self.part.wrapped)
    new_part = part_as_solid.fillet(radius, edge_list=edges_to_fillet)
    return self.__class__(self.wrap_result(new_part), self.name, self.tags, self.joints)

offset(distance, kind=bd.Kind.ARC)

Offsets the entity.

Parameters:

Name Type Description Default
distance float

The distance to offset.

required
kind str

The kind of offset to perform. Defaults to 'arc'.

ARC
Source code in src/igniscad/mixins.py
def offset(self, distance, kind=bd.Kind.ARC):
    """
    Offsets the entity.

    Args:
        distance (float): The distance to offset.
        kind (str, optional): The kind of offset to perform. Defaults to 'arc'.
    """
    if TYPE_CHECKING:
        assert isinstance(self, Entity)

    try:
        new_part = bd.offset(self.part, amount=distance, kind=kind)
    except RuntimeError as e:
        if "Unexpected result type" in str(e):
            # This error can happen with complex sketches like text, where offsetting
            # a character results in a shape that build123d doesn't expect.
            # The workaround is to offset each face of the compound individually
            # and combine the results, skipping any faces that fail to offset.
            all_faces = self.part.faces()
            offset_faces = []
            for face in all_faces:
                try:
                    # Each face of a text object is a letter.
                    offset_result = bd.offset(face, amount=distance, kind=kind)
                    if isinstance(offset_result, bd.Face):
                        offset_faces.append(offset_result)
                    elif isinstance(offset_result, bd.Compound):
                        offset_faces.extend(offset_result.faces())
                except RuntimeError:
                    pass

            if not offset_faces:
                raise RuntimeError("Offset operation failed for all faces.") from e

            new_part = bd.Sketch(offset_faces)
        else:
            raise e

    return self.__class__(self.wrap_result(new_part), self.name, self.tags, self.joints)

shell()

Creates a shell of the entity.

Source code in src/igniscad/mixins.py
def shell(self):
    """
    Creates a shell of the entity.
    """
    if TYPE_CHECKING:
        assert isinstance(self, Entity)

    shell_obj = self.part.shell()

    return self.__class__(shell_obj, self.name, self.tags, self.joints)

Box(x, y, z, name=None)

Wrapped function for build123d.Box.

Parameters:

Name Type Description Default
x float

X coordinate

required
y float

Y coordinate

required
z float

Z coordinate

required
name str

name of the Entity in context registry

None
Source code in src/igniscad/primitives.py
@validate_dimensions("x", "y", "z")
def Box(x: float, y: float, z: float, name: str = None) -> Entity:
    """
    Wrapped function for build123d.Box.

    Args:
        x (float): X coordinate
        y (float): Y coordinate
        z (float): Z coordinate
        name (str): name of the Entity in context registry
    """
    return Entity(bd.Box(x, y, z, align=(bd.Align.CENTER, bd.Align.CENTER, bd.Align.CENTER)), name)

Circle(r, name=None)

Creates a 2D circle sketch.

Parameters:

Name Type Description Default
r float

Radius of the circle.

required
name str

Name of the Entity in the context registry.

None
Source code in src/igniscad/primitives_2d.py
@validate_dimensions("r")
def Circle(r: float, name: str = None) -> Entity:
    """
    Creates a 2D circle sketch.

    Args:
        r (float): Radius of the circle.
        name (str): Name of the Entity in the context registry.
    """
    sketch = bd.Circle(r)
    return Entity(sketch, name)

Cone(bottom_radius, top_radius, h, name=None)

Wrapped function for build123d.Cone.

Parameters:

Name Type Description Default
bottom_radius float

bottom radius

required
top_radius float

top radius

required
h float

height

required
name str

name of the Entity in context registry

None
Source code in src/igniscad/primitives.py
@validate_dimensions("bottom_radius", "top_radius", "h")
def Cone(bottom_radius: float, top_radius: float, h: float, name: str = None) -> Entity:
    """
    Wrapped function for build123d.Cone.

    Args:
        bottom_radius (float): bottom radius
        top_radius (float): top radius
        h (float): height
        name (str): name of the Entity in context registry
    """
    return Entity(bd.Cone(bottom_radius=bottom_radius, top_radius=top_radius, height=h, align=(bd.Align.CENTER, bd.Align.CENTER, bd.Align.CENTER)), name)

CounterBoreHole(radius, cb_radius, cb_depth, height, name=None)

Creates a counter-bore hole shape (for boolean subtraction). AI description: A hole for a socket head cap screw.

Parameters:

Name Type Description Default
radius float

Through-hole radius (for the screw shaft)

required
cb_radius float

Counter-bore radius (for the screw head)

required
cb_depth float

Counter-bore depth

required
height float

Total height of the hole

required
name str

Name of the Entity in the context registry

None
Source code in src/igniscad/primitives.py
@validate_dimensions("radius", "cb_radius", "cb_depth", "height")
def CounterBoreHole(radius: float, cb_radius: float, cb_depth: float, height: float, name: str = None) -> Entity:
    """
    Creates a counter-bore hole shape (for boolean subtraction).
    AI description: A hole for a socket head cap screw.

    Args:
        radius (float): Through-hole radius (for the screw shaft)
        cb_radius (float): Counter-bore radius (for the screw head)
        cb_depth (float): Counter-bore depth
        height (float): Total height of the hole
        name (str): Name of the Entity in the context registry
    """
    # 1. Create the main through-hole shaft and convert to Part
    shaft_part = bd.Part(bd.Cylinder(radius=radius, height=height, align=(bd.Align.CENTER, bd.Align.CENTER, bd.Align.CENTER)))

    # 2. Create the counter-bore head and convert to Part
    head_part = bd.Part(bd.Cylinder(radius=cb_radius, height=cb_depth, align=(bd.Align.CENTER, bd.Align.CENTER, bd.Align.CENTER)))

    # 3. Move the head part to align with the top of the shaft
    head_offset = (height / 2) - (cb_depth / 2)
    head_moved_part = head_part.locate(bd.Location(bd.Vector(0, 0, head_offset)))

    # 4. Combine the parts
    combined_part = shaft_part + head_moved_part

    return Entity(combined_part, name)

CountersinkHole(radius, csk_radius, csk_angle, height, name=None)

Creates a countersink hole shape (for boolean subtraction). AI description: A hole for a countersunk screw.

Parameters:

Name Type Description Default
radius float

Through-hole radius

required
csk_radius float

Countersink top radius

required
csk_angle float

Countersink angle in degrees (e.g., 82, 90)

required
height float

Total height of the hole

required
name str

Name of the Entity in the context registry

None
Source code in src/igniscad/primitives.py
@validate_dimensions("radius", "csk_radius", "csk_angle", "height")
def CountersinkHole(radius: float, csk_radius: float, csk_angle: float, height: float, name: str = None) -> Entity:
    """
    Creates a countersink hole shape (for boolean subtraction).
    AI description: A hole for a countersunk screw.

    Args:
        radius (float): Through-hole radius
        csk_radius (float): Countersink top radius
        csk_angle (float): Countersink angle in degrees (e.g., 82, 90)
        height (float): Total height of the hole
        name (str): Name of the Entity in the context registry
    """
    # Calculate countersink depth from the angle and radii
    csk_depth = (csk_radius - radius) / math.tan(math.radians(csk_angle / 2))

    # 1. Create the main through-hole shaft and convert to Part
    shaft_part = bd.Part(bd.Cylinder(radius=radius, height=height, align=(bd.Align.CENTER, bd.Align.CENTER, bd.Align.CENTER)))

    # 2. Create the countersink head (a cone) and convert to Part
    head_part = bd.Part(bd.Cone(bottom_radius=radius, top_radius=csk_radius, height=csk_depth, align=(bd.Align.CENTER, bd.Align.CENTER, bd.Align.CENTER)))

    # 3. Move the head part to align with the top of the shaft
    head_offset = (height / 2) - (csk_depth / 2)
    head_moved_part = head_part.locate(bd.Location(bd.Vector(0, 0, head_offset)))

    # 4. Combine the parts
    combined_part = shaft_part + head_moved_part

    return Entity(combined_part, name)

Cylinder(r, h, name=None)

Wrapped function for build123d.Cylinder.

Parameters:

Name Type Description Default
r float

radius

required
h float

height

required
name str

name of the Entity in context registry

None
Source code in src/igniscad/primitives.py
@validate_dimensions("r", "h")
def Cylinder(r: float, h: float, name: str = None) -> Entity:
    """
    Wrapped function for build123d.Cylinder.

    Args:
        r (float): radius
        h (float): height
        name (str): name of the Entity in context registry
    """
    return Entity(bd.Cylinder(radius=r, height=h, align=(bd.Align.CENTER, bd.Align.CENTER, bd.Align.CENTER)), name)

Extrude(sketch, amount, name=None)

Extrudes a 2D sketch into a 3D part.

Parameters:

Name Type Description Default
sketch Entity

The 2D sketch entity to extrude.

required
amount float

The extrusion distance.

required
name str

Name of the new 3D Entity.

None
Source code in src/igniscad/primitives_2d.py
@validate_dimensions("amount")
def Extrude(sketch: Entity, amount: float, name: str = None) -> Entity:
    """
    Extrudes a 2D sketch into a 3D part.

    Args:
        sketch (Entity): The 2D sketch entity to extrude.
        amount (float): The extrusion distance.
        name (str): Name of the new 3D Entity.
    """
    if not isinstance(sketch.part, (bd.Sketch, bd.Face)):
        raise TypeError(f"Extrude operation requires a 2D sketch or face, not {type(sketch.part)}.")

    part = bd.extrude(sketch.part, amount=amount)
    return Entity(part, name)

ISO_Hole(size, depth, fit='Normal', name=None)

Creates a standard ISO metric screw clearance hole.

Parameters:

Name Type Description Default
size str

"M2", "M3", "M4", "M5", "M6", "M8", "M10", "M12"

required
depth float

Depth of the hole

required
fit str

"Close" (tight fit), "Normal" (standard), "Loose" (clearance)

'Normal'
name str

Name of the Entity in the context registry

None
Source code in src/igniscad/primitives.py
def ISO_Hole(size: str, depth: float, fit: str = "Normal", name: str = None) -> Entity:
    """
    Creates a standard ISO metric screw clearance hole.

    Args:
        size (str): "M2", "M3", "M4", "M5", "M6", "M8", "M10", "M12"
        depth (float): Depth of the hole
        fit (str): "Close" (tight fit), "Normal" (standard), "Loose" (clearance)
        name (str): Name of the Entity in the context registry
    """
    # ISO 273 clearance hole diameters (in mm)
    specs = {
        "M2": {"Close": 2.2, "Normal": 2.4, "Loose": 2.6},
        "M3": {"Close": 3.2, "Normal": 3.4, "Loose": 3.6},
        "M4": {"Close": 4.3, "Normal": 4.5, "Loose": 4.8},
        "M5": {"Close": 5.3, "Normal": 5.5, "Loose": 5.8},
        "M6": {"Close": 6.4, "Normal": 6.6, "Loose": 7},
        "M8": {"Close": 8.4, "Normal": 9, "Loose": 10},
        "M10": {"Close": 10.5, "Normal": 11, "Loose": 12},
        "M12": {"Close": 13, "Normal": 13.5, "Loose": 14.5},
    }

    size_upper = size.upper()
    if size_upper not in specs:
        raise ValueError(f"Unsupported ISO hole size: {size}. Supported sizes are: {list(specs.keys())}")
    if fit not in specs[size_upper]:
        raise ValueError(f"Unsupported fit: {fit}. Supported fits are: {list(specs[size_upper].keys())}")

    diameter = specs[size_upper][fit]
    radius = diameter / 2

    return Cylinder(r=radius, h=depth, name=name)

Loft(*sketches, name=None)

Creates a 3D part by lofting through a series of 2D sketches.

Parameters:

Name Type Description Default
*sketches Entity

A sequence of 2D sketch entities to loft through.

()
name str

Name of the new 3D Entity.

None
Source code in src/igniscad/primitives_2d.py
def Loft(*sketches: Entity, name: str = None) -> Entity:
    """
    Creates a 3D part by lofting through a series of 2D sketches.

    Args:
        *sketches (Entity): A sequence of 2D sketch entities to loft through.
        name (str): Name of the new 3D Entity.
    """
    profiles = []
    for sketch in sketches:
        if not isinstance(sketch.part, (bd.Sketch, bd.Face, bd.Wire)):
            raise TypeError("Loft requires sketches, faces, or wires.")
        profiles.append(sketch.part)

    part = bd.loft(sections=profiles)
    return Entity(part, name)

Polygon(*points, name=None)

Creates a 2D polygon from a list of points.

Parameters:

Name Type Description Default
*points

A list of (x, y) tuples representing the vertices of the polygon.

()
name str

Name of the Entity in the context registry.

None
Source code in src/igniscad/primitives_2d.py
@validate_vertices
def Polygon(*points, name: str = None) -> Entity:
    """
    Creates a 2D polygon from a list of points.

    Args:
        *points: A list of (x, y) tuples representing the vertices of the polygon.
        name (str): Name of the Entity in the context registry.
    """
    sketch = bd.Polygon(*points)
    return Entity(sketch, name)

Rectangle(x, y, name=None)

Creates a 2D rectangle sketch.

Parameters:

Name Type Description Default
x float

Width of the rectangle.

required
y float

Height of the rectangle.

required
name str

Name of the Entity in the context registry.

None
Source code in src/igniscad/primitives_2d.py
@validate_dimensions("x", "y")
def Rectangle(x: float, y: float, name: str = None) -> Entity:
    """
    Creates a 2D rectangle sketch.

    Args:
        x (float): Width of the rectangle.
        y (float): Height of the rectangle.
        name (str): Name of the Entity in the context registry.
    """
    sketch = bd.Rectangle(x, y)
    return Entity(sketch, name)

Revolve(sketch, axis=bd.Axis.X, name=None)

Revolves a 2D sketch around an axis to create a 3D part.

Parameters:

Name Type Description Default
sketch Entity

The 2D sketch entity to revolve.

required
axis Axis

The axis of revolution. Defaults to X-axis.

X
name str

Name of the new 3D Entity.

None
Source code in src/igniscad/primitives_2d.py
def Revolve(sketch: Entity, axis: bd.Axis = bd.Axis.X, name: str = None) -> Entity:
    """
    Revolves a 2D sketch around an axis to create a 3D part.

    Args:
        sketch (Entity): The 2D sketch entity to revolve.
        axis (bd.Axis): The axis of revolution. Defaults to X-axis.
        name (str): Name of the new 3D Entity.
    """
    if not isinstance(sketch.part, (bd.Sketch, bd.Face)):
        raise TypeError("Revolve operation requires a 2D sketch or face.")

    part = bd.revolve(sketch.part, axis=axis)
    return Entity(part, name)

Slot(w, h, d, name=None)

Wrapped function for a 3D slot.

Parameters:

Name Type Description Default
w float

slot width on the 2D sketch plane

required
h float

slot height on the 2D sketch plane(diameter)

required
d float

slot depth (extrusion amount)

required
name str

name of the Entity in context registry

None
Source code in src/igniscad/primitives.py
@validate_dimensions("w", "h", "d")
def Slot(w: float, h: float, d: float, name: str = None) -> Entity:
    """
    Wrapped function for a 3D slot.

    Args:
        w (float): slot width on the 2D sketch plane
        h (float): slot height on the 2D sketch plane(diameter)
        d (float): slot depth (extrusion amount)
        name (str): name of the Entity in context registry
    """
    slot_sketch = bd.Sketch(bd.SlotOverall(width=w, height=h / 2))
    # Extrude in both directions from the sketch plane to center the part
    part = bd.extrude(slot_sketch, amount=d / 2, both=True)

    return Entity(part, name)

Sphere(r, name=None)

Wrapped function for build123d.Sphere.

Parameters:

Name Type Description Default
r float

radius

required
name str

name of the Entity in context registry

None
Source code in src/igniscad/primitives.py
@validate_dimensions("r")
def Sphere(r: float, name: str = None) -> Entity:
    """
    Wrapped function for build123d.Sphere.

    Args:
        r (float): radius
        name (str): name of the Entity in context registry
    """
    return Entity(bd.Sphere(radius=r), name)

Sweep(sketch, path, name=None)

Sweeps a 2D sketch along a path to create a 3D part.

Parameters:

Name Type Description Default
sketch Entity

The 2D profile sketch.

required
path Entity

The 1D path (e.g., a Line, Spline, or Wire).

required
name str

Name of the new 3D Entity.

None
Source code in src/igniscad/primitives_2d.py
def Sweep(sketch: Entity, path: Entity, name: str = None) -> Entity:
    """
    Sweeps a 2D sketch along a path to create a 3D part.

    Args:
        sketch (Entity): The 2D profile sketch.
        path (Entity): The 1D path (e.g., a Line, Spline, or Wire).
        name (str): Name of the new 3D Entity.
    """
    if not isinstance(sketch.part, (bd.Sketch, bd.Face)):
        raise TypeError("Sweep profile must be a 2D sketch or face.")
    if not isinstance(path.part, (bd.Edge, bd.Wire)):
        raise TypeError("Sweep path must be an Edge or Wire.")

    part = bd.sweep(sketch.part, path=path.part)
    return Entity(part, name)

Text(txt, font_size, font='Arial', name=None)

Creates a 2D text sketch.

Parameters:

Name Type Description Default
txt str

The text to be rendered.

required
font_size float

The size of the font.

required
font str

The name of the font.

'Arial'
name str

Name of the Entity in the context registry.

None
Source code in src/igniscad/primitives_2d.py
@validate_dimensions("font_size")
def Text(txt: str, font_size: float, font: str = "Arial", name: str = None) -> Entity:
    """
    Creates a 2D text sketch.

    Args:
        txt (str): The text to be rendered.
        font_size (float): The size of the font.
        font (str): The name of the font.
        name (str): Name of the Entity in the context registry.
    """
    sketch = bd.Text(txt, font_size=font_size, font=font)
    return Entity(sketch, name)

Torus(major, minor, name=None)

Wrapped function for build123d.Torus.

Parameters:

Name Type Description Default
major float

major radius

required
minor float

minor radius

required
name str

name of the Entity in context registry

None
Source code in src/igniscad/primitives.py
@validate_dimensions("major", "minor")
def Torus(major: float, minor: float, name: str = None) -> Entity:
    """
    Wrapped function for build123d.Torus.

    Args:
        major (float): major radius
        minor (float): minor radius
        name (str): name of the Entity in context registry
    """
    return Entity(bd.Torus(major_radius=major, minor_radius=minor), name)

Wedge(xsize, ysize, zsize, xmax, xmin, ymax, ymin, name=None)

Wrapped function for build123d.Wedge.

Parameters:

Name Type Description Default
xsize float

Base width (X)

required
ysize float

Base depth (Y)

required
zsize float

Height (Z)

required
xmax float

X coordinate of the top face end (relative to origin 0)

required
xmin float

X coordinate of the top face start (relative to origin 0)

required
ymax float

Y coordinate of the top face end (mapped to build123d zmax)

required
ymin float

Y coordinate of the top face start (mapped to build123d zmin)

required
name str

name of the Entity in context registry

None
Note

In build123d, the zmax and zmin params control the Y direction, which can be confusing. They are re-mapped to ymax and ymin here for clarity.

Source code in src/igniscad/primitives.py
@validate_dimensions("xsize", "ysize", "zsize", "xmax", "xmin", "ymax", "ymin")
def Wedge(xsize: float, ysize: float, zsize: float, xmax: float, xmin: float, ymax: float, ymin: float,
          name: str = None) -> Entity:
    """
    Wrapped function for build123d.Wedge.

    Args:
        xsize (float): Base width (X)
        ysize (float): Base depth (Y)
        zsize (float): Height (Z)
        xmax (float): X coordinate of the top face end (relative to origin 0)
        xmin (float): X coordinate of the top face start (relative to origin 0)
        ymax (float): Y coordinate of the top face end (mapped to build123d zmax)
        ymin (float): Y coordinate of the top face start (mapped to build123d zmin)
        name (str): name of the Entity in context registry

    Note:
        In build123d, the *zmax* and *zmin* params control the Y direction, which can be confusing.
        They are re-mapped to *ymax* and *ymin* here for clarity.
    """
    return Entity(bd.Wedge(xsize=xsize, ysize=ysize, zsize=zsize, xmax=xmax, xmin=xmin, zmax=ymax, zmin=ymin), name)

show(model, mode='fallback')

Visualize the specified model.

Parameters:

Name Type Description Default
model Model

The model to visualize.

required
mode Literal['fallback', 'yacv', 'export'] = "fallback"

The method of visualization. "Fallback": to export the file when YACV is unavailable. "yacv": to visualize the model via Yet Another CAD Viewer. "export": to export the model to a *.stl file.

'fallback'
Source code in src/igniscad/visualization.py
def show(model: Model, mode: Literal['fallback', 'yacv', 'export'] = "fallback") -> None:
    """
    Visualize the specified model.

    Args:
        model (Model): The model to visualize.
        mode (Literal['fallback', 'yacv', 'export'] = "fallback"): The method of visualization.
            "Fallback": to export the file when YACV is unavailable.
            "yacv": to visualize the model via Yet Another CAD Viewer.
            "export": to export the model to a *.stl file.
    """
    get_logger(__name__).info(f"Processing model: {model.name}")
    match mode:
        case "fallback":
            if not _show_yacv_model(model, force=False):
                get_logger(__name__).warning("Viewer not available. Exporting to disk...")
                _export_stl_file(model)
        case "yacv":
            _show_yacv_model(model, force=True)
        case "export":
            _export_stl_file(model)

    return

validate_dimensions(*args_to_check)

Validate whether the specified dimensions are strictly positive (> 0). Raises InfeasibleEntityError if <= 0.

Parameters:

Name Type Description Default
args_to_check str

the arguments to validate

()
Source code in src/igniscad/helpers/validator.py
def validate_dimensions(*args_to_check: str):
    """
    Validate whether the specified dimensions are strictly positive (> 0).
    Raises InfeasibleEntityError if <= 0.

    Args:
        args_to_check(str): the arguments to validate
    """

    def decorator(func):
        sig = inspect.signature(func)

        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            bound_args = sig.bind(*args, **kwargs)
            bound_args.apply_defaults()
            params = bound_args.arguments

            invalid_params = []
            for arg_name in args_to_check:
                if arg_name in params:
                    val = params[arg_name]

                    if val <= TOLERANCE:
                        invalid_params.append(f"{arg_name}={val}")

            if invalid_params:
                func_name = func.__name__
                provided_name = params.get("name")

                display_name = provided_name if provided_name else f"{func_name}<Pending>"
                error_detail = ", ".join(invalid_params)
                raise InfeasibleEntityError(
                    entity=SimpleNamespace(name=display_name),
                    reason=f"Dimensions must be positive. Invalid arguments: {error_detail}"
                )

            return func(*args, **kwargs)
        return wrapper
    return decorator

validate_vertices(arg_name='points', min_points=3)

Validator for vertices / points. Validate the vertex count and the coincidence (distance between adjacent vertices).

Parameters:

Name Type Description Default
arg_name str

the argument to check.

'points'
min_points int

the smallest count of points.

3
Source code in src/igniscad/helpers/validator.py
def validate_vertices(arg_name: str = "points", min_points=3):
    """
    Validator for vertices / points.
    Validate the vertex count and the coincidence (distance between adjacent vertices).

    Args:
        arg_name(str): the argument to check.
        min_points(int): the smallest count of points.
    """

    def decorator(func):
        sig = inspect.signature(func)

        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            bound_args = sig.bind(*args, **kwargs)
            bound_args.apply_defaults()
            params = bound_args.arguments

            points = params.get(arg_name)


            if not points:
                raise InfeasibleEntityError(
                    entity=SimpleNamespace(name=f"{func.__name__}<Pending>"),
                    reason="No vertices provided."
                )

            if len(points) < min_points:
                raise InfeasibleEntityError(
                    entity=SimpleNamespace(name=f"{func.__name__}<Pending>"),
                    reason=f"Polygon requires at least {min_points} vertices. Got {len(points)}."
                )

            for i in range(len(points)):
                p1 = points[i]
                p2 = points[(i + 1) % len(points)]

                dist = math.sqrt((p1[0] - p2[0]) ** 2 + (p1[1] - p2[1]) ** 2)

                if dist <= TOLERANCE:
                    raise InfeasibleEntityError(
                        entity=SimpleNamespace(name=f"{func.__name__}<Pending>"),
                        reason=(
                            f"Vertex {i} {p1} and Vertex {(i + 1) % len(points)} {p2} are too close "
                            f"(distance {dist:.2e} < tolerance {TOLERANCE}). "
                            "This would create a zero-length edge."
                        )
                    )

            return func(*args, **kwargs)

        return wrapper

    return decorator