import cadquery as cq

if __name__ == "__main__":
    from ocp_vscode import *
from utils import *

import localselectors

tube_w = inch(2.0)
tube_h = inch(1.5)
tube_wall = inch(0.075)  # 1.519 #inch(0.062)
column_spacing = 560
column_h = 400
plate_z = inch(3 / 16)  # 2.54
plate_z2 = inch(3 / 8)  # 10

x_mount_z = inch(3 / 16)  # 5
stepper_mount_z = inch(3 / 16)  # 5

carrier_plate_z = inch(3 / 8)  # 10

ballscrew_x_offset = 4

ballscrew_len = 500
column_spacing = (
    ballscrew_len - 15 - 39 + 25 - 10 + 7 / 2 + 20 / 2 + 2 * x_mount_z + tube_w
)
print("column_spacing = ", column_spacing)

x_rail_spacing = 240
x_carriage_spacing = 120


class Gantry:
    def __init__(self, w):
        self.left = tubing(
            column_h,
            tube_w,
            tube_h,
            tube_wall,
            w.transformed(offset=(-0.5 * column_spacing, 0, 0)),
        )
        self.right = tubing(
            column_h,
            tube_w,
            tube_h,
            tube_wall,
            w.transformed(offset=(0.5 * column_spacing, 0, 0)),
        )
        upper_center = column_h - 0.5 * tube_w
        self.upper = tubing(
            column_spacing - tube_w,
            tube_h,
            tube_w,
            tube_wall,
            w.transformed(
                offset=(-0.5 * column_spacing + 0.5 * tube_w, 0, upper_center),
                rotate=(90, 90, 0),
            ),
        )
        lower_center = upper_center - x_rail_spacing
        self.lower = tubing(
            column_spacing - tube_w,
            tube_h,
            tube_w,
            tube_wall,
            w.transformed(
                offset=(-0.5 * column_spacing + 0.5 * tube_w, 0, lower_center),
                rotate=(90, 90, 0),
            ),
        )
        self.upper_rail = HGR20(
            w.transformed(
                offset=(0, 0.5 * tube_h + plate_z, upper_center), rotate=(-90, 0, 0)
            ),
            column_spacing + tube_w,
        )
        self.lower_rail = HGR20(
            w.transformed(
                offset=(0, 0.5 * tube_h + plate_z, lower_center), rotate=(-90, 0, 0)
            ),
            column_spacing + tube_w,
        )
        self.upper_front_plate = (
            cq.Workplane()
            .copyWorkplane(
                w.transformed(offset=(0, tube_h / 2, upper_center), rotate=(-90, 0, 0))
            )
            .tag("base")
            .rect(column_spacing + tube_w, tube_w)
            .extrude(plate_z)
        )
        self.lower_front_plate = (
            cq.Workplane()
            .copyWorkplane(
                w.transformed(offset=(0, tube_h / 2, lower_center), rotate=(-90, 0, 0))
            )
            .tag("base")
            .rect(column_spacing + tube_w, tube_w)
            .extrude(plate_z)
        )
        self.upper_back_plate = (
            cq.Workplane()
            .copyWorkplane(
                w.transformed(offset=(0, -tube_h / 2, upper_center), rotate=(90, 0, 0))
            )
            .tag("base")
            .rect(column_spacing + tube_w, tube_w)
            .extrude(plate_z)
        )
        self.lower_back_plate = (
            cq.Workplane()
            .copyWorkplane(
                w.transformed(offset=(0, -tube_h / 2, lower_center), rotate=(90, 0, 0))
            )
            .tag("base")
            .rect(column_spacing + tube_w, tube_w)
            .extrude(plate_z)
        )
        self.ball_screw_w = (
            cq.Workplane()
            .copyWorkplane(w)
            .transformed(
                offset=(
                    ballscrew_len / 2 - (-15 - 39 + 25 + 10 - 7 / 2 - 20 / 2) / 2,
                    ballscrew_x_offset,
                    (upper_center + lower_center) / 2,
                ),
                rotate=(0, 0, 90),
            )
            .transformed(rotate=(0, 90, 0))
        )
        self.ball_screw_model = SFU1204(self.ball_screw_w, ballscrew_len)
        self.floating_mount_w = self.ball_screw_w.transformed(
            offset=(0, ballscrew_len + (-10 + 7 / 2 + 20 / 2), 0), rotate=(-90, 0, 0)
        )

        self.ball_screw = self.ball_screw_model.ballscrew()
        self.fixed_bearing = self.ball_screw_model.fixedBearing()
        self.floating_bearing = self.ball_screw_model.floatingBearing()

        self.left = self.ball_screw_model.floatingBearingHoles(self.left)
        self.right = self.ball_screw_model.fixedBearingHoles(self.right)
        self.right = (
            self.right
            # holes for the ball screw
            .copyWorkplane(self.ball_screw_w.transformed(rotate=(90, 0, 0)))
            .hole(20)
            .copyWorkplane(self.ball_screw_w.transformed(rotate=(-90, 0, 0)))
            .hole(20)
        )

        self.upper_front_plate = self.upper_rail.holes(self.upper_front_plate)
        self.upper_back_plate = self.upper_rail.holes(self.upper_back_plate)

        self.lower_front_plate = self.lower_rail.holes(self.lower_front_plate)
        self.lower_back_plate = self.lower_rail.holes(self.lower_back_plate)

        right_bearing_base = (
            self.ball_screw_w.transformed(offset=(0, 0, -ballscrew_x_offset))
            .transformed(rotate=(-90, 0, 0))
            .transformed(offset=(0, 0, 15 + 39 - 25 - x_mount_z))
            .transformed(rotate=(0, 0, 90))
        )

        self.right_bearing_mount = (
            cq.Workplane()
            .copyWorkplane(right_bearing_base)
            .tag("base")
            .rect(tube_h, x_rail_spacing - tube_w)
            .extrude(x_mount_z)
            .center(-ballscrew_x_offset, 0)
            .tag("ballscrew_center")
            .circle(12 / 2)
            .extrude(20, combine="s")
            .workplaneFromTagged("ballscrew_center")
            .center(35 / 2 - 4 / 2, 0)
            .rect(4, 35)
            .extrude(20, combine="s")
            .workplaneFromTagged("ballscrew_center")
            .center(-35 / 2 + 4 / 2, 0)
            .rect(4, 35)
            .extrude(20, combine="s")
            .workplaneFromTagged("ballscrew_center")
            .center(0, -35 / 2 + 4 / 2)
            .rect(20, 4)
            .extrude(20, combine="s")
            .workplaneFromTagged("ballscrew_center")
            .center(0, 35 / 2 - 4 / 2)
            .rect(20, 4)
            .extrude(20, combine="s")
            # .rect(35, 35)
            # .extrude(20, combine="s")
        )
        self.right_bearing_mount = self.ball_screw_model.fixedBearingHoles(
            self.right_bearing_mount
        )
        self.left_bearing_mount = (
            w.copyWorkplane(self.floating_mount_w)
            .tag("base")
            .transformed(offset=(0, ballscrew_x_offset, 0), rotate=(0, 0, 90))
            .rect(tube_h, x_rail_spacing - tube_w)
            .extrude(x_mount_z)
        )
        # this doesn't work for some reason, i guess just use the fixed bearing mount flipped
        self.left_bearing_mount = self.ball_screw_model.floatingBearingHoles(
            self.left_bearing_mount
        )

        points = [(40 * (i - 5.5), y) for i in range(12) for y in [-18, 18]]
        points += [
            (8 * i + j, y)
            for i in range(-2, 3)
            for y in [-18, 18]
            for j in [column_spacing / 2, -column_spacing / 2]
        ]
        top_w = cq.Workplane().copyWorkplane(self.upper_front_plate)
        bot_w = cq.Workplane().copyWorkplane(self.lower_front_plate)

        def holes(w, ref):
            return w.copyWorkplane(ref).pushPoints(points).hole(3)

        self.upper_front_plate = holes(self.upper_front_plate, top_w)
        self.upper_back_plate = holes(self.upper_back_plate, top_w)
        # self.upper = top_holes(self.upper)
        self.lower_front_plate = holes(self.lower_front_plate, bot_w)
        self.lower_back_plate = holes(self.lower_back_plate, bot_w)
        # self.lower = top_holes(self.lower)

        stepper_mount_w = w.transformed(
            offset=(
                column_spacing / 2 + tube_w / 2,
                ballscrew_x_offset,
                (upper_center + lower_center) / 2,
            ),
            rotate=(0, 90, 0),
        )

        self.stepper_mount = (
            cq.Workplane()
            .copyWorkplane(
                stepper_mount_w.transformed(offset=(0, 0, 0), rotate=(0, 0, 45))
            )
            .tag("base")
            .rect(58.4, 58.4)
            .extrude(stepper_mount_z)
            .copyWorkplane(
                stepper_mount_w.transformed(offset=(0, -ballscrew_x_offset, 0))
            )
            .rect(88, tube_h)
            .extrude(stepper_mount_z)
            .copyWorkplane(
                stepper_mount_w.transformed(offset=(0, 0, 0), rotate=(180, 0, 45))
            )
            .rect(47.41, 47.41, forConstruction=True)
            .vertices()
            .hole(5)
            .copyWorkplane(
                stepper_mount_w.transformed(offset=(0, 0, 0), rotate=(180, 0, 0))
            )
            .hole(32)
            # .copyWorkplane(stepper_mount_w)
            # .circle(40)
            # .extrude(10)
        )
        self.stepper_mount = self.ball_screw_model.fixedBearingHoles(self.stepper_mount)

        def stepper_mount_holes(w):
            return (
                w.copyWorkplane(
                    stepper_mount_w.transformed(offset=(0, -ballscrew_x_offset, 5))
                )
                .rect(78, 26)
                .vertices()
                .hole(5)
            )

        self.stepper_mount = stepper_mount_holes(self.stepper_mount)
        self.right = stepper_mount_holes(self.right)
        self.right_bearing_mount = stepper_mount_holes(self.right_bearing_mount)

        self.combined_front_plate = (
            cq.Workplane()
            .copyWorkplane(
                w.transformed(
                    offset=(
                        column_spacing / 2,
                        tube_h / 2,
                        (upper_center + lower_center) / 2,
                    ),
                    rotate=(-90, 0, 0),
                )
            )
            .rect(20, upper_center - lower_center - tube_w + 5)
            .extrude(plate_z)
            .union(self.upper_front_plate)
            .union(self.lower_front_plate)
        )

    def assembly(self):
        return (
            cq.Assembly(name="gantry")
            .add(self.left, name="left column")
            .add(self.right, name="right column")
            .add(self.upper, name="upper platform")
            .add(self.lower, name="lower platform")
            .add(self.upper_rail.rail(), name="upper rail")
            .add(self.lower_rail.rail(), name="lower rail")
            .add(self.upper_front_plate, name="upper front plate")
            .add(self.lower_front_plate, name="lower front plate")
            .add(self.upper_back_plate, name="upper back plate")
            .add(self.lower_back_plate, name="lower back plate")
            .add(self.ball_screw, name="ball screw")
            .add(self.fixed_bearing, name="fixed bearing")
            .add(self.floating_bearing, name="floating bearing")
            .add(self.right_bearing_mount, name="fixed bearing mount")
            .add(self.left_bearing_mount, name="floating bearing mount")
            .add(self.stepper_mount, name="stepper mount")
            .add(self.combined_front_plate, name="combined front plate")
        )


