- SPipe_server_2026-10-30.zip 내용을 풀어서 등록 (압축 파일은 올리지 않음) - README에 데모 v2 서버 업데이트 순서 추가, 저장소에 없는 문서 목차 정리 - 패치내역을 docs/로 이동, _worklog.md 추가 - 임시 파일(backend/temp.*)과 API 키가 든 파일은 제외
267 lines
10 KiB
Python
267 lines
10 KiB
Python
import ctypes
|
|
import os
|
|
|
|
|
|
class EN:
|
|
# EN_CountType
|
|
NODECOUNT = 0
|
|
LINKCOUNT = 2
|
|
|
|
# EN_NodeProperty
|
|
ELEVATION = 0
|
|
BASEDEMAND = 1
|
|
EMITTER = 3
|
|
TANKLEVEL = 8
|
|
DEMAND = 9
|
|
HEAD = 10
|
|
PRESSURE = 11
|
|
QUALITY = 12
|
|
# EN_LinkProperty
|
|
DIAMETER = 0
|
|
LENGTH = 1
|
|
ROUGHNESS = 2
|
|
INITSTATUS = 4
|
|
INITSETTING = 5
|
|
KWALL = 7
|
|
STATUS = 11
|
|
SETTING = 12
|
|
FLOW = 8
|
|
VELOCITY = 9
|
|
HEADLOSS = 10
|
|
ENERGY = 13
|
|
LINKQUAL = 14
|
|
PUMP_EFFIC = 17
|
|
# EN_InitHydOption
|
|
NOSAVE = 0
|
|
SAVE = 1
|
|
|
|
# EN_Option
|
|
PRESS_UNITS = 25
|
|
|
|
# EN_TimeParameter
|
|
DURATION = 0
|
|
HYDSTEP = 1
|
|
REPORTSTEP = 5
|
|
REPORTSTART = 6
|
|
|
|
# EN_TimeParameter (cont.)
|
|
PATTERNSTEP = 3
|
|
PATTERNSTART = 4
|
|
|
|
# EN_ControlType
|
|
TIMER = 2
|
|
|
|
# EN_QualityType / quality time step
|
|
QUAL_TRACE = 3
|
|
QUALSTEP = 2
|
|
|
|
# EN_Option (energy)
|
|
GLOBALEFFIC = 8
|
|
GLOBALPRICE = 9
|
|
GLOBALPATTERN = 10
|
|
DEMANDCHARGE = 11
|
|
|
|
# EN_LinkProperty (pump energy)
|
|
PUMP_ECOST = 21
|
|
PUMP_EPAT = 22
|
|
|
|
# EN_NodeType / EN_LinkType
|
|
JUNCTION = 0
|
|
RESERVOIR = 1
|
|
TANK = 2
|
|
CVPIPE = 0
|
|
PIPE = 1
|
|
PUMP = 2
|
|
|
|
FLOW_UNITS = {
|
|
0: "CFS", 1: "GPM", 2: "MGD", 3: "IMGD", 4: "AFD",
|
|
5: "LPS", 6: "LPM", 7: "MLD", 8: "CMH", 9: "CMD", 10: "CMS"
|
|
}
|
|
|
|
PRESS_UNITS = {
|
|
0: "psi", 1: "kPa", 2: "m", 3: "bar", 4: "ft"
|
|
}
|
|
QUAL_TYPES = {
|
|
0: "NONE", 1: "CHEMICAL", 2: "AGE", 3: "TRACE"
|
|
}
|
|
|
|
|
|
class EpanetException(Exception):
|
|
pass
|
|
|
|
class EpanetAPI:
|
|
def __init__(self):
|
|
library_name = "epanet2.dll" if os.name == "nt" else "libepanet2.so"
|
|
dll_path = os.environ.get("EPANET_LIBRARY_PATH") or os.path.join(os.path.dirname(os.path.dirname(__file__)), "64bit", library_name)
|
|
if not os.path.exists(dll_path):
|
|
raise RuntimeError(f"DLL not found: {dll_path}")
|
|
|
|
self.lib = ctypes.CDLL(dll_path)
|
|
self.EN_Project = ctypes.c_void_p
|
|
self._setup()
|
|
|
|
def _setup(self):
|
|
def _errcheck(result, func, args):
|
|
if result > 10:
|
|
err_msg = ctypes.create_string_buffer(256)
|
|
self.lib.EN_geterror(result, err_msg, 256)
|
|
raise EpanetException(f"EPANET Error {result}: {err_msg.value.decode('utf-8').strip()}")
|
|
return result
|
|
|
|
self.lib.EN_geterror.argtypes = [ctypes.c_int, ctypes.c_char_p, ctypes.c_int]
|
|
self.lib.EN_geterror.restype = ctypes.c_int
|
|
|
|
self.lib.EN_getversion.argtypes = [ctypes.POINTER(ctypes.c_int)]
|
|
self.lib.EN_getversion.restype = ctypes.c_int
|
|
self.lib.EN_getversion.errcheck = _errcheck
|
|
|
|
self.lib.EN_createproject.argtypes = [ctypes.POINTER(self.EN_Project)]
|
|
self.lib.EN_createproject.restype = ctypes.c_int
|
|
self.lib.EN_createproject.errcheck = _errcheck
|
|
|
|
self.lib.EN_deleteproject.argtypes = [self.EN_Project]
|
|
self.lib.EN_deleteproject.restype = ctypes.c_int
|
|
self.lib.EN_deleteproject.errcheck = _errcheck
|
|
|
|
self.lib.EN_open.argtypes = [self.EN_Project, ctypes.c_char_p, ctypes.c_char_p, ctypes.c_char_p]
|
|
self.lib.EN_open.restype = ctypes.c_int
|
|
self.lib.EN_open.errcheck = _errcheck
|
|
|
|
self.lib.EN_close.argtypes = [self.EN_Project]
|
|
self.lib.EN_close.restype = ctypes.c_int
|
|
self.lib.EN_close.errcheck = _errcheck
|
|
|
|
self.lib.EN_getcount.argtypes = [self.EN_Project, ctypes.c_int, ctypes.POINTER(ctypes.c_int)]
|
|
self.lib.EN_getcount.restype = ctypes.c_int
|
|
self.lib.EN_getcount.errcheck = _errcheck
|
|
|
|
self.lib.EN_getnodeid.argtypes = [self.EN_Project, ctypes.c_int, ctypes.c_char_p]
|
|
self.lib.EN_getnodeid.restype = ctypes.c_int
|
|
self.lib.EN_getnodeid.errcheck = _errcheck
|
|
|
|
self.lib.EN_getnodetype.argtypes = [self.EN_Project, ctypes.c_int, ctypes.POINTER(ctypes.c_int)]
|
|
self.lib.EN_getnodetype.restype = ctypes.c_int
|
|
self.lib.EN_getnodetype.errcheck = _errcheck
|
|
|
|
self.lib.EN_getnodevalue.argtypes = [self.EN_Project, ctypes.c_int, ctypes.c_int, ctypes.POINTER(ctypes.c_double)]
|
|
self.lib.EN_getnodevalue.restype = ctypes.c_int
|
|
self.lib.EN_getnodevalue.errcheck = _errcheck
|
|
|
|
self.lib.EN_getcoord.argtypes = [self.EN_Project, ctypes.c_int, ctypes.POINTER(ctypes.c_double), ctypes.POINTER(ctypes.c_double)]
|
|
self.lib.EN_getcoord.restype = ctypes.c_int
|
|
self.lib.EN_getcoord.errcheck = _errcheck
|
|
|
|
self.lib.EN_getlinkid.argtypes = [self.EN_Project, ctypes.c_int, ctypes.c_char_p]
|
|
self.lib.EN_getlinkid.restype = ctypes.c_int
|
|
self.lib.EN_getlinkid.errcheck = _errcheck
|
|
|
|
self.lib.EN_getlinktype.argtypes = [self.EN_Project, ctypes.c_int, ctypes.POINTER(ctypes.c_int)]
|
|
self.lib.EN_getlinktype.restype = ctypes.c_int
|
|
self.lib.EN_getlinktype.errcheck = _errcheck
|
|
|
|
self.lib.EN_getlinknodes.argtypes = [self.EN_Project, ctypes.c_int, ctypes.POINTER(ctypes.c_int), ctypes.POINTER(ctypes.c_int)]
|
|
self.lib.EN_getlinknodes.restype = ctypes.c_int
|
|
self.lib.EN_getlinknodes.errcheck = _errcheck
|
|
|
|
self.lib.EN_getlinkvalue.argtypes = [self.EN_Project, ctypes.c_int, ctypes.c_int, ctypes.POINTER(ctypes.c_double)]
|
|
self.lib.EN_getlinkvalue.restype = ctypes.c_int
|
|
self.lib.EN_getlinkvalue.errcheck = _errcheck
|
|
|
|
self.lib.EN_getvertexcount.argtypes = [self.EN_Project, ctypes.c_int, ctypes.POINTER(ctypes.c_int)]
|
|
self.lib.EN_getvertexcount.restype = ctypes.c_int
|
|
self.lib.EN_getvertexcount.errcheck = _errcheck
|
|
|
|
self.lib.EN_getvertex.argtypes = [self.EN_Project, ctypes.c_int, ctypes.c_int, ctypes.POINTER(ctypes.c_double), ctypes.POINTER(ctypes.c_double)]
|
|
self.lib.EN_getvertex.restype = ctypes.c_int
|
|
self.lib.EN_getvertex.errcheck = _errcheck
|
|
|
|
self.lib.EN_getflowunits.argtypes = [self.EN_Project, ctypes.POINTER(ctypes.c_int)]
|
|
self.lib.EN_getflowunits.restype = ctypes.c_int
|
|
self.lib.EN_getflowunits.errcheck = _errcheck
|
|
|
|
self.lib.EN_getoption.argtypes = [self.EN_Project, ctypes.c_int, ctypes.POINTER(ctypes.c_double)]
|
|
self.lib.EN_getoption.restype = ctypes.c_int
|
|
self.lib.EN_getoption.errcheck = _errcheck
|
|
|
|
self.lib.EN_gettimeparam.argtypes = [self.EN_Project, ctypes.c_int, ctypes.POINTER(ctypes.c_long)]
|
|
self.lib.EN_gettimeparam.restype = ctypes.c_int
|
|
self.lib.EN_gettimeparam.errcheck = _errcheck
|
|
|
|
self.lib.EN_openH.argtypes = [self.EN_Project]
|
|
self.lib.EN_openH.restype = ctypes.c_int
|
|
self.lib.EN_openH.errcheck = _errcheck
|
|
|
|
self.lib.EN_initH.argtypes = [self.EN_Project, ctypes.c_int]
|
|
self.lib.EN_initH.restype = ctypes.c_int
|
|
self.lib.EN_initH.errcheck = _errcheck
|
|
|
|
self.lib.EN_runH.argtypes = [self.EN_Project, ctypes.POINTER(ctypes.c_long)]
|
|
self.lib.EN_runH.restype = ctypes.c_int
|
|
self.lib.EN_runH.errcheck = _errcheck
|
|
|
|
|
|
self.lib.EN_nextH.argtypes = [self.EN_Project, ctypes.POINTER(ctypes.c_long)]
|
|
self.lib.EN_nextH.restype = ctypes.c_int
|
|
self.lib.EN_nextH.errcheck = _errcheck
|
|
self.lib.EN_closeH.argtypes = [self.EN_Project]
|
|
self.lib.EN_closeH.restype = ctypes.c_int
|
|
self.lib.EN_closeH.errcheck = _errcheck
|
|
|
|
# Water Quality Analysis Bindings
|
|
self.lib.EN_openQ.argtypes = [self.EN_Project]
|
|
self.lib.EN_openQ.restype = ctypes.c_int
|
|
self.lib.EN_openQ.errcheck = _errcheck
|
|
|
|
self.lib.EN_initQ.argtypes = [self.EN_Project, ctypes.c_int]
|
|
self.lib.EN_initQ.restype = ctypes.c_int
|
|
self.lib.EN_initQ.errcheck = _errcheck
|
|
|
|
self.lib.EN_runQ.argtypes = [self.EN_Project, ctypes.POINTER(ctypes.c_long)]
|
|
self.lib.EN_runQ.restype = ctypes.c_int
|
|
self.lib.EN_runQ.errcheck = _errcheck
|
|
|
|
self.lib.EN_nextQ.argtypes = [self.EN_Project, ctypes.POINTER(ctypes.c_long)]
|
|
self.lib.EN_nextQ.restype = ctypes.c_int
|
|
self.lib.EN_nextQ.errcheck = _errcheck
|
|
|
|
self.lib.EN_closeQ.argtypes = [self.EN_Project]
|
|
self.lib.EN_closeQ.restype = ctypes.c_int
|
|
self.lib.EN_closeQ.errcheck = _errcheck
|
|
|
|
self.lib.EN_getqualinfo.argtypes = [
|
|
self.EN_Project,
|
|
ctypes.POINTER(ctypes.c_int),
|
|
ctypes.c_char_p,
|
|
ctypes.c_char_p,
|
|
ctypes.POINTER(ctypes.c_int),
|
|
]
|
|
self.lib.EN_getqualinfo.restype = ctypes.c_int
|
|
self.lib.EN_getqualinfo.errcheck = _errcheck
|
|
# ---- Bindings used by batch analyses (backend/batch.py) ----
|
|
P, I, D, L, S = self.EN_Project, ctypes.c_int, ctypes.c_double, ctypes.c_long, ctypes.c_char_p
|
|
|
|
def bind(name, argtypes):
|
|
fn = getattr(self.lib, name)
|
|
fn.argtypes = argtypes
|
|
fn.restype = ctypes.c_int
|
|
fn.errcheck = _errcheck
|
|
|
|
bind("EN_getnodeindex", [P, S, ctypes.POINTER(I)])
|
|
bind("EN_getlinkindex", [P, S, ctypes.POINTER(I)])
|
|
bind("EN_setnodevalue", [P, I, I, D])
|
|
bind("EN_setlinkvalue", [P, I, I, D])
|
|
bind("EN_settimeparam", [P, I, L])
|
|
bind("EN_addpattern", [P, S])
|
|
bind("EN_getpatternindex", [P, S, ctypes.POINTER(I)])
|
|
bind("EN_setpattern", [P, I, ctypes.POINTER(D), I])
|
|
bind("EN_adddemand", [P, I, D, S, S])
|
|
bind("EN_getnumdemands", [P, I, ctypes.POINTER(I)])
|
|
bind("EN_deletedemand", [P, I, I])
|
|
bind("EN_addcontrol", [P, I, I, D, I, D, ctypes.POINTER(I)])
|
|
bind("EN_deletecontrol", [P, I])
|
|
bind("EN_solveH", [P])
|
|
bind("EN_setqualtype", [P, I, S, S, S])
|
|
bind("EN_getpatternlen", [P, I, ctypes.POINTER(I)])
|
|
bind("EN_getpatternvalue", [P, I, I, ctypes.POINTER(D)])
|
|
bind("EN_getcontrol", [P, I, ctypes.POINTER(I), ctypes.POINTER(I), ctypes.POINTER(D), ctypes.POINTER(I), ctypes.POINTER(D)])
|