"""A loaf-pan-like open box (replaces the ObjectFolder loaf pan, whose mesh is full of slivers): the loaf pan's outer size
275.5 x 158.4 x 81.9 mm, vertical corners rounded (outer radius R), walls and floor T thick, open top. Meshed by marching
cubes of its signed distance at VOXEL mm (well-shaped triangles: manifold3d's triangulation of the flat faces is all
slivers, which the noise step cannot move, as with the loaf pan); edges chamfered at about a voxel. Writes
../meshes/open_box.ply.

    env -u PYTHONPATH PYTHONWARNINGS=ignore .venv/bin/python ~/code/data/meshes/fcp_2710_tests/scripts/make_openbox.py
"""
from pathlib import Path

import numpy as np
import trimesh

W, D, H = 275.5, 158.4, 81.9  # mm, the loaf pan's outer size
T = 6.0  # wall and floor thickness, mm (about 1.25 spacings at 10k points, under one at 2048)
R = 15.0  # outer corner radius, mm


VOXEL = 1.5  # mm
SHIFT = 0.3819660113  # of a voxel: the grid's offset from the faces (irrational)


def sdf_rrect(x, y, w, d, r):
    """Signed distance to a rounded rectangle centred at 0 (w x d, corner radius r) in the plane."""
    qx, qy = np.abs(x) - (w / 2 - r), np.abs(y) - (d / 2 - r)
    return np.hypot(np.maximum(qx, 0), np.maximum(qy, 0)) + np.minimum(np.maximum(qx, qy), 0) - r


def main():
    from skimage import measure

    pad = 3 * VOXEL + SHIFT * VOXEL  # no grid node on a face (marching cubes would make zero-area triangles there)
    xs = np.arange(-W / 2 - pad, W / 2 + pad, VOXEL)
    ys = np.arange(-D / 2 - pad, D / 2 + pad, VOXEL)
    zs = np.arange(-pad, H + pad, VOXEL)
    X, Y = np.meshgrid(xs, ys, indexing="ij")
    so = sdf_rrect(X, Y, W, D, R)[:, :, None]
    si = sdf_rrect(X, Y, W - 2 * T, D - 2 * T, R - T)[:, :, None]
    Z = zs[None, None, :]
    outer = np.maximum(so, np.maximum(-Z, Z - H))  # the solid block
    cavity = np.maximum(si, T - Z)  # the cavity, open upwards
    f = np.maximum(outer, -cavity).astype(np.float32)
    v, faces, _, _ = measure.marching_cubes(f, 0.0, spacing=(VOXEL,) * 3)
    v += np.array([xs[0], ys[0], zs[0]])
    t = trimesh.Trimesh(v, faces[:, ::-1], process=False)  # merging vertices makes it non-manifold; marching cubes is already
    if t.volume < 0:
        t.invert()
    assert t.is_watertight and t.volume > 0
    assert trimesh.Trimesh(t.vertices, t.faces, process=True).is_watertight  # as every script loads it
    out = Path(__file__).resolve().parent.parent / "meshes" / "open_box.ply"
    t.export(out)
    print(out, t.is_watertight, t.extents.round(1), len(t.faces), "faces")


if __name__ == "__main__":
    main()