z_rail_spacing = 110
z_carriage_spacing = 100
z_rail_len = 400
carriage_w = 250
carriage_h = 300
bolster_w = 30
bolster_z = 25

z_spacer_z = inch(3 / 8)  # 10
z_spacer_w = 40

z_ballscrew_len = 400
z_ballscrew_offset = 0


class Carriage:
    def __init__(self, gantry, x=0, z=0):
        self.z = z

        self.bul = gantry.upper_rail.carriage(x)
        self.bur = gantry.upper_rail.carriage(x + x_carriage_spacing)
        self.bll = gantry.lower_rail.carriage(x)
        self.blr = gantry.lower_rail.carriage(x + x_carriage_spacing)
        back = cq.Workplane("XZ").copyWorkplane(
            self.bul.workplaneFromTagged("mountingholes")
        )
        # why doesn't this track the carriage without me adding x to it?
        self.plate = (
            cq.Workplane()
            .copyWorkplane(back)
            .center(-x - HGR20.L - x_carriage_spacing / 2, -x_rail_spacing / 2)
            .tag("base")
            # .rect(HGR20.L+x_carriage_spacing, HGR20.W+x_rail_spacing)
            .rect(carriage_w, carriage_h)
            .extrude(plate_z2)
            .faces(">z")  # localselectors plugin
            .workplane()
            .tag("carriage_plate")
        )
        self.plate = gantry.upper_rail.carriageHoles(self.plate, x)
        self.plate = gantry.upper_rail.carriageHoles(self.plate, x + x_carriage_spacing)
        self.plate = gantry.lower_rail.carriageHoles(self.plate, x)
        self.plate = gantry.lower_rail.carriageHoles(self.plate, x + x_carriage_spacing)

        # creating the workplane the rails of the spindle carrier
        # mount to, because HGR works from that plane forward
        self.carrier_plane = (
            cq.Workplane()
            .copyWorkplane(self.plate.workplaneFromTagged("carriage_plate"))
            .transformed(offset=(0, 0, HGR20.H), rotate=(180, 0, 0))
        )

        self.left_rail = HGR20(
            self.carrier_plane.transformed(
                offset=(z_rail_spacing / 2, z, 0), rotate=(0, 0, 90)
            ),
            z_rail_len,
        )
        self.right_rail = HGR20(
            self.carrier_plane.transformed(
                offset=(-z_rail_spacing / 2, z, 0), rotate=(0, 0, 90)
            ),
            z_rail_len,
        )

        u_zcarriage_pos = z_rail_len / 2 - HGR20.L / 2 - z - z_carriage_spacing / 2
        l_zcarriage_pos = u_zcarriage_pos + z_carriage_spacing
        self.ful = self.left_rail.carriage(u_zcarriage_pos)
        self.fll = self.left_rail.carriage(l_zcarriage_pos)
        self.fur = self.right_rail.carriage(u_zcarriage_pos)
        self.flr = self.right_rail.carriage(l_zcarriage_pos)

        self.plate = self.left_rail.carriageHoles(self.plate, u_zcarriage_pos)
        self.plate = self.left_rail.carriageHoles(self.plate, l_zcarriage_pos)
        self.plate = self.right_rail.carriageHoles(self.plate, u_zcarriage_pos)
        self.plate = self.right_rail.carriageHoles(self.plate, l_zcarriage_pos)

        self.ball_nut = gantry.ball_screw_model.nut(x + carriage_w / 2)

        lbolster_w = self.plate.workplaneFromTagged("carriage_plate").transformed(
            offset=(-(carriage_w - bolster_w) / 2, 0, 0)
        )
        rbolster_w = self.plate.workplaneFromTagged("carriage_plate").transformed(
            offset=((carriage_w - bolster_w) / 2, 0, 0)
        )

        points = [(0, 50 * (i - 2.5)) for i in range(6)]

        self.lbolster = (
            cq.Workplane()
            .copyWorkplane(lbolster_w)
            .rect(bolster_w, carriage_h)
            .extrude(bolster_z)
            .copyWorkplane(lbolster_w.transformed(rotate=(0, 180, 0)))
            .pushPoints(points)
            .hole(3)
        )
        self.rbolster = (
            cq.Workplane()
            .copyWorkplane(rbolster_w)
            .rect(bolster_w, carriage_h)
            .extrude(bolster_z)
            .copyWorkplane(rbolster_w.transformed(rotate=(0, 180, 0)))
            .pushPoints(points)
            .hole(3)
        )

        self.plate = (
            self.plate.copyWorkplane(lbolster_w)
            .pushPoints(points)
            .hole(3)
            .copyWorkplane(rbolster_w)
            .pushPoints(points)
            .hole(3)
        )

        self.wp_carrier = (
            cq.Workplane()
            .copyWorkplane(self.plate.workplaneFromTagged("carriage_plate"))
            .transformed(offset=(0, -z, HGR20.H), rotate=(0, 0, 0))
        )
        self.x_nut_mount = (
            cq.Workplane()
            .copyWorkplane(back)
            .center(-x - HGR20.L - x_carriage_spacing / 2, -x_rail_spacing / 2)
            .transformed(rotate=(180, 0, 0))
            .rect(60, 60)
            .extrude(64 - 8)
            .faces(">z")
            .rect(60, 32)
            .extrude(8)
        )
        self.x_nut_mount = gantry.ball_screw_model.nutHoles(
            self.x_nut_mount, x + carriage_w / 2
        )
        self.x_nut_mount = self.x_nut_mount.copyWorkplane(
            gantry.ball_screw_model.w.transformed(
                offset=(0, x + carriage_w / 2, 0), rotate=(90, 0, 0)
            ),
        ).hole(14)

        def nut_mount_holes(s, forward=True):
            return (
                s.copyWorkplane(back)
                .center(-x - HGR20.L - x_carriage_spacing / 2, -x_rail_spacing / 2)
                .transformed(rotate=(0 if forward else 180, 0, 0))
                .rect(48, 48)
                .vertices()
                .hole(5)
            )

        self.x_nut_mount = nut_mount_holes(self.x_nut_mount)
        self.plate = nut_mount_holes(self.plate, forward=False)

        # these are filled in by the carrier
        self.z_nut_mount = None
        self.z_nut = None

    def assembly(self):
        assmbly = (
            cq.Assembly(name="x carriage")
            .add(self.bul, name="back ul carriage")
            .add(self.bur, name="back ur carriage")
            .add(self.bll, name="back ll carriage")
            .add(self.blr, name="back lr carriage")
            .add(self.plate, name="carrier plate")
            .add(self.left_rail.rail(), name="left rail")
            .add(self.right_rail.rail(), name="right rail")
            .add(self.ful, name="front ul carriage")
            .add(self.fur, name="front ur carriage")
            .add(self.fll, name="front ll carriage")
            .add(self.flr, name="front lr carriage")
            .add(self.lbolster, name="left bolster")
            .add(self.rbolster, name="right bolster")
            .add(self.ball_nut, name="ball nut")
            .add(self.x_nut_mount, name="x nut mount")
        )
        if self.z_nut_mount is not None:
            assmbly = assmbly.add(self.z_nut_mount, name="z nut mount")
        if self.z_nut is not None:
            assmbly = assmbly.add(self.z_nut, name="z nut")
        return assmbly


