UsmanGoraya commited on
Commit
b65fd70
·
verified ·
1 Parent(s): ac4e80c

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +114 -146
app.py CHANGED
@@ -3,104 +3,84 @@ import ezdxf
3
  import io
4
  import pandas as pd
5
  import matplotlib.pyplot as plt
6
- import matplotlib.patches as patches
7
  from fpdf import FPDF
8
- from tempfile import NamedTemporaryFile
9
  import tempfile
10
- import numpy as np
11
 
12
- # Constants
13
- BRICK_VOLUME_CFT = (9/12) * (4.5/12) * (3/12)
14
  CEMENT_SAND_RATIO = 1 / 6
15
  SAND_RATIO = 5 / 6
16
- CEMENT_DENSITY_KG_PER_CFT = 1440 / 35.3147
17
- CEMENT_BAG_WEIGHT_KG = 50
18
- DEFAULT_WALL_HEIGHT = 10 # ft
19
 
 
20
  st.set_page_config(page_title="Building Estimator from CAD", layout="wide")
21
  st.title("🏗️ Auto Estimation from AutoCAD (.dxf) Drawing")
22
 
23
  uploaded_file = st.file_uploader("Upload your DXF file", type=["dxf"])
24
 
 
25
  @st.cache_data
26
  def extract_geometry(file_bytes):
27
- doc = ezdxf.read(file_bytes)
28
- msp = doc.modelspace()
 
 
 
 
 
 
 
 
29
 
30
- lines = []
31
- polylines = []
32
- texts = []
33
-
34
- for e in msp:
35
- if e.dxftype() == "LINE":
36
- lines.append(((e.dxf.start.x, e.dxf.start.y), (e.dxf.end.x, e.dxf.end.y)))
37
- elif e.dxftype() == "LWPOLYLINE":
38
- polylines.append(e)
39
- elif e.dxftype() == "TEXT":
40
- texts.append(e.dxf.text.lower())
41
-
42
- wall_segments = []
43
- used = set()
44
- tolerance = 0.1 # ft
45
-
46
- # Group close parallel lines as walls
47
- for i, (start1, end1) in enumerate(lines):
48
- for j, (start2, end2) in enumerate(lines):
49
- if i >= j:
50
- continue
51
- if np.allclose([start1[0], end1[0]], [start2[0], end2[0]], atol=tolerance) or \
52
- np.allclose([start1[1], end1[1]], [start2[1], end2[1]], atol=tolerance):
53
- dist = np.linalg.norm(np.array(start1) - np.array(start2))
54
- if 0.6 < dist < 1: # Between 7in to 12in (i.e. wall thickness)
55
- wall_segments.append((start1, end1, start2, end2))
56
- used.add(i)
57
- used.add(j)
58
-
59
- wall_thicknesses = []
60
  rooms = []
61
  room_shapes = []
62
- for p in polylines:
63
- if p.closed:
64
- pts = p.get_points()
65
- xs = [pt[0] for pt in pts]
66
- ys = [pt[1] for pt in pts]
67
- min_x, max_x = min(xs), max(xs)
68
- min_y, max_y = min(ys), max(ys)
69
- l = (max_x - min_x) / 12
70
- w = (max_y - min_y) / 12
71
- if l > 1 and w > 1:
72
- rooms.append((round(l, 2), round(w, 2), DEFAULT_WALL_HEIGHT))
73
- room_shapes.append((min_x, min_y, max_x, max_y))
74
-
75
  doors = []
76
  windows = []
77
- for p in polylines:
78
- if p.closed:
79
- pts = p.get_points()
80
- xs = [pt[0] for pt in pts]
81
- ys = [pt[1] for pt in pts]
82
- min_x, max_x = min(xs), max(xs)
83
- min_y, max_y = min(ys), max(ys)
84
- l = (max_x - min_x) / 12
85
- h = (max_y - min_y) / 12
86
- if 2 <= l <= 5 and 6 <= h <= 8:
87
- doors.append((l, h))
88
- elif 2 <= l <= 6 and 2 <= h <= 5:
89
- windows.append((l, h))
90
-
91
- wall_thickness = 0.75 # default
92
- if wall_segments:
93
- pt1, _, pt2, _ = wall_segments[0]
94
- wall_thickness = round(np.linalg.norm(np.array(pt1) - np.array(pt2)) / 12, 2)
 
 
 
 
 
 
 
 
 
 
 
 
95
 
96
  return rooms, wall_thickness, doors, windows, room_shapes
97
 
 
 
