UsmanGoraya commited on
Commit
4ee301b
·
verified ·
1 Parent(s): ed62bce

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +82 -45
app.py CHANGED
@@ -1,54 +1,63 @@
1
  import streamlit as st
2
  import ezdxf
3
  import io
 
 
 
4
 
5
  # Constants
6
- BRICK_VOLUME_CFT = (9/12) * (4.5/12) * (3/12) # in cft
7
  CEMENT_SAND_RATIO = 1 / 6
8
  SAND_RATIO = 5 / 6
9
  CEMENT_DENSITY_KG_PER_CFT = 1440 / 35.3147
10
  CEMENT_BAG_WEIGHT_KG = 50
 
11
 
12
- st.set_page_config(page_title="CAD-based Quantity Estimator", page_icon="📐")
13
- st.title("📐 Brick, Cement & Sand Estimation from AutoCAD (.dxf)")
14
 
15
- uploaded_file = st.file_uploader("Upload your .dxf AutoCAD file", type=["dxf"])
 
16
 
17
- def extract_from_dxf(file):
18
- doc = ezdxf.read(file)
 
19
  msp = doc.modelspace()
20
 
21
  rooms = []
22
- wall_thickness = 0.75 # Default
23
  openings = []
 
 
24
 
25
  for entity in msp:
26
  if entity.dxftype() == "TEXT":
27
  content = entity.dxf.text.lower()
28
- if "room" in content and "x" in content:
29
  try:
30
- dims = content.split(":")[1].strip().split("x")
31
- l, w, h = map(float, dims)
32
- rooms.append((l, w, h))
33
  except:
34
  continue
35
- elif "wall thickness" in content:
36
  try:
37
- wt = float(content.split(":")[1].strip())
38
- wall_thickness = wt
39
  except:
40
  continue
41
- elif any(x in content for x in ["door", "window", "opening"]):
42
- try:
43
- dims = content.split(":")[1].strip().split("x")
44
- l, h = map(float, dims)
45
- openings.append(("opening", l, h))
46
- except:
47
- continue
48
-
49
- return rooms, wall_thickness, openings
50
 
51
- def estimate(rooms, wall_thickness, openings):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
52
  wall_volume = 0
53
  for length, width, height in rooms:
54
  wall_area = 2 * (length + width) * height
@@ -56,41 +65,69 @@ def estimate(rooms, wall_thickness, openings):
56
 
57
  opening_volume = sum(l * h * wall_thickness for _, l, h in openings)
58
 
59
- beam_volume = 0
60
- for _, l, _ in openings:
61
- beam_len = l + 1 # add 1ft
62
- beam_volume += beam_len * 0.75 * 0.75
63
 
64
- net_volume = wall_volume - opening_volume - beam_volume
65
 
66
  number_of_bricks = round((net_volume / BRICK_VOLUME_CFT) * 1.05)
67
  mortar_volume = net_volume * 0.25
68
  cement_volume = mortar_volume * CEMENT_SAND_RATIO
69
  sand_volume = mortar_volume * SAND_RATIO
70
-
71
  cement_bag_volume_cft = CEMENT_BAG_WEIGHT_KG / CEMENT_DENSITY_KG_PER_CFT
72
  cement_bags = round(cement_volume / cement_bag_volume_cft)
73
 
74
  return number_of_bricks, sand_volume, cement_bags
75
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
76
  if uploaded_file:
77
- st.success("✅ File uploaded. Parsing...")
78
- rooms, wall_thickness, openings = extract_from_dxf(io.BytesIO(uploaded_file.read()))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
79
 
80
- if rooms:
81
- st.write(f"🧱 Rooms Detected: {len(rooms)}")
82
- for i, r in enumerate(rooms, 1):
83
- st.write(f"Room {i}: {r[0]}x{r[1]}x{r[2]} ft")
84
 
85
- st.write(f"📏 Wall Thickness: {wall_thickness} ft")
86
- st.write(f"🚪 Openings Detected: {len(openings)}")
87
 
88
- bricks, sand, cement = estimate(rooms, wall_thickness, openings)
 
 
 
 
 
 
 
89
 
90
- st.subheader("📊 Estimated Quantities:")
91
- st.write(f"🧱 **Bricks Needed:** {bricks:,} bricks")
92
- st.write(f"🏖️ **Sand Needed:** {sand:.2f} cft")
93
- st.write(f"🪣 **Cement Needed:** {cement} bags (50kg each)")
94
- else:
95
- st.warning("No room data found. Please check the .dxf text format.")
96
 
 
1
  import streamlit as st
2
  import ezdxf
3
  import io
4
+ import pandas as pd
5
+ from fpdf import FPDF
6
+ from tempfile import NamedTemporaryFile
7
 
8
  # Constants
9
+ BRICK_VOLUME_CFT = (9/12) * (4.5/12) * (3/12)
10
  CEMENT_SAND_RATIO = 1 / 6
11
  SAND_RATIO = 5 / 6
12
  CEMENT_DENSITY_KG_PER_CFT = 1440 / 35.3147
13
  CEMENT_BAG_WEIGHT_KG = 50
14
+ DEFAULT_WALL_THICKNESS = 0.75
15
 
16
+ st.set_page_config(page_title="Building Estimator from CAD", layout="wide")
17
+ st.title("🏗️ Auto Estimation from AutoCAD (.dxf) Drawing")
18
 
