# -*- coding: utf-8 -*-
# FreeCAD Macro: Concrete-Based Large-Bore Spindle Assembly (Visualization-Grade)
#
# Creates a parametric assembly including:
# - Ground pipe spindle (4.5" OD, 3.5" ID) with 3" feed-through capability
# - Timken 68450 cone + matching cup (placeholder dimensions; default uses 68450/68726-ish envelope)
# - Seat collars (sleeves) on the spindle providing axial shoulders for the cones
# - Housing with cup bores + mounting features
# - 3-jaw chuck (simplified) + bolt-hole pattern + register
# - Concrete block with a pocket receiving the housing + anchor holes
# - NEMA 34 stepper, pulleys, belt, and a mounting plate/stand-off structure
#
# IMPORTANT:
# - This is a "solid layout model" suitable for visualization, fit checks, and iteration.
# - It is NOT a complete manufacturing drawing (no fillet/undercut/chamfer abutment compliance, no fits).
# - Bearing geometry is simplified (rings). Update bearing dims to match your exact cup choice and catalog.
#
# Usage:
# 1) Macro -> Create -> Paste -> Run
# 2) Edit Spreadsheet "SPINDLE_VARS" values (in inches) -> Run again to rebuild
#
import FreeCAD as App
import Part
import math

DOC = App.ActiveDocument
if DOC is None:
    DOC = App.newDocument("ConcreteSpindleAssembly")

MM_PER_IN = 25.4

def inch(x): return float(x) * MM_PER_IN

def get_or_create_spreadsheet(name="SPINDLE_VARS"):
    ss = DOC.getObject(name)
    if ss is None:
        ss = DOC.addObject("Spreadsheet::Sheet", name)
    return ss

def safe_set_alias(ss, cell, alias):
    try:
        ss.setAlias(cell, alias)
    except Exception:
        pass

def safe_set_comment(ss, cell, comment):
    try:
        ss.setComment(cell, comment)
    except Exception:
        pass

def ss_set(ss, cell, value, alias=None, comment=None):
    ss.set(cell, str(value))
    if alias: safe_set_alias(ss, cell, alias)
    if comment: safe_set_comment(ss, cell, comment)

def ss_get(ss, alias, default):
    try:
        return float(ss.get(alias))
    except Exception:
        return float(default)

def purge(prefix="GEN_"):
    todel = [o.Name for o in DOC.Objects if o.Name.startswith(prefix)]
    for n in todel:
        DOC.removeObject(n)

def add(name, shape):
    obj = DOC.addObject("Part::Feature", name)
    obj.Shape = shape
    return obj

def cyl(r_mm, h_mm, z0=0, x0=0, y0=0):
    return Part.makeCylinder(r_mm, h_mm, App.Vector(x0, y0, z0))

def box(dx_mm, dy_mm, dz_mm, x0=0, y0=0, z0=0):
    return Part.makeBox(dx_mm, dy_mm, dz_mm, App.Vector(x0, y0, z0))

def ring(od_in, id_in, w_in, z0_mm, name):
    od_r = inch(od_in/2)
    id_r = inch(id_in/2)
    w_mm  = inch(w_in)
    outer = cyl(od_r, w_mm, z0_mm)
    inner = cyl(id_r, w_mm, z0_mm)
    return add(name, outer.cut(inner))

def bolt_circle_holes(num, bcd_in, hole_d_in, thickness_mm, z0_mm, x0=0, y0=0):
    holes = []
    R = inch(bcd_in/2.0)
    d = inch(hole_d_in)
    for i in range(num):
        ang = 2*math.pi*i/num
        x = x0 + R*math.cos(ang)
        y = y0 + R*math.sin(ang)
        holes.append(Part.makeCylinder(d/2, thickness_mm, App.Vector(x, y, z0_mm)))
    comp = holes[0]
    for h in holes[1:]:
        comp = comp.fuse(h)
    return comp

# -----------------------------
# Parameters (Spreadsheet)
# -----------------------------
ss = get_or_create_spreadsheet("SPINDLE_VARS")

# First-run init check by alias existence
try:
    ss.get("pipe_od_in"); first_run = False
except Exception:
    first_run = True

