UsmanGoraya commited on
Commit
303e2bb
·
verified ·
1 Parent(s): 41136ca

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +86 -121
app.py CHANGED
@@ -1,62 +1,46 @@
 
1
  import streamlit as st
2
  import ezdxf
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
- import math
10
- from io import BytesIO
11
 
12
- # Constants for estimation
13
- BRICK_VOLUME_CFT = (9 / 12) * (4.5 / 12) * (3 / 12) # Brick Volume in Cubic Feet
14
  CEMENT_SAND_RATIO = 1 / 6
15
  SAND_RATIO = 5 / 6
16
- CEMENT_DENSITY_KG_PER_CFT = 1440 / 35.3147 # Cement Density in KG per Cubic Foot
17
- CEMENT_BAG_WEIGHT_KG = 50 # Weight of Cement Bag in KG
18
- DEFAULT_WALL_THICKNESS = 0.75 # Default wall thickness in feet
19
 
20
- # Streamlit Setup
21
  st.set_page_config(page_title="Building Estimator from CAD", layout="wide")
22
  st.title("🏗️ Auto Estimation from AutoCAD (.dxf) Drawing")
23
 
24
  uploaded_file = st.file_uploader("Upload your DXF file", type=["dxf"])
25
 
26
- # Function to Extract Geometry from DXF
27
  @st.cache_data
28
  def extract_geometry(file_bytes):
29
  try:
30
- # Use BytesIO to read the uploaded file bytes properly
31
- file_stream = BytesIO(file_bytes) # Convert bytes to a file-like object
32
- doc = ezdxf.read(file_stream)
33
  except Exception as e:
34
  st.error(f"Error reading DXF file: {e}")
35
- return [], 0.75, [], [], [], []
36
 
37
  msp = doc.modelspace()
38
  rooms = []
39
  room_shapes = []
40
- doors = []
41
- windows = []
42
  wall_thickness = DEFAULT_WALL_THICKNESS
43
  wall_pairs = []
44
 
45
- # Detecting walls (parallel lines that represent wall edges)
46
- wall_lines = []
47
- for entity in msp:
48
- if entity.dxftype() == "LINE":
49
- wall_lines.append(entity)
50
-
51
- # Check for parallel lines representing a wall (detect double lines)
52
- for i, line1 in enumerate(wall_lines):
53
- for line2 in wall_lines[i + 1:]:
54
- if are_lines_parallel(line1, line2):
55
- # Calculate the distance between the two parallel lines
56
- wall_thickness = calculate_wall_thickness(line1, line2)
57
- wall_pairs.append((line1, line2, wall_thickness))
58
-
59
- # Process rooms, doors, and windows from entities
60
  for entity in msp:
61
  if entity.dxftype() == "TEXT":
62
  content = entity.dxf.text.lower()
@@ -65,54 +49,42 @@ def extract_geometry(file_bytes):
65
  wall_thickness = float(content.split(":")[1].strip())
66
  except:
67
  continue
 
 
 
 
 
68
 
69
  elif entity.dxftype() == "LWPOLYLINE":
70
- # Check if it's a closed polyline representing a room
71
- if entity.closed:
72
  points = entity.get_points()
73
  x_vals = [p[0] for p in points]
74
  y_vals = [p[1] for p in points]
75
- width = abs(max(x_vals) - min(x_vals)) / 12 # Convert to feet
76
- height = abs(max(y_vals) - min(y_vals)) / 12 # Convert to feet
77
- if width > 2 and height > 2: # Make sure it's a room-like area
78
- rooms.append((width, height, 10)) # Add room with length, width, and height (default 10 ft)
 
79
  room_shapes.append((min(x_vals), min(y_vals), max(x_vals), max(y_vals)))
 
 
80
 
81
  elif entity.dxftype() == "LINE":
82
- # Check for doors or windows (typically represented as lines in DXF)
83
- if entity.dxf.layer.lower() == "doors":
84
- doors.append(("door", entity.dxf.start.x, entity.dxf.start.y))
85
- elif entity.dxf.layer.lower() == "windows":
86
- windows.append(("window", entity.dxf.start.x, entity.dxf.start.y))
87
-
88
- return rooms, wall_thickness, doors, windows, room_shapes, wall_pairs
89
-
90
-
91
- # Function to Check if Lines are Parallel
92
- def are_lines_parallel(line1, line2):
93
- dx1, dy1 = line1.dxf.end.x - line1.dxf.start.x, line1.dxf.end.y - line1.dxf.start.y
94
- dx2, dy2 = line2.dxf.end.x - line2.dxf.start.x, line2.dxf.end.y - line2.dxf.start.y
95
- # Check if the direction vectors are nearly parallel (with some tolerance)
96
- tolerance = 0.1
97
- return abs(dx1 * dy2 - dy1 * dx2) < tolerance
98
 
 
99
 
