import pandas as pd from itertools import permutations from fastapi import FastAPI, File, UploadFile, Form, HTTPException, Request, Response from fastapi.middleware.cors import CORSMiddleware from pydantic import BaseModel from typing import List app = FastAPI() app.add_middleware( CORSMiddleware, allow_origins=["*"], # Allows all origins allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) @app.options("/{rest_of_path:path}") async def preflight_handler(request: Request, rest_of_path: str) -> Response: return Response(headers={ "Access-Control-Allow-Origin": "*", # Allows all origins "Access-Control-Allow-Methods": "POST, GET, OPTIONS, PUT, DELETE", # Specify methods allowed "Access-Control-Allow-Headers": "*", # Allows all headers }) class Box: def __init__(self, length, width, height, quantity, box_type): self.length = length self.width = width self.height = height self.quantity = quantity if quantity > 0 else 1 # Ensure at least 1 box if quantity is not specified self.type = box_type def rotated_dimensions(self): # Return rotations that maximize base area and minimize height possible_rotations = [ (self.length, self.width, self.height), (self.length, self.height, self.width), (self.width, self.length, self.height), (self.width, self.height, self.length), (self.height, self.length, self.width), (self.height, self.width, self.length) ] # Sort by volume and base area to prioritize the best fit return sorted(possible_rotations, key=lambda x: (x[0] * x[1], x[2]), reverse=True) def volume(self): return self.length * self.width * self.height class Truck: def __init__(self, length, width, height): self.length = length self.width = width self.height = height self.volume = length * width * height self.placed_boxes = [] self.available_space = [(0, 0, 0, length, width, height)] # (x, y, z, l, w, h) def add_box(self, box): best_fit = None best_fit_space_index = -1 for rotation in box.rotated_dimensions(): for i, space in enumerate(self.available_space): x, y, z, l, w, h = space # Check if the box can fit in the current space if rotation[0] <= l and rotation[1] <= w and rotation[2] <= h: if not best_fit or (rotation[0] * rotation[1] > best_fit[0] * best_fit[1]): best_fit = rotation best_fit_space_index = i if best_fit: x, y, z, l, w, h = self.available_space[best_fit_space_index] box_position = (x, y, z) # Place the box in the truck self.placed_boxes.append((best_fit, box_position)) # Update available space after placing the box self.available_space.pop(best_fit_space_index) self.update_space(best_fit, box_position, l, w, h) return box_position else: return None def update_space(self, box, position, l, w, h): x, y, z = position bl, bw, bh = box # Create new spaces based on the placement of the new box new_spaces = [ (x + bl, y, z, l - bl, w, h), # Space to the right (x, y + bw, z, bl, w - bw, h), # Space in front (x, y, z + bh, bl, bw, h - bh), # Space above ] # Filter out invalid spaces new_spaces = [space for space in new_spaces if space[3] > 0 and space[4] > 0 and space[5] > 0] # Add new valid spaces to the available_space list self.available_space.extend(new_spaces) # Sort available spaces to prioritize lower and more central spaces for stacking self.available_space.sort(key=lambda space: (space[2], space[1], space[0])) def pack_boxes(truck, boxes): packed_positions = [] # Sort all boxes by volume (largest first) boxes.sort(key=lambda b: b.volume(), reverse=True) # Pack all boxes, ensuring practical stacking for box in boxes: for _ in range(box.quantity): position = truck.add_box(box) if position is None: break packed_positions.append((box, position)) return packed_positions @app.post("/upload/") async def upload_file( file: UploadFile = File(...), length: float = Form(...), width: float = Form(...), height: float = Form(...), ): if not file: raise HTTPException(status_code=400, detail="No file uploaded") ext = file.filename.split('.')[-1].lower() if ext == 'csv': data = pd.read_csv(file.file) elif ext in ['xls', 'xlsx']: data = pd.read_excel(file.file) else: raise HTTPException(status_code=400, detail="Unsupported file format") # Convert dimensions from CM to inches data['PieceLength'] = data['PieceLength'] / 2.54 data['PieceBreadth'] = data['PieceBreadth'] / 2.54 data['PieceHeight'] = data['PieceHeight'] / 2.54 # Create Box objects with quantity considered boxes = [ Box(row['PieceLength'], row['PieceBreadth'], row['PieceHeight'], row.get('Quantity', 1), f"{row['PieceNo']}-{row['Priority']}") for _, row in data.iterrows() ] # Convert truck dimensions from feet to inches truck_length = length * 12 # Convert to inches truck_width = width * 12 # Convert to inches truck_height = height * 12 # Convert to inches truck = Truck(truck_length, truck_width, truck_height) # Pack the boxes into the truck packed_positions = pack_boxes(truck, boxes) box_data = [ { 'length': box.length, 'width': box.width, 'height': box.height, 'type': box.type, 'quantity': box.quantity, 'position': {'x': pos[0], 'y': pos[1], 'z': pos[2]} } for box, pos in packed_positions ] print(f"quantity {[box_data[i]['quantity'] for i in range(len(box_data))]}") return {"boxes": box_data}