if first_run:
    ss_set(ss, "A1", "Parameter"); ss_set(ss, "B1", "Value (in)")

    # Spindle pipe (given)
    ss_set(ss, "A3", "pipe_od_in");    ss_set(ss, "B3", 4.500, "pipe_od_in", "Ground pipe OD (spindle tube OD)")
    ss_set(ss, "A4", "pipe_id_in");    ss_set(ss, "B4", 3.500, "pipe_id_in", "Pipe ID (through bore). 3.5\" gives clearance for 3\" bar feed")
    ss_set(ss, "A5", "spindle_len_in");ss_set(ss, "B5", 22.000, "spindle_len_in", "Overall spindle length (front face to rear end)")

    # Bearing set (Timken 68450 cone bore = 4.5\"; choose cup envelope)
    # Default here is approx for 68450/68726-ish: cup OD ~7.25, overall bearing width ~1.4688
    ss_set(ss, "A10", "brg_cone_id_in"); ss_set(ss, "B10", 4.500, "brg_cone_id_in", "Cone bore ID (fits spindle journal)")
    ss_set(ss, "A11", "brg_cup_od_in");  ss_set(ss, "B11", 7.250, "brg_cup_od_in", "Cup OD (housing bore). Update to your exact cup")
    ss_set(ss, "A12", "brg_width_in");   ss_set(ss, "B12", 1.469, "brg_width_in", "Bearing assembly width (simplified envelope)")

    # Two front bearings + one rear bearing
    ss_set(ss, "A13", "front_pair_gap_in"); ss_set(ss, "B13", 0.375, "front_pair_gap_in", "Gap between front bearing envelopes (spacer region)")
    ss_set(ss, "A14", "front_setback_in");  ss_set(ss, "B14", 1.250, "front_setback_in", "From spindle nose to first front bearing start")
    ss_set(ss, "A15", "rear_from_front_in");ss_set(ss, "B15", 10.000, "rear_from_front_in", "Distance from first front bearing start to rear bearing start")

    # Seat collars (provide shoulders behind cones)
    ss_set(ss, "A20", "collar_od_in"); ss_set(ss, "B20", 6.000, "collar_od_in", "Collar OD (sleeve on spindle) to provide shoulder faces")
    ss_set(ss, "A21", "collar_w_in");  ss_set(ss, "B21", 0.750, "collar_w_in", "Collar axial width")
    ss_set(ss, "A22", "collar_clear_in"); ss_set(ss, "B22", 0.002, "collar_clear_in", "ID clearance for collar over pipe OD (model only)")

    # Housing
    ss_set(ss, "A30", "housing_len_in"); ss_set(ss, "B30", 16.000, "housing_len_in", "Housing length")
    ss_set(ss, "A31", "housing_wall_in");ss_set(ss, "B31", 1.000, "housing_wall_in", "Housing wall outside cup OD")
    ss_set(ss, "A32", "housing_flange_od_in"); ss_set(ss, "B32", 10.000, "housing_flange_od_in", "Front housing flange OD")
    ss_set(ss, "A33", "housing_flange_thk_in"); ss_set(ss, "B33", 0.750, "housing_flange_thk_in", "Front housing flange thickness")
    ss_set(ss, "A34", "cup_bore_clear_in"); ss_set(ss, "B34", 0.002, "cup_bore_clear_in", "Model-only bore clearance for cup OD")

    # Chuck (simplified)
    ss_set(ss, "A40", "chuck_od_in"); ss_set(ss, "B40", 8.000, "chuck_od_in", "3-jaw chuck OD (simplified)")
    ss_set(ss, "A41", "chuck_thk_in");ss_set(ss, "B41", 3.000, "chuck_thk_in", "Chuck thickness")
    ss_set(ss, "A42", "chuck_bolt_qty"); ss_set(ss, "B42", 6, "chuck_bolt_qty", "Chuck bolt holes count")
    ss_set(ss, "A43", "chuck_bcd_in"); ss_set(ss, "B43", 6.250, "chuck_bcd_in", "Chuck bolt circle diameter")
    ss_set(ss, "A44", "chuck_bolt_d_in"); ss_set(ss, "B44", 0.500, "chuck_bolt_d_in", "Chuck bolt hole diameter")
    ss_set(ss, "A45", "chuck_register_od_in"); ss_set(ss, "B45", 6.000, "chuck_register_od_in", "Spindle nose register OD")

    # Concrete block
    ss_set(ss, "A50", "conc_x_in"); ss_set(ss, "B50", 24.000, "conc_x_in", "Concrete block X size")
    ss_set(ss, "A51", "conc_y_in"); ss_set(ss, "B51", 18.000, "conc_y_in", "Concrete block Y size")
    ss_set(ss, "A52", "conc_z_in"); ss_set(ss, "B52", 18.000, "conc_z_in", "Concrete block Z size")
    ss_set(ss, "A53", "conc_pocket_clear_in"); ss_set(ss, "B53", 0.250, "conc_pocket_clear_in", "Clearance around housing in concrete pocket")
    ss_set(ss, "A54", "anchor_hole_d_in"); ss_set(ss, "B54", 0.750, "anchor_hole_d_in", "Anchor hole diameter (simplified)")
    ss_set(ss, "A55", "anchor_bcd_in"); ss_set(ss, "B55", 12.000, "anchor_bcd_in", "Anchor bolt circle diameter on concrete top face")
    ss_set(ss, "A56", "anchor_qty"); ss_set(ss, "B56", 4, "anchor_qty", "Number of anchor holes")

    # Stepper + drive
    ss_set(ss, "A60", "nema34_body_x_in"); ss_set(ss, "B60", 3.400, "nema34_body_x_in", "NEMA 34 body X")
    ss_set(ss, "A61", "nema34_body_y_in"); ss_set(ss, "B61", 3.400, "nema34_body_y_in", "NEMA 34 body Y")
    ss_set(ss, "A62", "nema34_body_z_in"); ss_set(ss, "B62", 5.000, "nema34_body_z_in", "NEMA 34 body length")
    ss_set(ss, "A63", "stepper_shaft_d_in"); ss_set(ss, "B63", 0.500, "stepper_shaft_d_in", "Stepper shaft diameter (simplified)")
    ss_set(ss, "A64", "stepper_shaft_len_in"); ss_set(ss, "B64", 1.250, "stepper_shaft_len_in", "Stepper shaft length")

    ss_set(ss, "A65", "pulley_driver_od_in"); ss_set(ss, "B65", 2.000, "pulley_driver_od_in", "Driver pulley OD")
    ss_set(ss, "A66", "pulley_driven_od_in"); ss_set(ss, "B66", 6.000, "pulley_driven_od_in", "Driven pulley OD on spindle")
    ss_set(ss, "A67", "pulley_w_in"); ss_set(ss, "B67", 1.250, "pulley_w_in", "Pulley width")
    ss_set(ss, "A68", "belt_thk_in"); ss_set(ss, "B68", 0.250, "belt_thk_in", "Belt thickness (visual)")
    ss_set(ss, "A69", "belt_width_in"); ss_set(ss, "B69", 1.000, "belt_width_in", "Belt width (visual)")
    ss_set(ss, "A70", "stepper_offset_x_in"); ss_set(ss, "B70", 9.000, "stepper_offset_x_in", "Stepper center offset in +X from spindle axis")
    ss_set(ss, "A71", "stepper_offset_y_in"); ss_set(ss, "B71", 0.000, "stepper_offset_y_in", "Stepper center offset in Y from spindle axis")
    ss_set(ss, "A72", "stepper_mount_z_in"); ss_set(ss, "B72", 10.000, "stepper_mount_z_in", "Stepper center height above concrete base (Z)")