100
- # Function to Calculate the Thickness Between Two Parallel Lines
101
- def calculate_wall_thickness(line1, line2):
102
- x1, y1 = line1.dxf.start.x, line1.dxf.start.y
103
- x2, y2 = line2.dxf.start.x, line2.dxf.start.y
104
- # Calculate distance between the two lines using the distance formula
105
- distance = math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2)
106
- return distance / 12 # Convert to feet
107
-
108
-
109
- # Estimation Function
110
- def estimate(rooms, wall_thickness, doors, windows):
111
  wall_volume = sum(2 * (l + w) * h * wall_thickness for l, w, h in rooms)
112
- opening_volume = sum(l * h * wall_thickness for _, l, h in doors + windows)
113
- net_volume = wall_volume - opening_volume
 
114
 
115
- # Estimating the number of bricks
116
  number_of_bricks = round((net_volume / BRICK_VOLUME_CFT) * 1.05)
117
  mortar_volume = net_volume * 0.25
118
  cement_volume = mortar_volume * CEMENT_SAND_RATIO
@@ -123,6 +95,7 @@ def estimate(rooms, wall_thickness, doors, windows):
123
  return number_of_bricks, sand_volume, cement_bags, {
124
  "Wall Volume (cft)": wall_volume,
125
  "Opening Volume (cft)": opening_volume,
 
126
  "Net Volume (cft)": net_volume,
127
  "Mortar Volume (cft)": mortar_volume,
128
  "Cement Volume (cft)": cement_volume,
@@ -131,97 +104,89 @@ def estimate(rooms, wall_thickness, doors, windows):
131
  "Brick Volume (cft)": BRICK_VOLUME_CFT
132
  }
133
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
134
 
135
- # Function to Generate PDF with Estimation
136
  def generate_pdf(data_dict, calc_details, room_shapes):
137
  image_path = draw_plan_image(room_shapes)
 
138
  pdf = FPDF()
139
  pdf.add_page()
140
  pdf.set_font("Arial", 'B', size=14)
141
  pdf.cell(200, 10, "Estimation Report", ln=True, align='C')
142
  pdf.ln(5)
143
 
144
- # Add Summary of Quantities
145
  pdf.set_font("Arial", 'B', size=12)
146
  pdf.cell(200, 10, "Summary of Quantities", ln=True)
147
  pdf.set_font("Arial", '', size=11)
148
  for key, value in data_dict.items():
149
  pdf.cell(200, 10, f"{key}: {value}", ln=True)
150
 
151
- # Add Calculations with Formulas
152
  pdf.ln(8)
153
  pdf.set_font("Arial", 'B', size=12)
154
  pdf.cell(200, 10, "Step-by-Step Calculations with Formulas", ln=True)
 
155
  pdf.set_font("Arial", '', size=10)
156
  pdf.multi_cell(0, 8, f"""
157
  1. Wall Volume = 2 × (L + W) × H × t = {round(calc_details['Wall Volume (cft)'], 2)} cft
158
  2. Opening Volume = L × H × t = {round(calc_details['Opening Volume (cft)'], 2)} cft
159
- 3. Net Volume = Wall - Opening = {round(calc_details['Net Volume (cft)'], 2)} cft
160
- 4. Bricks = Net Volume / Brick Volume × 1.05 = {data_dict['Bricks Required']}
161
- 5. Mortar = Net Volume × 0.25 = {round(calc_details['Mortar Volume (cft)'], 2)} cft
162
- 6. Cement = Mortar × 1/6 = {round(calc_details['Cement Volume (cft)'], 2)} cft
163
- 7. Sand = Mortar × 5/6 = {round(calc_details['Sand Volume (cft)'], 2)} cft
164
- 8. Cement Bags = Cement / Bag Volume = {data_dict['Cement Bags']}
 
 
165
  """)
