Module piecad.primitives_2d

Create 2D objects such as circles and retangles.

Functions

def circle(radius: float, segments: int = -1) ‑> Obj2d
Expand source code
def circle(radius: float, segments: int = -1) -> Obj2d:
    """
    Make a circle of a given radius.

    For `segments` see the documentation of [`set_default_segments`](index.html#piecad.set_default_segments).

    Circles are created with the center at `(0,0)`
    """
    if segments == -1:
        segments = config["DefaultSegments"]
    _chkGT("radius", radius, 0.0)
    _chkGE("segments", segments, 3)

    if segments in _unit_circles:
        circ = _unit_circles[segments]
    else:
        circ = _m.CrossSection.circle(1, segments)
        _unit_circles[segments] = circ

    return Obj2d(circ.scale((radius, radius)))

Make a circle of a given radius.

For segments see the documentation of set_default_segments.

Circles are created with the center at (0,0)

def ellipse(radii: list[float, float], segments: int = -1) ‑> Obj2d
Expand source code
def ellipse(radii: list[float, float], segments: int = -1) -> Obj2d:
    """
    Make an ellipse with the given radii.

    For `segments` see the documentation of [`set_default_segments`](index.html#piecad.set_default_segments).

    Ellipses are created with the center at `(0,0)`
    """
    if segments == -1:
        segments = config["DefaultSegments"]
    _chkV2("radii", radii)
    _chkGE("segments", segments, 3)

    if segments in _unit_circles:
        circ = _unit_circles[segments]
    else:
        circ = _m.CrossSection.circle(1, segments)
        _unit_circles[segments] = circ

    return Obj2d(circ.scale(radii))

Make an ellipse with the given radii.

For segments see the documentation of set_default_segments.

Ellipses are created with the center at (0,0)

def polygon(paths: list[list[tuple[float, float]]], check: bool = True) ‑> Obj2d
Expand source code
def polygon(paths: list[list[tuple[float, float]]], check: bool = True) -> Obj2d:
    """
    Create a polygon from a single or multiple closed paths of points.

    Polygons follow the even/odd fill rule, meaning:
    * You can have multiple outer shapes.
    * Outer shapes can have holes.
    * Holes can have inner shapes.
    * Inner shapes can have holes
    * And so on.

    Holes can only occur inside a shape.
    A shape is either an outer shape or is inside a hole.

    Inside means fully contained inside. No intersections are allowed.

    By default we check if Clipper2 found a self-intersection.
    If it has, we issue a diagnostic message.
    The check takes about 40% more time, so in a heavily used
    situation, where you are absolutely sure you're not self-intersectiong,
    you can set `check` to 'False`.

    Be aware that if you set `check` to `False` that the underlying Clipper2
    library will attempt to "repair" any self-intersections.
    The "repair" is likely to cause a failed 3d print.
    (The "repair" is to emit two paths that overlap in one point, but
    are separate closed paths. These may get treated as two different objects
    that will be much too close together for 3d printing purposes.)

    If you understand winding, shapes are CCW, holes are CW.
    But Clipper2 automatically straightens it out for you.

    <iframe width="100%" height="300" src="examples/polygon.html"></iframe>
    """

    obj = Obj2d(_m.CrossSection(paths, _m.FillRule.EvenOdd))
    if check and len(obj.to_paths()) != len(paths):
        segments = []
        for path in paths:
            n = len(path)
            for i in range(0, n):
                segments.append((path[i], path[(i + 1) % n]))

        isects = _isect_segments_include_segments(segments)

        if len(isects) > 0:
            txt = []
            txt.append("ERROR: your polygon path(s) have self-intersection(s).\n")
            txt.append(
                "Intersections format: (Intersection_point, [(Segment1), (Segment2)])\n"
            )
            for isect in isects:
                txt.append(repr(isect) + "\n")
            raise ValidationError("".join(txt))
    return obj

Create a polygon from a single or multiple closed paths of points.

Polygons follow the even/odd fill rule, meaning: * You can have multiple outer shapes. * Outer shapes can have holes. * Holes can have inner shapes. * Inner shapes can have holes * And so on.

Holes can only occur inside a shape. A shape is either an outer shape or is inside a hole.

Inside means fully contained inside. No intersections are allowed.