98
  def estimate(rooms, wall_thickness, doors, windows):
99
  wall_volume = sum(2 * (l + w) * h * wall_thickness for l, w, h in rooms)
100
- opening_volume = sum(l * h * wall_thickness for l, h in doors + windows)
101
- beam_volume = sum((l + 1) * 0.75 * 0.75 for l, h in doors + windows)
102
- net_volume = wall_volume - opening_volume - beam_volume
103
 
 
104
  number_of_bricks = round((net_volume / BRICK_VOLUME_CFT) * 1.05)
105
  mortar_volume = net_volume * 0.25
106
  cement_volume = mortar_volume * CEMENT_SAND_RATIO
@@ -111,7 +91,6 @@ def estimate(rooms, wall_thickness, doors, windows):
111
  return number_of_bricks, sand_volume, cement_bags, {
112
  "Wall Volume (cft)": wall_volume,
113
  "Opening Volume (cft)": opening_volume,
114
- "Beam Volume (cft)": beam_volume,
115
  "Net Volume (cft)": net_volume,
116
  "Mortar Volume (cft)": mortar_volume,
117
  "Cement Volume (cft)": cement_volume,
@@ -120,107 +99,96 @@ def estimate(rooms, wall_thickness, doors, windows):
120
  "Brick Volume (cft)": BRICK_VOLUME_CFT
121
  }
122
 
123
- def draw_plan_image(room_shapes):
124
- fig, ax = plt.subplots()
125
- for x0, y0, x1, y1 in room_shapes:
126
- width = x1 - x0
127
- height = y1 - y0
128
- rect = patches.Rectangle((x0, y0), width, height, linewidth=1, edgecolor='black', facecolor='none')
129
- ax.add_patch(rect)
130
- ax.text(x0 + width / 2, y0 - 2, f"{round(width / 12, 1)} ft", ha='center', fontsize=8)
131
- ax.text(x1 + 2, y0 + height / 2, f"{round(height / 12, 1)} ft", va='center', fontsize=8, rotation=90)
132
- ax.set_aspect('equal')
133
- ax.axis('off')
134
- temp_file = tempfile.NamedTemporaryFile(delete=False, suffix=".png")
135
- fig.savefig(temp_file.name, bbox_inches='tight')
136
- plt.close(fig)
137
- return temp_file.name
138
-
139
- def generate_pdf(data_dict, calc_details, room_shapes, doors, windows):
140
  image_path = draw_plan_image(room_shapes)
141
  pdf = FPDF()
142
  pdf.add_page()
143
- pdf.set_font("Arial", 'B', 14)
144
  pdf.cell(200, 10, "Estimation Report", ln=True, align='C')
145
  pdf.ln(5)
146
 
147
- pdf.set_font("Arial", 'B', 12)
 
148
  pdf.cell(200, 10, "Summary of Quantities", ln=True)
149
- pdf.set_font("Arial", '', 11)
150
  for key, value in data_dict.items():
151
  pdf.cell(200, 10, f"{key}: {value}", ln=True)
152
 
153
- pdf.set_font("Arial", 'B', 12)
154
- pdf.cell(200, 10, "Doors", ln=True)
155
- pdf.set_font("Arial", '', 11)
156
- for i, (l, h) in enumerate(doors, 1):
157
- pdf.cell(200, 10, f"Door {i}: {l} ft x {h} ft", ln=True)
158
-
159
- pdf.set_font("Arial", 'B', 12)
160
- pdf.cell(200, 10, "Windows", ln=True)
161
- pdf.set_font("Arial", '', 11)
162
- for i, (l, h) in enumerate(windows, 1):
163
- pdf.cell(200, 10, f"Window {i}: {l} ft x {h} ft", ln=True)
164
-
165
- pdf.ln(6)
166
- pdf.set_font("Arial", 'B', 12)
167
  pdf.cell(200, 10, "Step-by-Step Calculations with Formulas", ln=True)
