# freecad_box_generator.py
#
# A Python script to generate a parametric box natively in FreeCAD.
#
# --- How to use (GUI) ---
# 1. Open FreeCAD and ensure you have a new, empty document open.
# 2. Paste this script into the Python console and press Enter.
#
# --- How to use (Command Line) ---
# FreeCADCmd.exe freecad_box_generator.py [output_file.FCStd]

import FreeCAD
import Part
import sys

# --- Customizable Parameters ---
# (Mirrors the parameters from the OpenSCAD file)

# Overall dimensions
TOTAL_WIDTH = 500
TOTAL_LENGTH = 500
BOX_HEIGHT = 80

# Material and slot properties
WALL_THICKNESS = 8

# Internal grid configuration
NUM_BOXES_U = 2 # Along X-axis (width)
NUM_BOXES_V = 2 # Along Y-axis (length)


def create_box_assembly(doc):
    """
    Generates the box assembly as native FreeCAD objects in the specified document.
    This function does not save the file.
    """
    
    # --- Create a Group for organization ---
    box_group = doc.addObject("App::DocumentObjectGroup", "BoxAssembly")

    # --- Create the Base Plate ---
    base = Part.makeBox(TOTAL_WIDTH, TOTAL_LENGTH, WALL_THICKNESS)
    base_obj = doc.addObject("Part::Feature", "Base")
    base_obj.Shape = base
    box_group.addObject(base_obj)

    # --- Calculate Inner Dimensions ---
    inner_width = TOTAL_WIDTH - (2 * WALL_THICKNESS)
    inner_length = TOTAL_LENGTH - (2 * WALL_THICKNESS)
    
    # --- Create Outer Walls ---
    # South Wall (along the front X-axis)
    wall_south = Part.makeBox(TOTAL_WIDTH, WALL_THICKNESS, BOX_HEIGHT)
    wall_south.translate(FreeCAD.Vector(0, 0, WALL_THICKNESS)) # Position it on top of the base
    wall_south_obj = doc.addObject("Part::Feature", "Wall_South")
    wall_south_obj.Shape = wall_south
    box_group.addObject(wall_south_obj)
    
    # North Wall (along the back X-axis)
    wall_north = Part.makeBox(TOTAL_WIDTH, WALL_THICKNESS, BOX_HEIGHT)
    wall_north.translate(FreeCAD.Vector(0, TOTAL_LENGTH - WALL_THICKNESS, WALL_THICKNESS))
    wall_north_obj = doc.addObject("Part::Feature", "Wall_North")
    wall_north_obj.Shape = wall_north
    box_group.addObject(wall_north_obj)

    # West Wall (along the left Y-axis, fits between N/S walls)
    wall_west = Part.makeBox(WALL_THICKNESS, inner_length, BOX_HEIGHT)
    wall_west.translate(FreeCAD.Vector(0, WALL_THICKNESS, WALL_THICKNESS))
    wall_west_obj = doc.addObject("Part::Feature", "Wall_West")
    wall_west_obj.Shape = wall_west
    box_group.addObject(wall_west_obj)
    
    # East Wall (along the right Y-axis, fits between N/S walls)
    wall_east = Part.makeBox(WALL_THICKNESS, inner_length, BOX_HEIGHT)
    wall_east.translate(FreeCAD.Vector(TOTAL_WIDTH - WALL_THICKNESS, WALL_THICKNESS, WALL_THICKNESS))
    wall_east_obj = doc.addObject("Part::Feature", "Wall_East")
    wall_east_obj.Shape = wall_east
    box_group.addObject(wall_east_obj)
    
    # --- Create Internal Dividers ---
    if NUM_BOXES_U > 1:
        # --- Vertical Dividers (along Y-axis) ---
        compartment_width = inner_width / NUM_BOXES_U
        for i in range(1, NUM_BOXES_U):
            # Create the main body of the divider
            x_pos = (i * compartment_width)
            divider_v_body = Part.makeBox(WALL_THICKNESS, inner_length, BOX_HEIGHT)
            
            # --- Create Notches for Horizontal Dividers (top-down) ---
            if NUM_BOXES_V > 1:
                # Create all notch cutting tools first
                notch_tools = []
                notch_cutout = Part.makeBox(WALL_THICKNESS, WALL_THICKNESS, BOX_HEIGHT / 2)
                for j in range(1, NUM_BOXES_V):
                    compartment_length = inner_length / NUM_BOXES_V
                    y_pos = (j * compartment_length) - (WALL_THICKNESS / 2)
                    notch_tools.append(notch_cutout.translated(FreeCAD.Vector(0, y_pos, BOX_HEIGHT / 2)))
                # Fuse them into a single cutting tool
                cutting_compound = Part.makeCompound(notch_tools)
                # Perform a single, efficient cut
                divider_v_body = divider_v_body.cut(cutting_compound)
            
            # Position the final divider and add to document
            divider_v_body.translate(FreeCAD.Vector(x_pos, WALL_THICKNESS, WALL_THICKNESS))
            divider_v_obj = doc.addObject("Part::Feature", f"Divider_V_{i}")
            divider_v_obj.Shape = divider_v_body
            box_group.addObject(divider_v_obj)

    if NUM_BOXES_V > 1:
        # --- Horizontal Dividers (along X-axis) ---
        compartment_length = inner_length / NUM_BOXES_V
        for i in range(1, NUM_BOXES_V):
            # Create the main body of the divider
            y_pos = (i * compartment_length)
            divider_h_body = Part.makeBox(inner_width, WALL_THICKNESS, BOX_HEIGHT)

            # --- Create Notches for Vertical Dividers (bottom-up) ---
            if NUM_BOXES_U > 1:
                # Create all notch cutting tools first
                notch_tools = []
                notch_cutout = Part.makeBox(WALL_THICKNESS, WALL_THICKNESS, BOX_HEIGHT / 2)
                for j in range(1, NUM_BOXES_U):
                    compartment_width = inner_width / NUM_BOXES_U
                    x_pos = (j * compartment_width) - (WALL_THICKNESS / 2)
                    notch_tools.append(notch_cutout.translated(FreeCAD.Vector(x_pos, 0, 0)))
                # Fuse them into a single cutting tool
                cutting_compound = Part.makeCompound(notch_tools)
                # Perform a single, efficient cut
                divider_h_body = divider_h_body.cut(cutting_compound)

            # Position the final divider and add to document
            divider_h_body.translate(FreeCAD.Vector(WALL_THICKNESS, y_pos, WALL_THICKNESS))
            divider_h_obj = doc.addObject("Part::Feature", f"Divider_H_{i}")
            divider_h_obj.Shape = divider_h_body
            box_group.addObject(divider_h_obj)

    # --- Finalize ---
    doc.recompute()
    print("Box assembly generated.")