DOC.recompute()

# -----------------------------
# Read parameters
# -----------------------------
pipe_od_in = ss_get(ss, "pipe_od_in", 4.5)
pipe_id_in = ss_get(ss, "pipe_id_in", 3.5)
spindle_len_in = ss_get(ss, "spindle_len_in", 22.0)

brg_cone_id_in = ss_get(ss, "brg_cone_id_in", 4.5)
brg_cup_od_in = ss_get(ss, "brg_cup_od_in", 7.25)
brg_width_in  = ss_get(ss, "brg_width_in", 1.469)

front_pair_gap_in = ss_get(ss, "front_pair_gap_in", 0.375)
front_setback_in  = ss_get(ss, "front_setback_in", 1.25)
rear_from_front_in = ss_get(ss, "rear_from_front_in", 10.0)

collar_od_in = ss_get(ss, "collar_od_in", 6.0)
collar_w_in  = ss_get(ss, "collar_w_in", 0.75)
collar_clear_in = ss_get(ss, "collar_clear_in", 0.002)

housing_len_in = ss_get(ss, "housing_len_in", 16.0)
housing_wall_in = ss_get(ss, "housing_wall_in", 1.0)
housing_flange_od_in = ss_get(ss, "housing_flange_od_in", 10.0)
housing_flange_thk_in = ss_get(ss, "housing_flange_thk_in", 0.75)
cup_bore_clear_in = ss_get(ss, "cup_bore_clear_in", 0.002)