166
 
167
- # Add 2D Plan with Dimensions
168
  pdf.ln(5)
169
  pdf.set_font("Arial", 'B', size=12)
170
  pdf.cell(200, 10, "2D Plan with Dimensions (in ft)", ln=True)
171
  pdf.image(image_path, x=10, y=None, w=180)
172
 
173
- # Export to PDF
174
  tmp = NamedTemporaryFile(delete=False, suffix=".pdf")
175
  pdf.output(tmp.name)
176
  return tmp.name
177
 
178
-
179
- # Function to Draw Plan Image
180
- def draw_plan_image(room_shapes):
181
  fig, ax = plt.subplots()
182
- for x0, y0, x1, y1 in room_shapes:
183
- width = x1 - x0
184
- height = y1 - y0
185
- rect = patches.Rectangle((x0, y0), width, height, linewidth=1, edgecolor='black', facecolor='none')
186
  ax.add_patch(rect)
187
- ax.text(x0 + width / 2, y0 - 2, f"{round(width / 12, 1)} ft", ha='center', fontsize=8)
188
- ax.text(x1 + 2, y0 + height / 2, f"{round(height / 12, 1)} ft", va='center', fontsize=8)
189
-
190
- ax.set_aspect('equal', 'box')
191
- ax.set_title("2D Room Layout")
192
- ax.set_xlabel("X (ft)")
193
- ax.set_ylabel("Y (ft)")
194
- plt.axis('off')
195
-
196
- img_path = "/tmp/room_layout.png"
197
- plt.savefig(img_path)
198
- plt.close()
199
- return img_path
200
-
201
-
202
- # Main function to handle Streamlit interface
203
- if uploaded_file is not None:
204
- file_bytes = uploaded_file.read()
205
 
206
- try:
207
- rooms, wall_thickness, doors, windows, room_shapes, wall_pairs = extract_geometry(file_bytes)
208
- st.write(f"Found {len(rooms)} rooms, {len(doors)} doors, {len(windows)} windows.")
209
-
210
- if rooms:
211
- number_of_bricks, sand_volume, cement_bags, calc_details = estimate(rooms, wall_thickness, doors, windows)
212
-
213
- st.write(f"Estimated Bricks Required: {number_of_bricks}")
214
- st.write(f"Estimated Cement Bags: {cement_bags}")
215
- st.write(f"Estimated Sand Volume (cft): {sand_volume}")
216
 
217
- pdf_file = generate_pdf({
218
- "Bricks Required": number_of_bricks,
219
- "Cement Bags": cement_bags,
220
- "Sand Volume (cft)": sand_volume
221
- }, calc_details, room_shapes)
 
222
 
223
- with open(pdf_file, "rb") as f:
224
- st.download_button("Download Estimation PDF", f, file_name="estimation_report.pdf")
225
 
226
- except Exception as e:
227
- st.error(f"Error: {str(e)}")
 
 
1
+
2
  import streamlit as st
3
  import ezdxf
4
  import io
5
  import pandas as pd
6
  import matplotlib.pyplot as plt
7
  from fpdf import FPDF
8
+ from tempfile import NamedTemporaryFile
9
  import matplotlib.patches as patches
10
  import tempfile
11
+ import io
 
12
 
13
+ # Constants
14
+ BRICK_VOLUME_CFT = (9/12) * (4.5/12) * (3/12)
15
  CEMENT_SAND_RATIO = 1 / 6
16
  SAND_RATIO = 5 / 6
17
+ CEMENT_DENSITY_KG_PER_CFT = 1440 / 35.3147
18
+ CEMENT_BAG_WEIGHT_KG = 50
19
+ DEFAULT_WALL_THICKNESS = 0.75
20
 
 
21
  st.set_page_config(page_title="Building Estimator from CAD", layout="wide")
22
  st.title("🏗️ Auto Estimation from AutoCAD (.dxf) Drawing")
23
 
24
  uploaded_file = st.file_uploader("Upload your DXF file", type=["dxf"])
25
 
 
26
  @st.cache_data
27
  def extract_geometry(file_bytes):
28
  try:
29
+ # Convert file bytes into a file-like object
30
+ file_stream = io.BytesIO(file_bytes)
31
+ doc = ezdxf.read(file_stream) # Use the correct function to read from the byte stream
32
  except Exception as e:
33
  st.error(f"Error reading DXF file: {e}")
34
+ return [], 0.75, [], 1, [], []
35
 
36
  msp = doc.modelspace()
37
  rooms = []
38
  room_shapes = []
39
+ openings = []
40
+ floors = 1
41
  wall_thickness = DEFAULT_WALL_THICKNESS
42
  wall_pairs = []
43
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
44
  for entity in msp:
45
  if entity.dxftype() == "TEXT":
46
  content = entity.dxf.text.lower()
 
49
  wall_thickness = float(content.split(":")[1].strip())
50
  except:
51
  continue
52
+ elif "floor" in content:
53
+ try:
54
+ floors = int(content.split(":")[1].strip())
55
+ except:
56
+ continue
57
 
58
  elif entity.dxftype() == "LWPOLYLINE":
59
+ if entity.closed and len(entity) == 4:
 
60
  points = entity.get_points()
61
  x_vals = [p[0] for p in points]
62
  y_vals = [p[1] for p in points]
63
+ length = abs(max(x_vals) - min(x_vals)) / 12
64
+ width = abs(max(y_vals) - min(y_vals)) / 12
65
+ height = 10
66
+ if length > 2 and width > 2:
67
+ rooms.append((length, width, height))
68
  room_shapes.append((min(x_vals), min(y_vals), max(x_vals), max(y_vals)))
69
+ else:
70
+ openings.append(("opening", length, height))
71
 
72
  elif entity.dxftype() == "LINE":
73
+ # Process lines for walls
74
+ start_point = entity.dxf.start
75
+ end_point = entity.dxf.end
76
+ distance = ((end_point.x - start_point.x) ** 2 + (end_point.y - start_point.y) ** 2) ** 0.5
77
+ if distance >= 9: # If the line represents a wall, assuming threshold distance for walls
78
+ wall_pairs.append((start_point, end_point))
 
 
 
 
 
 
 
 
 
 
79
 
80
+ return rooms, wall_thickness, openings, floors, room_shapes, wall_pairs
81
 
82
+ def estimate(rooms, wall_thickness, openings, floors):
 
 
 
 
 
 
 
 
 
 
83
  wall_volume = sum(2 * (l + w) * h * wall_thickness for l, w, h in rooms)
84
+ opening_volume = sum(l * h * wall_thickness for _, l, h in openings)
85
+ beam_volume = sum((l + 1) * 0.75 * 0.75 for _, l, _ in openings)
86
+ net_volume = (wall_volume - opening_volume - beam_volume) * floors
87
 
 
88
  number_of_bricks = round((net_volume / BRICK_VOLUME_CFT) * 1.05)
89
  mortar_volume = net_volume * 0.25
90
  cement_volume = mortar_volume * CEMENT_SAND_RATIO
 
95
  return number_of_bricks, sand_volume, cement_bags, {
96
  "Wall Volume (cft)": wall_volume,
97
  "Opening Volume (cft)": opening_volume,
98
+ "Beam Volume (cft)": beam_volume,
99
  "Net Volume (cft)": net_volume,
100
  "Mortar Volume (cft)": mortar_volume,
101
  "Cement Volume (cft)": cement_volume,
 
104
  "Brick Volume (cft)": BRICK_VOLUME_CFT
105
  }
106
 
107
+ def draw_plan_image(room_shapes):
108
+ fig, ax = plt.subplots()
109
+ for x0, y0, x1, y1 in room_shapes:
110
+ width = x1 - x0
111
+ height = y1 - y0
112
+ rect = patches.Rectangle((x0, y0), width, height, linewidth=1, edgecolor='black', facecolor='none')
113
+ ax.add_patch(rect)
114
+ ax.text(x0 + width / 2, y0 - 2, f"{round(width / 12, 1)} ft", ha='center', fontsize=8)
115
+ ax.text(x1 + 2, y0 + height / 2, f"{round(height / 12, 1)} ft", va='center', fontsize=8, rotation=90)
116
+ ax.set_aspect('equal')
117
+ ax.axis('off')
118
+ temp_file = tempfile.NamedTemporaryFile(delete=False, suffix=".png")
119
+ fig.savefig(temp_file.name, bbox_inches='tight')
120
+ plt.close(fig)
121
+ return temp_file.name
122
 
 
123
  def generate_pdf(data_dict, calc_details, room_shapes):