def main_cli():
    """Function to run when script is executed from the command line."""
    output_file = "box.FCStd"  # Default filename

    # Look for the output file as a positional argument after the script name
    # This is a robust way to handle arguments when FreeCAD's parser is unpredictable.
    try:
        script_index = [i for i, arg in enumerate(sys.argv) if 'freecad_box_generator.py' in arg][0]
        if script_index + 1 < len(sys.argv):
            output_file = sys.argv[script_index + 1]
    except IndexError:
        # This case handles running the script directly without FreeCAD, for debugging.
        pass

    doc = FreeCAD.newDocument("Box")
    create_box_assembly(doc)
    
    try:
        doc.saveAs(output_file)
        print(f"Successfully saved box assembly to: {output_file}")
    except Exception as e:
        print(f"Error saving file: {e}")
    
    FreeCAD.closeDocument(doc.Name)
    # Ensure the command-line application exits cleanly
    sys.exit()

def main_gui():
    """Function to run when script is executed from the FreeCAD GUI."""
    doc = FreeCAD.activeDocument()
    if not doc:
        doc = FreeCAD.newDocument("Box")
    
    create_box_assembly(doc)
    
    if FreeCAD.GuiUp:
        FreeCAD.Gui.activeDocument().activeView().viewAxonometric()
        FreeCAD.Gui.SendMsgToActiveView("ViewFit")

# --- Main Execution Block ---
# This determines if the script is running in the GUI or from the command line
# and calls the appropriate main function.
if FreeCAD.GuiUp:
    main_gui()
else:
    main_cli() 