Module piecad.utilities

Miscellaneous (but important) functions

Functions

def load(filename: str) ‑> Obj2d | Obj3d
Expand source code
def load(filename: str) -> Obj3d | Obj2d:
    """
    Load a 3d object from a file.

    The format read from `filename` is determined by the file's extention.

    The available formats for 3D are:

    | Type        | Extension    |
    |:------------|:------------:|
    | 3MF         |   .3mf       |
    | GLB         |   .glb       |
    | GLTF        |   .gltf      |
    | OBJ         |   .obj       |
    | PLY         |   .ply       |
    | STL         |   .stl_ascii |
    | STL binary  |   .stl       |

    \\(See [https://github/mikedh/trimesh] for more formats.\\)

    Currently 2d objects are not supported.
    """
    dot_idx = filename.rindex(".")
    ext = filename[dot_idx + 1 :]
    mesh = trimesh.exchange.load.load(filename, ext, force="mesh")
    if type(mesh) == trimesh.path.Path2D:
        raise ValidationError("Currently 2d objects are no supported.")
    else:
        o = Obj3d(_m.Manifold.create_from_verts_and_faces(mesh.vertices, mesh.faces))

    return o

Load a 3d object from a file.

The format read from filename is determined by the file's extention.

The available formats for 3D are:

Type Extension
3MF .3mf
GLB .glb
GLTF .gltf
OBJ .obj
PLY .ply
STL .stl_ascii
STL binary .stl

(See [https://github/mikedh/trimesh] for more formats.)

Currently 2d objects are not supported.

def save(filename: str,
*objs: Obj2d | Obj3d) ‑> None
Expand source code
def save(filename: str, *objs: Obj3d | Obj2d) -> None:
    """
    Save a 3d or 2d object in a file suitable for printing, etc.

    The format placed in [p:filename] is determined by the file's extention.

    The available formats for 3D are:

    | Type        | Extension    |
    |:------------|:------------:|
    | 3MF         |   .3mf       |
    | GLB         |   .glb       |
    | GLTF        |   .gltf      |
    | OBJ         |   .obj       |
    | PLY         |   .ply       |
    | STL         |   .stl_ascii |
    | STL binary  |   .stl       |

    \\(See [https://github/mikedh/trimesh] for more formats.\\)

    For 2D, only the SVG (.svg) format is available.
    """
    _chkGE("len(objs)", len(objs), 1)
    dot_idx = filename.rindex(".")
    ext = filename[dot_idx + 1 :]
    if type(objs[0]) == Obj3d:
        if len(objs) == 1:
            obj = objs[0]
            mesh = obj.mo.to_mesh()
            if mesh.vert_properties.shape[1] > 3:
                vertices = mesh.vert_properties[:, :3]
            else:
                vertices = mesh.vert_properties
            mesh_output = trimesh.Trimesh(
                vertices=vertices, faces=mesh.tri_verts, process=False
            )
            if obj._color != None:
                mesh_output.visual.vertex_colors = obj._color
            # Manifold3d has a different definition than Trimesh
            if not mesh_output.is_watertight:
                print("WARNING: output mesh is not watertight")
            trimesh.exchange.export.export_mesh(mesh_output, filename, ext)
        else:
            scene = trimesh.Scene()
            for obj in objs:
                mesh = obj.mo.to_mesh()
                if mesh.vert_properties.shape[1] > 3:
                    vertices = mesh.vert_properties[:, :3]
                else:
                    vertices = mesh.vert_properties
                mesh_output = trimesh.Trimesh(
                    vertices=vertices, faces=mesh.tri_verts, process=False
                )
                if obj._color != None:
                    mesh_output.visual.vertex_colors = obj._color
                # Manifold3d has a different definition than Trimesh
                if not mesh_output.is_watertight:
                    print("WARNING: output mesh is not watertight")
                scene.add_geometry(mesh_output)
            trimesh.exchange.export.export_scene(scene, filename, ext)
        # trimesh obj file export does not end with newline
        # currently this upsets prusa_slicer
        if ext == "obj":
            with open(filename, "a") as f:
                f.write("\n")
    else:  # Obj2d
        if ext != "svg":
            raise (ValidationError("Only the SVG format is supported for Obj2d."))
        _save_svg(filename, *objs)

Save a 3d or 2d object in a file suitable for printing, etc.

The format placed in [p:filename] is determined by the file's extention.

The available formats for 3D are:

Type Extension
3MF .3mf
GLB .glb
GLTF .gltf
OBJ .obj
PLY .ply
STL .stl_ascii
STL binary .stl

(See [https://github/mikedh/trimesh] for more formats.)

For 2D, only the SVG (.svg) format is available.

def view(obj: Obj2d | Obj3d,
title: str = '') ‑> None
Expand source code
def view(obj: Obj3d | Obj2d, title: str = "") -> None:
    """
    Use CADView protocol to display the geometry object.

    Returns obj unchanged... so that it works well in return statements.
    """
    global _view_thread
    if _viewer_available == False:
        return

    _chkGO("obj", obj)

    if title == "":
        title = _info_str("view")

    if type(obj) == Obj2d:
        color = obj._color
        obj = Obj2d(_m.Manifold.extrude(obj.mo, 0.1))
        obj._color = color

    if _view_thread == None:
        _view_thread = threading.Thread(target=_view_handler, daemon=True)
        _view_thread.start()
        atexit.register(_tell_view_handler_to_exit)

    mesh = obj.mo.to_mesh()
    if mesh.vert_properties.shape[1] > 3:
        vertices = mesh.vert_properties[:, :3]
    else:
        vertices = mesh.vert_properties
    faces = mesh.tri_verts
    view_data = {}
    view_data["title"] = title
    view_data["color"] = [210, 180, 140] if obj._color == None else obj._color
    view_data["vertices"] = vertices.tolist()
    fl = faces.tolist()
    # for one in fl:
    #    one.insert(0, len(one))
    # fl.insert(0, len(fl))
    view_data["faces"] = fl
    _view_queue.put(view_data)
    return obj

Use CADView protocol to display the geometry object.

Returns obj unchanged… so that it works well in return statements.

def winding(lt: list[tuple[float, float]]) ‑> str
Expand source code
def winding(lt: list[tuple[float, float]]) -> str:
    """
    String description of winding of a 2D polygon.

    Returns one of `"cw"`, `"ccw"`, `"zero"` or `"too small"`.
    """

    def wstr(winding):
        if winding > 0:
            return "cw"
        if winding < 0:
            return "ccw"
        return "zero"

    length = len(lt)
    if length < 3:
        return "too small"
    winding = 0.0
    for i in range(0, length):
        winding += (lt[(i + 1) % length][0] - lt[i][0]) * (
            lt[(i + 1) % length][1] + lt[i][1]
        )
    return wstr(winding)

String description of winding of a 2D polygon.

Returns one of "cw", "ccw", "zero" or "too small".