19
+ # Upload DXF file
20
+ uploaded_file = st.file_uploader("Upload your DXF file", type=["dxf"])
21
 
22
+ @st.cache_data
23
+ def extract_geometry(file_bytes):
24
+ doc = ezdxf.read_binary(file_bytes)
25
  msp = doc.modelspace()
26
 
27
  rooms = []
 
28
  openings = []
29
+ floors = 1
30
+ wall_thickness = DEFAULT_WALL_THICKNESS
31
 
32
  for entity in msp:
33
  if entity.dxftype() == "TEXT":
34
  content = entity.dxf.text.lower()
35
+ if "wall thickness" in content:
36
  try:
37
+ wall_thickness = float(content.split(":")[1].strip())
 
 
38
  except:
39
  continue
40
+ elif "floor" in content:
41
  try:
42
+ floors = int(content.split(":")[1].strip())
 
43
  except:
44
  continue
 
 
 
 
 
 
 
 
 
45
 
46
+ elif entity.dxftype() == "LWPOLYLINE":
47
+ if entity.closed and len(entity) == 4:
48
+ points = entity.get_points()
49
+ x_vals = [p[0] for p in points]
50
+ y_vals = [p[1] for p in points]
51
+ length = abs(max(x_vals) - min(x_vals)) / 12 # inches to ft
52
+ width = abs(max(y_vals) - min(y_vals)) / 12
53
+ height = 10 # default wall height in ft unless labeled
54
+ if length > 2 and width > 2: # Room
55
+ rooms.append((length, width, height))
56
+ else:
57
+ openings.append(("opening", length, height))
58
+ return rooms, wall_thickness, openings, floors
59
+
60
+ def estimate(rooms, wall_thickness, openings, floors):
61
  wall_volume = 0
62
  for length, width, height in rooms:
63
  wall_area = 2 * (length + width) * height
 
65
 
66
  opening_volume = sum(l * h * wall_thickness for _, l, h in openings)
67
 
68
+ beam_volume = sum((l + 1) * 0.75 * 0.75 for _, l, _ in openings)
 
 
 
69
 
70
+ net_volume = (wall_volume - opening_volume - beam_volume) * floors
71
 
72
  number_of_bricks = round((net_volume / BRICK_VOLUME_CFT) * 1.05)
73
  mortar_volume = net_volume * 0.25
74
  cement_volume = mortar_volume * CEMENT_SAND_RATIO
75
  sand_volume = mortar_volume * SAND_RATIO
 
76
  cement_bag_volume_cft = CEMENT_BAG_WEIGHT_KG / CEMENT_DENSITY_KG_PER_CFT
77
  cement_bags = round(cement_volume / cement_bag_volume_cft)
78
 
79
  return number_of_bricks, sand_volume, cement_bags
80
 
81
+ def generate_pdf(data_dict):
82
+ pdf = FPDF()
83
+ pdf.add_page()
84
+ pdf.set_font("Arial", size=12)
85
+ pdf.cell(200, 10, "Estimation Report", ln=True, align='C')
86
+ pdf.ln(10)
87
+
88
+ for key, value in data_dict.items():
89
+ pdf.cell(200, 10, f"{key}: {value}", ln=True)
90
+
91
+ tmp = NamedTemporaryFile(delete=False, suffix=".pdf")
92
+ pdf.output(tmp.name)
93
+ return tmp.name
94
+
95
  if uploaded_file:
96
+ file_bytes = uploaded_file.read()
97
+ rooms, wall_thickness, openings, floors = extract_geometry(io.BytesIO(file_bytes))
98
+
99
+ st.success(f"✔️ Parsed {len(rooms)} rooms and {len(openings)} openings across {floors} floor(s).")
100
+ st.write(f"📏 Wall Thickness: {wall_thickness} ft")
101
+
102
+ bricks, sand, cement = estimate(rooms, wall_thickness, openings, floors)
103
+
104
+ st.subheader("📊 Estimation Result")
105
+ st.write(f"🧱 **Bricks Required:** {bricks:,}")
106
+ st.write(f"🏖️ **Sand Volume:** {sand:.2f} cft")
107
+ st.write(f"🪣 **Cement Bags:** {cement} (50kg each)")
108
+
109
+ # Display table
110
+ df = pd.DataFrame({
111
+ "Item": ["Bricks", "Sand (cft)", "Cement (bags)"],
112
+ "Quantity": [bricks, round(sand, 2), cement]
113
+ })
114
+
115
+ st.dataframe(df)
116
 
117
+ # Export buttons
118
+ st.subheader("📤 Export")
119
+ col1, col2 = st.columns(2)
 
120
 
121
+ with col1:
122
+ st.download_button("📥 Download as Excel", df.to_csv(index=False).encode(), "estimates.csv", "text/csv")
123
 
124
+ with col2:
125
+ pdf_file = generate_pdf({
126
+ "Bricks Required": f"{bricks:,}",
127
+ "Sand Volume": f"{sand:.2f} cft",
128
+ "Cement Bags": f"{cement} bags"
129
+ })
130
+ with open(pdf_file, "rb") as f:
131
+ st.download_button("📄 Download as PDF", f.read(), "estimates.pdf", "application/pdf")
132
 
 
 
 
 
 
 
133