File size: 9,461 Bytes
6a808d5
 
34e5fc6
72726e0
 
 
6a808d5
a23c7e1
72726e0
 
 
 
3f2e2d5
9e93492
3f2e2d5
 
72726e0
6a808d5
afaddb4
 
 
 
 
 
 
 
6a808d5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
463d47b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
72726e0
 
 
463d47b
 
 
 
 
72726e0
6a808d5
72726e0
6a808d5
463d47b
6a808d5
 
72726e0
6a808d5
72726e0
6a808d5
72726e0
6a808d5
 
 
 
 
 
 
 
 
 
 
463d47b
 
 
 
 
 
 
 
 
 
 
6a808d5
72726e0
 
 
6a808d5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3dd9d64
463d47b
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
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

def suggest_truck(boxes):
    trucks = {
        "TATA ACE": {"length": 7, "width": 4.8, "height": 4.8},
        "ASHOK LEYLAND DOST": {"length": 7, "width": 4.8, "height": 4.8},
        "MAHINDRA BOLERO PICK UP": {"length": 8, "width": 5, "height": 4.8},
        "ASHOK LEYLAND BADA DOST": {"length": 9.5, "width": 5.5, "height": 5},
        "TATA 407": {"length": 9, "width": 5.5, "height": 5},
        "EICHER 14 FEET": {"length": 14, "width": 6, "height": 6.5},
        "EICHER 17 FEET": {"length": 17, "width": 6, "height": 7},
        "EICHER 19 FEET": {"length": 19, "width": 7, "height": 7},
        "TATA 22 FEET": {"length": 22, "width": 7.5, "height": 7},
        "TATA TRUCK (6 TYRE)": {"length": 17.5, "width": 7, "height": 7},
        "TAURUS 16 T (10 TYRE)": {"length": 21, "width": 7.2, "height": 7},
        "TAURUS 21 T (12 TYRE)": {"length": 24, "width": 7.3, "height": 7},
        "TAURUS 25 T (14 TYRE)": {"length": 28, "width": 7.8, "height": 7},
        "CONTAINER 20 FT": {"length": 20, "width": 8, "height": 8},
        "CONTAINER 32 FT SXL": {"length": 32, "width": 8, "height": 8},
        "CONTAINER 32 FT MXL": {"length": 32, "width": 8, "height": 8},
        "CONTAINER 32 FT SXL / MXL HQ": {"length": 32, "width": 8, "height": 10},
        "20 FEET OPEN ALL SIDE (ODC)": {"length": 20, "width": 8, "height": 8},
        "28-32 FEET OPEN-TRAILOR JCB ODC": {"length": 28, "width": 8, "height": 8},
        "32 FEET OPEN-TRAILOR ODC": {"length": 32, "width": 8, "height": 8},
        "40 FEET OPEN-TRAILOR ODC": {"length": 40, "width": 8, "height": 8},
        "SCV": {"length": 5, "width": 12, "height": 6},
        "LCV": {"length": 11, "width": 5, "height": 5},
        "ICV": {"length": 16, "width": 6.5, "height": 6.5},
        "MCV": {"length": 19, "width": 7, "height": 7}
    }

    # Sort trucks by volume (smallest first)
    sorted_trucks = sorted(trucks.items(), key=lambda t: t[1]['length'] * t[1]['width'] * t[1]['height'])
    
    # Try packing the boxes in each truck, return the first one that fits all boxes
    for truck_name, dimensions in sorted_trucks:
        truck = Truck(dimensions['length'] * 12, dimensions['width'] * 12, dimensions['height'] * 12)  # Convert to inches
        packed_positions = pack_boxes(truck, boxes)
        
        # Check if all boxes were successfully packed
        if len(packed_positions) == sum(box.quantity for box in boxes):
            return {"name": truck_name, "dimensions": dimensions}
    
    return None

@app.post("/upload/")
async def upload_file(
    file: UploadFile = File(...),
    length: float = Form(None),  # Make these optional
    width: float = Form(None),
    height: float = Form(None),
    name: str = Form(None),
    autoSuggest: bool = Form(False)
):
    if not file:
        raise HTTPException(status_code=400, detail="No file uploaded")
    
    print("truck name ", name)
    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()
    ]
    
    if autoSuggest:
        # Suggest a truck that can fit all the boxes
        suggested_truck = suggest_truck(boxes)
        if suggested_truck:
            length = suggested_truck['dimensions']['length']
            width = suggested_truck['dimensions']['width']
            height = suggested_truck['dimensions']['height']
        else:
            raise HTTPException(status_code=400, detail="No suitable truck found")
    
    # 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
    ]

    return {
        "boxes": box_data,
        "truck": {
            "name": suggested_truck["name"] if autoSuggest else name,
            "dimensions": {
                "length": length,
                "width": width,
                "height": height
            }
        }
    }