# برنامج هندسة معمارية متكامل - نسخة تعليمية (لـ Jupyter) # اسم الملف: برنامج_هندسة_معمارية.py # ملاحظة مهمة: هذا مشروع تعليمي متكامل يتضمن ميزات كثيرة (2D/3D, MEP, إنشائي، تصدير، Sun Path، إلخ). # يعتمد على مكتبات خارجية؛ قبل التشغيل تأكد من تثبيت المكتبات التالية في بيئة Jupyter: # pip install numpy matplotlib ipywidgets shapely ezdxf trimesh scipy pyntcloud pyproj # اختياريًا (IFC): pip install ifcopenshell (قد لا يكون متاحًا على بعض البيئات) # ملاحظة: إذا لم تكن مكتبات مثل ifcopenshell متاحة، سيبقى الكود قابلًا للتشغيل مع وظائف بديلة أو إشعارات. # %% """ ميزات هذا الملف (موجز): - إنشاء مخطط شبكي Parameteric - جدران داخلية وخارجية، أبواب ونوافذ بمعلمات - درج (Stair) وسقف - إضافة عناصر إنشائية: أعمدة، كمرات - أدوات MEP (خطوط كهرباء، صرف، مياه) كتراكبات - تعليم الأبعاد (dimensions) تلقائيًا - عرض ثلاثي الأبعاد مبسّط (extrude) باستخدام matplotlib 3D وtrimesh (إن توفر) - تصدير: SVG (2D), DXF (ezdxf)، OBJ (ثلاثي الأبعاد)، IFC (إن توفر) - حساب Sun Path وIllumination تقريبي - مواد/خامات بسيطة (ألوان/شفافية) - واجهة تفاعلية كاملة باستخدام ipywidgets محدودية: هذا ليس بديلًا احترافيًا لـ Revit/AutoCAD؛ لكنه نقطة بداية قوية قابلة للتوسع. """ # Imports import math import numpy as np import matplotlib.pyplot as plt from matplotlib.patches import Rectangle from mpl_toolkits.mplot3d import Axes3D from ipywidgets import interact, IntSlider, FloatSlider, Checkbox, Dropdown, HBox, VBox, Button, Tab # مكتبات اختيارية try: from shapely.geometry import Polygon, LineString, Point from shapely.ops import unary_union SHAPELY_AVAILABLE = True except Exception: SHAPELY_AVAILABLE = False try: import ezdxf EZDXF_AVAILABLE = True except Exception: EZDXF_AVAILABLE = False try: import trimesh TRIMESH_AVAILABLE = True except Exception: TRIMESH_AVAILABLE = False try: import ifcopenshell IFCO_AVAILABLE = True except Exception: IFCO_AVAILABLE = False plt.rcParams['figure.figsize'] = (9,6) # %% # ---------- Core geometry: walls, rooms, doors, windows ---------- class Wall: def __init__(self, x1, y1, x2, y2, thickness=0.2, is_external=False): self.x1, self.y1 = x1, y1 self.x2, self.y2 = x2, y2 self.thickness = thickness self.is_external = is_external def as_line(self): return ((self.x1, self.y1),(self.x2, self.y2)) class Door: def __init__(self, x, y, width=0.9, swing='inward', orientation=0): self.x = x; self.y = y self.width = width self.swing = swing self.orientation = orientation class Window: def __init__(self, x, y, width=1.2, sill=1.0, height=1.2): self.x = x; self.y = y self.width = width self.sill = sill self.height = height class Stair: def __init__(self, x, y, width=1.2, length=3.0, steps=12, direction=0): self.x=x; self.y=y; self.width=width; self.length=length; self.steps=steps; self.direction=direction class StructuralElement: def __init__(self, x, y, type='column', w=0.4, h=0.4): self.x=x; self.y=y; self.type=type; self.w=w; self.h=h class MEPLine: def __init__(self, points, system='electrical'): self.points = points self.system = system # container for the model class BuildingModel: def __init__(self): self.walls = [] self.doors = [] self.windows = [] self.stairs = [] self.structurals = [] self.mep = [] self.rooms = [] self.materials = {} def add_wall(self, x1,y1,x2,y2, thickness=0.2, is_external=False): self.walls.append(Wall(x1,y1,x2,y2, thickness, is_external)) def add_door(self, x,y, width=0.9, swing='inward', orientation=0): self.doors.append(Door(x,y,width,swing,orientation)) def add_window(self, x,y, width=1.2, sill=1.0, height=1.2): self.windows.append(Window(x,y,width,sill,height)) def add_stair(self, x,y,width=1.2,length=3.0,steps=12,direction=0): self.stairs.append(Stair(x,y,width,length,steps,direction)) def add_structural(self,x,y,type='column',w=0.4,h=0.4): self.structurals.append(StructuralElement(x,y,type,w,h)) def add_mep(self, points, system='electrical'): self.mep.append(MEPLine(points, system)) def add_room(self, polygon_points): self.rooms.append(polygon_points) def set_material(self, name, color, texture=None): self.materials[name] = {'color': color, 'texture': texture} model = BuildingModel() # %% # ---------- Generators: grid plan, walls from rooms, doors/windows placement ---------- def generate_grid_rooms(rows, cols, room_w, room_h, wall_thickness=0.2, gap=0.0): """ينشئ غرفًا على شكل شبكة ويعيد إحداثيات مستطيلات كل غرفة""" rooms = [] start_x = 0 start_y = 0 step_x = room_w + wall_thickness + gap step_y = room_h + wall_thickness + gap for r in range(rows): for c in range(cols): x = start_x + c*step_x y = start_y + r*step_y rooms.append((x,y,room_w,room_h)) return rooms def rooms_to_walls(rooms, wall_thickness=0.2, external_wall_thickness=None): """من قائمة غرف (x,y,w,h) يُولد جدران مربعة حولها (بسيط، ويفترض محاذاة محاور) يعيد قائمة حواف (x1,y1,x2,y2) تمثل جدرانًا. """ walls = [] for (x,y,w,h) in rooms: # جدران خارجية المستطيل walls.append((x,y,x+w,y)) walls.append((x+w,y,x+w,y+h)) walls.append((x+w,y+h,x,y+h)) walls.append((x,y+h,x,y)) # دمج الحواف المتطابقة لاحقًا (إن أردت Shapely يساعد هنا) return walls # Helper: add grid rooms to model def add_grid_to_model(rows,cols,room_w,room_h,wall_thickness=0.2,gap=0.0): rooms = generate_grid_rooms(rows,cols,room_w,room_h,wall_thickness,gap) for (x,y,w,h) in rooms: # add room polygon model.add_room([(x,y),(x+w,y),(x+w,y+h),(x,y+h)]) # generate walls simplified walls = rooms_to_walls(rooms,wall_thickness) for (x1,y1,x2,y2) in walls: model.add_wall(x1,y1,x2,y2,thickness=wall_thickness, is_external=False) # %% # ---------- Drawing 2D plan ---------- def draw_plan_2d(model, show_grid=True, show_labels=True, annotate_dims=True): fig, ax = plt.subplots() # draw rooms (fills) for i,room in enumerate(model.rooms): xs = [p[0] for p in room]+[room[0][0]] ys = [p[1] for p in room]+[room[0][1]] ax.fill(xs, ys, alpha=0.12) if show_labels: cx = sum(xs[:-1])/len(xs[:-1]) cy = sum(ys[:-1])/len(ys[:-1]) ax.text(cx, cy, f"Room {i+1}", ha='center', va='center', fontsize=8) # draw walls for w in model.walls: x1,y1 = w.x1, w.y1 x2,y2 = w.x2, w.y2 ax.plot([x1,x2],[y1,y2], linewidth=max(1, w.thickness*6), solid_capstyle='butt') # draw doors for d in model.doors: ax.plot([d.x],[d.y],'s',markersize=6) # draw windows for win in model.windows: ax.plot([win.x],[win.y],'D',markersize=6) # stairs for s in model.stairs: ax.add_patch(Rectangle((s.x, s.y), s.length, s.width, fill=True, alpha=0.2)) # structural for st in model.structurals: ax.add_patch(Rectangle((st.x - st.w/2, st.y - st.h/2), st.w, st.h, fill=True, alpha=0.8)) # MEP lines for m in model.mep: pts = np.array(m.points) ax.plot(pts[:,0], pts[:,1], linestyle='--' if m.system!='plumbing' else '-.') ax.text(pts[0][0], pts[0][1], m.system[0].upper(), fontsize=8) ax.set_aspect('equal') ax.set_xlabel('m') ax.set_ylabel('m') if annotate_dims: annotate_dimensions(ax, model) plt.grid(show_grid) plt.show() # dimensions annotator (very simple) def annotate_dimensions(ax, model): # for simplicity, annotate bounding box xs=[]; ys=[] for room in model.rooms: for p in room: xs.append(p[0]); ys.append(p[1]) if not xs: return minx=min(xs); maxx=max(xs); miny=min(ys); maxy=max(ys) ax.annotate(f'{(maxx-minx):.2f} m', xy=((minx+maxx)/2, miny-0.1), ha='center') ax.annotate(f'{(maxy-miny):.2f} m', xy=(maxx+0.1, (miny+maxy)/2), va='center', rotation=90) # %% # ---------- Simple 3D extrusion & view ---------- def extrude_room_to_mesh(room, height=3.0): # room: list of (x,y) polygon if not TRIMESH_AVAILABLE: # fallback: return vertices/faces simple verts = [] for (x,y) in room: verts.append((x,y,0)) for (x,y) in room: verts.append((x,y,height)) # no faces generated here return {'verts': verts, 'faces': []} poly = trimesh.path.polygons.polygons_to_trimesh([room]) mesh = poly.extrude(height) return mesh def draw_3d_model(model, height=3.0): if TRIMESH_AVAILABLE: scene = trimesh.Scene() for room in model.rooms: mesh = extrude_room_to_mesh(room, height=height) scene.add_geometry(mesh) scene.show() else: # simple matplotlib 3D plot (visual only) fig = plt.figure(); ax = fig.add_subplot(111, projection='3d') for room in model.rooms: xs = [p[0] for p in room]+[room[0][0]] ys = [p[1] for p in room]+[room[0][1]] zs0 = [0]*(len(xs)) zs1 = [height]*(len(xs)) ax.plot(xs, ys, zs0) ax.plot(xs, ys, zs1) for i in range(len(xs)-1): x0,y0 = xs[i], ys[i] x1,y1 = xs[i+1], ys[i+1] ax.plot([x0,x1],[y0,y1],[0,height]) ax.set_xlabel('X (m)'); ax.set_ylabel('Y (m)'); ax.set_zlabel('Z (m)') plt.show() # %% # ---------- Exporters: SVG, DXF, OBJ, IFC (if available) ---------- def export_svg(filename='plan.svg'): # render current 2D plan and save fig, ax = plt.subplots() draw_plan_2d(model, show_grid=False, show_labels=False, annotate_dims=False) fig.savefig(filename, bbox_inches='tight') plt.close(fig) print('Saved', filename) def export_dxf(filename='plan.dxf'): if not EZDXF_AVAILABLE: print('ezdxf غير متاح — ثبت المكتبة عبر pip إذا رغبت في التصدير إلى DXF') return doc = ezdxf.new('R2010') msp = doc.modelspace() for w in model.walls: msp.add_line((w.x1,w.y1),(w.x2,w.y2)) for d in model.doors: msp.add_point((d.x,d.y)) for win in model.windows: msp.add_point((win.x, win.y)) doc.saveas(filename) print('Saved', filename) def export_obj(filename='model.obj',height=3.0): if TRIMESH_AVAILABLE: combined = None for room in model.rooms: mesh = extrude_room_to_mesh(room, height) if combined is None: combined = mesh else: try: combined = trimesh.util.concatenate([combined, mesh]) except Exception: pass if combined is not None: combined.export(filename) print('Saved', filename) else: print('trimesh غير متاح — تصدير OBJ يتطلب مكتبة trimesh') def export_ifc(filename='model.ifc'): if not IFCO_AVAILABLE: print('ifcopenshell غير متاح — لا يمكن التصدير إلى IFC هنا') return # ملاحظة: إنشاء ملف IFC احترافي يتطلب معرفة بالـ schema؛ هنا مثال بسيط للغاية. ifc = ifcopenshell.file() print('تم إنشاء ملف IF C افتراضي (غير مكتمل).') # ... المزيد من العمل مطلوب لبناء نموذج IFC صالح ifc.write(filename) print('Saved', filename) # %% # ---------- Sun path and daylight (تقريبي) ---------- def sun_position(latitude, longitude, date, hour): """حساب زاوية الشمس (ارتفاع/زاوية) تقريبي جدًا (حوالي) - يستخدم صيغة مبسطة. - date: (year,month,day) - hour: ساعة عشرية (0-24) يرجع (azimuth_deg, altitude_deg) """ # تحويل إلى يوم السنة y,m,d = date dt = np.datetime64(f'{y:04d}-{m:02d}-{d:02d}') doy = int(str(dt.astype('datetime64[D]') - np.datetime64(f'{y:04d}-01-01'))[:2]) + 1 if False else (np.datetime64(f'{y:04d}-{m:02d}-{d:02d}') - np.datetime64(f'{y:04d}-01-01')).astype(int) + 1 # declination approx decl = 23.44 * math.sin(math.radians(360*(284 + doy)/365)) # hour angle solar_time = hour + longitude/15.0 H = 15*(solar_time - 12) lat = latitude # altitude alt = math.degrees(math.asin(math.sin(math.radians(lat))*math.sin(math.radians(decl)) + math.cos(math.radians(lat))*math.cos(math.radians(decl))*math.cos(math.radians(H)))) az = math.degrees(math.atan2(math.sin(math.radians(H)), math.cos(math.radians(H))*math.sin(math.radians(lat)) - math.tan(math.radians(decl))*math.cos(math.radians(lat)))) return az, alt # illuminated area simple approximation: cast rays from sun and cut polygons (requires shapely) def compute_sun_illumination(model, latitude, longitude, date, hour): if not SHAPELY_AVAILABLE: print('Shapely غير متاحة — لا يمكن حساب الإضاءة بدقة') return None az, alt = sun_position(latitude, longitude, date, hour) # سنبسط: إذا كانت زاوية الارتفاع < 0 فالنهار غروب if alt <= 0: return None # نُرجع فقط زاوية وارتفاع return {'azimuth': az, 'altitude': alt} # %% # ---------- Simple UI: نجمع كل شيء في تبويبات ---------- def reset_model(): global model model = BuildingModel() # UI callbacks def build_sample_model(): reset_model() add_grid_to_model(2,3,4.0,3.0,wall_thickness=0.2,gap=0.1) # أبواب ونوافذ افتراضية model.add_door(2.0, 0.0) model.add_window(1.0, 3.0) model.add_stair(6.5, 0.5, width=1.0, length=3.0) model.add_structural(4.0,1.5,'column',0.4,0.4) model.add_mep([(0.5,0.5),(2,0.5),(2,2)], system='electrical') # interactive controls rows_w = IntSlider(min=1,max=6,value=2,description='Rows') cols_w = IntSlider(min=1,max=6,value=3,description='Cols') room_w_w = FloatSlider(min=2.0,max=8.0,value=4.0,step=0.25,description='Room W') room_h_w = FloatSlider(min=2.0,max=8.0,value=3.0,step=0.25,description='Room H') wall_thick_w = FloatSlider(min=0.05,max=0.6,value=0.2,step=0.01,description='Wall t') gap_w = FloatSlider(min=0.0,max=1.0,value=0.1,step=0.05,description='Gap') btn_build = Button(description='Build Grid') btn_export_svg = Button(description='Export SVG') btn_export_dxf = Button(description='Export DXF') btn_export_obj = Button(description='Export OBJ') btn_draw_2d = Button(description='Draw 2D') btn_draw_3d = Button(description='Draw 3D') btn_sample = Button(description='Load Sample Model') def on_build_clicked(b): reset_model() add_grid_to_model(rows_w.value, cols_w.value, room_w_w.value, room_h_w.value, wall_thickness=wall_thick_w.value, gap=gap_w.value) print('Grid built') btn_build.on_click(on_build_clicked) def on_sample(b): build_sample_model() print('Sample model loaded') btn_sample.on_click(on_sample) def on_export_svg(b): export_svg('plan.svg') btn_export_svg.on_click(on_export_svg) def on_export_dxf(b): export_dxf('plan.dxf') btn_export_dxf.on_click(on_export_dxf) def on_export_obj(b): export_obj('model.obj') btn_export_obj.on_click(on_export_obj) def on_draw2d(b): draw_plan_2d(model) btn_draw_2d.on_click(on_draw2d) def on_draw3d(b): draw_3d_model(model) btn_draw_3d.on_click(on_draw3d) # Build the UI layout controls_box = VBox([HBox([rows_w,cols_w]), HBox([room_w_w, room_h_w]), HBox([wall_thick_w,gap_w]), HBox([btn_build, btn_sample])]) exports_box = HBox([btn_export_svg, btn_export_dxf, btn_export_obj]) views_box = HBox([btn_draw_2d, btn_draw_3d]) ui_tab = Tab() ui_tab.children = [controls_box, views_box, exports_box] ui_tab.set_title(0, 'Create') ui_tab.set_title(1, 'View') ui_tab.set_title(2, 'Export') # Display UI from IPython.display import display print('Load complete — استخدم الواجهات لبناء/عرض/تصدير النماذج') display(ui_tab) # %% # ----------Notes and next steps---------- # - هذا الكود مصمم كمخطط تعليمي وقابل للتوسيع. لإضافة قدرات أقوى: # * استعمل مكتبات BIM متقدمة وواجهات CAD (FreeCAD, Blender, Rhino + Grasshopper) # * استخدم ifcopenshell لإنشاء IFC احترافي (يتطلب فهم الـ schema) # * استخدم مكتبة OpenCASCADE أو OCC-bindings للتعامل مع عمليات CAD ثلاثية الأبعاد متقدمة # - إن أردت أقوم بتخصيص هذا الكود لإضافة: أبواب قابلة للفتح هندسياً، فتحات نوافذ أوتوماتيكية، قواعد إنشائية حسابية، أو واجهة تصدير DXF مفصلة. فقط اطلب أي جزء تريد تطويره أولاً.