chuck_od_in = ss_get(ss, "chuck_od_in", 8.0)
chuck_thk_in = ss_get(ss, "chuck_thk_in", 3.0)
chuck_bolt_qty = int(ss_get(ss, "chuck_bolt_qty", 6))
chuck_bcd_in = ss_get(ss, "chuck_bcd_in", 6.25)
chuck_bolt_d_in = ss_get(ss, "chuck_bolt_d_in", 0.5)
chuck_register_od_in = ss_get(ss, "chuck_register_od_in", 6.0)

conc_x_in = ss_get(ss, "conc_x_in", 24.0)
conc_y_in = ss_get(ss, "conc_y_in", 18.0)
conc_z_in = ss_get(ss, "conc_z_in", 18.0)
conc_pocket_clear_in = ss_get(ss, "conc_pocket_clear_in", 0.25)
anchor_hole_d_in = ss_get(ss, "anchor_hole_d_in", 0.75)
anchor_bcd_in = ss_get(ss, "anchor_bcd_in", 12.0)
anchor_qty = int(ss_get(ss, "anchor_qty", 4))

nema34_body_x_in = ss_get(ss, "nema34_body_x_in", 3.4)
nema34_body_y_in = ss_get(ss, "nema34_body_y_in", 3.4)
nema34_body_z_in = ss_get(ss, "nema34_body_z_in", 5.0)
stepper_shaft_d_in = ss_get(ss, "stepper_shaft_d_in", 0.5)
stepper_shaft_len_in = ss_get(ss, "stepper_shaft_len_in", 1.25)

pulley_driver_od_in = ss_get(ss, "pulley_driver_od_in", 2.0)
pulley_driven_od_in = ss_get(ss, "pulley_driven_od_in", 6.0)
pulley_w_in = ss_get(ss, "pulley_w_in", 1.25)
belt_thk_in = ss_get(ss, "belt_thk_in", 0.25)
belt_width_in = ss_get(ss, "belt_width_in", 1.0)

stepper_offset_x_in = ss_get(ss, "stepper_offset_x_in", 9.0)
stepper_offset_y_in = ss_get(ss, "stepper_offset_y_in", 0.0)
stepper_mount_z_in = ss_get(ss, "stepper_mount_z_in", 10.0)

# -----------------------------
# Layout coordinates
# -----------------------------
# Coordinate convention:
# - Spindle axis is Z-axis; nose at Z=0; extends +Z rearward.
# - Concrete block occupies 0..Z conc_z, centered in X/Y about origin.
# - Housing sits in a pocket near the top centerline.

purge("GEN_")

# Derived Z locations
z_nose = 0.0
z_flange_end = inch(housing_flange_thk_in)
z_brg1 = inch(front_setback_in) + z_flange_end
z_brg2 = z_brg1 + inch(brg_width_in) + inch(front_pair_gap_in)
z_brg3 = z_brg1 + inch(rear_from_front_in)

# Concrete block origin
conc_dx = inch(conc_x_in)
conc_dy = inch(conc_y_in)
conc_dz = inch(conc_z_in)

# Center block on X/Y, base at Z=0
conc_x0 = -conc_dx/2
conc_y0 = -conc_dy/2
conc_z0 = 0.0

# -----------------------------
# Concrete block with pocket + anchor holes
# -----------------------------
conc = box(conc_dx, conc_dy, conc_dz, conc_x0, conc_y0, conc_z0)

