#! /usr/bin/env python3
import sys

from solid import scad_render_to_file
from solid.objects import cube, cylinder, hole, part, rotate
from solid.utils import FORWARD_VEC, right, up

SEGMENTS = 120


def pipe_intersection_hole():
    pipe_od = 12
    pipe_id = 10
    seg_length = 30

    outer = cylinder(r=pipe_od, h=seg_length, center=True)
    inner = cylinder(r=pipe_id, h=seg_length + 2, center=True)

    # By declaring that the internal void of pipe_a should
    # explicitly remain empty, the combination of both pipes
    # is empty all the way through.

    # Any OpenSCAD / SolidPython object can be declared a hole(),
    # and after that will always be empty
    pipe_a = outer + hole()(inner)
    # Note that "pipe_a = outer - hole()(inner)" would work identically;
    # inner will always be subtracted now that it's a hole

    pipe_b = rotate(a=90, v=FORWARD_VEC)(pipe_a)
    return pipe_a + pipe_b


def pipe_intersection_no_hole():
    pipe_od = 12
    pipe_id = 10
    seg_length = 30

    outer = cylinder(r=pipe_od, h=seg_length, center=True)
    inner = cylinder(r=pipe_id, h=seg_length + 2, center=True)
    pipe_a = outer - inner

    pipe_b = rotate(a=90, v=FORWARD_VEC)(pipe_a)
    # pipe_a and pipe_b are both hollow, but because
    # their central voids aren't explicitly holes,
    # the union of both pipes has unwanted internal walls

    return pipe_a + pipe_b


def multipart_hole():
    # It's good to be able to keep holes empty, but often we want to put
    # things (bolts, etc.) in them.  The way to do this is to declare the
    # object containing the hole a "part".  Then, the hole will remain
    # empty no matter what you add to the 'part'.  But if you put an object
    # that is NOT part of the 'part' into the hole, it will still appear.

    # On the left (not_part), here's what happens if we try to put an object
    # into an explicit hole:  the object gets erased by the hole.

    # On the right (is_part), we mark the cube-with-hole as a "part",
    # and then insert the same 'bolt' cylinder into it.  The entire
    # bolt rematins.

    b = cube(10, center=True)
    c = cylinder(r=2, h=12, center=True)

    # A cube with an explicit hole
    not_part = b - hole()(c)

    # Mark this cube-with-hole as a separate part from the cylinder
    is_part = part()(not_part.copy())

    # This fits in the holes
    bolt = cylinder(r=1.5, h=14, center=True) + up(8)(cylinder(r=2.5, h=2.5, center=True))

    # The section of the bolt inside not_part disappears.  The section
    # of the bolt inside is_part is still there.
    return not_part + bolt + right(45)(is_part + bolt)


if __name__ == '__main__':
    out_dir = sys.argv[1] if len(sys.argv) > 1 else None

    # On the left, pipes with no explicit holes, which can give
    # unexpected walls where we don't want them.
    # On the right, we use the hole() function to fix the problem
    a = pipe_intersection_no_hole() + right(45)(pipe_intersection_hole())

    # Below is an example of how to put objects into holes and have them
    # still appear
    b = up(40)(multipart_hole())
    a += b

    file_out = scad_render_to_file(a, out_dir=out_dir, file_header=f'$fn = {SEGMENTS};', include_orig_code=True)
    print(f"{__file__}: SCAD file written to: \n{file_out}")
