import logging import math import base64 import io import ezdxf import matplotlib matplotlib.use('Agg') import matplotlib.pyplot as plt import matplotlib.patches as patches logger = logging.getLogger("tools") UNIT_PRICES = {"concrete_m3": 1000, "steel_ton": 35000} # ---------- تحديث أسعار المواد من قاعدة البيانات ---------- def update_unit_prices_from_db(project_id): from database import SessionLocal, ProjectSettings db = SessionLocal() try: settings = db.query(ProjectSettings).filter(ProjectSettings.project_id == project_id).first() if settings: global UNIT_PRICES UNIT_PRICES["concrete_m3"] = settings.concrete_price UNIT_PRICES["steel_ton"] = settings.steel_price except: pass finally: db.close() # --- دوال التصميم الإنشائي --- def structural_beam_analysis(length, load): try: L = length w = load * 10 M = (w * L**2) / 8 scenarios = [ _calc_beam(L, M, 250, int((L/12.0)*1000), "💰 Economic"), _calc_beam(L, M, 250, int((L/18.0)*1000), "🪶 Lightweight"), _calc_beam(L, M, 300, int((L/10.0)*1000), "🛡️ Safe") ] return {"success": True, "scenarios": scenarios} except Exception as e: return {"success": False, "error": str(e)} def _calc_beam(L, M, w, d, label): d_eff = d - 40 if d_eff <= 0: d_eff = 100 As_req = (M * 1e6) / (0.87 * 360 * d_eff) bars = math.ceil(As_req / 201) vol = (w/1000) * (d/1000) * L steel_w = (201 * bars * L * 7.85) / 1000000 cost = (vol * UNIT_PRICES['concrete_m3']) + (steel_w * UNIT_PRICES['steel_ton']) return { "label": label, "width_mm": w, "depth_mm": d, "steel_bars": f"{bars} T16", "cost_egp": round(cost), "raw_values": { "type": "beam", "depth_mm": d, "length_m": L, "steel_ratio_percent": round((As_req/(w*d_eff))*100, 2) } } def slab_analysis(length_short, length_long, thickness=0): try: ratio = length_long / length_short type_str = "Two-Way" if ratio < 2 else "One-Way" t = thickness if thickness > 0 else (length_short * 1000 / 30) load = 15.0 M = (load * length_short**2) / 8 As_req = (M * 1e6) / (0.87 * 360 * (t-20)) return { "success": True, "results": { "explanation": f"🔲 **بلاطة {type_str}**: السمك المطلوب {t:.0f} مم، الحديد التقريبي {As_req/201:.2f} قضيب/متر", "raw_values": {"type": "slab", "thickness_mm": t} } } except Exception as e: return {"success": False, "error": str(e)} def column_analysis(axial_load, height): try: Pu = axial_load * 1.5 * 10 Ag = Pu / (0.4 * 30) side = math.sqrt(Ag) * 1000 return { "success": True, "results": { "explanation": f"🏗️ **عمود**: المقاس المقترح {side:.0f}x{side:.0f} مم للحمل {axial_load} ك.ن", "raw_values": {"type": "column"} } } except Exception as e: return {"success": False, "error": str(e)} def foundation_analysis(load, soil_capacity): try: area = (load * 1.5) / (soil_capacity * 10) side = math.sqrt(area) thickness = side * 0.5 return { "success": True, "results": { "explanation": f"🧱 **قاعدة**: الأبعاد {side:.2f}x{side:.2f} متر، سمك {thickness*100:.0f} سم", "raw_values": {"type": "foundation"} } } except Exception as e: return {"success": False, "error": str(e)} def retaining_wall_analysis(height, soil_load): try: base_thickness = 0.1 * height + 0.3 base_width = 0.5 * height steel_main = f"16mm كل 200 مم" return { "success": True, "results": { "explanation": f"🧱 **جدار استنادي**: ارتفاع {height} م، عرض القاعدة {base_width:.2f} م، سمك القاعدة {base_thickness:.2f} م، تسليح رئيسي {steel_main}", "raw_values": {"type": "retaining_wall", "height": height, "base_width": base_width, "base_thickness": base_thickness} } } except Exception as e: return {"success": False, "error": str(e)} def stair_analysis(floor_height, riser=0.17, tread=0.30): try: number_of_risers = floor_height / riser number_of_treads = number_of_risers - 1 flight_length = number_of_treads * tread thickness = flight_length * 0.03 return { "success": True, "results": { "explanation": f"🪜 **سلم**: عدد القوائم {number_of_risers:.0f}، طول الباي {flight_length:.2f} م، السمك التقريبي {thickness*100:.0f} مم", "raw_values": {"type": "stair", "risers": number_of_risers, "flight_length": flight_length, "thickness_mm": thickness*1000} } } except Exception as e: return {"success": False, "error": str(e)} # --- دوال الحصر (BOQ) --- def boq_steel_calculator(d, l, c): wt = (d**2 / 162.2 * l * c) / 1000 cost = wt * UNIT_PRICES['steel_ton'] return {"success": True, "item": {"description": f"Steel {d}mm", "unit": "Ton", "quantity": round(wt,3), "total_price": round(cost)}} def boq_concrete_calculator(v): cost = v * UNIT_PRICES['concrete_m3'] return {"success": True, "item": {"description": "Concrete", "unit": "M3", "quantity": v, "total_price": round(cost)}} # --- الرسم و CAD --- def generate_beam_dxf(w, d, bars): try: doc = ezdxf.new() msp = doc.modelspace() msp.add_lwpolyline([(0,0), (w,0), (w,d), (0,d)], close=True) for i in range(bars): x = 40 + (w - 80) / (bars + 1) * (i + 1) msp.add_circle((x, 50), 8) stream = io.BytesIO() doc.write(stream) stream.seek(0) return base64.b64encode(stream.read()).decode('utf-8') except: return None def draw_beam_section(w, d, bars): try: fig, ax = plt.subplots(1, figsize=(6,4)) ax.add_patch(patches.Rectangle((0,0), w, d, facecolor='#f0f0f0', edgecolor='black')) if bars > 0: sp = (w - 80) / (bars + 1) for i in range(bars): ax.add_patch(patches.Circle((40 + sp*(i+1), 50), 10, color='blue')) ax.set_aspect('equal') ax.set_xlim(0, w) ax.set_ylim(0, d) ax.set_title(f"Beam Section {w}x{d} mm") buf = io.BytesIO() plt.savefig(buf, format='png', dpi=100) buf.seek(0) img = base64.b64encode(buf.read()).decode('utf-8') plt.close() return img except: return None def get_site_checklist(work_type): data = { "نجارة": ["مراجعة أبعاد", "رأسية الأعمدة"], "حدادة": ["أقطار الحديد", "التوشيك"], "صب": ["التربيط", "الدمك"], "عمود": ["الرأسية", "أماكن الوصلات"], "قاعدة": ["النظافة", "الغطاء الخرساني"] } items = data.get(work_type, ["عام"]) text = f"📋 **تشك ليست {work_type}:**\n" for i, item in enumerate(items, 1): text += f"{i}. {item}\n" return {"success": True, "items": items, "text": text}