# Pocket for housing (rectangular pocket for manufacturability)
# Pocket centered on axis, cut from top down.
pocket_clear = inch(conc_pocket_clear_in)
# Housing OD estimate: cup OD + 2*wall
housing_od_in = brg_cup_od_in + 2.0*housing_wall_in
pocket_w = inch(housing_od_in) + 2*pocket_clear
pocket_h = inch(housing_od_in) + 2*pocket_clear
pocket_depth = inch(housing_len_in) + inch(1.0)  # extend a bit

pocket = box(pocket_w, pocket_h, pocket_depth,
             -pocket_w/2, -pocket_h/2, conc_dz - pocket_depth)

conc = conc.cut(pocket)

# Anchor holes from top face
anchor_holes = bolt_circle_holes(anchor_qty, anchor_bcd_in, anchor_hole_d_in,
                                 thickness_mm=inch(2.0), z0_mm=conc_dz - inch(2.0))
conc = conc.cut(anchor_holes)

add("GEN_ConcreteBlock", conc)

# -----------------------------
# Housing (fixed cartridge)
# -----------------------------
housing_len = inch(housing_len_in)
cup_bore_r = inch((brg_cup_od_in + cup_bore_clear_in)/2.0)
housing_outer_r = inch((brg_cup_od_in + 2.0*housing_wall_in)/2.0)

housing_main = cyl(housing_outer_r, housing_len, z0=0)

# Front flange
flange_r = inch(housing_flange_od_in/2.0)
flange_thk = inch(housing_flange_thk_in)
flange = cyl(flange_r, flange_thk, z0=0)
housing_main = housing_main.fuse(flange)

# Inner clearance (so spindle rotates): clear a bit beyond pipe OD
clear_r = inch(pipe_od_in/2.0) + inch(0.10)
inner_clear = cyl(clear_r, housing_len, z0=0)
housing_main = housing_main.cut(inner_clear)

# Cup bores (3 bores)
cup1 = cyl(cup_bore_r, inch(brg_width_in), z0=z_brg1)
cup2 = cyl(cup_bore_r, inch(brg_width_in), z0=z_brg2)
cup3 = cyl(cup_bore_r, inch(brg_width_in), z0=z_brg3)
housing_main = housing_main.cut(cup1).cut(cup2).cut(cup3)

# Mount holes on flange (simple 6-hole pattern)
flange_holes = bolt_circle_holes(6, bcd_in=housing_flange_od_in*0.75, hole_d_in=0.625,
                                 thickness_mm=flange_thk, z0_mm=0.0)
housing_main = housing_main.cut(flange_holes)

housing_obj = add("GEN_Housing", housing_main)

# Place housing in concrete pocket (same axis, near top)
# Shift housing upward so flange near top face.
housing_shift_z = conc_dz - housing_len - inch(1.0)
housing_obj.Placement.Base = App.Vector(0, 0, housing_shift_z)

# -----------------------------
# Spindle pipe (rotating)
# -----------------------------
spindle_len = inch(spindle_len_in)
pipe_od_r = inch(pipe_od_in/2.0)
pipe_id_r = inch(pipe_id_in/2.0)

spindle_outer = cyl(pipe_od_r, spindle_len, z0=0)
spindle_bore  = cyl(pipe_id_r, spindle_len, z0=0)
spindle = spindle_outer.cut(spindle_bore)

# Spindle nose flange / chuck mount plate (simplified)
nose_flange_od_in = max(chuck_register_od_in + 2.0, 6.5)
nose_flange_r = inch(nose_flange_od_in/2.0)
nose_flange_thk = inch(1.0)
nose_flange = cyl(nose_flange_r, nose_flange_thk, z0=0)
# Cut center to pipe OD (so flange becomes a plate around the pipe)
nose_flange = nose_flange.cut(cyl(pipe_od_r, nose_flange_thk, z0=0))

# Register boss
reg_r = inch(chuck_register_od_in/2.0)
reg_thk = inch(0.375)
register = cyl(reg_r, reg_thk, z0=0)
register = register.cut(cyl(pipe_od_r, reg_thk, z0=0))

# Fuse to spindle
spindle = spindle.fuse(nose_flange).fuse(register)

# Bolt holes for chuck through the nose flange (assumed through 1.0")
bh = bolt_circle_holes(chuck_bolt_qty, chuck_bcd_in, chuck_bolt_d_in,
                       thickness_mm=nose_flange_thk, z0_mm=0.0)
spindle = spindle.cut(bh)