class Carrier:
    def __init__(self, carriage, z):
        self.plane = carriage.wp_carrier
        self.left_spacer = (
            cq.Workplane()
            .copyWorkplane(self.plane)
            .transformed(offset=(z_rail_spacing / 2, 0, 0))
            .tag("base")
            .rect(z_spacer_w, z_rail_len)
            .extrude(z_spacer_z)
        )
        self.right_spacer = (
            cq.Workplane()
            .copyWorkplane(self.plane)
            .transformed(offset=(-z_rail_spacing / 2, 0, 0))
            .tag("base")
            .rect(z_spacer_w, z_rail_len)
            .extrude(z_spacer_z)
        )

        self.plate = (
            cq.Workplane()
            .copyWorkplane(self.plane)
            .transformed(offset=(0, 0, z_spacer_z))
            .tag("base")
            .rect(200, z_rail_len)
            .extrude(carrier_plate_z)
            .copyWorkplane(self.plane)
            .center(0, z_rail_len / 2 - 40 / 2)
            .rect(68, 40)
            .extrude(20, combine="s")
        )
        self.plate = carriage.left_rail.holes(self.plate)
        self.plate = carriage.right_rail.holes(self.plate)
        self.left_spacer = carriage.left_rail.holes(self.left_spacer)
        self.right_spacer = carriage.right_rail.holes(self.right_spacer)

        self.ballscrew_model = SFU1204(
            self.plane.transformed(
                offset=(
                    0,
                    z_ballscrew_len / 2 + 20,
                    # 25,
                    z_spacer_z - 25 + 5,  # offset into the spindle plate 5mm
                ),  # -(HGR20.H - z_spacer_z) / 2 - 1.2),
                rotate=(180, 0, 0),
            ),
            z_ballscrew_len,
        )
        self.ballscrew = self.ballscrew_model.ballscrew()
        self.fixed_bearing = self.ballscrew_model.fixedBearing()
        self.floating_bearing = self.ballscrew_model.floatingBearing()

        angle_z = 0.188 * 25.4
        angle_w = 2.0 * 25.4
        self.stepper_bracket = (
            cq.Workplane()
            .copyWorkplane(self.plane)
            .transformed(offset=(-200 / 2, z_rail_len / 2, 0), rotate=(0, 90, 0))
            .sketch()
            .push([(-10 - 10 - angle_z + angle_w / 2, angle_z / 2 + 5)])
            .rect(angle_w, angle_z)
            .push([(-10 - 10 - angle_z / 2, 5 + angle_z - angle_w / 2)])
            .rect(angle_z, angle_w)
            .finalize()
            .extrude(200)
        )
        self.stepper_bracket = carriage.left_rail.holes(self.stepper_bracket)
        self.stepper_bracket = carriage.right_rail.holes(self.stepper_bracket)
        self.stepper_bracket = (
            self.stepper_bracket.copyWorkplane(
                self.ballscrew_model.w.transformed(rotate=(90, 0, 0))
            ).hole(32)
            # .copyWorkplane(self.ballscrew_model.w.transformed(rotate=(90, 0, 0)))
            # .pushPoints([(40, 0)])
            # .hole(5)
        )
        self.stepper_bracket = self.ballscrew_model.fixedBearingHoles(
            self.stepper_bracket
        )

        offset = (
            self.ballscrew_model.w.vertices().val()
            - self.plane.transformed(
                offset=(0, z_rail_len / 2 + 10, 0), rotate=(-90, 0, 0)
            )
            .vertices()
            .val()
        )

        # print("offset: ", offset)
        self.stepper_mount = (
            cq.Workplane()
            .copyWorkplane(
                self.plane.transformed(
                    offset=(0, z_rail_len / 2 + 10, 0), rotate=(-90, 0, 0)
                )
            )
            .tag("base")
            .tag("flange_center")
            .rect(100, 50)
            .extrude(stepper_mount_z)
            .workplaneFromTagged("flange_center")
            .transformed(rotate=(180, 0, 0))
            .rect(90, 25, forConstruction=True)
            .vertices()
            .hole(5)
            .copyWorkplane(
                self.plane.transformed(
                    offset=(0, z_rail_len / 2 + 10, 0), rotate=(-90, 0, 0)
                ).transformed(offset=(0, -offset.y, 0), rotate=(0, 0, 45))
            )
            .tag("stepper_center")
            .rect(58.4, 58.4)
            .extrude(stepper_mount_z)
            .workplaneFromTagged("stepper_center")
            .transformed(rotate=(180, 0, 0))
            .rect(47.41, 47.41, forConstruction=True)
            .vertices()
            .hole(5)
            .copyWorkplane(self.ballscrew_model.w.transformed(rotate=(90, 0, 0)))
            .hole(32)
        )
        self.stepper_bracket = (
            self.stepper_bracket.copyWorkplane(
                self.stepper_mount.workplaneFromTagged("stepper_center")
            )
            .rect(47.41, 47.41, forConstruction=True)
            .vertices()
            .hole(5)
            .copyWorkplane(self.stepper_mount.workplaneFromTagged("flange_center"))
            .transformed(rotate=(0, 0, 0))
            .rect(90, 25, forConstruction=True)
            .vertices()
            .hole(5)
        )

        self.stepper_lower_spacer = (
            cq.Workplane()
            .copyWorkplane(self.fixed_bearing.workplaneFromTagged("mounting face"))
            .rect(60, 40)
            .extrude(16)
            .copyWorkplane(self.ballscrew_model.w.transformed(rotate=(90, 0, 0)))
            .rect(36, 36)
            .cutThruAll()
        )
        self.stepper_lower_spacer = self.ballscrew_model.fixedBearingHoles(
            self.stepper_lower_spacer
        )
        self.stepper_mount = self.ballscrew_model.fixedBearingHoles(self.stepper_mount)

        def bracket_mounting_holes(s):
            points = [
                (x, y)
                for x in [-93, -83, 93, 83]
                for y in [-z_ballscrew_len / 2 + 30, -z_ballscrew_len / 2 + 10]
            ]
            return (
                s.copyWorkplane(self.plane.transformed(rotate=(180, 0, 0)))
                .pushPoints(points)
                .hole(5)
            )

        self.plate = bracket_mounting_holes(self.plate)
        self.stepper_bracket = bracket_mounting_holes(self.stepper_bracket)

        ballscrew_offset = z_ballscrew_len / 2 + carriage.z / 2 - z
        carriage.z_nut = self.ballscrew_model.nut(ballscrew_offset)
        carriage.z_nut_mount = (
            cq.Workplane()
            .copyWorkplane(carriage.plate.workplaneFromTagged("carriage_plate"))
            .rect(60, 60)
            .extrude(28)
            .faces(">z")
            .rect(32, 60)
            .extrude(8)
            .copyWorkplane(
                carriage.plate.workplaneFromTagged("carriage_plate").transformed(
                    rotate=(180, 0, 0)
                )
            )
            .rect(
                48, 48
            )  # mounting holes; these need to line up with the x nut mounting holes
            .vertices()
            .hole(5)
            .copyWorkplane(
                self.ballscrew_model.w.transformed(
                    offset=(0, ballscrew_offset, 0), rotate=(90, 0, 0)
                ),
            )
            .hole(14)  # hole for ballscrew
        )
        carriage.z_nut_mount = self.ballscrew_model.nutHoles(
            carriage.z_nut_mount, ballscrew_offset
        )

    def assembly(self):
        return (
            cq.Assembly(name="z carrier")
            .add(self.left_spacer, name="left spacer")
            .add(self.right_spacer, name="right spacer")
            .add(self.plate, name="z plate")
            .add(self.ballscrew, name="z ballscrew")
            .add(self.fixed_bearing, name="z fixed bearing")
            # .add(self.floating_bearing, name="z floating bearing")
            .add(self.stepper_bracket, name="stepper motor-ballscrew bracket")
            .add(self.stepper_lower_spacer, name="stepper motor lower spacer")
            .add(self.stepper_mount, name="stepper mount")
        )


