Spaces:
Build error
Build error
| import streamlit as st | |
| import ezdxf | |
| import io | |
| import pandas as pd | |
| import matplotlib.pyplot as plt | |
| from fpdf import FPDF | |
| from tempfile import NamedTemporaryFile | |
| import matplotlib.patches as patches | |
| import tempfile | |
| import io | |
| # Constants | |
| BRICK_VOLUME_CFT = (9/12) * (4.5/12) * (3/12) | |
| CEMENT_SAND_RATIO = 1 / 6 | |
| SAND_RATIO = 5 / 6 | |
| CEMENT_DENSITY_KG_PER_CFT = 1440 / 35.3147 | |
| CEMENT_BAG_WEIGHT_KG = 50 | |
| DEFAULT_WALL_THICKNESS = 0.75 | |
| st.set_page_config(page_title="Building Estimator from CAD", layout="wide") | |
| st.title("🏗️ Auto Estimation from AutoCAD (.dxf) Drawing") | |
| uploaded_file = st.file_uploader("Upload your DXF file", type=["dxf"]) | |
| def extract_geometry(file_bytes): | |
| try: | |
| # Convert file bytes into a file-like object | |
| file_stream = io.BytesIO(file_bytes) | |
| doc = ezdxf.read(file_stream) # Use the correct function to read from the byte stream | |
| except Exception as e: | |
| st.error(f"Error reading DXF file: {e}") | |
| return [], 0.75, [], 1, [], [] | |
| msp = doc.modelspace() | |
| rooms = [] | |
| room_shapes = [] | |
| openings = [] | |
| floors = 1 | |
| wall_thickness = DEFAULT_WALL_THICKNESS | |
| wall_pairs = [] | |
| for entity in msp: | |
| if entity.dxftype() == "TEXT": | |
| content = entity.dxf.text.lower() | |
| if "wall thickness" in content: | |
| try: | |
| wall_thickness = float(content.split(":")[1].strip()) | |
| except: | |
| continue | |
| elif "floor" in content: | |
| try: | |
| floors = int(content.split(":")[1].strip()) | |
| except: | |
| continue | |
| elif entity.dxftype() == "LWPOLYLINE": | |
| if entity.closed and len(entity) == 4: | |
| points = entity.get_points() | |
| x_vals = [p[0] for p in points] | |
| y_vals = [p[1] for p in points] | |
| length = abs(max(x_vals) - min(x_vals)) / 12 | |
| width = abs(max(y_vals) - min(y_vals)) / 12 | |
| height = 10 | |
| if length > 2 and width > 2: | |
| rooms.append((length, width, height)) | |
| room_shapes.append((min(x_vals), min(y_vals), max(x_vals), max(y_vals))) | |
| else: | |
| openings.append(("opening", length, height)) | |
| elif entity.dxftype() == "LINE": | |
| # Process lines for walls | |
| start_point = entity.dxf.start | |
| end_point = entity.dxf.end | |
| distance = ((end_point.x - start_point.x) ** 2 + (end_point.y - start_point.y) ** 2) ** 0.5 | |
| if distance >= 9: # If the line represents a wall, assuming threshold distance for walls | |
| wall_pairs.append((start_point, end_point)) | |
| return rooms, wall_thickness, openings, floors, room_shapes, wall_pairs | |
| def estimate(rooms, wall_thickness, openings, floors): | |
| wall_volume = sum(2 * (l + w) * h * wall_thickness for l, w, h in rooms) | |
| opening_volume = sum(l * h * wall_thickness for _, l, h in openings) | |
| beam_volume = sum((l + 1) * 0.75 * 0.75 for _, l, _ in openings) | |
| net_volume = (wall_volume - opening_volume - beam_volume) * floors | |
| number_of_bricks = round((net_volume / BRICK_VOLUME_CFT) * 1.05) | |
| mortar_volume = net_volume * 0.25 | |
| cement_volume = mortar_volume * CEMENT_SAND_RATIO | |
| sand_volume = mortar_volume * SAND_RATIO | |
| cement_bag_volume_cft = CEMENT_BAG_WEIGHT_KG / CEMENT_DENSITY_KG_PER_CFT | |
| cement_bags = round(cement_volume / cement_bag_volume_cft) | |
| return number_of_bricks, sand_volume, cement_bags, { | |
| "Wall Volume (cft)": wall_volume, | |
| "Opening Volume (cft)": opening_volume, | |
| "Beam Volume (cft)": beam_volume, | |
| "Net Volume (cft)": net_volume, | |
| "Mortar Volume (cft)": mortar_volume, | |
| "Cement Volume (cft)": cement_volume, | |
| "Sand Volume (cft)": sand_volume, | |
| "Cement Bag Volume (cft)": cement_bag_volume_cft, | |
| "Brick Volume (cft)": BRICK_VOLUME_CFT | |
| } | |
| def draw_plan_image(room_shapes): | |
| fig, ax = plt.subplots() | |
| for x0, y0, x1, y1 in room_shapes: | |
| width = x1 - x0 | |
| height = y1 - y0 | |
| rect = patches.Rectangle((x0, y0), width, height, linewidth=1, edgecolor='black', facecolor='none') | |
| ax.add_patch(rect) | |
| ax.text(x0 + width / 2, y0 - 2, f"{round(width / 12, 1)} ft", ha='center', fontsize=8) | |
| ax.text(x1 + 2, y0 + height / 2, f"{round(height / 12, 1)} ft", va='center', fontsize=8, rotation=90) | |
| ax.set_aspect('equal') | |
| ax.axis('off') | |
| temp_file = tempfile.NamedTemporaryFile(delete=False, suffix=".png") | |
| fig.savefig(temp_file.name, bbox_inches='tight') | |
| plt.close(fig) | |
| return temp_file.name | |
| def generate_pdf(data_dict, calc_details, room_shapes): | |
| image_path = draw_plan_image(room_shapes) | |
| pdf = FPDF() | |
| pdf.add_page() | |
| pdf.set_font("Arial", 'B', size=14) | |
| pdf.cell(200, 10, "Estimation Report", ln=True, align='C') | |
| pdf.ln(5) | |
| pdf.set_font("Arial", 'B', size=12) | |
| pdf.cell(200, 10, "Summary of Quantities", ln=True) | |
| pdf.set_font("Arial", '', size=11) | |
| for key, value in data_dict.items(): | |
| pdf.cell(200, 10, f"{key}: {value}", ln=True) | |
| pdf.ln(8) | |
| pdf.set_font("Arial", 'B', size=12) | |
| pdf.cell(200, 10, "Step-by-Step Calculations with Formulas", ln=True) | |
| pdf.set_font("Arial", '', size=10) | |
| pdf.multi_cell(0, 8, f""" | |
| 1. Wall Volume = 2 × (L + W) × H × t = {round(calc_details['Wall Volume (cft)'], 2)} cft | |
| 2. Opening Volume = L × H × t = {round(calc_details['Opening Volume (cft)'], 2)} cft | |
| 3. Beam Volume = (L+1) × 0.75 × 0.75 = {round(calc_details['Beam Volume (cft)'], 2)} cft | |
| 4. Net Volume = (Wall - Opening - Beam) × Floors = {round(calc_details['Net Volume (cft)'], 2)} cft | |
| 5. Brick Volume = 9" × 4.5" × 3" = {round(calc_details['Brick Volume (cft)'], 4)} cft | |
| 6. Bricks = Net Volume / Brick Vol × 1.05 = {data_dict['Bricks Required']} | |
| 7. Mortar = Net Volume × 0.25 = {round(calc_details['Mortar Volume (cft)'], 2)} cft | |
| 8. Cement = Mortar × 1/6 = {round(calc_details['Cement Volume (cft)'], 2)} cft | |
| 9. Sand = Mortar × 5/6 = {round(calc_details['Sand Volume (cft)'], 2)} cft | |
| 10. Cement Bags = Cement / Bag Volume = {data_dict['Cement Bags']} | |
| """) | |
| pdf.ln(5) | |
| pdf.set_font("Arial", 'B', size=12) | |
| pdf.cell(200, 10, "2D Plan with Dimensions (in ft)", ln=True) | |
| pdf.image(image_path, x=10, y=None, w=180) | |
| tmp = NamedTemporaryFile(delete=False, suffix=".pdf") | |
| pdf.output(tmp.name) | |
| return tmp.name | |
| def plot_rooms(shapes): | |
| fig, ax = plt.subplots() | |
| for x0, y0, x1, y1 in shapes: | |
| width = (x1 - x0) | |
| height = (y1 - y0) | |
| rect = plt.Rectangle((x0, y0), width, height, fill=False, edgecolor='blue', linewidth=2) | |
| ax.add_patch(rect) | |
| ax.set_title("🗏️ 2D Floor Plan") | |
| ax.set_aspect("equal") | |
| ax.axis("off") | |
| st.pyplot(fig) | |
| if uploaded_file: | |
| file_bytes = uploaded_file.read() | |
| rooms, wall_thickness, doors, windows, room_shapes, wall_pairs = extract_geometry(file_bytes) | |
| if rooms: | |
| st.subheader("🧱 Estimation Result") | |
| number_of_bricks, sand_volume, cement_bags, calc_details = estimate(rooms, wall_thickness, doors, 1) | |
| st.write(f"Number of Bricks: {number_of_bricks}") | |
| st.write(f"Sand Volume (cft): {round(sand_volume, 2)}") | |
| st.write(f"Cement Bags Required: {cement_bags}") | |
| plot_rooms(room_shapes) | |
| st.subheader("📄 Export") | |
| pdf_file = generate_pdf({"Bricks Required": number_of_bricks, "Cement Bags": cement_bags}, calc_details, room_shapes) | |
| st.download_button("Download PDF Report", pdf_file, file_name="estimation_report.pdf") | |