# Seat collars (sleeves) on spindle to provide shoulders for cones
collar_id_in = pipe_od_in + collar_clear_in
# Place collar behind each bearing envelope start (acting as a shoulder land)
collar_w = inch(collar_w_in)
for idx, zc in enumerate([z_brg1 - collar_w, z_brg2 - collar_w, z_brg3 - collar_w]):
    col_outer = cyl(inch(collar_od_in/2.0), collar_w, z0=zc)
    col_inner = cyl(inch(collar_id_in/2.0), collar_w, z0=zc)
    collar = col_outer.cut(col_inner)
    spindle = spindle.fuse(collar)

spindle_obj = add("GEN_Spindle", spindle)

# Place spindle coaxial with housing, slightly protruding front for chuck
spindle_obj.Placement.Base = App.Vector(0, 0, housing_shift_z - inch(2.0))

# -----------------------------
# Bearings (simplified as rings)
# -----------------------------
# Cone ring: ID=cone bore, OD approx = cup OD - some; Cup ring: ID ~ cone OD, OD=cup OD
# For visualization, we’ll show:
# - Cone (inner race) ring with OD = cone_od_vis
# - Cup (outer race) ring with OD = cup OD, ID = cup ID vis (slightly smaller)
cone_od_vis_in = brg_cup_od_in - 1.0
cup_id_vis_in  = cone_od_vis_in + 0.10

def bearing_pair(z0, n):
    ring(cone_od_vis_in, brg_cone_id_in, brg_width_in, z0, f"GEN_Cone_{n}")
    ring(brg_cup_od_in,  cup_id_vis_in,  brg_width_in, z0, f"GEN_Cup_{n}")

bearing_pair(z_brg1, 1)
bearing_pair(z_brg2, 2)
bearing_pair(z_brg3, 3)

# Align bearings with spindle/housing placements
for obj in DOC.Objects:
    if obj.Name.startswith("GEN_Cone_") or obj.Name.startswith("GEN_Cup_"):
        obj.Placement.Base = spindle_obj.Placement.Base

# -----------------------------
# Driven pulley on spindle (rear)
# -----------------------------
pulley_w = inch(pulley_w_in)
driven_r = inch(pulley_driven_od_in/2.0)
driven = cyl(driven_r, pulley_w, z0=spindle_len - pulley_w - inch(0.5))
driven = driven.cut(cyl(pipe_od_r, pulley_w, z0=spindle_len - pulley_w - inch(0.5)))
driven_obj = add("GEN_Pulley_Driven", driven)
driven_obj.Placement.Base = spindle_obj.Placement.Base

# -----------------------------
# Chuck (simplified 3-jaw chuck)
# -----------------------------
chuck_r = inch(chuck_od_in/2.0)
chuck_thk = inch(chuck_thk_in)
chuck = cyl(chuck_r, chuck_thk, z0=-chuck_thk)  # in front of nose at negative Z
# Bore clearance
chuck = chuck.cut(cyl(inch((chuck_register_od_in-0.05)/2.0), chuck_thk, z0=-chuck_thk))

# Add 3 jaws as simple rectangular extrusions on face
jaw_w = inch(1.0)
jaw_l = inch(1.5)
jaw_h = inch(0.8)
for i in range(3):
    ang = 2*math.pi*i/3
    rj = inch(chuck_od_in/2.0) - inch(1.2)
    x = rj*math.cos(ang) - jaw_l/2
    y = rj*math.sin(ang) - jaw_w/2
    jaw = box(jaw_l, jaw_w, jaw_h, x, y, -jaw_h)
    chuck = chuck.fuse(jaw)

# Chuck bolt holes matching spindle nose pattern
chuck_holes = bolt_circle_holes(chuck_bolt_qty, chuck_bcd_in, chuck_bolt_d_in,
                                thickness_mm=inch(1.0), z0_mm=-inch(1.0))
chuck = chuck.cut(chuck_holes)

chuck_obj = add("GEN_Chuck_3Jaw", chuck)
chuck_obj.Placement.Base = spindle_obj.Placement.Base

# -----------------------------
# NEMA 34 Stepper + driver pulley + mount plate
# -----------------------------
# Stepper body as box, shaft as cylinder.
step_dx = inch(nema34_body_x_in)
step_dy = inch(nema34_body_y_in)
step_dz = inch(nema34_body_z_in)