124
  image_path = draw_plan_image(room_shapes)
125
+
126
  pdf = FPDF()
127
  pdf.add_page()
128
  pdf.set_font("Arial", 'B', size=14)
129
  pdf.cell(200, 10, "Estimation Report", ln=True, align='C')
130
  pdf.ln(5)
131
 
 
132
  pdf.set_font("Arial", 'B', size=12)
133
  pdf.cell(200, 10, "Summary of Quantities", ln=True)
134
  pdf.set_font("Arial", '', size=11)
135
  for key, value in data_dict.items():
136
  pdf.cell(200, 10, f"{key}: {value}", ln=True)
137
 
 
138
  pdf.ln(8)
139
  pdf.set_font("Arial", 'B', size=12)
140
  pdf.cell(200, 10, "Step-by-Step Calculations with Formulas", ln=True)
141
+
142
  pdf.set_font("Arial", '', size=10)
143
  pdf.multi_cell(0, 8, f"""
144
  1. Wall Volume = 2 × (L + W) × H × t = {round(calc_details['Wall Volume (cft)'], 2)} cft
145
  2. Opening Volume = L × H × t = {round(calc_details['Opening Volume (cft)'], 2)} cft
146
+ 3. Beam Volume = (L+1) × 0.75 × 0.75 = {round(calc_details['Beam Volume (cft)'], 2)} cft
147
+ 4. Net Volume = (Wall - Opening - Beam) × Floors = {round(calc_details['Net Volume (cft)'], 2)} cft
148
+ 5. Brick Volume = 9" × 4.5" × 3" = {round(calc_details['Brick Volume (cft)'], 4)} cft
149
+ 6. Bricks = Net Volume / Brick Vol × 1.05 = {data_dict['Bricks Required']}
150
+ 7. Mortar = Net Volume × 0.25 = {round(calc_details['Mortar Volume (cft)'], 2)} cft
151
+ 8. Cement = Mortar × 1/6 = {round(calc_details['Cement Volume (cft)'], 2)} cft
152
+ 9. Sand = Mortar × 5/6 = {round(calc_details['Sand Volume (cft)'], 2)} cft
153
+ 10. Cement Bags = Cement / Bag Volume = {data_dict['Cement Bags']}
154
  """)
155
 
 
156
  pdf.ln(5)
157
  pdf.set_font("Arial", 'B', size=12)
158
  pdf.cell(200, 10, "2D Plan with Dimensions (in ft)", ln=True)
159
  pdf.image(image_path, x=10, y=None, w=180)
160
 
 
161
  tmp = NamedTemporaryFile(delete=False, suffix=".pdf")
162
  pdf.output(tmp.name)
163
  return tmp.name
164
 
165
+ def plot_rooms(shapes):
 
 
166
  fig, ax = plt.subplots()
167
+ for x0, y0, x1, y1 in shapes:
168
+ width = (x1 - x0)
169
+ height = (y1 - y0)
170
+ rect = plt.Rectangle((x0, y0), width, height, fill=False, edgecolor='blue', linewidth=2)
171
  ax.add_patch(rect)
172
+ ax.set_title("🗏️ 2D Floor Plan")
173
+ ax.set_aspect("equal")
174
+ ax.axis("off")
175
+ st.pyplot(fig)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
176
 
177
+ if uploaded_file:
178
+ file_bytes = uploaded_file.read()
179
+ rooms, wall_thickness, doors, windows, room_shapes, wall_pairs = extract_geometry(file_bytes)
 
 
 
 
 
 
 
180
 
181
+ if rooms:
182
+ st.subheader("🧱 Estimation Result")
183
+ number_of_bricks, sand_volume, cement_bags, calc_details = estimate(rooms, wall_thickness, doors, 1)
184
+ st.write(f"Number of Bricks: {number_of_bricks}")
185
+ st.write(f"Sand Volume (cft): {round(sand_volume, 2)}")
186
+ st.write(f"Cement Bags Required: {cement_bags}")
187
 
188
+ plot_rooms(room_shapes)
 
189
 
190
+ st.subheader("📄 Export")
191
+ pdf_file = generate_pdf({"Bricks Required": number_of_bricks, "Cement Bags": cement_bags}, calc_details, room_shapes)
192
+ st.download_button("Download PDF Report", pdf_file, file_name="estimation_report.pdf")