class SpindleMount:
    def __init__(self, carrier, z_offset=0):
        self.plane = (
            carrier.plane.transformed(offset=(0, z_offset, z_spacer_z + 10))
            .transformed(rotate=(-90, 0, 0))
            .transformed(rotate=(0, 0, 180))
        )
        self.spindleaxis = cq.Workplane().copyWorkplane(self.plane).center(0, 54)
        self.mount_points = [(x, y) for x in [-85, 85] for y in [60, 40, 20]]
        self.mount = (
            cq.Workplane()
            .copyWorkplane(self.plane)
            .hLineTo(184 / 2)
            .vLineTo(17)
            .hLineTo(113 / 2)
            .vLineTo(82)
            .hLineTo(-113 / 2)
            .vLineTo(17)
            .hLineTo(-184 / 2)
            .vLineTo(0)
            .close()
            .extrude(78)
            .copyWorkplane(self.spindleaxis)
            .cylinder(78, 104 / 2, angle=180, centered=[True, True, False])
            .copyWorkplane(self.spindleaxis)
            .cylinder(78, 80 / 2, centered=[True, True, False], combine="s")
            .copyWorkplane(
                carrier.plane.transformed(
                    offset=(0, z_offset, z_spacer_z + 10), rotate=(0, 180, 0)
                )
            )
            .tag("mounting_plane")
            .pushPoints(self.mount_points)
            .hole(6)
        )

    def mounting_holes(self, w):
        return (
            w.copyWorkplane(
                self.mount.workplaneFromTagged("mounting_plane").transformed(
                    rotate=(180, 0, 0)
                )
            )
            .pushPoints(self.mount_points)
            .hole(6)
        )

    def assembly(self):
        return cq.Assembly(name="spindle motor mount").add(self.mount, name="mount")


g = Gantry(cq.Workplane())
c = Carriage(g, 150, -50)
ca = Carrier(c, -50)
m = SpindleMount(ca, -200)
m2 = SpindleMount(ca, -200 + 80)
m3 = SpindleMount(ca, -200 + 160)
m4 = SpindleMount(ca, -200 + 240)
m5 = SpindleMount(ca, -200 + 320)
ca.plate = m.mounting_holes(ca.plate)
ca.plate = m2.mounting_holes(ca.plate)
ca.plate = m3.mounting_holes(ca.plate)
ca.plate = m4.mounting_holes(ca.plate)
ca.plate = m5.mounting_holes(ca.plate)

# TODO add ball screws and associated assembly

full_assembly = (
    cq.Assembly()
    .add(g.assembly(), name="gantry")
    .add(c.assembly(), name="carriage")
    .add(ca.assembly(), name="carrier")
    .add(m.assembly(), name="spindle mount")
)

if __name__ == "__main__":
    show_object(full_assembly)
    # show_object(ca.assembly())
    # show_object(c.assembly())
    # show_object(g.assembly())