168
- pdf.set_font("Arial", '', 10)
169
  pdf.multi_cell(0, 8, f"""
170
  1. Wall Volume = 2 × (L + W) × H × t = {round(calc_details['Wall Volume (cft)'], 2)} cft
171
  2. Opening Volume = L × H × t = {round(calc_details['Opening Volume (cft)'], 2)} cft
172
- 3. Beam Volume = (L+1) × 0.75 × 0.75 = {round(calc_details['Beam Volume (cft)'], 2)} cft
173
- 4. Net Volume = Wall - Opening - Beam = {round(calc_details['Net Volume (cft)'], 2)} cft
174
- 5. Brick Volume = 9in × 4.5in × 3in = {round(calc_details['Brick Volume (cft)'], 4)} cft
175
- 6. Bricks = Net Volume / Brick Vol × 1.05 = {data_dict['Bricks Required']}
176
- 7. Mortar = Net Volume × 0.25 = {round(calc_details['Mortar Volume (cft)'], 2)} cft
177
- 8. Cement = Mortar × 1/6 = {round(calc_details['Cement Volume (cft)'], 2)} cft
178
- 9. Sand = Mortar × 5/6 = {round(calc_details['Sand Volume (cft)'], 2)} cft
179
- 10. Cement Bags = Cement / Bag Volume = {data_dict['Cement Bags']}
180
  """)
 
 
181
  pdf.ln(5)
 
182
  pdf.cell(200, 10, "2D Plan with Dimensions (in ft)", ln=True)
183
  pdf.image(image_path, x=10, y=None, w=180)
184
 
 
185
  tmp = NamedTemporaryFile(delete=False, suffix=".pdf")
186
  pdf.output(tmp.name)
187
  return tmp.name
188
 
189
- # --- App Main Logic ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
190
  if uploaded_file:
191
  file_bytes = io.BytesIO(uploaded_file.read())
192
  rooms, wall_thickness, doors, windows, room_shapes = extract_geometry(file_bytes)
193
 
194
- st.success(f"✔️ Found {len(rooms)} room(s), {len(doors)} door(s), {len(windows)} window(s)")
195
- st.write(f"📏 Wall Thickness (from drawing): {wall_thickness:.2f} ft")
196
 
 
197
  bricks, sand, cement, calc_details = estimate(rooms, wall_thickness, doors, windows)
198
 
199
- st.subheader("📐 Room Dimensions")
200
- st.dataframe(pd.DataFrame(rooms, columns=["Length (ft)", "Width (ft)", "Height (ft)"]))
201
-
202
- st.subheader("🚪 Doors")
203
- st.dataframe(pd.DataFrame(doors, columns=["Width (ft)", "Height (ft)"]))
204
 
205
- st.subheader("🪟 Windows")
206
- st.dataframe(pd.DataFrame(windows, columns=["Width (ft)", "Height (ft)"]))
207
-
208
- st.subheader("📊 Estimation Results")
209
- df_result = pd.DataFrame({
210
- "Item": ["Bricks", "Sand (cft)", "Cement (bags)"],
211
- "Quantity": [bricks, round(sand, 2), cement]
212
- })
213
- st.dataframe(df_result)
214
 
 
215
  st.subheader("📄 Export")
216
- col1, col2 = st.columns(2)
217
- with col1:
218
- st.download_button("⬇️ Download Excel", df_result.to_csv(index=False).encode(), "estimates.csv", "text/csv")
219
- with col2:
220
- pdf_path = generate_pdf({
221
- "Bricks Required": f"{bricks:,}",
222
- "Sand Volume": f"{sand:.2f} cft",
223
- "Cement Bags": f"{cement} bags"
224
- }, calc_details, room_shapes, doors, windows)
225
- with open(pdf_path, "rb") as f:
226
- st.download_button("⬇️ Download PDF", f.read(), "estimates.pdf", "application/pdf")
 
3
  import io
4
  import pandas as pd
5
  import matplotlib.pyplot as plt
 
6
  from fpdf import FPDF
7
+ import matplotlib.patches as patches
8
  import tempfile
 
9
 
10
+ # Constants for estimation
11
+ BRICK_VOLUME_CFT = (9 / 12) * (4.5 / 12) * (3 / 12) # Brick Volume in Cubic Feet
12
  CEMENT_SAND_RATIO = 1 / 6
13
  SAND_RATIO = 5 / 6
14
+ CEMENT_DENSITY_KG_PER_CFT = 1440 / 35.3147 # Cement Density in KG per Cubic Foot
15
+ CEMENT_BAG_WEIGHT_KG = 50 # Weight of Cement Bag in KG
16
+ DEFAULT_WALL_THICKNESS = 0.75 # Default wall thickness in feet
17
 
18
+ # Streamlit Setup
19
  st.set_page_config(page_title="Building Estimator from CAD", layout="wide")
20
  st.title("🏗️ Auto Estimation from AutoCAD (.dxf) Drawing")
21
 
22
  uploaded_file = st.file_uploader("Upload your DXF file", type=["dxf"])
23
 
24
+ # Function to Extract Geometry from DXF
25
  @st.cache_data