By default we check if Clipper2 found a self-intersection. If it has, we issue a diagnostic message. The check takes about 40% more time, so in a heavily used situation, where you are absolutely sure you're not self-intersectiong, you can set check to 'False`.

Be aware that if you set check to False that the underlying Clipper2 library will attempt to "repair" any self-intersections. The "repair" is likely to cause a failed 3d print. (The "repair" is to emit two paths that overlap in one point, but are separate closed paths. These may get treated as two different objects that will be much too close together for 3d printing purposes.)

If you understand winding, shapes are CCW, holes are CW. But Clipper2 automatically straightens it out for you.

def rectangle(size: list[float, float], center: bool = False) ‑> Obj2d
Expand source code
def rectangle(size: list[float, float], center: bool = False) -> Obj2d:
    """
    Make a rectangle of a given size.

    By default, the bottom left corner of the rectangle will be at `(0,0)`.
    When `center` is `True` it will cause the rectangle to be centered at `(0,0)`.
    """
    if type(size) == float or type(size) == int:
        return square(size)
    _chkV2("size", size)

    return Obj2d(_m.CrossSection.square(size, center))

Make a rectangle of a given size.

By default, the bottom left corner of the rectangle will be at (0,0). When center is True it will cause the rectangle to be centered at (0,0).

def rounded_rectangle(size: list[float, float],
rounding_radius: float = 0.2,
segments: int = -1,
center: bool = False) ‑> Obj2d
Expand source code
def rounded_rectangle(
    size: list[float, float],
    rounding_radius: float = 0.2,
    segments: int = -1,
    center: bool = False,
) -> Obj2d:
    """
    Create a rectangle with rounded corners.

    The `rounded_rectangele` will have dimensions of `size` dimensions and with
    corners of `rounding_radius`.

    For `segments` see the documentation of [`set_default_segments`](index.html#piecad.set_default_segments).
    Each corner will be given approximately 1/4 of segments.

    By default, the bottom left corner of the square will be at `(0,0)`.
    When `center` is `True` it will cause the square to be centered at `(0,0)`.
    """
    if segments == -1:
        segments = config["DefaultSegments"]
    _chkGE("segments", segments, 3)
    _chkV2("size", size)
    _chkGT("rounding_radius", rounding_radius, 0)

    rr = rounding_radius
    circ = circle(rr, segments)

    x = size[0]
    y = size[1]

    c_x_off = -x / 2.0 if center else 0.0
    c_y_off = -y / 2.0 if center else 0.0

    return Obj2d(
        _m.CrossSection.batch_hull(
            [
                circ.translate((c_x_off + rr, c_y_off + rr)).mo,
                circ.translate((c_x_off + size[0] - rr, c_y_off + rr)).mo,
                circ.translate((c_x_off + size[0] - rr, c_y_off + size[1] - rr)).mo,
                circ.translate((c_x_off + rr, c_y_off + size[1] - rr)).mo,
            ]
        )
    )

    # segs_per_arc = segments // 4 + 1
    # deg_per_arc = 90.0 / segs_per_arc
    # pts = []
    # x, y = size

    # def make_arc_trig_vals(deg):
    #    end = deg + 90
    #    l = []
    #    for i in range(0, segs_per_arc - 1):
    #        l.append((cos(deg), sin(deg)))
    #        deg += deg_per_arc

    #    l.append((cos(end), sin(end)))
    #    return l

    # if segments in _arc_trig_vals_map:
    #    arc_trig_vals = _arc_trig_vals_map[segments]
    # else:
    #    arc_trig_vals = (
    #        make_arc_trig_vals(180),  # Bottom left
    #        make_arc_trig_vals(270),  # Bottom right
    #        make_arc_trig_vals(0),  # Top right
    #        make_arc_trig_vals(90),  # Top left
    #    )
    #    _arc_trig_vals_map[segments] = arc_trig_vals

    # rr = rounding_radius

    # pts = []
    # c_x_off = -x / 2.0 if center else 0.0
    # c_y_off = -y / 2.0 if center else 0.0

    # def arc(tvals, rad, x_off, y_off):
    #    x_off += c_x_off
    #    y_off += c_y_off
    #    for c, s in tvals:
    #        pts.append((x_off + rad * c, y_off + rad * s))

    # bl, br, tr, tl = arc_trig_vals
    # arc(bl, rr, rr, rr)  # Bottom left
    # arc(br, rr, x - rr, rr)  # Bottom right
    # arc(tr, rr, x - rr, y - rr)  # Top right
    # arc(tl, rr, rr, y - rr)  # Top left

    # return Obj2d(_m.CrossSection([pts], _m.FillRule.EvenOdd))

Create a rectangle with rounded corners.

The rounded_rectangele will have dimensions of size dimensions and with corners of rounding_radius.

For segments see the documentation of set_default_segments. Each corner will be given approximately 1/4 of segments.

By default, the bottom left corner of the square will be at (0,0). When center is True it will cause the square to be centered at (0,0).

def square(size: float, center: bool = False) ‑> Obj2d
Expand source code
def square(size: float, center: bool = False) -> Obj2d:
    """
    Make a square of a given size.

    By default, the bottom left corner of the square will be at `(0,0)`.
    When `center` is `True` it will cause the square to be centered at `(0,0)`.
    """
    if type(size) == list or type(size) == tuple:
        return rectangle(size)

    _chkGT("size", size, 0)

    return Obj2d(_m.CrossSection.square((size, size), center))

Make a square of a given size.

By default, the bottom left corner of the square will be at (0,0). When center is True it will cause the square to be centered at (0,0).

def star(num_points: int, outer_radius: float, inner_radius: float = 0.0) ‑> Obj2d
Expand source code
def star(num_points: int, outer_radius: float, inner_radius: float = 0.0) -> Obj2d:
    """
    Make a regular star of a given number of points.

    If `inner_radius` is `0.0` then it will be calculated based on outer_radius.

    Stars are created with the center at `(0,0)`.
    """
    _chkGE("num_points", num_points, 3)
    _chkGT("outer_radius", outer_radius, 0.0)
    _chkGE("inner_radius", inner_radius, 0.0)
    pts = []
    deg_per_np = 360.0 / num_points
    ido = deg_per_np / 2.0  # inner_degree_offset

    if inner_radius == 0.0:
        ratio = cos(360.0 / num_points) / cos(180 / num_points)
        inner_radius = outer_radius * ratio

    deg = 90
    for i in range(0, num_points):
        pts.append((outer_radius * cos(deg), outer_radius * sin(deg)))
        pts.append((inner_radius * cos(deg + ido), inner_radius * sin(deg + ido)))
        deg += deg_per_np

    return Obj2d(_m.CrossSection([pts]))

Make a regular star of a given number of points.

If inner_radius is 0.0 then it will be calculated based on outer_radius.

Stars are created with the center at (0,0).

def text(sz: float, tstr: str, inter_char_space=None)
Expand source code
def text(sz: float, tstr: str, inter_char_space=None):
    """
    Draw the unicode printable characters in `tstr` in shapes of size `sz`.

    The default font is `Roboto-Regular.ttf`.
    Also available is `Hack-Regular.ttf` (Monospaced).

    The default value for the spacing between characters (`inter_char_space`) is `sz/3.0`.
    """
    global _text_module
    if _text_module == None:
        _text_module = importlib.import_module("._text", "piecad")
    return _text_module._text_func(sz, tstr, inter_char_space=None)

Draw the unicode printable characters in tstr in shapes of size sz.

The default font is Roboto-Regular.ttf. Also available is Hack-Regular.ttf (Monospaced).

The default value for the spacing between characters (inter_char_space) is sz/3.0.

def text_set_font(font_name: str)
Expand source code
def text_set_font(font_name: str):
    """
    Set the current font used by `text`.
    Fonts are stored in the `_package_name_/fonts` directory.
    Or you can use a full pathname to the `.ttf` file.
    Most OpenType/TrueType fonts can be used.

    Only one font can be in use at a time. (The previous font is automatically closed.)

    The default font is `Roboto-Regular.ttf`.
    Also available is `Hack-Regular.ttf` (Monospaced).
    """
    global _text_module
    if _text_module == None:
        _text_module = importlib.import_module("._text", "piecad")
    return _text_module._set_font(font_name)

Set the current font used by text(). Fonts are stored in the _package_name_/fonts directory. Or you can use a full pathname to the .ttf file. Most OpenType/TrueType fonts can be used.

Only one font can be in use at a time. (The previous font is automatically closed.)

The default font is Roboto-Regular.ttf. Also available is Hack-Regular.ttf (Monospaced).