step_center_x = inch(stepper_offset_x_in)
step_center_y = inch(stepper_offset_y_in)
step_center_z = conc_z0 + inch(stepper_mount_z_in)

# Place stepper so shaft points toward -X (toward spindle), shaft axis parallel to X
# We'll model it by building along X and then placing via rotation.
body = box(step_dz, step_dx, step_dy,
           x0=-step_dz/2, y0=-step_dx/2, z0=-step_dy/2)

shaft_r = inch(stepper_shaft_d_in/2.0)
shaft_len = inch(stepper_shaft_len_in)
shaft = Part.makeCylinder(shaft_r, shaft_len, App.Vector(step_dz/2, 0, 0), App.Vector(1,0,0))

stepper_shape = body.fuse(shaft)
stepper_obj = add("GEN_Stepper_NEMA34", stepper_shape)

# Rotate so body X-axis aligns world X-axis already; then translate
stepper_obj.Placement = App.Placement(
    App.Vector(step_center_x, step_center_y, step_center_z),
    App.Rotation(App.Vector(0,1,0), 0)
)

# Driver pulley on shaft
driver_r = inch(pulley_driver_od_in/2.0)
driver = Part.makeCylinder(driver_r, pulley_w, App.Vector(step_dz/2 + shaft_len - pulley_w, 0, 0), App.Vector(1,0,0))
driver_obj = add("GEN_Pulley_Driver", driver)
driver_obj.Placement = stepper_obj.Placement

# Stepper mount plate (bolts to concrete top)
plate_thk = inch(0.5)
plate_dx = inch(8.0)
plate_dy = inch(6.0)
plate = box(plate_dx, plate_dy, plate_thk,
            x0=step_center_x - plate_dx/2,
            y0=step_center_y - plate_dy/2,
            z0=step_center_z - step_dy/2 - plate_thk)

# Add 4 mounting holes in plate
hole_d = inch(0.625)
for xsgn in [-1, 1]:
    for ysgn in [-1, 1]:
        hx = step_center_x + xsgn*inch(3.0)
        hy = step_center_y + ysgn*inch(2.0)
        hz = step_center_z - step_dy/2 - plate_thk
        plate = plate.cut(Part.makeCylinder(hole_d/2, plate_thk, App.Vector(hx, hy, hz)))

plate_obj = add("GEN_Stepper_MountPlate", plate)

# -----------------------------
# Belt (visual) as a thickened loop between pulley centers
# -----------------------------
# Compute pulley center points in world coordinates (approx)
# Driven pulley center: spindle axis at X=0,Y=0; Z at spindle placement + location of driven pulley mid
driven_center = spindle_obj.Placement.Base + App.Vector(0, 0, (spindle_len - pulley_w - inch(0.5)) + pulley_w/2)
driver_center = App.Vector(step_center_x + (step_dz/2 + shaft_len - pulley_w/2), step_center_y, step_center_z)

# Create a simple "belt slab" as a swept rectangle along a 2D polyline (top view)
# We'll approximate in XY plane at Z = average pulley Z, ignoring crown/teeth.
belt_z = (driven_center.z + driver_center.z)/2.0
belt_w = inch(belt_width_in)
belt_t = inch(belt_thk_in)

# Polyline points around pulleys (very simplified)
p1 = App.Vector(driven_center.x, driven_center.y + driven_r, belt_z)
p2 = App.Vector(driver_center.x, driver_center.y + driver_r, belt_z)
p3 = App.Vector(driver_center.x, driver_center.y - driver_r, belt_z)
p4 = App.Vector(driven_center.x, driven_center.y - driven_r, belt_z)

wire = Part.makePolygon([p1, p2, p3, p4, p1])
# Create belt cross-section rectangle
rect = Part.makePlane(belt_t, belt_w, App.Vector(0, -belt_w/2, 0), App.Vector(1,0,0), App.Vector(0,0,1)).Shape
# Sweep along wire
belt = rect.makePipeShell([wire], True, True)
belt_obj = add("GEN_Belt", belt)

DOC.recompute()
print("Generated concrete-based spindle assembly. Edit SPINDLE_VARS and rerun to rebuild.")