26
  def extract_geometry(file_bytes):
27
+ try:
28
+ # Try to open the file as ASCII first
29
+ doc = ezdxf.read(io.TextIOWrapper(file_bytes, encoding='utf-8', errors='ignore'))
30
+ except Exception as e:
31
+ try:
32
+ # If ASCII fails, attempt to read as binary DXF
33
+ doc = ezdxf.read(file_bytes)
34
+ except Exception as ex:
35
+ st.error(f"Error reading DXF file: {ex}")
36
+ return [], 0.75, [], [], []
37
 
38
+ msp = doc.modelspace()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
39
  rooms = []
40
  room_shapes = []
 
 
 
 
 
 
 
 
 
 
 
 
 
41
  doors = []
42
  windows = []
43
+ wall_thickness = DEFAULT_WALL_THICKNESS
44
+
45
+ # Loop through entities in the model space
46
+ for entity in msp:
47
+ if entity.dxftype() == "TEXT":
48
+ content = entity.dxf.text.lower()
49
+ if "wall thickness" in content:
50
+ try:
51
+ wall_thickness = float(content.split(":")[1].strip())
52
+ except:
53
+ continue
54
+
55
+ elif entity.dxftype() == "LWPOLYLINE":
56
+ # Check if it's a closed polyline representing a room
57
+ if entity.closed and len(entity) == 4:
58
+ points = entity.get_points()
59
+ x_vals = [p[0] for p in points]
60
+ y_vals = [p[1] for p in points]
61
+ length = abs(max(x_vals) - min(x_vals)) / 12 # Convert to feet
62
+ width = abs(max(y_vals) - min(y_vals)) / 12 # Convert to feet
63
+ if length > 2 and width > 2:
64
+ rooms.append((length, width, 10)) # Add room with length, width, and height (default 10 ft)
65
+ room_shapes.append((min(x_vals), min(y_vals), max(x_vals), max(y_vals)))
66
+
67
+ elif entity.dxftype() == "LINE":
68
+ # Check for doors or windows (typically represented as lines in DXF)
69
+ if entity.dxf.layer.lower() == "doors":
70
+ doors.append(("door", entity.dxf.start.x, entity.dxf.start.y))
71
+ elif entity.dxf.layer.lower() == "windows":
72
+ windows.append(("window", entity.dxf.start.x, entity.dxf.start.y))
73
 
74
  return rooms, wall_thickness, doors, windows, room_shapes
75
 
76
+
77
+ # Estimation Function
78
  def estimate(rooms, wall_thickness, doors, windows):
79
  wall_volume = sum(2 * (l + w) * h * wall_thickness for l, w, h in rooms)
80
+ opening_volume = sum(l * h * wall_thickness for _, l, h in doors + windows)
81
+ net_volume = wall_volume - opening_volume
 
82
 
83
+ # Estimating the number of bricks
84
  number_of_bricks = round((net_volume / BRICK_VOLUME_CFT) * 1.05)
85
  mortar_volume = net_volume * 0.25
86
  cement_volume = mortar_volume * CEMENT_SAND_RATIO
 
91
  return number_of_bricks, sand_volume, cement_bags, {
92
  "Wall Volume (cft)": wall_volume,
93
  "Opening Volume (cft)": opening_volume,
 
94
  "Net Volume (cft)": net_volume,
95
  "Mortar Volume (cft)": mortar_volume,
96
  "Cement Volume (cft)": cement_volume,
 
99
  "Brick Volume (cft)": BRICK_VOLUME_CFT
100
  }
101
 
102
+ # Function to Generate PDF with Estimation
103
+ def generate_pdf(data_dict, calc_details, room_shapes):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
104
  image_path = draw_plan_image(room_shapes)
105
  pdf = FPDF()
106
  pdf.add_page()
107
+ pdf.set_font("Arial", 'B', size=14)
108
  pdf.cell(200, 10, "Estimation Report", ln=True, align='C')
109
  pdf.ln(5)
110
 
111
+ # Add Summary of Quantities
112
+ pdf.set_font("Arial", 'B', size=12)
113
  pdf.cell(200, 10, "Summary of Quantities", ln=True)
114
+ pdf.set_font("Arial", '', size=11)
115
  for key, value in data_dict.items():
116
  pdf.cell(200, 10, f"{key}: {value}", ln=True)
117
 
118
+ # Add Calculations with Formulas
119
+ pdf.ln(8)
120
+ pdf.set_font("Arial", 'B', size=12)
 
 
 
 
 
 
 
 
 
 
 
