บทที่ 12 ภาคผนวก
Glossary (คำศัพท์)
| คำ | อธิบาย |
|---|---|
| COM | Component Object Model — เทคโนโลยี Windows ให้ process สื่อสารกัน |
| comtypes | Python library สำหรับเรียก COM objects |
| ProgID | ชื่อ string ที่ Windows ใช้ค้นหา COM object ที่ register ไว้ |
| GetActiveObject | เชื่อม COM object ที่เปิดอยู่แล้ว |
| CreateObject | สร้าง COM object (และ process) ใหม่ |
| SapModel | COM object หลักที่ใช้เข้าถึง ETABS model data |
| Load Case | กรณีแรงจริง เช่น DEAD, LIVE, EQX |
| Load Combination | การรวม Load Case ตามมาตรฐาน เช่น 1.2D+1.6L |
| ret | Return code จาก API call (0 = สำเร็จ) |
| dataclass | Python decorator สร้าง class สำหรับเก็บข้อมูลอัตโนมัติ |
| context manager | Pattern ใช้ with statement สำหรับ setup/cleanup อัตโนมัติ |
| venv | Virtual environment — แยก Python packages ตามโปรเจกต์ |
| pandas | Library สำหรับจัดการข้อมูลตาราง (DataFrame) |
| gc.collect() | บังคับ Python garbage collector เก็บ memory ทันที |
| pytest | Testing framework สำหรับ Python |
| Dynamic Dispatch | comtypes เรียก COM methods แบบ runtime (ไม่มี compile-time check) |
| STA | Single-Threaded Apartment — COM threading model (ห้ามใช้ข้าม thread) |
API Quick Reference (Python)
Connection
import comtypes.client
# Attach to running ETABSetabs = comtypes.client.GetActiveObject("CSI.ETABS.API.ETABSObject")
# Create new ETABSetabs = comtypes.client.CreateObject("CSI.ETABS.API.ETABSObject")etabs.ApplicationStart()
# Access SapModelsap_model = etabs.SapModelModel Info
sap_model.GetModelFilename() # → strsap_model.GetPresentUnits() # → intsap_model.SetPresentUnits(6) # 6 = kN_m_Csap_model.SetModelIsLocked(False) # Unlock for editingRead Element Names
ret, count, names = sap_model.FrameObj.GetNameList()ret, count, names = sap_model.PointObj.GetNameList()ret, count, names = sap_model.AreaObj.GetNameList()Read Properties
ret, x, y, z = sap_model.PointObj.GetCoordCartesian("1")ret, section, auto = sap_model.FrameObj.GetSection("B1")ret, count, names = sap_model.PropFrame.GetNameList()ret, count, names = sap_model.PropMaterial.GetNameList()Stories & Load Patterns
ret, count, names, elevs, heights, *rest = sap_model.Story.GetStories_2()ret, count, names, types, sw = sap_model.LoadPatterns.GetNameList()ret, count, names = sap_model.LoadCases.GetNameList()ret, count, names = sap_model.RespCombo.GetNameList()Analysis
sap_model.Analyze.RunAnalysis()sap_model.Results.Setup.DeselectAllCasesAndCombosForOutput()sap_model.Results.Setup.SetCaseSelectedForOutput("DEAD", True)sap_model.Results.Setup.SetComboSelectedForOutput("1.2D+1.6L", True)Results
ret, nr, *data = sap_model.Results.FrameForce("B1", 0)ret, nr, *data = sap_model.Results.JointDispl("1", 0)ret, nr, *data = sap_model.Results.StoryDrifts()ret, nr, *data = sap_model.Results.BaseReact()ret, nr, *data = sap_model.Results.AreaForceShell("F1", 0)Write Operations
sap_model.FrameObj.SetSection("B1", "W16X40")sap_model.FrameObj.AddByCoord(x1, y1, z1, x2, y2, z2, "", "W12X26")sap_model.PointObj.SetRestraint("1", [True, True, True, False, False, False])sap_model.LoadPatterns.Add("SDL", 1, 0, True)sap_model.FrameObj.SetLoadDistributed("B1", "LIVE", 1, 10, 0, 1, -10, -10, "Global", True, True)Cleanup
import gcdel sap_modeldel etabsgc.collect()Unit Systems
| ค่า | ชื่อ | Force | Length | Temperature |
|---|---|---|---|---|
| 1 | lb_in_F | lb | in | °F |
| 2 | lb_ft_F | lb | ft | °F |
| 3 | kip_in_F | kip | in | °F |
| 4 | kip_ft_F | kip | ft | °F |
| 5 | kN_mm_C | kN | mm | °C |
| 6 | kN_m_C | kN | m | °C |
| 7 | kgf_mm_C | kgf | mm | °C |
| 8 | kgf_m_C | kgf | m | °C |
| 9 | N_mm_C | N | mm | °C |
| 10 | N_m_C | N | m | °C |
| 11 | Ton_mm_C | Ton | mm | °C |
| 12 | Ton_m_C | Ton | m | °C |
| 13 | kN_cm_C | kN | cm | °C |
| 14 | kgf_cm_C | kgf | cm | °C |
| 15 | N_cm_C | N | cm | °C |
| 16 | Ton_cm_C | Ton | cm | °C |
Complete End-to-End Example
"""Complete ETABS API Example (Python)===================================Connect → Read → Analyze → Export Results
ต้องเปิด ETABS พร้อมโมเดลก่อนรัน"""import comtypes.clientimport pandas as pdimport gcfrom pathlib import Path
def check(ret: int, name: str): if ret != 0: raise RuntimeError(f"❌ {name} failed (ret={ret})")
def main(): etabs = None sap_model = None
try: # ═══════ 1. CONNECT ═══════ print("═══════════════════════════════════════") print(" ETABS API — Python Complete Example ") print("═══════════════════════════════════════")
etabs = comtypes.client.GetActiveObject( "CSI.ETABS.API.ETABSObject" ) sap_model = etabs.SapModel
if sap_model is None: print("❌ ไม่สามารถเข้าถึง SapModel") return
model_file = sap_model.GetModelFilename() print(f"\n📁 Model: {model_file}")
# ═══════ 2. SET UNITS ═══════ sap_model.SetPresentUnits(6) # kN_m_C print("📐 Units: kN_m_C")
# ═══════ 3. READ MODEL ═══════ ret, frame_count, frame_names = sap_model.FrameObj.GetNameList() check(ret, "FrameObj.GetNameList") print(f"\n🏗️ Frames: {frame_count}")
ret, joint_count, point_names = sap_model.PointObj.GetNameList() check(ret, "PointObj.GetNameList") print(f"📍 Joints: {joint_count}")
# ═══════ 4. ANALYZE ═══════ print("\n⏳ Running analysis...") ret = sap_model.Analyze.RunAnalysis() check(ret, "RunAnalysis") print("✅ Analysis completed")
# ═══════ 5. SETUP OUTPUT ═══════ sap_model.Results.Setup.DeselectAllCasesAndCombosForOutput() sap_model.Results.Setup.SetCaseSelectedForOutput("DEAD", True)
# ═══════ 6. READ RESULTS ═══════ rows = [] for name in frame_names: ret, nr, _obj, obj_sta, _elm, _es, \ load_case, _st, _sn, \ p_vals, v2_vals, _v3, _t, _m2, m3_vals = \ sap_model.Results.FrameForce(name, 0)
if ret != 0 or nr == 0: continue
for i in range(nr): rows.append({ "Frame": name, "Station": obj_sta[i], "LoadCase": load_case[i], "P": p_vals[i], "V2": v2_vals[i], "M3": m3_vals[i], })
df = pd.DataFrame(rows) print(f"\n📊 Total results: {len(df)}") print(df.head(10).to_string(index=False))
# ═══════ 7. EXPORT ═══════ output_dir = Path("output") output_dir.mkdir(exist_ok=True)
csv_path = output_dir / "frame_forces.csv" df.to_csv(csv_path, index=False) print(f"\n✅ Export: {csv_path}")
# Story Drift ret, nr_drift, d_story, d_case, d_dir, d_drift, *_ = \ sap_model.Results.StoryDrifts()
if ret == 0 and nr_drift > 0: df_drift = pd.DataFrame({ "Story": d_story, "Case": d_case, "Direction": d_dir, "Drift": d_drift, }) drift_path = output_dir / "story_drifts.csv" df_drift.to_csv(drift_path, index=False) print(f"✅ Export: {drift_path}")
print("\n═══════════════════════════════════════") print(" ALL DONE! ") print("═══════════════════════════════════════")
except OSError as e: print(f"❌ COM Error: {e}") print(" → ตรวจสอบว่า ETABS เปิดอยู่") except Exception as e: print(f"❌ Error: {e}") finally: # ═══════ 8. CLEANUP ═══════ if sap_model is not None: del sap_model if etabs is not None: del etabs gc.collect() print("\n🧹 Cleanup complete")
if __name__ == "__main__": main()What’s Next
หลังจบหนังสือเล่มนี้แล้ว สามารถต่อยอดได้:
| หัวข้อ | รายละเอียด |
|---|---|
| pandas ขั้นสูง | Pivot table, groupby สำหรับรายงาน |
| matplotlib / plotly | สร้างกราฟ force diagram, drift plot |
| FastAPI / Streamlit | สร้าง web dashboard แสดงผล ETABS data |
| Jupyter + widgets | Interactive notebook สำหรับ parametric study |
| Database | เก็บผลลัพธ์ใน SQLite/PostgreSQL สำหรับ tracking |
| CI/CD | GitHub Actions สำหรับ automated testing |
| win32com | ทางเลือกแทน comtypes สำหรับ COM interop (อ่านเพิ่มเติมด้านล่าง) |
| pyautogui | Auto-click ใน ETABS GUI (เมื่อ API ไม่ครอบคลุม) |
Cookbook: สร้าง Model จากศูนย์ (Model from Scratch)
ตัวอย่างการสร้างโครงสร้างคอนกรีตเสริมเหล็ก (Frame + Shell) ตั้งแต่เริ่มต้น:
"""Create Model from Scratch Exampleสร้าง Grid, Define Material, Draw Frame/Area, Assign Load"""import comtypes.client
def create_model(): # 1. Connect new instance etabs = comtypes.client.CreateObject("CSI.ETABS.API.ETABSObject") etabs.ApplicationStart() model = etabs.SapModel
# 2. Init New Model (kN-m units) ret = model.InitializeNewModel(6)
# 3. Create Grid (4x4 bays, 3 stories) ret = model.File.NewGridOnly(4, 12, 12, 4, 12, 12, 3, 3)
# 4. Define Material (Concrete 30MPa) # AddMaterial(Name, Type, Region, Standard, Grade) ret, mat_name = model.PropMaterial.AddMaterial( "C30", 2, "United States", "ACI 318-14", "4000Psi" )
# 5. Define Frame Sections (Rectangular) # SetRectangle(Name, Mat, Depth, Width) ret = model.PropFrame.SetRectangle("C40x40", "C30", 0.4, 0.4) ret = model.PropFrame.SetRectangle("B30x60", "C30", 0.6, 0.3)
# 6. Define Area Section (Slab 20cm) # SetShell(Name, Type, Mat, Thickness...) ret = model.PropArea.SetShell( "S20", 1, "C30", 0, 0.2, 0.2 )
# 7. Draw Columns & Beams # (Simplified loop) # Columns at intersections for x in range(0, 48+1, 12): for y in range(0, 48+1, 12): ret, name = model.FrameObj.AddByCoord( x, y, 0, x, y, 3, "", "C40x40" )
# Beams (X-direction) for y in range(0, 48+1, 12): ret, name = model.FrameObj.AddByCoord( 0, y, 3, 48, y, 3, "", "B30x60" )
# 8. Draw Slab ret, name = model.AreaObj.AddByCoord( 4, [0, 48, 48, 0], [0, 0, 48, 48], [3, 3, 3, 3], "", "S20" )
# 9. Assign Supports (Pin at base) # Select joints at Z=0 -> SetRestraint
print("✅ Model created successfully!")
# run: create_model()Alternative: win32com
นอกจาก comtypes แล้ว Python ยังมี pywin32 (win32com.client) ที่ใช้ได้เหมือนกัน:
ติดตั้ง
pip install pywin32การใช้งาน
ต่างกันแค่ตอน connect และการจัดการ constants:
import win32com.client
# Connectetabs = win32com.client.GetActiveObject("CSI.ETABS.API.ETABSObject")sap_model = etabs.SapModel
# การเรียก method เหมือนกันret, count, names = sap_model.FrameObj.GetNameList()
# ข้อดี: บางคนว่าเสถียรกว่าในบาง environment# ข้อเสีย: Syntax ในการเข้าถึง Enum ต่างกันเล็กน้อย