Module piecad.utilities

Miscellaneous (but important) functions

Functions

def check_mesh(vertices: list[tuple[float, float, float]], faces: list[tuple[int, int, int]]) ‑> bool
Expand source code
def check_mesh(
    vertices: list[tuple[float, float, float]], faces: list[tuple[int, int, int]]
) -> bool:
    """
    Check manifold and winding of the mesh defined in vertices and faces.

    Returns true if all is well. Otherwise diagnostics will be offered and false is returned.
    """
    return _check_mesh(vertices, faces)

Check manifold and winding of the mesh defined in vertices and faces.

Returns true if all is well. Otherwise diagnostics will be offered and false is returned.

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", process=True, validate=False
    )
    if type(mesh) == trimesh.path.Path2D:
        raise ValidationError("Currently 2d objects are no supported.")
    else:
        vertices = np.array(mesh.vertices, np.float64)
        faces = np.array(mesh.faces, np.uint64)
        o = Obj3d(_m.Manifold(_m.Mesh64(vertices, 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 quick_check_mesh(vertices: list[tuple[float, float, float]], faces: list[tuple[int, int, int]]) ‑> str
Expand source code
def quick_check_mesh(
    vertices: list[tuple[float, float, float]], faces: list[tuple[int, int, int]]
) -> str:
    """
    Same checking as check_mesh, but no advice is offered

    Returns an empty string `""` if all is well, otherwise a short string describing the problem is
    returned.
    """
    return _quick_check_mesh(vertices, faces)

Same checking as check_mesh, but no advice is offered

Returns an empty string "" if all is well, otherwise a short string describing the problem is returned.

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.

    If [p filename] does not contain a path separator character (`'/'` or `'\\'`) the
    `Downloads` directory for you platform is prepended. Thus if you set
    [p filename] to `output.obj`, the place where the file is saved is
    `c:\\Users\\username\\Downloads\\output.obj` on Windows or
    `/home/username/Downloads/output.obj` on Linux and MacOS.
    If you want a file saved in the current directory, prepend `'./'` or `'.\\'`.

    You can override the `Downloads` directory by setting the global environment variable
    `PIECAD_SAVE_DIR`.

    The model 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.
    """

    if filename.find("/") == -1 and filename.find("\\") == -1:
        filename = str(_Path(_get_save_dir()) / filename)
        print("Saving: " + filename)
    _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]
            if filename.endswith(".3mf"):
                _export_3mf(
                    filename,
                    obj.mo,
                    Obj3d.color_map,
                    Config.get_default_units(),
                    Config.get_default_color(),
                )
                return
            mesh = obj.mo.to_mesh64()
            if mesh.vert_properties.shape[1] > 3:
                vertices = mesh.vert_properties[:, :3]
            else:
                vertices = mesh.vert_properties

            face_colors = _face_colors(obj, mesh)
            mesh_output = trimesh.Trimesh(
                vertices=vertices,
                faces=mesh.tri_verts,
                face_colors=face_colors,
                process=True,
                validate=False,
            )
            # 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
                face_colors = _face_colors(obj, mesh)
                mesh_output = trimesh.Trimesh(
                    vertices=vertices,
                    faces=mesh.tri_verts,
                    face_colors=face_colors,
                    process=True,
                    validate=False,
                )
                # 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)
            if filename.endswith(".3mf"):
                s_mesh = scene.to_mesh64()
                s_vertices = np.array(s_mesh.vertices, np.float64)
                s_faces = np.array(s_mesh.faces, np.uint64)
                mo = _m.Manifold(_m.Mesh64(s_vertices, s_faces))
                _export_3mf(
                    filename,
                    mo,
                    Obj3d.color_map,
                    Config.get_default_units(),
                    Config.get_default_color(),
                )
                return
            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.

If [p filename] does not contain a path separator character ('/' or '\') the Downloads directory for you platform is prepended. Thus if you set [p filename] to output.obj, the place where the file is saved is c:\Users\username\Downloads\output.obj on Windows or /home/username/Downloads/output.obj on Linux and MacOS. If you want a file saved in the current directory, prepend './' or '.\'.

You can override the Downloads directory by setting the global environment variable PIECAD_SAVE_DIR.

The model 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 `Piecad-Viewer` to display the geometry object.

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

    ```
    return union(o1, o2, o3)
    # can be displayed in 3 parts and the whole object, like this:
    return view(union(view(o1), view(o2), view(o3)))
    ```

    If `Piecad-Viewer` is not already started, it will be auto-started.

    It is rarely necessary, but one can control `Piecad-Viewer` host and
    port, by setting your operating systems `PIECAD_VIEWER` environment
    variable.  By default this is set to: "127.0.0.1:8037".
    This environment variable is also used by the `piecad-viewer` program.
    """
    global _view_thread
    if _viewer_available == False:
        return
    hptmp = os.environ.get("PIECAD_VIEWER", None)
    if hptmp != None:
        _piecad_viewer = hptmp

    _chkGO("obj", obj)

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

    if type(obj) == Obj2d:
        color = obj._color
        if color == None:
            color = Config.get_default_color()
        obj = Obj3d(_m.Manifold.extrude(obj.mo, 0.1)).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_mesh64()
    if mesh.vert_properties.shape[1] > 3:
        vertices = mesh.vert_properties[:, :3]
    else:
        vertices = mesh.vert_properties
    faces = mesh.tri_verts
    face_colors = _face_colors(obj, mesh)
    view_data = {}
    view_data["title"] = title
    fc = face_colors.tolist()
    view_data["color"] = fc
    view_data["vertices"] = vertices.tolist()
    fl = faces.tolist()
    view_data["faces"] = fl
    _view_queue.put(view_data)
    return obj

Use Piecad-Viewer to display the geometry object.

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

return union(o1, o2, o3)
# can be displayed in 3 parts and the whole object, like this:
return view(union(view(o1), view(o2), view(o3)))

If Piecad-Viewer is not already started, it will be auto-started.

It is rarely necessary, but one can control Piecad-Viewer host and port, by setting your operating systems PIECAD_VIEWER environment variable. By default this is set to: "127.0.0.1:8037". This environment variable is also used by the piecad-viewer program.

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".