#!/usr/bin/env bash

# This example demonstrates a non-trivial application of DICOMautomaton for
# performing a difficult boolean operation: generation of a double-threaded
# machine screw, i.e., one that will simultaneously accept both clockwise and
# anti-clockwise nuts.
#
# A double-threaded screw can be generated by computing the boolean intersection
# of a screw with a clockwise thread, and a screw with an anti-clockwise thread.
# A standard M6 machine screw was modeled in a CAD program, and a mirror image
# of the screw mesh was also generated. However, due to numerical inaccuracies,
# boolean operations failed. The mesh was not manifold (i.e., watertight).
#
# Using DICOMautomaton, we can attempt to make the mesh manifold. However, most
# CAD programs cannot (easily) handle surface meshes, so import back into the
# CAD program might not work, and if it does, performing boolean operations
# may be extremely slow. Instead, we can use DICOMautomaton to perform the
# boolean operation and make the finished double-threaded screw.
#
# The basic process is
#  1. load the (non-manifold) surface mesh,
#  2. define an array of intersecting planes (images),
#  3. slice the mesh using the planes, extracting contours,
#  4. mirror the contours,
#  5. for each plane, perform a (2D) boolean operation, and finally
#  6. convert from contours back to mesh by stitching contours back together.
#
# This script performs the above procedure and generates a valid mesh. Note that
# no voxel interpolation is performed. By carefully chosing where to slice, no
# features are lost. Note that both clockwise and anti-clockwise threaded nuts
# can be generated from the input, but are not here for brevity.
#
# Written by Hal Clark, 2021.
#

set -eux
set -o pipefail

# First check if CGAL support is enabled. It is needed for this example.
function skip_example {
    echo "CGAL is not available. Skipping example..."
    exit 0
}
"${DCMA_BIN}" -u | \
  grep 'CGAL[ ]*true' | \
  grep . || skip_example


"${DCMA_BIN}" \
  "${TEST_FILES_ROOT}"/M6x20_screw_nonmanifold_binary.ply.tgz \
  `# ` \
  `# Extract contours by slicing the original mesh. ` \
  -o GenerateSyntheticImages \
    -p ImagePosition='-8,-8,-22' \
    -p NumberOfColumns=6 \
    -p NumberOfRows=6 \
    -p VoxelHeight=5.0 \
    -p VoxelWidth=5.0 \
    -p NumberOfImages=600 \
    -p SliceThickness=0.05 \
    -p SpacingBetweenSlices=0.05 \
  -o MakeMeshesManifold \
    -p MeshSelection='last' \
    -p MeshLabel='screw' \
  -o ConvertMeshesToContours \
    -p ImageSelection='last' \
    -p MeshSelection='last' \
    -p ROILabel='screw' \
  -o DeleteMeshes \
    -p MeshSelection='all' \
  -x SFML_Viewer \
  -x CropImageDoseToROIs \
  `# ` \
  `# Mirror the contours.` \
  `# ` \
  `# Note: It is generally better to mirror the mesh, if possible, because ` \
  `#       they may misalign with the original images. In this there is no ` \
  `#       risk because we also control the image orientation. ` \
  -o CopyContours \
    -p ROILabelRegex='screw' \
    -p ROILabel='anti' \
  -o GenerateWarp \
    -p Transforms='mirror(0,0,0, 1,0,0)' \
  -o WarpContours \
    -p ROILabelRegex='anti' \
  -x SFML_Viewer \
  `# ` \
  `# Perform the per-slice boolean intersection.` \
  -o ContourBooleanOperations \
    -p ROILabelRegexA='screw' \
    -p ROILabelRegexB='anti' \
    -p Operation='intersection' \
    -p OutputROILabel='dual' \
  -x SFML_Viewer \
  `# ` \
  `# Export a point cloud from the contour vertices.` \
  -o ConvertContoursToPoints \
    -p ROILabelRegex='dual' \
    -p Label='dual' \
    -p Method='vertices' \
  -o ExportPointClouds \
    -p PointSelection='last' \
    -p FilenameBase='M6x20_screw_dualthreaded_manifold.xyz' \
  `# ` \
  `# Export a mesh by stitching together the contours.` \
  -o ConvertContoursToMeshes \
    -p ROILabelRegex='dual' \
    -p MeshLabel='dual' \
  -o ExportSurfaceMeshesPLY \
    -p MeshSelection='last' \
    -p Filename='M6x20_screw_dualthreaded_manifold_binary.ply' \
    -p Variant='binary' \
  -x SFML_Viewer 