121
  pdf.cell(200, 10, "Step-by-Step Calculations with Formulas", ln=True)
122
+ pdf.set_font("Arial", '', size=10)
123
  pdf.multi_cell(0, 8, f"""
124
  1. Wall Volume = 2 × (L + W) × H × t = {round(calc_details['Wall Volume (cft)'], 2)} cft
125
  2. Opening Volume = L × H × t = {round(calc_details['Opening Volume (cft)'], 2)} cft
126
+ 3. Net Volume = Wall - Opening = {round(calc_details['Net Volume (cft)'], 2)} cft
127
+ 4. Bricks = Net Volume / Brick Volume × 1.05 = {data_dict['Bricks Required']}
128
+ 5. Mortar = Net Volume × 0.25 = {round(calc_details['Mortar Volume (cft)'], 2)} cft
129
+ 6. Cement = Mortar × 1/6 = {round(calc_details['Cement Volume (cft)'], 2)} cft
130
+ 7. Sand = Mortar × 5/6 = {round(calc_details['Sand Volume (cft)'], 2)} cft
131
+ 8. Cement Bags = Cement / Bag Volume = {data_dict['Cement Bags']}
 
 
132
  """)
133
+
134
+ # Add 2D Plan with Dimensions
135
  pdf.ln(5)
136
+ pdf.set_font("Arial", 'B', size=12)
137
  pdf.cell(200, 10, "2D Plan with Dimensions (in ft)", ln=True)
138
  pdf.image(image_path, x=10, y=None, w=180)
139
 
140
+ # Export to PDF
141
  tmp = NamedTemporaryFile(delete=False, suffix=".pdf")
142
  pdf.output(tmp.name)
143
  return tmp.name
144
 
145
+ # Function to Draw Plan Image
146
+ def draw_plan_image(room_shapes):
147
+ fig, ax = plt.subplots()
148
+ for x0, y0, x1, y1 in room_shapes:
149
+ width = x1 - x0
150
+ height = y1 - y0
151
+ rect = patches.Rectangle((x0, y0), width, height, linewidth=1, edgecolor='black', facecolor='none')
152
+ ax.add_patch(rect)
153
+ ax.text(x0 + width / 2, y0 - 2, f"{round(width / 12, 1)} ft", ha='center', fontsize=8)
154
+ ax.text(x1 + 2, y0 + height / 2, f"{round(height / 12, 1)} ft", va='center', fontsize=8, rotation=90)
155
+ ax.set_aspect('equal')
156
+ ax.axis('off')
157
+ temp_file = tempfile.NamedTemporaryFile(delete=False, suffix=".png")
158
+ fig.savefig(temp_file.name, bbox_inches='tight')
159
+ plt.close(fig)
160
+ return temp_file.name
161
+
162
+
163
+ # Streamlit logic to display results
164
  if uploaded_file:
165
  file_bytes = io.BytesIO(uploaded_file.read())
166
  rooms, wall_thickness, doors, windows, room_shapes = extract_geometry(file_bytes)
167
 
168
+ st.success(f"✔️ Parsed {len(rooms)} rooms, {len(doors)} doors, and {len(windows)} windows.")
169
+ st.write(f"📏 Wall Thickness: {wall_thickness} ft")
170
 
171
+ # Estimate the materials
172
  bricks, sand, cement, calc_details = estimate(rooms, wall_thickness, doors, windows)
173
 
174
+ # Display Room Dimensions
175
+ st.subheader("📊 Room Dimensions")
176
+ df_rooms = pd.DataFrame(rooms, columns=["Length (ft)", "Width (ft)", "Height (ft)"])
177
+ st.dataframe(df_rooms)
 
178
 
179
+ # Display Estimation Results
180
+ st.subheader("🧱 Estimation Result")
181
+ st.write(f"Total Bricks Required: {bricks}")
182
+ st.write(f"Total Sand Volume: {sand} cft")
183
+ st.write(f"Total Cement Bags: {cement}")
 
 
 
 
184
 
185
+ # Generate and provide download link for the PDF
186
  st.subheader("📄 Export")
187
+ pdf_file = generate_pdf(
188
+ {"Bricks Required": bricks, "Sand Volume (cft)": sand, "Cement Bags": cement},
189
+ calc_details,
190
+ room_shapes
191
+ )
192
+ with open(pdf_file, "rb") as f:
193
+ st.download_button("Download Estimation Report", f, file_name="Estimation_Report.pdf")
194
+